如果您需要使用-masm=intel,您需要确保您的内联汇编采用 Intel 语法。 Intel 语法是 dst, src(AT&T 语法是相反的)。这有点related answer 有一些有用的信息,关于 NASM 的 Intel 变体1(不是 GAS 的变体)和 AT&T 语法之间的一些差异:
有关如何将 NASM 英特尔语法转换为 GAS 的 AT&T 语法的信息可以在此Stackoverflow Answer 中找到,并且在此IBM article 中提供了许多有用的信息。
[剪辑]
总的来说,最大的区别是:
- 对于 AT&T 语法,源位于左侧,目标位于右侧,而英特尔则相反。
- 使用 AT&T 语法,寄存器名称前面带有
%
- 在 AT&T 语法中,立即数前面带有
$
- 内存操作数可能是最大的区别。 NASM 使用 [segment:disp+base+index*scale] 代替 GAS 的 segment:disp(base, index, scale) 语法。
您的代码中的问题是源操作数和目标操作数必须与您使用的原始 AT&T 语法相反。这段代码:
asm volatile ( "inb %1, %0"
: "=a"(ret)
: "Nd"(port) );
需要:
asm volatile ( "inb %0, %1"
: "=a"(ret)
: "Nd"(port) );
关于您的更新:问题在于 Intel 语法中的立即数没有以 $ 开头。这行有问题:
asm volatile ( "outb $0x80, %0" : : "a"(0) );
应该是:
asm volatile ( "outb 0x80, %0" : : "a"(0) );
如果你有一个合适的 outb 函数,你可以这样做:
#include <stdint.h>
#include "ioaccess.h"
uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb %0, %1"
: "=a"(ret)
: "Nd"(port) );
return ret;
}
void outb(uint16_t port, uint8_t byte)
{
asm volatile ( "outb %1, %0"
:
: "a"(byte),
"Nd"(port) );
}
void io_wait(void)
{
outb (0x80, 0);
}
支持 AT&T 和 Intel dialects 的稍微复杂一点的版本:
asm 模板中的多种汇编语言在 x86 等目标上,
GCC 支持多种汇编语言。 -masm 选项控制
GCC 使用哪种方言作为内联汇编程序的默认值。这
-masm 选项的目标特定文档包含列表
支持的方言,以及默认方言(如果选项是)
未指定。理解这些信息可能很重要,因为
使用一种方言编译时可以正常工作的汇编代码
如果使用另一个编译可能会失败。请参阅 x86 选项。
如果您的代码需要支持多种汇编语言(例如
例如,如果您正在编写需要支持
各种编译选项),使用这种形式的结构:
{ 方言0 |方言1 |方言2... }
在 x86 和 x86-64 目标上,有两种方言。 Dialect0 是 AT&T 语法,Dialect1 是 Intel 语法。这些功能可以通过这种方式重新设计:
#include <stdint.h>
#include "ioaccess.h"
uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb {%[port], %[retreg] | %[retreg], %[port]}"
: [retreg]"=a"(ret)
: [port]"Nd"(port) );
return ret;
}
void outb(uint16_t port, uint8_t byte)
{
asm volatile ( "outb {%[byte], %[port] | %[port], %[byte]}"
:
: [byte]"a"(byte),
[port]"Nd"(port) );
}
void io_wait(void)
{
outb (0x80, 0);
}
我还给出了约束符号名称,而不是使用 %0 和 %1 以使内联程序集更易于阅读和维护。从 GCC 文档中,每个约束具有以下形式:
[ [asmSymbolicName] ] 约束(cvariablename)
地点:
asmSymbolicName
指定操作数的符号名称。通过将其括在方括号中来引用汇编程序模板中的名称(即“%[Value]”)。名称的范围是包含定义的 asm 语句。任何有效的 C 变量名称都是可接受的,包括已在周围代码中定义的名称。同一 asm 语句中的两个操作数不能使用相同的符号名。
当不使用 asmSymbolicName 时,在汇编器模板的操作数列表中使用操作数的(从零开始的)位置。例如,如果有三个输出操作数,则在模板中使用“%0”来引用第一个,“%1”代表第二个,“%2”代表第三个。
无论您使用-masm=intel 还是-masm=att 选项编译,此版本都应该可以工作2
脚注
-
1虽然 NASM 英特尔方言和 GAS(GNU 汇编器)英特尔语法相似,但还是有一些区别。一种是 NASM Intel 语法使用 [segment:disp+base+index*scale],其中可以在
[] 内指定一个段,而 GAS 的 Intel 语法需要使用 segment 的段外部: [disp+base+index*scale].
-
2虽然代码可以运行,但您应该将所有这些基本函数直接放在
ioaccess.h 文件中,并从包含它们的.c 文件中删除它们。因为您将这些基本函数放在单独的.c 文件(外部链接)中,所以编译器无法尽可能地优化它们。您可以将函数修改为static inline 类型并将它们直接放在标题中。然后,编译器将能够通过消除函数调用开销来优化代码,并减少对额外加载和存储的需求。您将希望使用高于-O0 的优化进行编译。考虑-O2 或-O3。
-
关于操作系统开发的特别说明:
- 有许多玩具操作系统(OSDev Wiki 上的示例、教程和甚至代码)无法使用优化。许多失败是由于bad/poor inline assembly 或使用未定义的行为。内联汇编应该作为最后的手段。如果您的内核没有在优化的情况下运行,则可能不是编译器中的错误(有可能只是不太可能)。
- 请注意@PeterCordes 回答中有关可能触发 DMA 读取的端口访问的建议。