【问题标题】:GCC inline assembly intel syntax "Error: invalid use of register"GCC 内联汇编 intel 语法“错误:无效使用寄存器”
【发布时间】:2021-05-20 13:03:10
【问题描述】:

我决定尝试在我的基本操作系统中使用 c 中的内联汇编(使用 intel 语法,因为我习惯使用 NASM)。我遇到了一些我无法解决的问题,我试着到处寻找,但没有任何效果。在尝试错误消息后显示的代码时,GCC 会显示以下消息:

main.c: Assembler messages:
main.c:8: Error: invalid use of register

代码是:

int main(){
    asm("mov ah, 0x00\n\t");
    asm("mov al, 0x13\n\t");
    asm("int 0x10\n\t");
    asm("mov bx, 0xA000\n\t");
    asm("mov es, bx\n\t");
    asm("mov di, 0\n\t");
    asm("mov byte [es:di], 0x0f\n\t"); // Problem line of error.
    while(1){
        asm("nop");
    }
    return 0;
}

有人可以帮我吗?

【问题讨论】:

  • GCC 是否支持 NASM 风格的 intel 语法?也许试试mov byte ptr es:[di], 0x0f。我假设你在命令行告诉 GCC 你想使用 intel 语法,因为我在你的代码中没有看到任何 .intel_syntax 指令。
  • 仅供参考,这与使用 GNU C 内联汇编的安全方法相差甚远,即使除了使用 NASM 语法。切勿在非naked 函数中使用基本 Asm(无约束/破坏者)。您必须始终告诉编译器您正在修改哪些寄存器(以及您正在读取/写入的内存,除非您使用"memory" clobber)。另外,这里不需要销毁BX;使用 AX 或 DI 作为临时保存所需的 ES 值。

标签: c gcc x86 inline-assembly intel-syntax


【解决方案1】:

对此有很多要提的。首先,您使用的是 Intel 语法,这很好(至少对于 GCC,clang 更难)。你可以在编译时使用-masm=intel,或者在你的asm模板中作为一个hack切换到.intel_syntax noprefix,然后在你的asm字符串末尾切换回.att_syntax,这样编译器的其余代码仍然可以工作。 (How to set gcc to use intel syntax permanently?)

接下来,你有很多 asm() 块。最好有一个,这样你就可以为整个块指定输入/输出/clobbers/goto。这很容易做到,因为 C 连接了相邻的字符串。

您的代码中包含 NASM 样式的 [es:di]。在 GAS 的 Intel 语法方言中,段名称位于方括号之外:es:[di]。我还注意到[di] 不合适,除非您使用的是 16 位架构;方括号中的东西必须是平台上指针的大小...

...但是你也在使用int 0x10,这是一个 BIOS 中断,所以我猜测 16 位实际上是你想要的。

话虽如此,您确实需要养成指定 clobbers 的习惯。即指定你的代码修改了哪些寄存器,以及是否修改了条件码寄存器和内存。编译器正在决定将哪些变量保存在寄存器等中,并且不知道您的汇编代码做了什么。你必须告诉它你正在修改,例如ax,所以它知道并可以保存/恢复它。

GCC 并不真正了解分段(它是一个假定平面内存模型的可移植编译器,而不是专门用于 x86-16),因此将 esds 不同的基数保留是不安全的。例如GCC 可能会选择使用rep stosd 来初始化结构或数组。在这里很安全,因为您在 asm 之后的代码很少。

这是我对你的代码的重写。

int main()
{
    asm volatile(
        ".intel_syntax noprefix\n\t"
        "mov ah, 0x00\n\t"
        "mov al, 0x13\n\t"
        "int 0x10\n\t"
        "mov bx, 0xA000\n\t"
        "mov es, bx\n\t"
        "mov di, 0x00\n\t"
        "mov byte ptr es:[di], 0x0f\n\t"
        ".att_syntax"                      // undo .intel_syntax
        : /* output operands */
        : /* input operands */
        : "ax","bx","di","cc", "memory" /* clobbers */
    );
    while(1){
        // asm("nop");  // unneeded: empty infinite loops are already legal in C.
    }
    return 0;
}

如果您的代码修改甚至读取您也通过 C 指针访问的内存,您还应该在 clobbers 行中包含 "memory"。请参阅 documentation for inline assembly for gccHow can I indicate that the memory *pointed* to by an inline ASM argument may be used?

volatile 在这里不是绝对必要的,因为没有输出操作数,asm 语句隐式为volatile。但最好包含volatile 以明确(对人类读者)存在可见的副作用(BIOS 调用和存储到视频内存)。所以如果你确实包含了一个输出操作数,它就不会被优化掉。

附录: 一般来说,使用-masm=intel进行编译,而不是在清单中使用.intel_syntax。这让编译器在替换操作数时使用正确的语法。

【讨论】:

  • 在 inline-asm 模板中使用 ".intel_syntax noprefix\n\t" 不是一个好主意。 (使用gcc -masm=intel 以便操作数正常工作,尤其是"m" 将扩展为[reg] 而不是(%reg)。)。但是如果你这样做了,你需要用".att_syntax"结束这个块,否则当它试图汇编编译器生成的asm的其余部分时,你会让汇编器处于英特尔语法模式(这仍然是AT&T语法,除非你使用-masm=intel 在这种情况下,.intel_syntax noprefix 是多余的,至少对于 GCC 而言。IDK 如何在 clang 内联 asm 中安全地使用 Intel-syntax)
  • 我没有提到的一个非常好的观点!同意。
  • 这是-masm=intel,而不是-mintel。我在您的回答中修复了这个问题并调整了其他一些内容(包括添加指向现有问答的链接,这些链接更详细地介绍了您提到的一些事情。)
猜你喜欢
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 2021-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-15
  • 1970-01-01
相关资源
最近更新 更多