int main(void)
{
80483c5: 55 push %ebp
80483c6: 89 e5 mov %esp,%ebp
80483c8: 83 ec 08 sub $0x8,%esp
foo(2,3);
80483cb: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp)
80483d2: 00
80483d3: c7 04 24 02 00 00 00 movl $0x2,(%esp)
80483da: e8 cc ff ff ff call 80483ab
return 0;
80483df: b8 00 00 00 00 mov $0x0,%eax
}
我们来分析这段代码:foo(2,3)
我们知道:esp寄存器是存放栈区的内存地址的
我们看到,foo(2,3)函数的第二个参数是先执行保存的,然后在保存的是第一个参
数,第二个参数保存在esp+4指向的内存地址-----这个是基址寻址方式。
第一个参数保存在esp指向的内存位置。-----可见参数是从右像左依次压栈的。
然后执行call 指令:
它有以下两个作用:
1,foo函数调用完之后,要返回到call的下一条指令继续执行,所以call的下
一条指令的地址ox80483e9压栈,同时把esp的值减4,现在的值为oxbfflc418
2,修改程序计数器eip,跳转到foo函数的开头执行。
80483e4: c9 leave
80483e5: c3 ret
80483e6: 90 nop
80483e7: 90 nop
80483e8: 90 nop
80483e9: 90 nop
80483ea: 90 nop
80483eb: 90 nop
80483ec: 90 nop
80483ed: 90 nop
80483ee: 90 nop
80483ef: 90 nop
080483f0 <__libc_csu_fini>:
80483f0: 55 push %ebp
80483f1: 89 e5 mov %esp,%ebp
80483f3: 5d pop %ebp
80483f4: c3 ret
80483f5: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
80483f9: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
08048400 <__libc_csu_init>:
8048400: 55 push %ebp
8048401: 89 e5 mov %esp,%ebp
8048403: 57 push %edi
8048404: 56 push %esi
8048405: 53 push %ebx
8048406: e8 4f 00 00 00 call 804845a <__i686.get_pc_thunk.bx>
804840b: 81 c3 29 12 00 00 add $0x1229,%ebx
8048411: 83 ec 1c sub $0x1c,%esp
8048414: e8 5b fe ff ff call 8048274 <_init>
8048419: 8d bb 20 ff ff ff lea -0xe0(%ebx),%edi
804841f: 8d 83 20 ff ff ff lea -0xe0(%ebx),%eax
8048425: 29 c7 sub %eax,%edi
8048427: c1 ff 02 sar $0x2,%edi
804842a: 85 ff test %edi,%edi
804842c: 74 24 je 8048452 <__libc_csu_init+0x52>
804842e: 31 f6 xor %esi,%esi
8048430: 8b 45 10 mov 0x10(%ebp),%eax
8048433: 89 44 24 08 mov %eax,0x8(%esp)
8048437: 8b 45 0c mov 0xc(%ebp),%eax
804843a: 89 44 24 04 mov %eax,0x4(%esp)
804843e: 8b 45 08 mov 0x8(%ebp),%eax
8048441: 89 04 24 mov %eax,(%esp)
8048444: ff 94 b3 20 ff ff ff call *-0xe0(%ebx,%esi,4)
804844b: 83 c6 01 add $0x1,%esi
804844e: 39 fe cmp %edi,%esi
8048450: 72 de jb 8048430 <__libc_csu_init+0x30>
8048452: 83 c4 1c add $0x1c,%esp
8048455: 5b pop %ebx
8048456: 5e pop %esi
8048457: 5f pop %edi
8048458: 5d pop %ebp
8048459: c3 ret
0804845a <__i686.get_pc_thunk.bx>:
804845a: 8b 1c 24 mov (%esp),%ebx
804845d: c3 ret
804845e: 90 nop
804845f: 90 nop
08048460 <__do_global_ctors_aux>:
8048460: 55 push %ebp
8048461: 89 e5 mov %esp,%ebp
8048463: 53 push %ebx
8048464: 8d 64 24 fc lea -0x4(%esp),%esp
8048468: a1 54 95 04 08 mov 0x8049554,%eax
804846d: 83 f8 ff cmp $0xffffffff,%eax
8048470: 74 12 je 8048484 <__do_global_ctors_aux+0x24>
8048472: bb 54 95 04 08 mov $0x8049554,%ebx
8048477: 90 nop
8048478: 8d 5b fc lea -0x4(%ebx),%ebx
804847b: ff d0 call *%eax
804847d: 8b 03 mov (%ebx),%eax
804847f: 83 f8 ff cmp $0xffffffff,%eax
8048482: 75 f4 jne 8048478 <__do_global_ctors_aux+0x18>
8048484: 8d 64 24 04 lea 0x4(%esp),%esp
8048488: 5b pop %ebx
8048489: 5d pop %ebp
804848a: c3 ret
804848b: 90 nop
Disassembly of section .fini:
0804848c <_fini>:
804848c: 55 push %ebp
804848d: 89 e5 mov %esp,%ebp
804848f: 53 push %ebx
8048490: 83 ec 04 sub $0x4,%esp
8048493: e8 00 00 00 00 call 8048498 <_fini+0xc>
8048498: 5b pop %ebx
8048499: 81 c3 9c 11 00 00 add $0x119c,%ebx
804849f: e8 6c fe ff ff call 8048310 <__do_global_dtors_aux>
80484a4: 59 pop %ecx
80484a5: 5b pop %ebx
80484a6: c9 leave
80484a7: c3 ret
接下来我们用gdb 调试的结果和步骤为:
[yskcg@yskcg workc]$ gdb diaoyong
GNU gdb (GDB) Fedora (7.1-34.fc13)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
Reading symbols from /home/yskcg/workc/diaoyong...done.
(gdb) start
Temporary breakpoint 1 at 0x80483cb: file diaoyong.c, line 29.
Starting program: /home/yskcg/workc/diaoyong
Temporary breakpoint 1, main () at diaoyong.c:29
29 foo(2,3);
Missing separate debuginfos, use: debuginfo-install glibc-2.12.1-4.i686
(gdb) s
foo (a=2, b=3) at diaoyong.c:25
25 return bar(a,b);
(gdb)
bar (c=2, d=3) at diaoyong.c:20
20 int e=c+d;
(gdb) disassemble
Dump of assembler code for function bar:
0x08048394 <+0>: push %ebp
0x08048395 <+1>: mov %esp,%ebp
0x08048397 <+3>: sub $0x10,%esp
=> 0x0804839a <+6>: mov 0xc(%ebp),%eax
0x0804839d <+9>: mov 0x8(%ebp),%edx
0x080483a0 <+12>: lea (%edx,%eax,1),%eax
0x080483a3 <+15>: mov %eax,-0x4(%ebp)
0x080483a6 <+18>: mov -0x4(%ebp),%eax
0x080483a9 <+21>: leave /* 下面的两句是对开始的逆操作--push %ebp
和mov %esp ,%ebp*/
0x080483aa <+22>: ret
End of assembler dump.
(gdb) si
0x0804839d 20 int e=c+d;
(gdb)
0x080483a0 20 int e=c+d;
(gdb)
0x080483a3 20 int e=c+d;
(gdb)
21 return e;
(gdb) si
22 }
(gdb)
0x080483aa in bar (c=can't compute CFA for this frame
) at diaoyong.c:22
22 }
(gdb) bt
#0 0x080483aa in bar (c=can't compute CFA for this frame
) at diaoyong.c:22
#1 0x080483c3 in foo (a=2, b=3) at diaoyong.c:25
#2 0x080483df in main () at diaoyong.c:29
(gdb) info registers
eax 0x5 5
ecx 0x381adf3a 941285178
edx 0x2 2
ebx 0xa19ff4 10592244
esp 0xbffff2bc 0xbffff2bc
ebp 0xbffff2c8 0xbffff2c8
esi 0x0 0
edi 0x0 0
eip 0x80483aa 0x80483aa
eflags 0x200282 [ SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) x/20 $esp
0xbffff2bc: 134513603 2 3 -1073745192
0xbffff2cc: 134513631 2 3 -1073745064
0xbffff2dc: 9080006 1 -1073745020 -1073745012
0xbffff2ec: -1207962672 134513376 -1 8974276
0xbffff2fc: 134513173 1 -1073745088 8906549
(gdb)
这里我们用到的新的命令有:
disassemble:可以反汇编当前的函数。如果加上函数名,则反汇编指定的函数。
si:可以逐条指令的进行单步的测试。
info register 可以显示所有寄存器当前的值。
gdb中表示寄存器的时候要在前面加上$.
esp寄存器是存放栈区的内存地址的.
以 Push ebp 和mov %esp,%ebp这两句开始的,不用我多说大家也知道这两句的意思是以EBP代替ESP,作为访问堆栈的指针。
在gdb中可以用bt命令和fram命令查看每层栈桢上的参数和局部变量.
EBP、ESP、BP和SP都称为指针寄存器,主要用于存放堆栈内存储单元的偏移量,用它们可实现多种存储器操作数的寻址方式。
现在我们来说一说EBP:
EBP是基址指针寄存器:一般用来确认堆栈帧的起始位置,也就是指向栈底。也就是说,一般一个函数入口的地址也就存放在EBP中(所以一般在进入函数的时候将ebp寄存器内容压栈,即保存其函数的上级调用函数的栈基地址,以便于以后返回调用。
在每个函数的栈桢中,ebp指向栈底,而esp指向栈顶,在函数的执行过程中esp
会随着压栈和出栈操作随时变化,而ebp的是不变的,函数的参数和局部变量都是通过e
bp的值加上一个偏移量来访问。
现在知道main函数是如何把里面的函数调用起来的了:
首先,要有一个ebp寄存器,因为我们的ebp寄存器一般(人为习惯哈!并不是
是规定的,你可以用任何一个通用寄存器,除了一些特殊的指令)用来存放函
数的基地址,我们通过基地址可以来查找我们调用函数的位置,因为函数的执
行时,会产生一个栈,用来保存函数执行过程中的参数和变量,同时我们知道
栈是内存的一种申请方式之一,同时,栈的大小是有限的,一般用来存放局部
变量和参数,像全局变量和static之类的内存申请是--向高地址扩增,可以
非常的大,那也就是说,我们栈的大小是有限的,那么栈的扩展方向是向低地
址,因为,大小确定了嘛!
其次,因为,我们的ebp寄存器存放的是基地址,那么我们把这个值压栈,既
然说是基地址,那么,我们的ebp寄存器就是指向栈的底部了萨,之前,知道
栈是向低地址扩展的嘛!我们把ebp的值压栈之后,但我们调用函数的时候--
假设我们当前是在main函数中哈!就本例来说,程序接着会调用函数foo函数
那么我们,在调用foo函数的时候,会执行push %ebp---和--mov %esp,%ebp
语句,那么,什么又是esp呢?其实,esp也是一个通用寄存器,其实其作用
相当与一个指针的,esp是指向栈的栈顶,我们也可以通过esp来找到函数的参
数和元素。(先入栈的a1,a2,a3,a4,a5...an)----那么a1的位置是栈顶元素
,an是栈底元素。那我们执行相应的汇编语句的作用是什么呢?push %ebp,
是把前面一个函数的栈底地址压栈,然后,把现在调用的函数的esp寄存器中
的值赋值给ebp,也即是,我们把前面的一个函数的ebp的值(地址),存放在当
前调用函数的ebp中,那么当前的ebp的地址在执行mov %esp,%ebp 后,就把es
p寄存器中的值赋值给了,ebp,esp当然是上一个被执行函数的值了萨,内存
是连续的嘛!那么,这样,就把里面的函数调用起来了。
反过来,我们通过最后调用的函数,中的ebp的值,我们知道,上一个函数的
ebp的值,直到我们找到main函数的ebp的值。
因此,各层函数栈桢通过保存在栈上的ebp的值串起来了。
在调用函数结束后,有两个指令:
leave
ret
其中:leave 是push %ebp ---和----mov %esp,%ebp 的逆操作
ret 是call 指令的逆操作。
函数调用和返回过程中的规则:
1:参数压栈传递,并且是从右向左依次压栈。
2:ebp 总是指向当前栈桢的栈底
3:返回值通过eax寄存器进行传递。