跳到主要内容

汇编语言编程

环境

在线编辑器 https://cpulator.01xz.net/

arm文档 https://developer.arm.com/documentation/ddi0406/cd/

参照教程

Learn assembly language programming with ARMv7 in this beginner's course

https://www.youtube.com/watch?v=gfmRrPjnEw4

32位与16进制的关系(这里只讨论32位,64位同理)

二进制:0 1 16进制: 0 1 2 3 4 5 6 7 8 9 A B C D E F(a b c d e f),AF表示1015

计算的系统32位(bit位),则二进制占满32位,四个二进制位可以表示一个16进制的数据比如四个二进制数据 1 1 1 1 表示(12^0+12^1+12^2+1^23) 16进制的F(f),汇编中的地址与数据都是32位二进数据据以16进制显示,比如32位1 (1111 1111 1111 1111 1111 1111 1111 1111 )用16进制表示为ffffffff.

汇编中的程序入口

.global _start
_start:

  • 汇编中以 . 开头的名称并不是指令的助记符,而是 汇编指示 / 伪操作 ,不是真正的指令。

  • .globl 是一个关键字,让一个符号对链接器可见,可被其它链接对象模块使用.在 .globl _start 中,.globl 告诉汇编器,_start 这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号。如果一个符号没有用 .globl 声明,就表示这个符号不会被链接器用到。

  • _start 是一个符号,是汇编程序的默认入口标号。符号在汇编中代表一个地址。汇编程序中的符号经过汇编器的处理之后,所有的符号被替换成它所代表的地址值。(汇编器在翻译汇编程序时会计算每个数据对象和每条指令的地址,当看到一个符号定义时,就把它后面一条指令的地址作为这个符号所代表的地址。而 _start 这个符号又比较特殊,它所代表的地址是整个程序的入口地址)

.global _start
_start:
MOV R0,#30 //#号表示十进制数据
MOV R7,#1 //设置功能号,这里主要是对中断补补说明,根据实际情况后面可以有n条说明,也可不加,仅仅是个参数传递说明
SWI 0 //中断,参数是中断号说明

计数器

寄存器 R15 为程序计数器(PC),它指向 正在取指的地址。可以认为它是一个通用寄存器,但是对于它的使用有许多与指令相关的限制或特殊情况

寻址方式

