64位机器一次变长参数打印内存访问错误的定位
在维护路由代码时,修改了一份调试函数在shell中打印,发现打印后引发进程访问错误的内存。所有宏用数字代替,为错误的代码如下所示:
snprintf(buf, 512, "[%#x]%c%c%s [%d/%lu] ", rib->flags,
CHECK_FLAG (rib->flags, FLAG_SELECTED)
? '>' : ' ',
CHECK_FLAG (rib->flags, FLAG_STALE)
? 'p' : ' ',
route_type_str (rib->type), rib->distance, rib->metric)
其中route_type_str在另一个文件中定义:
char *route_type_str (u_char type)
{
switch (type)
{
case 1:
return "kernel";
case 2:
return "connected";
case 3:
return "static";
case 4:
case 5:
return "rip";
case 6:
case 7:
return "ospf";
case 8:
return "bgp";
case 9:
return "isis";
default:
return "unknown";
}
}
当打印到route_type_str指向的字符串后,发现内存访问错误。当定义一个局部变量string先获取route_type_str后,再打印就不会有错误。
在有错误的情况下,使用GDB跟踪代码,发现在打印是获取到的字符串指针是错误的。
比如访问的是”static”,其指针为 0x00599177,但是在打印时,通过va_list获取的地址为:0x00007fef00599177。
一开始怀疑va_list设置时错了,在网上搜索64位的va_list处理,获取到如下信息:
GCC的调用约定跟VC不同。前6个整数参数会依次放到rdi, rsi, rdx, rcx, r8, r9中,前8个浮点参数放到xmm0到xmm7中。除了使用了更多的寄存器,与vc不同的是,整数和浮点数寄存器是混合使用的不用为没用的参数预留。还是刚才的例子,第一个参数是int,第二个是double,第三个char*,第四个double,参数数会依次放到 rdi,xmm0,rsi,xmm1. 另外,没有在栈上预留寄存器区。<
使用GDB info register获取到寄存器信息,入参是正确的。在通过disassemble看到该打印函数调用处的上下文,也没有问题。再次跟踪到上次调用函数,反汇编看到如下代码:
0x00000000004690e6 <+71>: callq 0x460a98 <route_type_str>
0x00000000004690eb <+76>: mov %eax,%ecx
由于是64位机器,应该使用rxx的寄存器才对,这里使用了exx寄存器,导致后续在mov返回值的时候,只存了低4字节的指针。这样在后续处理时,导致指针地址获取错误。
但是为什么会出现这个问题呢?objdump整个进程,发现所有的这个函数的反汇编调用处都是exx寄存器。后来发现该函数没有声明,所有的调用处都是在其他文件中默认链接的。这些地方都默认返回int,在64位机器上,当使用这个返回指针时就出错了。
为了验证这个,写了2个小文件测试了下:
1.c:
char *rib_str(char type)
{
switch (type)
{
case 1:
return "kernel";
case 2:
return "static";
default:
return "unkown";
}
return "Null";
}
2.c
#include <stdio.h>
#include <string.h>
int main()
{
printf("%s\n",rib_str(1));
return 0;
}
gcc -o rib 1.c 2.c
objdump -d rib > 1.txt
143 0000000000400560 <main>:
144 400560: 55 push %rbp
145 400561: 48 89 e5 mov %rsp,%rbp
146 400564: bf 01 00 00 00 mov $0x1,%edi
147 400569: b8 00 00 00 00 mov $0x0,%eax
148 40056e: e8 bd ff ff ff callq 400530 <rib_str>
149 400573: 89 c6 mov %eax,%esi
150 400575: bf 35 06 40 00 mov $0x400635,%edi
151 40057a: b8 00 00 00 00 mov $0x0,%eax
152 40057f: e8 8c fe ff ff callq 400410 <printf@plt>
153 400584: b8 00 00 00 00 mov $0x0,%eax
因此一定要注意编译告警,注意函数声明。。。