strerror的坑

时间:2022-02-04 06:04:34

最近写的一段代码,总是出core,精简了一下,稳定复现。

#include <stdio.h>
#include <errno.h> int main()
{
printf("%s\n", strerror(errno));
return 0;
}

编译并执行,就会报Segmentation fault (core dumped)。

看下core的栈:

(gdb) bt
#0 0x0000003f0b06feb0 in strlen () from /lib64/tls/libc.so.6
#1 0x0000003f0b0429ac in vfprintf () from /lib64/tls/libc.so.6
#2 0x0000003f0b047f08 in printf () from /lib64/tls/libc.so.6
#3 0x000000000040058c in main ()

问题应该是出在strerror(errno)上了。

反编译一下代码,

0000000000400558 <main>:
400558: 55 push %rbp
400559: 48 89 e5 mov %rsp,%rbp
40055c: e8 27 ff ff ff callq 400488 <__errno_location@plt>
400561: 8b 38 mov (%rax),%edi
400563: b8 00 00 00 00 mov $0x0,%eax
400568: e8 0b ff ff ff callq 400478 <strerror@plt>
40056d: 89 c6 mov %eax,%esi 最关键的地方:strerror返回的地址存放在eax中,eax的值赋给esi
40056f: bf 7c 06 40 00 mov $0x40067c,%edi 这里0x40067c指向字符串"%s\n"
400574: b8 00 00 00 00 mov $0x0,%eax
400579: e8 ea fe ff ff callq 400468 <printf@plt>
40057e: b8 00 00 00 00 mov $0x0,%eax

差不多到这里问题就追踪出来了。由于是在64位系统上执行,因此eax只取了32位,赋给esi也只取了32位。而其实在64位系统上,strerror返回的是char *指针,应该是64位的。借用一张他人绘制的rax,eax间的关系图:

|63..32|31..16|15-8|7-0|
|AH.|AL.|
|AX.....|
|EAX............|
|RAX...................|

结论:

strerror函数声明在string.h头文件里,由于没有包含该头文件,编译器将strerror的返回值当做了int类型来处理,而int类型是32位的,因此当做指针传给printf,就跪了。后来我编译的时候打开warning,其实已经给出了问题所在:

cc -Wall test.c 

test.c: In function `main':
test.c:6: warning: implicit declaration of function `strerror'
test.c:6: warning: format argument is not a pointer (arg 2)

所以再一次的,请编译时开启warning提示。