The Microsoft Windows DbgHelp library provides three functions for walking the stack:
StackWalk
functionStackWalk64
functionStackWalkEx
functionUnfortunately, Microsoft's documentation does a poor job of explaining what is the difference between these three functions, and what advantages the newer function have over the older ones.
In the case of StackWalk
versus StackWalk64
, the difference is obvious: StackWalk
uses the STACKFRAME
structure, which represents memory addresses using the ADDRESS
structure, which uses a DWORD
to store the memory address – hence, StackWalk
can only handle 32-bit memory addresses. By contrast, StackWalk64
uses the STACKFRAME64
structure, which represents memory addresses using the ADDRESS64
structure, which uses a 64-bit DWORD64
to store the memory address. Hence, StackWalk64
can support 64-bit code, StackWalk
can only support 32-bit code (the API also appears to have some support for legacy 16-bit Windows code, but that is largely irrelevant nowadays.) The difference is in the name.
However, it is less clear what the difference between StackWalk64
and StackWalkEx
is. The main differences I can see, is that StackWalkEx
uses the newer STACKFRAME_EX
data structure, which adds an InlineFrameContext
member – but I can't find any documentation explaining what that is for. It also adds a StackFrameSize
member, whose purpose is rather obvious: to permit future additions to the STACKFRAME_EX
structure without having to create a whole new API (StackWalkEx2
or whatever). As well as the additions to the structure, StackWalkEx
adds a Flags
parameter, which as well as the default, supports a SYM_STKWALK_FORCE_FRAMEPTR
– but the documentation doesn't explain what that flag does.
So, in summary:
InlineFrameContext
for?SYM_STKWALK_FORCE_FRAMEPTR
do?StackWalkEx
would provide tangible benefit over StackWalk64
?Like you pointed out, there is not a lot of documentation for this.
So, this is just deduced from experience working with these APIs and knowing what the VS C++ compiler does.
The comment link to an answer looks to give a good breakdown of the call stack.
The VS C++ compiler optimizer can inline code when compiling/linking with Inline Function Expansion. This means there is no call stack when calling into an inlined method/function.
When this happens and you debug the application, you may notice you can still step into these inlined methods/functions. This happens because of the extra inline information in the PDB data.
When walking the stack, you can use the InlineFrameContext
to get inline information like filename/line number/variable information using SymFromInlineContextW
/SymGetLineFromInlineContextW
/SymSetScopeFromInlineContext
.
e.g.
if (frame.StackFrameSize >= sizeof(STACKFRAME_EX) && frame.InlineFrameContext != INLINE_FRAME_CONTEXT_IGNORE && frame.InlineFrameContext != INLINE_FRAME_CONTEXT_INIT)
{
if (!SymFromInlineContextW(process_, address, frame.InlineFrameContext, &info.symbol_displacement, symbol_.get()))
{
return std::move(info);
}
info.frame_content_found = true;
info.symbol_name = symbol_->Name;
info.in_line = static_cast<sym_tag_enum>(symbol_->Tag) == sym_tag_enum::Inlinee;
if (SymGetLineFromInlineContextW(process_, address, frame.InlineFrameContext, it->second.base, &info.line_displacement, &line_))
{
info.line_number = line_.LineNumber;
info.file_name = line_.FileName;
}
if (SymSetScopeFromInlineContext(process_, address, frame.InlineFrameContext))
{
local_variables_walk(info.local_variables, info.parameters, type, frame.AddrFrame.Offset, thread_context, {}, symbol_walk_options::inline_variables);
}
}
I've never used SYM_STKWALK_FORCE_FRAMEPTR
, but I would guess that it's forcing StackWalkEx
to assume that the thread context you have pointing to uses frame pointers. With the C++ compiler Frame-Pointer Omission option, it can generate code that does not use frame pointers in all cases. This makes it harder for StackWalkEx
to figure out what to do. I believe StackWalkEx
uses some sort of heuristics from any given context to figure out where the function return call setup is, but this can fail.
So, the only time I can think of when to use this switch is if:
StackWalkEx
, andThen you can try it to see if it gives back a better stack trace.
I would always use StackWalkEx
now over StackWalk64
due to being newer, and it allows you to extract more information out of a call stack walk than the StackWalk64
results (with information like inline symbol data and parameter and local variable data). I have also noticed that it gives better call stack results in cases where you don't have PDB information.