aarch64平台vsnprintf返回-1原因及core dump下GDB排查方法
问题背景
应用运行于aarch64(arm64)架构时出现段错误,观测到vsnprintf持续调用失败、返回值为-1,目前已确认段错误由第三方库的异常逻辑引发,待确认三个问题:
vsnprintf调用失败的全部可能原因- 无存活运行进程、仅持有core dump文件时,是否可通过GDB读取
errno定位vsnprintf失败原因 - 代码中传入的异常长度参数是否为
vsnprintf调用失败的诱因
问题涉及的代码实现如下:
int mg_printf(const char *fmt, ...) { int len; char mem[100]; va_list ap; va_start(ap, fmt); len = vsnprintf(mem, sizeof(mem), fmt, ap); // 此处持续调用失败 va_end(ap); return len; } // len值异常 int len = 1602988490; const char* pr = "[aaaa:bbbb:0000:0000:0000:0000:0000:0010]:12345"; mg_printf("Host: %.*s\r\n", len, pr);
问题解答
异常len参数是否是vsnprintf失败的直接诱因
是,这个异常值就是你当前场景下vsnprintf返回-1的核心原因。%.*s格式符的语义是:从传入的字符指针起始位置开始,最多读取「精度值」个字节的字符,遇到\0提前终止。你传入的精度值为1602988490,远大于pr指向的字符串实际长度(算上IPv6地址和端口一共也就40余字节)。vsnprintf处理该格式符时,会尝试从pr指针地址开始向后读取最高1.6GB左右的内存,这个范围远超过字符串本身所在的合法内存段,几乎必然触碰到未映射的内存页,触发内存访问错误,直接导致函数返回-1。
这里要注意:正常情况下vsnprintf遇到待输出内容长度超过传入的缓冲区大小时不会报错,只会返回内容的完整预期长度,不会返回-1。只有触发非法访问、格式错误、溢出类问题时才会返回-1。
vsnprintf调用失败的全部可能原因
- 传入的格式串指针
fmt为非法值,指向不可读内存 - 可变参数列表和格式串要求的参数类型、数量不匹配,比如
%.*s要求传入int类型精度、char*类型字符串指针,若传入的精度值过大、字符串指针为非法值,会导致读取非法内存 - 读取待格式化的源数据时触达不可读内存页,触发内存访问错误,此时
errno会被设置为EFAULT - 格式串中包含非法的多字节字符序列,编码校验失败,此时
errno会被设置为EILSEQ - 传入的输出缓冲区指针为非法值,指向不可写内存(你当前代码里
mem是栈上分配的100字节数组,属于合法地址,可排除该原因) - 部分libc实现中,若待格式化生成的字符串总长度超过
INT_MAX,会返回-1,此时errno会被设置为EOVERFLOW
core dump场景下能否通过GDB查看errno定位失败原因
只要core文件生成时相关内存没有被破坏,就可以查看,注意几个细节:
- aarch64架构下
errno是线程局部存储变量,GDB加载core文件后,切换到触发vsnprintf失败的对应线程,直接执行p errno就能读取到线程当时的errno值,再执行p strerror(errno)就能拿到对应的错误描述。 - 两种场景下读到的errno值没有参考价值:
vsnprintf返回-1之后,当前线程又执行了其他会修改errno的系统调用或库函数,core中存储的errno值会被后续逻辑覆盖,无法反映vsnprintf失败时的真实错误- 内存越界写、栈溢出等问题破坏了线程局部存储中errno所在的内存区域,读出来的值是随机垃圾值
- 针对你当前的场景,不需要依赖errno也能直接定位问题:在GDB中切到
mg_printf的栈帧,直接读取可变参数列表中对应的len值、pr指针值,再用x/s pr查看pr指向的实际字符串长度,就能直接验证是否是超大精度值触发的越界读问题。
内容的提问来源于stack exchange,提问作者w0wy
相关产品推荐
相关产品推荐

