在/linux0.11/kernel/trap.c文件中,第一次接触到C语言中的嵌入式汇编代码。详细的使用说明可以参考GNUgcc手册中第4章的内容或者参考文献《using assembly with gcc》。
具有输入和输出参数的嵌入式汇编的基本格式为:
asm(“汇编语句“
asm(”汇编语句“
: 输出寄存器
: 输入寄存器
: 会被修改的寄存器);
其中,”汇编语句“是你写汇编指令的地方;”输出寄存器“表示当这段嵌入汇编执行完之后,哪些寄存器用于存放输出数据。”输入寄存器“表示在执行汇编代码时,这里指定的一些寄存器中应该存放的输入值,他们分别对应着一C变量或者常数值。下面将举例说明嵌入式汇编的具体因使用方法。
例如1:
这段代码定义了1个嵌入式汇编函数。因为是宏语句,需要在一行上定义,因此这里使用反斜行‘\’将这些语句连成1行。第1行定义了宏的名称,即宏函数名称为get_seg_byte(seg,addr)。第3行定义了一个寄存器变量_reg 。 第4行上的_asm_表示嵌入汇编语句开始。从第4行到第7行的4条AT&T 格式的汇编语句。
第8行是输出寄存器,这句话的含义是在这段代码运行结束后将eax所代表的寄存器中的值放入_res 变量中,作为本函数的输出值。为了在上面汇编语句中使用该地址值,嵌入汇编程序规定把输入和输出寄存器统一按顺序编号,顺序是从输出寄存器序列从左到右从上到下以%0 开始,分别记为%0,%1,%2.。。.%9。因此,输出寄存器编号为%0 输入寄存器前一部分“”(seg)的编号为%1,而后一部分的编号为%2. 上面第6行上的%2代表(*(addr))这个内存偏移量。
现在分析4-7行上代码的具体作用。第1句将fs段寄存器的内容入栈;第2句将eax中的段值赋给fs段寄存器;第3句是把fs:(*addr))所制定的字节放入al寄存器。当执行完汇编语句后,输出寄存器eax的值将被放入_res。
经过上面的分析,我们知道宏名称中的seg代表一指定的内存段值,而addr表示一内存偏移地址量。到现在为止,我们应该很清楚这段程序的功能。该宏函数是从指定的段和偏移量的内存地址处取一个字节。
例如2:
1-3行这三句是常用的汇编语句,用以清方向标识位,城府保存值。第四行说明没有用到输出寄存器。第5行的含义是将count-1的值将在到ecx寄存器中(加载码是“c“)fill_value 加载到eax中,dest放到edi中。 为什们要让gcc编译程序去做这样的加载,而不让我们自己做呢?因为gcc在它进行寄存器分配时可以进行某些优化工作。例如fill_value值可能已经加载到eax中。如果在一个循环语句中的话,gcc可能在整个循环操作中保存eax,这样就可以在每次循环中少用1个movl语句。
最后1行是告诉gcc这些寄存器中的值已经改变了。很奇怪吧?不过,gcc知道你拿这些寄存器做了什么后,这确实能够对gcc的优化操作有所帮助。
下面是可能会用的寄存器加载码及其具体含义:
代码 说明
a 使用寄存器eax
b 使用寄存器ebx
c 使用寄存器ecx
d 使用寄存器edx
S 使用寄存器esi
D 使用寄存器edi
q 使用动态分配字节可寻址寄存器 (eax、ebx、ecx或edx)
r 使用任意动态分配的寄存器
g 使用通用有效的地址即可(eax、ebx、edx、ecx 或者内存变量)
A 使用eax和edx联合(64bit)
m 使用内存地址
o 使用内存地址,并可以加载偏移值
I 使用常数0-31
J 使用常数0-63
K 使用常数0-255
L 使用常数0-65536
M 使用常数0-3
N 使用1字节常数(0-255)
O 使用常数0-31
下面的例子不是让自己制定那个变量使用那个寄存器,而是让gcc为你选择
asm(“leal(%1,%1,4),%0”
:“r”(y)
:“0”(x));
第1句leal(r1,r2,4),r3 语句表示r3=r1+r2& TI mes;4。这个例子可以非常快的将x成5.其中“%0”,“%1” 是指gcc自动分配的寄存器。这里%1 代表输入值x要放入的寄存器,”%0“表示输出寄存器。所以如果gcc将r指定为eax的话,那么上面汇编语句的含义为”leal(eax,eax,4),eax“ 注意 如果不希望汇编语句被gcc优化而挪动地方,就需要在asm符号后面添加vola TI le关键字
asm vola TI le();或者
__asm__ __vola TI le__();