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

来源:互联网 发布:mac引导修复工具 编辑:程序博客网 时间:2024/05/29 11:21

今天我们来以一个简单的案例来解释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 DisasmAddress   Hex dump          Command                                  Comments004010A0  /$  55            PUSH EBP                                 ; INT StackFrame.main(argc,argv)004010A1  |.  8BEC          MOV EBP,ESP004010A3  |.  83EC 08       SUB ESP,8004010A6  |.  C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1004010AD  |.  C745 FC 02000 MOV DWORD PTR SS:[LOCAL.1],2004010B4  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]004010B7  |.  50            PUSH EAX                                 ; /b => 2004010B8  |.  8B4D F8       MOV ECX,DWORD PTR SS:[LOCAL.2]           ; |004010BB  |.  51            PUSH ECX                                 ; |a => 1004010BC  |.  E8 BFFFFFFF   CALL add                                 ; \add004010C1  |.  83C4 08       ADD ESP,8004010C4  |.  50            PUSH EAX004010C5  |.  68 48644100   PUSH OFFSET 00416448                     ; /_Format = "%d"004010CA  |.  E8 71FFFFFF   CALL printf                              ; \printf004010CF  |.  83C4 08       ADD ESP,8004010D2  |.  33C0          XOR EAX,EAX004010D4  |.  8BE5          MOV ESP,EBP004010D6  |.  5D            POP EBP004010D7  \.  C3            RETN


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

PUSH EBPMOV EBP,ESPSUB ESP,8***********省略********MOV ESP,EBPPOP EBPRETN

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],1004010AD  |.  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 => 2004010B8  |.  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 DisasmAddress   Hex dump          Command                                  Comments00401080  /$  55            PUSH EBP                                 ; INT StackFrame.add(a,b)00401081  |.  8BEC          MOV EBP,ESP00401083  |.  83EC 08       SUB ESP,800401086  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]00401089  |.  8945 FC       MOV DWORD PTR SS:[LOCAL.1],EAX0040108C  |.  8B4D 0C       MOV ECX,DWORD PTR SS:[ARG.2]0040108F  |.  894D F8       MOV DWORD PTR SS:[LOCAL.2],ECX00401092  |.  8B45 FC       MOV EAX,DWORD PTR SS:[LOCAL.1]00401095  |.  0345 F8       ADD EAX,DWORD PTR SS:[LOCAL.2]00401098  |.  8BE5          MOV ESP,EBP0040109A  |.  5D            POP EBP0040109B  \.  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],EAX0040108C  |.  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 EAX004010C5  |.  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 返回到启动函数了

原创粉丝点击