首页 > 解决方案 > asm volatile() 中的冒号是什么意思

问题描述

我不确定我是否不小心修改了代码,但在这里(我是内联汇编的初学者,但很习惯在不同的文件中汇编): -

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仍然存在错误,请参阅答案。)

标签: c++assemblygccinline-assembly

解决方案


显然,请阅读语法手册。 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加载指令写入寄存器的位置不同,编译器需要知道该结果的放置位置。

假设这是针对 AT&T 语法(不是) ,那么对于约束的顺序,您的操作数也有%0, %1错误的顺序。喜欢在模板中匹配的gcc -masm=intel命名约束可以避免这种情况。https://www.felixcloutier.com/x86/out采用Intel 语法或AT&T。[port] "dN" (port)%[port]out dx/imm8, AL/AX/EAXout %al/%ax/%eax, %dx/$imm8


另外,你分别out32()以另一种方式打破了。

约束允许编译器"dN"为该变量选择 DX 或立即数(如果它的值在编译时已知并且足够小)。

但是您的 asm 模板字符串没有引用%0第一个操作数,而是硬编码%%dx寄存器名称,这仅在编译器选择 DX 时才是正确的。

内联的优化构建out32(0x80, 0x1234)不会端口号与前面的指令一起放入 DX 中,而是选择N(无符号 8 位)约束,因为它更便宜。但是 no$0x80在最终编译器生成的 asm 中的任何地方都被填充,因为%0模板中没有供编译器扩展的内容。

在将整个 asm 传递给汇编器之前,想想 asm 模板字符串,例如编译器扩展的 printf 格式字符串。(包括编译器在编译其他 C 语句之前和之后生成的指令,以及对于某些约束,例如"r""d"将 C 变量或表达式的值放入寄存器(如果它不存在)。)

%%只是一个文字%,所以如果你想硬编码一个 AT&T 寄存器名称,比如%eax,你可以%%eax在 Extended Asm 模板中使用。

您可以在https://godbolt.org/上看到该 asm 。(使用“二进制”模式来查看生成的编译器生成的 asm 是否会真正汇编。使用内联 asm 时不能保证。)

对于工作outb/ 等宏,许多代码库都定义了它们,我认为一些 libc 实现具有内联包装器,比如可能是 MUSL,也可能是 glibc。如果您只想要工作代码,请不要在您不了解内联汇编时尝试编写自己的代码。

有关的:


推荐阅读