指令名称说明
LDR加载指令LDR指令用亍从存储器中将一个32位的字数据传送到目的寄存器中
.global _start
_start:
LDR R0,=list //直接寻址,找到.data中list的地址,同时也是第一个元素4的地址
LDR R1,[R0] //取R0寄存器中地址链接的值,即是把第个元素4取出来放入R1
LDR R2,[R0,#4] //偏移取值,这里表示R0偏移4个字节
//LDR R2,[R0,#4]! // !表示预增量(说明见下),此时R0的地址会跟随增加4,然后根据R0增加后的地址再寻址
//LDR R2,[R0] ,#4 //别一种预增量写法
.data
list:
.word 4,5,-9,1,0,2,-2

相关说明

1.编辑器数据以十进制显示 这里输入图片描述

2.上面的[R0]相当于其他语言如python中数组取值:

list=[1,2,3,4] 
list[0] # 相当于list[R0]

3.负数在内存中以补码的形式表示,如-9补码fffffff7,补码计算如-3: 3的二进制 =>00000011 求反=>11111100 补码->11111101

补码计算公式为: 整数求反+1

4 预增量!:

一般是在寄存器或寻址方式之后,对于加了叹号的情况,访问内存时先根据寻址方式更改寄存器的值,再按照该已经更新的值访问内存

基本算术计算

指令名称说明
ADD加法
ADDS加法加法且更新cpsr
ADC加法加法带有进位
ADCS加法加法带有进位且更新cpsr, 这里的更新是指更新调式器的显示,属于debug使用
SUB减法
SUBS减法减法且更新cpsr,这里的更新是指更新调式器的显示,属于debug使用
SBC减法加法带有进位
SBC减法减法带有进位且更新cpsr
.global _start
_start:
MOV R0,#5
MOV R1,#7
//ADD R2,R0,R1// 加法 R2=R0+R1
//SUB R2,R0,R1// 减法 R2=R0+R1
//SUBS R2,R0,R1// SUBS 在计算时会设置cpsr寄存器标志位,结果为负数,则N=1;如果结果为非负数,则N=0
//MUL R2,R0,R1// 乘法 R2=R0+R1

带有进位

.global _start
_start:
MOV R0,#0xFFFFFFFF
MOV R1,#3
ADDS R2,R0,R1
//ADC R2,R0,R1
//ADCS R2,R0,R1

相关说明

  • SUBS 在计算时会设置cpsr寄存器标志位,结果为负数,则N=1;如果结果为非负数,则N=0,

这里输入图片描述 cpulator编辑器中负数时N为高亮显示,spsr的第一个数代表一个标志位,当前示例subs减法值为负数,所以N为1,即 1111 显示为16进制为8所以图中第一个标志位为8

逻辑运算 与 或 异或

.global _start
_start:
MOV R0,#0XFF
MOV R1,#22

//AND R2,R0,R1 //与运算
//ORR R2,R0,R1 //或运算
EOR R2,R0,R1 //异或

类似功能指令MVN, MVNMOV指令用法差不多,唯一的区别是:它赋值的时候,先按位取反

.global _start
_start:
MVN R0,#4

执行结果: R0=-5

过程分析:先对4转换成2进制(00000100),取反(11111011),求其补码,因为是负数,所以先对其正数(01111011)求反(10000100),然后加1 (10000101)=-5

负数的补码求法:对其正数求反+1

移位操作

指令名称说明
LSL逻辑左移 Logical Shift LeftLSL可完成对通用寄存器中的内容进行逻辑(或算术)的左移操作,按操作数所指定的数量向左移位,低位用零来填充。
LSR循环右移(Rotate Right)LSR 可完成对通用寄存器中的内容进行右移的操作,按操作数所指定的数量向右移位,左端用零来填充。
ROR循环右移(Rotate Right)ROR 可完成对通用寄存器中的内容进行循环右移的操作,按操作数所指定的数量向右循环移位, 左端用右端移出的位来填充
.global _start
_start:
MOV R0,#10
LSL R0,#1 //左移: 左移一位即乘2
LSR R0,#1 //右移: 左移一位即除以2 LSR 可完成对通用寄存器中的内容进行右移的操作,按操作数所指定的数量向右移位,左端用零来填充。
MOV R1,R0,LSL #1 // 表示先将R0的值赋给R1,然后再对R1进行左移一位,相当下面拆分的两句代码
//MOV R1,R0
//LSL R1,#1

分支指令 比较器

指令名称说明
CMP比较指令用于把一个寄存器的内容和另一个寄存器的内容或一个立即数进行比较,同时更新CPSR中条件标志位的值。
BGT转移指令如果大于,则跳转分支
BAL转移指令无条件跳转
BLT转移指令小于跳转
BLE转移指令小于等于跳转
BEQ转移指令相等跳转
BNE转移指令不相等跳转
.global _start
_start:
MOV R0,#1
MOV R1,#2
CMP R0,R1//比较R0 R1的值并设置cpsr寄存器`N`标志位

BGT greater //如果是正数则跳转到greater 标签例将R0设置为#3
MOV R2,#2

//注如果比较的值是负数这个标签也会在最后执行
greater:
MOV R1,#1
.global _start
_start:
MOV R0,#3
MOV R1,#2
CMP R0,R1//比较R0 R1的值并设置cpsr寄存器`N`标志位

BGT greater //如果是正数则跳转到greater 标签例将R0设置为#3
BAL default //

//注如果比较的值是负数这个标签也会在最后执行
greater:
MOV R1,#1

default:
MOV R2,#2

循环

指令名称说明
.equ常量(伪指令)定义常量

0至10求和

.global _start 
.equ endlist,0xaaaaaaaa
_start:
LDR R0,=list
LDR R3,=endlist
LDR R1,[R0]
ADD R2,R2,R1
loop:
LDR R1,[R0,#4]!
CMP R1,R3
BEQ exit
ADD R2,R2,R1
BAL loop

exit:

.data
list:
.word 1,2,3,4,5,6,7,8,9,10

条件指令

通常写法

.global _start
_start:
MOV R0,#2
MOV R1,#4
CMP R0,R1
BLT addR2
BAL exit

addR2:
ADD R2,#1

eixt:

使用条件指令实现

.global _start
_start:
MOV R0,#2
MOV R1,#4
CMP R0,R1
ADDLT R2,#1 //指令+条件后缀 小于+1
//MOVGE R2,#5

函数

指令名称说明
bl带链接的跳转首先将当前指令的下一条指令地址保存在LR寄存器,然后跳转的lable
BX跳转指令跳转到指令中所指定的目标地址
B跳转程序无条件跳转到标号 Label 处执行

求和 : 低级写法

.global _start
_start:
MOV R0,#1
MOV R1,#3
BAL add2

add2:
ADD R2,R0,R1

求和 函数式调用子程序

.global _start
_start:
MOV R0,#1
MOV R1,#3
BL add2 //键接跳转,将下一条指令地址存入LR寄存器
MOV R3,#4

add2:
ADD R2,R0,R1
bx lr //跳转到LR寄存器中存的地址

函数压栈

压栈:主要是保存一些局部变量数据,将寄存器预留出来给子程序用 出栈:恢复数据奇存器,用于后续逻辑 主要是将数据缓存到一地方

.global _start
_start:
MOV R0,#1
MOV R1,#3
PUSH {R0,R1}
BL get_value
POP {R0,R1}
B end

get_value:
MOV R0,#5
MOV R1,#7
ADD R2,R0,R1
BX lr
end:

这里输入图片描述 push内存载图,由图可以看出push数据时SP寄存器存的是第一个数据的地址,即栈顶地址

硬件交互

指令名称说明
LDR内存 --> 寄存器
STR寄存器 --> 内存
.equ SWITCH , 0xff200040 //这个地址是右侧Switchs中显示的设备内存地址
.equ LED,0XFF200020 //这个地地是右侧led设备的内存地址

.global _start
_start:
LDR R0,=SWITCH
LDR R1,[R0] //R1的值为Switchs 二进制位的和,选中为1未选中为0

LDR R0,=LED//加载led地址
STR R1,[R0] //将选中数字输出到led中显示;将R1中的值存到R0所指定的地址中

这里输入图片描述

安装qemu

Qemu是一个开源的托管虚拟机,通过纯软件来实现虚拟化模拟器,几乎可以模拟任何硬件设备。比如:Qemu可以模拟出一个ARM系统中的:CPU、内存、IO设备等,然后在这个模拟层之上,可以跑一台ARM虚拟机,这个ARM虚拟机认为自己在和硬件进行打交道,但实际上这些硬件都是Qemu模拟出来的。