应在编译时将 Git 分支与版本固化为带 __attribute__((used)) 的 C++ 全局字符串常量,崩溃信号处理器中用 write() 安全输出,避免运行时调用 git 命令。

崩溃时如何获取编译时嵌入的 Git 分支与版本
直接在崩溃日志里打印 Git 信息,不能靠运行时调用 git 命令——进程已崩溃,shell 调用不可靠,且可能被沙箱/容器限制。正确做法是:在编译阶段把 Git 状态固化为 C++ 字符串常量,链接进二进制。崩溃堆栈捕获逻辑(如 signal handler)直接读取这些只读全局变量即可输出。
__attribute__((used)) 防止链接器丢弃 Git 全局变量
用 git describe --always --dirty 和 git rev-parse --abbrev-ref HEAD 生成字符串,写入头文件或 .cpp 文件后,必须加 __attribute__((used))(GCC/Clang)或 __declspec(selectany)(MSVC),否则 LTO 或 strip 会把未显式引用的全局变量优化掉——崩溃日志里就只剩空字符串。
示例(CMake 中生成):
# 在 CMakeLists.txt 中
execute_process(COMMAND git describe --always --dirty RESULT_VARIABLE GIT_DESCRIBE_RESULT OUTPUT_VARIABLE GIT_DESCRIBE)
if(GIT_DESCRIBE_RESULT EQUAL 0)
string(STRIP "${GIT_DESCRIBE}" GIT_DESCRIBE)
else()
set(GIT_DESCRIBE "unknown")
endif()
<p>execute_process(COMMAND git rev-parse --abbrev-ref HEAD RESULT_VARIABLE GIT_BRANCH_RESULT OUTPUT_VARIABLE GIT_BRANCH)
if(GIT_BRANCH_RESULT EQUAL 0)
string(STRIP "${GIT_BRANCH}" GIT_BRANCH)
else()
set(GIT_BRANCH "unknown")
endif()</p><p>configure_file("${CMAKE_SOURCE_DIR}/build_info.h.in" "${CMAKE_BINARY_DIR}/build_info.h")</p><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/6e7abc4abb9f" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">C++免费学习笔记(深入)</a>”;</p>对应 build_info.h.in:
#pragma once extern const char kGitBranch[] __attribute__((used)); extern const char kGitDescribe[] __attribute__((used));
再在 .cpp 中定义:
#include "build_info.h" const char kGitBranch[] __attribute__((used)) = "@GIT_BRANCH@"; const char kGitDescribe[] __attribute__((used)) = "@GIT_DESCRIBE@";
signal handler 中安全打印 Git 信息的限制
Linux/macOS 的 signal(SIGSEGV, ...) handler 是异步信号安全上下文,只能调用 async-signal-safe 函数(write() 可以,printf()/std::cout 不行)。所以崩溃日志中输出 Git 信息必须用 write(STDERR_FILENO, ...) 直接写原始字节。
关键点:
-
kGitBranch和kGitDescribe必须是全局、静态存储期、字面量初始化的字符串(不能是std::string) - 不要在 handler 里做任何内存分配、锁操作、虚函数调用
- 建议把 Git 信息拼接到预分配的固定大小 buffer 中(如
char buf[512]),再用write()一次性输出
CMake + Git hook 避免本地修改导致版本信息失效
仅靠 CMake configure 阶段执行 git 命令,容易漏掉后续工作区修改(比如改了代码但没 git add)。更健壮的做法:
- 在
pre-build步骤重新生成build_info.h,确保每次构建都反映最新状态 - 添加
.git/hooks/pre-commit检查是否误提交了硬编码的 “unknown” 字符串 - CI 构建时设置
CI=true环境变量,强制走 clean checkout +git fetch --tags,避免 shallow clone 导致git describe失败
如果项目用了 git worktree 或 submodule,git rev-parse --abbrev-ref HEAD 可能返回 HEAD 而非分支名——此时应 fallback 到 git symbolic-ref --short HEAD 2>/dev/null || echo "detached"。
Git 信息不是万能的,它只告诉你“编译那一刻”的状态;若想定位到具体哪次 CI 构建、哪个流水线任务,还得配合构建编号和时间戳一起写入。



















