__asan::ReportGenericError报告位置及macOS守护进程崩溃排查
macOS Launch Daemon退出崩溃排查与ASAN报告位置说明
一、崩溃核心信息
崩溃发生在MyService退出阶段,崩溃栈指向P1Timer::_cancelEvents函数第696行(调用MyFormat格式化字符串的位置),关键崩溃栈片段如下:
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x18fb0c724 __pthread_kill + 8 ... 7 libclang_rt.asan_osx_dynamic.dylib 0x104926498 __asan::ReportGenericError(...) + 1436 8 libclang_rt.asan_osx_dynamic.dylib 0x1048fd558 printf_common(...) + 1400 9 libclang_rt.asan_osx_dynamic.dylib 0x1048fd980 wrap_vsnprintf + 176 10 libclang_rt.asan_osx_dynamic.dylib 0x1048fe1b8 wrap_snprintf + 84 11 MyService 0x104222ce4 P1Timer::_cancelEvents(...) + 780 (P1Timer.h:696) ... 18 MyService 0x104161908 MySvc::~MySvc() + 228 (MySvc.cpp:160)
触发崩溃的代码片段:
// P1Timer.h:696 std::string strDbgDesc; MyFormat(strDbgDesc, "budleID=\"%s\", EventType=\"%s\"" , pstrBundleID ? pstrBundleID : "<null>", pstrEventType ? pstrEventType : "<null>");
调用_cancelEvents的代码:
_cancelEvents(_strTmrBundleID.c_str(), NULL, szCnt);
MyFormat函数实现:
std::string& MyFormat(std::string& str, const char *pFmt, ...) { va_list args; va_start (args, pFmt); int nchLen = vsnprintf(nullptr, 0, pFmt, args); str.resize(nchLen); vsnprintf(&str[0], nchLen + 1, pFmt, args); va_end(args); return str; }
二、崩溃原因分析
va_list重复使用的致命问题MyFormat函数中,第一次调用vsnprintf(nullptr, 0, pFmt, args)计算字符串长度时,已经遍历完va_list中的所有参数,导致args指针移动到参数列表末尾。第二次直接复用同一个va_list调用vsnprintf时,会读取超出参数列表范围的内存,触发ASAN的内存非法访问检测,最终调用abort()导致崩溃。潜在的对象生命周期风险
崩溃发生在MySvc的析构函数中,此时_strTmrBundleID作为MySvc的成员变量,可能已处于部分析构状态(若_cancelEvents在成员变量析构后调用),导致c_str()返回的指针指向已释放的内存区域,加剧内存非法访问风险。
三、__asan::ReportGenericError报告的位置
在macOS系统中,ASAN的错误输出默认发送到进程的标准错误流(stderr)。对于Launch Daemon这类后台服务:
- 终端查看:执行
log show --predicate 'process == "MyService"' --info --debug --last 1h(可调整时间范围),过滤MyService相关日志,ASAN的详细错误信息会包含具体内存违规类型(如越界访问、使用已释放内存等)和位置。 - 图形化查看:打开「控制台.app」,搜索进程名
MyService,查找包含ASAN标记的日志条目,获取完整错误详情。
修复建议
- 修复
MyFormat的va_list使用问题
通过va_copy复制一份va_list用于第二次调用,避免指针失效:
std::string& MyFormat(std::string& str, const char *pFmt, ...) { va_list args; va_start(args, pFmt); va_list args_copy; va_copy(args_copy, args); int nchLen = vsnprintf(nullptr, 0, pFmt, args); str.resize(nchLen); vsnprintf(&str[0], nchLen + 1, pFmt, args_copy); va_end(args_copy); va_end(args); return str; }
- 确保析构时对象的有效性
检查MySvc析构函数的执行顺序,保证调用_cancelEvents时,_strTmrBundleID仍处于有效状态(未被析构)。
内容的提问来源于stack exchange,提问作者c00000fd
相关产品推荐
相关产品推荐

