Crash 函数手册
C++ 后端服务器框架Crash 函数手册
CHAPTER 16

Crash 函数手册

6 分钟阅读 · 1,575 字

依据源码:include/Crash.h、src/Crash.cpp。


0. 编译器与平台宏(Crash.h L8–15)

在 Crash.h 中根据编译器定义以下宏之一(用于 printCrashInfo 分支与日志中的 Compiler 字段):

宏 条件
COMPILER_MSVC _MSC_VER
COMPILER_MINGW __MINGW32__
COMPILER_LINUX __linux__

注意:printCrashInfo 写日志时还引用了 COMPILER_CLANG、COMPILER_GCC(Crash.cpp L284–287),这些由 CMake/工程 定义;若未定义,会落入 Compiler: Unknown。这与头文件三元宏并存,属配置层细节。


1. 类型别名 CleanupCallback

  • 定义:using CleanupCallback = std::function<void()>(Crash.h L39)
  • 含义:在崩溃或主动 gracefulExit 时,在进程退出前执行的一段用户逻辑(例如关闭 socket、刷日志)。
  • 线程语义:在 gracefulExit 调用栈内同步执行;应简短、避免再次死锁。

2. 类 CrashHandler — 公开接口

2.1 static CrashHandler& instance()

  • 位置:Crash.h L44–47
  • 实现:Meyers 单例,static CrashHandler handler。
  • 线程安全:C++11 起静态局部变量初始化线程安全。
  • 生命周期:进程级,不手动销毁。

2.2 void init(const CleanupCallback& cleanupFunc)

  • 声明:Crash.h L50
  • 定义:Crash.cpp L26–80
  • 作用:安装各类崩溃/终止钩子,并保存清理回调。

步骤拆解

  1. 保存回调:cleanupCallback_ = cleanupFunc
  2. 重置退出标志:isExiting_ = false(允许再次 init 时重新注册语义上“新的一次”;实际项目通常只 init 一次)
  3. std::set_terminate(terminateHandler)
    未捕获的 C++ 异常导致 std::terminate 时进入 terminateHandler。
  4. 信号数组(Crash.cpp L34–44)
    SIGINT, SIGTERM, SIGSEGV, SIGABRT, SIGILL, SIGFPE,Linux 额外 SIGBUS。
  5. 逐个注册信号
    • Windows:signal(sig, signalHandler),失败打印 cerr
    • 非 Windows:sigaction,sa_handler = signalHandler,SA_RESTART,失败打印 cerr
  6. 平台扩展
    • Windows:SetUnhandledExceptionFilter(sehExceptionHandler);SetConsoleCtrlHandler(consoleCtrlHandler, TRUE)
    • Linux:再对 SIGQUIT 注册一次 sigaction(与上面相同 handler)
  7. 打印:Crash handler initialized successfully

可重复调用行为

  • 多次 init 会再次 set_terminate、signal/sigaction、Windows 过滤器注册;行为依赖 CRT/OS,建议进程内只调用一次。

2.3 void gracefulExit(int exitCode = 1, const std::string& reason = "Unknown crash")

  • 声明:Crash.h L53
  • 定义:Crash.cpp L83–122
  • 作用:统一的“崩溃/强制退出”出口:打印、写诊断、执行清理、立即终止进程。

步骤拆解

  1. isExiting_.exchange(true)
    若已为 true,直接 return(防止多线程重复进入完整退出流程)。
  2. std::lock_guard<std::mutex> lock(exitMutex_)
    序列化退出阶段的日志与清理(与 exchange 配合:只有首个线程做重活)。
  3. stderr 横幅:Reason、Exit code。
  4. printCrashInfo(exitCode, reason)
    控制台诊断 + 追加写 crash_log.txt(见第 4 节)。
  5. 清理回调
    若 cleanupCallback_ 非空:try/catch 包裹调用;清理失败只打日志,不阻止退出。
  6. 终止进程
    • Windows:ExitProcess(static_cast<UINT>(exitCode))
    • Linux:_exit(exitCode)

与 exit() 的差异

  • _exit / ExitProcess:不运行完整 C++ 静态析构、不刷全量 stdio;适合崩溃路径,避免在已损坏状态下再执行复杂析构。

面试点

  • 首入者执行清理;其它线程若再触发 gracefulExit 会在 exchange 后直接返回,可能仍在跑业务——需业务层配合停服。

3. CrashHandler — 私有成员

成员 含义
CleanupCallback cleanupCallback_ init 注入的清理逻辑
std::atomic<bool> isExiting_ 是否已进入/完成退出流程的门闩
std::mutex exitMutex_ 保护退出阶段临界区

