asm volatile()中的冒号是什么意思
what does the colon in asm volatile() mean
我不确定我是否不小心修改了一些代码,但就是这样(我是内联汇编的初学者,但很习惯在不同的文件中汇编):-
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”,为什么那些变量“port”和“data”在“a”和“dN”旁边的括号中以及“%[=23=”之间有什么区别][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://whosebug.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)
之类的命名约束可以避免这种情况。 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
第一个操作数,而是 hard-coding %%dx
寄存器名称,只有在编译器选择 DX 时才正确。
内联 out32(0x80, 0x1234)
的优化构建会 而不是 使用前面的指令将端口号放入 DX,而不是选择 N
(无符号 8-位)约束,因为它更便宜。但是在最终的 compiler-generated asm 中没有任何 [=28=]x80
被填充,因为模板中没有 %0
供编译器扩展。
在将整个 asm 传递给 assembler 之前,想想编译器扩展的 printf 格式字符串之类的 asm 模板字符串。 (包括之前和之后的 compiler-generated 指令,来自编译其他 C 语句,以及一些约束,如 "r"
或 "d"
将 C 变量或表达式的值放入寄存器,如果它不是已经在那里了。)
%%
只是一个文字 %
,所以如果你想 hard-code 一个像 %eax
这样的 AT&T 注册名,你可以在扩展 Asm 模板。
您可以在 https://godbolt.org/ 上看到该 asm。 (使用“二进制”模式查看生成的 compiler-generated asm 是否实际上 assemble。使用内联 asm 时不能保证这一点。)
为了工作 outb
/ 等宏,许多代码库都定义了它们,我认为一些 libc 实现具有内联包装器,例如 MUSL,也可能是 glibc。如果你只是想要工作代码,当你不知道内联 asm 时不要尝试编写自己的代码。
相关:
How do you explain gcc's inline assembly constraints for the IN, OUT instructions of i386? 分解了一个 inb
包装函数。
C inline asm for x86 in / out port I/O has operand-size mismatch 为 "d"
选择的寄存器大小取决于 C 类型宽度,并且必须是 16 位,因为 in
/out
专门用DX。 (I/O地址space是64k,不像内存地址space。)
-
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html 说明书。阅读它。内联汇编很容易犯微妙但危险的错误;不要依赖反复试验/“似乎有效”。
https://gcc.gnu.org/wiki/DontUseInlineAsm。 (或者在这种情况下,在 libc header 或其他东西中找到一些 known-good in/out 包装函数并使用它们而不是编写自己的函数,如果你不知道内联 asm。)
-
我不确定我是否不小心修改了一些代码,但就是这样(我是内联汇编的初学者,但很习惯在不同的文件中汇编):-
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”,为什么那些变量“port”和“data”在“a”和“dN”旁边的括号中以及“%[=23=”之间有什么区别][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://whosebug.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)
之类的命名约束可以避免这种情况。 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
第一个操作数,而是 hard-coding %%dx
寄存器名称,只有在编译器选择 DX 时才正确。
内联 out32(0x80, 0x1234)
的优化构建会 而不是 使用前面的指令将端口号放入 DX,而不是选择 N
(无符号 8-位)约束,因为它更便宜。但是在最终的 compiler-generated asm 中没有任何 [=28=]x80
被填充,因为模板中没有 %0
供编译器扩展。
在将整个 asm 传递给 assembler 之前,想想编译器扩展的 printf 格式字符串之类的 asm 模板字符串。 (包括之前和之后的 compiler-generated 指令,来自编译其他 C 语句,以及一些约束,如 "r"
或 "d"
将 C 变量或表达式的值放入寄存器,如果它不是已经在那里了。)
%%
只是一个文字 %
,所以如果你想 hard-code 一个像 %eax
这样的 AT&T 注册名,你可以在扩展 Asm 模板。
您可以在 https://godbolt.org/ 上看到该 asm。 (使用“二进制”模式查看生成的 compiler-generated asm 是否实际上 assemble。使用内联 asm 时不能保证这一点。)
为了工作 outb
/ 等宏,许多代码库都定义了它们,我认为一些 libc 实现具有内联包装器,例如 MUSL,也可能是 glibc。如果你只是想要工作代码,当你不知道内联 asm 时不要尝试编写自己的代码。
相关:
How do you explain gcc's inline assembly constraints for the IN, OUT instructions of i386? 分解了一个
inb
包装函数。C inline asm for x86 in / out port I/O has operand-size mismatch 为
"d"
选择的寄存器大小取决于 C 类型宽度,并且必须是 16 位,因为in
/out
专门用DX。 (I/O地址space是64k,不像内存地址space。)https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html 说明书。阅读它。内联汇编很容易犯微妙但危险的错误;不要依赖反复试验/“似乎有效”。
https://gcc.gnu.org/wiki/DontUseInlineAsm。 (或者在这种情况下,在 libc header 或其他东西中找到一些 known-good in/out 包装函数并使用它们而不是编写自己的函数,如果你不知道内联 asm。)