我目前正在x86操作系统上工作。我尝试从here实现inb函数,它给了我Error: Operand type mismatch for `in'
。
这可能与outb
或io_wait
相同。
我正在使用Intel语法(-masm=intel
),但不知道该怎么办。
代码:
#include <stdint.h>
#include "ioaccess.h"
uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb %1, %0"
: "=a"(ret)
: "Nd"(port) );
return ret;
}
使用AT&T语法,确实可以。
对于outb,在反转操作数之后我遇到了另一个问题:
void io_wait(void)
{
asm volatile ( "outb $0x80, %0" : : "a"(0) );
}
Error: operand size mismatch for `out'
答案 0 :(得分:4)
如果需要使用-masm=intel
,则需要确保内联程序集采用Intel语法。 Intel语法是dst,src(AT&T语法是相反的)。 related answer有点有关NASM的Intel变体 1 (不是GAS的变体)和AT&T语法之间的一些区别的有用信息:
此Stackoverflow Answer中提供了有关如何将NASM Intel语法转换为GAS的AT&T语法的信息,此IBM article中提供了许多有用的信息。
[snip]
通常最大的区别是:
- 使用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和英特尔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]]约束(变量名称)
位置:
asmSymbolicName
指定操作数的符号名称。将名称括在方括号中即可引用汇编程序模板中的名称(即“%[Value]”)。名称的范围是包含定义的asm语句。可以接受任何有效的C变量名称,包括周围代码中已定义的名称。同一asm语句中的两个操作数都不能使用相同的符号名。
不使用asmSymbolicName时,请在汇编器模板的操作数列表中使用操作数(从零开始)的位置。例如,如果有三个输出操作数,则在模板中使用“%0”来引用第一个,第二个是“%1”,第三个是“%2”。
无论您使用-masm=intel
还是-masm=att
选项进行编译,此版本均应运行 2
[]
内指定一个段,而GAS的Intel语法要求在该段之外使用段:[disp + base + index * scale] 。ioaccess.h
文件中,并从包含它们的.c
文件中删除它们。因为您将这些基本功能放置在单独的.c
文件中(外部链接),所以编译器无法尽其所能地优化它们。您可以将函数修改为static inline
类型,并将其直接放置在标题中。然后,编译器将能够通过消除函数调用开销来优化代码,并减少对额外加载和存储的需求。您将希望使用高于-O0
的优化进行编译。考虑-O2
或-O3
。 答案 1 :(得分:4)
对于GNU C内联汇编https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html,可以使用方言替代方式编写使用或不使用-masm=intel
的代码(对于其他人使用的标头,这是个好主意可能包括。)
它的工作方式类似于"{at&t stuff | intel stuff}"
:编译器根据当前模式选择保留|
的哪一侧。
AT&T与Intel语法之间的主要区别在于操作数列表是反向的,因此通常您会遇到类似"inb {%1,%0 | %0,%1}"
的内容。
这是@MichaelPetch的不错的功能版本,使用方言替代方式:
// make this a header: these single instructions can inline more cheaply
// than setting up args for a function call
#include <stdint.h>
static inline
uint8_t inb(uint16_t port)
{
uint8_t ret;
asm volatile ( "inb {%1, %0 | %0, %1}"
: "=a"(ret)
: "Nd"(port) );
return ret;
}
static inline
void outb(uint16_t port, uint8_t byte)
{
asm volatile ( "outb {%1, %0 | %0, %1}"
:
: "a"(byte),
"Nd"(port) );
}
static inline
void io_wait(void) {
outb (0x80, 0);
}
Linux / Glibc sys/io.h
宏有时使用%w1
将约束扩展为16位寄存器名称,但是使用正确大小的类型也可以。
如果您想要这些的内存屏障版本利用in
/ out
(或多或少)像lock
ed指令或{{ 1}},添加一个mfence
Clobber以停止编译时对其上的内存访问的重新排序。
如果端口I / O可以触发您最近写入的其他内存的DMA读取,您可能还需要一个"memory"
垃圾桶 。 (现代的x86具有相干的DMA,因此您不必显式刷新它,但是您不能让编译器在"memory"
之后重新排序它,甚至不能优化掉看似无效的存储。)
GAS不支持保存旧模式,因此,在串联asm中使用outb
会使您无法知道是否切换回.intel_syntax noprefix
。
但这通常还是不够的:在填充模板时,您需要让编译器以与语法模式匹配的方式格式化操作数。例如端口号需要扩展为.att_syntax
或$imm
(AT&T),而不是%dx
或dx
,而不带imm
前缀。
或者对于内存操作数,$
或[rdi + rax*4 + 8]
。
但是您仍然需要自己8(%rdi, %rax, 4)
来反转操作数列表;编译器不会尝试为您执行此操作。根据简单的规则,它纯粹是将文本替换为模板。