smhk

C stack trace in Windows

TL;DR: It’s possible to generate a C stack trace in Windows with GCC, but it’s easier with MSVC or LLVM/Clang. Either way, you need to install the Windows SDK and then compile and link against DbgHelp. With GCC, you also need to convert the DWARF debug information to PDB, which can be done with a tool called cv2pdb, but that depends upon installing the Visual Studio build tools.

These notes cover how to print a stack trace from a C application built using Mingw-w64 (with GCC or LLVM/Clang)1 in CMake on Windows 11.

An easier, more cross-platform approach at getting the same information is to use gdb. You can set a breakpoint, or step through, and then use backtrace (or bt) to get a stack trace. Another approach I’ve not yet explored would be to use rr, which is like gdb but allows you to replay the execution.

Other shortcomings of the approach detailed below are:

  • It adds more dependencies to the build, and adds complexity to the build process.
  • It only works for debug builds.
  • It only works on Windows.
  • For GCC builds, it requires the *.pdb file in the same directory as the executable.

So why would you implement this? Well:

  • For a quick developer feedback cycle: print the stack trace whenever an assertion fails to immediately get debug information.
  • To aid with bug reports from users: providing you’re happy with delivering a debug build, it enables automatically capturing useful stack trace information from users.

End result §

The resulting function log_stacktrace() walks the stack and prints the name, address and line of every function. The resulting stack trace, read from top to bottom, looks like this:

Stack trace:
    Function                                 Address            Line
    --------                                 -------            ----
    log_stacktrace                           0x00007FF7AE73ED8A C:\project\something\game\src\log\platform\windows\log_stacktrace.c:19
    engine_obj_helper                        0x00007FF7AE72FA9D C:\project\something\game\src\engine\engine_step.c:620
    engine_obj_resolve                       0x00007FF7AE731D5E C:\project\something\game\src\engine\engine_step.c:1207
    engine_step_resolve                      0x00007FF7AE7318A1 C:\project\something\game\src\engine\engine_step.c:1095
    engine_action                            0x00007FF7AE72BE17 C:\project\something\game\src\engine\engine_main.c:586
    input_keydown                            0x00007FF7AE72232B C:\project\something\game\src\app\app_play_main.c:149
    app_play_main_input_keydown              0x00007FF7AE722533 C:\project\something\game\src\app\app_play_main.c:231
    app_loop_main_unlimited                  0x00007FF7AE7219C1 C:\project\something\game\src\app\app_loop.c:163
    app_loop_main_limited                    0x00007FF7AE721864 C:\project\something\game\src\app\app_loop.c:94
    app_loop_main_normal                     0x00007FF7AE721847 C:\project\something\game\src\app\app_loop.c:85
    main                                     0x00007FF7AE72176E C:\project\something\game\app\main.c:40
    public_all                               0x00007FF7AE721340 Unknown
    public_all                               0x00007FF7AE721117 Unknown
    BaseThreadInitThunk                      0x00007FFBB210E8D7 Unknown
    RtlUserThreadStart                       0x00007FFBB437BF6C Unknown

Overview §

The Windows library DbgHelp provides the function StackWalk64(), which provides access to the stack information, and SymFromAddr(), which gets the human-readable name of each function. Using DbgHelp requires the C application to include dbghelp.h at compile time, to link against DbgHelp, and DbgHelp.dll must be present. Although Windows always includes a version of DbgHelp.dll, developers should obtain the right version by installing Debugging Tools for Windows from the Windows SDK.

In order for SymFromAddr() to get the human-readable function names, the executable must provide the PDB information. In simple terms, PDB (Program Database) is a file format by Microsoft which contains debug information. It can be embedded directly inside an executable, or can be a standalone *.pdb file in the same directory as the corresponding executable2. Microsoft’s MSVC compiler can generate PDB information, and so can LLVM/Clang3. Mingw-w64 does not have PDB support4.

Mingw-w64’s lack of support for PDB can be solved using cv2pdb, which given an executable containing DWARF5 information will output a *.pdb file with the PDB information. Running cv2pdb requires several other DLLs6 which are typically obtained by installing Microsoft Visual Studio. However, I discovered it is possible to “just” install MSVC v143 - VS 2022 C++ x86/64 build tools (Latest) (by manually selecting that component in the Microsoft Visual Studio installer) and that provides all the necessary DLLs.

