• arm学习之基本汇编指令


    1. 汇编文件中的主要符号

    1.1 汇编指令

    汇编指令:汇编指令是一条指令,编译器可以将其编生成32位的机器码,汇编指令占用代码段的内存空间,执行汇编指令可以完成某个特定的功能。

    mov r0, r1 @将r1寄存器中的内容拷贝到寄存器r0中
    
    • 1

    1.2 伪指令

    伪指令:本身不是一条指令,但是编译器可以将其编译生成多条汇编指令,通过多条汇编指令最终完成一条伪指令的功能。

    ldr r0, =0x12345678 @ 汇编指令只有32位,0x12345678就32位了,也不是立即数,所以指令无法存储,所以需要伪指令
    
    • 1

    1.3 伪操作

    伪操作:伪操作不占用代码段的内存空间,给编译器使用,直到编译器对代码进行编译的。

    .text  .end   .data   .global  .globl  .word .short  .byte  .if 0/1  .else  .endif
    
    • 1

    2. 汇编指令的分类

    1. 数据操作指令
        数据搬移指令
        算数运算指令
        移位操作指令
        位运算指令
        比较指令
    2. 跳转指令
    3. Load/Store内存读写指令
    4. 软中断指令
    5. 特殊功能寄存器读写指令
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. 基本汇编指令语法格式

    {cond}{s}  Rd, Rn, oprand_shifter
    
     :指令码  比如mov b add  sub 
    {cond} : 条件标志, 实现汇编指令的有条件执行
    {s} :状态位, 指令码后边加s,表示指令的执行结果影响CPSR的NZCV的变化;指令码后边不加s, 表示指令的执行结果不影响CPSR的NZCV位
    
    Rd : 目标寄存器,只能是一个普通的寄存器 R0-R15
    Rn : 第一个操作寄存器,只能是一个普通的寄存器 R0-R15
    oprand_shifter : 第二个操作数
        1> 可以是一个普通的寄存器 R0-R15
        2> 可以是一个立即数
        3> 可以是经过移位操作的寄存器
    
    
    注意:
    {cond}{s} : 连到一起写
    Rd, Rn, oprand_shifter  :三个之间使用英文的逗号隔开
    {cond}{s}和Rd, Rn, oprand_shifter使用空格隔开
    
    每条汇编指令单独占用一行。
    汇编指令不区分大小写:
    mov r0, #0xff  <==> MOV R0, #0xFF <==> MoV R0, #0xff
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    arm汇编指令基本格式.png

    4. 数据操作指令

    4.1 数据搬移指令

    4.1.1 指令格式

    {cond}{s} Rd, oprand_shifter

    1> 没有第一个操作寄存器
    2> oprand_shifter可以是寄存器也可以是立即数

    mov : 将oprand_shifter赋值给Rd寄存器
    mvn : 将oprand_shifter按位取反之后赋值给Rd寄存器
    
    • 1
    • 2

    4.1.2 测试代码

        @ 0xFF 是一个立即数,使用立即数时前边需要加#
        @ 第二个操作数是一个立即数
    	mov r0, #0xFF  @ r0 = 0xFF 
    	
        @ 第二个操作数是一个寄存器
    	mov r1, r0     @ r1 = r0
    	
        @ mvn指令
    	mvn r2, #0xFF  @ r2 = ~0xFF
    	mvn r3, r2     @ r3 = ~r2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.1.3 立即数

            mov r0, #0xF    @ OK
    	mov r1, #0xFF	@ OK
    	@mov r2, #0xFFF  @ ERROR
    	@mov r3, #0xFFFF @ ERROR
    	@mov r4, #0xFFFFF @ ERROR
    	mov r5, #0xFFFFFF @ OK
    	mov r6, #0xFFFFFFF @ OK
    	mov r7, #0xFFFFFFFF @ OK
    	mov r8, #0xFF000000
    	@mov r9, #0xFFF00000
    	mov r10, #0x1F800000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    立即数.png

    4.1.4 ldr伪指令

    格式:ldr{cond}  Rd, =0-4G之间的数    @ Rd = 0-4G之间的数
    
    ldr r0, =0x1234567
    
    • 1
    • 2
    • 3

    4.1.5 验证PC寄存器

        @ mov pc, #0x10
        @ 给PC寄存器赋值时需要是4的整数倍,原因是一条ARM指令占4字节的空间
        @ 如果给PC寄存器赋值不是4的整数倍,编译内部会自动的忽略数据的第[1:0]位
        @ 给PC赋值时,如果第1位和第0位为1,都当成0处理。
    	mov pc, #5      @ pc = 0x5
    	mov pc, #6      @ pc = 0x6
    	mov pc, #7      @ pc = 0x7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.2 移位操作指令

    4.2.1 指令格式

    {cond}{s}  Rd, Rn, oprand_shifter 
    
    Rd = Rn lsl/lsr/ror/asr oprand_shifter 
    
    lsl : 逻辑左移/无符号数左移 
    lsr :逻辑右移/无符号数右移
    asr :算数右移/有符号数的右移
    ror :循环右移
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.2.2 测试代码

            mov r0, #0xFF
    	@ 高位移出,低位补0
    	lsl r1, r0, #0x4   @ r1 = r0 << 4 = 0xFF0
    	
    	@ 低位移出,高位补0
    	lsr r2, r0, #0x4   @ r2 = r0 >> 4 = 0x0F
    	
    	@ 低位移出,高位补符号位,因为数据在计算机中按补码存储
    	asr r3, r0, #0x4   @ r3 = r0 >> 4 = 0x0F
    	
    	@ 低位移出,补到高位
    	ror r4, r0, #0x4   @ r4 = r0 >> 4 = 0xF000000F
    	
    	mov r0, #-0x7F000000
    	asr r5, r0, #0x4
    	
    	@ 移位操作指令中的立即数的范围为0-31
    	lsl r6, r0, #31
    	
    	@ 第二个操作为一个移位操作的寄存器
    	@ 第二个操作数只能是一个寄存器经过移位指令进行移位
    	mov r0, r1, lsl #4   @ r0 = (r1 << 4)
    	
    	@ 如果第二个操作数是一个立即数,可以使用<< >> ~ 进行运算
    	mov r0, #(0xFF << 4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4.3 位运算指令

    4.3.1 指令格式

    {cond}{s}  Rd, Rn, oprand_shifter  
    
    @ Rd = Rn 与/或/异或  oprand_shifter  
    and : 按位与运算  & 
    orr : 按位或运算  |
    eor : 按位异或运算  ^
    bic : 按位清除
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    与运算

    左操作数位运算符右操作数运算结果
    1&00
    0&00
    1&11
    0&10

    或运算

    左操作数位运算符右操作数运算结果
    1|00
    0|00
    1|11
    0|10

    异或运算

    左操作数位运算符右操作数运算结果
    1^00
    0^00
    1^11
    0^10

    与0清0,与1不变
    或1置1,或0不变
    异或1取反,异或0不变

    4.3.2 测试代码

            @ 假设你不知道R0寄存器中存储的值
    	ldr r0, =0x12345678
    	@ 31                                     0
    	@  **** **** **** **** **** **** **** ****
    	@ 1> 将R0寄存器中的值的第[3]位清0,保持其他位不变
            and r0, r0, #0xFFFFFFF7
            and r0, r0, #~(0x1 << 3)
    	bic r0, r0, #(0x1 << 3)
    	
    	@ 2> 将r0寄存器中的值的第[29]位置1,保持其他位不变
    	orr r0, r0, #(0x1 << 29)
    	
    	@ 3> 将R0寄存器中的值的第[7:4]位清0,保持其他位不变
    	and r0, r0, #(~(0xF << 4))
    	bic r0, r0, #(0xF << 4)
    
    	@ 4> 将R0寄存器中的值的第[15:8]位置1,保持其他位不变
    	orr r0, r0, #(0xFF << 8)
    	
    	@ 5> 将R0寄存器中的值的第[3:0]位按位取反,保持其他位不变
    	eor r0, r0, #(0xF << 0)
    	
    	@ 6> 将R0寄存器中的值的第[11:4]位修改为10101011,保持其他位不变	
    	@ 6.1> 先将对应的位清0
    	and r0, r0, #(~(0xFF << 4))
    	@ 6.2> 再将对应的位置1
    	orr r0, r0, #(0xAB << 4)
    	
    	@ 一般情况不使用先置1,后清0
    	@ 6.1 先将对应的位置1
    	orr r0, r0, #(0xFF << 4)
    	@ 6.2 再将对应的位清0
    	and r0, r0, #(~(0x54 << 4))
    	
    	@ bic位清除指令
    	@ 第二个操作数据的哪位是1,最终将第一个操作寄存器中的置的
    	@ 哪一位清0,并将结果写到目标寄存器中
    	bic r0, r0, #0xFF  @ 将R0的[7:0]位清0
    	
    	@ 如果目标寄存器的编号和第一个操作寄存器的编号相同,可以合并写一个
    	and r0, #~(0xF << 4)    @ 等价于 r0 &= ~(0xF << 4)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    4.4 算数运算指令

    4.4.1 指令格式

    {cond}{s}  Rd, Rn, oprand_shifter
    
    add : 普通的加法指令,不需要考虑进位标志位(C位)
    adc :带进位的加法指令,需要考虑进位标志位(C位)
    sub : 普通的减法指令,不需要考虑借位标志位(C位)
    sbc : 带借位的减法指令,需要考虑借位标志位(C位)
    mul : 乘法指令
    div : 除法指令, ARM-V7之前架构之前的架构都不支持除法指令,
    	ARM-v8架构之后架构支持除法指令
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.4.2 测试代码

            @ 案例1:实现两个64位数相加
    	@ r0和R1存放第1个64位的数 
    	@ r2和r3存放第2个64位的数 
    	@ r4和r5存放运算的结果
    	mov r0, #0xFFFFFFFE   @ 低32位
    	mov r1, #5     @ 高32位
    	mov r2, #6     @ 低32位
    	mov r3, #7     @ 高32位
    	
    	@ s : 指令的执行结果影响CPSR的NZCV位,修改NZCV值
    	adds r4, r0, r2  @ r4 = r0 + r2 = 0x4
    	@ adc : 带进位的加法指令,自动读取C位的值。
    	adc r5, r1, r3  @ r5 = r1 + r3 + C = 0xD
    	
    	@ 案例2:实现两个64位数相减
    	@ r0和R1存放第1个64位的数 
    	@ r2和r3存放第2个64位的数 
    	@ r4和r5存放运算的结果
    	mov r0, #5     @ 低32位
    	mov r1, #10    @ 高32位
    	mov r2, #6     @ 低32位
    	mov r3, #7     @ 高32位
    	
    	subs r4, r0, r2 @ r4 = r0 - r2 = 0xFFFFFFFF
    	sbc r5, r1, r3  @ r5 = r1 - r3 - !C = 0x2
    	
    	mov r0, #4
    	mov r1, #5
    	mul r2, r0, r1   @ r2 = r0 * r1 = 0x14
    	
    	@ 乘法指令的第二个操作数只能是一个寄存器
    	@ mul r2, r0, #5   @ error 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    4.5 比较指令

    4.5.1 指令格式

    cmp{cond}  Rn, oprand_shifter
    
    1. cmp指令没有目标寄存器,只有第一个操作寄存器和第二个操作数
    2. 比较指令比较的就是第一个操作寄存器和第二个操作数的大小,本质就是做减法运算
    3. 比较指令的运算结果最终影响的是CPSR的NZCV位,并且CMP指令后不需要加S.
    4. 使用比较指令之后的结果,必须和条件码配合使用,条件码如下如所示:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    条件码.png
    就是省略了回写的subs指令,可以直接用减法指令替换,其功能就是将cpsr寄存器的某些位置一清零。

    4.5.2 测试指令代码

    	@ 比较R0和R1寄存器中的值
    	@ 如果R0>R1,则R0 = R0-r1
    	@ 如果R0
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    5. 跳转指令

    5.1 指令格式

    b/bl{cond}   Label(标签,汇编函数的名字,表示函数的入口地址)
    
    b  ----> 不带返回值的跳转指令,执行跳转指令是不保存返回地址到LR寄存器中
    bl ----> 带返回值的跳转指令,执行跳转指令是保存返回地址到LR寄存器中
    
    b : 有去无回就用b,比如while(1)死循环
    bl : 有去有回就用bl,比如函数的调用
    
    跳转指令的本质就是修改PC值,执行跳转指令时将Label标签标识的地址赋值给PC.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.2 测试代码

            @ nop 空指令
    	nop
    	nop
    	nop 
    	@ 执行跳转指令时修改PC值执行add_func函数的第一条指令
    	@ 同时保存跳转指令的下一条指令的地址到LR寄存器
    	bl add_func
    	nop 
    	nop
    	nop
    	@ 跳转到loop标签下的第一条指令执行,
    	@ 不保存返回地址到LR中
    	b loop
    	
    add_func:
    	mov r0, #0x3
    	mov r1, #0x4
    	add r2, r0 , r1
    	mov pc, lr   @ mov r15, r14
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5.3 实现跳转的其他方式

    mov pc, lr     @ 一般用于函数的返回,
    
    mov pc, oprand_shifter  @ 需要确定跳转的地址, oprand_shifter必须是4的整数倍
    
    ldr pc, =Label   @ 等价于b Label
    
    ldr r0, =Label
    mov pc, r0       @ 等价于b Label
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    注意:

    mov pc, #Label 
    一般没人这么干,因为你不能保证你的标签是一个立即数
    
    要么是使用寄存器直接给pc赋值
    mov pc, rn 
    
    要么是使用ldr伪指令
    ldr pc, =Label 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.4 练习

    5.4.1 求两个数的最大公约数

    最大公约数.png
    思路:

    1. 先判断两个数是否相等,若相等则这个数就是最大公约数
    2. 若不相等,则让大数减小数
    3. 再次执行步骤1,直至找到最大公约数

    代码实现:

            mov r0, #9
    	mov r1, #15
    	mm:	
    	cmp r0, r1
    	beq loop
    	subhi r0, r0, r1
    	subcc r1, r1, r0
    	b mm
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.4.2 实现for循环,求1-100的和

    用C语言实现:

    sum = 0;
    for(i = 1;i <= 100; i++){
        sum += i;
    }
    
    • 1
    • 2
    • 3
    • 4
    for循环语句的执行过程
    for (1;2;3)
    {
    	4;
    }
    [1,2][4,3,2][4,3,2][4,3,2]........
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    汇编实现:

    	mov r0, #0   @ 等价于sum
    	mov r1, #1   @ 等价于i, 表达式i
    	mm:
    	cmp r1, #100 @ 表达式2
    	bhi loop
    	add r0, r0, r1 @ 表达式4
    	add r1, r1, #1 @ 表达式3
    	b mm
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    6. 特殊功能寄存器操作指令

    6.1 指令格式

    mrs  Rd, cpsr     @ Rd = cpsr
    msr  cpsr, oprand_shifter @ cpsr = oprand_shifter
    
    mrs : 将特殊功能寄存器中的值读到普通寄存器中 
    msr : 将普通寄存器中的值读到特殊功能寄存器中
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对于特殊功能寄存器的读写访问,只能使用mrs或者msr完成。

    6.2 测试代码

    	@ 系统上电就是一个复位的信号,默认处理器工作在SVC模式
    	
    	@ 从SVC模式切换到用户模式,修改CPSR的模式位,保证其他位不变
    	
    	@ 在SVC模式下CPSR寄存器中的值 
    	@ I[7] = 1   F[6] = 1  T[5] = 0  M[4:0] = 10011
    	@ 修改模式位,切换到用户模式,保证其他位不变
    	@ 修改之后,CPSR中的值应该为
    	@ I[7] = 1   F[6] = 1  T[5] = 0  M[4:0] = 10000
    	
    	@ 方式1:之间修改CPSR的模式位,前提必须的确定其他位的值
    	@ msr cpsr, #0xD0   @ 1101 0000
    	
    	@ 方式2: 只修改CPSR的模式位,其他位不变
    	@ 1. 先将cpsr中的值读到普通寄存器中
    	mrs  r0, cpsr
    	@ 2. 修改普通寄存器中的[4:0]位
    	bic r0, r0, #0x1F
    	orr r0, r0, #0x10
    	@ 3. 将结果写回到cpsr中
    	msr cpsr, r0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    7. Load/Store内存读写指令

    7.1 单寄存器指令

    7.1.1 指令码

    ldr/str  : 一次读写1个字空间的大小的数据
    ldrh/strh :一次读写半个字空间大小的数据
    ldrb/strb :一次读写一个字节空间大小的数据
    
    ld : Load(加载)
    st : Store(存储)
    r  : Register
    h  : Half
    b  : Byte
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    7.1.2 指令格式

    ldr/ldrh/ldrb  Rd, [Rm]
    	1> 将Rm寄存器中的数据当成一个内存的地址,
    	2> 将[Rm]指向的空间的数据读到Rd寄存器。
    	int a = 100;
    	int *p = &a;
    	int b = *p;
    	----------------------
    	Rm   <======>  p
    	[Rm] <======>  *p;
    	ldr Rd, [Rm] <=====>  b = *p;
    	
    str/strh/strb  Rn, [Rm]
    	1> 将Rm寄存器中的数据当成一个内存的地址,
    	2> 将Rn寄存器中的数据写到[Rm]指向的内存空间中。
    	int a = 100;
    	int *p = &a;
    	int b = 200; 
    	*p = b;
    	----------------
    	Rm   <======> p
    	[Rm] <======> *p
    	str Rn, [Rm] <=====>  *p = b;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    7.1.3 测试代码

    	ldr r0, =0x12345678
    	ldr r1, =0x40000800
    	
    	@ 将R0寄存器中的数据写到[R1]指向的地址空间中
    	str r0, [R1]
    	@ 将[R1]指向的地址空间中的数据读到R2寄存器中
    	ldr r2, [R1]
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    查看内存1.png
    查看内存2.png

    7.1.4 练习

    	ldr r0, =0x40000800
    	ldr r1, =0x11111111
    	ldr r2, =0x22222222
    	ldr r3, =0x33333333
    	
    	@ 将r1寄存器中的数据写到[R0+4]指向的地址空间中,
    	@ R0寄存器中的地址不变。
    	@ [0x40000804] = 0x11111111   R0 = 0x40000800
    	str r1, [r0, #4]
    	
    	
    	@ 将R2寄存器中的数据写到[R0]指向的地址空间中,
    	@ 同时更新R0寄存器中的地址,R0 = R0 + 4;
    	@ [0x40000800] = 0x22222222   R0 = 0x40000804
    	str r2, [r0], #4
    	
    	@ 将r3寄存器中的数据写到[R0+4]指向的地址空间中,
    	@ 同时更新R0寄存器中的地址,R0 = R0 + 4;
    	@ ! : 更新地址
    	@ [0x40000808] = 0x33333333   R0 = 0x40000808
    	str r3, [r0, #4]!
    	
    	@ 以上三种写法同样适用于ldr str ldrh strh ldrb strb
    	@ 偏移地址必须是自己操作空间整数倍。
    	
    	@ 使用汇编验证大小端,
    	@ 使用str存储数据,使用ldrb分别读取数据。
    	
    		
    	ldr r0, =0x40000800
    	ldr r1, =0x12345678
    	str r1, [r0]
    	ldrb r2, [r0, #0]
    	ldrb r3, [r0, #1]
    	ldrb r4, [r0, #2]
    	ldrb r5, [r0, #3]
    
    	
    	ldrb r2, [r0], #1
    	ldrb r3, [r0], #1
    	ldrb r4, [r0], #1
    	ldrb r5, [r0], #1
    	
    
    	ldr r0, =0x40000800
    	ldrb r2, [r0]
    	ldrb r3, [r0, #1]!
    	ldrb r4, [r0, #1]!
    	ldrb r5, [r0, #1]!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    7.2 多寄存器操作指令

    7.2.1 指令码

    stm : 将多个寄存器中的值写到连续的地址空间中
    ldm :将连续的地址空间中的数据读到多个寄存器中
    
    m : multi
    
    • 1
    • 2
    • 3
    • 4

    7.2.2 指令格式

    stm  Rm, {寄存器列表}
    	1> 将Rm寄存器中的数据当成一个地址看待
    	2> 多个不同编号的寄存器
    ldm  Rm, {寄存器列表}
    	1> 将Rm寄存器中的数据当成一个地址看待
    	2> 多个不同编号的寄存器
    
    寄存器列表的书写方式:
    	1> 如果寄存器列表中的寄存器编号连续使用“-”隔开, 比如:r0-r5
    	2> 如果寄存器列表中的寄存器编号不连续使用“,”隔开,比如:r0-r5, lr
    	3> 寄存器列表中的寄存器要求从小到大依次书写。
    	4> 如果寄存器列表中的寄存器,按照从大到小进行书写,所有的寄存器必须单独书写,
    		不可以使用"-",只能使用“,”,并且编译时会报警告。
    		比如:
    			r5-r0 : 错误书写格式
    			r5,r4,r3,r2,r1,r0 : 正确,但是报警告。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    7.2.3 测试代码

    	ldr r0, =0x40000800
    	ldr r1, =0x11111111
    	ldr r2, =0x22222222
    	ldr r3, =0x33333333
    	ldr r4, =0x44444444
    	ldr r5, =0x55555555
    	
    	@ 将r1-r5寄存器中的数据写到R0指向的连续的20字节内存空间中
    	stm r0, {r1-r5}
    	
    	@ 将r0指向的连续的20字节空间的数据读到r6-r10寄存器中。
    	@ ldm r0, {r6-r9,r10}
    	
    	ldm r0, {r10,r9,r8,r7,r6}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    stm-ldm.png
    stm-ldm2.png

    7.3 栈操作指令

    7.3.1 栈的类型

    增栈:压栈之后,栈指针向高地址方向移动。
    减栈:压栈指针,栈指针向低地址方向移动。
    满栈:当前栈指针指向的空间右有效的数据,需要先移动栈指针,让栈指针指向一个没有有效数据的空间,然后再压入数据,压入数据之后,栈指针指向的空间又包含有效数据了。
    满栈.png
    空栈:当前栈指针指向的栈空间,没有有效的数据,可以先进行压栈,压入数据之后栈指针指向的空间就有有效的数据了,因此需要移动栈指针,让栈指针再次指向一个没有有效数据的空间。
    空栈.png

    将定义两两结合就行成了:
    满减栈
    满增栈
    空减栈
    空增栈

    7.3.2 栈空间的操作方式

    满增栈 :Full Ascending 		stmfa/ldmfa
    满减栈 :Full Descending		stmfd/ldmfd
    空增栈 :Empty Ascending		stmea/ldmea
    空减栈 :Empty Descending		stmed/ldmed
    
    ARM处理器中默认使用的满减栈,stmfd/ldmfd。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7.3.3 指令格式

    stmfd sp!, {寄存器列表}
    ldmfd sp!, {寄存器列表}
    
    	1> 将sp寄存器中的数据当成一个地址看待
    	2> 多个不同编号的寄存器
    	3> ! : 作用,每次压栈或者出栈完成之后都需要跟新栈指针寄存器中的地址。
    
    寄存器列表的书写方式:
    	1> 如果寄存器列表中的寄存器编号连续使用“-”隔开, 比如:r0-r5
    	2> 如果寄存器列表中的寄存器编号不连续使用“,”隔开,比如:r0-r5, lr
    	3> 寄存器列表中的寄存器要求从小到大依次书写。
    	4> 如果寄存器列表中的寄存器,按照从大到小进行书写,所有的寄存器必须单独书写,
    		不可以使用"-",只能使用“,”,并且编译时会报警告。
    		比如:
    			r5-r0 : 错误书写格式
    			r5,r4,r3,r2,r1,r0 : 正确,但是报警告。
    	5> 不管寄存器列表中的寄存器如何书写,永远都是小编号的寄存器对应这低地址,
    		大编号的寄存器对应这高地址。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    7.3.4 测试代码

            @ 1. 初始化栈指针
    	ldr sp, =0x40000820
    	
    	@ 2. 初始化r0,r1寄存器
    	mov r0, #3
    	mov r1, #4
    	bl add_func
    	add r2, r0, r1  @ r2 = r0 + r1 = 0x7
    	nop
    	nop 
    	b loop
    	
    	add_func:
    		stmfd sp!, {r0-r1,lr} @ 压栈保存现场
    		mov r0, #5
    		mov r1, #6
    		bl sub_func 
    		add r3, r0, r1  @ r3 = r0 + r1 = 0xB
    		ldmfd sp!, {r0-r1,pc} @ 出栈恢复现场
    		@ mov pc, lr
    		
    	sub_func:
    		stmfd sp!, {r0-r1}   @ 压栈保存现场
    		mov r0, #10
    		mov r1, #7
    		sub r4, r0, r1  @ r4 = r0 - r1 = 0x3
    		ldmfd sp!, {r0-r1}   @ 出栈恢复现场
    		mov pc, lr
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
  • 相关阅读:
    基于token进行登录,每次请求携带token
    用这个衍生方法增加维度,风控模型特征太少也不怕
    对小数部分四舍五入到指定的位数 numpy.around()
    第十一周:机器学习
    表空间的空间管理算法
    Kruskal算法
    网络之以太网
    图像像素值统计&图像几何形状的绘制&随机数与随机颜色
    【Python 千题 —— 基础篇】字符串拼接
    URP渲染管线实战教程系列 之URP渲染管线实战解密(一)
  • 原文地址:https://blog.csdn.net/qq_41555003/article/details/127597229