首页 > 代码库 > GNU风格ARM汇编编程实战之一 <C与汇编混合编程>

GNU风格ARM汇编编程实战之一 <C与汇编混合编程>

  

一、参考资料


  1. 《ARM GCC内联汇编手册》:http://www.ethernut.de/en/documents/arm-inline-asm.html

  2. 《__asm__ __volatile__内嵌汇编用法简述》:http://www.embedu.org/Column/Column28.htm

  3. 《A?R?M?内?嵌?汇?编?示?例》:http://wenku.baidu.com/view/72c12e4133687e21af45a990.html



二、C与汇编混合编程概述



1. 应遵守的规则


  在使用C语言时,要用到和汇编语言的混合编程。若汇编代码较为简洁,则可使用直接内嵌汇编的方法;否则要将汇编程序以文件的形式加入到项目中,按照ATPCS(ARM/Thumb过程调用标准,ARM/Thumb Procedure Call Standard)的规定与C程序相互调用与访问。 


  在C程序和ARM汇编程序之间相互调用时,必须遵守ATPCS规则。ATPCS规定了一些子程序间调用的基本规则,哪寄存器的使用规则,堆栈的使用规则和参数的传递规则等。


2. ATPCS规则


2.1 寄存器的使用规则


  寄存器r0~r3子程序之间通过r0~r3来传递参数,当参数个数多于4个时,使用堆栈来传递参数。此时r0~r3可记作A1~A4

  寄存器r4~r11在子程序中使用r4~r11保存局部变量。因此当进行子程序调用时要注意对这些寄存器的保存和恢复。此时r4~r11可记作V1~V8

  寄存器r12:用于保存堆栈指针SP,当子程序返回时使用该寄存器出栈,记作IP

  寄存器r13堆栈指针,记作SP

  寄存器r14链接寄存器,记作LR。用于保存子程序的返回地址。

  寄存器r15程序计数器,记作PC


2.2 堆栈的使用规则


  ATPCS规定堆栈采用满递减类型(FD, Full Descending),即堆栈通过减小存储器地址而向下增长堆栈指针指向内含有效数据项的最低地址


2.3 参数的传递规则


  整数参数:前4个使用r0~r3传递,其他参数使用堆栈传递;
浮点参数:使用编号最小且能够满足需要的一组连续的FP寄存器传递参数。


  子程序的返回结果为一个32位整数时,通过r0返回;返回结果为一个64位整数时,通过r0和r1返回;依此类推。结果为浮点数时,通过浮点运算部件的寄存器F0、D0或者S0返回。


3. 汇编程序调用C程序


  汇编程序的书写要遵循ATPCS规则,以保证程序调用时参数正确传递。


  在汇编程序中调用C程序的方法为:首先,在汇编程序中使用IMPORT伪指令声明将要调用的C语言函数;然后,通过BL指令来调用C函数


  例如在一个C源文件中定义了如下求和函数:

int add(int x,int y)
{
	return(x+y);
}
  调用add()函数的汇编程序结构如下:
IMPORT add ;声明要调用的C函数
……
MOV r0,1
MOV r1,2
BL add ;调用C函数add
……
  当进行函数调用时,使用r0和r1实现参数传递,返回结果由r0带回。函数调用结束后,r0的值变成3。


4. C程序调用汇编程序


  C程序调用汇编程序时,汇编程序的书写也要遵循ATPCS规则,以保证程序调用时参数正确传递。


  在C程序中调用汇编子程序的方法为:首先,在汇编程序中使用EXPORT伪指令声明被调用的子程序,表示该子程序将在其他文件中被调用;然后在C程序中使用extern关键字声明要调用的汇编子程序为外部函数。


  例如在一个汇编源文件中定义了如下求和函数:

EXPORT add ;声明add子程序将被外部函数调用
……
add ;求和子程序add
ADD r0,r0,r1
MOV pc,lr
……
  在一个C程序的main()函数中对add汇编子程序进行了调用:

extern int add (int x,int y); //声明add为外部函数

void main(){
	int a=1,b=2,c;
	c=add(a,b); //调用add子程序
	...
}

       当main()函数调用add汇编子程序时,变量a、b的值会给了r0和r1,返回结果由r0带回,并赋值给变量c。函数调用结束后,变量c的值变成3。


5. C程序中内嵌汇编语句


       在C语言中内嵌汇编语句可以实现一些高级语言不能实现或者不容易实现的功能。对于时间紧迫的功能也可以通过在C语言中内嵌汇编语句来实现。内嵌的汇编器支持大部分ARM指令和Thumb指令,但是不支持诸如直接修改PC实现跳转的底层功能,也不能直接引用C语言中的变量。



三、C程序中内嵌汇编语句

  

  内联汇编格式如下:

__asm__ __volatile__(
	"Instruction List" 
	:Output Operand List
	:Input Operand List
	:Clobber/Modify List
);

  下面是个简单的内嵌汇编样例。

