/src/mozilla-central/mozglue/misc/StackWalk.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | /* API for getting a stack trace of the C/C++ stack on the current thread */ |
8 | | |
9 | | #include "mozilla/ArrayUtils.h" |
10 | | #include "mozilla/Assertions.h" |
11 | | #include "mozilla/IntegerPrintfMacros.h" |
12 | | #include "mozilla/StackWalk.h" |
13 | | |
14 | | #include <string.h> |
15 | | |
16 | | using namespace mozilla; |
17 | | |
18 | | // for _Unwind_Backtrace from libcxxrt or libunwind |
19 | | // cxxabi.h from libcxxrt implicitly includes unwind.h first |
20 | | #if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) |
21 | | #define _GNU_SOURCE |
22 | | #endif |
23 | | |
24 | | #if defined(HAVE_DLOPEN) || defined(XP_DARWIN) |
25 | | #include <dlfcn.h> |
26 | | #endif |
27 | | |
28 | | #if (defined(XP_DARWIN) && \ |
29 | | (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) |
30 | | #define MOZ_STACKWALK_SUPPORTS_MACOSX 1 |
31 | | #else |
32 | | #define MOZ_STACKWALK_SUPPORTS_MACOSX 0 |
33 | | #endif |
34 | | |
35 | | #if (defined(linux) && \ |
36 | | ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ |
37 | | defined(HAVE__UNWIND_BACKTRACE))) |
38 | | #define MOZ_STACKWALK_SUPPORTS_LINUX 1 |
39 | | #else |
40 | | #define MOZ_STACKWALK_SUPPORTS_LINUX 0 |
41 | | #endif |
42 | | |
43 | | #if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) |
44 | | #define HAVE___LIBC_STACK_END 1 |
45 | | #else |
46 | | #define HAVE___LIBC_STACK_END 0 |
47 | | #endif |
48 | | |
49 | | #if HAVE___LIBC_STACK_END |
50 | | extern MOZ_EXPORT void* __libc_stack_end; // from ld-linux.so |
51 | | #endif |
52 | | |
53 | | #ifdef ANDROID |
54 | | #include <algorithm> |
55 | | #include <unistd.h> |
56 | | #include <pthread.h> |
57 | | #endif |
58 | | |
59 | | #if MOZ_STACKWALK_SUPPORTS_WINDOWS |
60 | | |
61 | | #include <windows.h> |
62 | | #include <process.h> |
63 | | #include <stdio.h> |
64 | | #include <malloc.h> |
65 | | #include "mozilla/ArrayUtils.h" |
66 | | #include "mozilla/Atomics.h" |
67 | | #include "mozilla/StackWalk_windows.h" |
68 | | #include "mozilla/WindowsVersion.h" |
69 | | |
70 | | #include <imagehlp.h> |
71 | | // We need a way to know if we are building for WXP (or later), as if we are, we |
72 | | // need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. |
73 | | // A value of 9 indicates we want to use the new APIs. |
74 | | #if API_VERSION_NUMBER < 9 |
75 | | #error Too old imagehlp.h |
76 | | #endif |
77 | | |
78 | | struct WalkStackData |
79 | | { |
80 | | // Are we walking the stack of the calling thread? Note that we need to avoid |
81 | | // calling fprintf and friends if this is false, in order to avoid deadlocks. |
82 | | bool walkCallingThread; |
83 | | uint32_t skipFrames; |
84 | | HANDLE thread; |
85 | | HANDLE process; |
86 | | HANDLE eventStart; |
87 | | HANDLE eventEnd; |
88 | | void** pcs; |
89 | | uint32_t pc_size; |
90 | | uint32_t pc_count; |
91 | | uint32_t pc_max; |
92 | | void** sps; |
93 | | uint32_t sp_size; |
94 | | uint32_t sp_count; |
95 | | CONTEXT* context; |
96 | | }; |
97 | | |
98 | | CRITICAL_SECTION gDbgHelpCS; |
99 | | |
100 | | #ifdef _M_AMD64 |
101 | | // Because various Win64 APIs acquire function-table locks, we need a way of |
102 | | // preventing stack walking while those APIs are being called. Otherwise, the |
103 | | // stack walker may suspend a thread holding such a lock, and deadlock when the |
104 | | // stack unwind code attempts to wait for that lock. |
105 | | // |
106 | | // We're using an atomic counter rather than a critical section because we |
107 | | // don't require mutual exclusion with the stack walker. If the stack walker |
108 | | // determines that it's safe to start unwinding the suspended thread (i.e. |
109 | | // there are no suppressions when the unwind begins), then it's safe to |
110 | | // continue unwinding that thread even if other threads request suppressions |
111 | | // in the meantime, because we can't deadlock with those other threads. |
112 | | // |
113 | | // XXX: This global variable is a larger-than-necessary hammer. A more scoped |
114 | | // solution would be to maintain a counter per thread, but then it would be |
115 | | // more difficult for WalkStackMain64 to read the suspended thread's counter. |
116 | | static Atomic<size_t> sStackWalkSuppressions; |
117 | | |
118 | | MFBT_API |
119 | | AutoSuppressStackWalking::AutoSuppressStackWalking() |
120 | | { |
121 | | ++sStackWalkSuppressions; |
122 | | } |
123 | | |
124 | | MFBT_API |
125 | | AutoSuppressStackWalking::~AutoSuppressStackWalking() |
126 | | { |
127 | | --sStackWalkSuppressions; |
128 | | } |
129 | | |
130 | | static uint8_t* sJitCodeRegionStart; |
131 | | static size_t sJitCodeRegionSize; |
132 | | uint8_t* sMsMpegJitCodeRegionStart; |
133 | | size_t sMsMpegJitCodeRegionSize; |
134 | | |
135 | | MFBT_API void |
136 | | RegisterJitCodeRegion(uint8_t* aStart, size_t aSize) |
137 | | { |
138 | | // Currently we can only handle one JIT code region at a time |
139 | | MOZ_RELEASE_ASSERT(!sJitCodeRegionStart); |
140 | | |
141 | | sJitCodeRegionStart = aStart; |
142 | | sJitCodeRegionSize = aSize; |
143 | | } |
144 | | |
145 | | MFBT_API void |
146 | | UnregisterJitCodeRegion(uint8_t* aStart, size_t aSize) |
147 | | { |
148 | | // Currently we can only handle one JIT code region at a time |
149 | | MOZ_RELEASE_ASSERT(sJitCodeRegionStart && |
150 | | sJitCodeRegionStart == aStart && |
151 | | sJitCodeRegionSize == aSize); |
152 | | |
153 | | sJitCodeRegionStart = nullptr; |
154 | | sJitCodeRegionSize = 0; |
155 | | } |
156 | | |
157 | | #endif // _M_AMD64 |
158 | | |
159 | | // Routine to print an error message to standard error. |
160 | | static void |
161 | | PrintError(const char* aPrefix) |
162 | | { |
163 | | LPSTR lpMsgBuf; |
164 | | DWORD lastErr = GetLastError(); |
165 | | FormatMessageA( |
166 | | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
167 | | nullptr, |
168 | | lastErr, |
169 | | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language |
170 | | (LPSTR)&lpMsgBuf, |
171 | | 0, |
172 | | nullptr |
173 | | ); |
174 | | fprintf(stderr, "### ERROR: %s: %s", |
175 | | aPrefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); |
176 | | fflush(stderr); |
177 | | LocalFree(lpMsgBuf); |
178 | | } |
179 | | |
180 | | static void |
181 | | InitializeDbgHelpCriticalSection() |
182 | | { |
183 | | static bool initialized = false; |
184 | | if (initialized) { |
185 | | return; |
186 | | } |
187 | | ::InitializeCriticalSection(&gDbgHelpCS); |
188 | | initialized = true; |
189 | | } |
190 | | |
191 | | static void |
192 | | WalkStackMain64(struct WalkStackData* aData) |
193 | | { |
194 | | // Get a context for the specified thread. |
195 | | CONTEXT context_buf; |
196 | | CONTEXT* context; |
197 | | if (!aData->context) { |
198 | | context = &context_buf; |
199 | | memset(context, 0, sizeof(CONTEXT)); |
200 | | context->ContextFlags = CONTEXT_FULL; |
201 | | if (aData->walkCallingThread) { |
202 | | ::RtlCaptureContext(context); |
203 | | } else if (!GetThreadContext(aData->thread, context)) { |
204 | | return; |
205 | | } |
206 | | } else { |
207 | | context = aData->context; |
208 | | } |
209 | | |
210 | | #if defined(_M_IX86) || defined(_M_IA64) || defined(_M_ARM64) |
211 | | // Setup initial stack frame to walk from. |
212 | | STACKFRAME64 frame64; |
213 | | memset(&frame64, 0, sizeof(frame64)); |
214 | | #ifdef _M_IX86 |
215 | | frame64.AddrPC.Offset = context->Eip; |
216 | | frame64.AddrStack.Offset = context->Esp; |
217 | | frame64.AddrFrame.Offset = context->Ebp; |
218 | | #elif defined _M_IA64 |
219 | | frame64.AddrPC.Offset = context->StIIP; |
220 | | frame64.AddrStack.Offset = context->SP; |
221 | | frame64.AddrFrame.Offset = context->RsBSP; |
222 | | #elif defined _M_ARM64 |
223 | | frame64.AddrPC.Offset = context->Pc; |
224 | | frame64.AddrStack.Offset = context->Sp; |
225 | | frame64.AddrFrame.Offset = context->Fp; |
226 | | #endif |
227 | | frame64.AddrPC.Mode = AddrModeFlat; |
228 | | frame64.AddrStack.Mode = AddrModeFlat; |
229 | | frame64.AddrFrame.Mode = AddrModeFlat; |
230 | | frame64.AddrReturn.Mode = AddrModeFlat; |
231 | | #endif |
232 | | |
233 | | #ifdef _M_AMD64 |
234 | | // If there are any active suppressions, then at least one thread (we don't |
235 | | // know which) is holding a lock that can deadlock RtlVirtualUnwind. Since |
236 | | // that thread may be the one that we're trying to unwind, we can't proceed. |
237 | | // |
238 | | // But if there are no suppressions, then our target thread can't be holding |
239 | | // a lock, and it's safe to proceed. By virtue of being suspended, the target |
240 | | // thread can't acquire any new locks during the unwind process, so we only |
241 | | // need to do this check once. After that, sStackWalkSuppressions can be |
242 | | // changed by other threads while we're unwinding, and that's fine because |
243 | | // we can't deadlock with those threads. |
244 | | if (sStackWalkSuppressions) { |
245 | | return; |
246 | | } |
247 | | #endif |
248 | | |
249 | | #ifdef _M_AMD64 |
250 | | bool firstFrame = true; |
251 | | #endif |
252 | | |
253 | | // Skip our own stack walking frames. |
254 | | int skip = (aData->walkCallingThread ? 3 : 0) + aData->skipFrames; |
255 | | |
256 | | // Now walk the stack. |
257 | | while (true) { |
258 | | DWORD64 addr; |
259 | | DWORD64 spaddr; |
260 | | |
261 | | #if defined(_M_IX86) || defined(_M_IA64) || defined(_M_ARM64) |
262 | | // 32-bit frame unwinding. |
263 | | // Debug routines are not threadsafe, so grab the lock. |
264 | | EnterCriticalSection(&gDbgHelpCS); |
265 | | BOOL ok = StackWalk64( |
266 | | #if defined _M_IA64 |
267 | | IMAGE_FILE_MACHINE_IA64, |
268 | | #elif defined _M_IX86 |
269 | | IMAGE_FILE_MACHINE_I386, |
270 | | #elif defined _M_ARM64 |
271 | | IMAGE_FILE_MACHINE_ARM64, |
272 | | #endif |
273 | | aData->process, |
274 | | aData->thread, |
275 | | &frame64, |
276 | | context, |
277 | | nullptr, |
278 | | SymFunctionTableAccess64, // function table access routine |
279 | | SymGetModuleBase64, // module base routine |
280 | | 0 |
281 | | ); |
282 | | LeaveCriticalSection(&gDbgHelpCS); |
283 | | |
284 | | if (ok) { |
285 | | addr = frame64.AddrPC.Offset; |
286 | | spaddr = frame64.AddrStack.Offset; |
287 | | } else { |
288 | | addr = 0; |
289 | | spaddr = 0; |
290 | | if (aData->walkCallingThread) { |
291 | | PrintError("WalkStack64"); |
292 | | } |
293 | | } |
294 | | |
295 | | if (!ok) { |
296 | | break; |
297 | | } |
298 | | |
299 | | #elif defined(_M_AMD64) |
300 | | // If we reach a frame in JIT code, we don't have enough information to |
301 | | // unwind, so we have to give up. |
302 | | if (sJitCodeRegionStart && |
303 | | (uint8_t*)context->Rip >= sJitCodeRegionStart && |
304 | | (uint8_t*)context->Rip < sJitCodeRegionStart + sJitCodeRegionSize) { |
305 | | break; |
306 | | } |
307 | | |
308 | | // We must also avoid msmpeg2vdec.dll's JIT region: they don't generate |
309 | | // unwind data, so their JIT unwind callback just throws up its hands and |
310 | | // terminates the process. |
311 | | if (sMsMpegJitCodeRegionStart && |
312 | | (uint8_t*)context->Rip >= sMsMpegJitCodeRegionStart && |
313 | | (uint8_t*)context->Rip < sMsMpegJitCodeRegionStart + sMsMpegJitCodeRegionSize) { |
314 | | break; |
315 | | } |
316 | | |
317 | | // 64-bit frame unwinding. |
318 | | // Try to look up unwind metadata for the current function. |
319 | | ULONG64 imageBase; |
320 | | PRUNTIME_FUNCTION runtimeFunction = |
321 | | RtlLookupFunctionEntry(context->Rip, &imageBase, NULL); |
322 | | |
323 | | if (runtimeFunction) { |
324 | | PVOID dummyHandlerData; |
325 | | ULONG64 dummyEstablisherFrame; |
326 | | RtlVirtualUnwind(UNW_FLAG_NHANDLER, |
327 | | imageBase, |
328 | | context->Rip, |
329 | | runtimeFunction, |
330 | | context, |
331 | | &dummyHandlerData, |
332 | | &dummyEstablisherFrame, |
333 | | nullptr); |
334 | | } else if (firstFrame) { |
335 | | // Leaf functions can be unwound by hand. |
336 | | context->Rip = *reinterpret_cast<DWORD64*>(context->Rsp); |
337 | | context->Rsp += sizeof(void*); |
338 | | } else { |
339 | | // Something went wrong. |
340 | | break; |
341 | | } |
342 | | |
343 | | addr = context->Rip; |
344 | | spaddr = context->Rsp; |
345 | | firstFrame = false; |
346 | | #else |
347 | | #error "unknown platform" |
348 | | #endif |
349 | | |
350 | | if (addr == 0) { |
351 | | break; |
352 | | } |
353 | | |
354 | | if (skip-- > 0) { |
355 | | continue; |
356 | | } |
357 | | |
358 | | if (aData->pc_count < aData->pc_size) { |
359 | | aData->pcs[aData->pc_count] = (void*)addr; |
360 | | } |
361 | | ++aData->pc_count; |
362 | | |
363 | | if (aData->sp_count < aData->sp_size) { |
364 | | aData->sps[aData->sp_count] = (void*)spaddr; |
365 | | } |
366 | | ++aData->sp_count; |
367 | | |
368 | | if (aData->pc_max != 0 && aData->pc_count == aData->pc_max) { |
369 | | break; |
370 | | } |
371 | | |
372 | | #if defined(_M_IX86) || defined(_M_IA64) |
373 | | if (frame64.AddrReturn.Offset == 0) { |
374 | | break; |
375 | | } |
376 | | #endif |
377 | | } |
378 | | } |
379 | | |
380 | | /** |
381 | | * Walk the stack, translating PC's found into strings and recording the |
382 | | * chain in aBuffer. For this to work properly, the DLLs must be rebased |
383 | | * so that the address in the file agrees with the address in memory. |
384 | | * Otherwise StackWalk will return FALSE when it hits a frame in a DLL |
385 | | * whose in memory address doesn't match its in-file address. |
386 | | */ |
387 | | |
388 | | MFBT_API void |
389 | | MozStackWalkThread(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
390 | | uint32_t aMaxFrames, void* aClosure, |
391 | | HANDLE aThread, CONTEXT* aContext) |
392 | | { |
393 | | static HANDLE myProcess = nullptr; |
394 | | HANDLE myThread; |
395 | | struct WalkStackData data; |
396 | | |
397 | | InitializeDbgHelpCriticalSection(); |
398 | | |
399 | | HANDLE targetThread = aThread; |
400 | | if (!aThread) { |
401 | | targetThread = ::GetCurrentThread(); |
402 | | data.walkCallingThread = true; |
403 | | } else { |
404 | | DWORD threadId = ::GetThreadId(aThread); |
405 | | DWORD currentThreadId = ::GetCurrentThreadId(); |
406 | | data.walkCallingThread = (threadId == currentThreadId); |
407 | | } |
408 | | |
409 | | // Have to duplicate handle to get a real handle. |
410 | | if (!myProcess) { |
411 | | if (!::DuplicateHandle(::GetCurrentProcess(), |
412 | | ::GetCurrentProcess(), |
413 | | ::GetCurrentProcess(), |
414 | | &myProcess, |
415 | | PROCESS_ALL_ACCESS, FALSE, 0)) { |
416 | | if (data.walkCallingThread) { |
417 | | PrintError("DuplicateHandle (process)"); |
418 | | } |
419 | | return; |
420 | | } |
421 | | } |
422 | | if (!::DuplicateHandle(::GetCurrentProcess(), |
423 | | targetThread, |
424 | | ::GetCurrentProcess(), |
425 | | &myThread, |
426 | | THREAD_ALL_ACCESS, FALSE, 0)) { |
427 | | if (data.walkCallingThread) { |
428 | | PrintError("DuplicateHandle (thread)"); |
429 | | } |
430 | | return; |
431 | | } |
432 | | |
433 | | data.skipFrames = aSkipFrames; |
434 | | data.thread = myThread; |
435 | | data.process = myProcess; |
436 | | void* local_pcs[1024]; |
437 | | data.pcs = local_pcs; |
438 | | data.pc_count = 0; |
439 | | data.pc_size = ArrayLength(local_pcs); |
440 | | data.pc_max = aMaxFrames; |
441 | | void* local_sps[1024]; |
442 | | data.sps = local_sps; |
443 | | data.sp_count = 0; |
444 | | data.sp_size = ArrayLength(local_sps); |
445 | | data.context = aContext; |
446 | | |
447 | | WalkStackMain64(&data); |
448 | | |
449 | | if (data.pc_count > data.pc_size) { |
450 | | data.pcs = (void**)_alloca(data.pc_count * sizeof(void*)); |
451 | | data.pc_size = data.pc_count; |
452 | | data.pc_count = 0; |
453 | | data.sps = (void**)_alloca(data.sp_count * sizeof(void*)); |
454 | | data.sp_size = data.sp_count; |
455 | | data.sp_count = 0; |
456 | | WalkStackMain64(&data); |
457 | | } |
458 | | |
459 | | ::CloseHandle(myThread); |
460 | | |
461 | | for (uint32_t i = 0; i < data.pc_count; ++i) { |
462 | | (*aCallback)(i + 1, data.pcs[i], data.sps[i], aClosure); |
463 | | } |
464 | | } |
465 | | |
466 | | MFBT_API void |
467 | | MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
468 | | uint32_t aMaxFrames, void* aClosure) |
469 | | { |
470 | | MozStackWalkThread(aCallback, aSkipFrames, aMaxFrames, aClosure, |
471 | | nullptr, nullptr); |
472 | | } |
473 | | |
474 | | static BOOL CALLBACK |
475 | | callbackEspecial64( |
476 | | PCSTR aModuleName, |
477 | | DWORD64 aModuleBase, |
478 | | ULONG aModuleSize, |
479 | | PVOID aUserContext) |
480 | | { |
481 | | BOOL retval = TRUE; |
482 | | DWORD64 addr = *(DWORD64*)aUserContext; |
483 | | |
484 | | /* |
485 | | * You'll want to control this if we are running on an |
486 | | * architecture where the addresses go the other direction. |
487 | | * Not sure this is even a realistic consideration. |
488 | | */ |
489 | | const BOOL addressIncreases = TRUE; |
490 | | |
491 | | /* |
492 | | * If it falls in side the known range, load the symbols. |
493 | | */ |
494 | | if (addressIncreases |
495 | | ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize)) |
496 | | : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize)) |
497 | | ) { |
498 | | retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, |
499 | | (PSTR)aModuleName, nullptr, |
500 | | aModuleBase, aModuleSize); |
501 | | if (!retval) { |
502 | | PrintError("SymLoadModule64"); |
503 | | } |
504 | | } |
505 | | |
506 | | return retval; |
507 | | } |
508 | | |
509 | | /* |
510 | | * SymGetModuleInfoEspecial |
511 | | * |
512 | | * Attempt to determine the module information. |
513 | | * Bug 112196 says this DLL may not have been loaded at the time |
514 | | * SymInitialize was called, and thus the module information |
515 | | * and symbol information is not available. |
516 | | * This code rectifies that problem. |
517 | | */ |
518 | | |
519 | | // New members were added to IMAGEHLP_MODULE64 (that show up in the |
520 | | // Platform SDK that ships with VC8, but not the Platform SDK that ships |
521 | | // with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to |
522 | | // use them, and it's useful to be able to function correctly with the |
523 | | // older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll |
524 | | // version 5.1.) Since Platform SDK version need not correspond to |
525 | | // compiler version, and the version number in debughlp.h was NOT bumped |
526 | | // when these changes were made, ifdef based on a constant that was |
527 | | // added between these versions. |
528 | | #ifdef SSRVOPT_SETCONTEXT |
529 | | #define NS_IMAGEHLP_MODULE64_SIZE (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / sizeof(DWORD64)) * sizeof(DWORD64)) |
530 | | #else |
531 | | #define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) |
532 | | #endif |
533 | | |
534 | | BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, |
535 | | PIMAGEHLP_MODULE64 aModuleInfo, |
536 | | PIMAGEHLP_LINE64 aLineInfo) |
537 | | { |
538 | | BOOL retval = FALSE; |
539 | | |
540 | | /* |
541 | | * Init the vars if we have em. |
542 | | */ |
543 | | aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; |
544 | | if (aLineInfo) { |
545 | | aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
546 | | } |
547 | | |
548 | | /* |
549 | | * Give it a go. |
550 | | * It may already be loaded. |
551 | | */ |
552 | | retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); |
553 | | if (retval == FALSE) { |
554 | | /* |
555 | | * Not loaded, here's the magic. |
556 | | * Go through all the modules. |
557 | | */ |
558 | | // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the |
559 | | // constness of the first parameter of |
560 | | // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from |
561 | | // non-const to const over time). See bug 391848 and bug |
562 | | // 415426. |
563 | | BOOL enumRes = EnumerateLoadedModules64( |
564 | | aProcess, |
565 | | (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, |
566 | | (PVOID)&aAddr); |
567 | | if (enumRes != FALSE) { |
568 | | /* |
569 | | * One final go. |
570 | | * If it fails, then well, we have other problems. |
571 | | */ |
572 | | retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); |
573 | | } |
574 | | } |
575 | | |
576 | | /* |
577 | | * If we got module info, we may attempt line info as well. |
578 | | * We will not report failure if this does not work. |
579 | | */ |
580 | | if (retval != FALSE && aLineInfo) { |
581 | | DWORD displacement = 0; |
582 | | BOOL lineRes = FALSE; |
583 | | lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo); |
584 | | if (!lineRes) { |
585 | | // Clear out aLineInfo to indicate that it's not valid |
586 | | memset(aLineInfo, 0, sizeof(*aLineInfo)); |
587 | | } |
588 | | } |
589 | | |
590 | | return retval; |
591 | | } |
592 | | |
593 | | static bool |
594 | | EnsureSymInitialized() |
595 | | { |
596 | | static bool gInitialized = false; |
597 | | bool retStat; |
598 | | |
599 | | if (gInitialized) { |
600 | | return gInitialized; |
601 | | } |
602 | | |
603 | | InitializeDbgHelpCriticalSection(); |
604 | | |
605 | | SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); |
606 | | retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE); |
607 | | if (!retStat) { |
608 | | PrintError("SymInitialize"); |
609 | | } |
610 | | |
611 | | gInitialized = retStat; |
612 | | /* XXX At some point we need to arrange to call SymCleanup */ |
613 | | |
614 | | return retStat; |
615 | | } |
616 | | |
617 | | |
618 | | MFBT_API bool |
619 | | MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) |
620 | | { |
621 | | aDetails->library[0] = '\0'; |
622 | | aDetails->loffset = 0; |
623 | | aDetails->filename[0] = '\0'; |
624 | | aDetails->lineno = 0; |
625 | | aDetails->function[0] = '\0'; |
626 | | aDetails->foffset = 0; |
627 | | |
628 | | if (!EnsureSymInitialized()) { |
629 | | return false; |
630 | | } |
631 | | |
632 | | HANDLE myProcess = ::GetCurrentProcess(); |
633 | | BOOL ok; |
634 | | |
635 | | // debug routines are not threadsafe, so grab the lock. |
636 | | EnterCriticalSection(&gDbgHelpCS); |
637 | | |
638 | | // |
639 | | // Attempt to load module info before we attempt to resolve the symbol. |
640 | | // This just makes sure we get good info if available. |
641 | | // |
642 | | |
643 | | DWORD64 addr = (DWORD64)aPC; |
644 | | IMAGEHLP_MODULE64 modInfo; |
645 | | IMAGEHLP_LINE64 lineInfo; |
646 | | BOOL modInfoRes; |
647 | | modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); |
648 | | |
649 | | if (modInfoRes) { |
650 | | strncpy(aDetails->library, modInfo.LoadedImageName, |
651 | | sizeof(aDetails->library)); |
652 | | aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; |
653 | | aDetails->loffset = (char*)aPC - (char*)modInfo.BaseOfImage; |
654 | | |
655 | | if (lineInfo.FileName) { |
656 | | strncpy(aDetails->filename, lineInfo.FileName, |
657 | | sizeof(aDetails->filename)); |
658 | | aDetails->filename[mozilla::ArrayLength(aDetails->filename) - 1] = '\0'; |
659 | | aDetails->lineno = lineInfo.LineNumber; |
660 | | } |
661 | | } |
662 | | |
663 | | ULONG64 buffer[(sizeof(SYMBOL_INFO) + |
664 | | MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; |
665 | | PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; |
666 | | pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
667 | | pSymbol->MaxNameLen = MAX_SYM_NAME; |
668 | | |
669 | | DWORD64 displacement; |
670 | | ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); |
671 | | |
672 | | if (ok) { |
673 | | strncpy(aDetails->function, pSymbol->Name, |
674 | | sizeof(aDetails->function)); |
675 | | aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; |
676 | | aDetails->foffset = static_cast<ptrdiff_t>(displacement); |
677 | | } |
678 | | |
679 | | LeaveCriticalSection(&gDbgHelpCS); // release our lock |
680 | | return true; |
681 | | } |
682 | | |
683 | | // i386 or PPC Linux stackwalking code |
684 | | #elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || MOZ_STACKWALK_SUPPORTS_LINUX || MOZ_STACKWALK_SUPPORTS_MACOSX) |
685 | | |
686 | | #include <stdlib.h> |
687 | | #include <string.h> |
688 | | #include <stdio.h> |
689 | | |
690 | | // On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed |
691 | | // if __USE_GNU is defined. I suppose its some kind of standards |
692 | | // adherence thing. |
693 | | // |
694 | | #if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) |
695 | | #define __USE_GNU |
696 | | #endif |
697 | | |
698 | | // This thing is exported by libstdc++ |
699 | | // Yes, this is a gcc only hack |
700 | | #if defined(MOZ_DEMANGLE_SYMBOLS) |
701 | | #include <cxxabi.h> |
702 | | #endif // MOZ_DEMANGLE_SYMBOLS |
703 | | |
704 | | void DemangleSymbol(const char* aSymbol, |
705 | | char* aBuffer, |
706 | | int aBufLen) |
707 | 0 | { |
708 | 0 | aBuffer[0] = '\0'; |
709 | 0 |
|
710 | | #if defined(MOZ_DEMANGLE_SYMBOLS) |
711 | | /* See demangle.h in the gcc source for the voodoo */ |
712 | | char* demangled = abi::__cxa_demangle(aSymbol, 0, 0, 0); |
713 | | |
714 | | if (demangled) { |
715 | | strncpy(aBuffer, demangled, aBufLen); |
716 | | aBuffer[aBufLen - 1] = '\0'; |
717 | | free(demangled); |
718 | | } |
719 | | #endif // MOZ_DEMANGLE_SYMBOLS |
720 | | } |
721 | | |
722 | | // {x86, ppc} x {Linux, Mac} stackwalking code. |
723 | | #if ((defined(__i386) || defined(PPC) || defined(__ppc__)) && \ |
724 | | (MOZ_STACKWALK_SUPPORTS_MACOSX || MOZ_STACKWALK_SUPPORTS_LINUX)) |
725 | | |
726 | | MFBT_API void |
727 | | MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
728 | | uint32_t aMaxFrames, void* aClosure) |
729 | | { |
730 | | // Get the frame pointer |
731 | | void** bp = (void**)__builtin_frame_address(0); |
732 | | |
733 | | void* stackEnd; |
734 | | #if HAVE___LIBC_STACK_END |
735 | | stackEnd = __libc_stack_end; |
736 | | #elif defined(XP_DARWIN) |
737 | | stackEnd = pthread_get_stackaddr_np(pthread_self()); |
738 | | #elif defined(ANDROID) |
739 | | pthread_attr_t sattr; |
740 | | pthread_attr_init(&sattr); |
741 | | pthread_getattr_np(pthread_self(), &sattr); |
742 | | void* stackBase = stackEnd = nullptr; |
743 | | size_t stackSize = 0; |
744 | | if (gettid() != getpid()) { |
745 | | // bionic's pthread_attr_getstack doesn't tell the truth for the main |
746 | | // thread (see bug 846670). So don't use it for the main thread. |
747 | | if (!pthread_attr_getstack(&sattr, &stackBase, &stackSize)) { |
748 | | stackEnd = static_cast<char*>(stackBase) + stackSize; |
749 | | } else { |
750 | | stackEnd = nullptr; |
751 | | } |
752 | | } |
753 | | if (!stackEnd) { |
754 | | // So consider the current frame pointer + an arbitrary size of 8MB |
755 | | // (modulo overflow ; not really arbitrary as it's the default stack |
756 | | // size for the main thread) if pthread_attr_getstack failed for |
757 | | // some reason (or was skipped). |
758 | | static const uintptr_t kMaxStackSize = 8 * 1024 * 1024; |
759 | | uintptr_t maxStackStart = uintptr_t(-1) - kMaxStackSize; |
760 | | uintptr_t stackStart = std::max(maxStackStart, uintptr_t(bp)); |
761 | | stackEnd = reinterpret_cast<void*>(stackStart + kMaxStackSize); |
762 | | } |
763 | | #else |
764 | | # error Unsupported configuration |
765 | | #endif |
766 | | FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, aClosure, bp, |
767 | | stackEnd); |
768 | | } |
769 | | |
770 | | #elif defined(HAVE__UNWIND_BACKTRACE) |
771 | | |
772 | | // libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 |
773 | | #include <unwind.h> |
774 | | |
775 | | struct unwind_info |
776 | | { |
777 | | MozWalkStackCallback callback; |
778 | | int skip; |
779 | | int maxFrames; |
780 | | int numFrames; |
781 | | void* closure; |
782 | | }; |
783 | | |
784 | | static _Unwind_Reason_Code |
785 | | unwind_callback(struct _Unwind_Context* context, void* closure) |
786 | 0 | { |
787 | 0 | unwind_info* info = static_cast<unwind_info*>(closure); |
788 | 0 | void* pc = reinterpret_cast<void*>(_Unwind_GetIP(context)); |
789 | 0 | // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. |
790 | 0 | if (--info->skip < 0) { |
791 | 0 | info->numFrames++; |
792 | 0 | (*info->callback)(info->numFrames, pc, nullptr, info->closure); |
793 | 0 | if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { |
794 | 0 | // Again, any error code that stops the walk will do. |
795 | 0 | return _URC_FOREIGN_EXCEPTION_CAUGHT; |
796 | 0 | } |
797 | 0 | } |
798 | 0 | return _URC_NO_REASON; |
799 | 0 | } |
800 | | |
801 | | MFBT_API void |
802 | | MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
803 | | uint32_t aMaxFrames, void* aClosure) |
804 | 0 | { |
805 | 0 | unwind_info info; |
806 | 0 | info.callback = aCallback; |
807 | 0 | info.skip = aSkipFrames + 1; |
808 | 0 | info.maxFrames = aMaxFrames; |
809 | 0 | info.numFrames = 0; |
810 | 0 | info.closure = aClosure; |
811 | 0 |
|
812 | 0 | // We ignore the return value from _Unwind_Backtrace. There are three main |
813 | 0 | // reasons for this. |
814 | 0 | // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns |
815 | 0 | // _URC_FAILURE. See |
816 | 0 | // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. |
817 | 0 | // - If aMaxFrames != 0, we want to stop early, and the only way to do that |
818 | 0 | // is to make unwind_callback return something other than _URC_NO_REASON, |
819 | 0 | // which causes _Unwind_Backtrace to return a non-success code. |
820 | 0 | // - MozStackWalk doesn't have a return value anyway. |
821 | 0 | (void)_Unwind_Backtrace(unwind_callback, &info); |
822 | 0 | } |
823 | | |
824 | | #endif |
825 | | |
826 | | bool MFBT_API |
827 | | MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) |
828 | 0 | { |
829 | 0 | aDetails->library[0] = '\0'; |
830 | 0 | aDetails->loffset = 0; |
831 | 0 | aDetails->filename[0] = '\0'; |
832 | 0 | aDetails->lineno = 0; |
833 | 0 | aDetails->function[0] = '\0'; |
834 | 0 | aDetails->foffset = 0; |
835 | 0 |
|
836 | 0 | Dl_info info; |
837 | 0 | int ok = dladdr(aPC, &info); |
838 | 0 | if (!ok) { |
839 | 0 | return true; |
840 | 0 | } |
841 | 0 | |
842 | 0 | strncpy(aDetails->library, info.dli_fname, sizeof(aDetails->library)); |
843 | 0 | aDetails->library[mozilla::ArrayLength(aDetails->library) - 1] = '\0'; |
844 | 0 | aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; |
845 | 0 |
|
846 | 0 | const char* symbol = info.dli_sname; |
847 | 0 | if (!symbol || symbol[0] == '\0') { |
848 | 0 | return true; |
849 | 0 | } |
850 | 0 | |
851 | 0 | DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); |
852 | 0 |
|
853 | 0 | if (aDetails->function[0] == '\0') { |
854 | 0 | // Just use the mangled symbol if demangling failed. |
855 | 0 | strncpy(aDetails->function, symbol, sizeof(aDetails->function)); |
856 | 0 | aDetails->function[mozilla::ArrayLength(aDetails->function) - 1] = '\0'; |
857 | 0 | } |
858 | 0 |
|
859 | 0 | aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; |
860 | 0 | return true; |
861 | 0 | } |
862 | | |
863 | | #else // unsupported platform. |
864 | | |
865 | | MFBT_API void |
866 | | MozStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
867 | | uint32_t aMaxFrames, void* aClosure) |
868 | | { |
869 | | } |
870 | | |
871 | | MFBT_API bool |
872 | | MozDescribeCodeAddress(void* aPC, MozCodeAddressDetails* aDetails) |
873 | | { |
874 | | aDetails->library[0] = '\0'; |
875 | | aDetails->loffset = 0; |
876 | | aDetails->filename[0] = '\0'; |
877 | | aDetails->lineno = 0; |
878 | | aDetails->function[0] = '\0'; |
879 | | aDetails->foffset = 0; |
880 | | return false; |
881 | | } |
882 | | |
883 | | #endif |
884 | | |
885 | | #if defined(XP_WIN) || defined (XP_MACOSX) || defined (XP_LINUX) |
886 | | namespace mozilla { |
887 | | void |
888 | | FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
889 | | uint32_t aMaxFrames, void* aClosure, void** aBp, |
890 | | void* aStackEnd) |
891 | 0 | { |
892 | 0 | // Stack walking code courtesy Kipp's "leaky". |
893 | 0 |
|
894 | 0 | int32_t skip = aSkipFrames; |
895 | 0 | uint32_t numFrames = 0; |
896 | 0 | while (aBp) { |
897 | 0 | void** next = (void**)*aBp; |
898 | 0 | // aBp may not be a frame pointer on i386 if code was compiled with |
899 | 0 | // -fomit-frame-pointer, so do some sanity checks. |
900 | 0 | // (aBp should be a frame pointer on ppc(64) but checking anyway may help |
901 | 0 | // a little if the stack has been corrupted.) |
902 | 0 | // We don't need to check against the begining of the stack because |
903 | 0 | // we can assume that aBp > sp |
904 | 0 | if (next <= aBp || |
905 | 0 | next > aStackEnd || |
906 | 0 | (uintptr_t(next) & 3)) { |
907 | 0 | break; |
908 | 0 | } |
909 | | #if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) |
910 | | // ppc mac or powerpc64 linux |
911 | | void* pc = *(aBp + 2); |
912 | | aBp += 3; |
913 | | #else // i386 or powerpc32 linux |
914 | 0 | void* pc = *(aBp + 1); |
915 | 0 | aBp += 2; |
916 | 0 | #endif |
917 | 0 | if (--skip < 0) { |
918 | 0 | // Assume that the SP points to the BP of the function |
919 | 0 | // it called. We can't know the exact location of the SP |
920 | 0 | // but this should be sufficient for our use the SP |
921 | 0 | // to order elements on the stack. |
922 | 0 | numFrames++; |
923 | 0 | (*aCallback)(numFrames, pc, aBp, aClosure); |
924 | 0 | if (aMaxFrames != 0 && numFrames == aMaxFrames) { |
925 | 0 | break; |
926 | 0 | } |
927 | 0 | } |
928 | 0 | aBp = next; |
929 | 0 | } |
930 | 0 | } |
931 | | } // namespace mozilla |
932 | | |
933 | | #else |
934 | | |
935 | | namespace mozilla { |
936 | | MFBT_API void |
937 | | FramePointerStackWalk(MozWalkStackCallback aCallback, uint32_t aSkipFrames, |
938 | | uint32_t aMaxFrames, void* aClosure, void** aBp, |
939 | | void* aStackEnd) |
940 | | { |
941 | | } |
942 | | } |
943 | | |
944 | | #endif |
945 | | |
946 | | MFBT_API void |
947 | | MozFormatCodeAddressDetails(char* aBuffer, uint32_t aBufferSize, |
948 | | uint32_t aFrameNumber, void* aPC, |
949 | | const MozCodeAddressDetails* aDetails) |
950 | 0 | { |
951 | 0 | MozFormatCodeAddress(aBuffer, aBufferSize, |
952 | 0 | aFrameNumber, aPC, aDetails->function, |
953 | 0 | aDetails->library, aDetails->loffset, |
954 | 0 | aDetails->filename, aDetails->lineno); |
955 | 0 | } |
956 | | |
957 | | MFBT_API void |
958 | | MozFormatCodeAddress(char* aBuffer, uint32_t aBufferSize, uint32_t aFrameNumber, |
959 | | const void* aPC, const char* aFunction, |
960 | | const char* aLibrary, ptrdiff_t aLOffset, |
961 | | const char* aFileName, uint32_t aLineNo) |
962 | 0 | { |
963 | 0 | const char* function = aFunction && aFunction[0] ? aFunction : "???"; |
964 | 0 | if (aFileName && aFileName[0]) { |
965 | 0 | // We have a filename and (presumably) a line number. Use them. |
966 | 0 | snprintf(aBuffer, aBufferSize, |
967 | 0 | "#%02u: %s (%s:%u)", |
968 | 0 | aFrameNumber, function, aFileName, aLineNo); |
969 | 0 | } else if (aLibrary && aLibrary[0]) { |
970 | 0 | // We have no filename, but we do have a library name. Use it and the |
971 | 0 | // library offset, and print them in a way that scripts like |
972 | 0 | // fix_{linux,macosx}_stacks.py can easily post-process. |
973 | 0 | snprintf(aBuffer, aBufferSize, |
974 | 0 | "#%02u: %s[%s +0x%" PRIxPTR "]", |
975 | 0 | aFrameNumber, function, aLibrary, static_cast<uintptr_t>(aLOffset)); |
976 | 0 | } else { |
977 | 0 | // We have nothing useful to go on. (The format string is split because |
978 | 0 | // '??)' is a trigraph and causes a warning, sigh.) |
979 | 0 | snprintf(aBuffer, aBufferSize, |
980 | 0 | "#%02u: ??? (???:???" ")", |
981 | 0 | aFrameNumber); |
982 | 0 | } |
983 | 0 | } |