一、内联汇编
在内嵌汇编中,可以将C语言表达式指定为汇编指令的操作数,而且不用去管如何将C语言表达式的值读入哪个寄存器,以及如何将计算结果写回C变量,你只要告诉程序中C语言表达式与汇编指令操作数之间的对应关系即可, GCC会自动插入代码完成必要的操作。
1. 语法格式
带有C/C++表达式的内联汇编格式为:
asm ( 汇编语句模板: 输出部分: 输入部分: 破坏描述部分)
共四个部分:汇编语句模板,输出部分,输入部分,破坏描述部分,各部分使用“:”格开。
汇编语句模板必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空, 也需要用“:”格开。
2. 汇编语句模板
例:
asm volatile("movl %1,%0" : "=r" (result) : "r" (input));
“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇编靠它们将C语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表达式,本例中只有两个:“result”和“input”,第一个C表达式对应“%0”;第二个表达式对应“%1”,依次类推。
3. 输入输出部分
在每个操作数前面有一个用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。
“result”前面的限制字符串是“=r”,其中“r”表示需要将“result”先放入某个寄存器,然后在指令中使用该寄存器参加运算,而不是“result”本身(从表面上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写一些指令);“=”表示“result”是输出操作数,指令执行完后需要将寄存器中的值再存入变量“result”。“input”前面的“r”同理。
代码示例:
input= 1;
__asm__ __volatile__ ("movl %1, %0" : "=r" (result) : "r" (input));
gcc –c –S 得到汇编代码:
movl $1, input 对应C语言语句input = 1;
movl input, %eax GCC自动增加的代码,将input读入寄存器%eax
#APP GCC插入的注释,表示内嵌汇编开始
movl %eax,%eax 内嵌汇编语句
#NO_APP GCC 插入的注释,表示内嵌汇编结束
movl %eax, result GCC自动增加的代码,将寄存器的值写回C变量result中
4. 破坏描述部分
破坏描述符由逗号格开的字符串组成,每个字符串描述一种情况,一般是寄存器名;除寄存器外还有“memory”。例如:“%eax”,“%ebx”,“memory”等。
全部使用C代码编写的程序,将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;
}
gcc –c –S 得到汇编代码:
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。
5. 匹配限制符
C语言表达式的操作数至多有10个,分别用“%0”,“%1”….“%9,”表示。匹配限制符是一位数字:“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));
}
gcc –c –S 得到汇编代码:
movl $0, result(%rip)
movl $1, input(%rip)
movl result(%rip), %eax
#APP
addl input(%rip), %eax
#NO_APP
movl %eax, result(%rip)
输入部分中的result用匹配限制符“0”限制,表示%1与%0代表同一个变量,输入部分说明该变量的输入功能,输出部分说明该变量的输出功能,两者结合表示result是读写型。
二、MRC与MCR指令
1. MCR
MCR指令将ARM处理器的寄存器中的数据传送到协处理器的寄存器中。如果协处理器不能成功地执行该操作,将产生未定义的指令异常中断。
指令的语法格式:
MCR{} p15, 0, , , {,<opcode_2>}
MCR2 p15, 0, , , {,<opcode_2>}
其中,为指令执行的条件码。当忽略时指令为无条件执行。MCR2中,为Ob1111,指令为无条件执行指令。
<opcode_1>为协处理器将执行的操作的操作码。一般cp15就是MMU。对于CP15协处理器来说,<opcode_1>永远为0b000。当<opcode_1>不为0b000时,该指令操作结果不可预知。
作为元寄存器的ARM寄存器,其值被传送到得协处理器寄存器中。不能为PC,当其为PC时,指令操作结果不可预知。
作为目标寄存器的协处理器寄存器,其编号可能为C0,C1…C15。
附加的目标寄存器或者原操作数寄存器,用于区分同一个编号的不同物理寄存器。当指令中不需要提供附加信息时,将C0指定为,否则指令操作结果不可预知。
<opcode_2>提供附加信息,用于区别同一个编号的不同物理寄存器。当指令中指定附加信息时,省略<opcode_2>或者将其指定为0,否则指令操作结果不可预知。
2. MRC
MRC指令将协处理器的寄存器中数值传送到ARM处理器的寄存器中。如果协处理器不能成功地执行该操作,将产生未定义的指令异常中断。
指令的语法格式:
MRC{} p15, 0, , , {,<opcode_2>}
MRC2 p15, 0, , , {,<opcode_2>}
3. 示例
一个协处理器与寄存器之前操作的例子就是,一个浮点值在协处理器中转换成32位整型,然后它的结果传送到ARM920T的寄存器中MRC。相反则为MCR。
例如:
MRC p2,5,r3,c5,c6
协处理器p2把c5和c6经过5操作的结果赋给r3
MCR p6,0,r4,c5,c6
协处理器p6把r4执行0操作后将结果存放进c6
MRC p3,9,r3,c5,c6,2
协处理器p3把c5和c6经过9操作(类型2)的结果赋给r3
以上内容来源于网络知识总结,如有侵权请私信联系立即删除:)