memcpy异常场景下backtrace()调用崩溃,如何实现安全调用?
你遇到的二次崩溃本质是堆结构已被破坏导致的:当memcpy传入-1作为size时,由于size_t是无符号类型,这个值会被解析成极大的内存长度,触发大规模越界内存写入,直接破坏了malloc管理的堆元数据。此时在信号处理函数crit_err_hdlr中调用backtrace,而多数libc的backtrace实现会隐式调用malloc(比如分配临时存储),但堆已经损坏,malloc检测到无效的堆结构,直接触发二次崩溃。
而空指针写入的场景中,堆结构未被破坏,所以backtrace可以正常调用malloc完成栈回溯。
避免在信号处理函数中调用非异步信号安全的函数
POSIX标准明确规定,信号处理函数只能调用异步信号安全的函数(比如write、_exit等),malloc、backtrace、backtrace_symbols都不在这个列表里。直接在信号处理函数里调用这些函数本身就存在风险,尤其是堆已损坏的场景。预分配栈回溯缓冲区
把存储栈帧的void* array从信号处理函数的局部变量改为全局预分配的数组,避免在信号处理时触发栈上的内存分配(虽然栈分配本身是安全的,但全局分配更稳妥):// 全局预分配栈回溯缓冲区 #define BACKTRACE_SIZE 10 void* g_backtrace_array[BACKTRACE_SIZE]; void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { size_t size = backtrace(g_backtrace_array, BACKTRACE_SIZE); // 后续处理 }使用
backtrace_symbols_fd替代backtrace_symbols
如果需要解析栈帧为符号,不要用backtrace_symbols(它会调用malloc分配字符串数组),改用backtrace_symbols_fd,它直接将符号信息写入指定的文件描述符(比如STDERR_FILENO或预先打开的日志文件),完全避免malloc调用:void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { size_t size = backtrace(g_backtrace_array, BACKTRACE_SIZE); // 直接写入标准错误或日志文件 backtrace_symbols_fd(g_backtrace_array, size, STDERR_FILENO); }为信号处理函数分配独立栈
如果必须在信号处理中执行复杂操作,可以通过sigaltstack为信号处理函数设置独立的栈空间,避免原栈或堆损坏影响信号处理的执行:void backtrace_init() { struct sigaction sigact; stack_t sigstack; // 分配独立的信号栈 sigstack.ss_sp = malloc(SIGSTKSZ); sigstack.ss_size = SIGSTKSZ; sigstack.ss_flags = 0; sigaltstack(&sigstack, NULL); sigact.sa_sigaction = crit_err_hdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; // 指定使用独立栈 sigaction(SIGSEGV, &sigact, NULL); }注意:这里的
malloc要在程序初始化时调用,此时堆结构是完好的,不会有问题。从源头避免堆破坏
在调用memcpy等内存操作函数前,严格校验参数合法性,比如确保size不超过目标缓冲区的长度,避免传入负数或过大的无符号值,从根源减少堆损坏的场景。
内容的提问来源于stack exchange,提问作者Андрей Виноградов

