c language inline asm

来源:互联网 发布:淘宝客服主管 提成 编辑:程序博客网 时间:2024/04/30 01:31

内嵌汇编语法如下: 
           __asm__(
汇编语句模板输出部分输入部分破坏描述部分
共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。


__asm__("assembly code": "=a"(result):"d"(data1),"c"(data2));
把变量data1存放在edx寄存器中, 把c变量data2存放到ecx寄存器中, 内联汇编的结果
将存放在eax寄存器中, 然后传送给变量result

 int aa=10, bb=20, cc=30;

__asm__("add %%bx,%%ax":"=a"(dd):"a"(aa),"b"(cc));

(1)把 变量aa的值赋给寄存器ax 把变量cc的值赋给寄存器bx ,

(2)bx + ax 的值赋给 ax 

(3)从寄存器ax取值赋给变量dd

1.序号占位符:

 __asm__("add %[v2],%[v1]":[v3]"=a"(dd):[v1]"a"(aa),[v2]"b"(dd));

变量dd存入寄存器b 并给他们取别名 v2 (2)变量aa存入寄存器ax 并取别名v1 

v2+v1存入寄存器ax ax寄存器赋值给dd


GCC规定:在带有C/C++表达式的内联汇编语句的指令列表里列出的寄存器名称前面必须使用两个百分号(%%),一区别于占位符语法;

GCC对占位符进行编译的时候,会将每一个占位符替换为对应的Input/Output操作表达式所指定的寄存器/内存/立即数;

例如:
__asm__("addl %1,%0\n\t":"=a"(__out):"m"(__in1),"a"(__in2));
这个语句中,%0对应Output操作表达式"=a"(__out),而"=a"(__out)指定的寄存器是%eax,所以,占位符%0被替换为%eax;占位符%1对应Input操作表达式"m"(__in1),而"m"(__in1)被指定为内存,所以,占位符%1被替换位__in1的内存地址;
用一句话描述:序号占位符就是前面描述的%0、%1、%2、%3、%4、%5、%6、%7、%8、%9;其中,每一个占位符对应一个Input/Output的C/C++表达式;

2.名称占位符:

由于GCC中限制这种占位符的个数最多只能由这10个,这也就限制了Input/Output操作表达式中C/C++表达式的数量做多只能有10个;如果需要的C/C++表达式的数量超过10个,那么,这些需要占位符就不够用了;

GCC内联汇编提供了名称占位符来解决这个问题;即:使用一个名字字符串一个C/C++表达式对应;这个名字字符串就称为名称占位符;而这个名字通常使用与C/C++表达式中的变量完全相同的名字;
使用名字占位符时,内联汇编的Input/Output操作表达式中的C/C++表达式的格式如下:
[name] "constraint"(变量)
此时,指令列表中的占位符的书写格式如下:
%[name]
这个格式等价于序号占位符中的%0,%1,$2等等;
使用名称占位符时,一个name对应一个变量;
例如:
__asm__("imull %[value1],%[value2]"
        :[value2] "=r"(data2)
        :[value1] "r"(data1),"0"(data2));
此例中,名称占位符value1就对应变量data1,名称占位符value2对应变量data2;GCC编译的时候,同样会把这两个占位符分别替换成对应的变量所使用的寄存器/内存地址/立即数;而且也增强了代码的可读性;



1, 指定输入值和输出值, 输入值和输出值的列表格式为:
"constraint"(variable), 其中variable是程序中声明的c变量, 在扩展asm格式中, 局部和全局变量都可以使用,
使用constrant(约束)定义把变量存放到哪(输入)或从哪里传送变量(输出)
约束使用单一的字符, 如下:
约束                                   描述
a                         使用%eax, %ax, %al寄存器
b                         使用%ebx, %bx, %bl寄存器
c                         使用%ecx, %cx, %cl寄存器
d                         使用%edx, %dx, %dl寄存器
S                         使用%esi, %si寄存器
D                         使用%edi, %di寄存器
r                         使用任何可用的通用寄存器
q                         使用%eax, %ebx, %ecx,%edx之一
A                         对于64位值使用%eax, %edx寄存器
f                         使用浮点寄存器
t                         使用第一个(顶部)的浮点寄存器
u                         使用第二个浮点寄存器
m                         使用变量的内存位置
o                         使用偏移内存位置
V                         只使用直接内存位置
i                         使用立即整数值
n                         使用值已知的立即整数值
g                         使用任何可用的寄存器和内存位置


除了这些约束之外, 输出值还包含一个约束修饰符:
输出修饰符                             描述
+                         可以读取和写入操作数
=                         只能写入操作数
%                         如果有必要操作数可以和下一个操作数切换
&                         在内联函数完成之前, 可以删除和重新使用操作数


五、寄存器/内存修改标示(Clobber/Modify)
有时候,当你想通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去;那么你就可以在Clobber/Modify部分声明这些寄存器或内存;
1.寄存器修改通知:
这种情况一般发生在一个寄存器出现在指令列表中,但又不是Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式中使用"r"或"g"约束时由GCC选择的,同时,此寄存器被指令列表中的指令所修改,而这个寄存器只供当前内联汇编语句使用的情况;比如:
__asm__("movl %0,%%ebx"::"a"(__foo):"bx");
//这个内联汇编语句中,%ebx出现在指令列表中,并且被指令修改了,但是却未被任何Input/Output操作表达式是所指定,所以,你需要在Clobber/Modify部分指定"bx",以让GCC知道这一点;
因为你在Input/Output操作表达式中指定的寄存器,或当你为一些Input/Output操作表达式使用"r"/"g"约束,让GCC为你选择一个寄存器时,GCC对这些寄存器的状态是非常清楚的,它知道这些寄存器是被修改的,你根本不需要在Clobber/Modify部分声明它们;但除此之外,GCC对剩下的寄存器中哪些会被当前内联汇编语句所修改则一无所知;所以,如果你真的在当前内联汇编指令中修改了它们,那么就最好在Clobber/Modify部分声明它们,让GCC针对这些寄存器做相应的处理;否则,有可能会造成寄存器不一致,从而造成程序执行错误;

在Clobber/Modify部分声明这些寄存器的方法很简单,只需要将寄存器的名字用双引号括起来就可以;如果要声明多个寄存器,则相邻两个寄存器名字之间用逗号隔开;
例如:
__asm__("movl %0,%%ebx; popl %%ecx"::"a"(__foo):"bx","cx");
这个语句中,声明了bx和cx,告诉GCC:寄存器%ebx和%ecx可能会被修改,要求GCC考虑这个因素;
寄存器名称串:
"al"/"ax"/"eax":代表寄存器%eax
"bl"/"bx"/"ebx":代表寄存器%ebx
"cl"/"cx"/"ecx":代表寄存器%ecx
"dl"/"dx"/"edx":代表寄存器%edx
"si"/"esi":代表寄存器%esi
"di"/"edi":代表寄存器%edi
所以,只需要使用"ax","bx","cx","dx","si","di"就可以了,因为他们都代表对应的寄存器;
如果你在一个内敛汇编语句的Clobber/Modify部分向GCC声明了某个寄存器内存发生了改变,GCC在编译时,如果发现这个被声明的寄存器的内容在此内联汇编之后还要继续使用,那么,GCC会首先将此寄存器的内容保存起来,然后在此内联汇编语句的相关代码生成之后,再将其内容回复;
另外需要注意的是,如果你在Clobber/Modify部分声明了一个寄存器,那么这个寄存器将不能再被用作当前内敛汇编语句的Input/Output操作表达式的寄存器约束,如果Input/Output操作表达式的寄存器约束被指定为"r"/"g",GCC也不会选择已经被声明在Clobber/Modify部分中的寄存器;
例如:
__asm__("movl %0,%%ebx"::"a"(__foo):"ax","bx");
这条语句中的Input操作表达式"a"(__foo)中已经指定了寄存器%eax,那么在Clobber/Modify部分中个列出的"ax"就是非法的;编译时,GCC会报错;
2.内存修改通知:
除了寄存器的内容会被修改之外,内存的内容也会被修改;如果一个内联汇编语句的指令列表中的指令对内存进行了修改,或者在此内联汇编出现的地方,内存内容可能发生改变,而被改变的内存地址你没有在其Output操作表达式中使用"m"约束,这种情况下,你需要使用在Clobber/Modify部分使用字符串"memory"向GCC声明:"在这里,内存发生了,或可能发生了改变";
例如:
void* memset(void* s, char c, size_t count)
{
  __asm__("cld\n\d"
          "rep\n\t"
          "stosb"
          :/*no output*/
          :"a"(c),"D"(s),"c"(count)
          :"cx","di","memory");
  return s;
}
如果一个内联汇编语句的Clobber/Modify部分存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么,在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝;因为这个时候寄存器中的拷贝很可能已经和内存处的内容不一致了;
3.标志寄存器修改通知:
当一个内联汇编中包含影响标志寄存器eflags的条件,那么也需要在Clobber/Modify部分中使用"cc"来向GCC声明这一点

5.修饰符:
等号(=)和加号(+)作为修饰符,只能用于Output部分;等号(=)表示当前输出表达式的属性为只写,加号(+)表示当前输出表达式的属性为可读可写;这两个修饰符用于约束对输出表达式的操作,它们俩被写在输出表达式的约束部分中,并且只能写在第一个字符的位置;
符号&也写在输出表达式的约束部分,用于约束寄存器的分配,但是只能写在约束部分的第二个字符的位置上;


用符号&进行修饰时,等于向GCC声明:"GCC不得为任何Input操作表达式分配与此Output操作表达式相同的寄存器";

其原因是修饰符&意味着被其修饰的Output操作表达式要在所有的Input操作表达式被输入之前输出;

即:GCC会先使用输出值对被修饰符&修饰的Output操作表达式进行填充,然后,才对Input操作表达式进行输入;

这样的话,如果不使用修饰符&对Output操作表达式进行修饰,一旦后面的Input操作表达式使用了与Output操作表达式相同的寄存器,就会产生输入输出数据混乱的情况;相反,如果没有用修饰符&修饰输出操作表达式,那么,就意味着GCC会先把Input操作表达式的值输入到选定的寄存器中,然后经过处理,最后才用输出值填充对应的Output操作表达式;


所以,修饰符&的作用就是要求GCC编译器为所有的Input操作表达式分配别的寄存器,而不会分配与被修饰符&修饰的Output操作表达式相同的寄存器;修饰符&也写在操作约束中,即:&约束;由于GCC已经规定加号(+)或等号(=)占据约束的第一个字符,那么&约束只能占用第二个字符;


例如:
int __out, __in1, __in2;
__asm__("popl %0\n\t"
        "movl %1,%%esi\n\t"
        "movl %2,%%edi\n\t"
        :"=&a"(__out)
        :"r"(__in1),"r"(__in2));


注意:如果一个Output操作表达式的寄存器约束被指定为某个寄存器,只有当至少存在一个Input操作表达式的寄存器约束为可选约束(意思是GCC可以从多个寄存器中选取一个,或使用非寄存器方式)时,比如"r"或"g"时,此Output操作表达式使用符号&修饰才有意义;如果你为所有的Input操作表达式指定了固定的寄存器,或使用内存/立即数约束时,则此Output操作表达式使用符号&修饰没有任何意义;
比如:
__asm__("popl %0\n\t"
        "movl %1,%esi\n\t"
        "movl %2,%edi\n\t"
        :"=&a"(__out)
        :"m"(__in1),"c"(__in2));
此例中的Output操作表达式完全没有必要使用符号&来修饰,因为__in1和__in2都已经被指定了固定的寄存器,或使用了内存方式,GCC无从选择;
如果你已经为某个Output操作表达式指定了修饰符&,并指定了固定的寄存器,那么,就不能再为任何Input操作表达式指定这个寄存器了,否则会出现编译报错;
比如:
__asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&a"(__out):"a"(__in1),"c"(__in2));
对这条语句的编译就会报错;
相反,你也可以为Output指定可选约束,比如"r"或"g"等,让GCC为此Output操作表达式选择合适的寄存器,或使用内存方式,GCC在选择的时候,会排除掉已经被Input操作表达式所使用过的所有寄存器,然后在剩下的寄存器中选择,或者干脆使用内存方式;
比如:
__asm__("popl %0; movl %1,%%esi; movl %2,%%edi;":"=&r"(__out):"a"(__in1),"c"(__in2));
这三个修饰符只能用在Output操作表达式中,而修饰符%则恰恰相反,它只能用在Input操作表达式中;
修饰符%用于向GCC声明:"当前Input操作表达式中的C/C++表达式可以与下一个Input操作表达式中的C/C++表达式互换";这个修饰符一般用于符合交换律运算的地方;比如:加、乘、按位与&、按位或|等等;
例如:
__asm__("addl %1,%0\n\t":"=r"(__out):"%r"(__in1),"0"(__in2));
其中,"0"(__in2)表示使用与第一个Input操作表达式("r"(__in1))相同的寄存器或内存;
由于使用符号%修饰__in1的寄存器方式r,那么就表示,__in1与__in2可以互换位置;加法的两个操作数交换位置之后,和不变;
修饰符  I/O  意义
=        O    表示此Output操作表达式是只写的
+        O    表示此Output操作表达式是可读可写的
&        O    表示此Output操作表达式独占为其指定的寄存器
%        I    表示此Input操作表达式中的C/C++表达式可以与下一个Input操作表达式中的C/C++表达式互换



0 0