ARM汇编详解

 

基本指令学习

在博文:keil下ARM汇编程序建立与调试简介中学习建立ARM汇编程序工程。

本博文开始学习一步一步写ARM汇编程序。

一、重要概念理解

1. 立即数

1)把数据转换成二进制形式,从低到高写成 4位1组的形式,最高位一组不够4位的前面补0

2)数1的个数,如果大于8个【可能也是立即数,取反】不是立即数,如果小于等于8个 进行下面步骤

3)如果数据中间有连续的大于等于24个0,循环左移2的倍数,使高位全为0

4)找到最高位的1,去掉前面的最大偶数个0

5)找到最低位的1,去掉后面偶数个0

6)数剩下的位数,如果小于等于8位,那么这个数就是个立即数,反之就不是立即数

举例:判断0x80000001是不是立即数

1)二进制形式:1000 0000 00000000 0000 0000 0000 0001

2)1的个数小于8个,进入第三步

3)循环左移2位,这步结果为:00 0000 0000 0000 0000 0000 0000 000110

4)这步结果为:0110

5)这步结果为:0110

6)满足条件,所以0x80000001是立即数

判断一个数是否是立即数的目的是:在汇编操作中,有些指令操作的数只能是立即数,比如movr0,#0xnum ; num就必须是立即数。

2. 条件码,

条件码:本条指令的执行,依赖于上一个指令的执行结果举例理解,

比如寄存器中r0和r1分别保存两个数,如果r0小于r1,将r1值传给r0:  cmp r0,r1 ;比较r0和r1,会有某个标志位记下它们比较的结果movlt r0,r1 ;movlt = mov + lt ,查上表知为lt为带符号数小于,也就是说mov在r0< r1时,才执行mov操作。如何查看条件是否成立呢?cmpr0,r1执行后,cpu就查看cpsr中N是否等于V, 不等于是说明r0< r1成立,执行mov操作。  

3. cpsr中各个标志位具体含义   标志位  含                                 义       N  当用两个补码表示的带符号数进行运算时,N=1表示运算的结果为负数;N=0表示运算的结果为正数或零       Z  Z=1表示运算的结果为零,Z=0表示运算的结果非零。       C  可以有4种方法设置C的值:        -加法运算(包括CMP):当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。        -减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。        -对于包含移位操作的非加/减运算指令,C为移出值的最后一位。        -对于其它的非加/减运算指令,C的值通常不会改变。       V  可以有2种方法设置V的值:        -对于加减法运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号位溢出        -对于其它的非加/减运算指令,V的值通常不会改变。       Q  在ARM V5及以上版本的E系列处理器中,用Q标志位指示增强的DSP运算指令是否发生了溢出。在其它版本的处理器中,Q标志位无定义        

二、基本指令练习

1. 两个寄存器值相加减,

示例代码如下:       areaexample,code,readonly       entry start        ;r0+ r1 -> r2(注:前面带有; 表示注解)       mov r0,#0x2       movr1,#0x1        addr2, r0, r1        ;r0- r1 -> r2     mov r0,#0x2       movr1,#0x1        subr2, r0, r1        end  

2. 两个寄存器值相与或异或,示例代码如下       areaexample,code,readonly       entry start        ;r0and r1       movr0,#0x1       movr1,#0x3       andr0,r0,r1        ;r0orr r1       movr0,#0x2       movr1,#0x1       orrr0,r0,r1        end  3.将特定的第几位清0,示例代码如下:       areaexample,code,readonly       entry start       ;bic将特定的位清0     mov r0,#0xff       movr1,#0x04 ;(0000 0100,将第3位清0)       bicr2,r0,r1        end  

3. 逻辑左移或逻辑右移,示例代码如下       areaexample,code,readonly       entry start        ;逻辑左移低位补0,高位丢弃       movr0,#0x1       movr1,r0,lsl #1; r1 = r0 << 0        ;逻辑右移低位丢弃,高位补0       movr0,#0xf0000007       movr1,r0,lsr #1        end

4. 算数左移或算数右移,示例代码如下       areaexample,code,readonly       entry start        ;注:算数左移与逻辑左移是同一个指令都是lsl         ;算数右移低位丢弃,高位补符号位       movr0,#0xf0000007       movr1,r0,asr #1        end  

5. 循环右移,示例代码如下       areaexample,code,readonly       entry start        ;循环右移 ror       ;左端填充右端移出去的位       ;11110000 0000 0000 0000 0000 0000 0111循环右移结果:1111 1000 00000000 0000 0000 0000 0011       movr0, #0xf0000007       movr1,r0,ror #1        end  

6. 带扩展位的循环右移,一次只能移一位,示例代码如下:        areaexample,code,readonly       entry start        ;带扩展位的循环右移唯一的一个不需要指定移位位数的指令       ;移除去的位,移到 cpsr的 c位中       movr0, #0xf0000007       movsr1,r0,rrx ;注意带有s        end

调试窗口如下:

注1:r0 为 1111 0000 0000 0000 0000 0000 0000 0111

注2:r1 为 0111 1000 0000 0000 0000 0000 0000 0011,最右面的1移到CPSR寄存器中的C中注3:r15(pc)保存cpu要执行的下一条指令的地址,这个地址保存的内容可以在Disassembly窗口中看到。

三、提高练习

1. 64 位加减,示例代码如下:       areaexample,code,readonly       entry start        ;64位加法,一个64位保存在r0和r1中,一个64位保存在r2和r3中       movr0,#0xffffffff       movr1,#0x1        movr2,#0x1       movr3,#0x1        addsr0,r0,r2       adc  r4,r1,r3        ;64位减法,一个64位保存在r0和r1中,一个64位保存在r2和r3中       movr0,#0x0       movr1,#0x2        movr2,#0x1       movr3,#0x1        subsr0,r0,r2 ;注意c借位为0,否则为1       sbc  r4,r1,r3        end  

2. 求两个数差的绝对值,示例代码如下:       areaexample,code,readonly       entry start       ;取两个数差的绝对值       movr0,#5       movr1,#4        cmpr0,r1       beqover       subgtr3,r0,r1       subltr3,r1,r0 over       bover        end  

3. 求3个数的最大数       areaexample,code,readonly       entry start        ;求3个数的最大数,将最大值保存在r0中       mov r0,#3       mov r1,#4       mov r2,#5        cmp r0,r1       movlt r0,r1      ;如果r0 < r1, 将 r1 -> r0         cmpr0,r2       movltr0,r2      ;如果r0< r2, 将 r2 -> r0         end  


 


b与blb  

  无条件跳转,不考虑回来的问题bl    在跳转时会把下一条指令的地址装载到lr寄存器中,以方便cpu最终能回来 示例:通过bl跳到func,通过b跳到over,通过调试查看lr值,示例代码如下,       area example,code,readonly       entry start        ;bl和b       mov r0,#0x1        bl func       mov r2,#0x3       b over func       mov r1,#0x2       bx lr over       b over       end  提高:程序在执行时,先跳到func1, 再跳到func2,再跳到func3,然后再返回主程序继续执行。示例代码如下,       area example,code,readonly       entry start        mov r0,#0x1       bl func1       mov r0,#0x2       b over func1       mov r1,lr       bl func2       mov r0,#0x3       mov pc,r1 func2       mov r2,lr       bl func3       mov r0,#0x04       mov pc,r2 func3       mov r0,#0x05       bx lr over       b over       end         注1:bl指令的偏移量不是相对于正在执行的那条指令,而是相对于预取指令-三级流水线注2:bl命令,最大寻址空间为±32M,访问其他内存通过ldr指令注3:进入子函数,通过栈保存返回地址+通用寄存器的值          


通过读写cpsr spsr设置arm cpu工作模式

注1:需要通过mrs读取cpsr或spsr寄存器值 注2:cpsr_c指令中各个位含义[31:24]  条件标志位  f[23:16]  状态位域   s[15:8]   扩展位域   x[7:0]  控制位域  c示例:通过cpsr  spsr设置arm cpu工作模式,代码如下:       areaexample,code,readonly       entry start        bl enable_irq       bl disable_irq       bl stack_init       b  over enable_irq       ;读取cpsr的值到r0       mrs r0, cpsr       ; 将中断标志位置0,使能中断       bicr0,r0,#0x80      msr cpsr_c,r0       bx lr disable_irq       mrs r0,cpsr; 将中断标志位置1,禁止中断       orr r0,r0,#0x80      msr cpsr_c,r0       bx lr stack_init      ;当前cpu工作模式为svc模式,将堆栈地址初始化为33c00000       ldr sp , =0x33c00000                         ; 将cpu工作模式为undef模式,将堆栈地址初始化为0x34000000       msr cpsr_c, #0xdb       mov sp , #0x34000000; 将cpu工作模式为abort模式,将堆栈地址初始化为0x33f00000       ;abort       msr cpsr_c, #0xd7      ldr sp , =0x33f00000; 将cpu工作模式为irq模式,将堆栈地址初始化为0x33e000000       ;irq       msr cpsr_c, #0xd2       ldr sp , =0x33e00000; 将cpu工作模式为usr模式,将堆栈地址初始化为0x33d000000,注意在这种模式下,不可再设置cpsr寄存器,因为usr模式访问资源有限。       ;usr       ;msr cpsr_c, 0xd0       ;ldr sp , =0x33d00000;cpu工作模式为返回svc模式      ;svc       msr cpsr_c, #0xd3       bx lrover       bover       end svc模式调试界面如下                                                                                                                                                      undef模式调试界面如下    注1:切换模式 必须通过msr这个指令注2:切换模式后,看到的 sp lr spsr 都是新的模式下的寄存器。注3: usr模式下,不能修改cpsr-----usr是非特权模式
 


ARM处理器的八种寻址

1. 立即数寻址add,r0,r0,#1

2. 寄存器寻址       add r0, r1, r2  

3. 寄存器间接寻址:以寄存器中的值作为操作数的地址       Ldr r0, [r1]

4. 寄存器移位寻址       mov r1,#7       mov r2,#1       add r0, r1, r2 ,lsl #2

5. 基址变址寻址: 将基址寄存器的内容与指令中给出的偏移量相加,得到一个有效的操作地址。通常用于访问连续的地址空间。三种索引方式:前索引   ldr r0, [r1, #4];将r0中的值传递给r1中的值+4的地址,r1中的值保持不变,示例代码如下:       ldr r1,=0x40000000       mov r2,#0x1       str r2,[r1,#4]自动索引 ldr r0, [r1, #4]!;将r0中的值传递给r1中的值+4的地址,r1中的值变为r1中的值+4,示例代码如下:       ldr r1,=0x40000000       mov r2,#0x1       str r2,[r1,#4]!后索引   ldr r0, [r1],#4;将r0中的值传递给r1中的值的地址,r1中的值变为r1中的值+4,示例代码如下:       ldr r1,=0x40000000       mov r2,#0x1       str r2,[r1],#4

6. 多寄存器寻址:可以实现一条指令完成多个寄存器值的传送,最多一次传送16个通用寄存器的值,连续的寄存器用‘-’连接,否则用逗号分隔。Xx可以是ia,ib,da,dbia  每次传递后地址+1ib  每次传递前地址+1da 每次传递后地址-1db 每次传递前地址-1示例代码如下:       ldr r1,=0x40000000       mov r2,#0x1       mov r3,#0x2       mov r4,#0x3       mov r5,#0x4        stmia r1!,{r2-r5}

7. 相对寻址,如下代码:       blablelable:

8. 堆栈寻址: 堆栈寻址是一种数据结构,按先进先出的方式操作,r13(sp)寄存器指示当前的栈顶位置,arm支持4种堆栈操作方式ia,ib,da,db。入栈:stmfd sp!, {r0-r12}出栈:ldmfd sp!,{r0-r12}示例代码如下:       ldr sp,=0x40001000        movr1,#0x1       movr2,#0x2       movr3,#0x3       movr4,#0x4        stmfd sp!,{r1-r4} 示例:使用堆栈寻址实现保存调用函数时的返回地址和通用寄存器值。      area example,code,readonly       entry start       ;初始化堆栈值       ldr sp,=0x40001000       movr1,#0x1       movr2,#0x2       movr3,#0x3       movr4,#0x4       bl func1       b over func1             ;保存返回地址和通用寄存器值到栈种       stmfd sp!,{r1-r4,lr}     mov r1,#0xf       mov r2,#0xf       mov r3,#0xf       mov r4,#0xf       bl func2       ;将栈中值弹出到通用寄存器中,而且将lr值直接放在pc中,实现返回       ldmfd sp!,{r1-r4,pc} func2       ;如上       stmfdsp!,{r1-r4,lr}     mov r1,#0xe       mov r2,#0xe       mov r3,#0xe       mov r4,#0xe       ;如上       ldmfd sp!,{r1-r4,pc}      over       bover       end  
 


软中断SWI

在Linux程序中,用户程序是不能访问调用系统资源,但是可以通过系统调用或异常访问系统资源。那usr模式下是如何实现系统调用呢? 答案是通过SWI + 中断号。每一个系统调用对应一个SWI + 中断号,在arch/arm/kernel/entry-common.S下保存所有的SWI+ 中断号对应的系统调用。在usr模式下,调用swi时cpu会跳到Supervisor工作模式下,在这个过程中会执行异常处理的所有操作,这个过程参考博文:arm异常处理流程。 示例代码如下:       areafirst, code, readonly       code32       entry; 定义的异常向量表vector       breset_handler  ; 跳转到 reset_handler       nop        bswi_handler  ; SWI 指令异常跳转的地址       nop        nop        nop        nop       nop  swi_handler       ;swi handler code        ;异常处理首先要压栈保存处理器现场              mrsr0, cpsr       movr0, #1      mov r1, #2              movspc, lr     ; lr > pc 且 spsr -> cpsr返回 SVC -> USER reset_handler       ;初始化 SVC 模式堆栈       ldrsp, =0x40002000        ;修改当前的模式从SVC模式改变为USER模式       mrsr0, cpsr       bicr0, r0, #0x1f       orrr0, r0, #0x10       msrcpsr_c, r0     ;初始化 USER 模式堆栈       ldrsp, =0x40001000        movr0, #1                  ;USER SWI       swi5  ; open APP USER 这条语句由用户程序自己出发异常          ;观察并记录对比指令执行前后的 PC LR CPSR SPSR SP的变化           addr1, r0, r0stop       bstop       end cpu在swi 5 后异常处理流程由硬件自己完成,这个过程如下图:注1:如果同时返回到异常发生的位置,同时又能切换模式只能使用如下指令:  movs pc,lr注2:进入swi异常,如何得到 swi 指令后面跟的那个参数?所有的系统调用,都是依赖swi指令,操作系统提前给所有的系统调用编号思路:swi异常发生的时候,会把该指令的下一条指令的地址给 svc模式的lr,通过lr可以找到swi的地址,ldr r0, [lr,#-4]   ; 获得SWI指令的机器码bic r0, r0,#0xff000000  ; 通过机器码获得SWI NUMBER
 


伪指令是用于告诉汇编程序如何进行汇编的指令。这里指的是MDK下的伪指令与GNU下的伪指令不一样。

一、常见伪指令【area】定义一个代码段或者数据段格式:area  段名 属性1,属性2……段名:code:代码段、data:数据段属性:readonly:只读、readwrite、align[4] 4字节对齐、code32  4字节、code16【entry】汇编程序的入口点,一个完整的汇编程序至少要有一个entry,一个文件中最多有一个entry,也可以没有,如果有多个entry,由链接器指定哪个是程序的入口点【end】程序的结尾【EQU】定义一个变量,相当于c语言中的define【export】声明一个全局的标号【import】通知编译器,要使用的标号在其他的源文件中定义,相当于c语言中的extern  

二、符号定义伪指令gbla 、gbll 、gbls、GBLA伪操作声明一个全局的算术变量,并将其初始化为0。GBLL伪操作声明一个全局的逻辑变量,并将其初始化成{FALSE}。GBLS伪操作声明一个全局的串变量,并将其初始化为空串""。 lcla、lcll  、lclsGBLA伪操作声明一个局部的算术变量,并将其初始化为0。GBLL伪操作声明一个局部的逻辑变量,并将其初始化成{FALSE}。GBLS伪操作声明一个局部的串变量,并将其初始化为空串""。seta 、setl 、sets设置变量具体值示例代码如下:gbla  test1test1 seta  0xaagbll test2test2 setl {true}gbls test3test3 sets  "testing" dcb 、dcw 、dcd 、dcfd 、dcfs 、dcq 、space 、map fileddcb  类似于定义一个char数组,用于分配一片连续的字节存储单元,space  类似于mallocfen分配一片连续的内存区域并初始化为0filed  类似于一个结构体其他暂时不必了解。

三、控制伪指令【IF】if 逻辑表达式指令序列else指令序列Endif 【while】while指令语句wend
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值