测试环境:Red Hat Linux 7.2
注解 :
eip 寄存器内容式当前执行指令的下一条指令的地址;
mov eax, ebx 将寄存器eax内容移到ebx; 机器指令2字节。
leave 指令所做的操作相当于mov ebp, esp 然后 pop ebp; 机器指令1字节。
ret 指令所做的操作相当于pop eip; 机器指令1字节。
call addr 指令所做的操作相当于push eip 然后 jmp addr; 机器指令4字节。
jmp addr 执行所做的操作相当于 mov addr, eip; 机器指令2字节。
注意:我们所说的”相当于”是在功能上的等价,并不是实际机器指令的等价。
1. 编写测试程序,如下:
//file name : cc.c
#include
int foo(int fi,int fj)
{
int fk;
fk=3;
return(0);
}
int main()
{
int mi;
int mj;
mi=1;
mj=2;
foo(mi,mj);
return(0);
}
2. 对代码进行编译:
gcc –g –o cc cc.c
3. 用gdb进行debug:gdb cc
(1) 查看源程序:
(gdb) list
4 {
5 int fk;
6 fk=3;
7 return(0);
8 }
9 int main()
10 {
11 int mi;
12 int mj;
13 mi=1;
(gdb)
14 mj=2;
15 foo(mi,mj);
16
17 return(0);
18 }
(2)查看汇编代码:
(gdb) disass main
Dump of assembler code for function main:
0x8048444 : push %ebp
0x8048445 : mov %esp,%ebp
0x8048447 : sub $0x8,%esp
0x804844a : movl $0x1,0xfffffffc(%ebp)
0x8048451 : movl $0x2,0xfffffff8(%ebp)
0x8048458 : sub $0x8,%esp
0x804845b : pushl 0xfffffff8(%ebp)
0x804845e : pushl 0xfffffffc(%ebp)
0x8048461 : call 0x8048430
0x8048466 : add $0x10,%esp
0x8048469 : mov $0x0,%eax
0x804846e : leave
0x804846f : ret
End of assembler dump.
(gdb) disass foo
Dump of assembler code for function foo:
0x8048430 : push %ebp
0x8048431 : mov %esp,%ebp
0x8048433 : sub $0x4,%esp
0x8048436 : movl $0x3,0xfffffffc(%ebp)
0x804843d : mov $0x0,%eax
0x8048442 : leave
0x8048443 : ret
End of assembler dump.
(3)在主函数设置断点,并执行程序,让程序在main函数刚开始时暂停:
(gdb) break 9
Breakpoint 1 at 0x8048444: file c1.c, line 9.
(gdb) run
Starting program: /home/syf/p/cc
Breakpoint 1, main () at c1.c:10
10 {
(4)查看关键寄存器内容:
(gdb) i reg esp
esp 0xbffffaec 0xbffffaec
(gdb) i reg ebp
ebp 0xbffffb28 0xbffffb28
(gdb) i reg eip
eip 0x8048444 0x8048444
(5)查看栈空间内容:
(gdb) x/32xw 0xbffffae0
0xbffffae0: 0x080494e8 0x080495e4 0xbffffaf8 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1
0xbffffb10: 0x00000000 0xbffffb5c 0x4015ec3c 0x40016300
0xbffffb20: 0x00000001 0x08048330 0x00000000 0x08048351
0xbffffb30: 0x08048444 0x00000001 0xbffffb54 0x080482bc
0xbffffb40: 0x080484b0 0x4000dc14 0xbffffb4c 0x40016944
0xbffffb50: 0x00000001 0xbffffc53 0x00000000 0xbffffc62
注意:ebp内容是:0xbffffb28,这个地址对应的内容是0x00000000;
(6)执行一条机器指令(0x8048444 : push %ebp),即将ebp压栈:
(gdb) si
0x08048445 10 {
(gdb) i reg esp
esp 0xbffffae8 0xbffffae8
(gdb) i reg ebp
ebp 0xbffffb28 0xbffffb28
(gdb) i reg eip
eip 0x8048445 0x8048445
(gdb) x/12xw 0xbffffae0
0xbffffae0: 0x080494e8 0x080495e4 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1
可以看到,ebp(内容是0xbffffb28)被压到0xbffffae8处。
(7)执行一条机器指令(0x8048445 : mov %esp,%ebp),即将esp->ebp,查看寄存器内容和堆内容:
(gdb) si
0x08048447 in main () at c1.c:10
10 {
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg esp
esp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048447 0x8048447
(gdb) x/12xw 0xbffffae0
0xbffffae0: 0x080494e8 0x080495e4 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1
可以看到,栈空间内容没变,只是寄存器ebp变成了esp的值。
(8) 执行三条机器指令,为mi,mj赋值。
三条指令是:
0x8048447 : sub $0x8,%esp
0x804844a : movl $0x1,0xfffffffc(%ebp)
0x8048451 : movl $0x2,0xfffffff8(%ebp)
这三条指令的作用是为mi,mi赋值。
(gdb) si
13 mi=1;
(gdb) si
14 mj=2;
(gdb) si
15 foo(mi,mj);
(gdb) i reg esp
esp 0xbffffae0 0xbffffae0
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048458 0x8048458
(gdb) x/12xw 0xbffffae0
0xbffffae0: 0x00000002 0x00000001 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
0xbffffb00: 0x080484b0 0x00000000 0xbffffb28 0x400464f1
可以看到,系统为mi,mj在栈内分配了空间(对应的地址分别是:0xbffffae4,0xbffffae0),并进行了赋值(分别是:0x00000001,0x00000002)。
(9)执行下一条指令(0x8048458 : sub $0x8,%esp),将堆栈栈空间加8个字节的空间,没有意义,可以略去,在优化代码时,将被略去。
注:优化代码的方法是:gcc –O –o cc cc.c
(gdb) si
0x0804845b 15 foo(mi,mj);
(10)执行下两条指令,堆将要调用的函数foo(int, int)的参数压栈。两条指令是:
0x804845b : pushl 0xfffffff8(%ebp)
0x804845e : pushl 0xfffffffc(%ebp)
这两条指令的作用是对调用的函数foo(int fi, int fj)的参数压栈。
(gdb) si
0x0804845e 15 foo(mi,mj);
(gdb) si
0x08048461 15 foo(mi,mj);
(gdb) i reg esp
esp 0xbffffad0 0xbffffad0
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048461 0x8048461
(gdb) x/12xw 0xbffffad0
0xbffffad0: 0x00000001 0x00000002 0xbffffaf8 0x08048411
0xbffffae0: 0x00000002 0x00000001 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
(11)执行下一条指令:0x8048461 : call 0x8048430 。
这条call指令可以分解成两条指令:push eip;jmp 0x8048430。
其作用是保存main函数的foo(int, int)函数调用后的下一条指令的地址(0x8048466),以便foo(int ,int)函数调用返回后在main()函数继续指令,同时还要跳转到foo(int, int)的地址(0x8048430),以便执行函数foo(int ,int);
注意:此时的eip为0x8048466,对应的指令是:0x8048466 : add $0x10,%esp
(gdb) si
foo (fi=1, fj=-1073743020) at c1.c:4
4 {
(gdb) i reg esp
esp 0xbffffacc 0xbffffacc
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048430 0x8048430
(gdb) x/12xw 0xbffffacc
0xbffffacc: 0x08048466 0x00000001 0x00000002 0xbffffaf8
0xbffffadc: 0x08048411 0x00000002 0x00000001 0xbffffb28
0xbffffaec: 0x40046507 0x00000001 0xbffffb54 0xbffffb5c
(12)执行下两条指令,并查看寄存器和堆内容:
这两条指令是:
0x8048430 : push %ebp
0x8048431 : mov %esp,%ebp
这两条指令的作用是对main函数中的 ebp进行压栈,以便返回时用(和第15步对应),同时将esp赋值为ebp(ebp->esp),一般本函数返回(和第16步对应)。
(gdb) si
0x08048431 4 {
(gdb) si
0x08048433 in foo (fi=1, fj=2) at c1.c:4
4 {
(gdb) i reg esp
esp 0xbffffac8 0xbffffac8
(gdb) i reg ebp
ebp 0xbffffac8 0xbffffac8
(gdb) i reg eip
eip 0x8048433 0x8048433
(gdb) x/12xw 0xbffffac8
0xbffffac8: 0xbffffae8 0x08048466 0x00000001 0x00000002
0xbffffad8: 0xbffffaf8 0x08048411 0x00000002 0x00000001
0xbffffae8: 0xbffffb28 0x40046507 0x00000001 0xbffffb54
可以看到ebp的内容被压栈,压在地址0xbffffac8处。
(13)执行下两条指令,并查看寄存器和堆内容:
这两条指令是:
0x8048433 : sub $0x4,%esp
0x8048436 : movl $0x3,0xfffffffc(%ebp)
其作用是为fk赋值。
(gdb) si
7 return(0);
(gdb) i reg esp
esp 0xbffffac4 0xbffffac4
(gdb) i reg ebp
ebp 0xbffffac8 0xbffffac8
(gdb) i reg eip
eip 0x804843d 0x804843d
(gdb) x/12xw 0xbffffac4
0xbffffac4: 0x00000003 0xbffffae8 0x08048466 0x00000001
0xbffffad4: 0x00000002 0xbffffaf8 0x08048411 0x00000002
0xbffffae4: 0x00000001 0xbffffb28 0x40046507 0x00000001
可以看到系统在堆中为fk分配空间(0xbffffac4),并赋值(0x00000003)。
(14)执行下一条指令(0x8048469 : mov $0x0,%eax),其作用是为eax清空,即使返回值为0。
(gdb) i reg esp
esp 0xbffffac4 0xbffffac4
(gdb) i reg ebp
ebp 0xbffffac8 0xbffffac8
(gdb) i reg eip
eip 0x8048442 0x8048442
(gdb) x/12xw 0xbffffac4
0xbffffac4: 0x00000003 0xbffffae8 0x08048466 0x00000001
0xbffffad4: 0x00000002 0xbffffaf8 0x08048411 0x00000002
0xbffffae4: 0x00000001 0xbffffb28 0x40046507 0x00000001
(15)执行下一条指令(0x8048442 : leave),这条指令可以分解为mov ebp esp和pop ebp,其作用使弹出12步中保存的main函数中的ebp,为返回做准备.
(gdb) si
0x08048443 in foo (fi=1, fj=-1073743020) at c1.c:8
8 }
(gdb) i reg esp
esp 0xbffffacc 0xbffffacc
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048443 0x8048443
(gdb) x/12xw 0xbffffacc
0xbffffacc: 0x08048466 0x00000001 0x00000002 0xbffffaf8
0xbffffadc: 0x08048411 0x00000002 0x00000001 0xbffffb28
0xbffffaec: 0x40046507 0x00000001 0xbffffb54 0xbffffb5c
(16)执行下一条指令(0x8048443 : ret),即pop eip,返回11步中保存的返回地址:0x8048466 : add $0x10,%esp。
(gdb) si
0x08048466 in main () at c1.c:15
15 foo(mi,mj);
(gdb) i reg esp
esp 0xbffffad0 0xbffffad0
(gdb) i reg ebp
ebp 0xbffffae8 0xbffffae8
(gdb) i reg eip
eip 0x8048466 0x8048466
(gdb) x/12xw 0xbffffad0
0xbffffad0: 0x00000001 0x00000002 0xbffffaf8 0x08048411
0xbffffae0: 0x00000002 0x00000001 0xbffffb28 0x40046507
0xbffffaf0: 0x00000001 0xbffffb54 0xbffffb5c 0x080482d2
(17)此时,eip为:0x8048466 : add $0x10,%esp,
其后的指令还有:
0x8048466 : add $0x10,%esp
0x8048469 : mov $0x0,%eax
0x804846e : leave
0x804846f : ret
至此函数调用完成,我们不对下面的内容进行分析。