GCC内联汇编错误:'lidt'的指令后缀无效

时间:2019-06-03 09:58:18

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

我正在尝试从C调用一些汇编代码。最近,我在将程序从x86切换到x86-64之前开始工作。我有以下代码:

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

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

  

'lidt'的无效指令后缀

当我添加$令牌时:

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

我收到此错误:

  

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

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

1 个答案:

答案 0 :(得分:4)

在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垃圾桶或类似机制,以确保发出内联汇编之前,该地址上的数据在内存中可用。来自GCC documentation

  

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

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

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

脚语

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