In summary:

  1. Install Windows SDK to get DbgHelp.
  2. Update application to include dbghelp.h and link against DbgHelp.
  3. Write the log_stacktrace() function:
    • Use StackWalk64() to get the stack.
    • Use SymFromAddr() to get the address of each item in the stack.
    • Use SymGetLineFromAddr64() to get the file name and line number of each item in the stack.
  4. Install the MSVC tool chains (from Microsoft Visual Studio installer) to get the DLLs required for cv2pdb.
  5. Build your application using Mingw-w64 with debug information (generates the DWARF information).
  6. Run cv2pdb to generate a *.pdb file (containing the PDB information from parsing the DWARF information).
  7. Run your application and call log_stacktrace().

Steps to generate stack trace §

Install Windows SDK §

The first step is to install DbgHelp:

  • Go to DbgHelp Versions and follow the link for the Windows SDK.
  • Download the installer.
  • Run winsdksetup.exe.
  • When prompted, select the following:
    • Debugging Tools for Windows: this provides DbgHelp.
    • Windows SDK for Desktop C++ x86 Apps: this provides ImageHlp, which is requried by DbgHelp.
      • Selecting this will also automatically select three other features.
Screenshot of selecting the relevant Windows SDK debug tools.

Initially I just selected Debugging Tools for Windows, which appears to include DbgHelp but not ImageHelp. When compiling dbghelp.h this caused the error fatal error: minidumpapiset.h: No such file or directory. So make sure to install both.

Update CMake §

FindDbgHelp.cmake §

Create FindDbgHelp.cmake:

game/cmake/dbghelp/FindDbgHelp.cmake
# - Try to find DbgHelp
# Once done, this will define
#
#  DbgHelp_FOUND - system has DbgHelp
#  DbgHelp_INCLUDE_DIRS - the DbgHelp include directory
#  DbgHelp_LIBRARIES - the DbgHelp library

find_path(DbgHelp_INCLUDE_DIR dbghelp.h
    PATHS
    "$ENV{ProgramFiles}/Microsoft SDKs/Windows/v7.1/Include"
    "$ENV{ProgramFiles}/Windows Kits/10/Include"
    PATH_SUFFIXES um dbghelp
)