#include <stdio.h>  

int main(int argc, char **argv)  
{    
	int in = 100;   
	int out;  

   	__asm__ __volatile__(  
		"mov r0, %[input]\n"     
		"mov %[output], r0\n" 
		:[output]"=r"(out)    
		:[input]"r"(in)    
		:"r0"   
	);     

	printf("out = %d\n", out); 

	return 0;  
} 

  (1). __asm__是GCC 关键字asm 的宏定义。__asm__或asm用来声明一个内联汇编表达式,所以任何一个内联汇编表达式都是以它开头的,是必不可少的。

#define __asm__ asm

  (2). __volatile__是GCC 关键字volatile 的宏定义。__volatile__或volatile 是可选的。如果用了它,则是向GCC 声明不允许对该内联汇编优化,否则当使用了优化选项(-O)进行编译时,GCC 将会根据自己的判断决定是否将这个内联汇编表达式中的指令优化掉。

#define __volatile__ volatile

  (3). "Instruction List"指令列表每条指令都必须被双引号括起来;两条指令必须用换行"\n"或分号";"分开。

  (4). Output Operand List用来指定当前内联汇编语句的输出操作符列表。每一个输出操作符都由3个部分组成:方括号[]中的符号名限制字符串"=r"圆括号()中的C表达式构成。输出操作符之间用逗号","分割。即:[out1]"=r"(value1), [out2]"=r"(value2), ... [outn]"=r"(valuen)

  (5). Input Operand List用来指定当前内联汇编语句的输入操作符列表。表示方法同上。即:[in1]"=r"(value1), [in2]"=r"(value2), ... [inn]"=r"(valuen)

  (6). Clobber/Modify List通知GCC当前内联汇编语句可能会对某些寄存器或内存进行修改,希望GCC在编译时能够将这一点考虑进去。这种情况一般发生在一个寄存器出现在"Instruction List",但却不是由Input/Output操作表达式所指定的,也不是在一些Input/Output操作表达式使用"r"约束时由GCC为其选择的,同时此寄存器被"Instruction List"中的指令修改,而这个寄存器只是供当前内联汇编临时使用的情况。

  如果一个内联汇编语句的Clobber/Modify域存在"memory",那么GCC会保证在此内联汇编之前,如果某个内存的内容被装入了寄存器,那么在这个内联汇编之后,如果需要使用这个内存处的内容,就会直接到这个内存处重新读取,而不是使用被存放在寄存器中的拷贝。因为这个时候寄存器中的拷贝已经很可能和内存处的内容不一致了。


  综合上述讲述,普通的内嵌汇编可以如下表示:

__asm__ __volatile__(
	"Instruction 1\n"
	"Instruction 2\n"
	...
	"Instruction n\n"
	:[out1]"=r"(value1), [out2]"=r"(value2), ... [outn]"=r"(valuen)
	:[in1]"=r"(value1), [in2]"=r"(value2), ... [inn]"=r"(valuen)
	:"r0", "r1", ... "rn"
);
  输入/输出操作符列表中的组成部分之一是限制性字符串。对于ARM处理器,GCC4提供了以下限制:

Constraint

Usage in ARM state

Usage in Thumb state

f

Floating point registers f0 .. f7

Not available

h

Not available

Registers r8..r15

G

Immediate floating point constant

Not available

H

Same a G, but negated

Not available

I

Immediate value in data processing instructions
e.g. ORR R0, R0, #operand

Constant in the range 0 .. 255
e.g. SWI operand

J

Indexing constants -4095 .. 4095
e.g. LDR R1, [PC, #operand]

Constant in the range -255 .. -1
e.g. SUB R0, R0, #operand

K

Same as I, but inverted

Same as I, but shifted

L

Same as I, but negated

Constant in the range -7 .. 7
e.g. SUB R0, R1, #operand

l

Same as r

Registers r0..r7
e.g. PUSH operand

M

Constant in the range of 0 .. 32 or a power of 2
e.g. MOV R2, R1, ROR #operand

Constant that is a multiple of 4 in the range of 0 .. 1020
e.g. ADD R0, SP, #operand

m

Any valid memory address

N

Not available

Constant in the range of 0 .. 31
e.g. LSL R0, R1, #operand

O

Not available

Constant that is a multiple of 4 in the range of -508 .. 508
e.g. ADD SP, #operand

r

General register r0 .. r15
e.g. SUB operand1, operand2, operand3

Not available

w

Vector floating point registers s0 .. s31

Not available

X

Any operand

  限制字符可能要单个修饰符指示。要是没有修饰符指示的默认为只读操作符。

操作符

说明

=

只写操作,通常用于所有的输出操作符

+

读写操作符,必须用于输出操作符

&

只能用于输出的寄存器,不能使用与输入操作数相同的寄存器