通用裸机-arm汇编和cpu运行模式

发布时间:2024-07-03  

1 GNU 汇编格式

label:instruction @ comment

label 即标号,表示地址位置,有些指令前面可能会有标号,这样就可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意 label 后面的“:”,任何以“:”结尾的标识符都会被识别为一个标号。 instruction 即指令,也就是汇编指令或伪指令。 @符号,表示后面的是注释,就跟 C 语言里面的“/”和“/”一样,其实在 GNU 汇编文件中我们也可以使用“/*”和“*/”来注释。 comment 就是注释内容。


add:

MOVS R0, #0X12 @设置 R0=0X12

注意: ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用


1.1伪操作

1.1.1 .section

来定义一个段,汇编系统预定义了一些段名:


.text 表示代码段。

.data 初始化的数据段。

.bss 未初始化的数据段。

.rodata 只读数据段。

定义一个 testsetcion 段


.section .testsection

汇编程序的默认入口标号是_start,不过我们也可以在链接脚本中使用 ENTRY 来指明其它的入口点。


1.1.1 .global

.global _start

_start:

ldr r0, =0x12 @r0=0x12

.global 是伪操作,表示_start 是一个全局标号,类似 C 语言里面的全局变量一样,常见的伪操作有:


.byte 定义单字节数据,比如.byte 0x12。

.short 定义双字节数据,比如.short 0x1234。

.long 定义一个 4 字节数据,比如.long 0x12345678。

.equ 赋值语句,格式为:.equ 变量名,表达式,比如.equ num, 0x12,表示 num=0x12。

.align 数据字节对齐,比如:.align 4 表示 4 字节对齐。

.end 表示源文件结束。

.global 定义一个全局符号,格式为:.global symbol,比如:.global _start。

1.2 函数定义

函数名:

    函数体

    返回语句

GNU 汇编函数返回语句不是必须的,如下代码就是用汇编写的Cortex-A7 中断服务函数:


/* 未定义中断 */

Undefined_Handler:

    ldr r0, =Undefined_Handler

    bx r0

/* SVC 中断 */

SVC_Handler:

    ldr r0, =SVC_Handler

    bx r0

/* 预取终止中断 */

PrefAbort_Handler:

    ldr r0, =PrefAbort_Handler 

    bx r0

以函数 Undefined_Handler 为例我们来看一下汇编函数组成,“Undefined_Handler”就是函数名,“ldr r0, =Undefined_Handler”是函数体,“bx r0”是函数返回语句,“bx”指令是返回指令,函数返回语句不是必须的.


2 ARMv7汇编指令

2.1 数据移动指令

数据移动指令都是cpu内部寄存器之间的数据拷贝。


2.1.1 MOV

MOV R0,R1 @将寄存器 R1 中的数据传递给 R0,即 R0=R1

MOV R0, #0X12 @将立即数 0X12 传递给 R0 寄存器,即 R0=0X12

2.1.2 MRS

读取特殊寄存器的数据只能使用 MRS 指令:


MRS R0, CPSR @将 CPSR 里面的数据传递给 R0,即 R0=CPSR

2.1.3 MSR

和 MRS 刚好相反,通用寄存器写入到特殊寄存器


MSR CPSR, R0 @将 R0 中的数据复制到 CPSR 中,即 CPSR=R0

举个例子利用MRS MSR进行清bss (_bss_start, __bss_end定义在链接脚本):


.global _start

.global _bss_start

.global _bss_end


_bss_start:

    .word __bss_start

_bss_end:

    .word __bss_end


_start:

    //disable watchdog, disable icache dcache

    //init_clk

    //enter svc mode

    /*clear bss*/

    ldr r0, _bss_start

    ldr r1, _bss_end

    move r2, 0

clr_bss:

    stmia r0!, {r2} //复制一r2中的数据给r0, 并将指针r0增加4

    cmp r0, r1

    ble clr_bss /*if r02.1.4 CPS

特权模式下(除了用户模式,剩余的模式都是特权模式),可以通过CPS指令直接修改CPSR寄存器的M[4:0],让处理器进入不同的模式。

CPS #0x12 /*irq mode*/

CPS #0x13 /*svc mode*/


