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