首页 > 解决方案 > 您如何解释 gcc 对 i386 的 IN、OUT 指令的内联汇编约束?

问题描述

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

这是教程中的一个具体的工作示例。

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

inbIN是从 I/O 端口接收一个字节的 i386 指令的 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_t之间的端口接收 a 。这个和工作示例之间的唯一区别是现在是模板参数(使其有效地成为常量)并且约束是 now 。0x000xFFportuint8_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;
}

失败!

我认为“N”约束意味着“你必须有一个恒定的无符号 8 位整数用于这条指令”,但显然它不是,因为它是一个“不可能的约束”。模板参数不是uint8_t常量无符号 8 位整数吗?

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

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

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

为什么这只适用于"Nd"anduint16_t而不是"N"and 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 之间的任何值。在铿锵声中,这会产生“128 超出约束 N 的范围”错误。这会在 gcc 中产生更一般的错误。

标签: gccx86g++inline-assemblyosdev

解决方案


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

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

ñ

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

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

在其中一个相关线程中,建议您可以通过 ANDing (&) 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
}

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


推荐阅读