Skip to content

Instantly share code, notes, and snippets.

@mmozeiko
Last active October 4, 2025 20:33
Show Gist options
  • Save mmozeiko/78ed8378c2917fcb7d4596e986f4afaa to your computer and use it in GitHub Desktop.
Save mmozeiko/78ed8378c2917fcb7d4596e986f4afaa to your computer and use it in GitHub Desktop.
example for debug api & call stack unwinding & symbol lookup
#include <windows.h>
#include <dbghelp.h>
#include <stdio.h>
#include <assert.h>
#pragma comment (lib, "dbghelp")
int main()
{
STARTUPINFOW si = { .cb = sizeof(si) };
PROCESS_INFORMATION pi;
// see test.c and how to compile it to test.exe below
BOOL ok = CreateProcessW(L"test.exe", NULL, NULL, NULL, FALSE, DEBUG_PROCESS, NULL, NULL, &si, &pi);
assert(ok);
// if you want correct symbols for kernel32/ntdll modules, make sure _NT_SYMBOL_PATH points to folder where .pdb's are
SymSetOptions(SYMOPT_AUTO_PUBLICS | SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES);
BOOL init = SymInitializeW(pi.hProcess, NULL, FALSE);
assert(init);
BOOL first = TRUE;
// how many single-steps to do
int counter = 16;
for (;;)
{
DEBUG_EVENT info;
BOOL wait = WaitForDebugEvent(&info, INFINITE);
assert(wait);
if (info.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
{
// this is not really needed here, it's here only for simpler code in this example, so I
// can use SymGetModuleInfoW64. You can just pass NULL for path to SymLoadModuleExW and
// then use SymGetModuleBase64 to get module base that you then map to module name yourself
WCHAR path[MAX_PATH];
DWORD length = GetFinalPathNameByHandleW(info.u.CreateProcessInfo.hFile, path, _countof(path), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
DWORD64 load = SymLoadModuleExW(pi.hProcess, info.u.CreateProcessInfo.hFile, length ? path : NULL, NULL, (DWORD64)info.u.CreateProcessInfo.lpBaseOfImage, 0, NULL, 0);
assert(load);
}
else if (info.dwDebugEventCode == LOAD_DLL_DEBUG_EVENT)
{
// same as above
WCHAR path[MAX_PATH];
DWORD length = GetFinalPathNameByHandleW(info.u.LoadDll.hFile, path, _countof(path), FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
DWORD64 load = SymLoadModuleExW(pi.hProcess, info.u.LoadDll.hFile, length ? path : NULL, NULL, (DWORD64)info.u.LoadDll.lpBaseOfDll, 0, NULL, 0);
assert(load);
}
else if (info.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT || info.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT)
{
// ignored in this example
}
else if (info.dwDebugEventCode == EXCEPTION_DEBUG_EVENT)
{
if (first)
{
// ignore first breakpoint
assert(info.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT);
first = FALSE;
}
else
{
// expected breakpoint or single-step exception
assert(info.u.Exception.dwFirstChance == 1);
assert(info.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
|| info.u.Exception.ExceptionRecord.ExceptionCode == STATUS_SINGLE_STEP);
if (counter-- == 0) exit(0);
DWORD suspend = SuspendThread(pi.hThread);
assert(suspend != (DWORD)-1);
// stack unwinding needs only rip & rsp registers on x64
CONTEXT context = { .ContextFlags = CONTEXT_CONTROL };
BOOL ok = GetThreadContext(pi.hThread, &context);
assert(ok);
// enable single stepping
{
context.EFlags |= 0x100;
ok = SetThreadContext(pi.hThread, &context);
assert(ok);
}
printf("counter=%d\n", counter);
STACKFRAME64 frame =
{
.AddrPC.Offset = context.Rip,
.AddrPC.Mode = AddrModeFlat,
.AddrStack.Offset = context.Rsp,
.AddrStack.Mode = AddrModeFlat,
};
// unwind the stack
for (int idx=0; ; idx++)
{
ok = StackWalk64(IMAGE_FILE_MACHINE_AMD64, pi.hProcess, pi.hThread, &frame, &context, NULL, &SymFunctionTableAccess64, &SymGetModuleBase64, NULL);
if (!ok || frame.AddrPC.Offset == 0)
{
break;
}
printf("[%d] %016I64x ", idx, frame.AddrPC.Offset);
// module name
IMAGEHLP_MODULEW64 module = { .SizeOfStruct = sizeof(module) };
BOOL mod = SymGetModuleInfoW64(pi.hProcess, frame.AddrPC.Offset, &module);
if (mod)
{
printf("%ls", module.ModuleName);
}
// symbol name
SYMBOL_INFO_PACKAGEW symbol =
{
.si.SizeOfStruct = sizeof(symbol.si),
.si.MaxNameLen = _countof(symbol.name),
};
DWORD64 displ64;
ok = SymFromAddrW(pi.hProcess, frame.AddrPC.Offset, &displ64, &symbol.si);
if (ok)
{
printf("%s%ls+%I64u", mod ? "!" : "", symbol.si.Name, displ64);
}
// source file name & line number
IMAGEHLP_LINEW64 line = { .SizeOfStruct = sizeof(line) };
DWORD displ;
ok = SymGetLineFromAddrW64(pi.hProcess, frame.AddrPC.Offset, &displ, &line);
if (ok)
{
printf(" %ls @ line %u", line.FileName, line.LineNumber);
}
printf("\n");
}
printf("\n");
DWORD resume = ResumeThread(pi.hThread);
assert(resume != (DWORD)-1);
}
}
else
{
assert(0); // not expected in this example code
}
ContinueDebugEvent(info.dwProcessId, info.dwThreadId, DBG_CONTINUE);
}
}
// compile with: cl.exe -Z7 test.c -link -incremental:no
#include <intrin.h>
int fun3()
{
__debugbreak();
return 3;
}
int fun2()
{
fun3();
return 2;
}
int fun1()
{
fun2();
return 1;
}
int main()
{
fun1();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment