繁体   English   中英

奇怪的“ asm”操作数具有不可能的约束错误

[英]Strange 'asm' operand has impossible constraints error

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

#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);
       ^

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

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

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

就像@MichealPetch所说的那样 ,您正在以错误的方式进行处理。 如果您要为lgdt设置操作数,请在C中进行操作,并且仅对lgdt指令本身使用inline-asm。 请参阅标签Wiki和标签Wiki。

相关文章:用于弄乱Intel描述符表的C结构/联合: 如何在编译/链接时使用地址进行计算? (该问题想将表生成为静态数据,因此询问在编译时将地址分为低半/高半)。

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

链接器错误设置使用内联汇编程序通过LGDT指令加载GDT寄存器来加载GDT寄存器,这是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来存储2个从n开始的字节。 所以应该是一个uint16_t内存操作数。 如果听起来很复杂, 请https://gcc.gnu.org/wiki/DontUseInlineAsm 就像Michael所说的那样,用struct在C中完成此部分,并且仅对需要它的单个指令使用内联asm。

使用更少的更广泛的存储指令显然会更有效率。 IDK接下来您打算做什么,但是任何相邻的常量都应该合并到32位存储中,例如mov $(104 + 0x1234 << 16), %[n0]或类似内容。 同样, https://gcc.gnu.org/wiki/DontUseInlineAsm

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM