如何在 GNU 汇编程序中使用 ins 指令

How to use ins instruction with GNU assembler

如何在 GNU 汇编程序中使用 x86 ins 指令?指令参考建议使用 INS m8/16/32, DX 语法,例如m16(我假设)是任何 16 位通用寄存器,其唯一目的是表示是否应该读取 byte/word/doubleword,对吧?

现在,不幸的是,asError: operand type mismatch for 'ins' 拒绝了 ins %ax,%dx,这是为什么?

郑重声明,我知道我可以简单地使用 insb 等,但我是通过 C++ 程序中的内联汇编调用此指令的,要读取的输入大小取决于模板参数(编译时的字符串处理不是很实用)。

编辑:这是我现在拥有的,供参考(我不太喜欢这个宏)

#define INS(T) \
  __asm__ __volatile__("repne \n\t" \
                       "ins" T \
                       : "=D" (dest), "=c" (count) \
                       : "d" (port), "0" (dest), "1" (count) \
                       : "memory", "cc")

template<typename T>
void ins(uint16_t port, uint32_t dest, uint32_t count);

template<>
void ins<uint8_t>(uint16_t port, uint32_t dest, uint32_t count)
{ INS("b"); }

template<>
void ins<uint16_t>(uint16_t port, uint32_t dest, uint32_t count)
{ INS("w"); }

template<>
void ins<uint32_t>(uint16_t port, uint32_t dest, uint32_t count)
{ INS("l"); }

这个答案是对 OP 进行重大编辑之前提出的问题的第一个版本的回应。这解决了 INS 指令的 GNU 汇编程序的 AT&T 语法。

可写入AL/AX/EAX.

INS/INSB/INSW/INSD — Input from Port to String shows that there are really only 3 forms of the INS instruction. One that takes a byte(B), word(W), or double word(D). Ultimately a BYTE(b), WORD(w), or DWORD(l) is read from the port in DX and written to or ES:RDI, ES:EDI, ES:DI. There is no form of the INS instructions that take a register as a destination, unlike IN指令集参考

注意:对于 IN 指令,端口被视为源端口,并且是格式为 instruction src, dest.

的 AT&T 语法中的第一个参数

在 GNU 汇编程序中,最简单的方法是使用这 3 种形式中的任何一种:

insb      # Read BYTE from port in DX to [RDI] or ES:[EDI] or ES:[DI]
insw      # Read WORD from port in DX to [RDI] or ES:[EDI] or ES:[DI]
insl      # Read DWORD from port in DX to [RDI] or ES:[EDI] or ES:[DI]

在 16 位代码中,这些指令将执行:

insb      # Read BYTE from port in DX to ES:[DI]
insw      # Read WORD from port in DX to ES:[DI]
insl      # Read DWORD from port in DX to ES:[DI]

在 32 位代码中,这些指令将执行:

insb      # Read BYTE from port in DX to ES:[EDI]
insw      # Read WORD from port in DX to ES:[EDI]
insl      # Read DWORD from port in DX to ES:[EDI]

在 64 位代码中,这些指令将执行:

insb      # Read BYTE from port in DX to [RDI]
insw      # Read WORD from port in DX to [RDI]
insl      # Read DWORD from port in DX to [RDI]

为什么汇编程序也支持长格式?主要用于文档目的,但有一个细微的变化可以用长格式表示,那就是内存地址的大小(而不是要移动的数据的大小)。在 GNU 汇编程序中,这在 16 位代码中受支持:

insb (%dx),%es:(%di)      # also applies to INSW and INSL
insb (%dx),%es:(%edi)     # also applies to INSW and INSL

16 位代码可以使用 16 位或 32 位寄存器来形成内存操作数,这就是您可以覆盖它的方法(下面描述了另一种使用 addr 覆盖的方法)。在 32 位代码中,可以这样做:

insb (%dx),%es:(%di)      # also applies to INSW and INSL
insb (%dx),%es:(%edi)     # also applies to INSW and INSL

可以在 32 位代码的内存操作数中使用 16 位寄存器。在极少数用例中,这特别有用,但处理器支持它。在 64 位代码中,您可以在内存操作数中使用 32 位或 64 位寄存器,因此在 64 位代码中这是可能的:

insb (%dx),%es:(%rdi)      # also applies to INSW and INSL
insb (%dx),%es:(%edi)      # also applies to INSW and INSL

在 GNU 汇编程序中有一种更短的方法来更改内存地址大小,即使用 INSB/INSW/INSLaddr16addr32addr64 覆盖。作为 16 位代码的示例,这些是等效的:

addr32 insb               # Memory address is %es:(%edi). also applies to INSW and INSL
insb (%dx),%es:(%edi)     # Same as above

在 32 位代码中,这些是等价的:

addr16 insb               # Memory address is %es:(%di). also applies to INSW and INSL
insb (%dx),%es:(%di)      # Same as above

在 64 位代码中,这些是等价的:

addr32 insb               # Memory address is %es:(%edi). also applies to INSW and INSL
insb (%dx),%es:(%edi)     # Same as above

应该是内存引用,不是寄存器。 Intel 语法中的想法是,您可以为 32 位读取(又名 insd)编写 ins dword ptr [rdi], dx,为 16 位读取 insw 编写 ins word ptr [rdi], dx,等等。你甚至可以写入 ins dword ptr [foo], dx 并获得 32 位读取,但无论如何数据都会写入 [rdi]。尽管如此,AT&T 汇编程序语法根本不支持这一点,指定大小的唯一方法是使用操作数大小后缀。

在 GCC 内联汇编中,您可以获得一个操作数大小后缀 b,w,l,q,该后缀与操作数的大小(基于其类型)与 z 操作数修饰符相匹配。参见 the GCC manual, section "Extended Asm" under "x86 operand modifiers"。因此,如果您适当地使用类型并添加一个引用实际目的地(而不是指向它的指针)的操作数,您可以执行以下操作:

template<typename T>
void ins(uint16_t port, T *dest, uint32_t count) {
    asm volatile("rep ins%z2"
        : "+D" (dest), "+c" (count), "=m" (*dest)
        : "d" (port)
        : "memory");
}

Try it on godbolt

这里重要的是目标是 T * 而不是通用的 uint32_t 因为大小是从类型 T.

推断出来的

我还用 + 读写约束替换了重复的输入和输出操作数。挑剔的是,“cc”破坏是不必要的,因为 rep ins 不影响任何标志,但它在 x86 上是多余的,因为假定每个内联 asm 无论如何都会破坏标志。