• 汇编语言基础之五 一个源代码与反汇编代码对照的例子


    如下的C++代码,对应的汇编代码会是什么样子呢?

    #include <stdio.h>
    
    int Addemup(int,int);
    
    void main(void)
    {
        int x = 5;
        int y = 10;
        int z = 0;
        z = Addemup(x,y);
        printf("z= %i\n",z);
    }
    
    int Addemup(int a, int b)
    {
        int c = 0;
        c=a+b;
        return(c);
    }

    1:    #include <stdio.h>
    2:
    3:    int Addemup(int,int);
    4:
    5:    void main(void)
    6:    {
                addemup!main:
                00401000 55               push    ebp            ; save base pointer
                00401001 8bec            mov     ebp,esp        ; set stack pointer
                00401003 83ec0c         sub      esp,0xc        ; make room for locals
    
               //在这一段中,我们先保存了栈底指针(ebp),以便稍后恢复它。
             //之后让栈基指向栈顶,把原来的栈顶作为新栈底了,形象点的说,就是栈向上移动了原来栈长度的一段距离。
             //在这里,我们可以看出,若要访问函数参数,那么采用的相对于ebp的位移就是正的,
             //而函数的局部变量的位移就是负的。
    
    
    7:         int x = 5;
    8:         int y = 10;
    9:         int z = 0;
    
                00401006 c745fc05000000   mov     dword ptr [ebp-0x4],0x5    ; local x = 5
                0040100d c745f80a000000   mov     dword ptr [ebp-0x8],0xa    ; local y = 10
                00401014 c745f400000000   mov     dword ptr [ebp-0xc],0x0    ; local z = 0
    
                //ptr指令是修改属性运算符,用来明确指出变量、标号或地址表达式的类型属性(只在所在的指令内有效)。
             //类型放在PTR 之前,可以是BYTE、WORD、DWORD、NEAR、FAR。
             //这里的操作是将x5这个操作数扩展成dword存入地址为[ebp-0x4]的内存中
    
    10:
    11:        z = Addemup(x,y);
    
                0040101b 8b45f8         mov     eax,[ebp-0x8]    ; load eax with y
                0040101e 50               push    eax                    ; push y on stack
                0040101f 8b4dfc          mov     ecx,[ebp-0x4]    ; load ecx with x
                00401022 51               push    ecx                    ; push x on stack
                00401023 e81b000000    call    addemup!Addemup    ; call Addemup
                00401028 83c408           add     esp,0x8            ; fixup stack for args
                0040102b 8945f4           mov     [ebp-0xc],eax   ; z returned in eax
    
               //这里是Addemup函数的调用部分。先将x,y压入栈中,作为调用的参数。
             //压入了两个dword型参数,每个dword占4个字节,所以栈顶指针在压栈之后向上移动了8个字节.
               //调用结束之后,指示栈顶的esp被加回了8个字节,也就是恢复了栈的状态。
             //这里注意,一般函数调用的返回值都是放在eax之中的,
             //所以将eax的值拷贝到[ebp-0xc]中完成的就是给z赋予函数返回结果的动作。
             //可以看出,这里在函数调用结束后,去掉参数的栈顶的恢复工作是由调用者完成的。
    
    12:
    13:       printf("z= %i\n",z);
    
                0040102e 8b55f4           mov     edx,[ebp-0xc]     ; load edx with z
                00401031 52                 push    edx                   ; push z on the stack
                00401032 6830704000    push    0x407030           ; push ptr to “z=%i\n”
             00401037 e822000000   call      addemup!printf    ; call printf
                0040103c 83c408          add      esp,0x8             ; fix stack for args
    
                 //这里是一个对printf函数的调用,过程与上面的函数调用一样。
             //1.先将参数压栈; 2.保存当前栈底位置; 3.调用函数; 4.恢复栈顶位置
    14:    }
    
            0040103f 8be5            mov     esp,ebp        ; restore stack ptr
            00401041 5d               pop     ebp            ; restore base ptr
            00401042 c3               ret                ; return
            
            //这里还是main函数。函数的调用者负责调整栈顶指针,被调用的函数体要将栈顶,栈底都恢复到被调用之前的状态。
    15:
    16:    int Addemup(int a, int b)
    17:    {
    
                  addemup!Addemup:
                  00401043 55               push    ebp            ; save base pointer
                  00401044 8bec            mov     ebp,esp      ; set stack pointer
                  00401046 51               push    ecx            ; make room for local
    
                  //1. 保存栈底,2. 设置栈底,3. 修改栈顶,为本地变量分配空间
    
    18:         int c = 0;
    19:
    20:         c = a + b;
    
                  00401047 c745fc00000000   mov     dword ptr [ebp-0x4],0x0    ; local c = 0
                  0040104e 8b4508           mov     eax,[ebp+0x8]    ; set eax to local a
                  00401051 03450c           add      eax,[ebp+0xc]    ; eax = eax + local b
                  00401054 8945fc            mov     [ebp-0x4],eax    ; local c = a + b
    
                 //eax是累加器
    
    21:        return(c);
    
                 00401057 8b45fc             mov     eax,[ebp-0x4]  ; set eax to result c
    
                 //eax被用来返回值
    22:    }
    
    0040105a 8be5             mov     esp,ebp    ; restore stack ptr
    0040105c 5d                 pop     ebp          ; restore base ptr
    0040105d c3                 ret                ; return to caller
    
    //恢复栈顶,恢复栈底
    
    

    总结一下函数调用的过程中,栈的运作吧。该例子中使用的调用约定是CDECL,后面的文章会讲到。

    调用者负责的部分:

    阶段 动作 细节
    调用之前(状态一) 现在栈拥有一个调用函数之前的状态。假设ebp=a; esp=b;
    准备工作 将所有参数压入栈 随着压栈的动作,栈顶指针向低地址端移动。esp=b-x。(状态二)
    调用函数 见下表
    收尾工作 修改栈顶指针 恢复到状态一。esp=b, ebp=a

    被调用者负责的部分:

    阶段 动作 细节
    初进函数(状态二) 现在栈拥有一个状态。假设ebp=a; esp=b-x;
    初始化 1. 保存栈底,2. 设置栈底,3. 修改栈顶,为本地变量分配空间 ebp=b-x; esp=b-x-y;值a在栈中。(状态三)
    执行函数体  
    收尾工作 1.恢复栈顶,2.恢复栈底 恢复到状态二。esp=b-x; ebp = a;(状态二)

    表格注:

    x是参数所占的空间

    y是局部变量所占的空间

    有点麻烦,要多想几次才能记得下来。不过相信汇编代码多看一些,见得多了自然也就烂熟于心了。

    练习:

    if(i == 0) goto Label1;

    C代码:

    对应的汇编代码:

    mov eax, [ebp – 4]   ;这里假设i是第一个局部变量,如果是第一个参数,那应该是[ebp + 8]

    cmp eax, 0x0

    jz Label1

    问题:

    为什么代码中第七行下面的一句push ecx的目的是make room for local? 如何做到的?

    答:注释错误。PUSH ECX的目的是为了保存ECX的值。

  • 相关阅读:
    基于ModBus-TCP/IT 台达PLC 通讯协议解析
    TNS-12541: TNS: 无监听程序 解决方案
    一个很好的ping端口的工具
    上位机(开发)
    无名
    网站部署
    cordova 开发
    mono 开发
    调用 浏览器 插件
    MacBook 配置
  • 原文地址:https://www.cnblogs.com/awpatp/p/1593764.html
Copyright © 2020-2023  润新知