ARM汇编指令集

ARM汇编指令集的介绍,包括指令和伪指令。


指令和伪指令概念

指令

指令指的是CPU机器指令的助记符,是由CPU的指令集提供的,经过编译之后,会以二进制机器码的形式由CPU读取执行

伪指令

伪指令本质上不是指令,和CPU的机器指令没有任何关系,只是和指令一起写在代码中而已,是由编译器环境提供的,其目的是用于指导编译过程,伪指令经过编译后不会生成二进制机器码,仅仅在编译阶段有效果

指令编程风格

ARM官方风格

官方风格指令一般使用大写,例如:LDR R0,[R1],Windows中常使用这种风格

GUN Linux风格

指令一般使用小写字母,例如:ldr r0,[r1],Linux环境中常用这种风格

ARM汇编特点

  • LDR/STR架构
    1. 采用RISC架构,CPU本身不能直接读取内存,而需要把内存中的数据加载到CPU的通用寄存器中,才能被CPU处理
    2. ldr(load register)将内存中的数据加载到通用寄存器
    3. str(store register)将寄存器内容存入内存空间
    4. ldr和str组合,可以实现ARM CPU和内存的数据交换
  • 8种寻址方式
    1. 寄存器寻址:move r1,r2:把r2的值赋值到r1寄存器中
    2. 立即寻址:move r0,#0xFF00:把立即数0xFF00赋值给r0寄存器
    3. 寄存器移位寻址:move r0,r1,lsl #3:把r1左移三位(*8)之后的值赋值给r0寄存器
    4. 寄存器间接寻址:ldr r1,[r2]:寄存器有中括号,表示内存地址对应的数据,所以这里r2表示一个内存地址,[]表示取r2指针对应的数据,这句代码的意思是把r2对应的内存中的数据赋值给r1
    5. 基址变址寻址:ldr r1,[r2,#4]:将指针r2的值(内存地址)+4之后指向的数据赋值给r1
    6. 多寄存器寻址:ldmia r1!,{r2 - r7,r12}:这种情况下,r1是一个指针,里边存放的内存地址,然后以r1里边的内存地址为基地址,向后以此加1得到{}里的寄存器数量个内存地址,然后将刚才得到的这些内存地址指向的变量的值赋值给{}里的对应位置的寄存器,类似从内存中读取数组,然后把数组的元素依次赋值给这些寄存器
    7. 堆栈寻址:stmfd sp!,{r2 - r7,lr}:和多寄存器类似,区别是将栈SP中连续访问{}数量个字节,然后依次赋值给{}里的寄存器
    8. 相对寻址:beq flag::flag:标号用于标记标号后面那句指令的地址,常用来表示入口点,函数名就是一个标号,C语言中的goto就可以跳转到一个标号,在ARM汇编中用指令b flag:就可以跳转到flag:对应的标号处执行,和beq flag:是一样的,其原理是相对于PC程序位置寄存器做一个偏移
  • 指令后缀
    1. ARM中的指令可以带后缀,从而丰富该指令的功能,这种形式叫做指令族,常用的后缀有:
    2. B(byte):功能不变,操作长度变为8位(依赖CPU位数,以下相同)
    3. H(Halfword):功能不变,操作长度变为16位
    4. S(signed):功能不变,操作数变为有符号数
    5. S(S标识):影响CPSR里的NZCV标识位,
    6. 举例:
      1. ldr指令族:ldrb,ldrh,ldrsb ldrsh,从内存中加载指定长度的数据
      2. mov指令族:movs r0,#0,结果是0,赋值会影响CPSR的NZCV标识,将Z位置为1
  • 条件执行后缀
    1. 条件执行后缀用于限制该执行执行的,只有在符合条件之后才能够执行该指令
    2. ARM汇编指令集_第1张图片
    3. 举例:moveq r0,r1,如果eq成立,执行mov r0,r1,不成立则该条不执行,和C语言中的条件判断类似
    4. 条件后缀成立与否,不是取决于本条指令,而是取决于之前指令运行后的结果
    5. 条件后缀决定了本条指令是否执行,不会影响之前和之后指令
    6. 条件后缀和CPSR的NZCV位相关,例如,如果上一句代码执行的结果将Z置为1,下一句带有eq条件后缀的语句就会被执行
  • 多级指令流水线
    1. 多级流水线用于增加处理器处理指令的速度,
    2. 允许CPU同时异步的执行多条指令,而非上一条指令全部执行完毕之后才会执行下一条指令
    3. 多级可以简单那理解为把一条指令分为多个步骤来异步执行,例如:
      1. CPU把一条指令分为[取址,解码,执行]3个步骤,则为3级指令流水线
      2. 第一条指令进行取值操作
      3. 第一条指令取值完毕,进入解码操作,第二条指令紧随其后就开始执行取值操作
      4. 第一条指令解码完毕,进入执行操作,第二条指令紧接着进入解码操作,同时第三条指令进入取值操作
      5. 第一条指令执行完毕,第二条指令进入执行操作,第三条指令进入解码操作,第四条指令进入取值操作,依次类推
    4. 可见,多级流水线可以提高同时执行指令的数量,从而加速指令执行
    5. 需要注意的是,PC指向的是正在取值的指令,而非正在执行的指令,之间的差值就是流水线级数和单字节长度的乘积,在中断返回到PC的时候需要注意这个问题

ARM指令

数据处理指令

数据传输指令

  • mov:move,在两个寄存器之间或者立即数和寄存器之间传递数据,将后一个寄存器上的值或者立即数赋值给前一个寄存器
    • 例如:mov r1,r0
    • mov r1,#0xFF:将立即数0xFF赋值给寄存器r1
  • mvn:和mov用法一致,区别是mvn会把后一个寄存器的值或者立即数按位取反后赋值给前一个寄存器
    • 例如:mvn r0,#0xFF,则r0的值为0xffffff00(32位数据)

算术运算指令

  • add:加法运算
  • sub:减法运算
  • rsb:反减运算
  • adc: 带进位的加法运算
  • sbc: 带进位的减法运算
  • rsc:带进位的反减指令

逻辑指令

  • and:与操作
  • orr:或操作
  • eor:异或操作
  • bic:位清除操作

比较指令

  • cmp:比较大小
  • cmn:取反比较
  • tst:按位与运算
  • teq:按位异或运算

乘法指令

  • mvl: mla: umull: umlal: smull: smlal:

前导0计数

  • clz:统计一个数的二进制位前面有几个0

CPSR访问指令

mrs

用于读取CPSR和SPSR

msr

用于写CPSR和SPSR

CPSR和SPSR

  • CPSR是程序状态寄存器,整个Soc只有一个
  • SPSR在五种异常模式下各有一个,用于从普通模式进入异常模式的时候,保存普通模式下的CPSR,在返回普通模式时可以恢复原来的CPSR

跳转分支指令

  • b指令: 无条件直接跳转,没打算返回
  • bl指令:跳转前把返回地址放入lr中,以便返回,常用在函数中
  • bx指令:跳转同时切换到ARM模式,用于异常处理的跳转

内存访问指令

  • ldr:加载指定内存地址的数据到寄存器,按照字节访问
  • str:加载指定寄存器数据到内存地址中,按照字节访问
  • ldm:和ldr功能一样,一次多字节多寄存器访问
  • stm:和str功能一样,一次多字节多寄存器访问
  • swp:内存和寄存器互换指令,一边读一边写,例如:
    • swp r1,r2,[r0]:读取指针r0的数据到r1中,同时把r2的数据赋值给r0指针指向的变量

软中断指令

swi(software interrupt),在软件层模拟产生一个中断,这个中断会传送给CPU,常用于实现系统调用

立即数

非法与合法

ARM指令都是32为,除了指令标记和操作标记外,只能附带少位数的立即数,所以有非法与合法之分

  • 非法立即数:
  • 合法立即数:经过任意位数的移位后,非0部分可以用8位表示就是合法立即数

协处理器与指令

协处理器

协处理器属于Soc中另外一颗核心,用于协助主CPU实现某些功能,被主CPU调用来执行任务,协处理器和MMU,Cache,TLB有功能和管理上的联系
ARM设计可以支持多达16个协处理器,但是一般只实现其中的CP15

协处理器指令

  • mrc:读取CP15中的寄存器
  • mcr:向CP15中的寄存器写数据
  • 指令用法:mcr{<”cond”>} p15,<”opcode_1”>,<”Rd”>,<”Crn”>,<”Crm”>,{<”opcode_2”>}
    • opcode_1:对于CP15永远为0
    • Rd:ARM通用寄存器
    • Crn:CP15寄存器,取值范围c0~c15
    • Crm:CP15寄存器,一般为c0
    • opcode_2:省略或者为0

ldm,stm和栈

ldm,stm

ldr与str只能访问4个字节,当数据较大的时候,就会明显的降低效率,这时就需要使用到ldm和stm,ldm与stm是大量的从寄存器与内存交换数据的方式,常用于在内存和寄存器之间大量读取和写入数据:

  • stmia sp {r0 - r12}:stm表示进行批量数据操作,ia的意思是将r0存入SP的内存地址处,然后SP内存地址+4(32位),将r1存入该地址,内存地址再+4,存入r2,依次存到r12,
  • 这就是一个寄存器和内存交换大量数据的示例,在一个周期内完成了多个内存地址和多个寄存器的操作。
  • 除了ia后缀,还有多个不同功能的后缀:
    • ia:increase after,后增加,表示每个操作的时候,先传输数据,后增加内存地址,
    • ib:increase before,先增加,表示在每个操作的时候,先增加内存地址,再进行数据传输
    • da:decrease after:和ia一样,差别在于减少地址
    • db:decrease before:和ib一样,差别在于减少地址
    • fd:full decrease:满递减堆栈,查看栈的描述
    • ed:empty decrease:空递减堆栈
    • fa:满递增堆栈
    • ea:空递增堆栈
  • 操作栈时使用相同的后缀就不会出错

堆栈(栈)

  • 空栈:栈指针指向空位,每次可以直接存入,然后栈指针(SP)递增或者递减1格,取的时候要递增或者递减1格才能取出
  • 满栈:SP指向栈最后1格数据,存入的时候需要先移动1格才能存入,取的时候可以直接取出
  • 增栈:SP向地址增加的方向移动
  • 减栈:SP向地址减少的方向移动
  • 组合起来就可以成为空增/减栈,满增/减栈。四种形式

汇编中的符号

!号的作用

在汇编中常见”!”符号,究竟是用来干嘛的呢?
例如:

  • ldmia r0,{r2 - r3}:数据传输完毕之后指针r0的内存地址值还是第一次操作之前的值
  • ldmia r0!,{r2 - r3}:数据传输完毕之后指针r0的内存地址值是最后一次操作后的值

^号的作用

那么^号又是什么作用呢?
例如:

  • ldmfd sp!,{r0 - r6,pc}
  • ldmfd sp!,{r0 - r6,pc}^

作用:当目标寄存器中有pc时,会同时将SPSR写入到CPSR,一般用于从异常返回。

伪指令

伪指令的作用用于指导编译过程,伪指令并不是指令,编译后并不会生成二进制机器码,伪指令是和具体的编译环境有关的,我们使用的GNU编译工具链,所以需要使用GNU下的汇编伪指令。

GNU汇编符号

  • [@,#,//,/~/]:注释,和C语言的//是一样的
  • ::冒号,在汇编中以冒号结尾的是标号,标号标记标号后面的指令的地址,
  • .:点号,代表当前指令的地址,例如:[b .]指令会进入死循环
  • #:#或者$号后面跟着数值,代表一个立即数(不区分进制)

伪指令

  • .global _start:给_start外部可链接属性,可以在外部文件中访问_start
  • .section .text:指定当前段为代码段
  • .ascii .byte .short .long .word .quad .float .string:定义各种类型的数据
  • .align 4:以16字节对齐
  • .balignl 16 0x3C:b表示填充,align表示对齐,l表示long,以4字节为单位填充,16表示以16字节对齐,0x3C是用来填充的原料
  • .equ:宏定义
  • .end:表示一个文件的结束
  • .include:用于包含头文件
  • .arm / .code32:声明以下的代码是arm指令
  • .thumb / .code16:声明以下的代码是thumb指令

比较重要的伪指令

  • nop:空操作,什么也不执行
  • ldr:大范围地址加载指令,把内存地址加载到寄存器中,和ldr指令是有区别的
    • ldr指令附带立即数要考虑合法性问题,只能带合法立即数,带的立即数前缀#号
    • ldr伪指令,不用考虑立即数合法问题,带的立即数前面是一个=号,借助了编译器环境文字池的帮助,帮忙加载非法立即数
  • ldr可以加载的地址比较广,加载的地址和链接时给的地址有关,由连接脚本决定,在链接时确定,编译时会被mov或者以文字池方式处理
  • adr:小范围地址加载指令,可加载的范围比较小
  • adr:总是以P C为基准来表示地址,则该地址为相对地址,要在执行的时候才能确定,因此该指令和运行地址有关,可以用于检测程序当前运行地址在哪里,编译时会被sub或者add替代
  • adrl:中等范围地址加载指令

你可能感兴趣的:(ARM裸机)