画图解释一个汇编小例子

这是对应的C代码

int caller() {
    int temp1 = 125;
    int temp2 = 80;
    int sum = add(temp1, temp2);
    return sum;
}

int add(int x, int y) {
    return x + y;
}

这是对应的汇编代码:

caller:
push ebp
mov ebp, esp
sub esp, 24
mov [ebp-12], 125
mov [ebp-8], 80
mov eax, [ebp-8]
mov [esp+4], eax
mov eax, [ebp-12]
mov esp, eax
call add
mov [ebp-4], eax
mov eax,[ebp-4]
leave



add:
push ebp
mov ebp, esp
mov eax, [ebp+12]
mov edx, [ebp+8]
add eax, edx
leave
ret

下面开始解释:

第一句:push ebp。push指令是压栈的意思。esp和ebp分别是指向栈顶和栈底的寄存器。push压栈过程:

1.先++,即esp往下走一个单位,起到了扩容的作用。2.把元素放入栈中。即把ebp寄存器内部的值压栈。

这一步的作用是保存上一个函数栈帧的基地址,在该函数(caller)结束之后,可以通过这一个基地址找到上一个函数的函数栈帧基地址。

第一句代码对应图示:

初始状态:
在这里插入图片描述

push后的状态.esp++(++指的是加一个单位),ebp的地址入栈
在这里插入图片描述


第二句:mov ebp esp。mov是移动的指令。这里写的是intel x86的汇编指令,因此这句话的意思是把esp寄存器内的地址复制一份到ebp寄存器内。也就是说:此时ebp指向esp所指向的地址。也就是栈顶。

第二句代码的图示如下:

在这里插入图片描述


第三句:sub esp, 24.意思是把esp减去24个字节。由于用户栈是向下生长的。越下面地址越小,因此减去24就是向下移动。这里假设一个格子是4字节,因此也就是esp向下移动6个格子。

在这里插入图片描述


第四,五句:mov [ebp-12], 125。ebp向下三个单位被写入一个立即数125.这个125对应的c语言代码是那个temp1,在c语言代码里,越早定义的局部变量越靠下。mov [ebp-8], 80表示ebp向下2个单位被写入一个立即数80.也就是c语言代码里的temp2

第四,五句对应图示如下:

在这里插入图片描述


至此,c语言代码我们现在执行到了这一条语句int sum = add(temp1, temp2);这一条语句涉及到的知识点有函数栈帧的传参的切换。首先要进行的是函数栈帧的传参。要把temp1和temp2两个参数传给add这个函数栈帧。

第6,7,8,9句代码:

mov eax, [ebp-8]
mov [esp+4], eax
mov eax, [ebp-12]
mov esp, eax

首先我们要明确的是:函数传参时,参数应该存在函数栈帧的靠下的部分。

由于汇编当中不允许将内存里的东西mov到内存里。所有我们要借助一个寄存器eax来进行写入。因此这四句话的意思就是,我先把[ebp-8]里面的内容mov到eax里面,然后再将eax里面的内容mov到[esp+4]里面。后面两句同理。

这四句代码的图示如下:

在这里插入图片描述


第十句代码:call add。add是一个标识符。这句话就表示我现在要调用add函数了。当使用了call指令的时候,会将当前指令的地址入栈。(也就是PC程序计数器记录的指令地址,在intel x86里面PC程序计数器叫做IP寄存器。)入栈之后,就跳转到了add的函数栈帧里面了

第十句代码对应的图如下:

在这里插入图片描述


第11,12句代码:和之前同理,作用就是为当前函数开辟空间。(开辟函数栈帧)。这两句代码是每一个函数都需要的,因为它可以为函数栈帧分配空间。这两句话也可以合起来被称为enter指令


第13,14,15句代码:

mov eax, [ebp+12]
mov edx, [ebp+8]
add eax, edx

就是把两个参数temp1和temp2拿到寄存器里面相加。相加后放入到eax里面保存。

这里要说一下add函数栈帧是如何找到caller函数栈帧传入的temp1和temp2两个参数的。

在这里插入图片描述


第16句代码:leave。leave和enter一样也是一个缩写的指令。leave完整写出来应该是

mov ebp, esq  将esp指向ebp,其实就是收回函数栈帧的空间
pop ebp 将弹出的元素放到ebp里面。这里弹出的元素就是上一个函数栈帧的基地址。

leave把add的最上面那个栈内元素,也就是上一个函数栈帧的ebp地址获取后弹出了,现在ebp回到了上一个函数栈帧的ebp位置,也就是caller的ebp位置。

在这里插入图片描述


第17句代码 ret。

ret会把栈顶的IP寄存器所保存的指令地址获取并弹出。这样我们就可以找到我们原来执行到哪一条指令了。

我们将会回到call add这条代码处继续往下执行。

注意区分PC寄存器里的指令地址和ebp的基地址的区别。

基地址是函数栈帧栈底的地址,PC寄存器记录的是我们执行到了哪一条指令。

图示:我们可以发现栈顶(最底下)的ip寄存器已经被pop掉了。

在这里插入图片描述


第18, 19, 20句代码

mov [ebp-4], eax
mov eax,[ebp-4]
leave

刚刚add函数帮我计算的结果被保存在了eax寄存器里面。因此我们可以通过访问eax寄存器来得到这个结果,并把它放到sum这个局部变量里。至此,程序结束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值