最近看到一篇古老的帖子(http://thr3ads.net/dtrace-discuss/2008/03/379528-stack-tracing-and-uregs),说是执行完一个把堆栈写坏的函数,为什么DTrace输出EBP
的值改变了,但是EIP
的值没变?我觉得很有意思,就研究了一下。下面就是整个分析过程:
首先看一下C程序:
#include <stdio.h> int handle_reply(char *str){ char response[256]; strcpy(response,str); printf("The client says \"%s\"\n",response); return 0; } int main(void) { char str[300] = {0}; memset(str, 'A', sizeof(str) - 1); handle_reply(str); return 0; }
可以看到在handle_reply
这个函数里,response
数组只有256
个字节大小,可是我们拷贝的str
字符串有300
个字节(包含结尾NULL
)。从C语言的函数调用堆栈图可以看到,(X86体系结构,图片引用自这篇文章:http://www.unixwiz.net/techtips/win32-callconv-asm.html,有兴趣可以详细了解一下):
strcpy
函数会把堆栈上保存的EBP
和EIP
的值改成0x41414141
。
接下来是DTrace脚本(check_reg.d):
#!/usr/sbin/dtrace -s pid$target::handle_reply:entry { printf("\nEBP=0x%x EIP=0x%x\n", uregs[R_EBP], uregs[R_EIP]); } pid$target::handle_reply:return { printf("\nEBP=0x%x EIP=0x%x\n", uregs[R_EBP], uregs[R_EIP]); }
执行脚本:
bash-3.2# ./check_reg.d -c ./a dtrace: script './check_reg.d' matched 2 probes The client says "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" dtrace: pid 16509 has exited CPU ID FUNCTION:NAME 0 58549 handle_reply:entry EBP=0x804776c EIP=0x8050bb4 0 58550 handle_reply:return EBP=0x41414141 EIP=0x8050bef
可以看到EBP
的值的确在“pid$target::handle_reply:return
”这个probe触发时,输出为0x41414141
,但是EIP
的值不是。
下面看一下handle_reply
的汇编代码:
(gdb) disassemble handle_reply Dump of assembler code for function handle_reply: 0x08050bb4 <handle_reply+0>: push %ebp 0x08050bb5 <handle_reply+1>: mov %esp,%ebp 0x08050bb7 <handle_reply+3>: sub $0x108,%esp 0x08050bbd <handle_reply+9>: sub $0x8,%esp 0x08050bc0 <handle_reply+12>: pushl 0x8(%ebp) 0x08050bc3 <handle_reply+15>: lea -0x100(%ebp),%eax 0x08050bc9 <handle_reply+21>: push %eax 0x08050bca <handle_reply+22>: call 0x8050994 <strcpy@plt> 0x08050bcf <handle_reply+27>: add $0x10,%esp 0x08050bd2 <handle_reply+30>: sub $0x8,%esp 0x08050bd5 <handle_reply+33>: lea -0x100(%ebp),%eax 0x08050bdb <handle_reply+39>: push %eax 0x08050bdc <handle_reply+40>: push $0x8050d00 0x08050be1 <handle_reply+45>: call 0x80509a4 <printf@plt> 0x08050be6 <handle_reply+50>: add $0x10,%esp 0x08050be9 <handle_reply+53>: mov $0x0,%eax 0x08050bee <handle_reply+58>: leave 0x08050bef <handle_reply+59>: ret End of assembler dump.
可以看到“pid$target::handle_reply:entry
”这个probe触发时,EIP
值是0x8050bb4
,也就是handle_reply
的第一条汇编代码。
而“pid$target::handle_reply:return
”这个probe触发时,EIP
值是0x8050bef
,对应“ret
”这句汇编指令,也就是“leave
”这句指令已经执行完了,将要执行“ret
”指令。而EBP
值的恢复是通过“leave
”指令,EIP
值的恢复是通过“ret
”指令。所以这就可以解释为什么EBP
的值改变了,但是EIP
的值没变。(关于X86汇编知识,可参考http://stackoverflow.com/questions/5474355/about-leave-in-x86-assembly)。