您的位置:首页 > 运维架构 > Linux

linux gcc 内嵌汇编

2013-11-19 22:14 155 查看
通常嵌入到 C 代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到完整的内联汇编格式,即汇编模板:

__asm__   __volatile__ ("asm statements" : outputs : inputs : registers-modified);
__asm__ 表示后面的代码为内嵌汇编,asm
是 __asm__ 的别名。__volatile__ 表示编译器不要优化代码,后面的指令保留原样,volatile是它的别名。括号里面是汇编指令。 

插入到 C 代码中的汇编语句是以":"分隔的四个部分,其中第一部分就是汇编代码本身,通常称为指令部,其格式和在汇编语言中使用的格式基本相同。指令部分是必须的,而其它部分则可以根据实际情况而省略。如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。

下面介绍模板中的四个部分:

1、汇编语句模板

    汇编语句模板由汇编语句序列组成,语句之间使用“;”、“\n”或
“\n\t” 分开。指令中的操作数可以使用占位符引用 C语言变量,操作数占位符最多10
个,名称如下:%0,%1,…,%9。指令中使用占位符表示的操作数,总被视为 long型(4个字节),但对其施加的操作根据指令可以是字或者字节,当把操作数当作字或者字节使用时,默认为低字或者低字节。对字节操作可以显式的指明是低字节还是次字节。方法是在
%和序号之间插入一个字母,b 代表低字节,h
代表高字节,例如:%h1。

2、输出部分

    输出部分描述输出操作数,不同的操作数描述符之间用逗号格开,每个操作数描述符由限定字符串和 C
语言变量组成。每个输出操作数的限定字符串必须包含“=”表示他是一个输出操作数。例如:

 

         __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )

描述符字符串表示对该变量的限制条件,这样 GCC 就可以根据这些条件决定如何分配寄存器,如何产生必要的代码处理指令操作数与 C表达式或 C
变量之间的联系。

3、输入部分

    输入部分描述输入操作数,不同的操作数描述符之间使用逗号格开,每个操作数描述符由限定字符串和 C
语言表达式或者 C 语言变量组成。 示例如下:

例 1 :

__asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));

例 2:

Static __inline__ void __set_bit(int nr, volatile void * addr)
{

__asm__(
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}

后例功能是将 (*addr) 的第 nr
位设为 1。第一个占位符 %0 与 C 语言变量 ADDR
对应,第二个占位符 %1 与 C 语言变量 nr
对应。因此上面的汇编语句代码与下面的伪代码等价:btsl nr, ADDR,该指令的两个操作数不能全是内存变量,因此将 nr的限定字符串指定为“Ir”,将 nr
与立即数或者寄存器相关联,这样两个操作数中只有 ADDR为内存变量。

4、限制字符

    限制字符有很多种,有些是与特定体系结构相关,此处仅列出常用的限定字符和i386中可能用到的一些常用的限定符。它们的作用是指示编译器如何处理其后的 C语言变量与指令操作数之间的关系。

5、破坏描述部分

    破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有 “memory”。例如:“%eax”,“%ebx”,“memory”等。

第一个汇编的例子:输出字符

int main()
{
char const * MESSAGE = "hello world\n";
__asm__ __volatile__ ( "movl $4, %%eax;"
"movl $1, %%ebx;"
"movl %0, %%ecx;"
"movl $12 , %%edx;"
"int $0x80;"
: "=m"   (MESSAGE)
);
//printf("hello\n");
return 0;
}

例子说明:

1.首先写出汇编的标识符:__asm__;然后写出模板的指令语句,在指令语句中寄存器的的前面要加两个%,但是在破坏描述部分语句中只用加一个%,具体参加例子2;

2.每条汇编语句用“”括起来,并用;标识语句的结束;语句中的%x标识站位符,由输入和输出语句中操作数描述符描述;内嵌汇编可以传递最多十个描述符;m标识变量为内存变量,= 标明是输出操作符;既然MESSAGE为输入变量,可以在模板中,直接把MESSAGE中的值直接写到ECX中,修改后的代码,如下所示:

int main()
{
char const * MESSAGE = "hello world\n";
__asm__ __volatile__ ( "movl $4, %%eax;"
"movl $1, %%ebx;"
"movl $12 , %%edx;"
"int $0x80;"
:
: "c" (MESSAGE)
);
return 0;
}实例代码2:
/* inline.c */
int main()
{
int a = 10, b = 0;
__asm__ __volatile__("movl %1, %%eax;\\n\\r"
"movl %%eax, %0;"
:"=r"(b) /* 输出 */
:"r"(a) /* 输入 */
:"%eax"); /* 不受影响的寄存器 */

printf("Result: %d, %d\\n", a, b);
}代码理解:

上面的程序完成将变量a的值赋予变量b,有几点需要说明:

变量b是输出操作数,通过%0来引用,而变量a是输入操作数,通过%1来引用。
输入操作数和输出操作数都使用r进行约束,表示将变量a和变量b存储在寄存器中。输入约束和输出约束的不同点在于输出约束多一个约束修饰符'='。
在内联汇编语句中使用寄存器eax时,寄存器名前应该加两个'%',即%%eax。内联汇编中使用%0、%1等来标识变量,任何只带一个'%'的标识符都看成是操作数,而不是寄存器。
内联汇编语句的最后一个部分告诉GCC它将改变寄存器eax中的值,GCC在处理时不应使用该寄存器来存储任何其它的值。
由于变量b被指定成输出操作数,当内联汇编语句执行完毕后,它所保存的值将被更新。
在内联汇编中用到的操作数从输出部的第一个约束开始编号,序号从0开始,每个约束记数一次,指令部要引用这些操作数时,只需在序号前加上'%'作为前缀就可以了。需要注意的是,内联汇编语句的指令部在引用一个操作数时总是将其作为32位的长字使用,但实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:

限定符意义
"m"、"v"、"o"内存单元
"r"任何寄存器
"q"寄存器eax、ebx、ecx、edx之一
"i"、"h"直接操作数
"E"和"F"浮点数
"g"任意
"a"、"b"、"c"、"d"分别表示寄存器eax、ebx、ecx和edx
"S"和"D"寄存器esi、edi
"I"常数(0至31)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: