奇怪的’asm’操作数有不可能的约束错误

我正在尝试编译一个简单的C程序(Win7 32位,Mingw32 Shell和GCC 5.3.0)。 C代码是这样的:

#include  #include  #define _set_tssldt_desc(n,addr,type) \ __asm__ ("movw $104,%1\n\t" \ :\ :"a" (addr),\ "m" (*(n)),\ "m" (*(n+2)),\ "m" (*(n+4)),\ "m" (*(n+5)),\ "m" (*(n+6)),\ "m" (*(n+7))\ ) #define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") char *n; char *addr; int main(void) { char *n = (char *)malloc(100*sizeof(int)); char *addr = (char *)malloc(100*sizeof(int)); set_tss_desc(n, addr); free(n); free(addr); return 0; } 

_set_tssldt_desc(n,addr,type)是一个宏,它的主体是汇编代码。 set_tss_desc(n,addr)是另一个非常类似于_set_tssldt_desc(n,addr,type)宏。 在main函数中调用set_tss_desc(n,addr)宏。

当我尝试编译此代码时,编译器显示以下错误:

 $ gcc test.c test.c: In function 'main': test.c:5:1: error: 'asm' operand has impossible constraints __asm__ ("movw $104,%1\n\t" \ ^ test.c:16:30: note: in expansion of macro '_set_tssldt_desc' #define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") ^ test.c:25:3: note: in expansion of macro 'set_tss_desc' set_tss_desc(n, addr); ^ 

奇怪的是,如果我在main函数中注释调用指出,代码编译成功。

 int main(void) { char *n = (char *)malloc(100*sizeof(int)); char *addr = (char *)malloc(100*sizeof(int)); //I comment it out and code compiled. //set_tss_desc(n, addr); free(n); free(addr); return 0; } 

或者,如果我在汇编代码的输出部分删除了一些变量,它也会编译。

 #include  #include  #define _set_tssldt_desc(n,addr,type) \ __asm__ ("movw $104,%1\n\t" \ :\ :"a" (addr),\ "m" (*(n)),\ "m" (*(n+2)),\ "m" (*(n+4)),\ "m" (*(n+5)),\ "m" (*(n+6))\ ) //I DELETE "m" (*(n+7)) , code compiled #define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") char *n; char *addr; int main(void) { char *n = (char *)malloc(100*sizeof(int)); char *addr = (char *)malloc(100*sizeof(int)); set_tss_desc(n, addr); free(n); free(addr); return 0; } 

有人可以向我解释为什么会这样,以及如何解决这个问题?

正如@MichealPetch所说 ,你正在以错误的方式接近它。 如果您正在尝试为lgdt设置操作数,请在C中执行此操作,并仅对lgdt指令本身使用inline-asm。 请参阅inline-assembly标记wiki和x86标记wiki。

相关:用于搞乱英特尔描述符表的C结构/联合: 如何在编译/链接时使用地址进行计算? 。 (这个问题想要将表生成为静态数据,因此要求在编译时将地址分解为低/高一半)。

另外: 使用基本内核实现GDT以进行某些C + asm GDT操作。 或许不是,因为那里的答案只是说问题中的代码是有问题的,没有详细的修复。

链接器错误设置使用Inline程序集加载GDT寄存器和LGDT指令有Michael Petch的答案,其中包含一些指向更多指南/教程的链接。


尽管正确的解决方案是https://gcc.gnu.org/wiki/DontUseInlineAsm ,但回答特定问题仍然有用。

在启用优化的情况下编译很好。

对于-O0 ,gcc没有注意到或利用操作数彼此都是小的常量偏移的事实,并且可以使用具有偏移寻址模式的相同基址寄存器。 它希望将指向每个输入内存操作数的指针放入一个单独的寄存器中,但是用完了寄存器。 使用-O1或更高版本,CSE可以满足您的期望。

您可以在一个简化示例中看到这一点,其中最后3个内存操作数已注释,并更改asm字符串以包含所有操作数的asm注释。 来自Godbolt编译器浏览器的gcc5.3 -O0 -m32

 #define _set_tssldt_desc(n,addr,type) \ __asm__ ("movw $104,%1\n\t" \ "#operands: %0, %1, %2, %3\n" \ ... void simple_wrapper(char *n, char *addr) { set_tss_desc(n, addr); } pushl %ebp movl %esp, %ebp pushl %ebx movl 8(%ebp), %eax leal 2(%eax), %ecx movl 8(%ebp), %eax leal 4(%eax), %ebx movl 12(%ebp), %eax movl 8(%ebp), %edx #APP # your inline-asm code movw $104,(%edx) #operands: %eax, (%edx), (%ecx), (%ebx) #NO_APP nop # no idea why the compiler inserted a literal NOP here (not .p2align) popl %ebx popl %ebp ret 

但是,启用优化后,您就可以获得

 simple_wrapper: movl 4(%esp), %edx movl 8(%esp), %eax #APP movw $104,(%edx) #operands: %eax, (%edx), 2(%edx), 4(%edx) #NO_APP ret 

注意后面的操作数如何使用base + disp寻址模式。


你的约束完全是倒退的。 你写的内存是你告诉编译器的输入操作数。 它将假设内存未被asm语句修改,因此如果您在C中加载它,它可能会在asm之前移动该加载。 和其他可能的破损。

如果您使用了"=m"输出操作数,则此代码将是正确的(但与让编译器为您执行此操作相比仍然效率低下。)

您可以编写自己的asm来从单个内存输入操作数进行偏移,但是您需要做一些事情来告诉编译器asm语句读取的内存; 例如"=m" (*(struct {char a; char x[];} *) n)告诉它你从n开始编写整个对象。 (见这个答案 )。

AT&T语法x86内存操作数始终是可以离开的,因此如果你这样做,你可以使用2 + %[nbase]而不是单独的操作数

 asm("movw $104, %[nbase]\n\t" "movw $123, 2 + %[nbase]\n\t" : [nbase] "=m" (*(struct {char a; char x[];} *) n) : [addr] "ri" (addr) ); 

天然气将警告大约2 + (%ebx)或其最终结束,但没关系。

为您编写的每个位置使用单独的内存输出操作数将避免告诉编译器您编写哪个内存的任何问题。 但是你弄错了:你告诉编译器你的代码不使用n+1 ,而实际上你正在使用movw $104来存储从n开始的2个字节。 所以这应该是一个uint16_t内存操作数。 如果这听起来很复杂, 请https://gcc.gnu.org/wiki/DontUseInlineAsm 。 就像迈克尔所说,用C语言在C语言中做这个部分,并且只对需要它的单个指令使用内联asm。

使用更少的更宽存储指令显然会更有效。 IDK你接下来打算做什么,但是任何相邻的常量应该合并到一个32位的存储中,比如mov $(104 + 0x1234 << 16), %[n0]等等。 再次, https://gcc.gnu.org/wiki/DontUseInlineAsm 。