我不确定我是否不小心修改了代码,但它就在这里(我是内联汇编的初学者,但很习惯于在不同的文件中汇编):-
void out8(uint16 port, uint8 data) {asm volatile("outb %0, %1" : "dN"(port) : "a"(data));}
void out16(uint16 port, uint16 data) {asm volatile("outw %0, %1" : "dN"(port) : "a"(data));}
void out32(uint16 port, uint32 data) {asm volatile("outl %%eax, %%dx" : "dN"(port) : "a"(data));}
以前它没有给出错误,但现在是。 但是任何人都可以纠正此代码吗? 另外,告诉我冒号分隔值的依据是什么,冒号中的"dN"和"a"分隔区域, 内联程序集中的"%0"和"%1",为什么这些变量"端口"和"数据"在"a"和"dN"旁边的括号中,以及"%[reg]"和"%%[reg]"之间有什么区别,以便我以后得到它们时可以解决这样的问题。(TL;du(U代表理解)内联扩展程序集的手册页是日语(你知道我的意思,对吧?
<小时 />[已解决]
使用:-
void out(uint16 port, uint8 data) {asm volatile("outb %1, %0" :: "dN"(port), "a"(data));}
void out(uint16 port, uint16 data) {asm volatile("outw %1, %0" : : "dN"(port), "a"(data));}
// Warning, this one's still unsafe, even though it compiles
void out(uint16 port, uint32 data) {asm volatile("outl %%eax, %%dx" : : "dN"(port), "a"(data));}
(对未来读者的警告:outl
仍然有错误,请参阅答案。
显然,请阅读语法手册。 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html。
asm asm-qualifiers ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ])
另请参阅 https://stackoverflow.com/questions/tagged/inline-assembly 以获取指向官方文档以外的指南的链接。
您在输出部分中有"dN"(port)
,但它实际上是一个输入(没有=
或+
)。 是的,I/O 端口号应该是一个输入:它是 asm 语句需要从程序的周围代码中获取的东西,而不是 asm 语句提供回程序的东西。 因此,编译器需要知道如何将其提供给 asm 语句,而不是收集它。
如果您对out
有两个"输入"这一事实感到困惑,请将其视为商店指令。 两条数据来自 CPU(数据和地址),导致存储到内存。 与加载或in
不同,加载指令写入寄存器,编译器需要知道结果的位置。
您还具有约束顺序错误的%0, %1
操作数,假设这是针对 AT&T 语法(不是gcc -masm=intel
)。 命名约束(如[port] "dN" (port)
)以匹配模板中的%[port]
可以避免这种情况。https://www.felixcloutier.com/x86/out 在英特尔语法中out dx/imm8, AL/AX/EAX
,在AT&T中out %al/%ax/%eax, %dx/$imm8
。
另外,您以另一种方式分别破坏了out32()
。
"dN"
约束允许编译器为该变量选择 DX 或立即变量(如果其值在编译时已知且足够小)。
但是您的 asm 模板字符串不会引用第一个操作数%0
而是硬编码%%dx
寄存器名称,这仅在编译器选择 DX 时才是正确的。
内联out32(0x80, 0x1234)
的优化版本不会按照前面的说明将端口号放在 DX 中,而是选择N
(无符号 8 位)约束,因为它更便宜。 但是在最终编译器生成的 asm 中,没有$0x80
填充,因为模板中没有供编译器扩展的%0
。
在将整个 asm 传递给汇编器之前,请考虑编译器扩展的 asm 模板字符串,例如 printf 格式字符串。(包括编译器在编译其他 C 语句之前和之后生成的指令,以及某些约束(如"r"
或"d"
)将 C 变量或表达式的值放入寄存器(如果尚未存在)。
%%
只是一个文字%
,所以如果你想硬编码一个AT&T寄存器名称,如%eax
,你可以在扩展的Asm模板中使用%%eax
。
你可以在 https://godbolt.org/上看到那个 asm。 (使用"二进制"模式查看生成的编译器生成的asm是否实际组装。 使用内联 asm 时不能保证这一点。
对于工作outb
/等宏,许多代码库定义了它们,我认为一些libc实现具有内联包装器,例如MUSL,也许还有glibc。 如果你只是想要工作代码,当你不知道内联 asm 时,不要试图编写自己的代码。
相关:
你如何解释 gcc 对 i386 的 IN、OUT 指令的内联程序集约束? 分解
inb
包装器函数。用于x86输入/输出端口I/O的操作数大小不匹配,为
"d"
选择的寄存器大小取决于C类型宽度,并且必须是16位,因为in
/out
专门使用DX。 (I/O 地址空间为 64k,与内存地址空间不同。GCC 内联程序集"Nd"约束
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html 手册。 阅读它。 内联 asm 很容易微妙但危险的错误;不要依赖反复试验/"似乎有效"。
https://gcc.gnu.org/wiki/DontUseInlineAsm。 (或者在这种情况下,如果你不知道内联 asm,请在 libc 标头或其他东西中找到一些已知良好的 in/out 包装器函数,并使用它们而不是编写自己的包装器。
https://stackoverflow.com/tags/intel-syntax/info