1 //=============================================================================
3 * @file Stack_Trace.cpp
5 * @brief Encapsulate string representation of stack trace.
7 * Some platform-specific areas of this code have been adapted from
8 * examples found elsewhere. Specifically,
9 * - the GLIBC stack generation uses the documented "backtrace" API
10 * and is adapted from examples shown in relevant documentation
11 * and repeated elsewhere, e.g.,
12 * http://www.linuxselfhelp.com/gnu/glibc/html_chapter/libc_33.html
13 * - VxWorks kernel-mode stack tracing is adapted from a code example
14 * in the VxWorks FAQ at http://www.xs4all.nl/~borkhuis/vxworks/vxw_pt5.html
15 * although the undocumented functions it uses are also mentioned in
16 * various documents available on the WindRiver support website.
18 * If you add support for a new platform, please add a bullet to the
19 * above list with durable references to the origins of your code.
21 //=============================================================================
23 #include "ace/Stack_Trace.h"
24 #include "ace/Min_Max.h"
25 #include "ace/OS_NS_string.h"
26 #include "ace/OS_NS_stdio.h"
28 ACE_BEGIN_VERSIONED_NAMESPACE_DECL
31 This is ugly, simply because it's very platform-specific.
34 const char ACE_Stack_Trace::UNSUPPORTED
[] = "<stack traces unsupported platform>";
35 const char ACE_Stack_Trace::UNABLE_TO_GET_TRACE
[] = "<unable to get trace>";
37 ACE_Stack_Trace::ACE_Stack_Trace (ssize_t starting_frame_offset
, size_t num_frames
)
40 // cannot initialize arrays, so we must assign.
42 this->generate_trace (starting_frame_offset
, num_frames
);
46 ACE_Stack_Trace::c_str () const
48 return &this->buf_
[0];
52 determine_starting_frame (ssize_t initial_frame
, ssize_t offset
)
54 return ACE_MAX( initial_frame
+ offset
, static_cast<ssize_t
>(0));
57 #if defined(ACE_FACE_SAFETY_BASE) && !defined(ACE_FACE_DEV)
59 ACE_Stack_Trace::generate_trace (ssize_t starting_frame_offset
, size_t num_frames
)
61 ACE_UNUSED_ARG (starting_frame_offset
);
62 ACE_UNUSED_ARG (num_frames
);
63 ACE_OS::strcpy (&this->buf_
[0], UNABLE_TO_GET_TRACE
);
66 #elif (defined(__GLIBC__) || defined(ACE_HAS_EXECINFO_H))
67 // This is the code for glibc
68 # include <execinfo.h>
71 ACE_Stack_Trace::generate_trace (ssize_t starting_frame_offset
, size_t num_frames
)
73 const size_t MAX_FRAMES
= 128;
74 const ssize_t INITIAL_FRAME
= 3;
76 void* stack
[MAX_FRAMES
];
77 size_t stack_size
= 0;
81 num_frames
= MAX_FRAMES
;
83 size_t starting_frame
=
84 determine_starting_frame (INITIAL_FRAME
, starting_frame_offset
);
86 stack_size
= ::backtrace (&stack
[0], sizeof(stack
)/sizeof(stack
[0]));
89 stack_syms
= ::backtrace_symbols (stack
, stack_size
);
91 for (size_t i
= starting_frame
;
92 i
< stack_size
&& num_frames
> 0;
95 // this could be more efficient by remembering where we left off in buf_
96 char *symp
= &stack_syms
[i
][0];
97 while (this->buflen_
< SYMBUFSIZ
- 2 && *symp
!= '\0')
99 this->buf_
[this->buflen_
++] = *symp
++;
101 this->buf_
[this->buflen_
++] = '\n'; // put a newline at the end
103 this->buf_
[this->buflen_
] = '\0'; // zero terminate the string
109 ACE_OS::strcpy (&this->buf_
[0], UNABLE_TO_GET_TRACE
);
112 #elif defined(VXWORKS) && !defined(__RTP__)
114 # include <taskLib.h> // hopefully this is enough to get all the necessary #defines.
116 struct ACE_Stack_Trace_stackstate
118 ACE_Stack_Trace_stackstate (char* b
, size_t& bl
, size_t nf
, size_t sf
)
119 : buf(b
), buflen(bl
), num_frames(nf
), starting_frame(sf
)
125 size_t starting_frame
;
128 //@TODO: Replace with a TSS-based pointer to avoid problems in multithreaded environs,
129 // or use a mutex to serialize access to this.
130 static ACE_Stack_Trace_stackstate
* ACE_Stack_Trace_stateptr
= 0;
133 ACE_Stack_Trace_Add_Frame_To_Buf (INSTR
*caller
,
136 ACE_VX_USR_ARG_T
*args
)
138 if (ACE_Stack_Trace_stateptr
== 0)
141 ACE_Stack_Trace_stackstate
*stackstate
= ACE_Stack_Trace_stateptr
;
143 // Decrement the num_frames and starting_frame elements,
144 // then see if we're ready to start or ready to finish.
145 --stackstate
->num_frames
;
146 --stackstate
->starting_frame
;
148 if (stackstate
->num_frames
== 0 || stackstate
->starting_frame
> 0)
151 // These are references so that the structure gets updated
152 // in the code below.
153 char*& buf
= stackstate
->buf
;
154 size_t& len
= stackstate
->buflen
;
156 // At some point try using symFindByValue() to lookup func (and caller?)
157 // to print out symbols rather than simply addresses.
159 // VxWorks can pass -1 for "nargs" if there was an error
163 len
+= ACE_OS::sprintf (&buf
[len
], "%p: %p (", caller
, func
);
164 for (int i
= 0; i
< nargs
; ++i
)
167 len
+= ACE_OS::sprintf (&buf
[len
], ", ");
168 len
+= ACE_OS::sprintf(&buf
[len
], "0x" ACE_VX_ARG_FORMAT
, args
[i
]);
171 len
+= ACE_OS::sprintf(&buf
[len
], ")\n");
175 ACE_Stack_Trace::generate_trace (ssize_t starting_frame_offset
,
178 const size_t MAX_FRAMES
= 128;
179 const ssize_t INITIAL_FRAME
= 3;
182 num_frames
= MAX_FRAMES
;
184 size_t starting_frame
=
185 determine_starting_frame (INITIAL_FRAME
, starting_frame_offset
);
187 ACE_Stack_Trace_stackstate
state (&this->buf_
[0], this->buflen_
,
188 num_frames
, starting_frame
);
192 taskRegsGet (taskIdSelf(), ®s
);
193 // Maybe we should take a lock here to guard stateptr?
194 ACE_Stack_Trace_stateptr
= &state
;
195 trcStack (®s
, (FUNCPTR
)ACE_Stack_Trace_Add_Frame_To_Buf
, taskIdSelf ());
199 #elif defined(VXWORKS) && defined(__RTP__)
201 # include <taskLib.h>
202 # include <private/trcLibP.h>
204 // See memEdrLib.c in VxWorks RTP sources for an example of stack tracing.
206 static STATUS
ace_vx_rtp_pc_validate (INSTR
*pc
, TRC_OS_CTX
*)
208 return ALIGNED (pc
, sizeof (INSTR
)) ? OK
: ERROR
;
212 ACE_Stack_Trace::generate_trace (ssize_t starting_frame_offset
,
215 const size_t MAX_FRAMES
= 128;
216 const ssize_t INITIAL_FRAME
= 2;
218 if (num_frames
== 0) num_frames
= MAX_FRAMES
;
219 size_t starting_frame
=
220 determine_starting_frame (INITIAL_FRAME
, starting_frame_offset
);
226 if (taskInfoGet (taskIdSelf (), &desc
) == ERROR
) return;
229 osCtx
.stackBase
= desc
.td_pStackBase
;
230 osCtx
.stackEnd
= desc
.td_pStackEnd
;
231 #if (ACE_VXWORKS < 0x690)
232 osCtx
.pcValidateRtn
= reinterpret_cast<FUNCPTR
> (ace_vx_rtp_pc_validate
);
234 // reinterpret_cast causes an error
235 osCtx
.pcValidateRtn
= ace_vx_rtp_pc_validate
;
238 char *fp
= _WRS_FRAMEP_FROM_JMP_BUF (regs
);
239 INSTR
*pc
= _WRS_RET_PC_FROM_JMP_BUF (regs
);
241 for (size_t depth
= 0; depth
< num_frames
+ starting_frame
; ++depth
)
247 if (trcLibFuncs
.lvlInfoGet (fp
, pc
, &osCtx
, &prevFp
, &prevPc
, &prevFn
)
250 ACE_OS::strcpy (this->buf_
, UNABLE_TO_GET_TRACE
);
254 if(prevPc
== 0 || prevFp
== 0) break;
256 if (depth
>= starting_frame
)
258 //Hopefully a future version of VxWorks will have a system call
259 //for an RTP to query its own symbols, but this is not possible now.
260 //An enhancement request has been filed under WIND00123307.
261 const char *fnName
= "(no symbols)";
263 static const int N_ARGS
= 12;
264 ACE_VX_USR_ARG_T buf
[N_ARGS
];
265 ACE_VX_USR_ARG_T
*pArgs
= 0;
267 trcLibFuncs
.lvlArgsGet (prevPc
, prevFn
, prevFp
,
268 buf
, N_ARGS
, &pArgs
);
270 // VxWorks can return -1 for "numArgs" if there was an error
271 if (numArgs
== -1) numArgs
= 0;
273 size_t len
= ACE_OS::strlen (this->buf_
);
274 size_t space
= SYMBUFSIZ
- len
- 1;
275 char *cursor
= this->buf_
+ len
;
276 size_t written
= ACE_OS::snprintf (cursor
, space
, "%p %s",
281 if (space
< 1) return; //no point in logging when we're out of buffer
282 for (int arg
= 0; numArgs
!= -1 && pArgs
&& arg
< numArgs
; ++arg
)
284 if (arg
== 0) *cursor
++ = '(', --space
;
285 written
= ACE_OS::snprintf (cursor
, space
,
286 (arg
< numArgs
- 1) ?
287 ACE_VX_ARG_FORMAT
", " :
292 if (space
&& arg
== numArgs
- 1) *cursor
++ = ')', --space
;
294 if (space
) *cursor
++ = '\n', --space
;
295 *cursor
++ = 0; //we saved space for the null terminator
302 #elif defined(ACE_WIN64) && (_WIN32_WINNT <= _WIN32_WINNT_WIN2K)
303 # if defined(_MSC_VER)
304 # define STRING2(X) #X
305 # define STRING(X) STRING2(X)
306 # pragma message (__FILE__ "(" STRING(__LINE__) ") : warning: stack traces"\
307 " can't be generated on 64-bit Windows when _WIN32_WINNT is less than "\
313 ACE_Stack_Trace::generate_trace (ssize_t
, size_t)
315 ACE_OS::strcpy (&this->buf_
[0], "<stack traces unsupported on Win64 unless "
316 "ACE is built with _WIN32_WINNT set to 0x501 or above>");
319 #elif defined(ACE_WIN32) && !defined (__MINGW32__) && !defined(__BORLANDC__)
320 # include <windows.h>
321 # include <Dbghelp.h>
323 # define MAXTEXT 5000
326 //@TODO: Test with WCHAR
327 //@TODO: Need a common CriticalSection since dbghelp is not thread-safe
329 typedef struct _dbghelp_functions
331 HMODULE hMod
; //our handle to dbghelp.dll
333 //these already have typedefs in DbgHelp.h
334 DWORD64 (WINAPI
*SymGetModuleBase64
) (HANDLE hProc
, DWORD64 dwAddr
);
335 PVOID (WINAPI
*SymFunctionTableAccess64
) (HANDLE hProc
, DWORD64 AddrBase
);
337 typedef BOOL (WINAPI
*SymFromAddr_t
)
338 (HANDLE hProc
, DWORD64 Addr
, PDWORD64 Disp
, PSYMBOL_INFO Symbol
);
339 SymFromAddr_t SymFromAddr
;
341 typedef BOOL (WINAPI
*SymGetLineFromAddr64_t
) (HANDLE hProc
, DWORD64 dwAddr
,
342 PDWORD pdwDisplacement
,
343 PIMAGEHLP_LINE64 Line
);
344 SymGetLineFromAddr64_t SymGetLineFromAddr64
;
346 typedef DWORD (WINAPI
*SymSetOptions_t
) (DWORD SymOptions
);
347 SymSetOptions_t SymSetOptions
;
349 typedef DWORD (WINAPI
*SymGetOptions_t
) ();
350 SymGetOptions_t SymGetOptions
;
352 typedef BOOL (WINAPI
*SymInitialize_t
) (HANDLE hProc
, PCTSTR UserSearchPath
,
354 SymInitialize_t SymInitialize
;
357 (WINAPI
*StackWalk64_t
) (DWORD MachineType
, HANDLE hPRoc
, HANDLE hThr
,
358 LPSTACKFRAME64 StackFrame
, PVOID ContextRecord
,
359 PREAD_PROCESS_MEMORY_ROUTINE64 RMRoutine
,
360 PFUNCTION_TABLE_ACCESS_ROUTINE64 FTARoutine
,
361 PGET_MODULE_BASE_ROUTINE64 GMBRoutine
,
362 PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
363 StackWalk64_t StackWalk64
;
365 typedef BOOL (WINAPI
*SymCleanup_t
) (HANDLE hProc
);
366 SymCleanup_t SymCleanup
;
370 # pragma warning (push)
371 # pragma warning (disable:4706)
372 static bool load_dbghelp_library_if_needed (dbghelp_functions
*pDbg
)
374 //@TODO: See codeproject's StackWalker.cpp for the list of locations to
375 //search so we get the "enhanced" dbghelp if the user has it but it is not
377 if (!(pDbg
->hMod
= ACE_TEXT_LoadLibrary (ACE_TEXT ("Dbghelp"))))
380 //@TODO: Cache this so we don't have to re-link every time. When to unload?
382 # define LINK(TYPE, NAME) (pDbg->NAME = \
383 (TYPE) GetProcAddress (pDbg->hMod, #NAME))
384 # define LINK_T(NAME) LINK (dbghelp_functions::NAME##_t, NAME)
385 return LINK (PGET_MODULE_BASE_ROUTINE64
, SymGetModuleBase64
)
386 && LINK (PFUNCTION_TABLE_ACCESS_ROUTINE64
, SymFunctionTableAccess64
)
387 && LINK_T (SymFromAddr
) && LINK_T (SymGetLineFromAddr64
)
388 && LINK_T (SymSetOptions
)&& LINK_T (SymGetOptions
)
389 && LINK_T (SymInitialize
) && LINK_T (StackWalk64
) && LINK_T (SymCleanup
);
393 # pragma warning (pop)
399 dbghelp_functions
*pDbg
;
403 add_frame_to_buf (struct frame_state
const *fs
, void *usrarg
)
405 if (fs
== 0 || usrarg
== 0)
408 char *buf
= static_cast<char *> (usrarg
);
411 DWORD64 dwModBase
= fs
->pDbg
->SymGetModuleBase64 (GetCurrentProcess (),
412 fs
->sf
.AddrPC
.Offset
);
413 if (fs
->pDbg
->SymFromAddr (GetCurrentProcess (),
414 fs
->sf
.AddrPC
.Offset
, &disp
, fs
->pSym
))
416 IMAGEHLP_LINE64 line
= {sizeof (IMAGEHLP_LINE64
)};
418 if (fs
->pDbg
->SymGetLineFromAddr64 (GetCurrentProcess (),
419 fs
->sf
.AddrPC
.Offset
,
422 (void) ACE_OS::snprintf (buf
, ACE_Stack_Trace::SYMBUFSIZ
,
423 "%s%s() %s: %d + 0x%x\n",
424 buf
, fs
->pSym
->Name
, line
.FileName
,
425 line
.LineNumber
, lineDisp
);
429 (void) ACE_OS::snprintf (buf
, ACE_Stack_Trace::SYMBUFSIZ
,
430 "%s%s()+0x%x [0x%x]\n",
431 buf
, fs
->pSym
->Name
, disp
,
432 fs
->sf
.AddrPC
.Offset
- dwModBase
);
437 (void) ACE_OS::snprintf (buf
, ACE_Stack_Trace::SYMBUFSIZ
,
439 buf
, fs
->sf
.AddrPC
.Offset
- dwModBase
);
444 static void emptyStack () { }
446 #if defined (_MSC_VER)
447 # pragma warning(push)
448 // Suppress warning 4748 "/GS can not protect parameters and local
449 // variables from local buffer overrun because optimizations are
450 // disabled in function"
451 # pragma warning(disable: 4748)
452 #endif /* _MSC_VER */
455 cs_operate(int (*func
)(struct frame_state
const *, void *), void *usrarg
,
456 size_t starting_frame
, size_t num_frames
)
458 dbghelp_functions dbg
;
459 if (!load_dbghelp_library_if_needed (&dbg
))
461 ACE_OS::strcpy (static_cast<char *> (usrarg
),
462 "<error loading dbghelp.dll>");
463 if (dbg
.hMod
) FreeLibrary (dbg
.hMod
);
468 ZeroMemory (&fs
.sf
, sizeof (fs
.sf
));
470 emptyStack (); //Not sure what this should do, Chad?
473 ZeroMemory (&c
, sizeof (CONTEXT
));
474 c
.ContextFlags
= CONTEXT_FULL
;
476 # if defined (_M_IX86)
477 DWORD machine
= IMAGE_FILE_MACHINE_I386
;
485 fs
.sf
.AddrPC
.Offset
= c
.Eip
;
486 fs
.sf
.AddrStack
.Offset
= c
.Esp
;
487 fs
.sf
.AddrFrame
.Offset
= c
.Ebp
;
488 fs
.sf
.AddrPC
.Mode
= AddrModeFlat
;
489 fs
.sf
.AddrStack
.Mode
= AddrModeFlat
;
490 fs
.sf
.AddrFrame
.Mode
= AddrModeFlat
;
491 # elif defined (_M_X64)
492 DWORD machine
= IMAGE_FILE_MACHINE_AMD64
;
493 RtlCaptureContext (&c
);
494 fs
.sf
.AddrPC
.Offset
= c
.Rip
;
495 fs
.sf
.AddrFrame
.Offset
= c
.Rsp
; //should be Rbp or Rdi instead?
496 fs
.sf
.AddrStack
.Offset
= c
.Rsp
;
497 fs
.sf
.AddrPC
.Mode
= AddrModeFlat
;
498 fs
.sf
.AddrFrame
.Mode
= AddrModeFlat
;
499 fs
.sf
.AddrStack
.Mode
= AddrModeFlat
;
500 # elif defined (_M_IA64)
501 DWORD machine
= IMAGE_FILE_MACHINE_IA64
;
502 RtlCaptureContext (&c
);
503 fs
.sf
.AddrPC
.Offset
= c
.StIIP
;
504 fs
.sf
.AddrFrame
.Offset
= c
.RsBSP
;
505 fs
.sf
.AddrBStore
.Offset
= c
.RsBSP
;
506 fs
.sf
.AddrStack
.Offset
= c
.IntSp
;
507 fs
.sf
.AddrPC
.Mode
= AddrModeFlat
;
508 fs
.sf
.AddrFrame
.Mode
= AddrModeFlat
;
509 fs
.sf
.AddrBStore
.Mode
= AddrModeFlat
;
510 fs
.sf
.AddrStack
.Mode
= AddrModeFlat
;
511 # elif defined (_M_ARM)
512 DWORD machine
= IMAGE_FILE_MACHINE_ARM
;
513 fs
.sf
.AddrPC
.Offset
= c
.Pc
;
514 fs
.sf
.AddrFrame
.Offset
= c
.R11
;
515 fs
.sf
.AddrStack
.Offset
= c
.Sp
;
516 fs
.sf
.AddrPC
.Mode
= AddrModeFlat
;
517 fs
.sf
.AddrFrame
.Mode
= AddrModeFlat
;
518 fs
.sf
.AddrStack
.Mode
= AddrModeFlat
;
519 # elif defined (_M_ARM64)
520 DWORD machine
= IMAGE_FILE_MACHINE_ARM64
;
521 fs
.sf
.AddrPC
.Offset
= c
.Pc
;
522 fs
.sf
.AddrFrame
.Offset
= c
.Fp
;
523 fs
.sf
.AddrStack
.Offset
= c
.Sp
;
524 fs
.sf
.AddrPC
.Mode
= AddrModeFlat
;
525 fs
.sf
.AddrFrame
.Mode
= AddrModeFlat
;
526 fs
.sf
.AddrStack
.Mode
= AddrModeFlat
;
529 fs
.pSym
= (PSYMBOL_INFO
) GlobalAlloc (GMEM_FIXED
,
530 sizeof (SYMBOL_INFO
) +
531 sizeof (ACE_TCHAR
) * (SYMSIZE
- 1));
532 fs
.pSym
->SizeOfStruct
= sizeof (SYMBOL_INFO
);
533 fs
.pSym
->MaxNameLen
= SYMSIZE
* sizeof (ACE_TCHAR
);
534 dbg
.SymSetOptions (SYMOPT_DEFERRED_LOADS
| SYMOPT_LOAD_LINES
535 | SYMOPT_FAIL_CRITICAL_ERRORS
| dbg
.SymGetOptions ());
536 dbg
.SymInitialize (GetCurrentProcess (), 0, true);
537 //What does the "true" parameter mean when tracing the current process?
539 for (size_t current_frame
= 0; current_frame
< num_frames
+ starting_frame
;
542 BOOL ok
= dbg
.StackWalk64 (machine
,
543 GetCurrentProcess (),
546 dbg
.SymFunctionTableAccess64
,
547 dbg
.SymGetModuleBase64
, 0);
548 if (!ok
|| fs
.sf
.AddrFrame
.Offset
== 0)
551 if (current_frame
< starting_frame
)
557 dbg
.SymCleanup (GetCurrentProcess ());
558 GlobalFree (fs
.pSym
);
559 FreeLibrary (dbg
.hMod
);
564 #if defined (_MSC_VER)
565 // Restore the warning state to what it was before entry.
566 # pragma warning(pop)
567 #endif /* _MSC_VER */
570 ACE_Stack_Trace::generate_trace (ssize_t starting_frame_offset
,
573 const size_t MAX_FRAMES
= 128;
574 const ssize_t INITIAL_FRAME
= 3;
577 num_frames
= MAX_FRAMES
;
579 size_t starting_frame
=
580 determine_starting_frame (INITIAL_FRAME
, starting_frame_offset
);
582 cs_operate (&add_frame_to_buf
, &this->buf_
[0], starting_frame
, num_frames
);
585 #else // Unsupported platform
587 ACE_Stack_Trace::generate_trace (ssize_t
, size_t)
589 ACE_OS::strcpy (&this->buf_
[0], UNSUPPORTED
);
593 ACE_END_VERSIONED_NAMESPACE_DECL