Traccia dello stack di Windows C ++ da un’app in esecuzione

Tutti,

Ho visto un’app, un plug-in SVN di Visual Studio, che mostrava una traccia dello stack leggibile, quando si arrestava.

Mi piacerebbe aggiungerlo alla mia domanda. Come posso fornire questo? Nessuna trasmissione via email delle informazioni, basta una visualizzazione visiva.

Il nucleo del codice necessario è StackWalk64 . Per ottenere molto da questo, è anche necessario / necessario ottenere i nomi dei simboli con SymGetSymFromAddr64 (che richiede SymLoadModule64 ) e (probabilmente) SymGetLineFromAddr64 e GetThreadContext . Se il target è stato scritto in C ++, probabilmente vorrai usare UnDecorateSymbolName . Insieme a quelli avrai bisogno di alcuni accessori come SymInitialize , SymCleanup e probabilmente SymSetOptions .

Ecco una demo abbastanza minimale. Il dump dello stack che produce è più utile che bello, ma presumibilmente con le basi per eseguire la traccia dello stack, è ansible visualizzare i risultati come meglio credi:

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  #pragma comment(lib, "psapi.lib") #pragma comment(lib, "dbghelp.lib") // Some versions of imagehlp.dll lack the proper packing directives themselves // so we need to do it. #pragma pack( push, before_imagehlp, 8 ) #include  #pragma pack( pop, before_imagehlp ) struct module_data { std::string image_name; std::string module_name; void *base_address; DWORD load_size; }; typedef std::vector ModuleList; HANDLE thread_ready; bool show_stack(std::ostream &, HANDLE hThread, CONTEXT& c); DWORD __stdcall TargetThread( void *arg ); void ThreadFunc1(); void ThreadFunc2(); DWORD Filter( EXCEPTION_POINTERS *ep ); void *load_modules_symbols( HANDLE hProcess, DWORD pid ); int main( void ) { DWORD thread_id; thread_ready = CreateEvent( NULL, false, false, NULL ); HANDLE thread = CreateThread( NULL, 0, TargetThread, NULL, 0, &thread_id ); WaitForSingleObject( thread_ready, INFINITE ); CloseHandle(thread_ready); return 0; } // if you use C++ exception handling: install a translator function // with set_se_translator(). In the context of that function (but *not* // afterwards), you can either do your stack dump, or save the CONTEXT // record as a local copy. Note that you must do the stack dump at the // earliest opportunity, to avoid the interesting stack-frames being gone // by the time you do the dump. DWORD Filter(EXCEPTION_POINTERS *ep) { HANDLE thread; DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &thread, 0, false, DUPLICATE_SAME_ACCESS); std::cout << "Walking stack."; show_stack(std::cout, thread, *(ep->ContextRecord)); std::cout << "\nEnd of stack walk.\n"; CloseHandle(thread); return EXCEPTION_EXECUTE_HANDLER; } void ThreadFunc2() { __try { DebugBreak(); } __except (Filter(GetExceptionInformation())) { } SetEvent(thread_ready); } void ThreadFunc1(void (*f)()) { f(); } // We'll do a few levels of calls from our thread function so // there's something on the stack to walk... // DWORD __stdcall TargetThread(void *) { ThreadFunc1(ThreadFunc2); return 0; } class SymHandler { HANDLE p; public: SymHandler(HANDLE process, char const *path=NULL, bool intrude = false) : p(process) { if (!SymInitialize(p, path, intrude)) throw(std::logic_error("Unable to initialize symbol handler")); } ~SymHandler() { SymCleanup(p); } }; #ifdef _M_X64 STACKFRAME64 init_stack_frame(CONTEXT c) { STACKFRAME64 s; s.AddrPC.Offset = c.Rip; s.AddrPC.Mode = AddrModeFlat; s.AddrStack.Offset = c.Rsp; s.AddrStack.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Rbp; s.AddrFrame.Mode = AddrModeFlat; return s; } #else STACKFRAME64 init_stack_frame(CONTEXT c) { STACKFRAME64 s; s.AddrPC.Offset = c.Eip; s.AddrPC.Mode = AddrModeFlat; s.AddrStack.Offset = c.Esp; s.AddrStack.Mode = AddrModeFlat; s.AddrFrame.Offset = c.Ebp; s.AddrFrame.Mode = AddrModeFlat; return s; } #endif void sym_options(DWORD add, DWORD remove=0) { DWORD symOptions = SymGetOptions(); symOptions |= add; symOptions &= ~remove; SymSetOptions(symOptions); } class symbol { typedef IMAGEHLP_SYMBOL64 sym_type; sym_type *sym; static const int max_name_len = 1024; public: symbol(HANDLE process, DWORD64 address) : sym((sym_type *)::operator new(sizeof(*sym) + max_name_len)) { memset(sym, '\0', sizeof(*sym) + max_name_len); sym->SizeOfStruct = sizeof(*sym); sym->MaxNameLength = max_name_len; DWORD64 displacement; if (!SymGetSymFromAddr64(process, address, &displacement, sym)) throw(std::logic_error("Bad symbol")); } std::string name() { return std::string(sym->Name); } std::string undecorated_name() { std::vector und_name(max_name_len); UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE); return std::string(&und_name[0], strlen(&und_name[0])); } }; bool show_stack(std::ostream &os, HANDLE hThread, CONTEXT& c) { HANDLE process = GetCurrentProcess(); int frame_number=0; DWORD offset_from_symbol=0; IMAGEHLP_LINE64 line = {0}; SymHandler handler(process); sym_options(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); void *base = load_modules_symbols(process, GetCurrentProcessId()); STACKFRAME64 s = init_stack_frame(c); line.SizeOfStruct = sizeof line; IMAGE_NT_HEADERS *h = ImageNtHeader(base); DWORD image_type = h->FileHeader.Machine; do { if (!StackWalk64(image_type, process, hThread, &s, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) return false; os << std::setw(3) << "\n" << frame_number << "\t"; if ( s.AddrPC.Offset != 0 ) { std::cout << symbol(process, s.AddrPC.Offset).undecorated_name(); if (SymGetLineFromAddr64( process, s.AddrPC.Offset, &offset_from_symbol, &line ) ) os << "\t" << line.FileName << "(" << line.LineNumber << ")"; } else os << "(No Symbols: PC == 0)"; ++frame_number; } while (s.AddrReturn.Offset != 0); return true; } class get_mod_info { HANDLE process; static const int buffer_length = 4096; public: get_mod_info(HANDLE h) : process(h) {} module_data operator()(HMODULE module) { module_data ret; char temp[buffer_length]; MODULEINFO mi; GetModuleInformation(process, module, &mi, sizeof(mi)); ret.base_address = mi.lpBaseOfDll; ret.load_size = mi.SizeOfImage; GetModuleFileNameEx(process, module, temp, sizeof(temp)); ret.image_name = temp; GetModuleBaseName(process, module, temp, sizeof(temp)); ret.module_name = temp; std::vector img(ret.image_name.begin(), ret.image_name.end()); std::vector mod(ret.module_name.begin(), ret.module_name.end()); SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size); return ret; } }; void *load_modules_symbols(HANDLE process, DWORD pid) { ModuleList modules; DWORD cbNeeded; std::vector module_handles(1); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); module_handles.resize(cbNeeded/sizeof(HMODULE)); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); std::transform(module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process)); return modules[0].base_address; } 

