65.9K
CodeProject 正在变化。 阅读更多。
Home

C++ 异常与调用堆栈

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2023年8月13日

CPOL

2分钟阅读

viewsIcon

11547

downloadIcon

282

C++23 异常和 C++ Windows 异常与调用栈。

目录

示例代码托管在 Github 上。

我开发了两个 C++ 异常类,它们可以极大地帮助排查客户问题,这些问题在开发人员的系统上无法重现,并且调用了一个公共函数,但由于调用者众多而不知道是谁调用的。必须向客户提供一个可执行文件和一个与该可执行文件一起构建的 pdb 文件,才能获得正确的调用堆栈。pdb 文件包含调试符号。你不能提供稍后从相同代码构建的 pdb 文件。当构建可执行文件时,每个函数都会被赋予一个地址偏移量。pdb 是基于这些偏移量构建的。当再次构建二进制文件时,偏移量会发生变化。简而言之,pdb 的函数偏移量必须与可执行文件匹配。

带有调用堆栈的 C++23 异常类

今年通过的标准 C++23 引入了一个 stacktrace 类,用于检索调用堆栈跟踪。stacktrace 可以在异常期间的故障排除中提供帮助。因此,我从 std::runtime_error 派生了 Cpp23ExceptionWithCallstack,并添加了 stacktrace 作为成员以获取跟踪信息。我对 stacktrace 的使用很大程度上依赖于 Stephan T. Lavavej 的 Godbolt 示例

以下是如何使用 Cpp23ExceptionWithCallstack 的示例。

#include <iostream>
#include "Cpp23ExceptionWithCallstack.h"

using namespace std;

int inner(const int n) 
{
    if (n < 0) 
    {
        throw Cpp23ExceptionWithCallstack{"Error"};
    }
    return n * n;
}

int outer(const int n) 
{
    return inner(n);
}

int main() 
{
    try 
    {
        cout << outer(-5) << "\n";
    }
    catch (const Cpp23ExceptionWithCallstack& except) 
    {
        cout << except.GetCallstack() << std::endl;
    }
    return 0;
}

调用堆栈输出如下。请确保将可执行文件的 pdb 文件放在同一文件夹中,以在输出中获取函数名称、文件名和行号。

Cpp23CallStack!inner+0x3A, D:\GitHub\cpp_show_callstack\Cpp23CallStack\Cpp23CallStack.cpp(11)
Cpp23CallStack!outer+0x29, D:\GitHub\cpp_show_callstack\Cpp23CallStack\Cpp23CallStack.cpp(18)
Cpp23CallStack!main+0x37, D:\GitHub\cpp_show_callstack\Cpp23CallStack\Cpp23CallStack.cpp(22)
Cpp23CallStack!invoke_main+0x39, D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(79)
Cpp23CallStack!__scrt_common_main_seh+0x12E, D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(288)
Cpp23CallStack!__scrt_common_main+0xE, D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl(331)
Cpp23CallStack!mainCRTStartup+0xE, D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp(17)
KERNEL32!BaseThreadInitThunk+0x1D, unknown(0)
ntdll!RtlUserThreadStart+0x28, unknown(0)

带有调用堆栈的 C++ 异常类(仅限 Windows)

如果你没有使用 C++23 怎么办?别担心。我还有另一个 C++ 异常类,CppExceptionWithCallstack,它基于 StackWalker,由 Jochen Kalmbach 创建,它依赖于 Windows 调试 API 来完成其工作。最初,我在我的 StackWalker 派生类中遇到一个奇怪的问题,即第一次运行时打印了完整的调用堆栈,而在后续运行时仅打印了第一个函数。我调试了这个问题,发现其操作没有问题:它报告在第一个函数之后没有函数调用者。我在 StackWalker GitHub 上提交了此 issue。从那时起,我直接使用 StackWalker,而不是从它派生,我没有遇到这个问题。如果你遇到相同的问题,请告诉我。

以下是如何使用 CppExceptionWithCallstack 的示例。

#include <iostream>
#include "CppExceptionWithCallstack.h"

using namespace std;

int inner(const int n) 
{
    if (n < 0) 
    {
        throw CppExceptionWithCallstack{"Error"};
    }
    return n * n;
}

int outer(const int n) 
{
    return inner(n);
}

int main() 
{
    try 
    {
        cout << outer(-5) << "\n";
    }
    catch (const CppExceptionWithCallstack& except) 
    {
        cout << except.GetCallstack() << std::endl;
    }
    return 0;
}

这是调用堆栈输出。请注意,如上所述,你必须将可执行文件的 pdb 文件放在程序的文件夹中,才能在输出中获取函数名称、文件名和行号。

D:\GitHub\cpp_show_callstack\WinCallStack\StackWalker.cpp (1140): StackWalker::ShowCallstack
D:\GitHub\cpp_show_callstack\WinCallStack\CppExceptionWithCallstack.h (19): CppExceptionWithCallstack::CppExceptionWithCallstack
D:\GitHub\cpp_show_callstack\WinCallStack\WinCallStack.cpp (10): inner
D:\GitHub\cpp_show_callstack\WinCallStack\WinCallStack.cpp (18): outer
D:\GitHub\cpp_show_callstack\WinCallStack\WinCallStack.cpp (24): main
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl (79): invoke_main
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl (288): __scrt_common_main_seh
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl (331): __scrt_common_main
D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): mainCRTStartup
00007FFF7E7226AD (KERNEL32): (filename not available): BaseThreadInitThunk
00007FFF7F14AA68 (ntdll): (filename not available): RtlUserThreadStart

历史

  • 2023年8月14日:首次发布
© . All rights reserved.