* LEA指令
lea 7(%edx, %edx,4), %eax ==> 将寄存器%eax的值置为 5 * %edx + 7.
base(offset, index, i) 计算方法为base + offset + index * i
* leave指令
等价于:
movl %ebp %esp
popl %ebp
(gdb) disassemble
Dump of assembler code for function foo:
0x08048394 <+0>: push %ebp
0x08048395 <+1>: mov %esp,%ebp
0x08048397 <+3>: sub $0x10,%esp
0x0804839a <+6>: movl $0x0,-0x4(%ebp)
0x080483a1 <+13>: mov 0xc(%ebp),%eax
0x080483a4 <+16>: mov 0x8(%ebp),%edx
0x080483a7 <+19>: lea (%edx,%eax,1),%eax
=> 0x080483aa <+22>: leave
0x080483ab <+23>: ret
End of assembler dump.
(gdb) i r
eax 0x2 2
ecx 0xfd561843 -44689341
edx 0x0 0
ebx 0x28bff4 2670580
esp 0xbffff1c8 0xbffff1c8 #leave前esp的值为0xbffff1c8
ebp 0xbffff1d8 0xbffff1d8 #leave前ebp的值为0xbffff1d8,该值在执行leave时相当于执行一次pop,即0xbffff1d8+4=0xbffff1dc将作为esp的新值
esi 0x0 0
edi 0x0 0
eip 0x80483aa 0x80483aa <foo+22>
eflags 0x200282 [ SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) p/d ($ebp-$esp)/4
$3 = 4
(gdb) x/5xw $esp
0xbffff1c8: 0xbffff1f8 0x080483f9 0x0015ecbd 0x00000000
0xbffff1d8: 0xbffff1f8 #$ebp指向的栈底的值为0xbffff1f8,也就是前一栈帧的帧底,leave执行后该值将是ebp的新值
(gdb) ni #执行leave指令
0x080483ab in foo (a=0, b=2) at test.c:4
4 }
(gdb) i r
eax 0x2 2
ecx 0xfd561843 -44689341
edx 0x0 0
ebx 0x28bff4 2670580
esp 0xbffff1dc 0xbffff1dc #leave后ebp的值为0xbffff1dc
ebp 0xbffff1f8 0xbffff1f8 #leave后ebp的值为0xbffff1f8
esi 0x0 0
edi 0x0 0
eip 0x80483ab 0x80483ab <foo+23>
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/1xw $esp
0xbffff1dc: 0x080483d6 #这个状态正好相对就于call后的状态,即esp指向的正是lr返回地址,执行ret后0x080483d6将被弹入eip继续执行程序
(gdb) disassemble
Dump of assembler code for function foo:
0x08048394 <+0>: push %ebp
0x08048395 <+1>: mov %esp,%ebp
0x08048397 <+3>: sub $0x10,%esp
0x0804839a <+6>: movl $0x0,-0x4(%ebp)
0x080483a1 <+13>: mov 0xc(%ebp),%eax
0x080483a4 <+16>: mov 0x8(%ebp),%edx
0x080483a7 <+19>: lea (%edx,%eax,1),%eax
0x080483aa <+22>: leave
=> 0x080483ab <+23>: ret
End of assembler dump.
(gdb) ni
main () at test.c:12
12 return 0;
(gdb) disassemble
Dump of assembler code for function main:
0x080483b1 <+0>: push %ebp
0x080483b2 <+1>: mov %esp,%ebp
0x080483b4 <+3>: sub $0x18,%esp
0x080483b7 <+6>: movl $0x0,-0x4(%ebp)
0x080483be <+13>: call 0x80483ac <fn>
0x080483c3 <+18>: movl $0x2,0x4(%esp)
0x080483cb <+26>: mov -0x4(%ebp),%eax
0x080483ce <+29>: mov %eax,(%esp)
0x080483d1 <+32>: call 0x8048394 <foo>
=> 0x080483d6 <+37>: mov $0x0,%eax
0x080483db <+42>: leave
0x080483dc <+43>: ret
End of assembler dump.
(gdb)
综上,leave正是函数入口的
push %ebp
mov %esp,%ebp
的反操作.
而ret则是call的反操作. 正反操作成对后,从栈(尺寸度量)的角度考虑,变化为零.
这正是函数调用中,栈完美运行的奥秘.
还有一种情况比较特殊,就是这样的代码
void fn(){}
080483ac <fn>:
80483ac: 55 push %ebp
80483ad: 89 e5 mov %esp,%ebp
80483af: 5d pop %ebp
80483b0: c3 ret
从汇编上看,函数逻辑代码部分本身没有用到栈
push %ebp
与
pop %ebp
构成了一对正反操作.