1 Star2 Stars3 Stars4 Stars5 Stars (还没有评分)
Loading...

为什么EBP值变了,EIP值没变?

最近看到一篇古老的帖子(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,有兴趣可以详细了解一下):

stackframe-cdecl
strcpy函数会把堆栈上保存的EBPEIP的值改成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)。

Leave a Reply

Your email address will not be published. Required fields are marked *