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