测试程序
#include<stdio.h>
int bar(int c,int d)
{
int e = c+d;
return e;
}
int foo(int a,int b)
{
return bar(a,b);
}
int main()
{
int a = 1;
int b = 2;
foo(a,b);
return 0;
}
一些命令
readelf 显示ELF文件的信息
readelf -a file 显示所有的信息
hexdump -C file 使用16进制加ascii显示文件里的内容
objdump -d file 反汇编文件
objdump -dS file 反汇编文件,可以把源码和汇编代码串联起来看,编译时必须得有-g选项才可以
这里使用反汇编命令
objdump -dS memory.exe得到对应的汇编代码,下面只列出部分
int bar(int c,int d)
8048404: 55 push %ebp
8048405: 89 e5 mov %esp,%ebp
8048407: 83 ec 10 sub $0x10,%esp
{
int e = c+d;
804840a: 8b 45 0c mov 0xc(%ebp),%eax
804840d: 03 45 08 add 0x8(%ebp),%eax
8048410: 89 45 fc mov %eax,0xfffffffc(%ebp)
return e;
8048413: 8b 45 fc mov 0xfffffffc(%ebp),%eax
}
8048416: c9 leave
8048417: c3 ret
08048418 <_Z3fooii>:
int foo(int a,int b)
8048418: 55 push %ebp
8048419: 89 e5 mov %esp,%ebp
804841b: 83 ec 08 sub $0x8,%esp
{
return bar(a,b);
804841e: 8b 45 0c mov 0xc(%ebp),%eax
8048421: 89 44 24 04 mov %eax,0x4(%esp)
8048425: 8b 45 08 mov 0x8(%ebp),%eax
8048428: 89 04 24 mov %eax,(%esp)
804842b: e8 d4 ff ff ff call 8048404 <_Z3barii>
}
8048430: c9 leave
8048431: c3 ret
08048432 <main>:
int main()
8048432: 8d 4c 24 04 lea 0x4(%esp),%ecx
8048436: 83 e4 f0 and $0xfffffff0,%esp
8048439: ff 71 fc pushl 0xfffffffc(%ecx)
804843c: 55 push %ebp
804843d: 89 e5 mov %esp,%ebp
804843f: 51 push %ecx
8048440: 83 ec 18 sub $0x18,%esp
{
int a = 1;
8048443: c7 45 f4 01 00 00 00 movl $0x1,0xfffffff4(%ebp)
int b = 2;
804844a: c7 45 f8 02 00 00 00 movl $0x2,0xfffffff8(%ebp)
foo(a,b);
8048451: 8b 45 f8 mov 0xfffffff8(%ebp),%eax
8048454: 89 44 24 04 mov %eax,0x4(%esp)
8048458: 8b 45 f4 mov 0xfffffff4(%ebp),%eax
804845b: 89 04 24 mov %eax,(%esp)
804845e: e8 b5 ff ff ff call 8048418 <_Z3fooii>
return 0;
8048463: b8 00 00 00 00 mov $0x0,%eax
}
然后使用gdb调试memory.exe,下面是部分gdb的内容
Breakpoint 1, main () at memory.cpp:13
13 int a = 1;
(gdb) n
14 int b = 2;
(gdb) n
15 foo(a,b);
(gdb) p &a
$1 = (int *) 0xffd7e35c
(gdb) p &b
$2 = (int *) 0xffd7e360
(gdb) s
foo (a=1, b=2) at memory.cpp:9
9 return bar(a,b);
(gdb) s
bar (c=1, d=2) at memory.cpp:4
4 int e = c+d;
(gdb) p &c
$3 = (int *) 0xffd7e33c
(gdb) p &d
$4 = (int *) 0xffd7e340
(gdb) bt
#0 0x0804840d in bar (c=1, d=2) at memory.cpp:4
#1 0x08048430 in foo (a=1, b=2) at memory.cpp:9
#2 0x08048463 in main () at memory.cpp:15
(gdb) f 1
#1 0x08048430 in foo (a=1, b=2) at memory.cpp:9
9 return bar(a,b);
(gdb) p &a
$5 = (int *) 0xffd7e34c
(gdb) p &b
$6 = (int *) 0xffd7e350
(gdb) f 0
#0 0x0804840d in bar (c=1, d=2) at memory.cpp:4
4 int e = c+d;
(gdb) p &c
$7 = (int *) 0xffd7e33c
(gdb) x/100hb 0xffd7e32c
0xffd7e32c: 0x90 0x83 0x04 0x08 0x85 0x82 0x04 0x08
0xffd7e334: 0x44 0xe3 0xd7 0xff 0x30 0x84 0x04 0x08
0xffd7e33c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00
0xffd7e344: 0x68 0xe3 0xd7 0xff 0x63 0x84 0x04 0x08
0xffd7e34c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00
0xffd7e354: 0x03 0x00 0x00 0x00 0xa0 0x2c 0xe8 0xf7
0xffd7e35c: 0x01 0x00 0x00 0x00 0x02 0x00 0x00 0x00
0xffd7e364: 0x80 0xe3 0xd7 0xff 0xd8 0xe3 0xd7 0xff
0xffd7e36c: 0x7c 0x98 0xd7 0xf7 0xa0 0x9c 0xfd 0xf7
0xffd7e374: 0x90 0x84 0x04 0x08 0xd8 0xe3 0xd7 0xff
0xffd7e37c: 0x7c 0x98 0xd7 0xf7 0x01 0x00 0x00 0x00
0xffd7e384: 0x04 0xe4 0xd7 0xff 0x0c 0xe4 0xd7 0xff
0xffd7e38c: 0xbd 0xc2 0xfc 0xf7
(gdb) bt
#0 0x0804840d in bar (c=1, d=2) at memory.cpp:4
#1 0x08048430 in foo (a=1, b=2) at memory.cpp:9
#2 0x08048463 in main () at memory.cpp:15
(gdb) f 1
#1 0x08048430 in foo (a=1, b=2) at memory.cpp:9
9 return bar(a,b);
(gdb) f 0
#0 0x0804840d in bar (c=1, d=2) at memory.cpp:4
4 int e = c+d;
(gdb) f 2
#2 0x08048463 in main () at memory.cpp:15
15 foo(a,b);
(gdb) info registers
eax 0x2 2
ecx 0xffd7e380 -2628736
edx 0x1 1
ebx 0xf7e81ff4 -135782412
esp 0xffd7e34c 0xffd7e34c
ebp 0xffd7e368 0xffd7e368
esi 0xf7fd9ca0 -134374240
edi 0x0 0
eip 0x8048463 0x8048463 <main+49>
eflags 0x200286 [ PF SF IF ID ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
搞清楚ebp指向栈底,esp指向栈顶
压栈时,先修改esp即减4个字节,再把esp指向的内存赋值
出栈时,先把esp指向的内存返回,再修改esp即加4个字节
在main函数中时ebp为0xffd7e368,esp为0xffd7e34c
定义变量a时
movl $0x1,0xfffffff4(%ebp) //定义变量时用栈底的地址减去一定偏移量得到变量的地址,这个一定的偏移量是怎么算出来的?
定义变量b时
movl $0x2,0xfffffff8(%ebp)
所以a的地址为0xffd7e35c,值为0x01,b的地址为0xffd7e360,值为0x02
局部变量是在栈上分配内存,先定义的变量地址小,后定义的变量地址大
调用foo(a,b)函数时
要从右向左向栈中压入参数,这里的压栈操作没有修改esp,且确保所有的参数保存在栈上后,最左边的参数是保存在栈顶上
mov 0xfffffff8(%ebp),%eax 把b取出放到eax
mov %eax,0x4(%esp) 把eax中的值放到栈顶加4的内存上
mov 0xfffffff4(%ebp),%eax 把a取出放到eax
mov %eax,(%esp) 把eax中的值放到栈顶的内存上
调用call指令即调用foo()
call指令会执行下列操作
1.把call指令的下一条指令的地址保存起来即压入栈中,用于在call指令返回时继续执行
压栈会修改esp
此时esp由0xffd7e34c减小成0xffd7e348
2.修改程序计数器eip,即eip指向foo的地址0x8048418
调用foo(a,b)
int foo(int a,int b)
8048418: 55 push %ebp
8048419: 89 e5 mov %esp,%ebp
804841b: 83 ec 08 sub $0x8,%esp
{
return bar(a,b);
804841e: 8b 45 0c mov 0xc(%ebp),%eax
8048421: 89 44 24 04 mov %eax,0x4(%esp)
8048425: 8b 45 08 mov 0x8(%ebp),%eax
8048428: 89 04 24 mov %eax,(%esp)
804842b: e8 d4 ff ff ff call 8048404 <_Z3barii>
}
8048430: c9 leave
8048431: c3 ret
首先把ebp压栈,此时esp减4变为0xffd7e344,相应的内存值为0xffd7e368即保存之前的栈底到栈上
然后把当前的栈顶赋值给栈底,即ebp由0xffd7e368变为0xffd7e344,
再减小栈顶用于存放调用bar(a,b)函数时要压栈的两个参数,即esp变成0xffd7e33c
下面就是压栈两个参数了,再调用call指令调用bar函数,下面的流程和上面的分析基本一致。
注意在取参数时都是通过ebp加偏移来取参数的
int bar(int c,int d)
8048404: 55 push %ebp
8048405: 89 e5 mov %esp,%ebp
8048407: 83 ec 10 sub $0x10,%esp
{
int e = c+d;
804840a: 8b 45 0c mov 0xc(%ebp),%eax
804840d: 03 45 08 add 0x8(%ebp),%eax
8048410: 89 45 fc mov %eax,0xfffffffc(%ebp)
return e;
8048413: 8b 45 fc mov 0xfffffffc(%ebp),%eax
}
8048416: c9 leave
8048417: c3 ret
最后的结果存在eax中
执行leave指令
该指令是开关的push %ebp和mov %esp,%ebp的逆操作
1.把ebp的值赋给esp,恢复之前的栈顶
2.出栈赋值给ebp,恢复之前的栈底
执行ret指令
该指令是call指令的逆操作
1.现在esp所指向的栈顶保存着返回地址,把这个值恢复给eip,同时esp增加4
2.修改了程序计数器eip后,跳转到eip所指的地址继续执行。
最终就执行到main函数结束
注意函数调用和返回过程中的这些原则:
1.参数压栈传递,并且是从右向左依次压栈
2.ebp问题指向栈底
3.返回值是通过eax寄存器传递
操作系统和编译器选择了这样的方式实现c代码中的函数调用,这称为
calling convention,除此之外,操作系统还需要规定许多c代码和二进制指令
间的接口规范,统称为ABI(Application Binary Interface).