简单c语言汇编后代码解释(1)

今天我们来以一个简单的案例来解释c语言对应汇编语言的关系

 #include "stdio.h"
long add(long a, long b)
{
    long x = a, y = b;
    return (x + y);
}

int main(int argc, char* argv[])
{
        long a = 1, b = 2;

    printf("%d\n", add(a, b));

    return 0;
}

编译之后反汇编的代码如下 我们来逐一分析,需要注意我们这里是直接跳过了编译器附加的启动函数 来到了我们自己写的main函数

CPU Disasm
Address   Hex dump          Command                                  Comments
004010A0  /$  55            PUSH EBP                                 ; INT StackFrame.main(argc,argv)
004010A1  |.  8BEC          MOV EBP,ESP
004010A3  |.  83EC 08       SUB ESP,8
004010A6  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1
004010AD  |.  C745 FC 02000 MOV DWORD PTR SS:[LOCAL.1],2
004010B4  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]
004010B7  |.  50            PUSH EAX                                 ; /b => 2
004010B8  |.  8B4D F8       MOV ECX,DWORD PTR SS:[LOCAL.2]           ; |
004010BB  |.  51            PUSH ECX                                 ; |a => 1
004010BC  |.  E8 BFFFFFFF   CALL add                                 ; \add
004010C1  |.  83C4 08       ADD ESP,8
004010C4  |.  50            PUSH EAX
004010C5  |.  68 48644100   PUSH OFFSET 00416448                     ; /_Format = "%d
"
004010CA  |.  E8 71FFFFFF   CALL printf                              ; \printf
004010CF  |.  83C4 08       ADD ESP,8
004010D2  |.  33C0          XOR EAX,EAX
004010D4  |.  8BE5          MOV ESP,EBP
004010D6  |.  5D            POP EBP
004010D7  \.  C3            RETN


首先是前三句以及最后的三句 ,这六句在每次进入一个函数时都会出现

PUSH EBP
MOV EBP,ESP
SUB ESP,8
***********省略********
MOV ESP,EBP
POP EBP
RETN

push ebp pop ebp这一对是因为该函数中需要用到ebp寄存器,所以需要先把他原来的值压栈保护起来,等到该函数执行完毕时再返回

除了ebp之外,如果函数内还有用到其他寄存器比如eax ebx 也会出现类似的
开头先push eax 最后 pop eax 弹出eax

那么既然函数里需要用到ebp,那么ebp到底有什么用呢?
ebp其实是一个局部变量的基地址
我们可以看到
push ebp之后执行了一句mov ebp,esp
而esp是栈指针,所以此时ebp也指向了栈指针,而整个函数的局部变量,也是全部保存在栈中的,所以ebp其实是为了方便指向局部变量的一个寄存器
因为ebp的值在整个函数中都不会改变,所以此时引用栈中的局部变量就十分轻松。
接下来 sub esp,8是为了给局部变量留出空间
即ebp和esp之间存放局部变量



接下来

004010A6  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1
004010AD  |.  C745 FC 02000 MOV DWORD PTR SS:[LOCAL.1],2

就对应c语言中的 long a = 1, b = 2;
local.2 local.1即为ebp-8 ebp-4



然后紧跟的 下面四句

004010B4  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]
004010B7  |.  50            PUSH EAX                                 ; /b => 2
004010B8  |.  8B4D F8       MOV ECX,DWORD PTR SS:[LOCAL.2]           ; |
004010BB  |.  51            PUSH ECX                                 ; |a => 1

这四句 即为传递参数给函数 首先先把两个局部变量分别放到eax ecx,然后再Push到栈中,为什么不能直接push到栈中而用寄存器中转呢,很简单,因为x86cpu只支持这样的指令



接下来就是调用add方法了 然后我们进入add方法内部看一看

004010BC  |.  E8 BFFFFFFF   CALL add                                 ; \add

我们可以注意到 之前所说的那6句又重复出现了 这种调用可以无限嵌套下去直到爆栈

CPU Disasm
Address   Hex dump          Command                                  Comments
00401080  /$  55            PUSH EBP                                 ; INT StackFrame.add(a,b)
00401081  |.  8BEC          MOV EBP,ESP
00401083  |.  83EC 08       SUB ESP,8
00401086  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]
00401089  |.  8945 FC       MOV DWORD PTR SS:[LOCAL.1],EAX
0040108C  |.  8B4D 0C       MOV ECX,DWORD PTR SS:[ARG.2]
0040108F  |.  894D F8       MOV DWORD PTR SS:[LOCAL.2],ECX
00401092  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]
00401095  |.  0345 F8       ADD EAX,DWORD PTR SS:[LOCAL.2]
00401098  |.  8BE5          MOV ESP,EBP
0040109A  |.  5D            POP EBP
0040109B  \.  C3            RETN


add方法没什么好解释的,和上面一样 下面四句对应c语言中的 long x = a, y = b; 即把形参分别赋值给局部变量 形参是之前已经压栈进来的 所以去栈对应的位置取就行

00401086  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]
00401089  |.  8945 FC       MOV DWORD PTR SS:[LOCAL.1],EAX
0040108C  |.  8B4D 0C       MOV ECX,DWORD PTR SS:[ARG.2]
0040108F  |.  894D F8       MOV DWORD PTR SS:[LOCAL.2],ECX


下面两句完成了实际的加法功能,首先把局部变量放入寄存器中,然后再用寄存器做加法,为什么不能直接对两个内存单元做加法呢?和上面一样,不支持这种指令,
加法只能是先取内存单元到寄存器中,再对寄存器做加法

0401092  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]
00401095  |.  0345 F8       ADD EAX,DWORD PTR SS:[LOCAL.2]

接下来三句之前解释过了,不过这里注意一个细节,就是加法完成后结果值保存在了eax中,这是一个默认的返回值寄存器,即调用方法后结果值默认保存在eax中
不过对于有多返回值的语言比如lua,就不太清楚会怎么样了

接下来返回到主函数中 然后紧跟着执行了一句

004010C1  |.  83C4 08       ADD ESP,8

这是什么意思呢?其实这是一种调用约定,因为我们之前调用函数时压入了两个参数,而函数调用完成后栈指针应该恢复到原来位置,而这种是一种调用者恢复栈指针的做法,也可以在函数里面恢复栈指针



接下来四句 还是同样 先进行压栈 然后call 返回之后进行清栈 printf函数内部我们就不深究了

004010C4  |.  50            PUSH EAX
004010C5  |.  68 48644100   PUSH OFFSET 00416448                     
004010CA  |.  E8 71FFFFFF   CALL printf                             
004010CF  |.  83C4 08       ADD ESP,8


最后一句值得注意的就是这个了,前面说过 eax是默认存放返回值的一个寄存器,而我们的main函数其实是由别的启动函数调用的 所以我们要返回值给他
return 0就表示程序正常结束

004010D2  |.  33C0          XOR EAX,EAX

然后就是还原esp 返回到启动函数了

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值