【问题标题】:How to preserve argument/parameter registers under x86-64?如何在 x86-64 下保留参数/参数寄存器?
【发布时间】:2012-05-25 22:13:15
【问题描述】:

System V AMD64 ABI call convention 是这样规定的:

注册%rbp、%rbx和%r12到%r15“属于”呼叫 函数和被调用函数需要保存它们的值。 换句话说,被调用的函数必须保留这些寄存器' 调用者的值。剩余的寄存器“属于”被调用 功能。如果调用函数想要保留这样的寄存器 跨函数调用的值,它必须将值保存在其本地 堆栈帧。

例如,给定以下代码:

void f1(const int i, const double j, const char * k)
{
    printf("i = %d\n", i);
    printf("j = %g\n", j);
    printf("k = %s\n", k);
}

程序集表示为:

f1():
  4004f4:   55                      push   %rbp
  4004f5:   48 89 e5                mov    %rsp,%rbp
  4004f8:   48 83 ec 20             sub    $0x20,%rsp
  4004fc:   89 7d fc                mov    %edi,-0x4(%rbp)
  4004ff:   f2 0f 11 45 f0          movsd  %xmm0,-0x10(%rbp)
  400504:   48 89 75 e8             mov    %rsi,-0x18(%rbp)
  400508:   b8 70 06 40 00          mov    $0x400670,%eax
  40050d:   8b 55 fc                mov    -0x4(%rbp),%edx
  400510:   89 d6                   mov    %edx,%esi
  400512:   48 89 c7                mov    %rax,%rdi
  400515:   b8 00 00 00 00          mov    $0x0,%eax
  40051a:   e8 d1 fe ff ff          callq  4003f0 <printf@plt>
  40051f:   b8 78 06 40 00          mov    $0x400678,%eax
  400524:   f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0
  400529:   48 89 c7                mov    %rax,%rdi
  40052c:   b8 01 00 00 00          mov    $0x1,%eax
  400531:   e8 ba fe ff ff          callq  4003f0 <printf@plt>
  400536:   b8 80 06 40 00          mov    $0x400680,%eax
  40053b:   48 8b 55 e8             mov    -0x18(%rbp),%rdx
  40053f:   48 89 d6                mov    %rdx,%rsi
  400542:   48 89 c7                mov    %rax,%rdi
  400545:   b8 00 00 00 00          mov    $0x0,%eax
  40054a:   e8 a1 fe ff ff          callq  4003f0 <printf@plt>
  40054f:   c9                      leaveq 
  400550:   c3                      retq  

在本例中,参数已传入%edi、%xmm0 和%rsi。调用约定规定这些寄存器“属于”被调用函数,这意味着f1 没有义务保留它们的值。事实上,%edi、%xmm0 和 %rsi 都被丢弃在以下几行:

400510: 89 d6                   mov    %edx,%esi
400512: 48 89 c7                mov    %rax,%rdi
..
400524: f2 0f 10 45 f0          movsd  -0x10(%rbp),%xmm0

我想保留所有参数寄存器。文档说明该值可以保存在我尝试过的本地堆栈中,如下所示:

void f1(const int i, const double j, const char * k)
{
    uint32_t edi;
    __asm ("movl %%edi, %0;" : "=r" ( edi ));

    printf("i = %d\n", i);
    printf("j = %g\n", j);
    printf("k = %s\n", k);

    __asm ("movl %0, %%edi;" : "=d"( edi ));
}

这会生成以下内容:

