引用文章:
『转载』AT&T汇编语言与GCC内嵌汇编简介_大智的肋骨-CSDN博客
内嵌汇编举例在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作。
带有C/C++表达式的内联汇编格式为:
__asm__( 汇编语句模板: 输出部分: 输入部分: 破坏描述部分)
共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格 开,汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空, 也需要用“:”格开。
例子:
//根据__asm__内联汇编语法:"=r" (result)是输出部分,"r" (input)是输入部分
//所以eax寄存器值输出给result,input值输入给eax寄存器
//“r”将变量与某个通用寄存器关联起来,也就是eax ,ebx,ecx,edx,esi,edi中的一个
__asm__ __violate__ ("movl %1,%0" : "=r" (result) : "r" (input));
“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇编靠它们将C语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,“%1,”对应;注意对应顺序:第一个C表达式对应“%0”;第二个表达式对应“%1”,依次类推,操作数至多有10个,分别用“%0”,“%1”….“%9,”表示。在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。“result”前面的限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”与某个通用寄存器相关联,在指令中使用相应寄存器,而不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令。“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用该寄存器参加运算。
c源文件:
extern int input,result;
void test(void)
{
input= 1;
__asm__ __volatile__ ("movl %1,%0" :"=r" (result) : "r" (input));
return;
}
gcc –c –S得到该C文件源代码相对应的汇编代码:
8 movl $1, input 对应C语言语句input = 1;
9 movl input, %eax
10 #APP GCC插入的注释,表示内嵌汇编开始
11 movl %eax,%eax 我们的内嵌汇编语句
12 #NO_APP GCC 插入的注释,表示内嵌汇编结束
13 movl %eax, result 将结果存入result变量
注:movl指令格式:movl source, destination
第9行和第13行是GCC自动增加的代码,GCC根据限定字符串决定如何处理C表达式,本例两个表达式都被指定为“r”型,所以先使用指令:
movl input, %eax
将input读入寄存器%eax;GCC也指定一个寄存器与输出变量result相关,本例也是%eax,等得到操作结果后再使用指令: movl %eax, result 将寄存器的值写回C变量result中。从上面的汇编代码我们可以看出与result和input,相关连的寄存器都是%eax,GCC使用%eax,替换内嵌汇编指令模板中的%0,%1
破坏描述部分:
破坏描述符由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外 还有“memory”。例如:“%eax”,“%ebx”,“memory”等。
将C代码转换成汇编代码的时候,因为所有的代码都是用高级语言编写,编译器可以识别各种语句的作用,在转换的过程中所有的寄存器都由编译器决定如何分配使用,它有能力保证寄存器的使用不会冲突;也可以利用寄存器作为变量的缓冲区,因为寄存器的访问速度比内存快很多倍。如果全部使用汇编语言则由程序员去控制寄存器的使用,只能靠程序员去保证寄存器使用的正确性。但是如果两种语言混用情况就变复杂了,因为内嵌的汇编代码可以直接使用寄存器,而编译器在转换的时候并不去检查内嵌的汇编代码使用了哪些寄存器(因为很难检测汇编指令使用了哪些寄存器,例如有些指令隐式修改寄存器,有时内嵌的汇编代码会调用其他子过程,而子过程也会修改寄存器),因此需要一种机制通知编译器我们使用了哪些寄存器(程序员自己知道内嵌汇编代码中使用了哪些寄存器),否则对这些寄存器的使用就有可能导致错误,修改破坏描述部分可以起到这种作用。当然内嵌汇编的输入输出部分指明的寄存器或者指定为“r”,“g”型由编译器去分配的寄存器就不需要在破坏描述部分去描述,因为编译器已经知道了。
没有使用修改描述符:
int main(void)
{
int input, output,temp;
input = 1;
__asm__ __volatile__ ("movl $0, %%eax;movl %%eax, %1;movl %2, %%eax;movl %%eax, %0;"
:"=m"(output),"=m"(temp)
:"r"(input)
);
return 0;
}
gcc -c -S编译后:
movl $1,-4(%ebp)
movl -4(%ebp),%eax
#APP
movl $0, %eax;
movl %eax, -12(%ebp);
movl %eax, %eax;
movl %eax, -8(%ebp);
#NO_APP
显然GCC给input分配的寄存器也是%eax,发生了冲突,output的值始终为0,而不是input。
使用破坏描述后的代码:
int main(void)
{
int input, output,temp;
input = 1;
__asm__ __volatile__("movl $0, %%eax;movl %%eax, %1;movl %2, %%eax;movl %%eax, %0;"
:"=m"(output),"=m"(temp) /* output */
:"r"(input) /* input */
:"eax"); /* 描述符 */
return 0;
}
对应的汇编代码:
movl $1,-4(%ebp)
movl -4(%ebp),%edx
#APP
movl $0, %eax;
movl %eax, -12(%ebp);
movl %edx, %eax;
movl %eax, -8(%ebp);
#NO_APP
通过破坏描述部分,GCC得知%eax已被使用,因此给input分配了%edx。在使用内嵌汇编时请记 住一点:尽量告诉GCC尽可能多的信息,以防出错。
匹配限制符是一位数字:“0”、“1”……“9”,分别表示它限制的C表达式分别与占位符%0,%1,……%9对应的C变量匹配。例如使用“0”作为%1的限制字符,那么%0和%1表示同一个C变量。
extern int input,result;
void test_at_t()
{
result = 0;
input = 1;
__asm__
__volatile__ ("addl %2,%0":"=r"(result):"0"(result),"m"(input));
}
输入部分中的result用匹配限制符“0”限制,表示%1与%0,代表同一个变量,输入部分说明该变量的输入功能,输出部分说明该变量的输出功能,两者结合表示result是读写型。
gcc -S -c
movl $0, result(%rip)
movl $1, input(%rip)
movl result(%rip), %eax
#APP
addl input(%rip),%eax
#NO_APP
movl %eax, result(%rip)