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

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

问题描述:

我想编译一个简单的C程序(Win7 32bit,Mingw32 Shell和GCC 5.3.0)。 C代码是这样的:奇怪的'asm'操作数有不可能的约束错误

#include <stdio.h> 
#include <stdlib.h> 

#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)的宏。主函数中调用了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); 
    ^

奇怪的是,如果我的评论援引点出的主要功能,代码编译成功。或者,如果我删除汇编代码的输出部分中的一些变量,它也编译。

#include <stdio.h> 
#include <stdlib.h> 

#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; 
} 

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

+0

我猜不出寄存器。您正在使用* mingw *进行编译,托管时使用某种调用约定。 –

+0

升级您的编译器可能会有帮助。 GCC 5相当古老。 GCC 7.2是最新的。我推荐[Nuwen Mingw发行版](https://nuwen.net/mingw.html)作为Windows的另一个小型开发发行版。 – tambre

+2

使用用户定义的内联汇编程序的任何代码都不是“简单的C程序”。 –

As @MichealPetch says,你正在接近这个错误的方式。如果你试图为lgdt设置一个操作数,那么在C语言中执行该操作数,并且仅对lgdt指令本身使用inline-asm。请参阅标记wiki以及标记wiki。

相关:一个C结构/联合与英特尔描述符表:How to do computations with addresses at compile/linking time?。 (这个问题想要生成表格作为静态数据,因此要求在编译时将地址分为低/高两半)。

另外:Implementing GDT with basic kernel对于一些C + asm GDT操作。或者可能不是,因为那里的答案只是说问题中的代码是有问题的,没有详细的修复。

Linker error setting loading GDT register with LGDT instruction using Inline assembly有一个迈克尔佩奇的答案,有一些链接到更多的指南/教程。


它仍然回答的具体问题是有用的,即使正确的解决方法是https://gcc.gnu.org/wiki/DontUseInlineAsm

这个编译罚款启用优化。

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

您可以在最后3个内存操作数注释的简化示例中看到这一点,并将asm字符串更改为包含所有操作数的asm注释。从gcc5.3 -O0 -m32 on the Godbolt compiler explorer

#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 

通知后的操作数是如何使用基地+ DISP寻址模式。


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

如果您用过"=m"输出操作数,这个代码将是正确的(但相比让编译器仍然低效为您代劳。)

你可以写你的汇编做从单一的抵消本身内存输入操作数,但是你需要做一些事情来告诉编译器关于asm语句读取的内存;例如"=m" (*(struct {char a; char x[];} *) n)告诉它你写的整个对象从n开始。 (见this answer)。

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)或不管它最终被,但是这好。

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

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

+0

我错过了。如果你从Linus上拿走一个页面,你可以在没有警告的情况下做你想做的事情,并达到同样的行为。但是你可能已经知道Linus在内核中使用的诀窍。这个技巧将适用于clang和gcc。 –

+0

@MichaelPetch:不,我不知道,有什么把戏? –