【问题标题】:How do I get GCC to put a char in ah/bh/ch/dh?如何让 GCC 将字符放入 ah/bh/ch/dh?
【发布时间】:2020-08-25 13:32:26
【问题描述】:

假设我有一些内联程序集需要 ahbhchdh 中的特定 char 值。我怎样才能告诉 GCC 把它放在那里?我没有看到相关的约束,但是 GCC 手册说“如果您必须使用特定的寄存器,但您的机器约束没有提供足够的控制来选择您想要的特定寄存器,本地寄存器变量可能会提供一个解决方案",所以我试了一下:

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

但它没有用。它把它放在al 中:

        movb    4(%esp), %al
        # my value ended up in %al

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

有趣的是,当我使用 Clang 而不是 GCC(无论是使用 aQ 还是 r)进行编译时,我确实得到了想要的结果:

        movb    4(%esp), %ah
        # my value ended up in %ah

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

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

        movl    %edi, %eax
        # my value ended up in %al

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

        movb    %dil, -1(%rsp)
        movb    -1(%rsp), %al
        movb    %al, -2(%rsp)
        movb    -2(%rsp), %ah
        # my value ended up in %ah

这是我应该报告的 GCC 中的错误,还是不应该工作而只能在 Clang 中偶然工作的东西?如果是后者,有没有办法做我想做的事,还是我必须自己在程序集中的其他地方接受moving?

32-bit Godbolt link64-bit Godbolt link.

【问题讨论】:

  • 不应该是"r"(y)吗?
  • 更糟糕的是,如果你同时拥有ahal ... gcc 会默默地忽略其中一个。
  • 如果你在EAX中要求(int)y<<8,你可以在模板中使用%h0%b0扩展为AH和AL。 (如果 GCC 选择了 EBX,则为 BH 和 BL)。 gcc.gnu.org/onlinedocs/gcc/…"Q" 约束确保 %h0 修饰符可以通过选择 E[ABCD]X 之一来工作。这就是为什么存在 Q 约束,而不是让 GCC 自己真正pick AH。我希望它与register char foo asm("ah"); 兼容,但这是另一回事。
  • re: clang: 启用优化我假设它试图发出不可编码的mov %dil, %ah。 (DIL 需要 REX;AH 只能在没有 REX 的情况下进行编码)。任何 ICE 都可以被认为是一个错误,但是当 register asm 变量引发它,或者使 clang 像 GCC 一样工作并选择 AL 而不是 AH 时,错误修复可能只是一个更好的警告!或者可能发出 rorx $24, %edx, %eax 以在一条 BMI2 指令中将 DIL 转换为 AH,同时避免 REX 问题。
  • 这完全偏离了另一个方向,但我很好奇是什么迫使你在 AH 中加入一些东西。我可以想到一个用例,但这与 16 位实模式有关。尽管由于错误/缺陷,您无法直接加载 AH 并限制 GCC,但您始终可以在内联程序集中将某些内容移动到 AH 中。或者像彼得提到的那样简单地使用 EAX 并存储(int)y<<8。你问的是可行的,但不是你想要的直接(有效地)。我不得不处理这个问题的地方是使用内联汇编来进行 BIOS 调用。

标签: c gcc assembly x86 inline-assembly


【解决方案1】:

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

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

生产

f:
        xorl    %eax, %eax
        mov  4(%esp), %ah
        ret

我无法摆脱清除eaxxor。我的猜测是代码生成器将“%h1”解释为设置了 8 位的 32 位字,而不是字符寄存器引用。例如,这个:

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

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

【讨论】:

  • 你的第二个 C sn-p 看起来像直接的未定义行为,因为它在没有设置的情况下返回 a
  • 您的示例不太有意义,因为您将两个寄存器都作为输入操作数。但无论如何,寄存器修饰符会导致生成的程序集引用%ah,但编译器仍然不知道,而是会尝试将输入值加载到%al。您在示例中看不到这一点,因为您没有初始化 a。但如果你确实初始化它,就像在this example on godbolt 中一样,你会发现生成的代码不是你想要的。
  • 换句话说,我认为%h1 对代码生成器是完全不透明的。编译器生成周围的代码,就好像您刚刚编写了普通的%1,然后只需更改程序集中的寄存器名称。所以这确实不能解决问题。
  • 我认为寄存器修饰符功能适用于当您将值加载到 32 位寄存器中但您的程序集只想对其部分进行操作的情况。例如unsigned a=..., x; asm("movb $0x5a, %h0" : "=Q" (x) : "0" (a)) 将是在一条指令中实现x = a & 0xffff00ff | 0x5a00 的有效方法。
  • @NateEldredge mov 指令集a.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-04
  • 2022-10-14
  • 2021-02-13
  • 1970-01-01
相关资源
最近更新 更多