find_library(DbgHelp_LIBRARY dbghelp
    PATHS
    "$ENV{ProgramFiles}/Microsoft SDKs/Windows/v7.1/Lib"
    "$ENV{ProgramFiles}/Windows Kits/10/Lib"
    PATH_SUFFIXES x86 x64 um
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(DbgHelp DEFAULT_MSG DbgHelp_LIBRARY DbgHelp_INCLUDE_DIR)

if(DbgHelp_FOUND)
    set(DbgHelp_LIBRARIES ${DbgHelp_LIBRARY})
    set(DbgHelp_INCLUDE_DIRS ${DbgHelp_INCLUDE_DIR})
endif()

mark_as_advanced(DbgHelp_INCLUDE_DIR DbgHelp_LIBRARY)

Top-level CMakeLists.txt §

Update the top-level CMakeLists.txt to find DbgHelp:

game/CMakeLists.txt
# Add FindDbgHelp.cmake to the path.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dbghelp")

# DbgHelp is only valid on Windows.
if (WIN32)
    find_package(DbgHelp)
    if (DBGHELP_FOUND)
        include_directories(${DBGHELP_INCLUDE_DIR})
    endif()
    set(GAME_USE_DBGHELP ${DBGHELP_FOUND})
endif()

Application-level CMakeLists.txt §

Update the application-level CMakeLists.txt to link against DbgHelp.

game/app/CMakeLists.txt
list(APPEND APP_LIBS game_lib_1)
list(APPEND APP_LIBS game_lib_2)
list(APPEND APP_LIBS game_lib_3)

# ...

if (DBGHELP_FOUND)
    message("Linking against DbgHelp")
    list(APPEND APP_LIBS DbgHelp)
endif()

# ...

target_link_libraries(my_game PUBLIC "${APP_LIBS}")

Create stack trace function §

Create header file:

game/include/log/log_stacktrace.h
void log_stacktrace();

Create source file:

game/src/log/log_stacktrace.c
#include "log/log_stacktrace.h"

#include <stdio.h>
#include <windows.h>

// Must come after windows.h
#include <dbghelp.h>

void log_stacktrace()
{
    HANDLE       process = GetCurrentProcess();
    HANDLE       thread  = GetCurrentThread();
    CONTEXT      context;
    STACKFRAME64 stack;
    DWORD        machine_type;

    RtlCaptureContext(&context);

    ZeroMemory(&stack, sizeof(STACKFRAME64));

#ifdef _M_IX86
    machine_type           = IMAGE_FILE_MACHINE_I386;
    stack.AddrPC.Offset    = context.Eip;
    stack.AddrFrame.Offset = context.Ebp;
    stack.AddrStack.Offset = context.Esp;
#elif _M_X64
    machine_type           = IMAGE_FILE_MACHINE_AMD64;
    stack.AddrPC.Offset    = context.Rip;
    stack.AddrFrame.Offset = context.Rsp;
    stack.AddrStack.Offset = context.Rsp;
#elif _M_ARM64
    machine_type           = IMAGE_FILE_MACHINE_ARM64;
    stack.AddrPC.Offset    = context.Pc;
    stack.AddrFrame.Offset = context.Fp;
    stack.AddrStack.Offset = context.Sp;
#else
#error "Unsupported platform"
#endif

    stack.AddrPC.Mode    = AddrModeFlat;
    stack.AddrFrame.Mode = AddrModeFlat;
    stack.AddrStack.Mode = AddrModeFlat;

    SymInitialize(process, NULL, TRUE);
    SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);

    printf("Stack trace:\n");
    printf("    %-40s %-18s %s\n", "Function", "Address", "Line");
    printf("    %-40s %-18s %s\n", "--------", "-------", "----");

    DWORD frame_number = 0;
    while (StackWalk64(
        machine_type,
        process,
        thread,
        &stack,
        &context,
        NULL,
        SymFunctionTableAccess64,
        SymGetModuleBase64,
        NULL)) {
        if (stack.AddrPC.Offset == 0)
            break;

        DWORD64 symbol_addr  = stack.AddrPC.Offset;
        DWORD64 displacement = 0;
        char symbol_buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)] = {0};
        SYMBOL_INFO *symbol  = (SYMBOL_INFO *)symbol_buffer;
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        symbol->MaxNameLen   = MAX_SYM_NAME;

        // Get line information
        IMAGEHLP_LINE64 line = {0};
        line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
        DWORD line_displacement = 0;
        BOOL has_line = SymGetLineFromAddr64(process, symbol_addr, &line_displacement, &line);

        char function_name[MAX_SYM_NAME] = "Unknown";
        if (SymFromAddr(process, symbol_addr, &displacement, symbol)) {
            strncpy(function_name, symbol->Name, MAX_SYM_NAME - 1);
            function_name[MAX_SYM_NAME - 1] = '\0'; // Ensure null termination
        }

        // Format line information
        char line_info[256] = "Unknown";
        if (has_line) {
            snprintf(line_info, sizeof(line_info), "%s:%lu", line.FileName, line.LineNumber);
        }

        // Print with better alignment using format specifiers
        printf("    %-40.40s 0x%016llX %s\n",
               function_name,
               symbol_addr,
               line_info);

        frame_number++;
    }

    SymCleanup(process);
}

Build §

If DbgHelp is installed somewhere sensible, it should be possible to build with:

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles"

The CMAKE_BUILD_TYPE=Debug is necessary to generate the DWARF debug information.

If CMake cannot find DbgHelp, help point it in the right direction with these options:

  • Use DbgHelp_INCLUDE_DIR to specify the include directory (so it can find dbghelp.h).
  • Use DbgHelp_LIBRARY to specify the directory where the DLL can be found (so it can find DbgHelp.dll).

For example:

cmake -S . -B build -DDbgHelp_INCLUDE_DIR="C:\Program Files (x86)\Windows Kits\10" -DDbgHelp_LIBRARY="C:\Program Files (x86)\Windows Kits\10" -DCMAKE_BUILD_TYPE=Debug -G "MinGW Makefiles"

Check output §

Now run the program and give it a try. With GCC it should generate a stack trace like the following:

