在C中嵌套汇编的作用:
1) 汇编执行的代码效率更高;
2) 某些操作使用汇编编写代码更方便,如对协处理器的操作;
那么如何在C中嵌套汇编呢?
基本语法:
asm volatile (“code” : output : input : modify describtion);
如:
asm volatile(
“asm instruction \n”
“asm instruction \n”
“asm instruction \n”
………
“asm instruction \n”
:输出(如“=r”(val))
:输入(如“0”(val))
:描述(如”memory”,”cc”)
);
指令序列:在嵌套汇编中%0 %1 %2 分别表示参数0,参数1,参数2,最多可以到%10(别的博客里看到的,没有试验过)。每条指令后面都有一个\n作为分割符。
输出部分:可以用于指定%n输出到哪个变量,如第一个输出为”=r”(val),表示要将指令序列中的%0的值保存到val中,并且val的值需要用寄存器保存。多个输出参数用逗号分开。
输入部分:可以指定参数列表,如”0”(参数1) ,”1”(参数2)…..多个输入参数用逗号分开。
下面举例子说明一下:
1 #include<stdio.h>
2 #include<unistd.h>
3 main()
4 {
5 int a=6,b=13,c;
6 // int a=6,int b=13,int c;
7 // int a=6;
8 // int b=14;
9 // int c;
10 __asm__ volatile(
11 "mov r5,%0\n" //r5=6
12 "mov r6,%1\n" //r6=13
13 "add %2,%0,#2\n" //%2=8,a=8
14 :"=r"(b),"=r"(c),"=r"(a) //6,13,8
15 :"0"(a),"1"(b),"2"(c)//6 13
16 :"memory","cc"
17 );
18 printf("a b c %d %d %d \n",a,b,c);
19 }
~
运行结果是:a b c 8 6 13
为什么呢,首先在输入部分指定了%0=a=6,%1=b=13,%2=c.
在指令序列中运算之后,得到的是%2=8。由于输出部分指定了a为%2,因此此时a=8。
b的值是%0,%0是谁呢?看输入部分%0是a,那么b就是6。输出部分指定了c的值要用%1表示,而输入部分将%1设置为13,因此c为13。
所以最终结果就是 a=8,b=6,c=13.
在分析嵌套汇编的时候,要先看输入,再看指令序列,最后看输出。
可以看出,用C嵌套汇编,要交换2个变量的值是很容易的。如下面的例子就是交换了ab的值:main()
4 {
5 int a=6,b=13,c;
6 // int a=6,int b=13,int c;
7 // int a=6;
8 // int b=14;
9 // int c;
10 __asm__ volatile(
11 "nop\n"
12 :"=r"(b),"=r"(a) //6,13,8
13 :"0"(a),"1"(b)//6 13
14 :"memory","cc"
15 );
16 printf("a b c %d %d %d \n",a,b,c);
运行结果:a b c 13 6 0 ,可见,ab的值已经交换了。
常见的交换AB值的操作:
1.嵌套汇编,指定输入输出部分即可
2.异或运算:
{a = a ^ b; b = a ^ b; a = a ^ b;}
不过这种方法只能交换整数,因为一元运算符 ^ 要求操作数是整数。
3.加减法:
a=a+b-a;b=a+b-a;但是容易发生溢出.
4.新建变量c。c=a;a=b;b=c;
需要注意的问题是,在默认情况下,指令序列中的%0是由R0来存储的,%1是R1存储的
如下代码:
这里写代码片
```__asm__ __volatile__ (
95 "adrl r0, main\n"
96 "ldr r1,=0xb0003000\n"
97 "mov %0,r0\n"
98 "mov %1,r1\n"
99 "sub %2,%1,%0\n"
100 :"=r"(phy),"=r"(vaddr),"=r"(delat)
101 :"0"(phy),"1"(vaddr),"2"(delat)
102 );
@
这是个裸板程序,main函数是源文件中第一个被编译的文件,首先被执行,我们把他的链接地址指定为0xb0003000。我们把编译好的文件,用TFTP下载到板子的0xa8003000处,执行如下命令:
mw.b a8003000 ff 2000;tftp a8003000 test.bin;go 0xa8003000
看打印效果:
runaddr is 0xa8003000 linkaddr is 0xa8003000,offset is 0
很明显,这不是我们想要的,我们已经指定了linkaddr为0xb0003000。为什么呢?怎么改正呢?
反汇编看一下
<div class="se-preview-section-delimiter"></div>
b0003048: e24f0050 sub r0, pc, #80 ; 0x50
27 b000304c: e1a00000 nop ; (mov r0, r0)
28 b0003050: e59f17e4 ldr r1, [pc, #2020] ; b000383c
__asm__ __volatile__ (
95 "adrl r6, main\n"
96 "ldr r7,=0xb0003000\n"
97 "mov %0,r6\n"
98 "mov %1,r7\n"
99 "sub %2,%1,%0\n"
100 :"=r"(phy),"=r"(vaddr),"=r"(delat)
101 :"0"(phy),"1"(vaddr),"2"(delat)
102 //:"r0","r1","r2"
103 );
用r6/r7代替原来的r0/r1,但是这样依然是不安全的,最好是再明确地告诉编译器保护部分。
常见的描述信息
“memory”内存有改动,不使用寄存器做缓存。
“cc”
“registerr” //告诉编译器某些寄存器要在汇编指令中使用
详细的可以看看:
http://www.ethernut.de/en/documents/arm-inline-asm.html
“r12”, “cc”
informs the compiler that the assembly code modifies register r12 and updates the condition code flags
.
在汇编中调用C函数,如果要传递参数怎么办呢?
通常情况下,对于参数传递,分两种情况。一种情况是,本身传递的参数不多
于 4 个,就可以通过寄存器 r0~r3 传送参数。因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值,那么此时,这些寄存器就是空闲的,可以供我们使用的了,那就可以放参数。
另一种情况是,参数多于 4 个时,寄存器不够用,就得用栈了。
因此栈的作用:
1.保护现场。//保存被调用时的相关寄存器
2.参数传递 //
3.保存临时变量。//…
下面举个例子:
看看下面的arm的 异常向量表的建立方式。
所谓异常向量,是arm工作异常模式的时候才进入的。
除了系统、用户模式外剩下的5种都属于异常(现在多了一个监视模式)
即管理模式、未定义指令模式、数据访问终止模式、中断、快速中断模式。
但是异常向量有:复位、未定义、swi、指令预取、数据异常、保留、中断、快速中断
共32个字节。
__asm__ (
347 "vectors:\n"
348 "b reset\n"
349 "b und\n"
350 "b swi\n"
351 "b pre_abt\n"
352 "b dat_abt\n"
353 ".word 0\n"
354 "b irq\n"
355 "b fiq\n"
356 "reset:\n"
357 "und:\n"
358 "mov sp, #0x94000000\n" //未定义指令、复位的时候的栈顶。
359 "stmfd sp!, {r0-r12, lr}\n"//保护现场
360
361 "mov r0, sp\n"//参数传递
362 "mov r3, #0x94000000\n"
363 "ldr r3, [r3]\n"//
364 "blx r3\n"//执行异常处理函数
365
366 "mov sp, #0x94000000\n" //
367 "ldmea sp, {r0-r12, pc}^\n"//恢复现场。