【问题标题】:ASM x86 Function call : best method to save EBP and common registers (EAX, EBX, ...)ASM x86 函数调用:保存 EBP 和通用寄存器(EAX、EBX、...)的最佳方法
【发布时间】:2014-07-06 18:45:48
【问题描述】:

我想知道除了 EBP 之外保存通用寄存器(EAX、EBX 等)的最佳方法是什么。 这是我的示例代码。它只是调用我的 write 函数,在参数中传递字符串地址和长度(参数放在堆栈中)。 在 write 函数中,我保存 EBP,给它新的值,取回参数并调用 write syscall。

.section .data

    ask_number_str:
        .ascii "Hello world"
    anstr_end:
        .set ANSTR_SIZE, anstr_end - ask_number_str


.section .bss

.section .text

    .globl main

    main:
        movl $42, %eax
        movl $123, %ebx
        movl $456, %ecx
        movl $789, %edx
        pushl $ANSTR_SIZE
        pushl $ask_number_str
        call write
        add $8, %esp

    exit:
        movl $1, %eax
        movl $0, %ebx
        int $0x80

    write:
        pushl %ebp
        movl %esp, %ebp        
        movl $4, %eax
        movl $1, %ebx
        movl 8(%ebp), %ecx
        movl 12(%ebp), %edx
        int $0x80
        popl %ebp
        ret

我想在函数调用时添加 EAX、EBX、ECX 和 EDX 的保存。我看到了三种保存常用寄存器的方法:

  • 首先,将它们保存在 write 函数中,在“mov %esp, %ebp”之前(在为 ebp 分配新地址之前)。 => 问题:ESP,所以 EBP 会在堆栈中指向公共寄存器值之后,所以我必须深入堆栈才能找到参数(我必须通过 EAX、EBX、ECX 的值,找到参数之前的EDX)

  • 其次,将它们保存在写入函数中,在“mov %esp, %ebp”之后。 => 问题:ESP,因此 EBP 将在堆栈中指向公共寄存器值之前,所以我将不得不在堆栈中更深入地使用局部变量(如果我保存 EAX,我将必须传递 4 个值, EBX、ECX 和 EDX,以获取我的局部变量的起始地址)。

  • 第三,将它们保存在调用函数(这里是主函数)中,在函数调用之前(以及在参数推送之前)。栈没问题,但是我觉得这不是很优雅,每次调用都需要加8行(调用前4个push,后4个pop)。

那么,对你来说最好的方法是什么?当然是另一种我没有想到的方法:)

感谢您的帮助!

【问题讨论】:

    标签: assembly x86


    【解决方案1】:

    如果您不确定收银机的情况,以下这些说明可以轻松解决问题。

    PUSHA/PUSHAD -- Push all General Registers
    POPA/POPAD -- Pop all General Registers

    这些指令按一定顺序推送和弹出通用寄存器和 SI/ESI、DI/EDI 寄存器。

    PUSHA/PUSHAD 指令的顺序如下。

    Opcode  Instruction  Clocks   Description
    
    60      PUSHA        18       Push AX, CX, DX, BX, original SP, BP, SI, and DI
    60      PUSHAD       18       Push EAX, ECX, EDX, EBX, original ESP, EBP ESI, and EDI
    

    POPA/POPAD 指令的顺序如下。 (倒序)

    Opcode   Instruction   Clocks   Description
    
    61       POPA          24       Pop DI, SI, BP, SP, BX, DX, CX, and AX
    61       POPAD         24       Pop EDI, ESI, EBP, ESP(***),EBX, EDX, ECX, and EAX
    

    *** ESP 值被丢弃而不是加载到 ESP 中。

    【讨论】:

    • 不幸的是,AMD 从 x86_64 中删除了 PUSHA。可能是因为每次调用都有很多未使用的寄存器。
    • 确实存在 PUSHA,但对于“在函数中保存/恢复寄存器的最佳方式”这个问题,这是一个糟糕的答案。它非常慢,并且由于它会覆盖除 ESP 之外的所有 GP 寄存器,因此您必须在运行之前将所需的返回值存储到 EAX 的恢复槽中。
    【解决方案2】:

    如果您决定使用帧指针,通常的做法是在设置ebp 后保存它们。您保存的寄存器与任何局部变量没有区别,您甚至可以使用mov 指令而不是push/pop 来访问它们,如果您愿意,可以将它们放在您的其他变量下不想使用更大的偏移量。

    请注意,通常不需要帧指针,因为您可以相对于esp 进行寻址。此外,常见的调用约定通常允许修改 eaxecxedx,因此如果您遵守这些约定,则只需保存 ebx

    【讨论】:

    • 不使用 ebp 会使程序难以调试。并且使用 esp 作为指向局部变量和参数的指针会在函数使用堆栈时引入难以检测的错误,因为 esp 寄存器已更改。
    • 省略 ebp 不会对调试产生太大影响,如果有的话。仅使用 esp 是性能关键代码中的常见做法(这很可能是您使用 asm 的原因),甚至编译器也会这样做。通常esp 在函数体中不会改变,所以它和ebp 一样稳定。我怀疑它是难以检测错误的任何重要来源,在编写 asm 时你还有很多其他的 :) x86 没有很多寄存器,使用 ebp 作为另一个 GPR 可能比作为帧指针更有益。
    猜你喜欢
    • 2013-05-21
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 2014-01-14
    • 2014-11-26
    • 1970-01-01
    • 2023-03-25
    • 2012-01-11
    相关资源
    最近更新 更多