本文介绍了奇怪的"asm"操作数具有不可能的约束错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to compile a simple C program (Win7 32bit, Mingw32 Shell and GCC 5.3.0). The C code is like this:

#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)宏在主函数中被调用.

_set_tssldt_desc(n,addr,type) is a macro and its body is assembly code. set_tss_desc(n,addr) is another macro very similar to _set_tssldt_desc(n,addr,type). The set_tss_desc(n,addr) macro is called in main function.

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

When I'm trying to compile this code, the compiler's showing me the following error:

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

奇怪的是,如果我在主函数中指出调用,则代码成功编译.

The strange thing is, if I comment invoke point out in main function, the code compiled successfully.

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

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

Or, if I delete some variables in output part of assembly code, it also compiled.

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

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

Can someone explain to me why that is and how to fix this?

推荐答案

,您正在以错误的方式进行处理.如果要为lgdt设置操作数,请在C语言中进行操作,并且仅对lgdt指令本身使用inline-asm.请参见 inline-assembly 标签维基的问题,以及 x86 标签Wiki的问题.

As @MichealPetch says, you're approaching this the wrong way. If you're trying to set up an operand for lgdt, do that in C and only use inline-asm for the lgdt instruction itself. See the inline-assembly tag wiki, and the x86 tag wiki.

相关:用于弄乱英特尔描述符表的C结构/联合:. (该问题想将表生成为静态数据,因此询问在编译时将地址分为低/高两半).

Related: a C struct/union for messing with Intel descriptor-tables: How to do computations with addresses at compile/linking time?. (The question wanted to generate the table as static data, hence asking about breaking addresses into low / high halves at compile time).

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

Also: Implementing GDT with basic kernel for some C + asm GDT manipulation. Or maybe not, since the answer there just says the code in the question is problematic, without a detailed fix.

链接器错误设置正在加载GDT使用内联汇编向LGDT指令注册,Michael Petch给出了答案,并提供了指向更多指南/教程的一些链接.

Linker error setting loading GDT register with LGDT instruction using Inline assembly has an answer from Michael Petch, with some links to more guides/tutorials.

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

It's still useful to answer the specific question, even though the right fix is https://gcc.gnu.org/wiki/DontUseInlineAsm.

可以在启用优化的情况下进行编译.

对于-O0,gcc不会注意到或利用以下事实:操作数彼此之间都是小的常量偏移量,并且可以在偏移量寻址模式下使用相同的基址寄存器.它希望将指向每个输入存储器操作数的指针放入单独的寄存器中,但用完了寄存器.在-O1或更高版本中,CSE会达到您的期望.

With -O0, gcc doesn't notice or take advantage of the fact that the operands are all small constant offsets from each other, and can use the same base register with an offset addressing mode. It wants to put a pointer to each input memory operand into a separate register, but runs out of registers. With -O1 or higher, CSE does what you'd expect.

您可以在简化的示例中看到这一点,对最后3个内存操作数进行注释,然后将asm字符串更改为在所有操作数中都包含一个asm注释.来自 gcc5.3 -O0 -m32在Godbolt编译器上xplorer :

You can see this in a reduced example with the last 3 memory operands commented, and changing the asm string to include an asm comment with all the operands. From 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

但是启用优化后,您会得到

But with optimization enabled, you get

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

请注意后面的操作数如何使用基址+分配寻址方式.

Notice how the later operands use base+disp addressing modes.

您的约束完全落后.您正在写入的内存中,您已经告诉编译器是一个输入操作数.它将假定该内存未由asm语句修改,因此,如果从C中加载该内存,则它可能会移动asm之前的负载.以及其他可能的损坏.

Your constraints are totally backwards. You're writing to memory that you've told the compiler is an input operand. It will assume that the memory is not modified by the asm statement, so if you load from it in C, it might move that load ahead of the asm. And other possible breakage.

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

If you had used "=m" output operands, this code would be correct (but still inefficient compared to letting the compiler do it for you.)

您可能已经编写了asm来对单个内存输入操作数进行偏移,但是随后您需要做一些事情来告知编译器asm语句读取的内存;例如"=m" (*(struct {char a; char x[];} *) n)告诉您您从n开始编写了整个对象. (请参见此答案).

You could have written your asm to do the offsetting itself from a single memory-input operand, but then you'd need to do something to tell the compiler about that the memory read by the asm statement; e.g. "=m" (*(struct {char a; char x[];} *) n) to tell it that you write the entire object starting at n. (See this answer).

AT& T语法x86内存操作数始终是可偏移的,因此您可以使用2 + %[nbase]而不是单独的操作数

AT&T syntax x86 memory operands are always offsetable, so you can use 2 + %[nbase] instead of a separate operand, if you do

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)或最终导致的后果,但这没关系.

gas will warn about 2 + (%ebx) or whatever it ends up being, but that's ok.

在每个写入的位置使用单独的内存输出操作数,可以避免告诉编译器您写入哪个内存的任何问题.但是您弄错了:您已经告诉编译器您的代码实际上没有使用n+1,而实际上您是在使用movw $104来存储从n开始的2个字节.所以应该是一个uint16_t内存操作数.如果这听起来很复杂,请 https://gcc.gnu.org/wiki/DontUseInlineAsm .就像Michael所说的那样,用struct在C中完成这一部分,并且仅对需要它的单个指令使用内联asm.

Using a separate memory output operand for each place you write will avoid any problems about telling the compiler which memory you write. But you got it wrong: you've told the compiler that your code doesn't use n+1 when in fact you're using movw $104 to store 2 bytes starting at n. So that should be a uint16_t memory operand. If this sounds complicated, https://gcc.gnu.org/wiki/DontUseInlineAsm. Like Michael said, do this part in C with a struct, and only use inline asm for a single instruction that needs it.

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

It would obviously be more efficient to use fewer wider store instructions. IDK what you're planning to do next, but any adjacent constants should be coalesced into a 32-bit store, like mov $(104 + 0x1234 << 16), %[n0] or something. Again, https://gcc.gnu.org/wiki/DontUseInlineAsm.

这篇关于奇怪的"asm"操作数具有不可能的约束错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-29 07:07