【问题标题】:Windows C++ stack trace from a running app来自正在运行的应用程序的 Windows C++ 堆栈跟踪
【发布时间】:2011-09-06 13:15:11
【问题描述】:

全部,

我看到一个应用程序,一个 SVN Visual Studio 插件,它在崩溃时显示了一个漂亮的可读堆栈跟踪。

我很想将它添加到我的应用程序中。 我该如何提供?无需通过电子邮件发送信息,只需视觉显示即可。

【问题讨论】:

    标签: visual-c++


    【解决方案1】:

    必要代码的核心是StackWalk64。为了从中获得更多,您还需要/需要使用SymGetSymFromAddr64(需要SymLoadModule64)和(可能)SymGetLineFromAddr64GetThreadContext 来获取符号名称。如果目标是用 C++ 编写的,您可能还想使用UnDecorateSymbolName。除了这些,您还需要一些辅助工具,例如 SymInitializeSymCleanup,可能还有 SymSetOptions

    这是一个相当简单的演示。它产生的堆栈转储比美观更实用,但大概有了进行堆栈跟踪的基础,您可以根据需要显示结果:

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

    【讨论】:

    • 我尝试了这段代码,它在我的开发笔记本电脑上运行,但是当我的同事尝试运行它时,他们没有得到任何堆栈跟踪信息。他们是否需要安装一些 .dll 或其他文件?
    • 嗯...实际上并不需要额外的 DLL,但可执行文件需要有调试信息才能完成很多工作。
    • 我自然有调试信息。在我的开发笔记本电脑上,它可以在 VS++ 内部或外部工作。但是在我尝试过的每台没有安装 VS++ 的笔记本电脑上,我都没有得到堆栈跟踪。您是否在没有 VS++ 的计算机上尝试过您的代码?
    • @TimCooper:是的,很多——但已经有一段时间了,所以虽然我不记得安装额外的“东西”来让它工作,但这可能只是我的结果健忘。
    • 经过一些艰苦的“消息窗口”调试后,我了解到它在 SymGetSymFromAddr64() 中失败 - 这将返回 FALSE。但是当我遍历堆栈时,这总是返回 FALSE 它返回没有意义的垃圾(随机不相关的方法名称)。重申一下,它是一个调试模式 .exe,可以在我的开发机器上生成完美的符号堆栈跟踪。
    【解决方案2】:

    我修改了 Jerry Coffin 的代码,如下所示。修改:

    A.我总是错过最重要的框架:实际触发异常的行。原来这是因为“do”循环正在移动到顶部而不是底部的下一帧。

    B.我删除了线程的东西。

    C.我简化了一些。

    您应该在底部剪切“WinMain()”函数,而是将 __try .. __except 内容放在您自己的“main/WinMain”函数中。还可以用您自己的函数替换“YourMessage”以显示消息框或通过电子邮件发送或其他任何内容。

    符号信息存储在 .pdb 文件中,而不是 .exe 中。您需要为您的用户提供您的 .pdb 文件,并且您需要确保他们的进程可以找到它。请参阅下面代码中的字符串 - 将其替换为可在用户计算机上运行的文件夹,或 NULL - NULL 表示它将在进程的当前工作目录中搜索。 .pdb 文件在用户计算机上的名称必须与运行编译器时的名称完全相同 - 要将其配置为不同的名称,请参阅“属性 > 链接器 > 调试 > 生成程序数据库文件”。

    #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"
                "you@initech.com\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;
        }
    }
    

    【讨论】:

    • 这是我发现的唯一一段代码,它可以在大约 7 小时的 printfing 后获得堆栈跟踪。它应该得到比我能给你更多的分数。
    • @Tim 一个基本问题,但是如何将您的代码挂接到我的应用程序,以便捕获未捕获的异常(例如除以 0)
    • 上面提供的“WinMain”已经给你一个被零除的错误,然后被捕获,作为框架的测试。
    • 堆栈跟踪超过10为什么会退出?
    【解决方案3】:

    或者从这里使用 stackwalker...http://www.codeproject.com/Articles/11132/Walking-the-callstack

    在您的项目中使用文件 stackwalker.cpp 和 stackwalker.h

    最少的代码:

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

    你需要生成一些调试信息来查看函数名和行号 在 Microsoft VIsual C++ 的情况下使用正确的标志 您可能还需要将 .pdb 文件放在 exe 所在的位置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-13
      • 1970-01-01
      • 2014-10-10
      • 1970-01-01
      • 2023-03-14
      • 2023-04-07
      • 2017-12-01
      • 1970-01-01
      相关资源
      最近更新 更多