当前位置: 首页 > news >正文

记一次va_list导致的段错误崩溃排查

记一次va_list导致的段错误崩溃排查

问题

为了适配GTest框架到鸿蒙,需要让GTest的日志输出使用 OH_LOG_Print 函数,因此写出了类似下面的代码:

#include <cstdarg>
#include <cstdio>const char *RenderMsg(const char *fmt, ...)
{thread_local char renderBuf[2048];va_list args;va_start(args, fmt);int rendered = vsnprintf(renderBuf, sizeof(renderBuf) - 1, fmt, args);va_end(args);renderBuf[rendered] = '\0'; // abandon all bytes exceed.return renderBuf;
}#define GTEST_PRINTF(fmt, ...) printf("%s\n", RenderMsg(fmt, ##__VA_ARGS__))
#define GTEST_VPRINTF GTEST_PRINTFstatic void GPrintf(const char *fmt, ...)
{va_list args;va_start(args, fmt);GTEST_PRINTF(fmt, args); // 展开后是:printf("%s\n", RenderMsg(fmt, args)),非预期。va_end(args);
}int main()
{int num_disabled = 2;GTEST_PRINTF("  YOU HAVE %d DISABLED %s\n\n",num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); // OK, 展开后是:printf("%s\n", RenderMsg("  YOU HAVE %d DISABLED %s\n\n", num_disabled, num_disabled == 1 ? "TEST" : "TESTS"))GPrintf("  YOU HAVE %d DISABLED %s\n\n",num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); // 错误
}

上述代码在 Linux 上能运行,结果是,显然第二个输出是异常的:

  YOU HAVE 2 DISABLED TESTSYOU HAVE 489683816 DISABLED TESTS

在鸿蒙上会段错误,显示崩溃在 vsnprintf 中:

Reason:Signal:SIGSEGV(SEGV_MAPERR)@000000000000000000  probably caused by NULL pointer dereference
Fault thread info:
Tid:47314, Name:om.example.arks
#00 pc 00000000000a5290 /system/lib/ld-musl-aarch64.so.1(strnlen+16)(0afe599f71bfbe812637821352708631)
#01 pc 00000000001ac87c /system/lib/ld-musl-aarch64.so.1(printf_core+2180)(0afe599f71bfbe812637821352708631)
#02 pc 00000000001abe70 /system/lib/ld-musl-aarch64.so.1(vfprintf+188)(0afe599f71bfbe812637821352708631)
#03 pc 00000000001b6474 /system/lib/ld-musl-aarch64.so.1(vsnprintf+164)(0afe599f71bfbe812637821352708631)
#04 pc 00000000002e8964 /data/storage/el1/bundle/libs/arm64/libmytest.so(testing::internal::RenderMsg(char const*, ...)+204)(f01dc55fe98c38079fa11f285d170d860a89d2e5)

分析

由于崩溃在 vsnprintf ,标准库不大可能有BUG,而 RenderMsg 本身是经过单测验证的函数,也没有问题,那么问题大概出现在 va_list 上。

注意到宏展开过程:

GTEST_PRINTF(fmt, args); // 展开后是:printf("%s\n", RenderMsg(fmt, args)),非预期。

这里 args 已经是一个 va_list 了,那么传递给 RenderMsg 的就是单个实参 args ,而非预期的 num_disabled, num_disabled == 1 ? "TEST" : "TESTS" 两个实参。而 fmt 的值是 " YOU HAVE %d DISABLED %s\n\n" ,所以导致第一个格式化控制符 %d 读取的是 args 中的前4字节(自然是看不懂的垃圾值),而第二个格式化控制符 %s 读取的是 args 中接下来的8字节(自然是无效地址,所以段错误)。

此问题解决方案也很简单,原因就是对于使用了 va_listGTEST_VPRINTF ,不能简单地直接用 GTEST_PRINTF ,而是要准备一个专门的接收 va_list 的版本:

