3

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

现在,不幸的是,as拒绝ins %ax,%dxError: operand type mismatch for 'ins',这是为什么呢?

作为记录,我知道我可以简单地使用insbetc. 但我通过 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"); }
4

2 回答 2

5

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

在 GCC 内联汇编中,您可以获得与操作数大小b,w,l,q(基于其类型)与z操作数修饰符匹配的操作数大小后缀。请参阅GCC 手册的“x86 操作数修饰符”下的“扩展 Asm”部分。因此,如果您适当地使用类型并添加一个引用实际目标(而不是指向它的指针)的操作数,您可以执行以下操作:

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");
}

在godbolt上试试

这里重要的是目标是 aT *而不是泛型uint32_t,因为大小是从 type 推断出来的T

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

于 2020-11-21T18:29:25.003 回答
1

该答案是对 OP 在主要编辑之前提出的问题的第一个版本的回应。这解决了该指令的 GNU 汇编器的 AT&T 语法INS

INS/INSB/INSW/INSD的指令集参考——从端口输入到字符串表明实际上只有 3 种形式的 INS 指令。采用字节(B)、字(W)或双字(D)的格式。最终,从 DX 中的端口读取 BYTE( b)、WORD( w) 或 DWORD( l) 并将其写入或ES:RDI、ES:EDI、ES:DI。没有任何形式的INS指令将寄存器作为目标,不像IN可以写入AL/AX/EAX的指令。

注意:在IN指令中,端口被认为是源,并且是 AT&T 语法中格式为 的第一个参数instruction src, dest

在 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
于 2020-11-21T18:28:32.610 回答