如何让 GCC 在 ah/bh/ch/dh 中放置一个字符?

Jos*_*ica 11 c x86 assembly gcc inline-assembly

假设我有一些内联汇编,需要一个特定char的值ahbhch,或dh。我怎么能告诉 GCC 把它放在那里?我没有看到相关的约束来做到这一点,但 GCC 手册说“如果你必须使用特定的寄存器,但你的机器约束没有提供足够的控制来选择你想要的特定寄存器,局部寄存器变量可能会提供一个解决方案",所以我试过:

void f(char x) {
    register char y __asm__("ah") = x;
    __asm__ __volatile__(
        "# my value ended up in %0" :: "a"(y)
    );
}
Run Code Online (Sandbox Code Playgroud)

但它没有用。它al改为:

        movb    4(%esp), %al
        # my value ended up in %al
Run Code Online (Sandbox Code Playgroud)

特定于 x86 的Q约束看起来也很接近我想要的,所以我尝试用它代替a,但结果相同。我还尝试使用更通用的r.

有趣的是,当我编译铿锵而不是GCC(是否带aQr),然后我得到了想要的结果:

        movb    4(%esp), %ah
        # my value ended up in %ah
Run Code Online (Sandbox Code Playgroud)

我还尝试使用bhchdh代替ah,它们的每种组合都会产生类似的结果。

我还尝试编译为 64 位而不是 32 位。在那里,GCC 仍然做着基本相同的错误:

        movl    %edi, %eax
        # my value ended up in %al
Run Code Online (Sandbox Code Playgroud)

Cannot encode high byte register in REX-prefixed instruction除非我关闭优化(我打开了LLVM 错误 #45865),否则Clang 完全无法编译,在这种情况下,它最终确实在正确的位置获得了值:

        movb    %dil, -1(%rsp)
        movb    -1(%rsp), %al
        movb    %al, -2(%rsp)
        movb    -2(%rsp), %ah
        # my value ended up in %ah
Run Code Online (Sandbox Code Playgroud)

这是我应该报告的 GCC 中的错误,还是这不应该起作用并且仅在 Clang 中偶然起作用?如果是后者,有没有办法做我想做的事,还是我必须mov自己从大会内部的其他地方解决它?

32 位 Godbolt 链接64 位 Godbolt 链接

Gen*_*ene 1

显然,约束不允许选择嵌套寄存器,但您可以h向指令引用添加修饰符。输入操作数的文档中提到了这一点。例如,

void f(char x) {
    char a;
    __asm__ __volatile__(
        "mov  %0, %h1" :: "X"(x), "a"(a)
    );
}
Run Code Online (Sandbox Code Playgroud)

产生

f:
        xorl    %eax, %eax
        mov  4(%esp), %ah
        ret
Run Code Online (Sandbox Code Playgroud)

我一直没能摆脱那个xor清除的东西eax。我的猜测是代码生成器将“%h1”解释为具有 8 位集的 32 位字,而不是字符寄存器引用。例如,这个:

char f(char x) {
    char a;
    __asm__ __volatile__(
        "movb  %0, %h1" :: "X"(x), "a"(a)
    );
    return a;
}
Run Code Online (Sandbox Code Playgroud)

...编译为相同的代码,即使它返回\0,不是很直观。

  • 你的例子不太有意义,因为你有两个寄存器作为输入操作数。但无论如何,寄存器修饰符会导致生成的程序集引用“%ah”,但编译器仍然不知道这一点,并且会尝试将输入值加载到“%al”中。您在示例中看不到这一点,因为您没有初始化“a”。但如果你确实初始化它,就像 [godbolt 上的这个示例](https://godbolt.org/) 中那样,你会发现生成的代码不是你想要的。 (2认同)
  • 我认为寄存器修饰符功能适用于当您将一个值加载到 32 位寄存器中但您的程序集只想对其一部分进行操作的情况。例如`unsigned a=..., x; asm("movb $0x5a, %h0" : "=Q" (x) : "0" (a))` 将是实现 `x = a & 0xffff00ff | 的有效方法 0x5a00` 在一条指令中。 (2认同)