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

Rob*_*t B 8 x86 gcc g++ inline-assembly osdev

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

这是一个教程中特定的工作示例.

static inline uint8_t inb(uint16_t port)
{
    uint8_t ret;
    asm volatile ( "inb %1, %0"
                   : "=a"(ret)
                   : "Nd"(port) );
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

inb是AT&T语法 - 代表IN从I/O端口接收一个字节的i386 指令.

以下是本指令的规格,取自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
Run Code Online (Sandbox Code Playgroud)

给出类似uint8_t x = inb(0x80);汇编输出的声明,正确地说inb $0x80,%al.它使用IN AL,imm8了指令的形式.

现在,假设我只关心IN AL,imm8表单,接收uint8_t来自端口0x000xFF包含端口的端口.这和工作示例之间的唯一区别是,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;
}
Run Code Online (Sandbox Code Playgroud)

失败!

我认为"N"约束意味着"你必须为这条指令设置一个恒定的无符号8位整数",但很明显它并不是因为它是一个"不可能的约束".uint8_t模板参数不是一个常量无符号8位整数吗?

如果我用"Nd"替换"N",我会得到一个不同的错误:

./test.h: Assembler messages:
./test.h:23: Error: operand type mismatch for `in'
Run Code Online (Sandbox Code Playgroud)

在这种情况下,汇编器输出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();
}
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*tch 1

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

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

无符号 8 位整数常量(用于 in 和 out 指令)。

有一个与 GCC 的 bugzilla类似的错误报告,标题为“常量约束检查符号扩展了无符号常量输入操作数”。

在相关线程之一中,建议您可以通过将 (&) 0xff 与常量进行与运算(即:N & 0xff)来解决此问题。我还发现静态转换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
}
Run Code Online (Sandbox Code Playgroud)

要测试这一点,您可以在godbolt上使用它。