c++ - 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
仍然存在错误,请参阅答案。)
解决方案
显然,请阅读语法手册。 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/EAX
out %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。如果您只想要工作代码,请不要在您不了解内联汇编时尝试编写自己的代码。
有关的:
您如何解释 gcc 对 i386 的 IN、OUT 指令的内联汇编约束?分解一个
inb
包装函数。x86 输入/输出端口 I/O 的 C inline asm 操作数大小不匹配,选择的寄存器大小
"d"
取决于 C 类型宽度,并且必须是 16 位,因为in
/out
专门使用 DX。(I/O 地址空间为 64k,与内存地址空间不同。)https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html 手册。阅读它。内联汇编很容易出现微妙但危险的错误;不要依赖反复试验/“似乎有效”。
https://gcc.gnu.org/wiki/DontUseInlineAsm。(或者在这种情况下,如果您不知道内联汇编,请在 libc 标头或其他东西中找到一些已知良好的输入/输出包装函数并使用它们而不是编写自己的。)
推荐阅读
- python - 如何使用函数参数作为分配给函数中 input() 的变量的名称?
- css - React devtools 自动触发调试器
- python - 运行 Python 脚本的 Linux 服务不更新 SQL 数据库
- python - Python / Google API 客户端 v3 / 确保线程安全
- ios - SwiftUI 中默认展开的 List 或 OutlineGroup
- c# - 为什么 C# Math.Sin() 从 C++ sin() 返回不同的值?
- python - 在 Python 中使用 replace() 函数替换字符串中的元音
- amazon-web-services - AWS Fargate 可以像 Lambda 那样自动扩展吗?
- fonts - PostScript /(字体名称) FindFont 产生“未找到,使用 Courier”。
- c++ - 我可以隐藏第三方头文件中的定义吗?