Last active
October 4, 2025 20:33
-
-
Save mmozeiko/78ed8378c2917fcb7d4596e986f4afaa to your computer and use it in GitHub Desktop.
example for debug api & call stack unwinding & symbol lookup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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