打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
ASM调试

1. 最简单的代码:

//// test1.c

int main(){

    return 1;

}

 

编译、反汇编:

gcc test1.c

gdb ./a.out

(gdb)disassemble main

 

0x08048344<main+0>:  lea    0x4(%esp),%ecx   ;取出esp寄存器里的值加上4将得到值传递给ecx

0x08048348<main+4>:  and    $0xfffffff0,%esp  ;使栈地址16字节对齐

0x0804834b<main+7>:  pushl  -0x4(%ecx) ;取出寄存器ecx的值减去4esp的值,将得到的值作为地址在内存找到该地址对应的值将其压入栈中。

0x0804834e<main+10>:            push   %ebp

0x0804834f <main+11>: mov   %esp,%ebp  ;创建Stack Frame(栈框架)

0x08048351<main+13>:            push   %ecx

0x08048352<main+14>:            mov    $0x1,%eax

0x08048357<main+19>:            pop    %ecx

0x08048358<main+20>:            pop    %ebp

0x08048359<main+21>:            lea    -0x4(%ecx),%esp  ;取出ecx 寄存器里的值减去4将得到值传递给esp还原esp的值

0x0804835c <main+24>:            ret 

 

常用指令解释:

CALL指令:

用来调用一个函数或过程,此时,下一条指令地址会被压入堆栈,以备返回时能恢复执行下条指令。
RET
指令:

用来从一个函数或过程返回,之前CALL保存的下条指令地址会从栈内弹出到EIP寄存器中,程序转到CALL之前下条指令处执行

ENTER指令:

建立当前函数的栈框架,即相当于以下两条指令:
        pushl   %ebp
        movl    %esp,%ebp

LEAVE
指令:

释放当前函数或者过程的栈框架,即相当于以下两条指令:
        movl ebp esp
        popl ebp

 

2. 函数间的调用代码:

假如函数A调用函数B,函数B调用函数C

///// test2.c

void c(){}

 

void b(){c();}

 

void a(){b();}

 

int main(){

   a();

   return 1;

}

编译、反汇编:

gcc test1.c

gdb ./a.out

(gdb) disassemble main

Dump of assembler code for function main:

0x0804835d <main+0>:  lea    0x4(%esp),%ecx

0x08048361 <main+4>:  and    $0xfffffff0,%esp

0x08048364 <main+7>:  pushl  -0x4(%ecx)

0x08048367 <main+10>:            push   %ebp

0x08048368 <main+11>:            mov    %esp,%ebp

0x0804836a<main+13>:            push   %ecx

0x0804836b <main+14>:            call   0x8048353 <a>

0x08048370 <main+19>:            mov    $0x1,%eax

0x08048375 <main+24>:            pop    %ecx

0x08048376 <main+25>:            pop    %ebp

0x08048377 <main+26>:            lea    -0x4(%ecx),%esp

0x0804837a<main+29>:            ret   

End of assembler dump.

(gdb) disassemble a

Dump of assembler code for function a:

0x08048353 <a+0>:         push   %ebp

0x08048354 <a+1>:         mov    %esp,%ebp

0x08048356 <a+3>:         call   0x8048349 <b>

0x0804835b <a+8>:         pop    %ebp

0x0804835c<a+9>:          ret   

End of assembler dump.

(gdb) disassemble b

Dump of assembler code for function b:

0x08048349 <b+0>:         push   %ebp

0x0804834a<b+1>:         mov    %esp,%ebp

0x0804834c<b+3>:          call   0x8048344 <c>

0x08048351 <b+8>:         pop    %ebp

0x08048352 <b+9>:         ret   

End of assembler dump.

(gdb) disassemble c

Dump of assembler code for function c:

0x08048344 <c+0>:          push   %ebp

0x08048345 <c+1>:          mov    %esp,%ebp

0x08048347 <c+3>:          pop    %ebp

0x08048348 <c+4>:          ret   

End of assembler dump.

 

函数调用栈的状态:

    +-------------------------+----> 高地址
    | EIP (Main
函数返回地址)   |
    +-------------------------+
    | EBP (Main
函数的EBP)      |--+ <------当前函数AEBPA (SFP框架指针)
    +-------------------------+   +-->offsetA
    | A
中的局部变量           | --+ <------ESP指向函数A新分配的局部变量,局部变量可以通过EBPA-offsetA访问
    +-------------------------+
    | Arg .(
函数B的参数)     |   --+<------ B函数的参数可以由BEBPB+offsetB访问
   +-------------------------+   +--> offsetB

   | EIP (A函数的返回地址)    |   |
    +-------------------------+ --+
    | EBP (A
函数的EBP)        |<--+ <------ 当前函数BEBPB (SFP框架指针)
    +-------------------------+

    | B
中的局部变量         |  
    +-------------------------+  
    | Arg .(
函数C的参数)     |  
    +-------------------------+  
    | EIP (B
函数的返回地址)   |  
    +-------------------------+ 
    |EBP (B
函数的EBP)      | --+ <------ 当前函数CEBPC (SFP框架指针)
    +-------------------------+
    | C 中的局部变量          |
    | ..........              | <------ ESP
指向函数C新分配的局部变量

 +-------------------------+----> 低地址

函数被调用时
    1) EIP/EBP
成为新函数栈的边界
   
