ARM处理器中有R0-R15共16个寄存器(
可以直接访问)
这里以S3C2440为例,其GPIO控制器有GPFCON、GPFDAT寄存器(
以地址访问)
[tr]寄存器别名使用规则[/tr]
r15 | pc | 程序计数器 |
r14 | lr | 链接寄存器 |
r13 | sp | 数据栈指针 |
r12 | ip | 子程序内部调用的scratch寄存器 |
r11 | v8 | ARM状态局部变量寄存器8 |
r10 | v7、sl | ARM状态局部变量寄存器7、在支持数据栈检查的ATPCS中为数据栈限制指针 |
r9 | v6、*** | ARM状态局部变量寄存器6、在支持RWPI的ATPCS中为静态基址寄存器 |
r8 | v5 | ARM状态局部变量寄存器5 |
r7 | v4、wr | ARM状态局部变量寄存器4、Thumb状态工作寄存器 |
r6 | v3 | ARM状态局部变量寄存器3 |
r5 | v2 | ARM状态局部变量寄存器2 |
r4 | v1 | ARM状态局部变量寄存器1 |
r3 | a4 | 参数/结果/scratch寄存器4 |
r2 | a3 | 参数/结果/scratch寄存器3 |
r1 | a2 | 参数/结果/scratch寄存器2 |
r0 | a1 | 参数/结果/scratch寄存器1 |
寄存器的使用规则总结如下:
子程序间通过寄存器 r0~ r3来传递参数,这时可以使用它们的别名a0~ a3. 被调用的子程序返回前无需恢复r0~r3的内容。
在子程序中, 使用r4~ r11来保存局部变量,这时可以使用它们的别名v1~ v8。如果在子程序中使用了它们的某些寄存器,子程序进入时要保存这些寄存器的值,在返回前恢复它们;对于子程序中没有使用到的寄存器,则不必进行这些操作。在Thumb程序中,通常只能使用寄存器r4~ r7来保存局部变量。
寄存器r12用作子程序间scratch寄存器,别名为ip。
寄存器r13用作数据栈指针,别名为sp在子程序中寄存器r13不能用作其他用途。 它的值在进入、退出子程序时必须相等。
寄存器r14称为连接寄存器,别名为Ir它用于保存子程序的返回地址。如果在子程序中保存了返回地址(比如将Ir 值保存到数据栈中), r14可以用作其他用途。
寄存器r15是程序计数器,别名为pc。它不能用作其他用途。
对于R15、R14 和R13
R15 pc Program Counter:程序计数器=当前指令+8:流水线(当前执行地址A的指令,已经在对地址A+4的指令进行译码,已经在读取地址A+8->[PC的值]的指令)
R14 lr Link Register:返回地址(例如使用函数调用的时候返回原来的地方,保留原来的地方)
R13 sp:Stack Pointer:栈指针
内存访问指令:ldr、str、ldm、stm
LDR(load)读内存:LDR R0,[R1]
假设R1的值是x,读取地址x上的数据(4字节),保存到R0中,而LDRB不同,B代表Byte 表示读内存1个字节
STR(store)写内存命令:STR R0,[R1] 假设R1的值是x,把R0的值写到地址x(4字节)
ldr rl, [r2, #4]
将地址为r2+4的内存单元数据读取到r1中
ldr rl, [r2]
将地址为r2的内存单元位数据读取到r1中
ldr r1,[r2],#4
将地址为r2的内存单元数据读取到r1中,然后r2=r2+4
str rl,[r2, #4]
将r1的数据保存到地址为r2+4的内存单元中
str r1, [r2]
将r1的数据保存到地址为r2的内存单元中
str r1, [r2],#4
将r1的数据保存到地址为r2的内存单元中,然后r2=r2+4
ldr指令从内存中读取数据到寄存器,str指令把寄存器的值存储到内存中,他们操作的数据都是32为的
ldm和stm属于批量内存访问指令,只用一条指令就可以读写多个数据。
相对跳转指令:b、bl
这两条指令的不同之处在与bl指令除了跳转之外,还将返回地址(bl的下一条指令的地址)
这两条指令的可跳转范围是当前难治性的前后32MB
它们是位置无关的指令
数据传送指令mov,地址读取伪指令ldr
mov指令可以把一个寄存器的值赋给另一个寄存器,或者把一个常数赋给寄存器
mov指令传送的常数必须能用立即数来表示
当不知道一个数能否用"立即数"来表示时。可以使用ldr命令来复制。ldr是伪指令,它并不是真实存在的指令,编译器会把他扩展成真正的指令:如果该常数能够用"立即数"来表示,则使用mov指令
LDR:LDR R0,=0x12345678,结果R0=0x12345678 伪指令,它会被拆分为几条真正的RAM指令
MOV R0,#0x12345678 错误 ARM指令32位 里面的某位表示MOV指令、R0剩下的不足32位不能表示任意值,只能表示简单值(被称为立即数)
所以引入了伪指令 LDR R0,=任意值
加减指令add、sub
例如
add r1,r2, #1 表示r1=r2+1,即寄存器r1的值等于寄存器r2的值加上1
sub r1,r2, #1 表示r1=r2-1
程序状态寄存器的访问指令:msr、mrs
ARM处理器有一个程序状态寄存器(cpsr),它用来控制处理器的工作模式、设置中断的总开关。
msr cpsr, r0 复制r0到cpsr中
mrs r0, cpsr 复制cpsr到r0中
其他伪指令
汇编程序汇总,常常见到下面语句:
.extern main
.text
.global _start
_start:
“extern"定义一个外部符号(可以是变量也可以是函数),上面的代码表示本文件中引用main是一个外部函数
.text 部分是处理器开始执行代码的地方,指定了后续编译出来的内容放在代码段【可执行】,是arm-gcc编译器的关键词。
.global关键字用来让一个符号对链接器可见,可以供其他链接对象模块使用;告诉编译器后续跟的是一个全局可见的名字【可能是变量,也可以是函数名】
.global _start 让 _start 符号成为可见的标识符,这样链接器就知道跳转到程序中的什么地方并开始执行。
_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序【如加载器】寻找到。
linux寻找这个 _start 标签作为程序的默认进入点。
利用改变MOV指令机器码来实现点亮另一个LED
C/汇编语言给人类给方便使用,转换为bin文件含有机器码给CPU使用
这里以S3C2440为例
/*
* 点亮LED1: gpf4
*/
.text
.global _start
_start:
/* 配置GPF4为输出引脚
* 把0x100写到地址0x56000050
*/
ldr r1, =0x56000050
ldr r0, =0x100 /* mov r0, #0x100 */
str r0, [r1]
/* 设置GPF4输出高电平
* 把0写到地址0x56000054
*/
ldr r1, =0x56000054
ldr r0, =0 /* mov r0, #0 */
str r0, [r1]
/* 死循环 */
halt:
b halt
其反汇编如下
led_on.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e59f1014 ldr r1, [pc, #20] ; 1c <.text+0x1c>
4: e3a00c01 mov r0, #256 ; 0x100
8: e5810000 str r0, [r1]
c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20>
10: e3a00000 mov r0, #0 ; 0x0
14: e5810000 str r0, [r1]
00000018
:
18: eafffffe b 18
1c: 56000050 undefined
20: 56000054 undefined
4: e3a00c01 mov r0, #256 ; 0x100
bit24-21:1101表示mov指令
bit15-12:0000表示R0
bit11-0(立即数,拆分为高四位rotate 低八位immed_8 立即数=immed_8循环右移(2*rotate)位)来表示0x100
例:立即数1100 0000 0001 immed_8=0000 0001右移2 * 12=24位 为0x100
则立即数0x400 表示为 0x1011 0000 0001
把mov r0, #256改成mov r0, #0x400 就是修改bit11-0,同时可以使得S3C2440的另一LED亮
假设immed_8也为1,则0x400等于1循环右移22位,rotate=22/2=11
则立即数bit11-0:1011 0000 0001,则改变mov机器码为0xe3a00b01就可以实现点亮另一个LED
ldmia和stmdb指令
其他形式简单的描述指令的行为,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。
stmdb为(Decrement Before)先减后存,基本使用stmdb命令,其他后缀少
ldria为(Increment After)先读后增
stmdb:假设sp=4096 dp(先减后存 高编号寄存器存在高地址)
stmdb sp!, {fp, ip, lr, pc} fp:r11 ip:r12 lr:r14 pc:r15 //{}无论括号内怎么放都没有影响
1.先减4096-4=4092
2.后存:4092~4095存放PC的值 r15
3.先减:sp’=sp-4=4088
4.后存:4088~4091存放lr的值 r14
!:最终的被修改的sp值=4080 如果没有!,sp最终还是=4096
ldmia:假设sp=4080(先读后增加 高编号寄存器存在高地址) ldmia sp, {fp, sp, pc} fp:r11
sp:r13 pc:r15
1.先读:fp=4080~4083的值=原来保存的fp
2.后增:sp’=sp+4=4084
3.先读:sp=4084~4087的值=原来保存的ip
4.后增:sp’=sp+4=4088
5.先读:pc=4088~4091的值=原来保存的lr的值
6.后增:sp’=sp+4=4092 sp后面无!:sp修改后的值不存入sp中
C程序内部机制
strat.S:1.设置栈 2.调用main,并把返回地址保存在lr中
led.c:main 1.定义两个局部变量 2.设置变量 3.return 0
栈:sp所指向的内存,这段内存可读可写
为何要设置栈?因为C函数要用
怎么使用栈?a.保存局部变量 b.保存lr等寄存器
调用者如何传参数给被调用者 r0-r3
被调用者如何传返回值给调用者 r0-r3
怎么从栈中恢复那些寄存器
在函数中,r4-r11可能被使用,所以说在入口保存它们,在出口恢复它们
.text
.global _start
_start:
/* 设置内存: sp 栈 */
ldr sp, =4096 /* nand启动 */
// ldr sp, =0x40000000+4096 /* nor启动 */
/* 调用main */
bl main
halt:
b halt
1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
/* 配置GPF4为输出引脚 */
*pGPFCON = 0x100;
/* 设置GPF4输出0 */
*pGPFDAT = 0;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000 sp=4096
4: e59fd004 ldr sp, [pc, #4] ; 10 <.text+0x10>
8: eb000001 bl 14
0000000c :
c: eafffffe b c
10: 40001000 andmi r1, r0, r0
00000014 :
14: e1a0c00d mov ip, sp ip=sp=4096
18: e92dd800 stmdb sp!, {fp, ip, lr, pc} 4092:pc=18+8=0x1a 4088:lr=c 4084:ip=4096 4080:fp?
1c: e24cb004 sub fp, ip, #4 ; 0x4 fp=ip-4=4096-4=4092
20: e24dd008 sub sp, sp, #8 ; 0x8 sp=sp-8=4080-8=4072
24: e3a03456 mov r3, #1442840576 ; 0x56000000 r3=0x56000000
28: e2833050 add r3, r3, #80 ; 0x50 r3=0x56000050
2c: e50b3010 str r3, [fp, #-16] r3存入[fp-16]=[4076] 局部变量保存在栈中
30: e3a03456 mov r3, #1442840576 ; 0x56000000 r3=0x56000000
34: e2833054 add r3, r3, #84 ; 0x54 r3=0x56000054
38: e50b3014 str r3, [fp, #-20] r3存入[fp-20]=[4072]
3c: e51b2010 ldr r2, [fp, #-16] r2=[4092-16]=[4076]=0x56000050
40: e3a03c01 mov r3, #256 ; 0x100
44: e5823000 str r3, [r2]
48: e51b2014 ldr r2, [fp, #-20]
4c: e3a03000 mov r3, #0 ; 0x0
50: e5823000 str r3, [r2]
54: e3a03000 mov r3, #0 ; 0x0 // 调用者可以通过r0-r3传参到被调用者
58: e1a00003 mov r0, r3
5c: e24bd00c sub sp, fp, #12 ; 0xc sp=4092-12=4080
60: e89da800 ldmia sp, {fp, sp, pc} 从栈中恢复寄存器 fp=[4080]=原来保存的fp sp=[4084]=4096 pc=[4088]=c
Disassembly of section .comment:
00000000 <.comment>:
0: 43434700 cmpmi r3, #0 ; 0x0
4: 4728203a undefined
8: 2029554e eorcs r5, r9, lr, asr #10
c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1}
10: Address 0x10 is out of bounds.