在x86程序集中的过程中在哪里调用ret指令有关系吗 [英] Does it matter where the ret instruction is called in a procedure in x86 assembly

查看:62
本文介绍了在x86程序集中的过程中在哪里调用ret指令有关系吗的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在学习x86汇编.但是,在使用堆栈进行函数调用时,我仍然不清楚.我知道,调用指令将涉及将返回地址压入堆栈,然后将要调用的函数的地址加载到程序计数器中.ret指令会将这个地址加载回程序计数器.

I am currently learning x86 assembly. Something is not clear to me still however when using the stack for function calls. I understand that the call instruction will involve pushing the return address on the stack and then load the program counter with the address of the function to call. The ret instruction will load this address back to the program counter.

我的困惑是,在过程/函数中调用ret指令是否重要?它会始终找到存储在堆栈上的正确返回地址,还是堆栈指针当前必须指向返回地址的存储位置?如果是这种情况,我们难道不能只使用推入和弹出操作,而不是使用呼叫和退出操作吗?

My confusion is, does it matter when the ret instruction is called within the procedure/function? Will it always find the correct return address stored on the stack, or must the stack pointer be currently pointing to where the return address was stored? If that's the case, can't we just use push and pop instead of call and ret?

例如,下面的代码可能是进入函数的第一个代码,如果我们将不同的寄存器压入堆栈,则必须仅在以相反的顺序弹出寄存器后才调用ret指令,以便在弹出%ebp之后在指令中,堆栈指针将指向堆栈上返回地址的正确位置,还是无论调用它的位置,它仍能找到它?预先感谢

For example, the code below could be the first on entering the function , if we push different registers on the stack, must the ret instruction only be called after the registers are popped in the reverse order so that after the pop %ebp instruction , the stack pointer will point to the correct place on the stack where the return address is, or will it still find it regardless where it is called? Thanks in advance

push %ebp
mov %ebp, %esp
//push other registers

...
//pop other registers
mov %esp, %ebp
(could ret instruction go here for example and still pop the correct return address?)
pop %ebp
ret

推荐答案

CPU不知道什么是函数/等... ret 指令将从 esp 跳到那里.例如,您可以执行以下操作(以说明CPU对您如何结构化地组织源代码不感兴趣):

The CPU has no idea what is function/etc... The ret instruction will fetch value from memory pointed to by esp a jump there. For example you can do things like (to illustrate the CPU is not interested into how you structurally organize your source code):

   ; slow alternative to "jmp continue_there_address"
   push continue_there_address
   ret
continue_there_address:
   ...

只要 esp 指向 ret 时的返回地址,您也无需从堆栈中还原寄存器(甚至无需将它们还原到原始寄存器中).code>被执行,将被使用:

Also you don't need to restore the registers from stack, (not even restore them to the original registers), as long as esp points to the return address when ret is executed, it will be used:

    call SomeFunction
    ...

SomeFunction:
    push eax
    push ebx
    push ecx
    add  esp,8   ; forget about last 2 push
    pop  ecx     ; ecx = original eax
    ret          ; returns back after call

如果您的函数应该可以与代码的其他部分进行互操作,则您可能仍希望按照编程平台的调用约定的要求存储/恢复寄存器,因此从调用者的角度出发,您将不​​会进行修改一些应该保留的寄存器值,等等...但是这些都不影响CPU和执行指令 ret ,CPU只是从堆栈中加载值( [esp] ),然后跳到那里.

If your function should be interoperable from other parts of code, you may still want to store/restore the registers as required by the calling convention of the platform you are programming for, so from the caller point of view you will not modify some register value which should be preserved, etc... but none of that bothers CPU and executing instruction ret, the CPU just loads value from stack ([esp]), and jumps there.

此外,当返回地址存储到堆栈中时,它与以任何方式推送到堆栈中的其他值没有什么不同,它们都是写在内存中的值,因此 ret 没有机会为了以某种方式在堆栈中找到返回地址"并跳过值",对于CPU而言,内存中的值看起来是相同的,每个32位值就是32位值.无论是通过 call push mov 还是其他方式存储的,该信息(值的来源)都无关紧要存储,只有值.

Also when the return address is stored to stack, it does not differ from other values pushed to stack in any way, all of them are just values written in memory, so the ret has no chance to somehow find "return address" in stack and skip "values", for CPU the values in memory look the same, each 32 bit value is that, 32 bit value. Whether it was stored by call, push, mov, or something else, doesn't matter, that information (origin of value) is not stored, only value.

如果是这种情况,我们难道不能只使用推和弹出功能而不是调用和重拨功能吗?

If that's the case, can't we just use push and pop instead of call and ret?

您当然可以将首选的返回地址 push 放入堆栈中(我的第一个示例).但是您不能执行 pop eip ,没有此类说明.实际上,这就是 ret 的作用,因此 pop eip 实际上是同一件事,但是没有x86汇编程序员使用这种助记符,并且操作码与其他 pop 指令.您当然可以 将返回地址弹出到不同的寄存器中,例如 eax ,然后执行 jmp eax 具有较慢的 ret 替代方法(也可以修改 eax ).

You can certainly push preferred return address into stack (my first example). But you can't do pop eip, there's no such instruction. Actually that's what ret does, so pop eip is effectively the same thing, but no x86 assembly programmer use such mnemonics, and the opcode differs from other pop instructions. You can of course pop the return address into different register, like eax, and then do jmp eax, to have slow ret alternative (modifying also eax).

也就是说,复杂的现代x86 CPU确实会跟踪 call/ret 配对(以预测下一个 ret 将返回的位置,因此它可以预取代码)快速前进),因此,如果您使用这些非标准方法中的一种,CPU将在某个时候意识到其返回地址的预测系统偏离了真实状态,因此必须丢弃所有这些缓存/预加载并重新-从真实的 eip 值中获取所有内容,因此您可能会因混淆而付出性能损失.

That said, the complex modern x86 CPUs do keep some track of call/ret pairings (to predict where the next ret will return, so it can prefetch the code ahead quickly), so if you will use one of those alternative non-standard ways, at some point the CPU will realize it's prediction system for return address is off the real state, and it will have to drop all those caches/preloads and re-fetch everything from real eip value, so you may pay performance penalty for confusing it.

这篇关于在x86程序集中的过程中在哪里调用ret指令有关系吗的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