目 录
1 GNU ARM汇编
不同的汇编器对汇编语言的语法要求不一样。目前常用的ARM汇编环境有以下两种:
u ARMASM:ARM公司的汇编器,适合在Windows平台下使用
u GNU ARM ASM:GNU交叉编译工具链中的汇编器,适合于Linux开发平台。
本文主要讨论GNU ARM语法
1.1 基本语法
在GNU ARM汇编中,一行语句的基本格式如下:
标签:指令 @ 注释
解释如下:
u 标签可以代表“标签处的代码”也可以代表“标签处的数据的地址” 。
u 指令包括两种:ARM指令、汇编伪指令。
应用举例:
.text @ 代码段开始
.global add @ 声明add为全局标签,这样它就可以在其它文件中使用
add: @ 标签
add r0, r0, r1 @ 将两个参数相加,结果放在r0中
mov pc, lr @函数调用返回
1.2 GNU ARM汇编伪指令
伪指令均是以小数点开头。
此处略去具体的GNU ARM汇编伪指令,如需参考建议阅读何永琪主编的《嵌入式Linux系统实用开发》4.4节相关内容(原书P158页)。
2 ATPCS约定
在汇编语言与C语言混合编程的情况下,或者是各个源文件不是由同一个编译器进行编译的情况下,所有的汇编代码的生成(或编写)都必须遵循统一调用接口规范,这才能保证链接出来的程序功能是正确(即所写的汇编代码可直接与C语言相互调用)。
2.1 寄存器使用规定
ATPCS对通用寄存器的用途做了一些规定,并且根据不同用途为寄存器定义了别名,如下表2-1-1所示。
表2-1-1 通用寄存器的用途与别名
寄存器 | 别名 | 用途 |
r0 | A1 | 函数参数和返回值 |
r1 | A2 | 函数参数和返回值 |
r2 | A3 | 函数参数 |
r3 | A4 | 函数参数 |
r4 | V1 | 变量 |
r5 | V2 | 变量 |
R6 | V3 | 变量 |
r7 | V4 | 变量 |
R8 | V5 | 变量 |
r9 | V6, sb | 变量,或静态数据基址 |
r10 | V7, sl | 变量,或栈限制 |
r11 | V8, fp | 变量,或帧指针 |
r12 | Ip | 函数调用中间临时寄存器 |
r13 | Sp | 栈指针 |
r14 | Lr | 链接寄存器 |
r15 | Pc | 程序计数器 |
r0 - r3 这4个寄存器用来传递函数调用第1到第4个参数,更多的参数则必须需要通过栈来传递。而r0同时用来存放函数调用的返回值。被调用的子程序在返回前无须恢复这些寄存器的内容。
r4 – r11 这8个寄存器可作为一般的临时变量使用(即局部变量),子程序进入时必须保存这些寄存器的值,在返回前必须返回前必须恢复这些寄存器的值【以便恢复上一个局部变量的值】。其中,r11(fp)是函数的帧指针。函数在栈中保存的所有的局部信息,包括返回地址、局部变量等构成了函数的帧。fp指向函数帧的第一个字,在函数执行过程中,fp保存不变。
r12(ip) 在函数调用时,用保存栈指针的临时寄存器。
r13(sp)
r14(lr) 用于保存子程序的返回地址。
r15(pc) 程序计数器,不能用作其它用途。
应用举例,见表2-1-2:
原sp(调用新函数前的栈指针) | Data(有数据) | 4 |
fp(由于调用新的函数,而导致栈延长,这是新延长的栈开始的地方,我们称之为函数帧的指针,所有的信息。变量从这里开始存入) | Pc | 0(新函数开始的地方) |
| Lr(内含子程序返回地址) | -4 |
| ip(原sp) | -8 |
| fp(原fp) | -12 |
| a1 | -16 |
| a2 | -20 |
Sp | a3 | -24 |
3 汇编与C语言对照
汇编语言的结构:
① 开头声明
② 数据初始化
④函数(入栈、操作、出栈)
③ 被初始化的数据的地址(文字池)
注意:②和③联系比较大,但为了让③处在不会被执行的地方,所以将③放在④之后。
下面的例子是一个算术运算的汇编与C的对照,首先是C语言
/*文件名:test.c*/
/*说明:算术运算*/
int v1 = 1;
static int v2 = 2;
int main(void)
{
int vr;
int v3 = 3, v4 = 4;
vr = (v1 + v2 ) – ( v3 + v4 )
return vr;
}
以下为汇编语言的内容(请结合表2-1-2理解本程序):
.file “test.c”
.global v1 @声明v1为全局标签
.data @ 数据段开始
.align 2 @地址与4的倍数对齐
.type v1, %object @v1标签代表数据对象
.size v1, 4 @对象v1的长度为4
v1:
.word 1 @存放v1的初始值1
.align 2 @地址与4的倍数对齐
.type v2, %object @v2标签代表数据对象
.size v2, 4 @对象v2的长度为4
v2:
.word 2 @存放v2的初始值2
.text @ 代码段开始
.align 2
.global main @ 声明main为全局标签
.type main, %function @ main标签代表函数
main:
@ args = 0, pretend = 0, frame = 12
@ frame_needed = 1, uses_anonymous_args = 0
@入栈及开辟存放局部变量的空间
mov ip, sp @暂时用ip保存栈指针
stmfd sp!, {fp, ip, lr, pc} @ 入栈
sub fp, ip, #4 @ fp指向入栈的第一个元素(函数帧的开始)
sub sp, ip, #12 @ 在栈上开辟3个整形数的空间,用于存放局部变量
@给v3,v4赋初值
mov r3, #3
str r3, [fp, #-20] @ 将v3赋值为3
mov r3, #4
str r3, [fp, #-24] @ 将v4赋值为4
@计算表达式v1+v2,将结果放到r1中
ldr r3, .L2 @ 将变量v1的地址加载到r3
ldr r2, .L2+4 @ 将变量v2的地址加载到r2
ldr r1, [r3, #0] @ 将变量v1的值加载到r1
ldr r3, [r2, #0] @ 将变量v2的值加载到r3
add r1, r1, r3 @ v1+v2的结果放到r1中
@计算表达式(v3+v4) ,将结果放到r3中
ldr r2, [fp, #-20] @ 将变量v3的值加载到r2上
ldr r3, [fp, #-24] @ 将变量v4的值加载到r3上
add r3, r2, r3 @ (v1+v2)+(v3+v4)的结果放到r3中
@将(v1+v2)-(v3+v4)的值放到r3中,保存,返回
rsb r3, r3, r1 @ 将(v1+v2) - (v3+v4)的值放到r3之中
str r3, [fp, #-16] @将结果保存到vr中
ldr r3, [fp, #-16] @将vr的值加载到r3中
mov r0, r3 @ 将(v1+v2) - (v3+v4)的值放到r0中返回
@出栈
sub sp, fp, #12 @ 重设栈指针准备返回
ldmfd sp, {fp, sp, pc} @ 返回
.L3:
.align 2
.L2
.word v1 @变量v1的地址,即标号v1【使.L2获得v1的指针】
.word v2 @变量v2的地址,即标号v2
.size main, .-main @ main函数的大小,当前位置减去main标号处
.ident “GCC: (GNU) 3.4.4”
这里有点4需要注意的:
① 全局变量都被放在数据段上,数据段中保存的其实是变量的初始值
② 未用static声明的变量会被声明为.global,表示他可以链接到其它文件。
③ 加载全局变量实际上使用的是文字池的方法,即将变量地址放在代码段中某个不会执行到的位置,使用时先加载变量的地址,然后通过变量的地址得到变量的值。
④ 每条汇编指令只能执行一个简单的运算,计算的中间结果使用寄存器保存。如果表达式非常复杂以至于无法用寄存器保存所有的中间结果,则还会在栈上开辟局部变量来保存。【非静态的局部变量都放在栈上,通过帧指针和偏移量的方式来访问。帧指针(fp)在开始时设好,整个函数执行期间不会该变(而期间sp会改变)。】
声明:本博文为我学习《嵌入式Linux系统实用开发》时做的笔记,文中有很多内容摘自此书,当然也有我对一些较难知识点的思考、总结。