函数被调用时,返回时的EIP首先被压入堆栈;创建栈框架时,上级函数栈的EBP被压入堆栈,与EIP一道行成新函数栈框架的边界
    2) EBP
成为栈框架指针SFP,用来指示新函数栈的边界
   
栈框架建立后,EBP指向的栈的内容就是上一级函数栈的EBP,可以想象,通过EBP就可以把层层调用函数的栈都回朔遍历一遍,调试器就是利用这个特性实现 backtrace功能的
    3) ESP
总是作为栈指针指向栈顶,用来分配栈空间
   
栈分配空间给函数局部变量时的语句通常就是给ESP减去一个常数值,例如,分配一个整型数据就是 ESP-4
    4)
函数的参数传递和局部变量访问可以通过SFPEBP来实现
    由于栈框架指针永远指向当前函数的栈基地址,参数和局部变量访问通常为如下形式:
        +8+xx(%ebp)        ;
函数入口参数的的访问
       -xx(%ebp)          ; 函数局部变量访问

 

3含局部变量时:

int main(){

  int a = 3;

  int b = 5;

return 1;

}

 

(gdb) disassemblemain

Dump of assemblercode for function main:

0x08048344<main+0>:  lea    0x4(%esp),%ecx

0x08048348<main+4>:  and    $0xfffffff0,%esp

0x0804834b<main+7>:  pushl  -0x4(%ecx)

0x0804834e<main+10>:            push   %ebp

0x0804834f <main+11>: mov   %esp,%ebp

0x08048351<main+13>:            push   %ecx

0x08048352<main+14>:            sub    $0x10,%esp

0x08048355<main+17>:            movl   $0x3,-0x8(%ebp)  ; a = 3

0x0804835c <main+24>:            movl   $0x5,-0xc(%ebp) ; b = 5;

0x08048363<main+31>:            mov    $0x1,%eax ;return 1;

0x08048368<main+36>:            add    $0x10,%esp

0x0804836b<main+39>:            pop    %ecx

0x0804836c <main+40>:            pop    %ebp

0x0804836d<main+41>:            lea    -0x4(%ecx),%esp

0x08048370<main+44>:            ret   

End of assembler dump.

通过反汇编代码对程序运行时的寄存器和栈的观察和分析,可以得出局部变量在栈中的访问和分配及释放方式:
        1.
局部变量的分配,可以通过esp减去所需字节数
           sub    $0x10,%esp

       2.局部变量的释放,可以通过esp加上已分配的字节
           add    $0x10,%esp     
        3.
局部变量的访问,可以通过ebp减去偏移量
           movl   $0x3,-0x8(%ebp)

           

4. 函数调用时有参数

intfunc(int m, int n)

{

   return m+n;

}

 

int main(){

  int a = 3;

  int b = 5;

  int c = 0;

  c = func(a, b);

  return c;

}

 

(gdb) disassemblemain

Dump of assemblercode for function main:

0x0804834f <main+0>:   lea   0x4(%esp),%ecx

0x08048353<main+4>:  and    $0xfffffff0,%esp

0x08048356<main+7>:  pushl  -0x4(%ecx)

0x08048359<main+10>:            push   %ebp

0x0804835a <main+11>:            mov    %esp,%ebp

0x0804835c <main+13>:            push   %ecx

0x0804835d<main+14>:            sub    $0x18,%esp

0x08048360<main+17>:            movl   $0x3,-0x8(%ebp)  ;a = 3

0x08048367<main+24>:            movl   $0x5,-0xc(%ebp)  ;b = 5

0x0804836e<main+31>:            movl   $0x0,-0x10(%ebp)  ;c = 0;

0x08048375<main+38>:            mov    -0xc(%ebp),%eax  

0x08048378<main+41>:            mov    %eax,0x4(%esp)  ; n = b

0x0804837c <main+45>:            mov    -0x8(%ebp),%eax

0x0804837f <main+48>: mov   %eax,(%esp)    ; m = a;

0x08048382<main+51>:            call   0x8048344 <func>  ;func(a,b);

0x08048387<main+56>:            mov    %eax,-0x10(%ebp) ; c = func(a, b);

0x0804838a <main+59>:            mov    -0x10(%ebp),%eax ; return c

0x0804838d<main+62>:            add    $0x18,%esp

0x08048390<main+65>:            pop    %ecx

0x08048391<main+66>:            pop    %ebp

0x08048392<main+67>:            lea    -0x4(%ecx),%esp

0x08048395<main+70>:            ret   

End of assemblerdump.

(gdb) disassemblefunc

Dump of assemblercode for function func:

0x08048344<func+0>:   push   %ebp

0x08048345<func+1>:   mov    %esp,%ebp

0x08048347 <func+3>:   mov    0xc(%ebp),%eax ; n

0x0804834a<func+6>:   add    0x8(%ebp),%eax ; m+n

0x0804834d<func+9>:   pop    %ebp

0x0804834e<func+10>: ret   

End of assemblerdump.

参数的访问,可以通过ebp加上减去偏移量:

mov    0xc(%ebp),%eax
add     0x8(%ebp),%eax

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
C函数返回结构体在汇编下的实现
通过内存布局完成局部变量访问解析
C|从汇编的角度理解函数调用与参数传递
C++中的成员函数调用原理及this指针的传递方式
深入了解GOT,PLT和动态链接
深入理解C语言的函数调用过程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服