专题3-汇编语言得玩转
第1课-汇编概述
1.为什么学习汇编指令
以后的工作中我们用的都是中高端的处理器,基本不会用汇编去编程完成这个产品。但是在bootloader和内核的编程中我们都是要用汇编语言的,这期间c语言的运行环境还没有搭建。在对效率有有特殊要求的地方我们还是需要汇编语言的。
2.分类
目前常用的ARM的汇编指令有两种:
(1)ARM标准汇编:适用于ARM公司的汇编器,适合在windows平台下使用,如ADS中使用。ADS是基于windows的集成程序。
(2)GNU汇编:适用于GNU交叉编译器工具链中的汇编器,适合linux开发平台。我们使用的是GNU汇编。
3.汇编程序框架
.sectiom.data
<初始化的数据>
.section.bss
<未初始化的数据>
.section.text /*代码段*/
.global _start /*全局访问*/
_start:
<汇编代码>
简化:
.text
.global _start
_start:
<汇编代码>
4.编程准备
(1)首先在相应的文档里面建立一个名字为start.s的文件,文件内容如下:
.text
.global _start //定义全局变量
_start: //程序入口
mov r1,#1 /*将1这个立即数,放在r1寄存器里面*/
mov r2,#2
mov r3,#3
(2)建立一个Makefile,内容如下:
all:start.o
arm-linux-ld -Ttext 0x20000000 -o start.elf $^ /*指明程序运行的起止地址*/
%.o : %.S
Arm-linux-gcc -g -o $@ $^ .c
Clean
rm *.o *.elf
/*针对210的开发板,代码段的指定开头就是20000000,对于6410是50000000,2440是30000000*/
连接器脚本对整个程序的地址进行排布,可以在开头进行运行地址的设置。
以Jlink的方式连接开发板与电脑,运行Jlink发现联接上了,启动我们自己安装的eclipse软件,编译我们编译的软件。按上面章节的方法,进行一系列的配置与初始化调试。
第二课-指令分类学习
GUN汇编和ARM标准汇编是有区别的,一般来说GUN是小写的,标准ARM是大写的。
1. 算数和逻辑指令
(1)mov装载
mov{条件}[S] <dest>, <op_1>
C语言转换:dest = op_1
mov从一个寄存器、被位移的寄存器、或一个立即数装载到目的寄存器。
dest必须是通用寄存器,op_1可以是通用寄存器、状态寄存器也可以是数字。
注:汇编语言的注释是@符号后面加注释的。
(2)mvn取反装载
mvn{条件}[S] <dest>, <op_1>
C语言转换:dest = !op_1
mvn从一个寄存器、被位移的寄存器、或一个立即数装载到目的寄存器。不同之处是在传送之前位被取反了,是把取反的值传给一个寄存器。这是逻辑操作不是算数操作,这个取反的值加1才是它的取负的值。
mov R0, #4 R0变成-5
mov R0, #0 R0变成-1
(3)sub相减
sub{条件}[S] <dest>, <op_1> <op_2>
dest= op_1- op_2
sub用来执行减法操作,操作数1是一个寄存器,操作数2可以是一个寄存器、被位移的寄存器或者是一个立即数。
(4)add相加
add{条件}[S] <dest>, <op_1> <op_2>
dest= op_1+op_2
add用来执行加法操作,操作数1是一个寄存器,操作数2可以是一个寄存器、被位移的寄存器或者是一个立即数。
(5)and逻辑与
and{条件}[S] <dest>, <op_1> <op_2>
dest= op_1&op_2
and用来执行逻辑与操作。操作数1是一个寄存器,操作数2可以是一个寄存器、被位移的寄存器或者是一个立即数。
(6)bic位清除
bic{条件}[S] <dest>, <op_1> <op_2>
dest= op_1and(!op_2)
在一个字中清除位的一种方法,与or位设置是相反的操作。操作数2是一个32位的位操作符(mask)。如果在操作码中设置了某一位,就清除这一位。未设置操作码的位保持不变。
bic r0, r0, #1011 表示清除r0中的第0、1、3位,其余的位不变
2. 比较指令
(1)cmp比较
cmp{条件}[S] <op_1> <op_2>
status= op_1-op_2
status为1表示操作数1大,为-1表示操作数2大,为0表示一样大。它改变的是CPSR寄存器的31位(M位)于30位(N位)。
例子
mov r1, #2
cmp r1, #1
cpsr为200001d3
mov r1, #2
cmp r1, #1
cpsr为800001d3
mov r1, #2
cmp r1, #2
cpsr为600001d3
(2)tst按位与
tst{条件}[S] <op_1> <op_2>
status= op_1 AND op_2
按位与之后的结果是0,CPSR的30位(Z位)置1,否则CPSR的30位(Z位)不置1。
例子
mov r1, #0b101
tst r1, #0b001
cpsr为200001d3
mov r1, #0b101
tst r1, #0b101
cpsr为600001d3
3. 跳转指令
汇编的程序需要分支指令完成C语言中的if…else操作
(1)b分支
b{条件} <地址>
存储在分支指令中的实际的值是相当于当前的r15的一个偏移量,并不是一个绝对地址。它的值由汇编器来计算,它是24位有符号量,左移两位后有符号扩展为32位,表示的有效偏移量是26位(+/-32M)。
例子:
mov r1,#6
mov r2,#5
cmp r1,r2
bgt branch1:
add r3, r1, r2
b end
branch1:
sub r3,r1,r2
end:
nop
EQ : 等于
如果一次比较之后设置了 Z 标志。
NE : 不等于
如果一次比较之后清除了 Z 标志。
VS : 溢出设置
如果在一次算术操作之后设置了 V 标志,计算的结果不适合放入一个 32bit 目标寄存器中。
VC : 溢出清除
如果清除了 V 标志,与 VS 相反。
HI : 高于(无符号)
如果一次比较之后设置了 C 标志并清除了 Z 标志。
LS : 低于或同于(无符号)
如果一次比较操作之后清除了 C 标志或设置了 Z 标志。
PL : 正号
如果一次算术操作之后清除了 N。出于定义‘正号’的目的,零是正数的原因是它不是负数...
MI : 负号
如果一次算术操作之后设置了 N 标志。
CS : 进位设置
如果一次算术操作或移位操作之后设置了 C 标志,操作的结果不能表示为 32bit。你可以把 C 标志当作结果的第 33 位。
CC : 进位清除
与 CS 相反。
GE : 大于或等于(有符号)
如果一次比较之后...
设置了 N 标志并设置了 V 标志
或者...
清除了 N 标志并清除了 V 标志。
GT : 大于(有符号)
如果一次比较之后...
设置了 N 标志并设置了 V 标志
或者...
清除了 N 标志并清除了 V 标志
并且...
清除了 Z 标志。
LE : 小于或等于(有符号)
如果一次比较之后...
设置了 N 标志并清除了 V 标志
或者...
清除了 N 标志并设置了 V 标志
并且...
设置了 Z 标志。
LT : 小于(有符号)
如果一次比较之后...
设置了 N 标志并清除了 V 标志。
或者...
清除了 N 标志并设置了 V 标志。
AL : 总是
缺省条件,所以不用明显声明。
NV : 从不
不是特别有用,它表示应当永远不执行这个指令。是穷人的 NOP。
包含 NV 是为了完整性(与 AL 相对),你不应该在你的代码中使用它。
(2)bl带链接返回的分支
b指令在跳转之前不能将返回地址保存,bl就可以将返回地址保存,可以起到类似C语言的对函数进行封装的效果。
例如:
mov r1, #6
cmp r1, #5
bl func1:
mov r1, #2
cmp r1, #3
func1:
mov r1, #r2
mov r2, #3
mov pc, lr
4. 移位指令
(1)lsl逻辑(算数)左移
例如:
mov r1, #0b1
mov r1,r1, lsl#2
对r1左移两位,将结果再存在r1中,r1从0b1变成0b100
(2)ror循环右移
例如:
mov r1, #0b11
mov r1,r1, ror#1
r1的值从0b11变成0b1000000000000000000000000000001,一共32位。
5. 程序状态字访问指令
程序状态寄存器所用的访问指令和前面的指令不能是一样的,对它们的修改,需要将CPSR或者SPSR中的指令搬移到通用寄存器,改好后再搬回程序状态寄存器。
msr(搬入)和mrs(搬出)两条指令:
例子:
mrs r0, cpsr
orr r0, 0b100
msr cpsr, r0
将cpsr的第三位设置为1
6. 存储器访问指令
(1)ldr将内存中的值导入寄存器
str rd, [Rbase] 存储 rd 到 Rbase 所包含的有效地址。
str rd, [Rbase, Rindex] 存储 rd 到 Rbase + Rindex 所合成的有效地址。
str rd, [Rbase, #index] 存储 rd 到 Rbase + index 所合成的有效地址。
index 是一个立即值。
例如,rTR rd, [R1, #16] 将把 rd 存储到 r1+16。
str rd, [Rbase, Rindex]! 存储 rd 到 Rbase + Rindex 所合成的有效地址,
并且把这个新地址写回到 Rbase。
str rd, [Rbase, #index]! 存储 rd 到 Rbase + index 所合成的有效地址,
并且并且把这个新地址写回到 Rbase。
str rd, [Rbase], Rindex 存储 rd 到 Rbase 所包含的有效地址。
把 Rbase + Rindex 所合成的有效地址写回 Rbase。
str rd, [Rbase, Rindex, lsl #2]
存储 rd 到 Rbase + (Rindex * 4) 所合成的有效地址。
str rd, place 存储 rd 到 PC + place 所合成的有效地址。
(2)str将寄存器中的值保存到内存
第3课-伪指令
一.ARM机器码
汇编程序通过汇编器转化成机器码,机器码才能够在嵌入式芯片上运行。Arm机器码是32位的整数,机器码被分成不同大小的段,每个段都有各自独特的功能
mov r0, r1的机器码是e1a00001
二进制是:1110 00 0 1101 0 0000 0000 000000000001
moveq r0, #255的机器码是03a000ff
二进制是:0000 00 1 1101 0 0000 0000 000011111111
对于mov指令它的机器码格式如下:
第一段前4位表示条件,1110表示无条件,0000表示等于的条件
第二段是保留位,是00
第三段表示0-11存的是立即数的话就是1,存的是寄存器的话就是0
第四段opcode,它的作用是区分指令,mov指令就是1101
第五段是S,程序运行后影响CPSR寄存器就是1,不影响就是0
第六段是Rn,
第七段是目的寄存器,我们用的是r0,用的是0来标号,r1是0001
第八段是源操作数,寄存器r1是00000000001,#255是00001111111
这样我们可以观测到,第八段中存放数的大小是有限的,这样引出伪指令的概念。
二.定义类伪指令
伪指令本身并没有对应的机器码,它只是在编译而定时候起作用,或者转化为其他的实际指令来运行。
(1) global标明全局符号
(2) data标明数据段
(3) ascii标明字符串指令
(4) byte标明字节的指令
(5) word标明字的指令
例子:
.data
hello:
.ascii “hellloworld”
bh:
.byte 0x1
ADD
.word 0xff
(6) equ相当于宏定义
.equ DA,0x89
mov r0, DA
(7) align对齐,在文件前面加上.align就会表示按四字节对齐
三.操作类伪指令
(1)nop
空操作,主要的作用是延时的作用。
(2)ldr(与存储器的访问指令不同)
mov r0, 0x1ff运行的时候会出错,因为mov指令只能处理8位的二进制数,0x1ff是9位,机器码的0-11位中有四位是存储左移与右移操作的,所以存储的内容不能超过8位。
它可以在r0里面填充超过8位的立即数,但是格式特殊:
ldr r0, =0x1ff
第4课-协处理访问指令
一.什么是协处理器
协处理器用于执行特定的处理任务,如:数学协处理器可以控制数字处理,以减轻处理器的负担。ARM可支持最多16个协处理器,其中CP15是最重要的一个。
- CP15的作用
CP15是系统控制协处理器,提供了额外的寄存器,通过这些寄存器,可以达到配置与控制caches、MMU以及时钟参数。
- CP15寄存器
CP15提供了16组寄存器,具体的使用可以在Arm公司提供的芯片手册中找到。我们通过它提供的16组寄存器,可以访问cp15寄存器。
二.协处理器访问
1. mcr
r表示通用寄存器,c表示协处理器寄存器
2.mrc
格式:
例子:读取mainID
mrc p15, 0, r0, c0, c0, 0 表示读取mianID寄存器的值