f1():
  4004f4:   55                      push   %rbp
  4004f5:   48 89 e5                mov    %rsp,%rbp
  4004f8:   53                      push   %rbx
  4004f9:   48 83 ec 38             sub    $0x38,%rsp
  4004fd:   89 7d dc                mov    %edi,-0x24(%rbp)
  400500:   f2 0f 11 45 d0          movsd  %xmm0,-0x30(%rbp)
  400505:   48 89 75 c8             mov    %rsi,-0x38(%rbp)
  400509:   89 fb                   mov    %edi,%ebx
  40050b:   89 5d ec                mov    %ebx,-0x14(%rbp)
  40050e:   b8 80 06 40 00          mov    $0x400680,%eax
  400513:   8b 55 dc                mov    -0x24(%rbp),%edx
  400516:   89 d6                   mov    %edx,%esi
  400518:   48 89 c7                mov    %rax,%rdi
  40051b:   b8 00 00 00 00          mov    $0x0,%eax
  400520:   e8 cb fe ff ff          callq  4003f0 <printf@plt>
  400525:   b8 88 06 40 00          mov    $0x400688,%eax
  40052a:   f2 0f 10 45 d0          movsd  -0x30(%rbp),%xmm0
  40052f:   48 89 c7                mov    %rax,%rdi
  400532:   b8 01 00 00 00          mov    $0x1,%eax
  400537:   e8 b4 fe ff ff          callq  4003f0 <printf@plt>
  40053c:   b8 90 06 40 00          mov    $0x400690,%eax
  400541:   48 8b 55 c8             mov    -0x38(%rbp),%rdx
  400545:   48 89 d6                mov    %rdx,%rsi
  400548:   48 89 c7                mov    %rax,%rdi
  40054b:   b8 00 00 00 00          mov    $0x0,%eax
  400550:   e8 9b fe ff ff          callq  4003f0 <printf@plt>
  400555:   89 d7                   mov    %edx,%edi
  400557:   89 d3                   mov    %edx,%ebx
  400559:   89 5d ec                mov    %ebx,-0x14(%rbp)
  40055c:   48 83 c4 38             add    $0x38,%rsp
  400560:   5b                      pop    %rbx
  400561:   5d                      pop    %rbp
  400562:   c3                      retq   

这似乎不会恢复%edi 的值。保存所有参数/参数寄存器的正确方法是什么?

【问题讨论】:

  • 您是否检查过生成的程序集并在调试器中验证是否恢复了正确的值?内联汇编可能是一个讨厌的野兽。
  • @NiklasB.:我确实看过反汇编,但发布起来非常冗长。这里是:pastebin.com/HUexwViQ 看了之后我不确定是哪里出了问题。执行第一个printf 后发生段错误。
  • 抱歉,没有意识到这一点。我不认为你可以像你想做的那样玩筹码。您可能想要做的是声明局部变量或局部缓冲区并使用它们来保存寄存器值。
  • @NiklasB.:我刚刚添加了我曾经尝试这样做的扩展 asm。
  • 我没有看到它恢复了价值。也许漏洞优化器正在剥离您的还原,因为它知道 RDI 可以被破坏?

标签: c gcc assembly 64-bit


【解决方案1】:

你不能像这样混合 C 和 asm。特别是,在内联汇编中执行孤立的 push 或 pop 指令会严重破坏事情;任何给定的内联汇编块在堆栈指针上的净偏移量必须为 0。

确实,ABI 文档与内联汇编的使用无关。为此,您需要遵循 GCC 内联 asm 合同,记录您用于输入和输出的寄存器以及 clobber 列表中的内容。如果您在汇编程序中编写整个函数(在 .s 文件而不是 .c 文件中),则 ABI 是相关的。

【讨论】:

  • 所以这是在扩展的 asm 代码中处理的(我猜这就是它没有段错误的原因)。你知道为什么在那种情况下%edi 没有恢复吗?我链接到 ABI 文档以提供我正在尝试做的事情的上下文,以防有更好的方法我不知道。
  • ABI 管理调用者和被调用者之间的函数调用合约。内联 ASM 中没有函数调用。
  • 现在,如果你想从你的 asm 块中调用一个用 C 语言编写的外部函数,该函数经过编译以匹配 ABI 合约,那么你必须遵循 ABI 规则来电者...
  • 我希望 C 函数保留参数寄存器。我只是使用 ASM,因为我认为这可能是实现这一目标的最佳方式。也许问题中有一些不清楚的地方,但我们似乎在不同的页面上。有什么我可以澄清的可以对此有所帮助的吗?
  • 不,没有办法。当您在函数体中获得任何代码(asm 或 C)时,编译器生成的任意代码(通常统称为 function prologue)已经运行并可能破坏任何寄存器未由 ABI 指定为被调用者保存。如果您需要保存额外的寄存器 - 这仅在从您自己的 asm 调用该函数时才有必要或有用! - 然后你需要在 asm 中编写一个包装函数,在调用你的函数之前将寄存器保存在堆栈上。
猜你喜欢
  • 2011-11-04
  • 2019-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-25
  • 2014-01-20
  • 2023-04-07
相关资源
最近更新 更多