TestStackWalk.cpp (10223B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // The header under test. 8 #include "mozilla/StackWalk.h" 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Attributes.h" 12 13 #include <algorithm> 14 15 #include "gtest/gtest.h" 16 17 MOZ_EXPORT bool gStackWalkTesterDummy = true; 18 19 struct StackWalkTester; 20 21 // Descriptor of the recursive function calls wanted, and for each of them 22 // whether to perform tail call optimization or not. 23 struct CallInfo { 24 int (*mFunc)(int aDepth, int aLastSkipped, int aIgnored, 25 StackWalkTester& aTester); 26 bool mTailCall; 27 28 bool TailCall() { 29 #if defined(__i386__) || defined(MOZ_CODE_COVERAGE) 30 // We can't make tail calls happen on i386 because all arguments to 31 // functions are on the stack, so the stack pointer needs to be updated 32 // before the call and restored after the call, so tail call optimization 33 // never happens. 34 // Similarly, code-coverage flags don't guarantee that tail call 35 // optimization will happen. 36 return false; 37 #else 38 return mTailCall; 39 #endif 40 } 41 }; 42 43 struct PCRange { 44 void* mStart; 45 void* mEnd; 46 }; 47 48 // PCRange pretty printer for gtest assertions. 49 std::ostream& operator<<(std::ostream& aStream, const PCRange& aRange) { 50 aStream << aRange.mStart; 51 aStream << "-"; 52 aStream << aRange.mEnd; 53 return aStream; 54 } 55 56 // Allow to use EXPECT_EQ with a vector of PCRanges and a vector of plain 57 // addresses, allowing a more useful output when the test fails, showing 58 // both lists. 59 bool operator==(const std::vector<PCRange>& aRanges, 60 const std::vector<void*>& aPtrs) { 61 if (aRanges.size() != aPtrs.size()) { 62 return false; 63 } 64 for (size_t i = 0; i < aRanges.size(); i++) { 65 auto range = aRanges[i]; 66 auto ptr = reinterpret_cast<uintptr_t>(aPtrs[i]); 67 if (ptr <= reinterpret_cast<uintptr_t>(range.mStart) || 68 ptr >= reinterpret_cast<uintptr_t>(range.mEnd)) { 69 return false; 70 } 71 } 72 return true; 73 } 74 75 struct StackWalkTester { 76 // Description of the recursion of functions to perform for the testcase. 77 std::vector<CallInfo> mFuncCalls; 78 // Collection of PCs reported by MozStackWalk. 79 std::vector<void*> mFramePCs; 80 // Collection of PCs expected per what was observed while recursing. 81 std::vector<PCRange> mExpectedFramePCs; 82 // The aFirstFramePC value that will be passed to MozStackWalk. 83 void* mFirstFramePC = nullptr; 84 85 // Callback to be given to the stack walker. 86 // aClosure should point at an instance of this class. 87 static void StackWalkCallback(uint32_t aFrameNumber, void* aPC, void* aSP, 88 void* aClosure) { 89 ASSERT_NE(aClosure, nullptr); 90 StackWalkTester& tester = *reinterpret_cast<StackWalkTester*>(aClosure); 91 tester.mFramePCs.push_back(aPC); 92 EXPECT_EQ(tester.mFramePCs.size(), size_t(aFrameNumber)) 93 << "Frame number doesn't match"; 94 } 95 96 // Callers of this function get a range of addresses with: 97 // ``` 98 // label: 99 // recursion(); 100 // AddExpectedPC(&&label); 101 // ``` 102 // This intends to record the range from label to the return of AddExpectedPC. 103 // The ideal code would be: 104 // ``` 105 // recursion(); 106 // label: 107 // AddExpectedPC(&&label); 108 // ``` 109 // and we wouldn't need to keep ranges. But while this works fine with Clang, 110 // GCC actually sometimes reorders code such the address received by 111 // AddExpectedPC is the address *before* the recursion. 112 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99784 Using a label before the 113 // recursion and CallerPC() from a function call after the recursion makes it 114 // less likely for things to go wrong. 115 MOZ_NEVER_INLINE void AddExpectedPC(void* aPC) { 116 mExpectedFramePCs.push_back({aPC, CallerPC()}); 117 } 118 119 // Function intended to be called in sequence for recursion. 120 // CallInfo lists are meant to contain a sequence of IntermediateCallback<1>, 121 // IntermediateCallback<2>, etc. 122 // aDepth is a counter of how deep the recursion has gone so far; 123 // aLastSkipped is the depth of the last frame we want skipped in the 124 // testcase; aIgnored is there to avoid the compiler merging both recursive 125 // function calls, which would prevent tail call optimization happening on one 126 // of them. aTester is the instance of this class for the testcase. 127 template <int Id> 128 MOZ_NEVER_INLINE MOZ_EXPORT static int IntermediateCallback( 129 int aDepth, int aLastSkipped, int aIgnored, StackWalkTester& aTester) { 130 auto& callInfo = aTester.mFuncCalls.at(aDepth + 1); 131 if (aDepth == aLastSkipped) { 132 aTester.mFirstFramePC = CallerPC(); 133 } 134 if (aTester.mFuncCalls.at(aDepth).TailCall()) { 135 return callInfo.mFunc(aDepth + 1, aLastSkipped, Id, aTester); 136 // Since we're doing a tail call, we're not expecting this frame appearing 137 // in the trace. 138 } 139 here: 140 callInfo.mFunc(aDepth + 1, aLastSkipped, Id + 1, aTester); 141 aTester.AddExpectedPC(&&here); 142 return 0; 143 } 144 145 #if defined(__clang__) 146 __attribute__((no_sanitize("function"))) 147 #endif 148 MOZ_NEVER_INLINE MOZ_EXPORT static void 149 LeafCallback(int aDepth, int aLastSkipped, int aIgnored, 150 StackWalkTester& aTester) { 151 if (aDepth == aLastSkipped) { 152 aTester.mFirstFramePC = CallerPC(); 153 } 154 if (aTester.mFuncCalls.at(aDepth).TailCall()) { 155 // For the same reason that we have the aIgnored argument on these 156 // callbacks, we need to avoid both MozStackWalk calls to be merged by the 157 // compiler, so we use different values of aMaxFrames for that. 158 return MozStackWalk(StackWalkTester::StackWalkCallback, 159 aTester.mFirstFramePC, 160 /*aMaxFrames*/ 19, &aTester); 161 // Since we're doing a tail call, we're not expecting this frame appearing 162 // in the trace. 163 } 164 here: 165 MozStackWalk(StackWalkTester::StackWalkCallback, aTester.mFirstFramePC, 166 /*aMaxFrames*/ 20, &aTester); 167 aTester.AddExpectedPC(&&here); 168 // Because we return nothing from this function, simply returning here would 169 // produce a tail-call optimization, which we explicitly don't want to 170 // happen. So we add a branch that depends on an extern value to prevent 171 // that from happening. 172 MOZ_RELEASE_ASSERT(gStackWalkTesterDummy); 173 } 174 175 explicit StackWalkTester(std::initializer_list<CallInfo> aFuncCalls) 176 : mFuncCalls(aFuncCalls) {} 177 178 // Dump a vector of PCRanges as WalkTheStack would, for test failure output. 179 // Only the end of the range is shown. Not ideal, but 180 // MozFormatCodeAddressDetails only knows to deal with one address at a time. 181 // The full ranges would be printed by EXPECT_EQ anyways. 182 static std::string DumpFrames(std::vector<PCRange>& aFramePCRanges) { 183 std::vector<void*> framePCs; 184 framePCs.reserve(aFramePCRanges.size()); 185 for (auto range : aFramePCRanges) { 186 framePCs.push_back(range.mEnd); 187 } 188 return DumpFrames(framePCs); 189 } 190 191 // Dump a vector of addresses as WalkTheStack would, for test failure output. 192 static std::string DumpFrames(std::vector<void*>& aFramePCs) { 193 size_t n = 0; 194 std::string result; 195 for (auto* framePC : aFramePCs) { 196 char buf[1024]; 197 MozCodeAddressDetails details; 198 result.append(" "); 199 n++; 200 if (MozDescribeCodeAddress(framePC, &details)) { 201 int length = 202 MozFormatCodeAddressDetails(buf, sizeof(buf), n, framePC, &details); 203 result.append(buf, std::min(length, (int)sizeof(buf) - 1)); 204 } else { 205 result.append("MozDescribeCodeAddress failed"); 206 } 207 result.append("\n"); 208 } 209 return result; 210 } 211 212 // Dump a description of the given test case. 213 static std::string DumpFuncCalls(std::vector<CallInfo>& aFuncCalls) { 214 std::string result; 215 for (auto funcCall : aFuncCalls) { 216 MozCodeAddressDetails details; 217 result.append(" "); 218 if (MozDescribeCodeAddress(reinterpret_cast<void*>(funcCall.mFunc), 219 &details)) { 220 result.append(details.function); 221 if (funcCall.TailCall()) { 222 result.append(" tail call"); 223 } 224 } else { 225 result.append("MozDescribeCodeAddress failed"); 226 } 227 result.append("\n"); 228 } 229 return result; 230 } 231 232 MOZ_EXPORT MOZ_NEVER_INLINE void RunTest(int aLastSkipped) { 233 ASSERT_TRUE(aLastSkipped < (int)mFuncCalls.size()); 234 mFramePCs.clear(); 235 mExpectedFramePCs.clear(); 236 mFirstFramePC = nullptr; 237 auto& callInfo = mFuncCalls.at(0); 238 here: 239 callInfo.mFunc(0, aLastSkipped, 0, *this); 240 AddExpectedPC(&&here); 241 if (aLastSkipped < 0) { 242 aLastSkipped = mFuncCalls.size(); 243 } 244 for (int i = (int)mFuncCalls.size() - 1; i >= aLastSkipped; i--) { 245 if (!mFuncCalls.at(i).TailCall()) { 246 mExpectedFramePCs.erase(mExpectedFramePCs.begin()); 247 } 248 } 249 mFramePCs.resize(std::min(mExpectedFramePCs.size(), mFramePCs.size())); 250 EXPECT_EQ(mExpectedFramePCs, mFramePCs) 251 << "Expected frames:\n" 252 << DumpFrames(mExpectedFramePCs) << "Found frames:\n" 253 << DumpFrames(mFramePCs) 254 << "Function calls data (last skipped: " << aLastSkipped << "):\n" 255 << DumpFuncCalls(mFuncCalls); 256 } 257 }; 258 259 TEST(TestStackWalk, StackWalk) 260 { 261 const auto foo = StackWalkTester::IntermediateCallback<1>; 262 const auto bar = StackWalkTester::IntermediateCallback<2>; 263 const auto qux = StackWalkTester::IntermediateCallback<3>; 264 const auto leaf = reinterpret_cast<int (*)(int, int, int, StackWalkTester&)>( 265 StackWalkTester::LeafCallback); 266 267 const std::initializer_list<CallInfo> tests[] = { 268 {{foo, false}, {bar, false}, {qux, false}, {leaf, false}}, 269 {{foo, false}, {bar, true}, {qux, false}, {leaf, false}}, 270 {{foo, false}, {bar, false}, {qux, false}, {leaf, true}}, 271 {{foo, true}, {bar, false}, {qux, true}, {leaf, true}}, 272 }; 273 for (auto test : tests) { 274 StackWalkTester tester(test); 275 for (int i = -1; i < (int)test.size(); i++) { 276 tester.RunTest(i); 277 } 278 } 279 }