#include <cstdarg>
#include <cstdio>const char *RenderMsg(const char *fmt, ...)
{thread_local char renderBuf[2048];va_list args;va_start(args, fmt);int rendered = vsnprintf(renderBuf, sizeof(renderBuf) - 1, fmt, args);va_end(args);renderBuf[rendered] = '\0'; // abandon all bytes exceed.return renderBuf;
}const char *RenderMsg(const char *fmt, va_list args) // 专用版本
{thread_local char renderBuf[2048];int rendered = vsnprintf(renderBuf, sizeof(renderBuf) - 1, fmt, args);renderBuf[rendered] = '\0'; // abandon all bytes exceed.return renderBuf;
}#define GTEST_PRINTF(fmt, ...) printf("%s\n", RenderMsg(fmt, ##__VA_ARGS__))
#define GTEST_VPRINTF(fmt, args) printf("%s\n", RenderMsg(fmt, args))static void GPrintf(const char *fmt, ...)
{va_list args;va_start(args, fmt);// GTEST_PRINTF(fmt, args); // 展开后是:printf("%s\n", RenderMsg(fmt, args)),非预期。GTEST_VPRINTF(fmt, args); // 展开后是:printf("%s\n", RenderMsg(fmt, args)),符合预期。va_end(args);
}int main()
{int num_disabled = 2;GTEST_PRINTF("  YOU HAVE %d DISABLED %s\n\n",num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); // OK, 展开后是:printf("%s\n", RenderMsg("  YOU HAVE %d DISABLED %s\n\n", num_disabled, num_disabled == 1 ? "TEST" : "TESTS"))GPrintf("  YOU HAVE %d DISABLED %s\n\n",num_disabled, num_disabled == 1 ? "TEST" : "TESTS"); // 错误
}
http://www.jsqmd.com/news/273711/

相关文章:

  • 新疆新东方烹饪学校学费多少,详细地址为你揭晓 - 工业品牌热点
  • 2026最新特殊月子餐/贵阳月子中心推荐!贵阳南明区/贵阳市中心/花果园专业母婴护理机构权威指南 - 品牌推荐2026
  • 裁纸机厂家哪个好?一文为你揭秘! - 星辉数控
  • 【软考每日一练006】文件索引节点(i-node)解构:从物理底层到多级寻址计算
  • 测试工程师都在用的Linux命令清单(建议收藏)
  • 【开题答辩全过程】以 茉莉园小区物业快速维修服务系统为例,包含答辩的问题和答案
  • (8-2)UENUM(..)
  • RPA直播间自动抢福袋神器 - 高级品牌推荐官
  • 【软考每日一练007】位图计算与内存管理深度全解
  • 直播间自动抢福袋软件 - 高级品牌推荐官
  • (8-1)UENUM(..)
  • 浏览器RPA - 高级品牌推荐官
  • UE5 C++(37-3):
  • 推荐的工业AI大模型在制造业中的应用案例
  • 杭州市英语雅思培训辅导机构推荐,权威出国雅思课程中心学校口碑排行榜2026 - 老周说教育
  • 【改进差分优化算法L-SHADE-SPACMA】差分进化算法(DE)及其变体L-SHADE-SPACMA在CEC2005函数寻优的对比研究附Matlab代码
  • Prometheus-4·监控mariadb数据库Grafana展示数据
  • 【概率最小均方(PLMS)自适应滤波器】PLMS对高斯和非高斯噪声具有较强的鲁棒性附Matlab代码
  • Deepoc具身模型:农业除草机器人的智能核心
  • 【复现】遗传算法求解分布式电源选址定容问题并考虑环境因素研究【IEEE33节点】附Matlab代码
  • 代理ip哪家强 - 高级品牌推荐官
  • 2026年海外代理哪家强 - 高级品牌推荐官
  • 【改进差分优化算法JaDE】差分进化算法(DE)及其变体自适应权重差分进化算法(JaDE)在CEC2005函数寻优的对比研究附Matlab代码
  • 2026年指纹浏览器哪家强 - 高级品牌推荐官
  • 2026年代理ip哪家稳定 - 高级品牌推荐官
  • 【改进差分优化算法L-SHADE】差分进化算法(DE)及其变体线性种群缩减的SHADE(L-SHADE)在CEC2005函数寻优的对比研究附Matlab代码
  • 使用python的pymodbus实现modbus slave 模拟从站一
  • 芯片制造中如何高效上传设计文档?
  • 了解秀优国际会展的技术优势,性价比高的会展企业排名 - 工业品牌热点
  • 2026年海外代理哪家稳定 - 高级品牌推荐官