记得n年前汇编考试我在上面写作文,最后老师还是放过去了,语言嘛,用不上自然没动力学。x86的汇编跟blackfin的汇编比起来似乎要零碎一点,不过都是寄存器的操作。
最简单的hello和main函数
int hello(int i)
{
return i;
}
int main(void)
{
hello(1);
return 42;
}
vc2008的反汇编如下:(本机cpu:Intel Celeron CPU 2.66GHz)
main()函数
int main(void)
{
004113E0 push ebp
004113E1 mov ebp,esp
004113E3 sub esp,0C0h
004113E9 push ebx
004113EA push esi
004113EB push edi
004113EC lea edi,[ebp-0C0h] //把偏移地址存入edi
004113F2 mov ecx,30h
004113F7 mov eax,0CCCCCCCCh
004113FC rep stos dword ptr es:[edi]//重复ecx次按一次4个字节保存eax到es:[edi]地址,每重复一次,edi地址按4个字节递增。dword ptr 为没有寄存器参与的内存单元访问指令指明访问的长度。
hello(1);
004113FE push 1
00411400 call @ILT+10(_hello) (41100Fh)
00411405 add esp,4
return 42;
00411408 mov eax,2Ah //返回值放入EAX
}
0041140D pop edi
0041140E pop esi
0041140F pop ebx
00411410 add esp,0C0h
00411416 cmp ebp,esp
00411418 call @ILT+315(__RTC_CheckEsp) (411140h)
0041141D mov esp,ebp
0041141F pop ebp
00411420 ret
跳转
@ILT+10(_hello):
0041100F jmp hello (4113A0h)
hello()函数
int hello(int i)
{
004113A0 push ebp
004113A1 mov ebp,esp
004113A3 sub esp,0C0h
004113A9 push ebx
004113AA push esi
004113AB push edi
004113AC lea edi,[ebp-0C0h]
004113B2 mov ecx,30h
004113B7 mov eax,0CCCCCCCCh
004113BC rep stos dword ptr es:[edi]
return i;
004113BE mov eax,dword ptr [i] //返回值放入EAX
}
004113C1 pop edi
004113C2 pop esi
004113C3 pop ebx
004113C4 mov esp,ebp
004113C6 pop ebp
004113C7 ret
寄存器组
数据寄存器:保存操作数和运算结果等信息。
EAX:Accumulator。函数返回值。取低16位为AX,分割为8位寄存器AH-AL。指令ret返回用到。
EBX:Base Register。
ECX:Count Register。
EDX:Data Register。
指针寄存器:EBP,ESP可作为通用寄存器,即可存储算术逻辑运算的操作数和运算结果。
EBP:Base Pointer,基指针寄存器,直接访问栈中的数据。
ESP:Stack Pointer,栈指针寄存器,只可访问栈顶。指令pop/push时自动变。
EIP:Instruction Pointer,指令指针寄存器,存放下次将要执行的指令在代码段中的偏移量。每走一条指令自动变一次,如果希望跳转后能返回继续就需要跳转前把它放入栈中,返回时出栈。
变址寄存器:主要用于存放存储单元在段内的偏移量。
ESI: Source Index,源变址寄存器。EDS:ESI即源串段寄存器:源串变址,ESI在串操作中自动增减。
EDI:Destination Index,目标变址寄存器。EES:EDI即目标串段寄存器:目标串变址,EDI在串操作中自动增减。
段寄存器:内存分段。这里最多为6个内存段,不同的内存段放入不同的东西。
ECS:Code Segment Register,代码段寄存器。
EDS:Data Segment Register,数据段寄存器。
EES:Extra Segment Register,附加段寄存器。
ESS:Stack Segment Register,栈段寄存器。
EFS:Extra Segment Register,附加段寄存器。
EGS:Extra Segment Register,附加段寄存器。
函数调用过程call 和ret
函数调用如何定位指令地址,利用和保护栈空间的呢?主要关注EIP,EBP,ESP的变化就可以看出来了。
int main(void)
{
...
hello(1);
004113FE push 1
//EIP = 00411400 ESP = 0013FE98 EBP = 0013FF68
00411400 call @ILT+10(_hello) (41100Fh) //跳转到hello()
//从hello()返回
//EIP = 00411405 ESP = 0013FE98 EBP = 0013FF68
00411405 add esp,4
...
}
//EIP = 0041100F ESP = 0013FE94 EBP = 0013FF68
@ILT+10(_hello):
0041100F jmp hello (4113A0h)
int hello(int i)
{
//EIP = 004113A0 ESP = 0013FE94 EBP = 0013FF68
004113A0 push ebp
//EIP = 004113A1 ESP = 0013FE90 EBP = 0013FF68
004113A1 mov ebp,esp
//EIP = 004113A3 ESP = 0013FE90 EBP = 0013FE90
...
return i;
004113BE mov eax,dword ptr [i]
}
...
//EIP = 004113C4 ESP = 0013FDD0 EBP = 0013FE90
004113C4 mov esp,ebp
//EIP = 004113C6 ESP = 0013FE90 EBP = 0013FE90
004113C6 pop ebp
//EIP = 004113C7 ESP = 0013FE94 EBP = 0013FF68
004113C7 ret
call指令:
观察
//EIP = 00411400 ESP = 0013FE98 EBP = 0013FF68
00411400 call @ILT+10(_hello) (41100Fh) //跳转到hello()
00411405 add esp,4
到
//EIP = 0041100F ESP = 0013FE94 EBP = 0013FF68
0041100F jmp hello (4113A0h)
再到
int hello(int i)
{
//EIP = 004113A0 ESP = 0013FE94 EBP = 0013FF68
004113A0 push ebp
EIP始终指向下一个要执行的指令,call指令使得ESP往上走了4个字节,观察对应地址的内容,发现是call后一条指令的地址入栈了。这是call指令隐含了跳转前先计算下一条指令的地址放入EIP,然后入栈相当于push eip,再让EIP指向跳转的指令地址。另外看到因为jmp不需要返回它的下一条指令,所以从jmp到hello()时EIP没有入栈,ESP也没变。
ret指令:
观察
//EIP = 004113C7 ESP = 0013FE94 EBP = 0013FF68
004113C7 ret
到
//从hello()返回
//EIP = 00411405 ESP = 0013FE98 EBP = 0013FF68
00411405 add esp,4
ret指令使得ESP往下走了4个字节,EIP刚好是从栈顶取出来的。这是ret指令隐含了返回前相当于先出栈pop eip。
栈空间转换和恢复:
int hello(int i)
{
004113A0 push ebp
004113A1 mov ebp,esp
...
004113C4 mov esp,ebp
004113C6 pop ebp
004113C7 ret
进入函数时ebp入栈,返回前ebp出栈,相当于恢复前一函数的ebp。
进入函数时ebp=esp,ebp在此函数中一直保持不变,返回前esp=ebp,相当于恢复前一函数的esp。
进入函数时ebp=esp,即把上一函数的栈顶位置作为当前子函数的基指针地址。(ebp相当于blackfin里的FP寄存器)
至于此函数如何用栈空间esp,就是它的自由啦。
函数参数传递和栈数据变化:
观察:函数hello传递参数i
//EIP = 004113FE ESP = 0013FE9C EBP = 0013FF68
//memory 0x0013fe94: 1027039f 0013feb8
004113FE push 1
//EIP = 00411400 ESP = 0013FE98 EBP = 0013FF68
//memory 0x0013fe94: 1027039f 00000001
00411400 call @ILT+10(_hello) (41100Fh) //跳转到hello()
//EIP = 00411405 ESP = 0013FE98 EBP = 0013FF68
00411405 add esp,4 //和参数传递的push 1对应,esp恢复
//EIP = 00411408 ESP = 0013FE9C EBP = 0013FF68
到
//EIP = 0041100F ESP = 0013FE94 EBP = 0013FF68
//memory 0x0013fe94: 00411405 00000001
0041100F jmp hello (4113A0h)
再到
int hello(int i)
{
//EIP = 004113A0 ESP = 0013FE94 EBP = 0013FF68
//memory 0x0013fe94: 00411405 00000001
//&i : 0x0013fe98, i=1
004113A0 push ebp
...
return i;
004113BE mov eax,dword ptr [i]
}