你如何解释gcc对i386的IN,OUT指令的内联汇编约束?

时间:2018-06-08 22:28:14

标签: gcc assembly x86 inline-assembly

据我所知,gcc内联汇编中使用的约束告诉gcc输入和输出变量必须去(或必须)才能生成有效的汇编。正如精细手册所说,"限制了操作数的位置"。

以下是来自a tutorial的具体实例。

static inline uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb %1, %0"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}
对于从I / O端口接收一个字节的i386 inb指令,

IN是AT& T语法。

以下是本指令的规格,取自i386手册。请注意,端口号从0x00000xFFFF

IN AL,imm8  // Input byte from immediate port into AL
IN AX,imm8  // Input word from immediate port into AX
IN EAX,imm8 // Input dword from immediate port into EAX
IN AL,DX    // Input byte from port DX into AL
IN AX,DX    // Input word from port DX into AX
IN EAX,DX   // Input dword from port DX into EAX

鉴于像uint8_t x = inb(0x80);这样的语句,汇编输出正确地为inb $0x80,%al。它使用了IN AL,imm8形式的指令。

现在,让我们说我只关心IN AL,imm8表单,从uint8_t0x00之间的端口接收0xFF。这个和工作示例之间的唯一区别是port现在是uint8_t模板参数(使其有效地成为常量),现在约束为"N"

template<uint8_t port>
static inline uint8_t inb()
{
    uint8_t ret;
    asm volatile ( "inb %1, %0"
                   : "=a"(ret)
                   : "N"(port) );
    return ret;
}

失败!

我认为&#34; N&#34;约束意味着,&#34;你必须有一个常量的无符号8位整数用于这个指令&#34;,但很明显它没有,因为它是一个&#34;不可能的约束&#34;。 uint8_t模板参数是否为常量无符号8位整数?

如果我更换&#34; N&#34;与&#34; Nd&#34;,我得到一个不同的错误:

./test.h: Assembler messages:
./test.h:23: Error: operand type mismatch for `in'

在这种情况下,汇编程序输出为inb %dl, %al,显然无效。

为什么这只适用于"Nd"uint16_t而不是"N"uint8_t

编辑:

这是我在godbolt.org上试过的精简版:

#include <cstdint>

template<uint8_t N>
class Port {
 public:
  uint8_t in() const {
    uint8_t data;

    asm volatile("inb %[port], %%al"
                     :  
                     :  [port] "N" (N)
                     :  // clobbers
    );
    return data;    
  }
};

void func() {
    Port<0x7F>().in();
}

有趣的是,这种方法很好,除非您将N更改为0x80和0xFF之间的任何值。在clang上,这会产生&#34; 128超出约束N&#34;错误。这会在gcc中生成更一般的错误。

1 个答案:

答案 0 :(得分:1)

根据约束记录的方式,您的代码应按预期工作。

一年多以后,这似乎仍然是一个错误。看来编译器正在将N从无符号值转换为有符号值,并试图将其传递给内联汇编约束。当传递到约束中的值不能表示为8位带符号的值时,这当然会失败。假设input constraint "N"允许无符号的8位值,并且应接受0到255(0xff)之间的任何值:

  

N

     

无符号8位整数常量(用于输入和输出指令)。

与GCC的bugzilla类似,bug report的标题为“ 常量约束检查符号扩展了无符号常量输入操作数”。

在一个相关的线程中,建议您可以通过将(&)0xff与常数(即:N & 0xff进行ANDing来解决此问题。我还发现将N静态转换为比uint8_t宽的无符号类型也可以:

#include <cstdint>

template<uint8_t N>
class Port {
 public:
  uint8_t in() const {
    uint8_t data;

    asm volatile("inb %[port], %0"
                     : "=a"(data)
                     :  [port] "N" (static_cast<uint16_t>(N))
                     :  // clobbers
    );
    return data;    
  }
};

void func() {
    Port<0x7f>().in();
    Port<0x80>().in();
//    Port<0x100>().in();    // Fails as expected since it doesn't fit in a uint8_t
}

要对此进行测试,您可以在godbolt上使用它。