2.2 数据存取指令(访问存储器RAM)

2.2.1 LDR

数据加载指令,从指定地址读取到cpu寄存器。


LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004

LDR R1, [R0] @读取地址 0X0209C004 中的数据到 R1 寄存器中

2.2.2 STR

数据存放指令,从cpu寄存器写入指定地址。


LDR R0, =0X0209C004 @将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004

LDR R1, =0X20000002 @R1 保存要写入到寄存器的值,即 R1=0X20000002

STR R1, [R0] @将 R1 中的值写入到 R0 中所保存的地址中

LDR 和 STR 都是按照4 byte进行读取和写入的,也就是操作的 32 位数据,如果要按照字节、半字进行操作的话可以在指令“LDR”后面加上 B 或 H,比如按字节操作的指令就是 LDRB 和STRB,按半字(16位)操作的指令就是 LDRH 和 STRH。


2.2.3 多寄存器加载存储指令LDMIA,STMIA等

1.LDMIA指令、LDMIB指令、LDMDB指令、LDMDA指令 LDM是LDR指令的增强型 , 将连续的数据加载到多组寄存器。 DB (Decrement Before)栈指针先减小再操作、DA(Decrement After)栈指针先操作再减小。 IB(Increment Before)栈指针先增加再操作、IA(Increment After)栈指针先操作再增加。


LDMIA指令,IA表示每次传送后地址加4

LDMIB指令,IB表示每次传送前地址加4

LDMDA指令,DA表示每次传送后地址减4

LDMDB指令,DB表示每次传送前地址减4


LDMIA R14,{R0-R3,R12} /*从R14寄存器指向的地址取出5个32位数据分别存进到R0-R4以及R12*/


//等效于

//R0=*R14

//R1=*(R14+4)

//R2=*(R14+8)

//R3=*(R14+12)

//R12=*(R14+16)


LDMIA R1!,{R4-R11} /*从R1指向的地址取8个32位数据存入R4-R11, 每取一次,让R1指针加4,因此最后R1指针加了32*/

2.STMIA指令、STMIB指令、STMDB指令、STMDA指令 同理,STM是STR指令的增强型 , 将多组寄存器数据保存进连续地址空间。


STMIA R13!,{R0-R1} /*将R0,R1寄存器中的数据存入R13指向的栈空间, r13指向的地址存入R0数据,再地址+4后存入R1的数据*/

2.3 入栈出栈指令

函调调用过程中离不开现场的保护和恢复。保存 R0~R15 寄存器的操作就叫做现场保护,恢复 R0~R15 寄存器的操作就叫做恢复现场。


2.3.1 PUSH

比如要将 R0~R3 和 R12 这 5 个寄存器压栈,当前的 SP (stack pointer)指针指向 0X80000000,我们知道栈空间的地址是向下增长的,堆空间地址向上增长。


PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈

那么压栈完成以后的堆栈如下:入栈保护现场完这5个寄存器后,SP指向0X7FFFFFEC(每压栈一个寄存器,SP地址减4)

再次保存LR寄存器,进行压栈:

PUSH {LR} @将 LR 进行压栈

2.3.2 POP

POP {LR} @先恢复 LR

POP {R0~R3,R12} @在恢复 R0~R3,R12

可以看出入栈出栈本质都是对SP指针进行加减,入栈减,出栈加,入栈把寄存器依次保存进SP指向的地址去,出栈从SP地址依次取出数据。


2.3.3 STMFD和LDMFD

入栈出栈的另外一种写法是“STMFD SP!”和“LDMFD SP!”。


STMFD SP!,{R0~R3, R12} @R0~R3,R12 入栈

STMFD SP!,{LR} @LR 入栈

bl xxx

LDMFD SP!, {LR} @先恢复 LR

LDMFD SP!, {R0~R3, R12} @再恢复 R0~R3, R12

STMFD 可以分为两部分:STM 和 FD,同理,LDMFD 也可以分为 LDM 和 FD。前面我们讲了 LDR 和 STR,这两个是数据加载和存储指令,但是每次只能读写存储器中的一个数据。STM 和 LDM 就是多存储和多加载,可以连续的读写存储器中的多个连续数据。 FD 是 Full Descending 的缩写,即满递减的意思。根据 ATPCS 规则,ARM 使用的 FD 类型的堆栈,SP 指向最后一个入栈的数值,堆栈是由高地址向下增长的,也就是前面说的向下增长的堆栈,因此最常用的指令就是 STMFD 和 LDMFD。STM 和 LDM 的指令寄存器列表中编号小的对应低地址,编号高的对应高地址.


