【问题标题】:what does the colon in asm volatile() meanasm volatile() 中的冒号是什么意思
【发布时间】:2021-11-15 21:06:25
【问题描述】:

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

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”旁边的括号中的那些变量“port”和“data”以及“%[ 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 仍有错误,请参阅答案。)

【问题讨论】:

  • 在询问涉及构建错误的问题时,总是包含您遇到的实际错误。最好是一个合适的minimal reproducible example 也向我们展示。
  • 你读过多少the GCC documentationinline assembly documentation 关于这个问题的哪些部分你不明白?
  • 显然,请阅读语法手册。 gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html。由于将端口号作为输出(在第一个: 之后,而不是第二个),它们都被破坏了。另外,你单独打破了out32"dN" 约束允许编译器选择 DX 或立即数,但您不引用 %0 第一个操作数,而是始终假设它将选择 DX。因此,为out32(12, 0x1234) 内联的优化构建将not 将端口号放入DX,但模板中没有%0$12 不会被替换到编译器的任何地方-生成的汇编。
  • 另请参阅stackoverflow.com/questions/tagged/inline-assembly 以获取指向官方文档以外的指南的链接。如果您对out 有两个“输入”这一事实感到困惑,请将其视为存储指令。两条数据来自 CPU(数据和地址),从而存储到内存中。与 load 或 in 不同,其中 load 指令写入寄存器,编译器需要知道结果放在哪里。
  • 正如我所说,GNU C 内联汇编使编写可编译但不能通过约束向编译器/优化器准确描述模板的代码变得非常容易。因此,它会因周围代码和编译器选项的某些组合而中断。 “它编译”是必要的,但 不足以使内联汇编正确。

标签: c++ assembly gcc inline-assembly


【解决方案1】:

显然,请阅读语法手册。 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(数据和地址),导致存储到内存中。与 load 或 in 不同,其中 load 指令写入寄存器,编译器需要知道结果放在哪里。

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


另外,你以另一种方式单独破坏了out32()

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

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

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

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

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

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

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

相关:

【讨论】:

    猜你喜欢
    • 2012-10-09
    • 2020-05-21
    • 1970-01-01
    • 2011-01-03
    • 2019-09-14
    • 2012-09-25
    • 2021-02-06
    • 1970-01-01
    相关资源
    最近更新 更多