Stack trace:
    Function                                 Address            Line
    --------                                 -------            ----
    log_stacktrace                           0x00007FF7AE73ED8A Unknown
    engine_obj_helper                        0x00007FF7AE72FA9D Unknown
    engine_obj_resolve                       0x00007FF7AE731D5E Unknown
    engine_step_resolve                      0x00007FF7AE7318A1 Unknown
    engine_action                            0x00007FF7AE72BE17 Unknown
    input_keydown                            0x00007FF7AE72232B Unknown
    app_play_main_input_keydown              0x00007FF7AE722533 Unknown
    app_loop_main_unlimited                  0x00007FF7AE7219C1 Unknown
    app_loop_main_limited                    0x00007FF7AE721864 Unknown
    app_loop_main_normal                     0x00007FF7AE721847 Unknown
    main                                     0x00007FF7AE72176E Unknown
    public_all                               0x00007FF7AE721340 Unknown
    public_all                               0x00007FF7AE721117 Unknown
    BaseThreadInitThunk                      0x00007FFBB210E8D7 Unknown
    RtlUserThreadStart                       0x00007FFBB437BF6C Unknown

Pretty good, but we’re not quite there: all the line information is Unknown.

Extract PDB information §

If your stack trace lists Unknown for all the lines, this is most likely because your build does not contain PDB information.

MSVC and LLVM/Clang have PDB support3 but GCC does not. Fortunately, the tool cv2pdb can be used to extract the necessary PDB information from the DWARF information that GCC generates in a debug build.

As best as I can tell, there are two independent (but closely related) concepts of debug information and exception handling.

Each of the WinLibs versions provides a 32-bit/i686 release with dwarf exception handling, and a 64-bit/x86_64 release with seh exception handling. Both of these releases are capable of generating DWARF debug information in a debug build, despite only the 32-bit release having dwarf in the name.

In other words, the following approach works for 32-bit and 64-bit builds, despite only 32-bit builds having dwarf in the name, and cv2pdb relies upon there being DWARF debug information in the executable.

Install C++ build tools §

cv2pdb relies upon some libraries provided by Microsoft Visual Studio. While Microsoft will try to persuade you otherwise, you do not need to install the entirety of Visual Studio, nor do you need to install an entire so-called workload. Instead, you can install a single component weighing in at “only” 3.57 GB. (Unfortunately this appears to be as small a download as is possible to support cv2pdb via official means).

  • Download Visual Studio with C++ (Community 2022).
  • Run the downloaded file VisualStudioSetup.exe.
  • When prompted to choose workloads, instead click on the components tab at the top. This way you can save disk space by installing only the relevant MSVC build tools, rather than installing all the gumpf that a workload includes.
  • Choose the relevant MSVC build tool. For me, that is: MSVC v143 - VS 2022 C++ x86/64 build tools (Latest).
Screenshot of selecting the relevant MSVC build tools.
  • Once the install has finished, restart. (Not sure if necessary, but wanted to ensure the new tools are definitely on the PATH).

Run cv2pdb §

  • Go to the cv2pdb GitHub release pages and download the latest ZIP (currently cv2pdb-0.53.zip).
  • Extract the ZIP.
  • There are two executables: cv2pdb.exe for 32-bit executables, and cv2pdb64.exe for 64-bit executables.
  • I am using 64-bit, chose cv2pdb64.exe.
  • Copy cv2pdb64.exe into the same directory as your executable.
  • Run: ./cv2pdb64.exe my_game.exe
  • If it works, it will generate a file my_game.pdb in the same directory.
  • Now try running your application again. This time the stack trace should look like the end result, i.e. it should show line numbers for your application.

We’re almost done…

Automating cv2pdb §

It’s a pain to run manually cv2pdb64.exe each time. Fortunately this can easily be automated with CMake using a POST_BUILD custom command.

Update the application-level CMakeLists.txt to call add_custom_command() as follows:

game/app/CMakeLists.txt
if (DBGHELP_FOUND)
    message("Linking against DbgHelp")
    list(APPEND APP_LIBS DbgHelp)

    # Automatically run cv2pdb64 as a post-build step
    add_custom_command(TARGET my_game POST_BUILD
        COMMAND $<TARGET_FILE_DIR:my_game>/cv2pdb64.exe $<TARGET_FILE:my_game>
        COMMENT "Generating PDB using cv2pdb64.exe"
        VERBATIM
    )
endif()

Now, every time you perform a build with DbgHelp enabled, CMake will automatically run ./cv2pdb64.exe my_game.exe.

