Linux ARM 下C嵌套汇编

时间:2021-06-11 01:02:22

在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"//恢复现场。