|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ |
|
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/Assertions.h" |
|
10 #include "mozilla/IntegerPrintfMacros.h" |
|
11 #include "mozilla/StackWalk.h" |
|
12 #include "nsStackWalkPrivate.h" |
|
13 |
|
14 #include "nsStackWalk.h" |
|
15 |
|
16 using namespace mozilla; |
|
17 |
|
18 // The presence of this address is the stack must stop the stack walk. If |
|
19 // there is no such address, the structure will be {nullptr, true}. |
|
20 struct CriticalAddress { |
|
21 void* mAddr; |
|
22 bool mInit; |
|
23 }; |
|
24 static CriticalAddress gCriticalAddress; |
|
25 |
|
26 // for _Unwind_Backtrace from libcxxrt or libunwind |
|
27 // cxxabi.h from libcxxrt implicitly includes unwind.h first |
|
28 #if defined(HAVE__UNWIND_BACKTRACE) && !defined(_GNU_SOURCE) |
|
29 #define _GNU_SOURCE |
|
30 #endif |
|
31 |
|
32 #if defined(HAVE_DLOPEN) || defined(XP_MACOSX) |
|
33 #include <dlfcn.h> |
|
34 #endif |
|
35 |
|
36 #define NSSTACKWALK_SUPPORTS_MACOSX \ |
|
37 (defined(XP_MACOSX) && \ |
|
38 (defined(__i386) || defined(__ppc__) || defined(HAVE__UNWIND_BACKTRACE))) |
|
39 |
|
40 #define NSSTACKWALK_SUPPORTS_LINUX \ |
|
41 (defined(linux) && \ |
|
42 ((defined(__GNUC__) && (defined(__i386) || defined(PPC))) || \ |
|
43 defined(HAVE__UNWIND_BACKTRACE))) |
|
44 |
|
45 #define NSSTACKWALK_SUPPORTS_SOLARIS \ |
|
46 (defined(__sun) && \ |
|
47 (defined(__sparc) || defined(sparc) || defined(__i386) || defined(i386))) |
|
48 |
|
49 #if NSSTACKWALK_SUPPORTS_MACOSX |
|
50 #include <pthread.h> |
|
51 #include <CoreServices/CoreServices.h> |
|
52 |
|
53 typedef void |
|
54 malloc_logger_t(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, |
|
55 uintptr_t result, uint32_t num_hot_frames_to_skip); |
|
56 extern malloc_logger_t *malloc_logger; |
|
57 |
|
58 static void |
|
59 stack_callback(void *pc, void *sp, void *closure) |
|
60 { |
|
61 const char *name = reinterpret_cast<char *>(closure); |
|
62 Dl_info info; |
|
63 |
|
64 // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The |
|
65 // stack shows up as having two pthread_cond_wait$UNIX2003 frames. The |
|
66 // correct one is the first that we find on our way up, so the |
|
67 // following check for gCriticalAddress.mAddr is critical. |
|
68 if (gCriticalAddress.mAddr || dladdr(pc, &info) == 0 || |
|
69 info.dli_sname == nullptr || strcmp(info.dli_sname, name) != 0) |
|
70 return; |
|
71 gCriticalAddress.mAddr = pc; |
|
72 } |
|
73 |
|
74 #ifdef DEBUG |
|
75 #define MAC_OS_X_VERSION_10_7_HEX 0x00001070 |
|
76 |
|
77 static int32_t OSXVersion() |
|
78 { |
|
79 static int32_t gOSXVersion = 0x0; |
|
80 if (gOSXVersion == 0x0) { |
|
81 OSErr err = ::Gestalt(gestaltSystemVersion, (SInt32*)&gOSXVersion); |
|
82 MOZ_ASSERT(err == noErr); |
|
83 } |
|
84 return gOSXVersion; |
|
85 } |
|
86 |
|
87 static bool OnLionOrLater() |
|
88 { |
|
89 return (OSXVersion() >= MAC_OS_X_VERSION_10_7_HEX); |
|
90 } |
|
91 #endif |
|
92 |
|
93 static void |
|
94 my_malloc_logger(uint32_t type, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, |
|
95 uintptr_t result, uint32_t num_hot_frames_to_skip) |
|
96 { |
|
97 static bool once = false; |
|
98 if (once) |
|
99 return; |
|
100 once = true; |
|
101 |
|
102 // On Leopard dladdr returns the wrong value for "new_sem_from_pool". The |
|
103 // stack shows up as having two pthread_cond_wait$UNIX2003 frames. |
|
104 const char *name = "new_sem_from_pool"; |
|
105 NS_StackWalk(stack_callback, /* skipFrames */ 0, /* maxFrames */ 0, |
|
106 const_cast<char*>(name), 0, nullptr); |
|
107 } |
|
108 |
|
109 // This is called from NS_LogInit() and from the stack walking functions, but |
|
110 // only the first call has any effect. We need to call this function from both |
|
111 // places because it must run before any mutexes are created, and also before |
|
112 // any objects whose refcounts we're logging are created. Running this |
|
113 // function during NS_LogInit() ensures that we meet the first criterion, and |
|
114 // running this function during the stack walking functions ensures we meet the |
|
115 // second criterion. |
|
116 void |
|
117 StackWalkInitCriticalAddress() |
|
118 { |
|
119 if(gCriticalAddress.mInit) |
|
120 return; |
|
121 gCriticalAddress.mInit = true; |
|
122 // We must not do work when 'new_sem_from_pool' calls realloc, since |
|
123 // it holds a non-reentrant spin-lock and we will quickly deadlock. |
|
124 // new_sem_from_pool is not directly accessible using dlsym, so |
|
125 // we force a situation where new_sem_from_pool is on the stack and |
|
126 // use dladdr to check the addresses. |
|
127 |
|
128 // malloc_logger can be set by external tools like 'Instruments' or 'leaks' |
|
129 malloc_logger_t *old_malloc_logger = malloc_logger; |
|
130 malloc_logger = my_malloc_logger; |
|
131 |
|
132 pthread_cond_t cond; |
|
133 int r = pthread_cond_init(&cond, 0); |
|
134 MOZ_ASSERT(r == 0); |
|
135 pthread_mutex_t mutex; |
|
136 r = pthread_mutex_init(&mutex,0); |
|
137 MOZ_ASSERT(r == 0); |
|
138 r = pthread_mutex_lock(&mutex); |
|
139 MOZ_ASSERT(r == 0); |
|
140 struct timespec abstime = {0, 1}; |
|
141 r = pthread_cond_timedwait_relative_np(&cond, &mutex, &abstime); |
|
142 |
|
143 // restore the previous malloc logger |
|
144 malloc_logger = old_malloc_logger; |
|
145 |
|
146 // On Lion, malloc is no longer called from pthread_cond_*wait*. This prevents |
|
147 // us from finding the address, but that is fine, since with no call to malloc |
|
148 // there is no critical address. |
|
149 MOZ_ASSERT(OnLionOrLater() || gCriticalAddress.mAddr != nullptr); |
|
150 MOZ_ASSERT(r == ETIMEDOUT); |
|
151 r = pthread_mutex_unlock(&mutex); |
|
152 MOZ_ASSERT(r == 0); |
|
153 r = pthread_mutex_destroy(&mutex); |
|
154 MOZ_ASSERT(r == 0); |
|
155 r = pthread_cond_destroy(&cond); |
|
156 MOZ_ASSERT(r == 0); |
|
157 } |
|
158 |
|
159 static bool IsCriticalAddress(void* aPC) |
|
160 { |
|
161 return gCriticalAddress.mAddr == aPC; |
|
162 } |
|
163 #else |
|
164 static bool IsCriticalAddress(void* aPC) |
|
165 { |
|
166 return false; |
|
167 } |
|
168 // We still initialize gCriticalAddress.mInit so that this code behaves |
|
169 // the same on all platforms. Otherwise a failure to init would be visible |
|
170 // only on OS X. |
|
171 void |
|
172 StackWalkInitCriticalAddress() |
|
173 { |
|
174 gCriticalAddress.mInit = true; |
|
175 } |
|
176 #endif |
|
177 |
|
178 #if defined(_WIN32) && (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_IA64)) // WIN32 x86 stack walking code |
|
179 |
|
180 #include "nscore.h" |
|
181 #include <windows.h> |
|
182 #include <process.h> |
|
183 #include <stdio.h> |
|
184 #include <malloc.h> |
|
185 #include "plstr.h" |
|
186 #include "mozilla/ArrayUtils.h" |
|
187 |
|
188 #include "nspr.h" |
|
189 #include <imagehlp.h> |
|
190 // We need a way to know if we are building for WXP (or later), as if we are, we |
|
191 // need to use the newer 64-bit APIs. API_VERSION_NUMBER seems to fit the bill. |
|
192 // A value of 9 indicates we want to use the new APIs. |
|
193 #if API_VERSION_NUMBER < 9 |
|
194 #error Too old imagehlp.h |
|
195 #endif |
|
196 |
|
197 // Define these as static pointers so that we can load the DLL on the |
|
198 // fly (and not introduce a link-time dependency on it). Tip o' the |
|
199 // hat to Matt Pietrick for this idea. See: |
|
200 // |
|
201 // http://msdn.microsoft.com/library/periodic/period97/F1/D3/S245C6.htm |
|
202 // |
|
203 extern "C" { |
|
204 |
|
205 extern HANDLE hStackWalkMutex; |
|
206 |
|
207 bool EnsureSymInitialized(); |
|
208 |
|
209 bool EnsureWalkThreadReady(); |
|
210 |
|
211 struct WalkStackData { |
|
212 uint32_t skipFrames; |
|
213 HANDLE thread; |
|
214 bool walkCallingThread; |
|
215 HANDLE process; |
|
216 HANDLE eventStart; |
|
217 HANDLE eventEnd; |
|
218 void **pcs; |
|
219 uint32_t pc_size; |
|
220 uint32_t pc_count; |
|
221 uint32_t pc_max; |
|
222 void **sps; |
|
223 uint32_t sp_size; |
|
224 uint32_t sp_count; |
|
225 void *platformData; |
|
226 }; |
|
227 |
|
228 void PrintError(char *prefix, WalkStackData* data); |
|
229 unsigned int WINAPI WalkStackThread(void* data); |
|
230 void WalkStackMain64(struct WalkStackData* data); |
|
231 |
|
232 |
|
233 DWORD gStackWalkThread; |
|
234 CRITICAL_SECTION gDbgHelpCS; |
|
235 |
|
236 } |
|
237 |
|
238 // Routine to print an error message to standard error. |
|
239 void PrintError(const char *prefix) |
|
240 { |
|
241 LPVOID lpMsgBuf; |
|
242 DWORD lastErr = GetLastError(); |
|
243 FormatMessageA( |
|
244 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, |
|
245 nullptr, |
|
246 lastErr, |
|
247 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language |
|
248 (LPSTR) &lpMsgBuf, |
|
249 0, |
|
250 nullptr |
|
251 ); |
|
252 fprintf(stderr, "### ERROR: %s: %s", |
|
253 prefix, lpMsgBuf ? lpMsgBuf : "(null)\n"); |
|
254 fflush(stderr); |
|
255 LocalFree(lpMsgBuf); |
|
256 } |
|
257 |
|
258 bool |
|
259 EnsureWalkThreadReady() |
|
260 { |
|
261 static bool walkThreadReady = false; |
|
262 static HANDLE stackWalkThread = nullptr; |
|
263 static HANDLE readyEvent = nullptr; |
|
264 |
|
265 if (walkThreadReady) |
|
266 return walkThreadReady; |
|
267 |
|
268 if (stackWalkThread == nullptr) { |
|
269 readyEvent = ::CreateEvent(nullptr, FALSE /* auto-reset*/, |
|
270 FALSE /* initially non-signaled */, |
|
271 nullptr); |
|
272 if (readyEvent == nullptr) { |
|
273 PrintError("CreateEvent"); |
|
274 return false; |
|
275 } |
|
276 |
|
277 unsigned int threadID; |
|
278 stackWalkThread = (HANDLE) |
|
279 _beginthreadex(nullptr, 0, WalkStackThread, (void*)readyEvent, |
|
280 0, &threadID); |
|
281 if (stackWalkThread == nullptr) { |
|
282 PrintError("CreateThread"); |
|
283 ::CloseHandle(readyEvent); |
|
284 readyEvent = nullptr; |
|
285 return false; |
|
286 } |
|
287 gStackWalkThread = threadID; |
|
288 ::CloseHandle(stackWalkThread); |
|
289 } |
|
290 |
|
291 MOZ_ASSERT((stackWalkThread != nullptr && readyEvent != nullptr) || |
|
292 (stackWalkThread == nullptr && readyEvent == nullptr)); |
|
293 |
|
294 // The thread was created. Try to wait an arbitrary amount of time (1 second |
|
295 // should be enough) for its event loop to start before posting events to it. |
|
296 DWORD waitRet = ::WaitForSingleObject(readyEvent, 1000); |
|
297 if (waitRet == WAIT_TIMEOUT) { |
|
298 // We get a timeout if we're called during static initialization because |
|
299 // the thread will only start executing after we return so it couldn't |
|
300 // have signalled the event. If that is the case, give up for now and |
|
301 // try again next time we're called. |
|
302 return false; |
|
303 } |
|
304 ::CloseHandle(readyEvent); |
|
305 stackWalkThread = nullptr; |
|
306 readyEvent = nullptr; |
|
307 |
|
308 |
|
309 ::InitializeCriticalSection(&gDbgHelpCS); |
|
310 |
|
311 return walkThreadReady = true; |
|
312 } |
|
313 |
|
314 void |
|
315 WalkStackMain64(struct WalkStackData* data) |
|
316 { |
|
317 // Get the context information for the thread. That way we will |
|
318 // know where our sp, fp, pc, etc. are and can fill in the |
|
319 // STACKFRAME64 with the initial values. |
|
320 CONTEXT context; |
|
321 HANDLE myProcess = data->process; |
|
322 HANDLE myThread = data->thread; |
|
323 DWORD64 addr; |
|
324 DWORD64 spaddr; |
|
325 STACKFRAME64 frame64; |
|
326 // skip our own stack walking frames |
|
327 int skip = (data->walkCallingThread ? 3 : 0) + data->skipFrames; |
|
328 BOOL ok; |
|
329 |
|
330 // Get a context for the specified thread. |
|
331 if (!data->platformData) { |
|
332 memset(&context, 0, sizeof(CONTEXT)); |
|
333 context.ContextFlags = CONTEXT_FULL; |
|
334 if (!GetThreadContext(myThread, &context)) { |
|
335 if (data->walkCallingThread) { |
|
336 PrintError("GetThreadContext"); |
|
337 } |
|
338 return; |
|
339 } |
|
340 } else { |
|
341 context = *static_cast<CONTEXT*>(data->platformData); |
|
342 } |
|
343 |
|
344 // Setup initial stack frame to walk from |
|
345 memset(&frame64, 0, sizeof(frame64)); |
|
346 #ifdef _M_IX86 |
|
347 frame64.AddrPC.Offset = context.Eip; |
|
348 frame64.AddrStack.Offset = context.Esp; |
|
349 frame64.AddrFrame.Offset = context.Ebp; |
|
350 #elif defined _M_AMD64 |
|
351 frame64.AddrPC.Offset = context.Rip; |
|
352 frame64.AddrStack.Offset = context.Rsp; |
|
353 frame64.AddrFrame.Offset = context.Rbp; |
|
354 #elif defined _M_IA64 |
|
355 frame64.AddrPC.Offset = context.StIIP; |
|
356 frame64.AddrStack.Offset = context.SP; |
|
357 frame64.AddrFrame.Offset = context.RsBSP; |
|
358 #else |
|
359 #error "Should not have compiled this code" |
|
360 #endif |
|
361 frame64.AddrPC.Mode = AddrModeFlat; |
|
362 frame64.AddrStack.Mode = AddrModeFlat; |
|
363 frame64.AddrFrame.Mode = AddrModeFlat; |
|
364 frame64.AddrReturn.Mode = AddrModeFlat; |
|
365 |
|
366 // Now walk the stack |
|
367 while (1) { |
|
368 |
|
369 // debug routines are not threadsafe, so grab the lock. |
|
370 EnterCriticalSection(&gDbgHelpCS); |
|
371 ok = StackWalk64( |
|
372 #ifdef _M_AMD64 |
|
373 IMAGE_FILE_MACHINE_AMD64, |
|
374 #elif defined _M_IA64 |
|
375 IMAGE_FILE_MACHINE_IA64, |
|
376 #elif defined _M_IX86 |
|
377 IMAGE_FILE_MACHINE_I386, |
|
378 #else |
|
379 #error "Should not have compiled this code" |
|
380 #endif |
|
381 myProcess, |
|
382 myThread, |
|
383 &frame64, |
|
384 &context, |
|
385 nullptr, |
|
386 SymFunctionTableAccess64, // function table access routine |
|
387 SymGetModuleBase64, // module base routine |
|
388 0 |
|
389 ); |
|
390 LeaveCriticalSection(&gDbgHelpCS); |
|
391 |
|
392 if (ok) { |
|
393 addr = frame64.AddrPC.Offset; |
|
394 spaddr = frame64.AddrStack.Offset; |
|
395 } else { |
|
396 addr = 0; |
|
397 spaddr = 0; |
|
398 if (data->walkCallingThread) { |
|
399 PrintError("WalkStack64"); |
|
400 } |
|
401 } |
|
402 |
|
403 if (!ok || (addr == 0)) { |
|
404 break; |
|
405 } |
|
406 |
|
407 if (skip-- > 0) { |
|
408 continue; |
|
409 } |
|
410 |
|
411 if (data->pc_count < data->pc_size) |
|
412 data->pcs[data->pc_count] = (void*)addr; |
|
413 ++data->pc_count; |
|
414 |
|
415 if (data->sp_count < data->sp_size) |
|
416 data->sps[data->sp_count] = (void*)spaddr; |
|
417 ++data->sp_count; |
|
418 |
|
419 if (data->pc_max != 0 && data->pc_count == data->pc_max) |
|
420 break; |
|
421 |
|
422 if (frame64.AddrReturn.Offset == 0) |
|
423 break; |
|
424 } |
|
425 return; |
|
426 } |
|
427 |
|
428 |
|
429 unsigned int WINAPI |
|
430 WalkStackThread(void* aData) |
|
431 { |
|
432 BOOL msgRet; |
|
433 MSG msg; |
|
434 |
|
435 // Call PeekMessage to force creation of a message queue so that |
|
436 // other threads can safely post events to us. |
|
437 ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); |
|
438 |
|
439 // and tell the thread that created us that we're ready. |
|
440 HANDLE readyEvent = (HANDLE)aData; |
|
441 ::SetEvent(readyEvent); |
|
442 |
|
443 while ((msgRet = ::GetMessage(&msg, (HWND)-1, 0, 0)) != 0) { |
|
444 if (msgRet == -1) { |
|
445 PrintError("GetMessage"); |
|
446 } else { |
|
447 DWORD ret; |
|
448 |
|
449 struct WalkStackData *data = (WalkStackData *)msg.lParam; |
|
450 if (!data) |
|
451 continue; |
|
452 |
|
453 // Don't suspend the calling thread until it's waiting for |
|
454 // us; otherwise the number of frames on the stack could vary. |
|
455 ret = ::WaitForSingleObject(data->eventStart, INFINITE); |
|
456 if (ret != WAIT_OBJECT_0) |
|
457 PrintError("WaitForSingleObject"); |
|
458 |
|
459 // Suspend the calling thread, dump his stack, and then resume him. |
|
460 // He's currently waiting for us to finish so now should be a good time. |
|
461 ret = ::SuspendThread( data->thread ); |
|
462 if (ret == -1) { |
|
463 PrintError("ThreadSuspend"); |
|
464 } |
|
465 else { |
|
466 WalkStackMain64(data); |
|
467 |
|
468 ret = ::ResumeThread(data->thread); |
|
469 if (ret == -1) { |
|
470 PrintError("ThreadResume"); |
|
471 } |
|
472 } |
|
473 |
|
474 ::SetEvent(data->eventEnd); |
|
475 } |
|
476 } |
|
477 |
|
478 return 0; |
|
479 } |
|
480 |
|
481 /** |
|
482 * Walk the stack, translating PC's found into strings and recording the |
|
483 * chain in aBuffer. For this to work properly, the DLLs must be rebased |
|
484 * so that the address in the file agrees with the address in memory. |
|
485 * Otherwise StackWalk will return FALSE when it hits a frame in a DLL |
|
486 * whose in memory address doesn't match its in-file address. |
|
487 */ |
|
488 |
|
489 EXPORT_XPCOM_API(nsresult) |
|
490 NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
491 uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, |
|
492 void *aPlatformData) |
|
493 { |
|
494 StackWalkInitCriticalAddress(); |
|
495 static HANDLE myProcess = nullptr; |
|
496 HANDLE myThread; |
|
497 DWORD walkerReturn; |
|
498 struct WalkStackData data; |
|
499 |
|
500 if (!EnsureWalkThreadReady()) |
|
501 return NS_ERROR_FAILURE; |
|
502 |
|
503 HANDLE targetThread = ::GetCurrentThread(); |
|
504 data.walkCallingThread = true; |
|
505 if (aThread) { |
|
506 HANDLE threadToWalk = reinterpret_cast<HANDLE> (aThread); |
|
507 // walkCallingThread indicates whether we are walking the caller's stack |
|
508 data.walkCallingThread = (threadToWalk == targetThread); |
|
509 targetThread = threadToWalk; |
|
510 } |
|
511 |
|
512 // We need to avoid calling fprintf and friends if we're walking the stack of |
|
513 // another thread, in order to avoid deadlocks. |
|
514 const bool shouldBeThreadSafe = !!aThread; |
|
515 |
|
516 // Have to duplicate handle to get a real handle. |
|
517 if (!myProcess) { |
|
518 if (!::DuplicateHandle(::GetCurrentProcess(), |
|
519 ::GetCurrentProcess(), |
|
520 ::GetCurrentProcess(), |
|
521 &myProcess, |
|
522 PROCESS_ALL_ACCESS, FALSE, 0)) { |
|
523 if (!shouldBeThreadSafe) { |
|
524 PrintError("DuplicateHandle (process)"); |
|
525 } |
|
526 return NS_ERROR_FAILURE; |
|
527 } |
|
528 } |
|
529 if (!::DuplicateHandle(::GetCurrentProcess(), |
|
530 targetThread, |
|
531 ::GetCurrentProcess(), |
|
532 &myThread, |
|
533 THREAD_ALL_ACCESS, FALSE, 0)) { |
|
534 if (!shouldBeThreadSafe) { |
|
535 PrintError("DuplicateHandle (thread)"); |
|
536 } |
|
537 return NS_ERROR_FAILURE; |
|
538 } |
|
539 |
|
540 data.skipFrames = aSkipFrames; |
|
541 data.thread = myThread; |
|
542 data.process = myProcess; |
|
543 void *local_pcs[1024]; |
|
544 data.pcs = local_pcs; |
|
545 data.pc_count = 0; |
|
546 data.pc_size = ArrayLength(local_pcs); |
|
547 data.pc_max = aMaxFrames; |
|
548 void *local_sps[1024]; |
|
549 data.sps = local_sps; |
|
550 data.sp_count = 0; |
|
551 data.sp_size = ArrayLength(local_sps); |
|
552 data.platformData = aPlatformData; |
|
553 |
|
554 if (aThread) { |
|
555 // If we're walking the stack of another thread, we don't need to |
|
556 // use a separate walker thread. |
|
557 WalkStackMain64(&data); |
|
558 |
|
559 if (data.pc_count > data.pc_size) { |
|
560 data.pcs = (void**) _alloca(data.pc_count * sizeof(void*)); |
|
561 data.pc_size = data.pc_count; |
|
562 data.pc_count = 0; |
|
563 data.sps = (void**) _alloca(data.sp_count * sizeof(void*)); |
|
564 data.sp_size = data.sp_count; |
|
565 data.sp_count = 0; |
|
566 WalkStackMain64(&data); |
|
567 } |
|
568 } else { |
|
569 data.eventStart = ::CreateEvent(nullptr, FALSE /* auto-reset*/, |
|
570 FALSE /* initially non-signaled */, nullptr); |
|
571 data.eventEnd = ::CreateEvent(nullptr, FALSE /* auto-reset*/, |
|
572 FALSE /* initially non-signaled */, nullptr); |
|
573 |
|
574 ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); |
|
575 |
|
576 walkerReturn = ::SignalObjectAndWait(data.eventStart, |
|
577 data.eventEnd, INFINITE, FALSE); |
|
578 if (walkerReturn != WAIT_OBJECT_0 && !shouldBeThreadSafe) |
|
579 PrintError("SignalObjectAndWait (1)"); |
|
580 if (data.pc_count > data.pc_size) { |
|
581 data.pcs = (void**) _alloca(data.pc_count * sizeof(void*)); |
|
582 data.pc_size = data.pc_count; |
|
583 data.pc_count = 0; |
|
584 data.sps = (void**) _alloca(data.sp_count * sizeof(void*)); |
|
585 data.sp_size = data.sp_count; |
|
586 data.sp_count = 0; |
|
587 ::PostThreadMessage(gStackWalkThread, WM_USER, 0, (LPARAM)&data); |
|
588 walkerReturn = ::SignalObjectAndWait(data.eventStart, |
|
589 data.eventEnd, INFINITE, FALSE); |
|
590 if (walkerReturn != WAIT_OBJECT_0 && !shouldBeThreadSafe) |
|
591 PrintError("SignalObjectAndWait (2)"); |
|
592 } |
|
593 |
|
594 ::CloseHandle(data.eventStart); |
|
595 ::CloseHandle(data.eventEnd); |
|
596 } |
|
597 |
|
598 ::CloseHandle(myThread); |
|
599 |
|
600 for (uint32_t i = 0; i < data.pc_count; ++i) |
|
601 (*aCallback)(data.pcs[i], data.sps[i], aClosure); |
|
602 |
|
603 return data.pc_count == 0 ? NS_ERROR_FAILURE : NS_OK; |
|
604 } |
|
605 |
|
606 |
|
607 static BOOL CALLBACK callbackEspecial64( |
|
608 PCSTR aModuleName, |
|
609 DWORD64 aModuleBase, |
|
610 ULONG aModuleSize, |
|
611 PVOID aUserContext) |
|
612 { |
|
613 BOOL retval = TRUE; |
|
614 DWORD64 addr = *(DWORD64*)aUserContext; |
|
615 |
|
616 /* |
|
617 * You'll want to control this if we are running on an |
|
618 * architecture where the addresses go the other direction. |
|
619 * Not sure this is even a realistic consideration. |
|
620 */ |
|
621 const BOOL addressIncreases = TRUE; |
|
622 |
|
623 /* |
|
624 * If it falls in side the known range, load the symbols. |
|
625 */ |
|
626 if (addressIncreases |
|
627 ? (addr >= aModuleBase && addr <= (aModuleBase + aModuleSize)) |
|
628 : (addr <= aModuleBase && addr >= (aModuleBase - aModuleSize)) |
|
629 ) { |
|
630 retval = !!SymLoadModule64(GetCurrentProcess(), nullptr, |
|
631 (PSTR)aModuleName, nullptr, |
|
632 aModuleBase, aModuleSize); |
|
633 if (!retval) |
|
634 PrintError("SymLoadModule64"); |
|
635 } |
|
636 |
|
637 return retval; |
|
638 } |
|
639 |
|
640 /* |
|
641 * SymGetModuleInfoEspecial |
|
642 * |
|
643 * Attempt to determine the module information. |
|
644 * Bug 112196 says this DLL may not have been loaded at the time |
|
645 * SymInitialize was called, and thus the module information |
|
646 * and symbol information is not available. |
|
647 * This code rectifies that problem. |
|
648 */ |
|
649 |
|
650 // New members were added to IMAGEHLP_MODULE64 (that show up in the |
|
651 // Platform SDK that ships with VC8, but not the Platform SDK that ships |
|
652 // with VC7.1, i.e., between DbgHelp 6.0 and 6.1), but we don't need to |
|
653 // use them, and it's useful to be able to function correctly with the |
|
654 // older library. (Stock Windows XP SP2 seems to ship with dbghelp.dll |
|
655 // version 5.1.) Since Platform SDK version need not correspond to |
|
656 // compiler version, and the version number in debughlp.h was NOT bumped |
|
657 // when these changes were made, ifdef based on a constant that was |
|
658 // added between these versions. |
|
659 #ifdef SSRVOPT_SETCONTEXT |
|
660 #define NS_IMAGEHLP_MODULE64_SIZE (((offsetof(IMAGEHLP_MODULE64, LoadedPdbName) + sizeof(DWORD64) - 1) / sizeof(DWORD64)) * sizeof(DWORD64)) |
|
661 #else |
|
662 #define NS_IMAGEHLP_MODULE64_SIZE sizeof(IMAGEHLP_MODULE64) |
|
663 #endif |
|
664 |
|
665 BOOL SymGetModuleInfoEspecial64(HANDLE aProcess, DWORD64 aAddr, PIMAGEHLP_MODULE64 aModuleInfo, PIMAGEHLP_LINE64 aLineInfo) |
|
666 { |
|
667 BOOL retval = FALSE; |
|
668 |
|
669 /* |
|
670 * Init the vars if we have em. |
|
671 */ |
|
672 aModuleInfo->SizeOfStruct = NS_IMAGEHLP_MODULE64_SIZE; |
|
673 if (nullptr != aLineInfo) { |
|
674 aLineInfo->SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
|
675 } |
|
676 |
|
677 /* |
|
678 * Give it a go. |
|
679 * It may already be loaded. |
|
680 */ |
|
681 retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); |
|
682 |
|
683 if (FALSE == retval) { |
|
684 BOOL enumRes = FALSE; |
|
685 |
|
686 /* |
|
687 * Not loaded, here's the magic. |
|
688 * Go through all the modules. |
|
689 */ |
|
690 // Need to cast to PENUMLOADED_MODULES_CALLBACK64 because the |
|
691 // constness of the first parameter of |
|
692 // PENUMLOADED_MODULES_CALLBACK64 varies over SDK versions (from |
|
693 // non-const to const over time). See bug 391848 and bug |
|
694 // 415426. |
|
695 enumRes = EnumerateLoadedModules64(aProcess, (PENUMLOADED_MODULES_CALLBACK64)callbackEspecial64, (PVOID)&aAddr); |
|
696 if (FALSE != enumRes) |
|
697 { |
|
698 /* |
|
699 * One final go. |
|
700 * If it fails, then well, we have other problems. |
|
701 */ |
|
702 retval = SymGetModuleInfo64(aProcess, aAddr, aModuleInfo); |
|
703 } |
|
704 } |
|
705 |
|
706 /* |
|
707 * If we got module info, we may attempt line info as well. |
|
708 * We will not report failure if this does not work. |
|
709 */ |
|
710 if (FALSE != retval && nullptr != aLineInfo) { |
|
711 DWORD displacement = 0; |
|
712 BOOL lineRes = FALSE; |
|
713 lineRes = SymGetLineFromAddr64(aProcess, aAddr, &displacement, aLineInfo); |
|
714 if (!lineRes) { |
|
715 // Clear out aLineInfo to indicate that it's not valid |
|
716 memset(aLineInfo, 0, sizeof(*aLineInfo)); |
|
717 } |
|
718 } |
|
719 |
|
720 return retval; |
|
721 } |
|
722 |
|
723 bool |
|
724 EnsureSymInitialized() |
|
725 { |
|
726 static bool gInitialized = false; |
|
727 bool retStat; |
|
728 |
|
729 if (gInitialized) |
|
730 return gInitialized; |
|
731 |
|
732 if (!EnsureWalkThreadReady()) |
|
733 return false; |
|
734 |
|
735 SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); |
|
736 retStat = SymInitialize(GetCurrentProcess(), nullptr, TRUE); |
|
737 if (!retStat) |
|
738 PrintError("SymInitialize"); |
|
739 |
|
740 gInitialized = retStat; |
|
741 /* XXX At some point we need to arrange to call SymCleanup */ |
|
742 |
|
743 return retStat; |
|
744 } |
|
745 |
|
746 |
|
747 EXPORT_XPCOM_API(nsresult) |
|
748 NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) |
|
749 { |
|
750 aDetails->library[0] = '\0'; |
|
751 aDetails->loffset = 0; |
|
752 aDetails->filename[0] = '\0'; |
|
753 aDetails->lineno = 0; |
|
754 aDetails->function[0] = '\0'; |
|
755 aDetails->foffset = 0; |
|
756 |
|
757 if (!EnsureSymInitialized()) |
|
758 return NS_ERROR_FAILURE; |
|
759 |
|
760 HANDLE myProcess = ::GetCurrentProcess(); |
|
761 BOOL ok; |
|
762 |
|
763 // debug routines are not threadsafe, so grab the lock. |
|
764 EnterCriticalSection(&gDbgHelpCS); |
|
765 |
|
766 // |
|
767 // Attempt to load module info before we attempt to resolve the symbol. |
|
768 // This just makes sure we get good info if available. |
|
769 // |
|
770 |
|
771 DWORD64 addr = (DWORD64)aPC; |
|
772 IMAGEHLP_MODULE64 modInfo; |
|
773 IMAGEHLP_LINE64 lineInfo; |
|
774 BOOL modInfoRes; |
|
775 modInfoRes = SymGetModuleInfoEspecial64(myProcess, addr, &modInfo, &lineInfo); |
|
776 |
|
777 if (modInfoRes) { |
|
778 PL_strncpyz(aDetails->library, modInfo.ModuleName, |
|
779 sizeof(aDetails->library)); |
|
780 aDetails->loffset = (char*) aPC - (char*) modInfo.BaseOfImage; |
|
781 |
|
782 if (lineInfo.FileName) { |
|
783 PL_strncpyz(aDetails->filename, lineInfo.FileName, |
|
784 sizeof(aDetails->filename)); |
|
785 aDetails->lineno = lineInfo.LineNumber; |
|
786 } |
|
787 } |
|
788 |
|
789 ULONG64 buffer[(sizeof(SYMBOL_INFO) + |
|
790 MAX_SYM_NAME*sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64)]; |
|
791 PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; |
|
792 pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); |
|
793 pSymbol->MaxNameLen = MAX_SYM_NAME; |
|
794 |
|
795 DWORD64 displacement; |
|
796 ok = SymFromAddr(myProcess, addr, &displacement, pSymbol); |
|
797 |
|
798 if (ok) { |
|
799 PL_strncpyz(aDetails->function, pSymbol->Name, |
|
800 sizeof(aDetails->function)); |
|
801 aDetails->foffset = static_cast<ptrdiff_t>(displacement); |
|
802 } |
|
803 |
|
804 LeaveCriticalSection(&gDbgHelpCS); // release our lock |
|
805 return NS_OK; |
|
806 } |
|
807 |
|
808 EXPORT_XPCOM_API(nsresult) |
|
809 NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, |
|
810 char *aBuffer, uint32_t aBufferSize) |
|
811 { |
|
812 if (aDetails->function[0]) { |
|
813 _snprintf(aBuffer, aBufferSize, "%s+0x%08lX [%s +0x%016lX]", |
|
814 aDetails->function, aDetails->foffset, |
|
815 aDetails->library, aDetails->loffset); |
|
816 } else if (aDetails->library[0]) { |
|
817 _snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%016lX]", |
|
818 aDetails->library, aDetails->loffset); |
|
819 } else { |
|
820 _snprintf(aBuffer, aBufferSize, "UNKNOWN 0x%016lX", aPC); |
|
821 } |
|
822 |
|
823 aBuffer[aBufferSize - 1] = '\0'; |
|
824 |
|
825 uint32_t len = strlen(aBuffer); |
|
826 if (aDetails->filename[0]) { |
|
827 _snprintf(aBuffer + len, aBufferSize - len, " (%s, line %d)\n", |
|
828 aDetails->filename, aDetails->lineno); |
|
829 } else { |
|
830 aBuffer[len] = '\n'; |
|
831 if (++len != aBufferSize) |
|
832 aBuffer[len] = '\0'; |
|
833 } |
|
834 aBuffer[aBufferSize - 2] = '\n'; |
|
835 aBuffer[aBufferSize - 1] = '\0'; |
|
836 return NS_OK; |
|
837 } |
|
838 |
|
839 // WIN32 x86 stack walking code |
|
840 // i386 or PPC Linux stackwalking code or Solaris |
|
841 #elif HAVE_DLADDR && (HAVE__UNWIND_BACKTRACE || NSSTACKWALK_SUPPORTS_LINUX || NSSTACKWALK_SUPPORTS_SOLARIS || NSSTACKWALK_SUPPORTS_MACOSX) |
|
842 |
|
843 #include <stdlib.h> |
|
844 #include <string.h> |
|
845 #include "nscore.h" |
|
846 #include <stdio.h> |
|
847 #include "plstr.h" |
|
848 |
|
849 // On glibc 2.1, the Dl_info api defined in <dlfcn.h> is only exposed |
|
850 // if __USE_GNU is defined. I suppose its some kind of standards |
|
851 // adherence thing. |
|
852 // |
|
853 #if (__GLIBC_MINOR__ >= 1) && !defined(__USE_GNU) |
|
854 #define __USE_GNU |
|
855 #endif |
|
856 |
|
857 // This thing is exported by libstdc++ |
|
858 // Yes, this is a gcc only hack |
|
859 #if defined(MOZ_DEMANGLE_SYMBOLS) |
|
860 #include <cxxabi.h> |
|
861 #endif // MOZ_DEMANGLE_SYMBOLS |
|
862 |
|
863 void DemangleSymbol(const char * aSymbol, |
|
864 char * aBuffer, |
|
865 int aBufLen) |
|
866 { |
|
867 aBuffer[0] = '\0'; |
|
868 |
|
869 #if defined(MOZ_DEMANGLE_SYMBOLS) |
|
870 /* See demangle.h in the gcc source for the voodoo */ |
|
871 char * demangled = abi::__cxa_demangle(aSymbol,0,0,0); |
|
872 |
|
873 if (demangled) |
|
874 { |
|
875 PL_strncpyz(aBuffer,demangled,aBufLen); |
|
876 free(demangled); |
|
877 } |
|
878 #endif // MOZ_DEMANGLE_SYMBOLS |
|
879 } |
|
880 |
|
881 |
|
882 #if NSSTACKWALK_SUPPORTS_SOLARIS |
|
883 |
|
884 /* |
|
885 * Stack walking code for Solaris courtesy of Bart Smaalder's "memtrak". |
|
886 */ |
|
887 |
|
888 #include <synch.h> |
|
889 #include <ucontext.h> |
|
890 #include <sys/frame.h> |
|
891 #include <sys/regset.h> |
|
892 #include <sys/stack.h> |
|
893 |
|
894 static int load_address ( void * pc, void * arg ); |
|
895 static struct bucket * newbucket ( void * pc ); |
|
896 static struct frame * cs_getmyframeptr ( void ); |
|
897 static void cs_walk_stack ( void * (*read_func)(char * address), |
|
898 struct frame * fp, |
|
899 int (*operate_func)(void *, void *, void *), |
|
900 void * usrarg ); |
|
901 static void cs_operate ( void (*operate_func)(void *, void *, void *), |
|
902 void * usrarg ); |
|
903 |
|
904 #ifndef STACK_BIAS |
|
905 #define STACK_BIAS 0 |
|
906 #endif /*STACK_BIAS*/ |
|
907 |
|
908 #define LOGSIZE 4096 |
|
909 |
|
910 /* type of demangling function */ |
|
911 typedef int demf_t(const char *, char *, size_t); |
|
912 |
|
913 static demf_t *demf; |
|
914 |
|
915 static int initialized = 0; |
|
916 |
|
917 #if defined(sparc) || defined(__sparc) |
|
918 #define FRAME_PTR_REGISTER REG_SP |
|
919 #endif |
|
920 |
|
921 #if defined(i386) || defined(__i386) |
|
922 #define FRAME_PTR_REGISTER EBP |
|
923 #endif |
|
924 |
|
925 struct bucket { |
|
926 void * pc; |
|
927 int index; |
|
928 struct bucket * next; |
|
929 }; |
|
930 |
|
931 struct my_user_args { |
|
932 NS_WalkStackCallback callback; |
|
933 uint32_t skipFrames; |
|
934 uint32_t maxFrames; |
|
935 uint32_t numFrames; |
|
936 void *closure; |
|
937 }; |
|
938 |
|
939 |
|
940 static void myinit(); |
|
941 |
|
942 #pragma init (myinit) |
|
943 |
|
944 static void |
|
945 myinit() |
|
946 { |
|
947 |
|
948 if (! initialized) { |
|
949 #ifndef __GNUC__ |
|
950 void *handle; |
|
951 const char *libdem = "libdemangle.so.1"; |
|
952 |
|
953 /* load libdemangle if we can and need to (only try this once) */ |
|
954 if ((handle = dlopen(libdem, RTLD_LAZY)) != nullptr) { |
|
955 demf = (demf_t *)dlsym(handle, |
|
956 "cplus_demangle"); /*lint !e611 */ |
|
957 /* |
|
958 * lint override above is to prevent lint from |
|
959 * complaining about "suspicious cast". |
|
960 */ |
|
961 } |
|
962 #endif /*__GNUC__*/ |
|
963 } |
|
964 initialized = 1; |
|
965 } |
|
966 |
|
967 |
|
968 static int |
|
969 load_address(void * pc, void * arg) |
|
970 { |
|
971 static struct bucket table[2048]; |
|
972 static mutex_t lock; |
|
973 struct bucket * ptr; |
|
974 struct my_user_args * args = (struct my_user_args *) arg; |
|
975 |
|
976 unsigned int val = NS_PTR_TO_INT32(pc); |
|
977 |
|
978 ptr = table + ((val >> 2)&2047); |
|
979 |
|
980 mutex_lock(&lock); |
|
981 while (ptr->next) { |
|
982 if (ptr->next->pc == pc) |
|
983 break; |
|
984 ptr = ptr->next; |
|
985 } |
|
986 |
|
987 int stop = 0; |
|
988 if (ptr->next) { |
|
989 mutex_unlock(&lock); |
|
990 } else { |
|
991 (args->callback)(pc, args->closure); |
|
992 args->numFrames++; |
|
993 if (args->maxFrames != 0 && args->numFrames == args->maxFrames) |
|
994 stop = 1; // causes us to stop getting frames |
|
995 |
|
996 ptr->next = newbucket(pc); |
|
997 mutex_unlock(&lock); |
|
998 } |
|
999 return stop; |
|
1000 } |
|
1001 |
|
1002 |
|
1003 static struct bucket * |
|
1004 newbucket(void * pc) |
|
1005 { |
|
1006 struct bucket * ptr = (struct bucket *) malloc(sizeof (*ptr)); |
|
1007 static int index; /* protected by lock in caller */ |
|
1008 |
|
1009 ptr->index = index++; |
|
1010 ptr->next = nullptr; |
|
1011 ptr->pc = pc; |
|
1012 return (ptr); |
|
1013 } |
|
1014 |
|
1015 |
|
1016 static struct frame * |
|
1017 csgetframeptr() |
|
1018 { |
|
1019 ucontext_t u; |
|
1020 struct frame *fp; |
|
1021 |
|
1022 (void) getcontext(&u); |
|
1023 |
|
1024 fp = (struct frame *) |
|
1025 ((char *)u.uc_mcontext.gregs[FRAME_PTR_REGISTER] + |
|
1026 STACK_BIAS); |
|
1027 |
|
1028 /* make sure to return parents frame pointer.... */ |
|
1029 |
|
1030 return ((struct frame *)((ulong_t)fp->fr_savfp + STACK_BIAS)); |
|
1031 } |
|
1032 |
|
1033 |
|
1034 static void |
|
1035 cswalkstack(struct frame *fp, int (*operate_func)(void *, void *, void *), |
|
1036 void *usrarg) |
|
1037 { |
|
1038 |
|
1039 while (fp != 0 && fp->fr_savpc != 0) { |
|
1040 |
|
1041 if (operate_func((void *)fp->fr_savpc, nullptr, usrarg) != 0) |
|
1042 break; |
|
1043 /* |
|
1044 * watch out - libthread stacks look funny at the top |
|
1045 * so they may not have their STACK_BIAS set |
|
1046 */ |
|
1047 |
|
1048 fp = (struct frame *)((ulong_t)fp->fr_savfp + |
|
1049 (fp->fr_savfp?(ulong_t)STACK_BIAS:0)); |
|
1050 } |
|
1051 } |
|
1052 |
|
1053 |
|
1054 static void |
|
1055 cs_operate(int (*operate_func)(void *, void *, void *), void * usrarg) |
|
1056 { |
|
1057 cswalkstack(csgetframeptr(), operate_func, usrarg); |
|
1058 } |
|
1059 |
|
1060 EXPORT_XPCOM_API(nsresult) |
|
1061 NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
1062 uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, |
|
1063 void *aPlatformData) |
|
1064 { |
|
1065 MOZ_ASSERT(!aThread); |
|
1066 MOZ_ASSERT(!aPlatformData); |
|
1067 struct my_user_args args; |
|
1068 |
|
1069 StackWalkInitCriticalAddress(); |
|
1070 |
|
1071 if (!initialized) |
|
1072 myinit(); |
|
1073 |
|
1074 args.callback = aCallback; |
|
1075 args.skipFrames = aSkipFrames; /* XXX Not handled! */ |
|
1076 args.maxFrames = aMaxFrames; |
|
1077 args.numFrames = 0; |
|
1078 args.closure = aClosure; |
|
1079 cs_operate(load_address, &args); |
|
1080 return args.numFrames == 0 ? NS_ERROR_FAILURE : NS_OK; |
|
1081 } |
|
1082 |
|
1083 EXPORT_XPCOM_API(nsresult) |
|
1084 NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) |
|
1085 { |
|
1086 aDetails->library[0] = '\0'; |
|
1087 aDetails->loffset = 0; |
|
1088 aDetails->filename[0] = '\0'; |
|
1089 aDetails->lineno = 0; |
|
1090 aDetails->function[0] = '\0'; |
|
1091 aDetails->foffset = 0; |
|
1092 |
|
1093 char dembuff[4096]; |
|
1094 Dl_info info; |
|
1095 |
|
1096 if (dladdr(aPC, & info)) { |
|
1097 if (info.dli_fname) { |
|
1098 PL_strncpyz(aDetails->library, info.dli_fname, |
|
1099 sizeof(aDetails->library)); |
|
1100 aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; |
|
1101 } |
|
1102 if (info.dli_sname) { |
|
1103 aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; |
|
1104 #ifdef __GNUC__ |
|
1105 DemangleSymbol(info.dli_sname, dembuff, sizeof(dembuff)); |
|
1106 #else |
|
1107 if (!demf || demf(info.dli_sname, dembuff, sizeof (dembuff))) |
|
1108 dembuff[0] = 0; |
|
1109 #endif /*__GNUC__*/ |
|
1110 PL_strncpyz(aDetails->function, |
|
1111 (dembuff[0] != '\0') ? dembuff : info.dli_sname, |
|
1112 sizeof(aDetails->function)); |
|
1113 } |
|
1114 } |
|
1115 |
|
1116 return NS_OK; |
|
1117 } |
|
1118 |
|
1119 EXPORT_XPCOM_API(nsresult) |
|
1120 NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, |
|
1121 char *aBuffer, uint32_t aBufferSize) |
|
1122 { |
|
1123 snprintf(aBuffer, aBufferSize, "%p %s:%s+0x%lx\n", |
|
1124 aPC, |
|
1125 aDetails->library[0] ? aDetails->library : "??", |
|
1126 aDetails->function[0] ? aDetails->function : "??", |
|
1127 aDetails->foffset); |
|
1128 return NS_OK; |
|
1129 } |
|
1130 |
|
1131 #else // not __sun-specific |
|
1132 |
|
1133 #if __GLIBC__ > 2 || __GLIBC_MINOR > 1 |
|
1134 #define HAVE___LIBC_STACK_END 1 |
|
1135 #else |
|
1136 #define HAVE___LIBC_STACK_END 0 |
|
1137 #endif |
|
1138 |
|
1139 #if HAVE___LIBC_STACK_END |
|
1140 extern void *__libc_stack_end; // from ld-linux.so |
|
1141 #endif |
|
1142 namespace mozilla { |
|
1143 nsresult |
|
1144 FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
1145 uint32_t aMaxFrames, void *aClosure, void **bp, |
|
1146 void *aStackEnd) |
|
1147 { |
|
1148 // Stack walking code courtesy Kipp's "leaky". |
|
1149 |
|
1150 int32_t skip = aSkipFrames; |
|
1151 uint32_t numFrames = 0; |
|
1152 while (1) { |
|
1153 void **next = (void**)*bp; |
|
1154 // bp may not be a frame pointer on i386 if code was compiled with |
|
1155 // -fomit-frame-pointer, so do some sanity checks. |
|
1156 // (bp should be a frame pointer on ppc(64) but checking anyway may help |
|
1157 // a little if the stack has been corrupted.) |
|
1158 // We don't need to check against the begining of the stack because |
|
1159 // we can assume that bp > sp |
|
1160 if (next <= bp || |
|
1161 next > aStackEnd || |
|
1162 (long(next) & 3)) { |
|
1163 break; |
|
1164 } |
|
1165 #if (defined(__ppc__) && defined(XP_MACOSX)) || defined(__powerpc64__) |
|
1166 // ppc mac or powerpc64 linux |
|
1167 void *pc = *(bp+2); |
|
1168 bp += 3; |
|
1169 #else // i386 or powerpc32 linux |
|
1170 void *pc = *(bp+1); |
|
1171 bp += 2; |
|
1172 #endif |
|
1173 if (IsCriticalAddress(pc)) { |
|
1174 printf("Aborting stack trace, PC is critical\n"); |
|
1175 return NS_ERROR_UNEXPECTED; |
|
1176 } |
|
1177 if (--skip < 0) { |
|
1178 // Assume that the SP points to the BP of the function |
|
1179 // it called. We can't know the exact location of the SP |
|
1180 // but this should be sufficient for our use the SP |
|
1181 // to order elements on the stack. |
|
1182 (*aCallback)(pc, bp, aClosure); |
|
1183 numFrames++; |
|
1184 if (aMaxFrames != 0 && numFrames == aMaxFrames) |
|
1185 break; |
|
1186 } |
|
1187 bp = next; |
|
1188 } |
|
1189 return numFrames == 0 ? NS_ERROR_FAILURE : NS_OK; |
|
1190 } |
|
1191 |
|
1192 } |
|
1193 |
|
1194 #define X86_OR_PPC (defined(__i386) || defined(PPC) || defined(__ppc__)) |
|
1195 #if X86_OR_PPC && (NSSTACKWALK_SUPPORTS_MACOSX || NSSTACKWALK_SUPPORTS_LINUX) // i386 or PPC Linux or Mac stackwalking code |
|
1196 |
|
1197 EXPORT_XPCOM_API(nsresult) |
|
1198 NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
1199 uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, |
|
1200 void *aPlatformData) |
|
1201 { |
|
1202 MOZ_ASSERT(!aThread); |
|
1203 MOZ_ASSERT(!aPlatformData); |
|
1204 StackWalkInitCriticalAddress(); |
|
1205 |
|
1206 // Get the frame pointer |
|
1207 void **bp; |
|
1208 #if defined(__i386) |
|
1209 __asm__( "movl %%ebp, %0" : "=g"(bp)); |
|
1210 #else |
|
1211 // It would be nice if this worked uniformly, but at least on i386 and |
|
1212 // x86_64, it stopped working with gcc 4.1, because it points to the |
|
1213 // end of the saved registers instead of the start. |
|
1214 bp = (void**) __builtin_frame_address(0); |
|
1215 #endif |
|
1216 |
|
1217 void *stackEnd; |
|
1218 #if HAVE___LIBC_STACK_END |
|
1219 stackEnd = __libc_stack_end; |
|
1220 #else |
|
1221 stackEnd = reinterpret_cast<void*>(-1); |
|
1222 #endif |
|
1223 return FramePointerStackWalk(aCallback, aSkipFrames, aMaxFrames, |
|
1224 aClosure, bp, stackEnd); |
|
1225 |
|
1226 } |
|
1227 |
|
1228 #elif defined(HAVE__UNWIND_BACKTRACE) |
|
1229 |
|
1230 // libgcc_s.so symbols _Unwind_Backtrace@@GCC_3.3 and _Unwind_GetIP@@GCC_3.0 |
|
1231 #include <unwind.h> |
|
1232 |
|
1233 struct unwind_info { |
|
1234 NS_WalkStackCallback callback; |
|
1235 int skip; |
|
1236 int maxFrames; |
|
1237 int numFrames; |
|
1238 bool isCriticalAbort; |
|
1239 void *closure; |
|
1240 }; |
|
1241 |
|
1242 static _Unwind_Reason_Code |
|
1243 unwind_callback (struct _Unwind_Context *context, void *closure) |
|
1244 { |
|
1245 unwind_info *info = static_cast<unwind_info *>(closure); |
|
1246 void *pc = reinterpret_cast<void *>(_Unwind_GetIP(context)); |
|
1247 // TODO Use something like '_Unwind_GetGR()' to get the stack pointer. |
|
1248 if (IsCriticalAddress(pc)) { |
|
1249 printf("Aborting stack trace, PC is critical\n"); |
|
1250 info->isCriticalAbort = true; |
|
1251 // We just want to stop the walk, so any error code will do. Using |
|
1252 // _URC_NORMAL_STOP would probably be the most accurate, but it is not |
|
1253 // defined on Android for ARM. |
|
1254 return _URC_FOREIGN_EXCEPTION_CAUGHT; |
|
1255 } |
|
1256 if (--info->skip < 0) { |
|
1257 (*info->callback)(pc, nullptr, info->closure); |
|
1258 info->numFrames++; |
|
1259 if (info->maxFrames != 0 && info->numFrames == info->maxFrames) { |
|
1260 // Again, any error code that stops the walk will do. |
|
1261 return _URC_FOREIGN_EXCEPTION_CAUGHT; |
|
1262 } |
|
1263 } |
|
1264 return _URC_NO_REASON; |
|
1265 } |
|
1266 |
|
1267 EXPORT_XPCOM_API(nsresult) |
|
1268 NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
1269 uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, |
|
1270 void *aPlatformData) |
|
1271 { |
|
1272 MOZ_ASSERT(!aThread); |
|
1273 MOZ_ASSERT(!aPlatformData); |
|
1274 StackWalkInitCriticalAddress(); |
|
1275 unwind_info info; |
|
1276 info.callback = aCallback; |
|
1277 info.skip = aSkipFrames + 1; |
|
1278 info.maxFrames = aMaxFrames; |
|
1279 info.numFrames = 0; |
|
1280 info.isCriticalAbort = false; |
|
1281 info.closure = aClosure; |
|
1282 |
|
1283 (void)_Unwind_Backtrace(unwind_callback, &info); |
|
1284 |
|
1285 // We ignore the return value from _Unwind_Backtrace and instead determine |
|
1286 // the outcome from |info|. There are two main reasons for this: |
|
1287 // - On ARM/Android bionic's _Unwind_Backtrace usually (always?) returns |
|
1288 // _URC_FAILURE. See |
|
1289 // https://bugzilla.mozilla.org/show_bug.cgi?id=717853#c110. |
|
1290 // - If aMaxFrames != 0, we want to stop early, and the only way to do that |
|
1291 // is to make unwind_callback return something other than _URC_NO_REASON, |
|
1292 // which causes _Unwind_Backtrace to return a non-success code. |
|
1293 if (info.isCriticalAbort) |
|
1294 return NS_ERROR_UNEXPECTED; |
|
1295 return info.numFrames == 0 ? NS_ERROR_FAILURE : NS_OK; |
|
1296 } |
|
1297 |
|
1298 #endif |
|
1299 |
|
1300 EXPORT_XPCOM_API(nsresult) |
|
1301 NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) |
|
1302 { |
|
1303 aDetails->library[0] = '\0'; |
|
1304 aDetails->loffset = 0; |
|
1305 aDetails->filename[0] = '\0'; |
|
1306 aDetails->lineno = 0; |
|
1307 aDetails->function[0] = '\0'; |
|
1308 aDetails->foffset = 0; |
|
1309 |
|
1310 Dl_info info; |
|
1311 int ok = dladdr(aPC, &info); |
|
1312 if (!ok) { |
|
1313 return NS_OK; |
|
1314 } |
|
1315 |
|
1316 PL_strncpyz(aDetails->library, info.dli_fname, sizeof(aDetails->library)); |
|
1317 aDetails->loffset = (char*)aPC - (char*)info.dli_fbase; |
|
1318 |
|
1319 const char * symbol = info.dli_sname; |
|
1320 if (!symbol || symbol[0] == '\0') { |
|
1321 return NS_OK; |
|
1322 } |
|
1323 |
|
1324 DemangleSymbol(symbol, aDetails->function, sizeof(aDetails->function)); |
|
1325 |
|
1326 if (aDetails->function[0] == '\0') { |
|
1327 // Just use the mangled symbol if demangling failed. |
|
1328 PL_strncpyz(aDetails->function, symbol, sizeof(aDetails->function)); |
|
1329 } |
|
1330 |
|
1331 aDetails->foffset = (char*)aPC - (char*)info.dli_saddr; |
|
1332 return NS_OK; |
|
1333 } |
|
1334 |
|
1335 EXPORT_XPCOM_API(nsresult) |
|
1336 NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, |
|
1337 char *aBuffer, uint32_t aBufferSize) |
|
1338 { |
|
1339 if (!aDetails->library[0]) { |
|
1340 snprintf(aBuffer, aBufferSize, "UNKNOWN %p\n", aPC); |
|
1341 } else if (!aDetails->function[0]) { |
|
1342 snprintf(aBuffer, aBufferSize, "UNKNOWN [%s +0x%08" PRIXPTR "]\n", |
|
1343 aDetails->library, aDetails->loffset); |
|
1344 } else { |
|
1345 snprintf(aBuffer, aBufferSize, "%s+0x%08" PRIXPTR |
|
1346 " [%s +0x%08" PRIXPTR "]\n", |
|
1347 aDetails->function, aDetails->foffset, |
|
1348 aDetails->library, aDetails->loffset); |
|
1349 } |
|
1350 return NS_OK; |
|
1351 } |
|
1352 |
|
1353 #endif |
|
1354 |
|
1355 #else // unsupported platform. |
|
1356 |
|
1357 EXPORT_XPCOM_API(nsresult) |
|
1358 NS_StackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
1359 uint32_t aMaxFrames, void *aClosure, uintptr_t aThread, |
|
1360 void *aPlatformData) |
|
1361 { |
|
1362 MOZ_ASSERT(!aThread); |
|
1363 MOZ_ASSERT(!aPlatformData); |
|
1364 return NS_ERROR_NOT_IMPLEMENTED; |
|
1365 } |
|
1366 |
|
1367 namespace mozilla { |
|
1368 nsresult |
|
1369 FramePointerStackWalk(NS_WalkStackCallback aCallback, uint32_t aSkipFrames, |
|
1370 void *aClosure, void **bp) |
|
1371 { |
|
1372 return NS_ERROR_NOT_IMPLEMENTED; |
|
1373 } |
|
1374 } |
|
1375 |
|
1376 EXPORT_XPCOM_API(nsresult) |
|
1377 NS_DescribeCodeAddress(void *aPC, nsCodeAddressDetails *aDetails) |
|
1378 { |
|
1379 aDetails->library[0] = '\0'; |
|
1380 aDetails->loffset = 0; |
|
1381 aDetails->filename[0] = '\0'; |
|
1382 aDetails->lineno = 0; |
|
1383 aDetails->function[0] = '\0'; |
|
1384 aDetails->foffset = 0; |
|
1385 return NS_ERROR_NOT_IMPLEMENTED; |
|
1386 } |
|
1387 |
|
1388 EXPORT_XPCOM_API(nsresult) |
|
1389 NS_FormatCodeAddressDetails(void *aPC, const nsCodeAddressDetails *aDetails, |
|
1390 char *aBuffer, uint32_t aBufferSize) |
|
1391 { |
|
1392 aBuffer[0] = '\0'; |
|
1393 return NS_ERROR_NOT_IMPLEMENTED; |
|
1394 } |
|
1395 |
|
1396 #endif |