首页 > 编程语言 >ARM汇编指令

ARM汇编指令

时间:2024-09-19 21:23:42浏览次数:10  
标签:汇编 0000 do ldr 指令 寄存器 r0 ARM

一、学习ARM汇编的目的

        学习arm汇编的主要目的是为了编写arm启动代码,启动代码启动以后,引导程序到c语言环境下运行。换句话说启动代码的目的是为了在处理器复位以后搭建c语言最基本的需求。因此启动代码的主要任务有:

  1. 初始化异常向量表;
  2. 初始化各工作模式的栈指针寄存器;
  3. 开启arm内核中断允许;
  4. 将工作模式设置为user模式;
  5. 完成上述工作后,引导程序进入c语言主函数执行;

因此汇编指令的学习主要是围绕这几个目的展开,主要学习跟上述目的相关的指令。

二、ARM汇编常用指令

1、mov指令

       加载12位立即数到寄存器或转移一个寄存器的值到另外一个寄存器

mov r0, #2 ;加载立即数2到寄存器r0,MOV{S}<c> <Rd>, #<const>

mov r1, r0 ;将r0寄存器的值加载到r1,MOV{S}<c> <Rd>, <Rm>

        大多数指令的格式为opcode rd, rn ,rm,其中,rd是目标寄存器,rn是第一操作数寄存器。

2、add指令

        常用的两种方式

ADD{S}<c> <Rd>, <Rn>, #<const>

ADD{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}

3、sub指令

SUB{S}<c> <Rd>, <Rn>, #<const>

SUB{S}<c> <Rd>, <Rn>, <Rm>{, <shift>}

以上四条指令都有立即数作为第二操作数的情况,那么是什么立即数呢?准确的说这里所指的是12位立即数imm12。

三、立即数

1、判断条件

  • 如果某个数的数值范围是0~255之间,那么这个数一定是立即数;
  • 把某个数展开成2进制,这个数的最高位1至最低位1之间的二进制数序列的位数不能超过8位;
  • 这个数的二进制序列的右边必须为偶数个连续的 0

2、举例

0x234 = 0000 0000 0000 0000 0000 0010 0011 0100

最高位1至最低位1之间的二进制数序列:1000 1101没有超过8位

末尾1的右边有2个0,所以0x234是立即数

0x3f4 = 0000 0000 0000 0000 0000 0011 1111 0100

最高位1至最低位1之间的二进制数序列:1111 1101 从第一个1开始到最后一个1之间没有超过8位

末尾1的右边有2个0,所以0x3f4是立即数

0x132 = 0000 0000 0000 0000 0000 0001 0011 0010

最高位1至最低位1之间的二进制数序列:1001 1001 从第一个1开始到最后一个1之间没有超过8位

末尾1的右边有1个0,不满足第二条,所以0x132不是立即数

0x7f8 = 0000 0000 0000 0000 0000 0111 1111 1000

最高位1至最低位1之间的二进制数序列:1111 1111从第一个1开始到最后一个1之间没有超过8位

末尾1的右边有3个0,不满足第二条,所以0x7f8不是立即数

0xfab4 = 0000 0000 0000 0000 1111 1010 1011 0100

最高位1至最低位1之间的二进制数序列:0011 1110 1010 1101 从第一个1开始到最后一个1之间超过8位,不满足条件1,所以这个数不是立即数

3、立即数判断原理

        因为ARM中将这 12bits 分为 8bit 常数(0~255)和 4bit 循环右移位值(0~15)

        8bit 常数范围(0~255),位移的步进值是以2为单位(即实际位移 2 * rotate 位),可以表示循环有以(0~30)偶数位: 0、2、4、6、8、10、12、14、16、18、20、22、24、26、28、30。在实际存储这个数值的时候,要想办法把这个数压缩到这12位中去。压缩的方法就是找一个数,这个数必须是一个8bit数,之后循环右移2 * rotate位。如果能找打这个数,那么待保存的数就是立即数,否则就不是。

四、非立即数存取指令

      如果我们就是希望把一个非立即数存进rn,做法如下:

1、ldr寄存器加载指令

