【问题标题】:GCC Inline assembly error: invalid instruction suffix for `lidt'GCC 内联汇编错误:“lidt”的指令后缀无效
【发布时间】:2019-10-18 20:50:47
【问题描述】:

我正在尝试从 C 调用一些汇编代码。我最近将程序从 x86 切换到 x86-64 并且在它工作之前。我有这个代码:

__asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));

其中&idtreg 是对结构的引用。用 GCC 编译它会给我这个错误:

“lidt”的指令后缀无效

当我添加一个$ 令牌时:

__asm__ __volatile__("lidtl $(%0)" : : "r" (&idt_reg));

我收到此错误:

非法立即寄存器操作数(%rax)

为什么会出现这个问题,我该如何解决?

【问题讨论】:

  • 64位模式不应该是lidtq吗?

标签: gcc assembly x86-64 inline-assembly osdev


【解决方案1】:

在 32 位代码中,LIDT 的内存操作数是 32 位的。在 AT&T 语法中,L 指令后缀强制汇编器始终假定 long(32 位)。在 64 位代码中,内存操作数是 64 位的。如果你使用指令后缀,那么它必须是Q(四字)。四字是 64 位的。

汇编器足够聪明,可以根据生成的 32 位或 64 位代码知道 LIDT 的大小。更好的选择是让汇编程序通过在指令后缀中省略大小来推断大小。只需使用 LIDT 代替。代码可能如下所示:

__asm__ ("lidt %0" : : "m" (idt_reg));

我已经删除了volatile,因为它在没有输出操作数时是隐式的。我使用m(内存操作数)作为约束,以避免通过寄存器传递内存地址时出现问题。通过寄存器传递地址需要memory clobber 或类似机制,以确保在发出内联汇编之前该地址处的数据在内存中可用。来自GCC documentation

“内存”破坏器告诉编译器汇编代码 对未列出的项目执行内存读取或写入 输入和输出操作数(例如,访问内存 由输入参数之一指向)。确保内存包含 正确的值,GCC 可能需要刷新特定的寄存器值以 执行 asm 之前的内存。

如果您确实使用了r 约束(没有必要),那么正确的代码应该是:

__asm__ ("lidt (%0)" : : "r" (&idt_reg) : "memory");

脚注

  • 通过删除 LQ 指令后缀,此版本应该可以编译生成 32 位或 64 位程序。
  • GCC 的内联汇编是tricky to get right,如果你弄错了,生成的代码可能并不总是你所期望的。

【讨论】:

  • 操作数实际上是 48 或 80 位,不是吗?而且我认为没有任何操作数大小后缀。所以使用lq 有点用词不当,但这可能正是gas 的作用。
  • @NateEldredge :对于这些指令(如 LIDT 和 LGDT)来说,这是 GAS 语法的一个怪癖。在我的脚注中,我碰巧提到你可以去掉后缀,而 GAS 可以推断出大小。除非无法推断,否则我更喜欢没有后缀的指令
  • 所以要明确一点,lidtl 用于 32 位模式,它需要一个 48 位操作数,lidtq 用于 64 位模式,它需要一个 80 位操作数。正如你所说,普通的lidt 也可以。
  • @NateEldredge:正确。同样,如果在生成 64 位代码时使用L 后缀,GNU 汇编器将给出关于 LIDT 非法后缀的错误。如果在生成 32 位指令时使用 Q 后缀,则会给出关于无效后缀的错误。在 32 位代码中,实际上也可以使用 W 后缀,但这是另一回事。
  • 在生成 32 位指令时,W 后缀实际上会强制使用 0x66 前缀,因此 LIDT 指令的行为类似于实模式或 16 位保护模式变体,其中基地址在IDTR(IDT 记录)被视为 32 位地址,其中前 8 位设置为零。 GAS 这种方式很奇怪。注意:在 80286 上,IDTR 具有 32 位基地址,但处理器仅读取前 3 个字节(24 位),而忽略最后一个字节。 NASM 语法为 o16 lidt [mem]lidt [word mem]
猜你喜欢
  • 2018-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
  • 1970-01-01
  • 2016-09-06
  • 1970-01-01
  • 2020-01-19
相关资源
最近更新 更多