最近阅读linux源码,发现很多以asm volatile开头的C语言内嵌汇编语代码的函数,于是了解了一下,c语言内嵌汇编代码格式为:__ asm __ __ volatile __”(“statements”: “output_regs”:“input_regs”:;clobbered_regs):
“__ asm ”表示后面的代码为内嵌汇编,“asm”是“ asm ”的别名。
“ volatile __”表示编译器不要优化代码,后面的指令保留原样,
例子:
#include <stdio.h>
int main(){
unsigned int val1=1;
unsigned int val2=2;
unsigned int val3=0;
printf("val1:%d,val2:%d,val3:%d\n",val1,val2,val3);
asm volatile(
"movl $0,%%eax\n\t"
"addl %1,%%eax\n\t"
"addl %2,%%eax\n\t"
"movl %%eax,%0\n\t"
:"=m"(val3)
:"c"(val1),"d"(val2)
);
printf("val1:%d+val2:%d=val3:%d\n",val1,val2,val3);
}
其中“%n”,代表占位符,内嵌汇编依赖占位符与指令操作数相对应,,n指令操作数即输出部分和输入部分中的第n个变量,%0即输出部分的val3,%1即输入部分的val1,如此。操作数前用引号括起来的字符串是对该操作数的限制或者要求,“c”代表使用ecx装val1的值,“d”代表用edx装载val2;“=m”代表将值写入内存变量val3中。
在linux系统上运行如下:
内嵌汇编代码
asm(
汇编语句模板:
输出部分:
输入部分:
破坏描边部分
);
汇编语句模板
汇编语句模板由汇编语句序列组成,语句之间使用“;”、“\n”或“\n\t”分开。
指令中的操作数可以使用占位符引用C语言变量,操作数占位符最多10个,名称如下:%0,%1…,%9。
指令中使用占位符表示的操作数,总被视为long型(4,个字节),但对其施加的操作,根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节。 对字节操作可以显式的指明是低字节还是次字节。方法是在%和序号之间插入一个字母, “b”代表低字节,“h”代表高字节,例如:%h1。
输出部分
输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串(用引号括起来的字符)和C语言变量(val1,val2,val3)组成。每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。
输入部分
输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和C语言表达式或者C语言变量组成。
破坏描边部分
一下参考自:https://blog.csdn.net/dongyu_1989/article/details/80386061
通常编写程序只使用一种语言:高级语言或者汇编语言。高级语言编译的步骤大致如下:
l
预处理;
l
编译
l
汇编
l
链接
我们这里只关心第二步编译(将C代码转换成汇编代码):因为所有的代码都是用高级语言编写,编译器可以识别各种语句的作用,在转换的过程中所有的寄存器都由编译器决定如何分配使用,它有能力保证寄存器的使用不会冲突;也可以利用寄存器作为变量的缓冲区,因为寄存器的访问速度比内存快很多倍。如果全部使用汇编语言则由程序员去控制寄存器的使用,只能靠程序员去保证寄存器使用的正确性。但是如果两种语言混用情况就变复杂了,因为内嵌的汇编代码可以直接使用寄存器,而编译器在转换的时候并不去检查内嵌的汇编代码使用了哪些寄存器(因为很难检测汇编指令使用了哪些寄存器,例如有些指令隐式修改寄存器,有时内嵌的汇编代码会调用其他子过程而子过程也会修改寄存器),因此需要一种机制通知编译器我们使用了哪些寄存器(程序员自己知道内嵌汇编代码中使用了哪些寄存器),否则对这些寄存器的使用就有可能导致错误,修改描述部分可以起到这种作用。当然内嵌汇编的输入输出部分指明的寄存器或者指定为“r”,“g”型由编译器去分配的寄存器就不需要在破坏描述部分去描述,因为编译器已经知道了。
破坏描述符由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有“memory”。例如:“%eax”,“%ebx”,“memory”等。
限定字符
限定字符 | 含义 |
---|---|
“a” | 将输入变量放入eax |
“b” | 将输入变量放入ebx |
“c” | 将输入变量放入ecx |
“d” | 将输入变量放入edx |
“s” | 将输入变量放入esi |
“D” | 将输入变量放入edi |
“q” | 将输入变量放入eax,ebx ,ecx ,edx中的一个 |
“r” | 将输入变量放入通用寄存器,也就是eax ,ebx,ecx,edx,esi,edi中的一个 |
“A” | 放入eax和edx,把eax和edx,合成一个64位的寄存器(uselong longs) |
“m” | 内存变量 |
“o” | 操作数为内存变量,但是其寻址方式是偏移量类型,也即是基址寻址,或者是基址加变址寻址 |
“V” | 操作数为内存变量,但寻址方式不是偏移量类型 |
“,” | 操作数为内存变量,但寻址方式为自动增量 |
“p” | 操作数是一个合法的内存地址(指针) |
“g” | 将输入变量放入eax,ebx,ecx ,edx中的一个或者作为内存变量 |
“X” | 操作数可以是任何类型 |
“I” | 0-31 之间的立即数(用于32位移位指令) |
“J” | 0-63 之间的立即数(用于64 位移位指令) |
“N” | 0-255 ,之间的立即数(用于out 指令) |
“i” | 立即数 |
“n” | 立即数,有些系统不支持除字以外的立即数,这些系统应该使用“n”而不是“i” |
“=” | 操作数在指令中是只写的(输出操作数) |
“+” | 操作数在指令中是读写类型的(输入输出操作数) |
“f” | 浮点数 |
“t” | 第一个浮点寄存器 |
“u” | 第二个浮点寄存器 |
“G” | 标准的80387 |
% | 该操作数可以和下一个操作数交换位置 |
# | 部分注释 |
* | 表示如果选用寄存器,则气候的字母被忽略 |
“&” | 表示输入和输出操作数不能使用相同的寄存器, |
这样可以避免很多错误。 |