首先,在前面已经将ARM架构体系和计算机系统的简单讲解,如果到时候忘了直接先浏览一下前面写好的《从计算机系统组成到ARM体系架构》。
下面是一个从ARM切换到Thumb状态,再从Thumb状态切换到ARM状态的例子:
.text
mov r3,#6 @PC=0x00000000 这句不是必须的
adr r0,thumb @pc=0x00000004 伪指令真正的汇编指令是:add r0,pc,0x4,这里的PC+0x4=Thumb的地址即=0x00000010,这里的PC是一个特别的值
add r0,r0,#1 @pc=0x00000008 想要切换到Thumb状态需要让指定转跳的地址最后一位为1
bx r0 @pc=0x0000000c 转跳到thumb,即切换为Thumb状态,bx和bl是有区别的,bx可以切换状态,CPSR的状态会改变
.code 16 @ 即指定步长为0x2,16位地址每次偏移2个字节
thumb:
mov r3,#4 @pc=0x00000010 这句不是必须的
ldr r3,=ARM @pc=0x00000012 真正的汇编指令:LDR r3,[pc,#0xc] 即ARM的地址0x00000018
bx r3 @pc=0x00000014 转跳到ARM,即切换为ARM状态,因为转跳地址最后一位不是1;同时CPSR寄存器的值会改变
.code 32 @ 即指定步长为0x4,32位地址每次偏移4字节
ARM:
mov r3,#2 @pc=0x00000018 这句不是必须的
mov r3,#4 @pc=0x0000001c 这句不是必须的
.end
| ARM | Tumb |
|---|---|
| 32位的代码密度,代码量更大 | 16位的代码密度,代码量更小 |
| 执行效率更高 | 执行效率更低 |
| 完整的体系结构,可以执行异常 | 不是完整的体系结构,想要执行异常需要切换到ARM状态,只支持通用功能 |
| ARM指令集 | Thumb指令集,这两个指令集有区别,具体差异可以百度 |
.text
ldr r1,=0xFFFFFFFC @r1=0xFFFFFFFC
adds r3,r1,#0x8 @r3=0x4,cpsr寄存器的C位置1
ldr r3,=5 @r3=3
adc r4,r3,#3 @r4=r3+3+(寄存器CPSR的)c,即r4=3+5+1
.end
.text
ldr r1,=0X1 @r1=0x1
ldr r2,=0XFF @r2=0xFF
ldr r4,=0x2 @r4=0x2
ldr r5,=0x3 @r5=0x3
subs r3,r2,r1 @r3=r2-r1,CPSR的中的C=1
sbc r6,r4,r5 @r6=r4-r5-!c 即r6=2-3-0=0xFFFFFFFF
subs r3,r1,r2 @r3=r1-r2,CPSR中N=1,C=0;
sbc r6,r4,r5 @r6=r4-r5-!c 即r6=2-3-1=0xFFFFFFFE
.end
.text
ldr r1,=0x4 @ r1=0x4
ldr r2,=0x20000002 @ r2=0x20000002
ldr r5,=0x2 @r5=0x2
ldr r6,=0x2 @r6=0x2
ldr r7,=0x3 @r7=0x2
mul r3,r1,r2 @r3=r1+r2=0x80000008 CPSR寄存器中N不会置为1
muls r4,r1,r2 @r4=r1+r2=0x80000008 CPSR寄存器中的N位置1
mla r8,r5,r6,r7 @r8(=r5*r6)+r7
.end
.text @代码段
@msr cpsr,0xD0 @这句没啥用,之前我以为是模式问题
ldr r1,=0x87654321 @r1=0x87654321
mov r2,#0x40 @r2=0x40
STR r1,[r2] @*(0x40)=0x87654321,将寄存器r1种的值传给寄存器r2存放的值代表的地址中
LDR r4,[r2] @r4=*(0x40)将寄存器r2存放的值代表的地址中的值传给r4
.data @数据段
buf: @标签
.space 4 @分配4个字节的空间
.end
| 满递减堆栈(Full decending)-FD :堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向最后一个元素。注意,最后一个元素是最后压入的数据。 |
|---|
| 空递减堆栈(Empty descending)—ED:堆栈首部是高地址,堆栈向低地址增长。栈指针总是指向下一个将要放入数据的空位置。 |
| 满递增堆栈(Full ascending)—FA 堆栈首部是低地址,堆栈向高地址增长。栈指针总是指向堆栈最后一个元素。 |
| 空递增堆栈(Empty ascending)-EA 堆栈首部指向低地址,堆栈向高地址增长。栈指针总是指向下一个将要放入数据的空位置 |
.text @代码段声明
mov r1,#1 @r1=1
mov r2,#6 @r2=6
ldr sp, =stack_base @为堆栈开辟空间,sp是堆栈指针,指向栈顶元素,sp=B0
stmfd sp!, {r0-r12} @入栈顺序是从后向前;将B0-(1*4)=r12,B0-(2*4)=r11,B0-(3*4)=r10,B0-(4*4)=r9,.....B0-(13*4)=r0; 同时sp=sp-0*4,sp=sp-1*4,sp=sp-2*4
mov r1,#2 @r1=2
ldmfd sp!, {r0-r12}^ @出栈顺序是从前往后,r0=B0-(13*4),r1=B0-(12*4),.......r12=B0-(1*4)
mov r1,#3 @r1=3
mov r2,#7 @r2=7
.data @数据段声明
buf: @标签
.space 48 @分配空间48字节
stack_base: @栈基地址,也就是SP最初指的地址
.end
ldr sp, =stack_base
stmfd sp!, {r0-r12}
@这两句实际执行的过程如下图,stmfd压栈的过程并未完全列出,但实际上原理可以类推;
@由压栈过程可以反推出栈过程,这里就不画图了;

| 指令 | 示例 | 功能 |
|---|---|---|
| add | add r1,r2,r3 | r1=r2+r3 |
| adc | adc r1,r2,r3 | r1=r2+r3+c,这里的C是CPSR的进位标志位 |
| sub | sub r1,r2,r3 | r1=r2-r3 |
| rsb | rsb r1,r2,r3 | r1=r3-r2 |
| sbc | sbc r1,r2,r3 | r1=r2-r3-!C,这里的C是CPSR的借位标志 |
| rsc | rsc r1,r2,r3 | r1=r3-r2-!C.这里的V是CPSR的借位标志 |
| and | and r0,r0,#0x0F | r0=r0&0x0F; 实际上r0等于r0中的值和0F按位与 |
| orr | orr r0,r0,#0x0F | 实际上r0=r0按位或0x0F |
| eor | eor r0,r0,#0xF | 实际上r0=r0按位异或0x0F |
| bic | bic r0,r0,0x1F | 实际上就是清除r0的[4:0]位,0x1F=B0001 1111,也就是后5位 |
| cmp | cmp r1,#10 | 就是比较r1和10是否相等;cpsr=r1-10(只会影响N,Z,C);(1)如果r1>10,cpsr的C位置1;(2)如果r1==10,cpsr的Z位和C位置1;(3)如果r1<10,cpsr的N位置1;cmp是一个有点特别的指令,可以纤细看cpsr描述 |
| cmn | cmn r1,#10 | 就是比较r1和10是否不相等,cpsrt=r1+10;(只会影响N,Z,C) |
| tst | tst r1,#3 | cpsr= r1&3 |
| teq | teq r1,r2 | cpsr=r1^r2 |
| mov | mov r1,#3 | r1=3 |
| mvn | mvn r1,#3 | r1=~3 |
| lsr | lsr r1,#2 | r1=r1/4;等于将r1右移2位 |
| lsl | lsr,r1,#2 | r1=r1*4;等于将r1左移2位 |
| asr | asr r1,#2 | 逻辑右移,右移,空出来的位置由之前的符号位补齐 |
| ror | ror r1,#2 | 将r1循环右移2位 |
| 指令 | 功能 |
|---|---|
| B 跳转指令 | B[条件] 目的地址; 直接跳转到目的地址开始执行 |
| BL 带返回的跳转指令 | BL[条件] 目的地址;跳转到目的地址开始执行,但是跳转前会先将PC的保存到LR |
| BX 带状态切换的跳转指令 | BX [条件] 目的地址;跳转到目的地址开始执行,目标地址的指令可以是ARM指令也可以是Thumb指令,这个指令能够改变CPSR |
| BXL 带返回和状态切换的跳转指令 | BXL[条件] 目的地址;这个指令就是BL和BX的融合版,参照上面的特性即可 |


.text
mov r1,#0 @r1=0
cmp r1,#0 @r1和0比较
moveq r2,#1 @如果r1和0相等,那么r2=1
movne r2,#2 @如果r1和0不相等,那么r2=2
.end
.text
mov r1,#0 @r1=0
mov r2,#2 @r2=2
cmp r1,#2 @r1和2比较
movcs r3,#3 @r1如果大于等于2,那么r3=3
cmpcc r2,#1 @r1如果小于2,那么比较r2和1
movhi r3,#4 @r2如果大于1,那么r3=4
.end
https://blog.csdn.net/silent123go/article/details/53169783?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522166150679616781683951306%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=166150679616781683951306&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allbaidu_landing_v2~default-1-53169783-null-null.142v42pc_rank_34_ecpm25,185v2control&utm_term=ARM%E5%8D%8F%E5%A4%84%E7%90%86%E5%99%A8%E6%9C%89%E5%93%AA%E4%BA%9B&spm=1018.2226.3001.4187
| 异常的优先级(级别号越低优先级越高) | 优先级级别号 | 异常原因 |
|---|---|---|
| Rest | 0 | 上电,复位 |
| Data Abort | 1 | 访问非法内存(内存不存在或没有访问权限) |
| FIQ | 2 | 快中断 |
| IRQ | 3 | 普通中断 |
| Prefetch Abort | 4 | 地址预取指令执行时发现地址种的数据无法取出或无法访问,一般时B(分支指令跳转)触发 |
| swi | 5 | 软件中断 |
| Undefine instruction | 6 | 当流水线中的非法指令达到执行状态时执行 |

.text
ldr pc,_main @0x00 Rest
ldr pc,_undef_oder @0x04 Undefine Instruction
ldr pc,_swi_handler @0x08 Supervisor call(svc)
ldr pc,_pref_abort @0x0c Prefetch Abort
ldr pc,_data_abort @0x10 Data Abort
NOP @0x14 Not used
ldr pc,_irq @0x18 IRQ(interrupt)
ldr pc,_fiq @0x1c FIQ(fast interrupt)
_fiq:
_main:
.word main @//定义一个标签main,放在_main:标签下,并且main这个标签占4个字节 ,即_main对应代表4个字节的一个标签
_undef_oder:
_swi_handler:
.word swi_handler
_pref_abort:
_data_abort:
_irq:
swi_handler:
stmfd sp!,{r0-r12,lr} @//保护现场,入栈
sub r0,lr,#4 @ r0=lr-4 //得到swi指令的地址
ldr r0,[r0] @ r0=*(r0) //得到swi指令地址中保存的机器码
bic r0,#0xFF000000 @ //将高8位置零,就会得到低24位的数据,也就是中断号
cmp r0,#0 @//判断终端号是否为0
bleq write @//终端号为0,则实现open函数功能
cmp r0,#1 @//判断中断号是否为1
bleq read @//中断号为1,实现read函数功能;在执行地址转跳的时候lr寄存器会自自动赋值为pc+0x4即lr=pc+0x4
ldmfd sp!,{r0-r12,pc}^ @//恢复现场,出栈
write:
mov r0,#0x9 @//r0=0x9
mov r1,#0x20 @//r1=0x20
str r0,[r1] @//*(0x20)=0x9 将0x=9写入内存地址
mov pc,lr @//返回之前的位置并偏移0x4;
read:
mov r1,#0x20 @//r1=0x20
ldr r0,[r1] @r0=*(0x20) 从内存地址获取数据到r0
mov pc,lr @//返回之前的位置并偏移0x4;
main:
mov r0,#0x6 @r0=0x6
mov r1,#0x8 @r1=0x8
ldr sp,=stack_base @//指定栈指针指向栈底部,也就是sp寄存器保存栈地地址 ,注意这一步需要在切换到User模式之前
msr cpsr,#0xD0 @//从管理模式切换到User模式
swi 0 @//触发软件中断,且中断号为0
NOP @//空指令
swi 1 @//触发软件中断,且中断号为0
NOP
.data
buf: @//切换到数据段
.space 100 @//开辟100字节空间
stack_base: @//栈底位置
.end
在产生异常的时候spsr会自动保存当前cpsr中的值,当异常结束再将spsr中的值自动赋值给cpsr
| 原因 |
|---|
| 1.FIQ的处理优先级比IRQ更高,甚至可以打断正在执行的IRQ; |
| 2.FIQ模式有自己独有的寄存器,而IRQ需要和其他模式共用寄存器,在中断处理的保护/恢复现场会更快; |
| 3.在异常向量表中,FIQ处在最末尾。在异常向量表中IRQ只能保存中断处理程序的首地址,在发生IRQ时需要一次跳转;而FIQ处在最末尾,所以可以直接将FIQ模式下的中断处理程序紧接着存放,这样在处理FIQ时就少一次跳转。 |
注意:(1)在之前的章节已经讲过FIQ模式有5个额外的私有寄存器(r8-r12);
(2)在处理中断的时候应该先保护现场,即保护非私有的寄存器;

| 伪操作符 | 作用 |
|---|---|
| .arm | 定义一下代码使用ARM指令集编译 |
| .thumb | 定义一下代码使用Thumb指令编译 |
| .section | 定义一个段,使用方式如.section expr,expr可以是.text .data .bss |
| .text | 将定义符开始的代码编译到代码段 |
| .data | 定制符开始代码编译到数据段,初始化数据段 |
| .bss | 将数据变量放到.bss段,未初始化数据段 |
| .rodata | 只读数据段,或者称为常量区,用于存放如字符串,全局const变量(并不所有常量都在.rodata段如立即数是保存在.text段的;重复字符串会合并,程序中只保留一份;某些系统)中的.rodata段会被多个进程共享,同于提高空间利用率;另外嵌入式系统中,rodata不会加载到ARM中,而是直接在ROM中读取 |
| .align | 通过用零或指定的数据进行填充来使当前位置和指定边界对齐 |
| .global | 用来声明一个全局变量 |
| .org | 指定从当前位置加指定偏移量的地址开始存放代码,这之间的内存单元由指定的数据填充 |
| _start | 汇编程序的缺省入口是_start标志用户也可以用ENTRY标志指明其它入口 |
| .end | 文件结束 |
.对齐伪指令ALIGN
对齐伪指令格局:
ALIGN Num
其间:Num有必要是2的幂,如:2、4、8和16等。
伪指令的作用是:告知汇编程序,本伪指令下面的内存变量有必要从下一个能被Num整除的地址开端分配。
假如下一个地址正好能被Num整除,那么,该伪指令不起作用,不然,汇编程序将空出若干个字节,直到下一个地址能被Num整除停止。
.global _start
_start:
.text
mov r0,#1
mov r1,#2
mov r2,#3
mov r3,#4
b main
buf:
.byte 0x88 @在这个位置占用一个字节的空间,这个空间中保存的数据是0x88
.align(4) @假如没有这一句,main处地址是0x11,明显不是4的倍数,无法执行后续命令,会直接报错;如果加了.aglign(4)表示会自动补充对齐,即让main处地址为0x14;
main:
mov r1,#3
mov r1,#3
.end
.global _start
_start:
.org 0x14,0x11 @指定从当前地址加偏移地址0x14开始存放代码,并且当前地址到当前地址加偏移地址0x12之间内存单元即,每个字节用0x11填充
.text
mov r0,#1
mov r1,#2
mov r2,#3
mov r3,#4
b main
buf:
.byte 0x88
.align(4)
main:
mov r1,#3
mov r1,#3
.end
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0;
. = ALIGN(4);
.text :
{
./arm.o(.text)
*(.text)
}
. = ALIGN(4);
.rodata :
{ *(.rodata) }
. = ALIGN(4);
.data :
{ *(.data) }
. = ALIGN(4);
.bss :
{ *(.bss) }
}
.global _start
_start:
.text
mov r0,#0
mov r1,#1
mov r2,#2
.end
global _start
start:
.text
mov r0,#0x1
ldr r2,=thumb @伪指令r2=thumb
add r1,r0,r2
bx r1
thumb:
.Thumb @等效于.code 16
mov r0,#1
mov r1,#2
mov r2,#3
.end
或者
global _start
start:
.text
mov r0,#0x1
ldr r2,_thumb @不是伪指令,r2=*(_thumb)
add r1,r0,r2
bx r1
_thumb:
.word thumb
thumb:
.Thumb @等效于.code 16
mov r0,#1
mov r1,#2
mov r2,#3
.end
在C语言中我们对于宏的操作都不会陌生 好处呢这里也就不多讲了
提一点:有些函数 实体部分其实只有一行 这里可以进行宏代替 这样就可以减少函数的损耗
函数的消耗对比于普通的宏来讲 主要在于进入函数的压栈 跳转 出函数的出栈 跳转
宏没有这个过程 就等于较少了消耗
这里的arm汇编中给出 arm汇编和gnu arm 汇编 对于宏的操作
arm汇编
MACRO ;宏开始
$label HH $base,$ap,$d,$c,$b ;必须顶格写 声明宏名:HH 后面是他的四个参数
$label ;宏的标签
DCD (\base << 20) | (\ap << 10) | \ ;宏的实体
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
MEND ;宏结束的标志
gnu arm汇编的宏
.macro HH base,ap,d,c,b ;宏开始 宏名 参数
.word (\base << 20) | (\ap << 10) | \ ;宏实体部分
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm ;宏结束