簡體   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