【问题标题】:Correctly restoring callee-saved registers正确恢复被调用者保存的寄存器
【发布时间】:2021-02-15 14:54:13
【问题描述】:

我试图正确理解我在何处以及如何将被调用者保存的寄存器(如 ebx)推入/移出堆栈以恢复它们以供以后使用。

这段代码是否正确恢复了ebx 寄存器?

global main
extern printf

section .text:

print:
    mov     eax, 0x1
    add     eax, ebx

    push    eax
    push    message
    call    printf
    add     esp, 8
    ret

main:
    mov     ebx, 0x1
    push    ebx
    call    print
    pop     ebx
    ret

message db "result = %d", 10, 0       

我应该像这样使用后直接弹出ebx吗?:

global main
extern printf

section .text:

print:
    push    ebx
    mov     ebx, 0x1
    mov     eax, 0x1
    add     eax, ebx

    push    eax
    push    message
    call    printf
    add     esp, 8
    pop     ebx
    ret
main:
    call print
    ret

message db "result = %d", 10, 0       

【问题讨论】:

    标签: assembly scope x86 cpu-registers calling-convention


    【解决方案1】:

    两种方式都可以,但第二种方式更传统一些。

    规则是这样的:当编译器生成调用函数的代码时,它假定调用后 ebx 的内容与之前相同。在您的程序中,唯一适用的情况是调用main 的启动代码。对于您的代码的两个版本,ebxmain 返回时与输入时具有相同的值,所以一切都很好。

    如果您的程序中有一个名为print 的C 函数,那么第一个版本会很糟糕,而需要第二个版本。但是在第一个版本中,print 仅从您手动编码的 main 函数中调用,并且您知道 print 将破坏 ebx 并且您正在采取适当的措施来保存和恢复它,所以没关系。

    换句话说,在代码的第一个版本中,print 不符合标准 C 调用约定。但是由于您从未真正从 C 代码中调用它,因此这不一定是问题。在第二个版本中,它确实符合要求,这可能在美学上更好,并且维护起来可能更容易。

    【讨论】:

    • 如果以类似的方式使用,还需要哪些其他寄存器在汇编中使用pushed 和popped?只是eaxebx
    • @kdi_342:任何保留调用的寄存器都应该被同等对待。对于 32 位 x86 调用约定,这是除 EAX、ECX 和 EDX 之外的所有整数 reg。另见What are callee and caller saved registers?
    • 第一种方法实际上并不好:main 在保存/恢复它之前写入 EBX,这完全没有意义。 @kdi_342,您已经销毁了调用者的值,如果 print 遵循 ABI,则 call print 将保留该值。但它不遵循标准调用约定:它显然是使用 EBX 将 arg 传递给 print?整个事情非常奇怪,因为任何地方都没有ret 指令,所以两个版本都会循环,直到它们溢出堆栈并崩溃。
    • @Nate:你在想 AT&T 的语法吗? add eax, ebx 读取 EBX 而不写入它,所以 print 的第一个版本不会破坏 EBX。但它确实读了它。 IDK 对这段代码怎么说,因为它太奇怪了,但你的答案似乎与代码不一致。
    • 是的,你是对的,我没有仔细阅读。当我有更多时间时会尝试修复。
    猜你喜欢
    • 2019-08-18
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 2020-11-19
    • 2018-02-27
    相关资源
    最近更新 更多