V1

2022/12/23阅读:17主题:橙心

arm底层奠定基础 (汇编)

arm底层汇编

目录

前言

在学习和工作中,遇到关于嵌入式程序运行的困惑,通过底层汇编,可以知道arm怎么做到保护现场、linux怎么做到安全运行和系统调用等。

arm发展简介

arm发展历程

指令集的设计是处理器结构中最重要的一个部分,用ARM的术语称之为ISA(Instruction Set Architecture)。根据不同的指令集进行区分,ARM11芯片之后,也就是从ARMv7架构开始,ARM的命名方式有所改变。新的处理器家族,改以Cortex命名,并分为三个系列,分别是Cortex-A,Cortex-R,Cortex-M。(A、R、M),比较熟悉,stm32F4是cortex-M4,imx6ull是cortex-A7。2011 年 10 月,arm公司推出armv8架构,从之前32位到64位,支持64位指令集,在内存、虚拟化和安全有了一定的提升。2021年,arm已经推出armv9。

一、32位 ARMv7

以cortex-M为例

ARM主要有7种基本工作模式,USER、FIQ、IRQ、Supervisor、Abort、Undef和System。如果cortex-A会多出2种,安全监控模式(mon):可在安全模式和非安全模式下转换;HYP虚拟化模式。

CPU的模式可以简单的理解为当前CPU的工作状态
arm寄存器示意图

1、bank寄存器

带有三角,表示bank 寄存器,该模式下独有的寄存器,没有带三角,表示各个模式共用这部分寄存器。

什么是寄存器?

存放数据的地方,cpu内部访问,读取速度最快。

2、特殊寄存器

r15 PC程序计数器(Program Counter),存储下一条要执行的指令的地址

r14 LR 连接寄存器(Link Register ),保存函数返回地址,当通过BL或BLX指令调用函数时,硬件自动将函数返回地址保存在R14寄存器中。当函数完成时,将LR值传到PC,即可返回到被调用位置。

r13 SP 堆栈指针(Process Stack Pointer),保护现场和恢复现场要用,当发生异常的时候,硬件会把当前状态(使用到寄存器数值)保存在堆栈中,SP保存这个堆栈指针,异常处理完成,通过SP出栈,恢复到异常前的状态。

**CPSR程序状态寄存器(current program status register)**,CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义。

CPSR位域寄存器

二、汇编指令

思考下C语言编译流程,预处理(preprocessing),对头文件和宏展开→编译(compilation),将展开后的程序转成汇编代码→汇编,将汇编代码转成零一二进制机器码,目标文件;链接,将多个目标文件链接成可执行的文件。

现在我们介绍汇编程序,汇编程序相比C语言,有执行效率高的特点,但是可读性差。汇编程序编译开始流程差不多是C语言汇编步骤的开始,此编译非彼编译。汇编语言,包括两种指令,一种是汇编指令,一种是伪指令。汇编指令有对应的机器码,伪指令并没有对应的机器码,最终不会被CPU执行,而是会被编译器所执行。

1、基础指令

;将立即数3 放在r13   注意:mov指令只能用于通用的寄存器
mov   r13,#3   
;cpsr是特殊寄存器
mrs   r0,cpsr
msr   cpsr,r0

;相与
and   r0,r0,#0xFFFFFFE0
;相或
orr   r0,r0,#0x10

;逻辑左移
mov   r0,r1,LSL#2      
mov   r0,r1,LSR#2


;比较指令
cmp   r0,#0
;相等 r1 = 0
moveq r1,#0
;大于 r1 = r1 + 3
addgt r1,r1,#3

;跳转到标号为main地代码处 (只能短跳转32M) 
b    main   
;跳转函数func,并保存下一条要执行的指令的位置到 lr寄存器, 当跳转代码结束后,用mov pc,lr指令跳回来   
bl   func      
;相等(指CPSR寄存器中的Z条件码置位时)时,跳转到地址addr处               
beq  addr     
;不等时,跳转到地址addr                   
bne  addr     

