崩溃监听实践
# 01.崩溃监听实践
# 目录介绍
- 01.QT应用崩溃概念
- 1.1 背景介绍
- 1.2 崩溃基础概念
- 02.崩溃监听方案
- 2.1 Unix信号处理
- 2.2 结构化异常处理
- 2.3 Qt原生方案
- 2.4 崩溃报告库
- 2.5 综合多种技术
- 03.崩溃监听实践
- 3.1 总的思路概括
- 3.2 崩溃处理类
- 3.3 崩溃处理实现
- 3.4 在程序中使用
- 3.5 实现原理分析
- 3.6 模拟C++崩溃
- 3.7 模拟Qt崩溃
- 3.8 验证崩溃处理
- 04.高级功能设计
- 4.1 符号化堆栈跟踪
- 4.2 远程错误报告
- 4.3 多线程支持
- 06.常见问题思考
- 6.1 信号函数中崩溃
- 6.2 堆栈信息不完整
# 02.崩溃监听方案
| 方案 | 适用场景 | 复杂度 | 跨平台 | 信息丰富度 |
|---|---|---|---|---|
| 信号处理 | 简单应用、快速调试 | ★☆☆ | Linux/Mac | ★★☆ |
| SEH | Windows 专用应用 | ★★☆ | 仅 Windows | ★★☆ |
| Qt 消息处理器 | Qt 框架错误 | ★☆☆ | 跨平台 | ★★☆ |
| Breakpad | 生产环境、跨平台 | ★★★ | 跨平台 | ★★★ |
| Crashpad | 现代应用、高性能 | ★★★ | 跨平台 | ★★★★ |
| QBreakpad | Qt 应用集成 | ★★☆ | 跨平台 | ★★★ |
根据应用需求选择合适的方案:
- 普通应用:信号处理 + Qt 消息处理器
- 商业应用:Breakpad/Crashpad
- Qt 专属应用:QBreakpad
- 高性能要求:Crashpad
- 复杂环境:混合方案
# 2.1 Unix信号处理
Linux/Unix信号处理
#include <csignal>
#include <execinfo.h>
void signalHandler(int sig) {
void* array[10];
int size = backtrace(array, 10);
char** symbols = backtrace_symbols(array, size);
// 记录堆栈信息
for (int i = 0; i < size; i++) {
qCritical() << symbols[i];
}
free(symbols);
exit(1);
}
int main(int argc, char *argv[]) {
// 注册信号处理
signal(SIGSEGV, signalHandler); // 段错误
signal(SIGABRT, signalHandler); // 中止信号
signal(SIGFPE, signalHandler); // 浮点异常
QApplication app(argc, argv);
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
优点:简单直接;跨 Linux/Unix 平台;可获取堆栈信息
缺点:Windows 不支持;信号处理函数中只能使用异步安全函数
# 2.2 结构化异常处理
Windows 结构化异常处理 (SEH)
#include <windows.h>
LONG WINAPI exceptionHandler(PEXCEPTION_POINTERS pExceptionPtrs) {
// 记录异常信息
qCritical() << "Exception code: 0x" << hex << pExceptionPtrs->ExceptionRecord->ExceptionCode;
return EXCEPTION_EXECUTE_HANDLER;
}
int main(int argc, char *argv[]) {
__try {
QApplication app(argc, argv);
// ...
return app.exec();
}
__except(exceptionHandler(GetExceptionInformation())) {
return -1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
优点:Windows 平台原生支持;可捕获硬件异常
缺点:仅限 Windows;无法获取完整堆栈信息
# 2.3 Qt原生方案
1.Qt 消息处理器
#include <QDebug>
void qtMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (type == QtFatalMsg) {
// 记录致命错误
qCritical() << "Fatal error:" << msg;
qCritical() << "File:" << context.file;
qCritical() << "Line:" << context.line;
qCritical() << "Function:" << context.function;
// 获取堆栈信息
void* array[10];
int size = backtrace(array, 10);
char** symbols = backtrace_symbols(array, size);
for (int i = 0; i < size; i++) {
qCritical() << symbols[i];
}
free(symbols);
}
}
int main(int argc, char *argv[]) {
qInstallMessageHandler(qtMessageHandler);
QApplication app(argc, argv);
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
优点:捕获 Qt 框架自身的错误;可获取上下文信息
缺点:无法捕获非 Qt 错误(如 C++ 标准库错误)
2.QObject错误处理
class SafeObject : public QObject {
Q_OBJECT
protected:
bool event(QEvent *event) override {
try {
return QObject::event(event);
} catch (const std::exception& e) {
qCritical() << "Exception caught:" << e.what();
return true;
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
优点:对象级别的错误捕获;防止异常传播
缺点:无法捕获所有错误;性能开销较大
# 2.4 崩溃报告库
- Google Breakpad。优点:跨平台支持;生成 minidump 文件;支持符号解析;可集成远程报告。缺点:需要额外依赖;集成复杂度较高
- Crashpad (Breakpad 继任者)。优点:更现代的设计;更好的性能;支持更多平台。缺点:集成更复杂;文档较少
# 2.5 综合多种技术
混合方案:综合多种技术
#include <QApplication>
#include <csignal>
#include <QBreakpadHandler.h>
#include <client/crashpad_client.h>
// 信号处理
void signalHandler(int sig) {
// 记录信号信息
// ...
exit(1);
}
// Qt 消息处理器
void qtMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
if (type == QtFatalMsg) {
// 处理致命错误
// ...
}
}
int main(int argc, char *argv[]) {
// 基础信号处理
signal(SIGSEGV, signalHandler);
signal(SIGABRT, signalHandler);
// Qt 消息处理
qInstallMessageHandler(qtMessageHandler);
// Breakpad 崩溃报告
QBreakpadInstance.setDumpPath("crashes/");
// Crashpad 集成
crashpad::CrashpadClient client;
client.StartHandler(/*...*/);
QApplication app(argc, argv);
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 03.崩溃监听实践
为了在Qt应用中捕获崩溃信息,我们可以结合信号处理(针对C++层面的崩溃)和Qt的消息处理器(针对Qt的致命错误)。包括如何设置信号处理器和Qt消息处理器,以及如何记录崩溃信息到日志文件。
- 设置信号处理器,捕获常见的崩溃信号(如SIGSEGV, SIGABRT等)。
- 设置Qt消息处理器,捕获Qt的致命错误(如QtFatalMsg)。
- 在信号处理器和Qt消息处理器中,记录崩溃时的堆栈信息。
- 将崩溃信息写入日志文件,并确保在崩溃前刷新日志。
# 3.1 总的思路概括
在 Qt 应用中监听崩溃需要结合信号处理和 Qt 的错误处理机制。下面是一个完整的解决方案,包括实现原理、代码示例和最佳实践。
# 3.2 崩溃处理类
崩溃处理类 (CrashHandler.h)
#pragma once
#include <QObject>
#include <QThread>
#include <csignal>
#include <execinfo.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <sys/resource.h>
#include <yt_palm_utils.h>
class CrashHandler : public QObject {
Q_OBJECT
public:
explicit CrashHandler(QObject* parent = nullptr);
~CrashHandler();
static void enableCoreDumps();
static void installQtHandler();
private:
void setupSignalHandlers();
static void signalHandler(int sig);
static void qtMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
static const char* getSignalName(int sig);
static void printStackTrace(std::stringstream& ss);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 3.3 崩溃处理实现
崩溃处理实现 (CrashHandler.cpp)
#include "CrashHandler.h"
#include <QCoreApplication>
#include <iostream>
#include <fstream>
CrashHandler::CrashHandler(QObject* parent) : QObject(parent) {
setupSignalHandlers();
installQtHandler();
}
CrashHandler::~CrashHandler() {
// 恢复默认信号处理
signal(SIGSEGV, SIG_DFL);
signal(SIGABRT, SIG_DFL);
signal(SIGFPE, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGSYS, SIG_DFL);
signal(SIGTRAP, SIG_DFL);
signal(SIGXCPU, SIG_DFL);
signal(SIGXFSZ, SIG_DFL);
}
void CrashHandler::enableCoreDumps() {
struct rlimit core_limit = { RLIM_INFINITY, RLIM_INFINITY };
setrlimit(RLIMIT_CORE, &core_limit);
}
void CrashHandler::installQtHandler() {
qInstallMessageHandler(qtMessageHandler);
}
void CrashHandler::setupSignalHandlers() {
// 致命错误信号
signal(SIGSEGV, signalHandler); // 段错误
signal(SIGABRT, signalHandler); // 中止信号
signal(SIGFPE, signalHandler); // 浮点异常
signal(SIGILL, signalHandler); // 非法指令
signal(SIGBUS, signalHandler); // 总线错误
signal(SIGSYS, signalHandler); // 非法系统调用
// 终端相关信号
signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号
// 其他致命信号
signal(SIGTRAP, signalHandler); // 陷阱跟踪
signal(SIGXCPU, signalHandler); // CPU时间超限
signal(SIGXFSZ, signalHandler); // 文件大小超限
}
void CrashHandler::signalHandler(int sig) {
// 防止递归崩溃处理
static volatile sig_atomic_t handlingCrash = 0;
if (handlingCrash) {
_exit(EXIT_FAILURE);
}
handlingCrash = 1;
// 构建错误信息
std::stringstream ss;
ss << "\n⚡️ CRASH DETECTED ⚡️\n";
ss << "════════════════════════════════════════\n";
ss << "Signal: " << getSignalName(sig) << " (" << sig << ")\n";
ss << "PID: " << getpid() << "\n";
ss << "Thread ID: " << QThread::currentThreadId() << "\n";
ss << "Timestamp: " << QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "\n";
// 打印堆栈跟踪
printStackTrace(ss);
ss << "════════════════════════════════════════\n";
std::string logMsg = ss.str();
// 输出到日志
YT_LOGFATAL(QString::fromStdString(logMsg));
// 写入崩溃文件
std::ofstream crashFile("crash_report.log", std::ios::app);
if (crashFile.is_open()) {
crashFile << logMsg;
crashFile.close();
}
// 立即终止进程
_exit(EXIT_FAILURE);
}
void CrashHandler::qtMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
// 处理 Qt 致命错误
if (type == QtFatalMsg) {
std::stringstream ss;
ss << "\n⚡️ QT FATAL ERROR ⚡️\n";
ss << "════════════════════════════════════════\n";
ss << "Message: " << msg.toStdString() << "\n";
ss << "File: " << context.file << "\n";
ss << "Line: " << context.line << "\n";
ss << "Function: " << context.function << "\n";
ss << "PID: " << getpid() << "\n";
ss << "Thread ID: " << QThread::currentThreadId() << "\n";
ss << "Timestamp: " << QDateTime::currentDateTime().toString(Qt::ISODate).toStdString() << "\n";
// 打印堆栈跟踪
printStackTrace(ss);
ss << "════════════════════════════════════════\n";
std::string logMsg = ss.str();
YT_LOGFATAL(QString::fromStdString(logMsg));
// 写入崩溃文件
std::ofstream crashFile("crash_report.log", std::ios::app);
if (crashFile.is_open()) {
crashFile << logMsg;
crashFile.close();
}
// 触发信号处理
signalHandler(SIGABRT);
} else {
// 其他日志级别正常处理
// 这里可以使用你现有的日志处理机制
}
}
void CrashHandler::printStackTrace(std::stringstream& ss) {
// 获取堆栈跟踪
void* callstack[20];
const int maxFrames = sizeof(callstack) / sizeof(callstack[0]);
int frames = backtrace(callstack, maxFrames);
char** symbols = backtrace_symbols(callstack, frames);
ss << "Stack trace (" << frames << " frames):\n";
if (symbols) {
for (int i = 0; i < frames; ++i) {
ss << " #" << i << " " << symbols[i] << "\n";
}
free(symbols);
} else {
ss << " Failed to get stack symbols\n";
}
}
const char* CrashHandler::getSignalName(int sig) {
switch(sig) {
case SIGSEGV: return "Segmentation fault";
case SIGABRT: return "Abort";
case SIGFPE: return "Floating point exception";
case SIGILL: return "Illegal instruction";
case SIGBUS: return "Bus error";
case SIGSYS: return "Bad system call";
case SIGTRAP: return "Trap";
case SIGXCPU: return "CPU time limit exceeded";
case SIGXFSZ: return "File size limit exceeded";
default: return "Unknown signal";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# 3.4 在程序中使用
在应用程序中使用 (main.cpp)
#include <QApplication>
#include "CrashHandler.h"
int main(int argc, char *argv[]) {
// 启用核心转储
CrashHandler::enableCoreDumps();
// 安装崩溃处理器
CrashHandler crashHandler;
return app.exec();
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 3.5 实现原理分析
- 信号处理机制:使用
signal()函数注册信号处理程序;捕获常见崩溃信号:SIGSEGV、SIGABRT、SIGFPE 等;在信号处理函数中获取堆栈跟踪信息 - Qt 错误处理:使用
qInstallMessageHandler()安装自定义消息处理器;捕获 QtFatalMsg 级别的错误;将 Qt 错误转换为信号处理 - 堆栈跟踪:使用
backtrace()和backtrace_symbols()获取堆栈信息;将堆栈信息格式化为可读字符串 - 核心转储:使用
setrlimit()启用核心转储;生成 core dump 文件用于事后分析
# 3.6 模拟C++崩溃
- 方法 1:调用 Qt.quit() 或 Qt.exit()。通过调用 Qt.quit() 或 Qt.exit() 可以强制退出应用程序,模拟崩溃的效果。测试结果:应用退出
Button {
onClicked: {
console.log("Simulating crash...");
Qt.quit(); // 或者使用 Qt.exit(1);
}
}
1
2
3
4
5
6
2
3
4
5
6
- 触发未捕获的异常。在 QML 中触发未捕获的异常,可以模拟应用程序崩溃的效果。测试结果:不会崩溃
Button {
onClicked: {
var obj = null;
obj.invalidMethod(); // 触发未捕获的异常
}
}
1
2
3
4
5
6
2
3
4
5
6
- 方法 3:调用 C++ 代码触发崩溃。在 QML 中调用 C++ 代码,通过 C++ 触发崩溃(如访问空指针、除以零等)。测试结果:崩溃
Q_INVOKABLE void simulateCrash() {
qDebug() << "Simulating crash in C++...";
int* ptr = nullptr;
*ptr = 42; // 访问空指针,触发崩溃
}
1
2
3
4
5
2
3
4
5
- 方法 4:调用系统函数触发崩溃。在 QML 中调用 C++ 代码,通过系统函数(如 abort() 或 exit())触发崩溃。测试结果:崩溃
Q_INVOKABLE void simulateCrash() {
qDebug() << "Simulating crash in C++...";
std::abort(); // 或者使用 std::exit(1);
}
1
2
3
4
2
3
4
- 方法 5:模拟内存泄漏。通过不断分配内存而不释放,模拟内存泄漏导致应用程序崩溃。测试结果:崩溃
Q_INVOKABLE void simulateCrash() {
std::vector<int*> ptrs;
while (true) {
ptrs.push_back(new int[1000000]); // 不断分配内存
}
}
1
2
3
4
5
6
2
3
4
5
6
# 3.7 模拟Qt崩溃
在 Qt 应用中,要模拟触发 qInstallMessageHandler 监听的崩溃,主要可以通过触发 Qt 的致命错误(QtFatalMsg)来实现。
qFatal 与信号崩溃的区别:qInstallMessageHandler 只能捕获通过 qFatal() 或 Q_ASSERT 系列宏触发的错误
- 直接调用 qFatal(),这是最直接的方法,直接调用 Qt 的致命错误函数:
#include <QDebug>
void triggerFatalError()
{
// 直接触发致命错误
qFatal("This is a simulated fatal error");
}
1
2
3
4
5
6
7
2
3
4
5
6
7
- 使用 Q_ASSERT 宏,Qt 的断言宏在失败时会触发致命错误:
void triggerAssertFailure() {
// 触发断言失败
int* ptr = nullptr;
Q_ASSERT(ptr != nullptr); // 这将触发 qFatal
// 带消息的断言
Q_ASSERT_X(false, "Test Assertion", "This is a test assertion failure");
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 触发 Qt 内部错误,模拟一些会导致 Qt 内部调用
qFatal()的场景:
void triggerQtInternalError()
{
// 尝试创建无效的 QObject 连接
QObject obj1, obj2;
bool success = QObject::connect(&obj1, SIGNAL(destroyed()), &obj2, SLOT(deleteLater()));
Q_ASSERT_X(!success, "Connection", "This connection should fail");
// 尝试访问无效的 QModelIndex
QModelIndex invalidIndex;
QVariant data = invalidIndex.data();
Q_ASSERT_X(!data.isValid(), "ModelIndex", "Invalid index should return invalid data");
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 使用 Q_CHECK_PTR,Qt 的内存检查宏在检测到空指针时会触发致命错误:
void triggerNullPointerCheck()
{
// 分配内存失败检查
int* ptr = new int[1000000000000]; // 尝试分配超大内存
Q_CHECK_PTR(ptr); // 内存分配失败时会触发 qFatal
}
1
2
3
4
5
6
2
3
4
5
6
- 模拟资源耗尽错误
void triggerResourceExhaustion()
{
// 创建大量对象耗尽资源
QVector<QString*> strings;
try {
while (true) {
strings.append(new QString(100000, 'X')); // 分配大字符串
}
} catch (const std::bad_alloc&) {
qFatal("Memory allocation failed");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 内存损坏测试
void triggerMemoryCorruption()
{
int *array = new int[10];
array[10] = 42; // 故意越界写入
qFatal("Memory corruption simulated");
}
1
2
3
4
5
6
2
3
4
5
6
# 3.8 验证崩溃处理
- 检查日志文件:确认崩溃日志已生成;验证日志包含时间戳、错误消息和堆栈跟踪;检查系统信息和应用信息是否正确
- 观察程序行为:程序应在崩溃后立即终止;日志中不应有二次崩溃记录
- 分析堆栈跟踪:使用 addr2line 解析地址;验证堆栈跟踪指向正确的代码位置;确保符号已正确解析
- 性能测试:确保崩溃处理不会导致二次崩溃;验证内存使用情况
# 04.高级功能设计
# 4.1 符号化堆栈跟踪
添加符号解析功能,使堆栈跟踪更易读:
#include <cxxabi.h>
std::string CrashHandler::demangleSymbol(const char* symbol) {
// 示例输入: ./program(_ZN9MyClass10myFunctionEv+0x15) [0x123456]
char* begin = nullptr;
char* end = nullptr;
// 查找函数名开始和结束位置
for (char* p = const_cast<char*>(symbol); *p; ++p) {
if (*p == '(') begin = p;
else if (*p == '+' || *p == ')') end = p;
}
if (begin && end && begin < end) {
*begin++ = '\0';
*end = '\0';
int status = 0;
char* demangled = abi::__cxa_demangle(begin, nullptr, nullptr, &status);
if (status == 0 && demangled) {
std::string result = symbol; // 文件名部分
result += "(";
result += demangled;
if (end < symbol + strlen(symbol)) {
result += end; // 地址偏移部分
}
free(demangled);
return result;
}
}
return symbol; // 无法解析则返回原始字符串
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 4.2 远程错误报告
添加网络功能,将崩溃报告发送到服务器:
void CrashHandler::reportCrash(const std::string& report) {
QNetworkAccessManager manager;
QNetworkRequest request(QUrl("https://your-server.com/crash-report"));
QJsonObject json;
json["app"] = QCoreApplication::applicationName();
json["version"] = QCoreApplication::applicationVersion();
json["platform"] = QSysInfo::prettyProductName();
json["report"] = QString::fromStdString(report);
//然后发出网络请求
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
监控崩溃报告:定期检查崩溃日志文件;设置自动报警机制
# 4.3 多线程支持
确保在多线程环境中正确处理信号:
// 在需要监控的线程中调用
void setupThreadSignalHandlers() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGSEGV);
sigaddset(&mask, SIGABRT);
// 添加其他信号...
pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 06.常见问题思考
# 6.1 信号函数中崩溃
解决方案:
- 使用异步信号安全函数
- 避免复杂的内存分配
- 添加递归保护
- 使用 _exit() 替代 exit()
# 6.2 堆栈信息不完整
解决方案:
- 增加堆栈帧数量
void* callstack[50];1 - 使用 -fno-omit-frame-pointer 编译选项
- 使用 -rdynamic 链接选项
上次更新: 2026/06/10, 11:13:41