LDR{<c>}{<q>} <Rt>, <label> ;如ldr r0, =0x2FAB4

ldr指令多用于从ram中将一个32位的字数据传送到目的寄存器中

LDR<c> <Rt>, [<Rn>{, #+/-<imm12>}] 如:

LDR   R0,[R1,#8]             ;将内存地址为R1+8的字数据读入寄存器R0,这里的#8作为12位立即数是可以省略的

LDR<c> <Rt>, [<Rn>], #+/-<imm12> 如:

ldr r0, [r1], #8 ;将内存地址R1的字数据读入r0,之后r1+8

LDR<c> <Rt>, [<Rn>, #+/-<imm12>]! 如:

LDR   R0,[R1,#8] !          ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。

2、bic指定位清零指令

BIC{S}<c> <Rd>, <Rn>, #<const>;将rn中的字数据const为1的比特清零,把结果放入rd

3、orr指定位置位指令

ORR{S}<c> <Rd>, <Rn>, #<const>

五、CPSR寄存器得N,V,C,Z位        

1、n,v,c,z位的更新        

        汇编指令的s后缀,几乎所有的汇编指令都可以在指令后面加上s后缀,s后缀的含义是在指令执行过程中会更新cpsr寄存器的N,V,C,Z位

N:在结果是有符号的二进制补码情况下,如果结果为负数,则N=1;如果结果为非负数,则N=0

Z:如果结果为0,则Z=1;如果结果为非零,否则Z=0

C:是针对无符号数最高有效位向更高位进位时C=1;减法中运算结果的最高有效位从更高位借位时C=0

V:该位是针对有符号数的操作,会在下面两种情形变为1,两个最高有效位均为0的数相加,得到的结果最高有效位为1;两个最高有效位均为1的数相加,得到的结果最高有效位为0;除了这两种情况以外V位为0

例如:

mov r0, #0xFFFFFFFF

adds r1, r0, #1

上面的操作会导致Z,C置位,这是因为结果为0,并且从无符号数角度来看,已经从最高位向更高位进位了

mov r0. #0x7FFFFFFF

adds r1, r0, #1

会造成N位和C位置位,这是因为计算结果0x80000000最为位为1,代表负数,并且 从有符号角度来看,把一个整数加成了负数。

2、更新N,V,C,Z位的作用

       更新N,V,C,Z位有什么用呢?几乎所有的arm指令都可以在指令之后可选地增加执行条件

例如:movcs r0, #100;表示只有在C位置位的情况下才能把100加载入r0,这样的话就可以非常方便地实现指令的有条件执行。

例如:实现两个unsigned long long类型变量的求和,

unsigned long long l1 = 0x00000000FFFFFFFF;

unsigned long long l2 = 0x000000000000003;

很明显结果为0x0000000100000002

我们用r0装l1的高4字节,r1装l1的低四字节:

用r2装l2的高4字节,r3装l2的低四字节

用r4装结果的高4字节,r5装结果的低四字节

第6~7行:装入被加数

第8~9行:装入加数

第10~11行:清空装结果的变量

第12行:先加低四字节,这里因为考虑到进位问题,所以要更新N,V,C,Z位

第13行:之前的进位导致更高位数据的丢失,必须把这一位补回来,要不要补就看之前时候进位,进位标志是cs

六、其他指令

1、CMP比较指令

        用于比较两个寄存器的值或者比较一个寄存器和立即数的值,其原理是对待比较的两个数求差,看结果是否为0,这个指令会无条件修改N,V,C,Z位。

如:

mov r0, #100

cmp r0, #100

会导致Z置位,从条件码表可知,只要Z置位就是两数相等。

2、跳转指令b

b指令类似c语言的goto语句,能够实现无条件跳转。跳转时需要一个lable,表示要跳转到什么地方去

配合之前的条件码,就可以实现一些较为复杂的操作,例如实现从0加到100的和

这就是循环实现的机制

        事实上,程序跳转工作更多的是为了实现类似函数的功能,此时lable就是函数的函数名,其实lable本身代表的就是待跳转那一行指令的地址,b指令本质上就是把待跳转那行的地址装入pc寄存器,但是函数在调用完毕之后要回到调用处的下一行指令处执行,为了能够回到调用的下一行,需要使用bl指令。bl和b之间的区别就在于bl会在lr寄存器中保存回来的地址。        

3、栈的实现类型

2440实现保护和恢复现场使用的栈是数组栈,即用一段连续的内存空间为栈提供空间。从数组栈的具体实现来看入栈的方式有四种做法:

  • 空增:先写入数据,再让栈指针自增;
  • 空减:先写入数据,再让栈指针自减;
  • 满增:先让栈指针自增,再写入数据;
  • 满减:先让栈指针自减,再写入数据。

        arm体系采用的方案是满减,但是在进行操作之前,我们必须告诉2440栈底的位置,这里我们把栈底设置为0x40001000,从地址0x40000000开始的0x1000这段内存空间对应的是2440内部的一段ram,总共4k。实际能够使用的内存空间为[0x40000000~0x40000FFF],设置栈底指针寄存器: ldr sp =0x40001000

4、入栈保护指令stmfd(STMDB)

STMFD<c> <Rn>{!}, <registers>

其中Rn表示栈底指针寄存器,< registers >表示需要入栈保护的寄存器,!表示入栈之后sp自动自减。如:

stmfd sp!, {r0, r1, r2, r3-r12, lr}

5、出栈恢复指令ldmfd(LDM/LDMIA/)

LDMFD<c> <Rn>{!}, <registers>

中Rn表示栈底指针寄存器,< registers >表示需要入栈保护的寄存器,!表示出栈之后sp自动自增。如:

ldmfd sp!, {r0, r1, r2, r3-r12, lr}

七、在汇编中调用c语言编写的函数

设有c语言定义的函数void func_c(void);在汇编代码中调用该函数,只需用import声明函数名即可,之后就可以使用bl指令调用该函数,注意,既然是调函数,就一定要保护现场

1、向c函数传参

向c函数传参的方法很简单,如果参数个数小于等于4个,就直接用r0~r3传参,c函数返回值通过r0寄存器返回:

设有c函数:

int add_c(int a, int b, int c, int d)

{

return a + b + c + d;

}

如果参数个数大于4个,从第五个参数开始就需要通过栈来传参

        在c语言中调用汇编编写的函数类似,不过在汇编中用export声明函数,同时需要在c语言中用extern声明函数,按照标准,调用者负责保护现场和恢复现场

传参方法于此类似

2、切换arm内核的工作模式

        切换工作方式的思路很简单,由于内核的工作模式是由cpsr寄存器的低5位来设置的,那么就可以先把cpsr读出来,更改低5位之后再设置进去。这里读取cpsr使用mrs指令,写cpsr寄存器用msr指令,需要注意的是在keil环境下写cpsr需要写成:   msr cpsr_c r0;将r0的值写入到cpsr寄存器

学习了这些指令,最终就可以编写我们自己的启动代码了

启动代码示例如下:

    preserve8
    area reset, code, readonly
    code32
    entry

    b start
    ldr pc, =do_undifined
    ldr pc, =do_swi
    ldr pc, =do_p_abort
    ldr pc, =do_d_abort
    nop
    ldr pc, do_irq
    ldr pc, do_fiq
    

do_fiq
    b do_fiq    

do_irq
    import irq_handler
    sub lr, lr, #4
    stmfd sp!, {r0-r12, lr}
    bl  irq_handler
    ldmfd sp!, {r0-r12, pc}^

do_d_abort
    b do_d_abort

do_p_abort
    b do_p_abort

do_swi
    import swi_handler
    stmfd sp!, {r0-r12, lr}
    bl  swi_handler
    ldmfd sp!, {r0-r12, pc}^

do_undifined
    b  do_undifined

start
    ldr sp, =0x40001000 ;svc_sp

    mrs r0, cpsr
    bic r0, r0, #0x1F
    orr    r0, r0, #0x12
    bic r0, #(1 << 7)
    msr cpsr_c, r0

    ldr r0, =0x40001000
    sub r0, r0, #1024
    mov sp, r0

    mrs r0, cpsr
    bic r0, r0, #0x1F
    orr r0, r0, #0x10
    msr cpsr_c, r0

    ldr r0, =0x40001000
    sub r0, r0, #2048
    mov sp, r0
    
    import main
    b main

finished
    b finished

    end

标签:汇编,0000,do,ldr,指令,寄存器,r0,ARM
From: https://blog.csdn.net/qq_63574400/article/details/142369671

相关文章

  • 关于HarmonyOS的学习
    day34一、设计模式   +设计模式是一些对解决问题的总结和经验,设计模式是解决某个问题的最佳的方案,无数人优秀人的程序员的实验和错误总结   +共23种1.工厂模式     =>对于批量创建对象的一种解决方案     =>函数可以传递参数......
  • HarmonyOS开发之RichEditor组件实现评论编辑功能
    随着社交媒体和即时通讯应用的普及,用户对于内容创作的需求日益增长,特别是对于评论、回复等互动形式。为了满足这一需求,HarmonyOSNEXT提供了强大的RichEditor组件,支持图文混排和文本交互式编辑,使得开发者可以轻松构建功能丰富的编辑界面。本文将通过几个具体场景,详细介绍如何利用Ri......
  • vue3自定义指令,全局注册
    1这是directive.js注册的的importtype{Directive,DirectiveBinding}from"vue";importuseUserStorefrom"@/stores/user";import{createPinia}from"pinia";constuserStore=useUserStore(createPinia());//buttonList:["......
  • 鸿蒙(HarmonyOS)--函数、类的声明和使用
    目录1.函数1.1函数的声明1.2可选参数 1.2.1 参数名?:类型  1.2.2参数名:类型=值 1.3Rest参数 1.4返回类型1.4.1显示返回1.4.2隐示返回1.4.3无返回类型1.5函数的作用域1.5.1全局作用域1.5.2局部作用域1.6函数调用1.7函数类型 1.8 箭头函数/l......
  • 鸿蒙(HarmonyOS)--编程语言-ArkTS 语言基础
    目录 ArkTS基础知识1声明1.1变量声明1.2常量声明1.3自动类型推断 2类型2.1基本类型 2.1.1 string2.1.2  number2.1.3boolean2.2引用类型2.2.1Object类型 2.2.2 Array类型2.2.3Void类型 2.3枚举类型 Enum2.4联合类型 Union 2.5 类型别......
  • 『杂项』Linux 常用指令
      不会吧不会吧不会还有Oier不会Linux指令要写一篇博客记一下  今年S组人数骤增,遂ctrl+cv出本篇博客以获得两分(Linux常用指令文件和目录管理命令ls:列出当前目录中的文件和子目录。pwd:显示当前工作目录的路径。cd:切换工作目录。mkdir:创建新目录。rmdi......
  • ARM基础知识点及简单汇编语法
    计算机最小系统是一个能启动并运行基本功能的系统,其组成包括:处理器(CPU):执行指令的核心组件。内存:RAM:存储运行中的程序和数据。ROM:存储引导程序或固件。存储:用于存储操作系统和应用程序的设备,如闪存。输入输出接口:基本的通信接口,如串行接口(UART)和GPIO(通用输入输出)。电源:提供......
  • C----函数递归之反汇编
    环境win10vc6.0debug代码关于求阶层问题:n!=n(n-1)!;(n-1)!=(n-1)(n-2)!例如5!=5(4)!4!=43!3!=32!2!=21函数递归的出口是1,所以函数递归最重要的条件是去寻找递归的出口intfun(inti){intsum=0;if(i==1){return1;}else{sum=......
  • # HarmonyOSNEXT应用开发性能优化篇(四)
    本篇是性能优化的最后一篇,合理使用系统接口,前边三篇分别介绍了预加载、布局和状态变量方面上的优化合理使用系统接口,避免冗余操作在使用系统的高频回调接口时,删除不必要的Trace和日志打印,避免冗余操作,以减少系统的开销,优化性能,下边分几个小点进行介绍。避免在系统高频回......
  • 【Linux】Linux的基本指令(1)
    Aclownisalwaysaclown.......