#include #include #include #include #include #pragma comment (lib, "dbghelp") #pragma comment (lib, "advapi32") int main(int argc, char* argv[]) { if (argc != 2) { printf("Usage: %s process_id\n", argv[0]); return 1; } // enable debug privilege, to be able to call SuspendThread on other processes HANDLE token; if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) { TOKEN_PRIVILEGES tp = { .PrivilegeCount = 1, .Privileges[0].Attributes = SE_PRIVILEGE_ENABLED, }; if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid) || !AdjustTokenPrivileges(token, FALSE, &tp, 0, NULL, 0)) { printf("ERROR: cannot enable debug privilege!\n"); return 1; } CloseHandle(token); } DWORD processId = atoi(argv[1]); HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); BOOL isWow64 = FALSE; #if defined(_M_AMD64) IsWow64Process(process, &isWow64); #endif SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_UNDNAME); // TRUE here means that dbghelp will enumerate modules for the process // and initialize info needed to lookup addresses & symbols from this process // if you want to lookup info from modules that are loaded later you will need to // either manually refresh module list with SymRefreshModuleList, or implement custom callbacks // for StackWalk64 call that loads module info (SymLoadModuleEx) for addresses it needs if (!SymInitializeW(process, NULL, TRUE)) { printf("ERROR: cannot initialize dbghelp\n"); } HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (snapshot) { THREADENTRY32 entry = { .dwSize = sizeof(entry), }; if (Thread32First(snapshot, &entry)) { do { if (entry.th32OwnerProcessID != processId) { continue; } DWORD threadId = entry.th32ThreadID; printf("Thread %lu\n", threadId); HANDLE thread = OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, FALSE, threadId); if (thread) { if (isWow64 ? Wow64SuspendThread(thread) : SuspendThread(thread)) { STACKFRAME64 frame = { .AddrPC.Mode = AddrModeFlat, .AddrStack.Mode = AddrModeFlat, .AddrFrame.Mode = AddrModeFlat, }; DWORD machine = IMAGE_FILE_MACHINE_UNKNOWN; CONTEXT context; LPVOID contextPtr = NULL; #if defined(_M_ARM64) machine = IMAGE_FILE_MACHINE_ARM64; context.ContextFlags = CONTEXT_CONTROL; if (GetThreadContext(thread, &context)) { frame.AddrPC.Offset = context.Pc; frame.AddrStack.Offset = context.Sp; frame.AddrFrame.Offset = context.Fp; contextPtr = &context; } #elif defined(_M_AMD64) machine = isWow64 ? IMAGE_FILE_MACHINE_I386 : IMAGE_FILE_MACHINE_AMD64; WOW64_CONTEXT contextWow64; if (isWow64) { contextWow64.ContextFlags = WOW64_CONTEXT_CONTROL; if (Wow64GetThreadContext(thread, &contextWow64)) { frame.AddrPC.Offset = contextWow64.Eip; frame.AddrStack.Offset = contextWow64.Esp; frame.AddrFrame.Offset = contextWow64.Ebp; contextPtr = &contextWow64; } } else { context.ContextFlags = CONTEXT_CONTROL; if (GetThreadContext(thread, &context)) { frame.AddrPC.Offset = context.Rip; frame.AddrStack.Offset = context.Rsp; contextPtr = &context; } } #elif defined(_M_IX86) machine = IMAGE_FILE_MACHINE_I386; context.ContextFlags = CONTEXT_CONTROL; if (GetThreadContext(thread, &context)) { frame.AddrPC.Offset = context.Eip; frame.AddrStack.Offset = context.Esp; frame.AddrFrame.Offset = context.Ebp; contextPtr = &context; } #else #error not unsupported #endif if (contextPtr) { for (int idx = 0; /* empty */; idx++) { if (!StackWalk64(machine, process, thread, &frame, contextPtr, 0, &SymFunctionTableAccess64, &SymGetModuleBase64, 0)) { break; } if (frame.AddrPC.Offset == 0) { break; } printf("%d. %016I64x\n", idx, frame.AddrPC.Offset); } } else { printf("[error] cannot get thread context\n"); } ResumeThread(thread); } else { printf("[error] cannot suspend thread\n"); } CloseHandle(thread); } else { printf("[error] cannot open thread\n"); } } while (Thread32Next(snapshot, &entry)); } CloseHandle(snapshot); } SymCleanup(process); CloseHandle(process); }