We’re finally done, hurrah! 🎉

Conclusion §

This adds some big dependencies to the developer setup, but are at least implemented as optional (by using if (DBGHELP_FOUND)). A proper debugger such as gdb or rr would get you the same information and save all the setup trouble, but that does require the developer to run the debugger. Once this stack trace implementation is up and running it provides some quick and useful feedback.

  • For testing: the executable can be delivered to users along with the *.pdb file, but the users will need the DbgHelp.dll to be present. This could potentially be included alongside the executable to save the user from having to intall the Windows SDK.
  • For production: this implementation is not applicable, because it only works for debug builds. However, by their very nature, production or “release” builds typically do not generate debug information.

Whether this makes sense for your situation is up to you!

Troubleshooting §

Following are some issues, and how I resolved them.

CMake cannot find DBGHELP §

-- Could NOT find DBGHELP (missing: DBGHELP_LIBRARY DBGHELP_INCLUDE_DIR)
  • Make sure that the Windows SDK is installed as described here.
  • Make sure that DBGHELP_INCLUDE_DIR and DBGHELP_LIBRARY are set correctly, e.g. by setting them at build time with -D as described here.

Cannot find minidumpapiset.h §

In file included from C:\project\game\src\log\platform\windows\log_stacktrace.c:5:
C:/PROGRA~2/WI3CF2~1/10/DEBUGG~1/inc/dbghelp.h:4364:10: fatal error: minidumpapiset.h: No such file or directory
 4364 | #include <minidumpapiset.h>
      |          ^~~~~~~~~~~~~~~~~~

Had to go to Windows installed apps, find the “Windows Software Development Kit”, do “Change”, and select “Windows SDK for Desktop C++”. This also selected a bunch of other stuff.

Then minidumpapiset.h was at: C:\Program Files (x86)\Windows Kits\10\Include\10.0.26100.0\um.

Also had to change DBGHELP_INCLUDE_DIR from C:\Program Files (x86)\Windows Kits\10\Debuggers\inc to C:\Program Files (x86)\Windows Kits\10 so that it can find both dbghelp.h (which is in Debuggers\inc\) and minidumpapiset.h (which is in Include\10.0.26100.0\um\).

Error linking against DbgHelp §

[ 97%] Built target game_app
[ 98%] Building C object app/CMakeFiles/my_game.dir/main.c.obj
[100%] Linking C executable my_game.exe
C:/Program Files (x86)/mingw-w64/winlibs-i686-posix-dwarf-gcc-13.2.0-llvm-18.1.3-mingw-w64ucrt-11.0.1-r7/mingw32/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: ../src/log/libuot_log.a(log_stacktrace.c.obj):log_stacktrace.:(.text+0x2a): undefined reference to `_imp__SymInitialize@12'
C:/Program Files (x86)/mingw-w64/winlibs-i686-posix-dwarf-gcc-13.2.0-llvm-18.1.3-mingw-w64ucrt-11.0.1-r7/mingw32/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: ../src/log/libuot_log.a(log_stacktrace.c.obj):log_stacktrace.:(.text+0x163): undefined reference to `_imp__SymFromAddr@20'
C:/Program Files (x86)/mingw-w64/winlibs-i686-posix-dwarf-gcc-13.2.0-llvm-18.1.3-mingw-w64ucrt-11.0.1-r7/mingw32/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: ../src/log/libuot_log.a(log_stacktrace.c.obj):log_stacktrace.:(.text+0x1e6): undefined reference to `_imp__SymGetModuleBase64@12'
C:/Program Files (x86)/mingw-w64/winlibs-i686-posix-dwarf-gcc-13.2.0-llvm-18.1.3-mingw-w64ucrt-11.0.1-r7/mingw32/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: ../src/log/libuot_log.a(log_stacktrace.c.obj):log_stacktrace.:(.text+0x1f0): undefined reference to `_imp__SymFunctionTableAccess64@12'
C:/Program Files (x86)/mingw-w64/winlibs-i686-posix-dwarf-gcc-13.2.0-llvm-18.1.3-mingw-w64ucrt-11.0.1-r7/mingw32/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: ../src/log/libuot_log.a(log_stacktrace.c.obj):log_stacktrace.:(.text+0x227): undefined reference to `_imp__StackWalk64@36'
C:/Program Files (x86)/mingw-w64/winlibs-i686-posix-dwarf-gcc-13.2.0-llvm-18.1.3-mingw-w64ucrt-11.0.1-r7/mingw32/bin/../lib/gcc/i686-w64-mingw32/13.2.0/../../../../i686-w64-mingw32/bin/ld.exe: ../src/log/libuot_log.a(log_stacktrace.c.obj):log_stacktrace.:(.text+0x23f): undefined reference to `_imp__SymCleanup@4'
collect2.exe: error: ld returned 1 exit status
mingw32-make[2]: *** [app\CMakeFiles\my_game.dir\build.make:120: app/my_game.exe] Error 1
mingw32-make[1]: *** [CMakeFiles\Makefile2:939: app/CMakeFiles/my_game.dir/all] Error 2
mingw32-make: *** [Makefile:90: all] Error 2
  • Make sure you are not mixing up 32-bit and 64-bit.
  • Make sure target_link_libraries(...) is linking your application against DbgHelp.
  • Make sure that the Windows SDK is installed as described here. Note that DbgHelp requires ImageHlp.

