C语言的反汇编代码

来源:互联网 发布:山东广电网络倒闭 编辑:程序博客网 时间:2024/04/27 19:59

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 的值 减去4 esp 的值, 将得到的值作为地址 在内存找到该地址对应的值 将其压入栈中。

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)       | --+ <------ 当前函数A 的EBPA ( 即SFP 框架指针
)
    +-------------------------+  
+-->offsetA
     | A 中的局部变量            | --+ <------ESP 指向函数A新分配的局部变量, 局部变量可以通过EBPA-offsetA 访问
     +-------------------------+
    | Arg .( 函数B 的参数)      |   --+ <------ B 函数的参数可以由B 的EBPB+offsetB 访问

 
    +-------------------------+   +--> offsetB

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

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

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

函数被调用时
    1) EIP/EBP
成为新函数栈的边界
    函数被调用时,返回时的 EIP 首先被压入堆栈;创建栈框架时,上级函数栈的 EBP 被压入堆栈,与 EIP 一道行成新函数栈框架的边界

    2) EBP
成为栈框架指针 SFP ,用来指示新函数栈的边界
   
栈框架建立后, EBP 指向的栈的内容就是上一级函数栈的 EBP ,可以想象,通过 EBP 就可以把层层调用函数的栈都回朔遍历一遍,调试器就是利用这个特性实现 backtrace 功能的

    3) ESP
总是作为栈指针指向栈顶,用来分配栈空间
    栈分配空间给函数局部变量时的语句通常就是给 ESP 减去一个常数值,例如,分配一个整型数据就是
ESP-4
    4)
函数的参数传递和局部变量访问可以通过 SFP 即 EBP 来实现

    由于栈框架指针永远指向当前函数的栈基地址,参数和局部变量访问通常为如下形式:
        +8+xx(%ebp)         ; 函数入口参数的的访问
        -xx(%ebp)           ; 函数局部变量访问

 

3 含局部变量时:

int main(){

  int a = 3;

  int b = 5;

return 1;

}

 

(gdb) disassemble main

Dump of assembler code 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. 函数调用时有参数

int func(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) disassemble main

Dump of assembler code 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 assembler dump.

(gdb) disassemble func

Dump of assembler code 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 assembler dump.

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

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