windowswinapistack-framedbghelp

What advantages does StackWalkEx have over StackWalk64?


The Microsoft Windows DbgHelp library provides three functions for walking the stack:

Unfortunately, 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:

  1. What is InlineFrameContext for?
  2. What does SYM_STKWALK_FORCE_FRAMEPTR do?
  3. Can anyone give a real world use case where using StackWalkEx would provide tangible benefit over StackWalk64?

Solution

  • 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:

    1. you have no PDB information to help StackWalkEx, and
    2. it's not giving back a useful stack trace and it looks like it should.

    Then 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.