2.4 跳转指令

2.4.1 B 指令

B 指令会将 PC 寄存器的值设置为跳转目标地址,如果要调用的函数不会再返回到原来的执行处,那就可以用 B 指令.


_start:

ldr sp,=0X80200000 @设置栈指针

b main @跳转到 main 函数

在汇编中初始化 C 运行环境,然后跳转到 C 文件的 main 函数中运行,上述代码只是初始化了 SP 指针,有些处理器还需要做其他的初始化,比如初始化 DDR 等等.


2.4.2 BL 指令

有返回的跳转,跳转之前会在寄存器 LR(R14)中保存当前 PC 寄存器值,所以可以通过将 LR 寄存器中的值重新加载到 PC 中来继续从跳转之前的代码处运行,这是子程序调用一个基本但常用的手段。 比如 Cortex-A 处理器的 irq 中断服务函数都是汇编写的,主要用汇编来实现现场的保护和恢复、获取中断号等。但是具体的中断处理过程都是 C 函数,所以就会存在汇编中调用 C 函数的问题。而且当 C 语言版本的中断处理函数执行完成以后是需要返回到irq 汇编中断服务函数,因为还要处理其他的工作,一般是恢复现场。


push {r0, r1} @保存 r0,r1

cps #0x13 @进入 SVC 模式,允许其他中断再次进去


bl system_irqhandler @加载 C 语言中断处理函数到 r2 寄存器中


cps #0x12 @进入 IRQ 模式

pop {r0, r1} 

