为什么我不能在 C 中获取 asm 寄存器的值?

Why can't I get the value of asm registers in C?

我正在尝试获取汇编寄存器的值 rdirsirdxrcxr8,但是我'我得到了错误的值,所以我不知道我正在做的是获取这些值还是告诉编译器写入这些寄存器,如果是这样的话我怎么能实现我想要做的事情(把C 变量中汇编寄存器的值)?

此代码编译时(gcc -S test.c

#include <stdio.h>

void    beautiful_function(int a, int b, int c, int d, int e) {
    register long   rdi asm("rdi");
    register long   rsi asm("rsi");
    register long   rdx asm("rdx");
    register long   rcx asm("rcx");
    register long   r8 asm("r8");

    const long      save_rdi = rdi;
    const long      save_rsi = rsi;
    const long      save_rdx = rdx;
    const long      save_rcx = rcx;
    const long      save_r8 = r8;
    printf("%ld\n%ld\n%ld\n%ld\n%ld\n", save_rdi, save_rsi, save_rdx, save_rcx, save_r8);
}

int main(void) {
    beautiful_function(1, 2, 3, 4, 5);
}

它输出以下汇编代码(在函数调用之前):

    movl    , %edi
    movl    , %esi
    movl    , %edx
    movl    , %ecx
    movl    , %r8d
    callq   _beautiful_function

当我编译并执行它时输出:

0
0
4294967296
140732705630496
140732705630520
(some undefined values)

我做错了什么?我该怎么做?

您的代码无效,因为 Specifying Registers for Local Variables 明确告诉您不要做您所做的事情:

The only supported use for this feature is to specify registers for input and output operands when calling Extended asm (see Extended Asm).

Other than when invoking the Extended asm, the contents of the specified register are not guaranteed. For this reason, the following uses are explicitly not supported. If they appear to work, it is only happenstance, and may stop working as intended due to (seemingly) unrelated changes in surrounding code, or even minor changes in the optimization of a future version of gcc:

  • Passing parameters to or from Basic asm
  • Passing parameters to or from Extended asm without using input or output operands.
  • Passing parameters to or from routines written in assembler (or other languages) using non-standard calling conventions.

要将寄存器的值放入变量中,可以使用Extended asm,像这样:

long rdi, rsi, rdx, rcx;
register long r8 asm("r8");
asm("" : "=D"(rdi), "=S"(rsi), "=d"(rdx), "=c"(rcx), "=r"(r8));

但请注意,即使这样也可能无法满足您的要求:编译器有权在其他地方复制函数的参数,并在您的扩展 asm 运行之前将寄存器重新用于不同的东西,甚至不这样做如果您从未通过普通 C 变量读取参数,则完全传递参数。 (事实上​​ ,即使启用优化后我发布的内容也不起作用。)如果你想做你正在做的事情,你应该强烈考虑只在汇编中编写你的整个函数而不是在 C 函数中内联汇编。

即使您有一个有效的方法来做到这一点(这不是),它可能 只在函数的顶部才有意义 它不是内联的。所以你可能需要 __attribute__((noinline, noclone))。 (noclone 是一个 GCC 属性,clang 会警告无法识别;这意味着不要使用较少的实际参数制作函数的替代版本,在其中一些是可以传播到克隆中的已知常量的情况下调用.)

register ... asm local vars 不能保证做任何事情 除了 当用作扩展 Asm 语句的操作数时。如果未初始化,GCC 有时仍会读取命名寄存器,但 clang 不会。 (看起来你在 Mac,其中 gcc 命令实际上是 clang,因为很多构建脚本使用 gcc 而不是 cc。)

因此,即使没有优化,beautiful_function 的独立非内联版本在读取 [=20] 中的 rdi C 变量时也只是读取未初始化的堆栈 space =]. (GCC 确实碰巧在这里做你想做的,即使在 -Os - 优化但选择不内联你的函数。参见 clang and GCC (targeting Linux) on Godbolt,带有 asm + 程序输出。)。


使用 asm 语句让 register asm 做某事

(这会按照你说的去做(读取寄存器),但是由于其他优化,当调用者可以看到定义时,仍然不会产生 1 2 3 4 5 with clang。只有实际的 GCC。那里可能是禁用某些相关 IPA / IPO 优化的 clang 选项,但我没有找到。)

