1. 最简单的代码:
//// test1.c
int main(){
return 1;
}
编译、反汇编:
gcc test1.c
gdb ./a.out
(gdb) disassemble main
0x08048344
0x08048348
0x0804834b
0x0804834e
0x0804834f
0x08048351
0x08048352
0x08048357
0x08048358
0x08048359
0x0804835c
常用指令解释:
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
0x08048361
0x08048364
0x08048367
0x08048368
0x0804836a
0x08048370
0x08048375
0x08048376
0x08048377
0x0804837a
End of assembler dump.
(gdb) disassemble a
Dump of assembler code for function a:
0x08048353 : push %ebp
0x08048354 : mov %esp,%ebp
0x08048356 : call 0x8048349
0x0804835b : pop %ebp
0x0804835c : ret
End of assembler dump.
(gdb) disassemble b
Dump of assembler code for function b:
0x08048349 : push %ebp
0x0804834a : mov %esp,%ebp
0x0804834c : call 0x8048344
0x08048351 : pop %ebp
0x08048352 : ret
End of assembler dump.
(gdb) disassemble c
Dump of assembler code for function c:
0x08048344
0x08048345
0x08048347
0x08048348
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
0x08048348
0x0804834b
0x0804834e
0x0804834f
0x08048351
0x08048352
0x08048355
0x0804835c
0x08048363
0x08048368
0x0804836b
0x0804836c
0x0804836d
0x08048370
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
0x08048353
0x08048356
0x08048359
0x0804835a
0x0804835c
0x0804835d
0x08048360
0x08048367
0x0804836e
0x08048375
0x08048378
0x0804837c
0x0804837f
0x08048382
0x08048387
0x0804838a
0x0804838d
0x08048390
0x08048391
0x08048392
0x08048395
End of assembler dump.
(gdb) disassemble func
Dump of assembler code for function func:
0x08048344
0x08048345
0x08048347
0x0804834a
0x0804834d
0x0804834e
End of assembler dump.
参数的访问,可以通过ebp加上减去偏移量:
mov 0xc(%ebp),%eax
add 0x8(%ebp),%eax