Ho modificato il codice di Jerry Coffin come vedi sotto. modifiche:

A. Mi mancava sempre il frame più importante di tutti: la linea che ha effettivamente triggersto l’eccezione. È venuto fuori questo perché il ciclo “do” si stava spostando verso il frame successivo in alto anziché in basso.

B. Ho rimosso la roba di threading.

C. Ho semplificato alcuni bit.

Dovresti ritagliare la funzione “WinMain ()” nella parte inferiore e mettere invece __try .. __continua nella tua funzione ‘main / WinMain’. Sostituisci anche “YourMessage” con la tua funzione per visualizzare un messaggio o inviarlo via email o altro.

Le informazioni simboliche sono memorizzate all’interno di un file .pdb, non di .exe. Devi dare ai tuoi utenti il ​​tuo file .pdb, e devi assicurarti che il loro processo possa trovarlo. Vedi la stringa all’interno del codice qui sotto – sostituisci questo con una cartella che funzionerà sul computer degli utenti, oppure NULL – NULL significa che cercherà nella directory di lavoro corrente del processo. Il file .pdb deve avere esattamente lo stesso nome sul computer degli utenti come quando è stato eseguito il compilatore – per configurarlo su qualcosa di diverso, vedere “Proprietà> Linker> Debug> Genera file di database del programma”.

 #include  #include  #include  #include  #include  #include  #include  // Thanks, Jerry Coffin. #pragma comment(lib, "psapi.lib") #pragma comment(lib, "dbghelp.lib") // Some versions of imagehlp.dll lack the proper packing directives themselves // so we need to do it. #pragma pack( push, before_imagehlp, 8 ) #include  #pragma pack( pop, before_imagehlp ) struct module_data { std::string image_name; std::string module_name; void *base_address; DWORD load_size; }; DWORD DumpStackTrace( EXCEPTION_POINTERS *ep ); extern void YourMessage(const char* title, const char *fmt, ...);// Replace this with your own function class symbol { typedef IMAGEHLP_SYMBOL64 sym_type; sym_type *sym; static const int max_name_len = 1024; public: symbol(HANDLE process, DWORD64 address) : sym((sym_type *)::operator new(sizeof(*sym) + max_name_len)) { memset(sym, '\0', sizeof(*sym) + max_name_len); sym->SizeOfStruct = sizeof(*sym); sym->MaxNameLength = max_name_len; DWORD64 displacement; SymGetSymFromAddr64(process, address, &displacement, sym); } std::string name() { return std::string(sym->Name); } std::string undecorated_name() { if (*sym->Name == '\0') return ""; std::vector und_name(max_name_len); UnDecorateSymbolName(sym->Name, &und_name[0], max_name_len, UNDNAME_COMPLETE); return std::string(&und_name[0], strlen(&und_name[0])); } }; class get_mod_info { HANDLE process; static const int buffer_length = 4096; public: get_mod_info(HANDLE h) : process(h) {} module_data operator()(HMODULE module) { module_data ret; char temp[buffer_length]; MODULEINFO mi; GetModuleInformation(process, module, &mi, sizeof(mi)); ret.base_address = mi.lpBaseOfDll; ret.load_size = mi.SizeOfImage; GetModuleFileNameEx(process, module, temp, sizeof(temp)); ret.image_name = temp; GetModuleBaseName(process, module, temp, sizeof(temp)); ret.module_name = temp; std::vector img(ret.image_name.begin(), ret.image_name.end()); std::vector mod(ret.module_name.begin(), ret.module_name.end()); SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size); return ret; } }; // if you use C++ exception handling: install a translator function // with set_se_translator(). In the context of that function (but *not* // afterwards), you can either do your stack dump, or save the CONTEXT // record as a local copy. Note that you must do the stack dump at the // earliest opportunity, to avoid the interesting stack-frames being gone // by the time you do the dump. DWORD DumpStackTrace(EXCEPTION_POINTERS *ep) { HANDLE process = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); int frame_number=0; DWORD offset_from_symbol=0; IMAGEHLP_LINE64 line = {0}; std::vector modules; DWORD cbNeeded; std::vector module_handles(1); // Load the symbols: // WARNING: You'll need to replace  with either NULL // or some folder where your clients will be able to find the .pdb file. if (!SymInitialize(process, , false)) throw(std::logic_error("Unable to initialize symbol handler")); DWORD symOptions = SymGetOptions(); symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; SymSetOptions(symOptions); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); module_handles.resize(cbNeeded/sizeof(HMODULE)); EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); std::transform(module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process)); void *base = modules[0].base_address; // Setup stuff: CONTEXT* context = ep->ContextRecord; #ifdef _M_X64 STACKFRAME64 frame; frame.AddrPC.Offset = context->Rip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = context->Rsp; frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Offset = context->Rbp; frame.AddrFrame.Mode = AddrModeFlat; #else STACKFRAME64 frame; frame.AddrPC.Offset = context->Eip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = context->Esp; frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Offset = context->Ebp; frame.AddrFrame.Mode = AddrModeFlat; #endif line.SizeOfStruct = sizeof line; IMAGE_NT_HEADERS *h = ImageNtHeader(base); DWORD image_type = h->FileHeader.Machine; int n = 0; // Build the string: std::ostringstream builder; do { if ( frame.AddrPC.Offset != 0 ) { std::string fnName = symbol(process, frame.AddrPC.Offset).undecorated_name(); builder << fnName; if (SymGetLineFromAddr64( process, frame.AddrPC.Offset, &offset_from_symbol, &line)) builder << " " /*<< line.FileName*/ << "(" << line.LineNumber << ")\n"; else builder << "\n"; if (fnName == "main") break; if (fnName == "RaiseException") { // This is what we get when compiled in Release mode: YourMessage("Crash", "Your program has crashed.\n\n"); return EXCEPTION_EXECUTE_HANDLER; } } else builder << "(No Symbols: PC == 0)"; if (!StackWalk64(image_type, process, hThread, &frame, context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) break; if (++n > 10) break; } while (frame.AddrReturn.Offset != 0); //return EXCEPTION_EXECUTE_HANDLER; SymCleanup(process); // Display the string: YourMessage("Stack Trace", "Your program has crashed. Send a screenshot of this message to:\n" "[email protected]\n\n%s", builder.str().c_str()); return EXCEPTION_EXECUTE_HANDLER; } int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR args, int nCmdShow) { __try { //  int f = 0; f = 7 / f; // Trigger a divide-by-zero exception return 0; } __except (DumpStackTrace(GetExceptionInformation())) { return 1; } } 

O usa lo stackwalker da qui … http://www.codeproject.com/Articles/11132/Walking-the-callstack

usa i file stackwalker.cpp e stackwalker.h nel tuo progetto

Codice minimo:

 #include "StackWalker.h" class StackWalker2: public StackWalker { public: string output; void OnOutput(LPCSTR szText) { output+=szText; // this will collect stack info in output string } }; StackWalker2 sw; sw.ShowCallstack(); cout << sw.output; 

È necessario generare alcune informazioni di debug per vedere i nomi delle funzioni e i numeri di riga Usa il flag corretto in caso di Microsoft VIsual C ++ Potrebbe essere necessario inserire anche il file .pdb dove si trova il tuo exe.