linux gcc 内嵌汇编
2013-11-19 22:14
155 查看
通常嵌入到 C 代码中的汇编语句很难做到与其它部分没有任何关系,因此更多时候需要用到完整的内联汇编格式,即汇编模板:
是 __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 :
例 2:
后例功能是将 (*addr) 的第 nr
位设为 1。第一个占位符 %0 与 C 语言变量 ADDR
对应,第二个占位符 %1 与 C 语言变量 nr
对应。因此上面的汇编语句代码与下面的伪代码等价:btsl nr, ADDR,该指令的两个操作数不能全是内存变量,因此将 nr的限定字符串指定为“Ir”,将 nr
与立即数或者寄存器相关联,这样两个操作数中只有 ADDR为内存变量。
4、限制字符
限制字符有很多种,有些是与特定体系结构相关,此处仅列出常用的限定字符和i386中可能用到的一些常用的限定符。它们的作用是指示编译器如何处理其后的 C语言变量与指令操作数之间的关系。
5、破坏描述部分
破坏描述符用于通知编译器我们使用了哪些寄存器或内存,由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有 “memory”。例如:“%eax”,“%ebx”,“memory”等。
第一个汇编的例子:输出字符
例子说明:
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位的长字使用,但实际情况可能需要的是字或字节,因此应该在约束中指明正确的限定符:
__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) |
相关文章推荐
- AT&T汇编语言与GCC内嵌汇编,Linux内核数据结构之链表
- 和菜鸟一起学linux之GCC内嵌汇编简单实例
- 和菜鸟一起学linux之GCC内嵌汇编简单实例
- 和菜鸟一起学linux之GCC内嵌汇编简单实例
- 和菜鸟一起学linux之GCC内嵌汇编简单实例
- 一本Linux下AT&T汇编语言与GCC内嵌汇编入门的中文书籍
- GCC内嵌汇编之语法详解 -- LINUX
- __asm__ __volatile__ (GCC的内嵌汇编语法 )
- ARM GCC 内嵌(inline)汇编手册
- ARM GCC 内嵌汇编手册
- gcc 内嵌汇编的学习笔记 I
- linux 内嵌汇编语法
- 【转】gcc内嵌汇编简介
- AT&T汇编语言与GCC内嵌汇编简介
- gcc 内嵌汇编 __asm__ __volatile__()语法
- GCC 的内嵌汇编语法收集
- gcc编译c语言中内嵌汇编
- __asm__ __volatile__ GCC的内嵌汇编语法 AT&T汇编语言语法(一)
- ARM GCC 内嵌汇编手册
- __asm__ __volatile__ GCC的内嵌汇编语法 AT&T汇编语言语法