Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows C++ stack trace from a running app

Tags:

visual-c++

All,

I saw an app, an SVN Visual Studio plugin, that displayed a beautiful readable stack trace, when it crashed.

I would love to add that to my application. How do I provide that? No emailing of the information, just a visual display is enough.

like image 605
reza Avatar asked Jun 01 '11 18:06

reza


People also ask

How do I run a stack trace in Windows?

To open the Call Stack window in Visual Studio, from the Debug menu, choose Windows>Call Stack. To set the local context to a particular row in the stack trace display, select and hold (or double click) the first column of the row.

How do I capture a stack trace?

You can obtain a stack trace from a thread – by calling the getStackTrace method on that Thread instance. This invocation returns an array of StackTraceElement, from which details about stack frames of the thread can be extracted.

How can I see my call stack?

View the call stack while in the debugger While debugging, in the Debug menu, select Windows > Call Stack or press ctrl + alt + C . A yellow arrow identifies the stack frame where the execution pointer is currently located.

What is the difference between call stack and stack trace?

A call stack is typically "the current stack of operations" - i.e. while it's running. A stack trace is typically a copy of the call stack which is logged at some sort of failure, e.g. an exception.


3 Answers

The core of the necessary code is StackWalk64. To get much from that, you'll also want/need to get symbol names with SymGetSymFromAddr64 (which requires SymLoadModule64) and (probably) SymGetLineFromAddr64 and GetThreadContext. If the target was written in C++, you'll probably also want to use UnDecorateSymbolName. Along with those you'll need a few ancillaries like SymInitialize, SymCleanup, and probably SymSetOptions.

Here's a fairly minimal demo. The stack dump it produces is more utilitarian than beautiful, but presumably with the basics of doing the stack trace, you can display the results as you see fit:

#include <windows.h> #include <winnt.h>  #include <string> #include <vector> #include <Psapi.h> #include <algorithm> #include <iomanip> #include <iostream> #include <stdexcept> #include <iterator>  #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 <imagehlp.h> #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<module_data> 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<char> 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<char> img(ret.image_name.begin(), ret.image_name.end());         std::vector<char> 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<HMODULE> 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; } 
like image 191
Jerry Coffin Avatar answered Sep 28 '22 08:09

Jerry Coffin


I modified Jerry Coffin's code as you see below. Modifications:

A. I was always missing the most important frame of all: the line that actually triggered the exception. Turns out this was because the "do" loop was moving to the next frame at the top instead of at the bottom.

B. I removed the threading stuff.

C. I simplified a few bits.

You should cut the "WinMain()" function at the bottom and instead put the __try .. __except stuff in your own 'main/WinMain' function. Also replace 'YourMessage' with your own function to display a message box or email it or whatever.

The symbolic information is stored inside a .pdb file, not the .exe. You need to give your users your .pdb file, and you need to ensure that their process can find it. See the string inside the code below - replace this with a folder that will work on the users' machine, or NULL - NULL means it will search in the process's current working directory. The .pdb file must have the exact same name on the users' computer as it had when you ran your compiler - to configure this to something different, see "Properties > Linker > Debugging > Generate Program Database File".

#include <windows.h>
#include <string>
#include <sstream>
#include <vector>
#include <Psapi.h>
#include <algorithm>
#include <iterator>


// 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 <imagehlp.h>
#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 "<couldn't map PC to fn name>";
        std::vector<char> 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<char> img(ret.image_name.begin(), ret.image_name.end());
        std::vector<char> 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<module_data> modules;
    DWORD cbNeeded;
    std::vector<HMODULE> module_handles(1);

    // Load the symbols:
    // WARNING: You'll need to replace <pdb-search-path> with either NULL
    // or some folder where your clients will be able to find the .pdb file.
    if (!SymInitialize(process, <pdb-search-path>, 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 { 
        // <Your code goes here>
        int f = 0;
        f = 7 / f;   // Trigger a divide-by-zero exception
        return 0;
    } __except (DumpStackTrace(GetExceptionInformation())) {  
        return 1;
    }
}
like image 21
Tim Cooper Avatar answered Sep 28 '22 09:09

Tim Cooper


OR use stackwalker from here...http://www.codeproject.com/Articles/11132/Walking-the-callstack

use file stackwalker.cpp and stackwalker.h in your project

Minimal code:

#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;

You need to generate some debugging info to see function names and line numbers Use proper flag in case of Microsoft VIsual C++ You may need to put .pdb file as well where your exe is.

like image 25
Aftershock Avatar answered Sep 28 '22 10:09

Aftershock