您可以使用带有空模板字符串的 asm volatile() 语句来告诉编译器这些寄存器中的值现在是那些 C 变量的值。 (register ... asm 声明强制它为正确的变量选择正确的寄存器)

#include <stdlib.h> 
#include <stdio.h>

__attribute__((noinline,noclone))
void    beautiful_function(int a, int b, int c, int d, int e) {
    register long   rdi asm("rdi");
    register long   rsi asm("rsi");
    register long   rdx asm("rdx");
    register long   rcx asm("rcx");
    register long   r8 asm("r8");

    // "activate" the register-asm locals:
    // associate register values with C vars here, at this point
   asm volatile("nop  # asm statement here"        // can be empty, nop is just because Godbolt filters asm comments
       : "=r"(rdi), "=r"(rsi), "=r"(rdx), "=r"(rcx), "=r"(r8) );

    const long      save_rdi = rdi;
    const long      save_rsi = rsi;
    const long      save_rdx = rdx;
    const long      save_rcx = rcx;
    const long      save_r8 = r8;
    printf("%ld\n%ld\n%ld\n%ld\n%ld\n", save_rdi, save_rsi, save_rdx, save_rcx, save_r8);
}

int main(void) {
    beautiful_function(1, 2, 3, 4, 5);
}

这使得您的 beautiful_function 中的 asm 确实捕获了寄存器的传入值。 (它不内联,并且编译器恰好在踩到任何这些寄存器的 asm 语句之前没有使用任何指令。后者通常不能保证。)

On Godbolt with clang -O3 and gcc -O3

gcc -O3 确实有效,打印出您期望的内容。 clang 仍然打印垃圾,因为 调用者看到 args 未被使用,并决定不设置这些寄存器 。 (如果您对调用者隐藏了定义,例如在没有 LTO 的另一个文件中,则不会发生这种情况。)

(使用 GCC,noninline,noclone 属性足以禁用此过程间优化,但使用 clang 则不行。甚至使用 -fPIC 编译也不可能。我想这个想法是符号插入提供确实使用其参数的 beautiful_function 的替代定义将违反 C 中的单一定义规则。因此,如果 clang 可以看到函数的定义,它会假定函数是这样工作的,即使它不是允许实际内联它。)

有叮当声:

main:
        pushq   %rax          # align the stack
     # arg-passing optimized away
        callq   beautiful_function@PLT
    # indirect through the PLT because I compiled for Linux with -fPIC, 
    # and the function isn't "static"
        xorl    %eax, %eax
        popq    %rcx
        retq

但是 beautiful_function 的实际定义完全符合您的要求:

# clang -O3
beautiful_function:
        pushq   %r14
        pushq   %rbx
        nop     # asm statement here
        movq    %rdi, %r9             # copying all 5 register outputs to different regs
        movq    %rsi, %r10
        movq    %rdx, %r11
        movq    %rcx, %rbx
        movq    %r8, %r14
        leaq    .L.str(%rip), %rdi
        xorl    %eax, %eax
        movq    %r9, %rsi                # then copying them to printf args
        movq    %r10, %rdx
        movq    %r11, %rcx
        movq    %rbx, %r8
        movq    %r14, %r9
        popq    %rbx
        popq    %r14
        jmp     printf@PLT              # TAILCALL

GCC 浪费更少的指令,例如从 movq %r8, %r9 开始将您的 r8 C var 作为第 6 个 arg 移动到 printf。然后 movq %rcx, %r8 设置第 5 个 arg,在读取所有输出寄存器之前覆盖其中一个输出寄存器。一些 clang 过于谨慎了。但是,clang 仍然 push/pop %r12 围绕 asm 语句;我不明白为什么。它以尾调用 printf 结束,所以它不是为了对齐。


相关:

  • How to specify a specific register to assign the result of a C expression in inline assembly in GCC? - 相反的问题:在特定点在特定寄存器中具体化一个C变量值。
  • Reading a register value into a C variable - 以前的规范问答,它使用现在不受支持的 register ... asm("regname") 方法,就像您尝试的那样。或者使用 register-asm global 变量,这会影响所有代码的效率,否则不会影响它。
  • 忘记了I'd answered那个Q&A,跟这个基本一样的点。还有其他一些要点,例如这不适用于堆栈指针等寄存器。