str r0, [r1, #0X10] @中断执行完成,写 EOIR

跳转指令总结: 有多种跳转操作,比如: ①、直接使用跳转指令 B、BL、BX 等。 ②、直接向 PC 寄存器里面写入数据。

2.5 算数运算指令

加减乘除,常用的运算指令用法:

2.6 逻辑运算指令

与或非指令用法:

来看一个例子利用arm汇编进行初始化C语言环境。让arm进入svc模式,才能访问特殊寄存器如cpsr, spsr, sp指针。


.global _start

_start:

    /* 进入SVC模式 */

    mrs r0, cpsr

    bic r0, r0, #0x1f  /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 */

    orr r0, r0, #0x13  /* r0或上0x13,表示使用SVC模式 */

    msr cpsr, r0 /* 将r0 的数据写入到cpsr_c中 */


    ldr sp, =0X80200000 /* 设置栈指针 */

    b main /* 跳转到main函数 */

2.7 内存屏障指令

2.7.1 Data Memory Barrier(DMB):数据内存屏障

DMB指令确保在DMB之前的所有显式数据内存传输指令都已经在内存中读取或写入完成,同时确保任何后续的数据内存传输指令都将在DMB执行之后开始执行,否则有些数据传输指令可能会提前执行。保证了两个内存访问能按正确的顺序执行。 应用场景: (1)DMA 在使用DMA控制器时,需要在CPU内存访问和DMA操作之间插入DMB屏障,以确保CPU当前的内存读写操作在DMA开始之前完成。


(2)多核系统中的信号量 在多核系统中,使用信号量进行核间同步。需要使用DMB来强制指定内存执行顺序,以避免潜在的竞态条件或数据不一致性。当一个核要访问共享资源之前,它会先检查信号量的状态。如果信号量已经被另一个核获取,当前核就必须等待,直到信号量状态变为可用。这个等待过程需要保证在一个核释放信号量之后,其他核能够立即看到信号量状态的变化,而不是因为处理器优化或缓存导致的无效读取而产生错误。


2.7.2 Data Synchronization Barrier(DSB):数据同步屏障

在多线程编程中,两个线程同时对共享的内存进行读写操作,由于读/写操作的重排序,就会导致数据的不一致, DSB指令时,它确保在DSB之前的所有显式数据内存传输指令都已经在内存中读取或写入完成,同时确保任何后续的指令都将在DSB执行之后开始执行。 应用场景: 例如启用或禁用特定的中断、配置时钟、设置系统控制位等。为了确保对SCS的修改在下一条指令执行之前生效,需要使用DSB指令进行数据同步。一些特殊的指令如SVC(Supervisor Call,特权级调用)、WFI(Wait For Interrupt,等待中断)、WFE(Wait For Event,等待事件)等操作,涉及到特权级的转换或者等待系统事件发生,需要使用DSB指令。


2.7.3 Instruction Synchronization Barrier(ISB):指令同步屏障

插入ISB指令,处理器会将流水线中的指令全部刷新,从而确保之前的指令不会影响后续指令的执行,并且后续指令将从正确的上下文开始重新获取。 应用场景: 在进行异常进入之前,处理器会执行ISB操作。这样做的目的是刷新指令流水线,确保异常处理程序的指令是从正确的地址开始执行,避免异常之前的指令对异常处理程序造成干扰。 在进行异常返回之前,处理器同样会执行ISB操作。这样做的目的是刷新指令流水线,确保返回时从正确的地址重新获取指令,避免异常处理程序的指令对正常任务造成干扰。


3 arm-v7 cpu运行模式

以前的 ARM 处理器有 7 中运行模型:User、FIQ、IRQ、Supervisor(SVC)、Abort、Undef和 System,其中 User 是非特权模式,其余 6 中都是特权模式。


到了Cortex-A7 处理器有 9 种处理模式: | 模式 | 描述 | | :------------: | :------------: | | User(USR) | 用户模式,非特权模式,大部分程序运行的时候就处于此模式。 | | FIQ | 快速中断模式,进入 FIQ 中断异常 | | IRQ | 一般中断模式。 | | Supervisor(SVC) | 超级管理员模式,特权模式,供操作系统使用。 | | Monitor(MON) | 监视模式?这个模式用于安全扩展模式。 | | Abort(ABT) | 数据访问终止模式,用于虚拟存储以及存储保护。 | | Hyp(HYP) | Hyp(HYP) 超级监视模式?用于虚拟化扩展。 | | Undef(UND) | Undef(UND) 未定义指令终止模式。 | | System(SYS) | System(SYS) 系统模式,用于运行特权级的操作系统任务 |


九种模式所对应的寄存器:

arm920t cpu模式


4 arm-v7 cpu通用和特殊寄存器

ARM 架构提供了 16 个 32 位的通用寄存器(R0~R15)供软件使用,前 15 个(R0~R14)可以用作通用的数据存储,R15 是程序计数器 PC,用来保存将要执行的指令。ARM 还提供了一个当前程序状态寄存器 CPSR 和一个备份程序状态寄存器 SPSR,SPSR 寄存器就是 CPSR 寄存器的备份。

4.1 通用寄存器

R0~R15 就是通用寄存器,通用寄存器可以分为以下三类: ①、未备份寄存器,即 R0~R7。 ②、备份寄存器,即 R8~R14。 ③、程序计数器 PC,即 R15。


4.1.2 未备份寄存器R0-R7

未备份寄存器指的是 R0~R7 这 8 个寄存器,因为在所有的处理器模式下这 8 个寄存器都是同一个物理寄存器,在不同的模式下,这 8 个寄存器中的数据就会被破坏.


4.1.3 备份寄存器

4.1.3.1 R8~R12

R8~R12 这 5 个寄存器有2种物理寄存器.在快速中断模式下(FIQ)它们对应着 Rx_irq(x=8~12)物理寄存器,其他模式下对应着 Rx(8~12)物理寄存器. FIQ 模式下的 R8~R12 是独立的,因此中断处理程序可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程。


4.1.3.2 R13 (SP)

R13 一共有 8 个物理寄存器,其中一个是用户模式(User)和系统模式(Sys)共用的,剩下的 7 个分别对应 7 种不同的模式。R13 也叫做 SP,用来做为栈指针。基本上每种模式 都有一个自己的 R13 物理寄存器,应用程序会初始化 R13,使其指向该模式专用的栈地址,这就是常说的初始化 SP 指针.


4.1.3.2 R14 (LR)

R14 一共有 7 个物理寄存器,其中一个是用户模式(User)、系统模式(Sys)和超级监视模式(Hyp)所共有的,剩下的 6 个分别对应 6 种不同的模式. LR被叫做链接寄存器: ①用来存放子函数的返回地址。 在子函数中,将 R14(LR)中的值赋给 R15(PC)即可完成子函数返回,比如在子程序中可以使用如下代码:


MOV PC, LR

②当异常发生以后,该异常模式对应的 R14寄存器被设置成该异常模式将要返回的地址.


subs pc, lr, #4             /* 将lr-4赋给pc */

比如下面代码示例:


0X2000 MOV R1, R0 ;执行

0X2004 MOV R2, R3 ;译指

0X2008 MOV R4, R5 ;取值 PC

当前正在执行 0X2000地址处的指令“MOV R1, R0”,但是 PC 里面已经保存了 0X2008 地址处的指令“MOV R4, R5”。假设此时发生了中断,中断发生的时候保存在 lr 中的是 pc 的值,也就是地址 0X2008。


4.1.3.2 R15 (PC)

R15 保存着当前执行的指令地址值加 8 个字节,这是因为 ARM的流水线机制导致的。ARM 处理器 3 级流水线:取指->译码->执行,这三级流水线循环执行,比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中. 对于arm32位处理器:


R15 (PC)值 = 当前执行的程序位置 + 8 个字节

4.2 特殊寄存器

4.2.1 CPSR

当前程序状态寄存器(current program status register),所有模式共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问。


N(bit31):当两个补码表示的 有符号整数运算的时候,N=1 表示运算对的结果为负数,N=0表示结果为正数。 Z(bit30):Z=1 表示运算结果为零,Z=0 表示运算结果不为零,对于 CMP 指令,Z=1 表示进行比较的两个数大小相等。 C(bit29):在加法指令中,当结果产生了进位,则 C=1,表示无符号数运算发生上溢,其它情况下 C=0。在减法指令中,当运算中发生借位,则 C=0,表示无符号数运算发生下溢,其它情况下 C=1。对于包含移位操作的非加/减法运算指令,C 中包含最后一次溢出的位的数值,对于其它非加/减运算指令,C 位的值通常不受影响。 V(bit28):对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时,V=1 表示符号位溢出,通常其他位不影响 V 位。 Q(bit27):仅 ARM v5TE_J 架构支持,表示饱和状态,Q=1 表示累积饱和,Q=0 表示累积不饱和。 IT1:0:和 IT7:2一起组成 IT[7:0],作为 IF-THEN 指令执行状态。 J(bit24):仅 ARM_v5TE-J 架构支持,J=1 表示处于 Jazelle 状态,此位通常和 T(bit5)位一起表示当前所使用的指令集:

image.png

GE3:0:SIMD 指令有效,大于或等于。 IT7:2:参考 IT[1:0]。 E(bit9):大小端控制位,E=1 表示大端模式,E=0 表示小端模式。 A(bit8):禁止异步中断位,A=1 表示禁止异步中断。 I(bit7):I=1 禁止 IRQ,I=0 使能 IRQ。 F(bit6):F=1 禁止 FIQ,F=0 使能 FIQ。 T(bit5):控制指令执行状态,表明本指令是 ARM 指令还是 Thumb 指令,通常和 J(bit24)一起表明指令类型,参考 J(bit24)位。 M[4:0]:处理器模式控制位 cpsr最常用就是来控制处理器模式 | M[4:0] | 处理器模式 | | ------------ | ------------ | | 10000 | User 模式 | | 10001 | FIQ 模式 | | 10010 | IRQ 模式 | | 10011 | Supervisor(SVC)模式 | | 10110 | Monitor(MON)模式 | | 10111 | Abort(ABT)模式 | | 11010 | Hyp(HYP)模式 | | 11011 | Undef(UND)模式 | | 11111 | System(SYS)模式 |


/* 进入SVC模式 */

    mrs r0, cpsr

    bic r0, r0, #0x1f   /* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4  */

    orr r0, r0, #0x13   /* r0或上0x13,表示使用SVC模式                   */

    msr cpsr, r0        /* 将r0 的数据写入到cpsr_c中                    */

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>