Compiler error: unknown type name 'PSTR' etc §

In file included from C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/dbghelp.h:17,
                 from C:\project\game\src\log\platform\windows\log_stacktrace.c:3:
C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/psdk_inc/_dbg_LOAD_IMAGE.h:24:5: error: unknown type name 'PSTR'
   24 |     PSTR ModuleName;
      |     ^~~~
C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/psdk_inc/_dbg_LOAD_IMAGE.h:25:5: error: unknown type name 'HANDLE'
   25 |     HANDLE hFile;
      |     ^~~~~~
C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/psdk_inc/_dbg_LOAD_IMAGE.h:26:5: error: unknown type name 'PUCHAR'
   26 |     PUCHAR MappedAddress;
      |     ^~~~~~
C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/psdk_inc/_dbg_LOAD_IMAGE.h:28:5: error: unknown type name 'PIMAGE_NT_HEADERS64'
   28 |     PIMAGE_NT_HEADERS64 FileHeader;
      |     ^~~~~~~~~~~~~~~~~~~
C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/psdk_inc/_dbg_LOAD_IMAGE.h:32:5: error: unknown type name 'PIMAGE_SECTION_HEADER'
   32 |     PIMAGE_SECTION_HEADER LastRvaSection;
      |     ^~~~~~~~~~~~~~~~~~~~~
C:/Program Files (x86)/mingw-w64/winlibs-x86_64-posix-seh-gcc-14.2.0-llvm-19.1.7-mingw-w64ucrt-12.0.0-r3/mingw64/x86_64-w64-mingw32/include/psdk_inc/_dbg_LOAD_IMAGE.h:33:5: error: unknown type name 'ULONG'
   33 |     ULONG NumberOfSections;
      |     ^~~~~
...and so on for a couple thousand lines...

Make sure that windows.h is included before dbghelp.h, e.g.:

#include <windows.h>

// Must come after windows.h
#include <dbghelp.h>

Beware that clang-format may helpfully place dbghelp.h above windows.h.


  1. Mingw-w64 was forked from MinGW in 2007. It is not a compiler, but is a collection of header files, libraries and tools that can be combined with a compiler toolchain (e.g. GCC or LLVM) to build native Windows applications. Mingw-w64 is released as source, not binaries. Most users install a pre-built toolchain from this list on the Mingw-w64 website. I chose to use the latest WinLibs 64-bit UCRT runtime with both GCC and LLVM. ↩︎

  2. I may have over-simplified. See this excellent article for much more detail on PDB. ↩︎

  3. LLVM/Clang has had PDB support since 2017. It requires some extra options, e.g. -g -gcodeview --for-linker --pdb=file.pdb↩︎ ↩︎

  4. According to the mingw-w64 “Contribute” page, PDB support is a topic “for which developer-time has been scarce”. ↩︎

  5. DWARF is a very common debug file format which Mingw-w64 can generate. ↩︎

  6. It’s not clear exactly which DLLs it requires. From this issue it looks like it requires mspdb140.dll, which spawns mspdbsrv.exe, which in turn requires mspdbcore.dll, msvcp140.dll, msvcp140_atomic_wait.dll, tbbmalloc.dll, vcruntime140.dll and msobj140.dll↩︎

  • Tagged
  • c