如何在 GNU 汇编程序中使用 ins 指令
How to use ins instruction with GNU assembler
如何在 GNU 汇编程序中使用 x86 ins
指令?指令参考建议使用 INS m8/16/32, DX
语法,例如m16
(我假设)是任何 16 位通用寄存器,其唯一目的是表示是否应该读取 byte/word/doubleword,对吧?
现在,不幸的是,as
用 Error: 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/INSL
和 addr16
、addr32
和 addr64
覆盖。作为 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");
}
这里重要的是目标是 T *
而不是通用的 uint32_t
因为大小是从类型 T
.
推断出来的
我还用 +
读写约束替换了重复的输入和输出操作数。挑剔的是,“cc”破坏是不必要的,因为 rep ins
不影响任何标志,但它在 x86 上是多余的,因为假定每个内联 asm 无论如何都会破坏标志。
如何在 GNU 汇编程序中使用 x86 ins
指令?指令参考建议使用 INS m8/16/32, DX
语法,例如m16
(我假设)是任何 16 位通用寄存器,其唯一目的是表示是否应该读取 byte/word/doubleword,对吧?
现在,不幸的是,as
用 Error: 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
.
在 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/INSL
和 addr16
、addr32
和 addr64
覆盖。作为 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");
}
这里重要的是目标是 T *
而不是通用的 uint32_t
因为大小是从类型 T
.
我还用 +
读写约束替换了重复的输入和输出操作数。挑剔的是,“cc”破坏是不必要的,因为 rep ins
不影响任何标志,但它在 x86 上是多余的,因为假定每个内联 asm 无论如何都会破坏标志。