bic    r0,r0,#0x0B    ;清除r0中的位 01、和 3  
tst    r0,#0x20       ;测试第6位是否为0 ,为0则Z标志置1 
cmp    r1,r0          ;将寄存器R1的值与寄存器R0的值相减,
                      ;并根据结果设置CPSR的标志位


2、armv7汇编demo

  .text
  b   main  
  nop
  nop
  nop
  nop
  nop
  nop
  nop
main: 
  ldr  r0,=buf
  ldr r1,[r0]
  mov r2,#5
  str r2,[r0]
  str r2,[r0,#8]
main_end:
  b  main_end

对应的机器码:

keil debug

数据不合法

;立即数不合法  因为机器码中只有低12位是存储数据位 借助伪指令,编译器进行编译
mov r3,#0x1101
;使用伪指令
ldr r3,=0x1101
;但是这条指令却是合法的
mov r3,#0x1100000

;立即数合法性判断:循环移动到低8位能放下,并且移动次数是偶数次。
;使用伪指令可以避免,立即数合法判断。

机器码:

反汇编示意图

立即数合法性判断:循环移动到低8位能放下,并且移动次数是偶数次,0x1100000是0x11相左移动6次。

3、存储器之间不能直接拷贝,必须通过寄存器中转

ldr r0,[r7] ;4字节载入
ldrb r0,[r7] ;1字节载入
ldrh r0,[r7] ;2字节载入
ldr r0,[r7,#8] ;r7加上8后,载入对应地址的内容
ldr pc,_irq ;将标号中的内容载入,机器码
ldr pc,=irq  ;将标号地址载入

str r0,[r3]
str r0,[r3,#4]
str r0,[r3],#4


伪指令

.section               //定义内存段
.text                  //将定义符开始的代码编译到代码段
.data                  //数据段
.if  .else  .endif     //条件编译
.end                   //文件结束
                   
.byte   0x11, ’a’,0    //定义char型数组(.byte 对应1字节)
.word  0x120x445566  //定义int型数组(. word 对应4字节)
.quad  0x3FA0          //分配8字节的空间(.quad 对应8字节)
.string  “abcd\0”      //定义字符串
.align   4             //2^4 =16 字节对齐

 ldr r0, =0xE0028008   //载入大常数0xE0028008 到r0中
.equ  GPG3CON, 0XE03001C0     //定义宏
.global  _start         //声明_start 为全局符号

堆栈操作

 stmfd sp!,{r0-r12,lr}       
//将寄存器r0~r12 lr中的值存入栈中   常用于中断保护现场,! 表示会自动偏移                                       
 ldmfd sp!,{r0-r12,pc}^   
//将栈中值逐个弹出到寄存器r0~r12 pc中  常用于中恢复断现场,^表示会恢复spsr到cpsr

三、中断向量表

指示中断服务程序的入口位置,中断向量的顺序是固化,不可改变。(是顺序,不是地址,起始地址可以变,偏移地址)

arm异常处理地址

当发生中断或者异常时,arm会自动跳到对应中断入口,由硬件完成。

以stm32启动文件为例,startup_stm32f10x.md.S

...
; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY ;只读数据段
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size
;DCD 连续 一段连续空间 外部中断发生时,把要处理的动作放在EXTI0_IRQHandler中断服务函数           
__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC
                DCD     EXTI0_IRQHandler           ; EXTI Line 0
                DCD     EXTI1_IRQHandler           ; EXTI Line 1
                ......
  .text
  ;---------vector table
  b   main    ;0x00 reset
  nop         ;0x04 undef         
  ldr pc,_sirq_hander  ;0x08 soft irq
  nop         ;0x0c prefecth abort
  nop         ;0x10 data abort
  nop         ;0x14 reserved
  nop         ;0x18 irq
  nop         ;0x1C fiq
_sirq_hander:
  .word  sirq_hander
  
  ;----handler vector table
sirq_hander:
  stmfd sp!,{r0-r12,lr} ; 入栈 保护现场
  ldr r0,[lr,#-4]       ; 获取到软件中断 号 是1还是2
  bic r0,#0xff000000
  cmp r0,#1   
  addeq  r2,#1
  cmp r0,#2
  subeq  r2,#1

sirq_hander_end:
  ldmfd sp!,{r0-r12,pc}  ;出栈  恢复现场
    ;----------app----
main: 
    ldr sp,=stack_buf  ;保存栈地址
    mov r1,#1
    mov r2,#2
    swi 0x1             ;软中断 让linux陷入内核 中断号1
    cmp r2,#2
    moveq r4,#4
    movne r4,#6
    swi 0x2            ;中断号2
    mov r0,#3  
main_end:
    b  main_end
  .data

    .space 15*4     ;数据段 
stack_buf:          ;因为栈是向下增长,递减,这里要注意。
  .end

四、解惑

1、linux 上层最终怎么进入内核层?

通过系统调用,即swi指令软中断,进入异常,改变arm权限,获取访问硬件的权限。

2、中断怎么做到现场保护和恢复现场?

在进入中断,一般是user模式切换到异常模式(应用层到内核层),因为中断需要用到cpu的寄存器(有共用寄存器),为了不破坏原有的工作环境,会对前一个模式进行现场保护,会对各个寄存器值和代码段地址保存到栈中,记录栈指针到SP。当中断完成后,通过SP堆栈指针,出栈,恢复现场,恢复成之前的环境。

3、当man函数中,调用外部函数时,外部函数执行完,怎么返回到main函数继续执行后面代码?

LR寄存器保存函数返回地址。mov PC,LR。

4、快速中断为什么会比一般中断响应快?

1.FIQ的处理优先级比IRQ更高,甚至可以打断正在执行的IRQ;
2.FIQ模式有自己独有的寄存器,而IRQ需要和其他模式共用寄存器(共用寄存器多),在中断处理的保护/恢复现场会更快;
3.在异常向量表中,FIQ处在最末尾。在异常向量表中IRQ只能保存中断处理程序的首地址,在发生IRQ时需要一次跳转;而FIQ处在最末尾,所以可以直接将FIQ模式下的中断处理程序紧接着存放,这样在处理FIQ时就少一次跳转。

5、对于c语言 i = 1,在用汇编怎么实现的?

把1放在寄存器0中,将i地址载入到寄存器1中,最后将寄存器0数据存入到i地址。C语言一条语句可能对应多个汇编指令。这里可以联想为什么会有系统中会有锁和原子的机制,在上层一条语句其实对应底层多条指令,特别是对多线程而言,竞争访问同一资源的情况。

mov r0,#1
ldr r1,=i
str r0,[r1]

arm官网

armv8 64位

armv8相对armv7增加哪些?以下是arm社区回答

对比图

在内存、虚拟化和安全有了一定的提升,执行状态可在AArch64和AArch32来回切换,兼容之前32位arm指令集。

AArch64中,已经没有User、SVC、ABT等处理器模式的概念,但ARMv8需要向前兼容,在AArch32中,就把这些处理器模式映射到了4个Exception level。

异常类型:SError 系统错误,FIQ 快速中断,IRQ一般中断和Synchronous同步异常。

异常级别有,EL0、EL1和EL2和EL3,

armv8异常示例图

每个异常等级都有自己的异常向量表。异常向量表中的每一项都会保存有异常处理的跳转函数,然后跳转过去处理异常。每个向量表基虚拟地址是由矢量基址寄存器设置的,例如VBAR_EL3,VBAR_EL2和VBAR_EL1。

每个表有16个条目,每个条目的大小为128字节(32条指令)。ARMv8的向量表如下图所示,可以看到每一种异常都有固定的偏移地址。

SVC指令可以用来从EL0的用户应用程序调用到EL1的内核。HVC和SMC系统调用指令以类似的方式将处理器移动到EL2和EL3。当处理器在EL0(应用程序)执行时,它不能直接调用管理程序(EL2)或安全监视器(EL3)。

2、armv8基础汇编指令

b.ne    label    //不等时跳转
cbz  w10, 1f   //w10值等于0的适合,跳转导1f  
ret              //子程序返回指令,返回地址默认保存在LR(X30),代替了mov pc,lr

ldr   x0,=__main  //大范围的地址读取:把标号__main(地址)读入x0
adr     x0,vector    //小范围的地址读取:把标号vector(地址)读入x0,标号距当前指令PC的偏移小于1M
stp  x29, x30, [sp, #-16]!   
    //入栈:把x29, x30 存储到sp-16指向的空间后,sp自减16 (因寄存器是8字节,栈是向下生长故是 -16)                              
    //类似前索引: *(sp-16) = x29,x30   sp=sp-16 (!使得sp能自更新)  把 x29,x30看成整体              
    //stp只支持2个寄存器,代替了复杂的stmfd  (64位汇编,取消了批量操作指令)     
ldp   x29, x30, [sp],#16   //出栈: 把sp指向的空间内容载入到x29, x30后,sp加16
                             //类似后索引: x29,x30=*sp  sp=sp+16   

mrs  x0, sctlr_el1   //读sctlr_el1内容到x0  (注:系统寄存器,都通过mrs msr来操作)
msr    sctlr_el1, x0   //写x0内容到 sctlr_el1
svc     #2      //系统调用指令(触发一个同步异常,cpu则会陷入EL1)

.global  _start     //声明_start 为全局符号(让链接脚本能看到)
.quad  0x3FA0       //在存储器中分配8个字节,初值设为0x3FA0
.align  4           //2^4 =16 字节对齐
.macro  myAdd, x,y  //宏函数, 类似 myAdd(x, y) 
    add \x,\x,\y
.endm    
myAdd  x0,x2


b.ne   lable   //不等时跳转到标号
cbz  w10, 1f   //w10值等于0的适合,跳转导1f  
ret              //子程序返回指令,返回地址默认保存在LR(X30),代替了mov pc,lr  


.macro  myAdd, x,y      //宏函数, 类似 myAdd(x, y) 
        add   \x,\x,\y
.endm    
myAdd  x0,x2  

3、armv8中断向量表示例

.globl  _start
_start:
    mrs   x1,SPSel
    mrs   x2,CurrentEL
    mov   x0,#0
    msr   SPSel,x0
    mov   x0, #0x5
    adr   x0,vectors
    msr   vbar_el1,x0
    svc   #0x02  //系统调用 ,
reset_end:
    b  reset_end

do_bad_sync:
   mov   x2,#1
   b    reset_end

do_bad_irq:
   mov   x2,#2
   b    reset_end

  .align  11  //2^11=2048  整个异常向量表 2K对齐  -> 通过对齐,实现向量表空间的预留
              //16个异常 ,每个异常32条指令    16*32*4=2048 
               //16个异常,这里使用前8个
vectors:
    //===============sp0===============
    //---同步异常
    .align  7  //2^7     1000 0000 =0x80   字节对齐
    mov  x0,#1
    b    do_bad_sync
    //---irq异常
    .align  7  //2^7     1000 0000 =0x80
    mov  x0,#1
    b    do_bad_irq
    //---fiq异常
    .align  7  
    mov  x0,#1
    b    reset_end
    //---SError异常
    .align  7  
    mov  x0,#1
    b    reset_end
    //===============sp_elx===============
    //---同步异常
    .align  7  //2^7     1000 0000 =0x80   字节对齐
    mov  x0,#1
    b    do_bad_sync
    //---irq异常
    .align  7  //2^7     1000 0000 =0x80
    mov  x0,#1
    b    do_bad_irq
    //---fiq异常
    .align  7  
    mov  x0,#1
    b    reset_end
    //---SError异常
    .align  7  
    mov  x0,#1
    b    reset_end

如果有帮助,可以关注 小昭debug,有学习资料等你拿。

分类:

后端

标签:

后端

作者介绍

V1