私有构造 / 析构

  • CrashHandler():cleanupCallback_(nullptr), isExiting_(false)
  • ~CrashHandler() = default:单例进程结束时随静态存储期销毁,通常发生在 main 返回之后;崩溃路径已 _exit,往往走不到这里。

4. static void signalHandler(int sig)

  • 声明:Crash.h L64
  • 定义:Crash.cpp L125–141
  • 作用:将信号编号映射为可读 reason 字符串,调用 instance().gracefulExit(sig, reason)。

映射表

信号 reason 文案
SIGINT Ctrl+C
SIGTERM Kill
SIGSEGV 段错误
SIGABRT abort
SIGILL 非法指令
SIGFPE 浮点异常
Linux SIGBUS 总线错误
Linux SIGQUIT Ctrl+\
default Unknown signal (编号)

注意:gracefulExit 的第一个参数此处为 sig,在 Linux 上会作为“退出码”传入 _exit(sig);与业务约定的 0–255 可能不一致,属实现细节。


5. Windows 专有静态函数

5.1 static LONG WINAPI sehExceptionHandler(EXCEPTION_POINTERS* pException)

  • 声明:Crash.h L68
  • 定义:Crash.cpp L144–166
  • 作用:未处理 SEH 时由系统调用;解析 ExceptionCode,gracefulExit(code, reason),返回 EXCEPTION_EXECUTE_HANDLER。

显式映射的异常码

  • EXCEPTION_ACCESS_VIOLATION
  • EXCEPTION_INT_DIVIDE_BY_ZERO
  • EXCEPTION_STACK_OVERFLOW
  • 其他:格式化为十六进制码字符串

空指针:若 pException 为空,exceptionCode = 0。


5.2 static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType)

  • 声明:Crash.h L70
  • 定义:Crash.cpp L171–188
  • 作用:控制台控制事件(关闭窗口、Ctrl+C、关机等)映射为 reason,调用 gracefulExit((int)dwCtrlType, reason),返回 TRUE。

6. static void terminateHandler()

  • 声明:Crash.h L74
  • 定义:Crash.cpp L192–202
  • 作用:std::terminate 钩子;尝试 throw; 在 terminate 上下文中捕获 std::exception 的 what,否则标记未知类型;最后 gracefulExit(EXIT_FAILURE, reason)。

限制:在部分实现中 terminate 时 throw; 行为依赖标准库;此处用于尽力提取信息。


7. static void printCrashInfo(int sig, const std::string& reason)

  • 声明:Crash.h L77
  • 定义:Crash.cpp L220–298
  • 参数:
    • sig:在函数开头 (void)sig,控制台分支不使用;仅用于写文件时作为 ExitCode 字段(L277)。
    • reason:人类可读原因;MinGW 分支会再打一份到 cerr。

控制台输出(条件编译)

  • COMPILER_MSVC:打印 Windows/MSVC 标题与时间;注释称 MSVC 栈回溯逻辑省略。
  • COMPILER_MINGW:时间、reason;SymInitialize + 模块快照打印前 10 个模块基址;提示 MinGW 无完整 SEH 栈回溯。
  • COMPILER_LINUX:打印 Linux 标题与时间;注释称 Linux 专属回溯省略。

文件输出(跨分支共用)

  • 追加打开 crash_log.txt
  • 一行:[时间] ExitCode: <sig>, Reason: <reason>
  • 下一行:Compiler 类型(依赖 COMPILER_* 宏组合)
  • 分隔线 ----------------------------------------

辅助函数 formatCurrentTime()(文件内 static)

  • 位置:Crash.cpp L205–216
  • 作用:localtime_s(Windows)或 localtime_r(Linux),格式 %Y-%m-%d %H:%M:%S。

8. 宏 SAFE_ASSERT(expr)

  • 位置:Crash.h L88–93
  • 展开:若 !(expr),则 CrashHandler::instance().gracefulExit(1, "Assert failed: " #expr)。
  • 与 assert() 区别:不依赖 NDEBUG;失败即走统一崩溃退出与清理(若已 init)。

9. 与 Server 的集成(调用链记忆)

Server 构造函数中典型写法:

CrashHandler::instance().init([this]() { this->cleanup(); });

因此 SIGINT/SIGTERM、未处理异常、Windows SEH/控制台事件等路径最终会尝试执行 Server::cleanup()(在仍安全的前提下)。


10. 异步信号安全(面试高频)

在 signalHandler 中调用 gracefulExit,内部大量使用 iostream、锁、回调——不属于 POSIX 异步信号安全函数。

  • 工程上常见折中:信号处理器只做 write/置原子标志,由主线程收尾。
  • 本实现优先“统一落盘与清理”,适合服务端工具场景;严谨生产可再分层。

下一模块(索引顺序)

DBConnector → docs/modules/detail/DBConnector_FUNCTIONS.md。

全部章节