Uboot学习笔记(一)ARM汇编

碎碎念

从五月份开始忙了很多事,电赛、找实习、实习、离职,可能自己有点飘飘然了吧,刚刚大三觉得自己的能力已经足够去工作了,但是经过一个多月的工作还是觉得自己有不少的差距,不想继续做MCU,但是Linux也仅仅是刚入门的水平,还要学习不少的东西,所以离职回学校了,继续开始学习Linux开发。当实力撑不起你的野心的时候就应该学习。
博客也断更了好久,应该拾起来了,会坚持记录自己从现在直到明年秋招拿到offer这段时间的学习、复习、面试过程。

简介

在四月份时学习了Linux的驱动的框架以及plantform总线驱动、字符驱动、中断的知识点(当然到现在已经忘了好多了-.-),现在要开始钻研一下Uboot,Uboot是一个十分常用的跨平台BootLoader,可以在X86、ARM、PowerPC、Risc-V等平台进行开机的引导,BootLoader的工作就类似于我们电脑的BIOS,在开机的时候完成硬件的初始化、时钟系统的配置、内存管理单元mmu的初始化以及堆栈的设置,然后它会从各种存储介质(USB otg、emmc、硬盘、sd卡)中加载操作系统内核,所以说BootLoader就是一个系统从上电开始执行的第一个程序,对于嵌入式工程师来说,会编译烧录BootLoader、了解BootLoader的工作机制、了解修改的方法是最最基本的能力。
为什么会选取Uboot来学习呢,正时因为它开源、广泛使用、资料丰富,对学习者十分友好,BootLoader的工作基本是类似的,所以说学会了一种别的上手起来也是触类旁通的。
正因为BootLoader使命的特殊性(上电开始执行),而在刚刚上电的时候是没有C语言的运行环境的,没有MMU不能使用虚拟内存、堆栈未进行初始化,所以说要学习Uboot,首先学习ARM的汇编是必须的,只有能读懂ARM汇编才能去理解Uboot的工作机制。
汇编是比较低级的程序语言,也可以被叫做机器码的助记码,汇编代码会因为所使用编译器的不同而有不同的语法,我们的开发环境是Linux,使用的是GCC编译器,所以学习的是符合GNU语法的汇编

可以参考的文档

学习和ARM汇编可以参考如下两个文档:

  • Arm cortex-A的编程手册《ARM Cortex-A(armV7)编程手册V4.0》、《ARM Cortex-A(armV8)编程手册V1.0》
  • ARM指令集开发手册《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》

基本语法

每条语句包含三个可选的部分
label: instruction @ comment

  • label:即标号,表示地址位置,有些指令前面可能会有标号,这样可以通过这个标号得到指令的地址,标号也可以用来表示数据地址。注意:任何以冒号“:”结尾的标识符都会被认识是一个标号
  • instruction:即指令,也就是汇编指令或伪指令
  • comment:注释内容

示例:

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

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

用户可以使用.section伪操作来定义一个段,汇编系统预定义了一些段名

  • .text 代码段
  • .data 初始化的数据段
  • .bss 未初始化的数据段
  • .rodata 只读数据段

我们可以自己使用.section 来定义一个段,每个段以段名开始,以下一段名或者文件结束为结束

常见的伪操作:

  • .byte 定义单字节操作,比如.byte 0x12
  • .short 定义双字节数据,比如.byte 0x1234
  • .long 定义4字节数据,比如.long 0x12345678
  • .equ 赋值语句,格式为.equ 变量名,表达式,比如:.equ num,0x12
  • .align 数据字节对齐,比如:.align 4 表示4字节对齐
  • .end 表示源文件结束
  • .global 定义一个全局符号,格式为:.global symbol,比如:.global _start

GNU汇编同样支持函数,函数格式如下:

函数名:
	函数体
	返回语句

bx语句是返回指令,函数返回语句不是必须的

常用汇编指令

常用的汇编指令基本可以分为六类:数据处理指令、特殊寄存器操作指令、加载/存储操作指令、栈操作指令、跳转指令、协处理器操作指令

数据处理指令

数据处理指令包括:数据传输指令、算数逻辑运算指令、条件判断指令
(1)数据传送指令用于在寄存器和存储器之间进行数据的双向传输;
(2)算术逻辑运算指令完成常用的算术与逻辑的运算,该类指令不但将运算结果保存在目的寄存器中,同时更新CPSR中的相应条件标志位;
(3)比较指令不保存运算结果,只更新CPSR中相应的条件标志位。

数据传输指令

MOV
格式:MOV{条件}{S} 目的寄存器,源操作数
MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项决定指令的操作是否影响CPSR中条件标志位的值,当没有S 时指令不更新CPSR中条件标志位的值。
指令示例:

MOV R1,R0            @将寄存器R0的值传送到寄存器R1
MOV PC,R14           @将寄存器R14的值传送到 PC,常用于子程序返回
MOV R1,R0,LSL#3    @将寄存器R0的值左移3位后传送到R1

算数逻辑运算指令

在这里插入图片描述
在这里插入图片描述

比较指令

CMP
格式:CMP{条件} 操作数1,操作数2
CMP指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新CPSR中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。 标志位表示的是操作数1与操作数2的关系(大、小、相等),例如,当操作数1大于操作操作数2,则此后的有GT后缀的指令将可以执行。
指令示例:

CMP   R1,R0       @将寄存器R1的值与寄存器R0的值相减,并根据 结果设置CPSR的标志位
CMP R1,#100      @将寄存器R1的值与立即数100相减,并根 据结果设置CPSR的标志位

特殊寄存器操作指令

1、MRS
格式: MRS{条件} 通用寄存器 程序状态寄存器(CPSR或SPSR)
MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:

  • 当需要改变程序状态寄存器的内容时,可用MRS将程序状态寄存器的内容读入通用寄存器,修改后再写回程序状态寄存器。
  • 当在异常处理或进程切换时,需要保存程序状态寄存器的值,可先用该指令读出程序状态寄存器的值,然后保存。

指令示例:

MRS R0,CPSR                        @传送CPSR的内容到R0
MRS R0,SPSR                        @传送 SPSR的内容到R0

2、MSR
格式: MSR{条件} 程序状态寄存器(CPSR或SPSR)_<域>,操作数
MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中,操作数可以为通用寄存器或立即数。<域>用于设置程序状态寄存器中需要 操作的位,32位的程序状态寄存器可分为4个域:

  • 位[31:24]为条件位域,用f表示;
  • 位[23:16]为状态位域,用s表示;
  • 位[15:8] 为扩展位域,用x表示;
  • 位[7:0] 为控制位域,用c表示;
    该指令通常用于恢复或改变程序状态寄存器的内容,在使用时,一般要在MSR指令中指明将要操作的域。
    指令示例:
MSR CPSR,R0        @传送R0的内容到CPSR
MSR SPSR,R0        @传送R0的内容到SPSR
MSR CPSR_c,R0     @传送R0的内容到SPSR,但仅仅修改CPSR中的控制位域

加载/存储指令

CPU通过使用加载/存储指令来实现寄存器和存储器的数据存取
1、LDR
格式:LDR{条件} 目的寄存器,<存储器地址>
LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取32位的字数据到通用寄存器,然后对数据进行处理。当程序计数器PC作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。该指令在程序设计中比较常用,且寻址方式灵活多样。
指令示例:

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

2、STR
格式: STR{条件} 源寄存器,<存储器地址>
STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。 该指令在程序设计中比较常用,且寻址方式灵活多样,使用方式可参考指令LDR。
指令示例:

STR R0,[R1],#8           @将R0中的字数据写入以R1为地址的存储器中,并将新地址R1+8写入R1。
STR R0,[R1,#8]           @将R0中的字数据写入以R1+8为地址的存储器中。

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

栈操作指令

在A函数中调用B函数,调用完成后返回A函数
在跳转之前要将当前处理器状态保存起来(保存R0-R15这些寄存器的值)当B函数执行完成后在用前面保存的寄存器值恢复R0-R15即可
上面的过程叫做现场保护,恢复参数的过程叫做恢复现场
现场保护时进行压栈,恢复现场时进行出栈操作
压栈使用PUSH,出栈使用POP
PUSH和POP是一种多存储和多加载指令,即一次可以操作多个寄存器数据,利用当前的栈指针SP来生成地址
注意处理器的堆栈是向下增长的
1、PUSH
格式:PUSH{条件} 寄存器列表
压栈
指令示例:

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

2、POP
出栈
指令示例:

POP {LR} @先恢复LR
POP {R0~R3,R12} @再恢复R0~R3和R12

注意点:
出栈就是SP从当前位置开始,地址一次减小来提取堆栈中的数据到要恢复的寄存器列表中。
PUSH和POP的另一种写法是“STMFD SP!”和“LDMFDSP!”
STM和LDM就是多加载和多存储,可以连续读写存储器中的多个连续数据
FD是Full Descending的缩写,即满递减

跳转指令

有多种跳转操作:
1、直接使用跳转指令B、BL、BX等
2、直接向PC寄存器里面写入数据
在这里插入图片描述
1、B
指令格式:B

_start:
	ldr sp,=0x80200000 @设置栈指针
	b main @跳转到main函数

在汇编中初始化C运行环境,然后跳转到C文件的main函数中运行
因为跳转到C就不会再回到汇编了,所以使用B指令
2、BL
指令格式:BL

push {r0,r1} @保存r0,r1
cps #0x13 @进入SVC模式,允许其他中断再次进入

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

cps #0x12 @进入IRQ模式
pop {r0,r1}
str r0, [r1, #0x10] @中断执行完成,写EOIR

协处理器指令

Arm有16个协处理器 ,cp15协处理器可以设置,其他的不可以设置
CP15协处理器有16个32位的寄存器 c0-c15
mrc指令将协处理器寄存器中的数据传送到 ARM 处理器的寄存器中.若协处理器不能完成该操作,产生未定义的异常中断。
mrc p15, 0, r0, c0, c0, 0 //将cp15协处理器的c0寄存器数值以c0,0的格式读取到r0
mcr指令将ARM处理器的寄存器中的数据传送到协处理器的寄存器中.若协处理器不能成功地执行该操作,将产生未定义指令异常中断.。

异常产生指令

1、SWI
格式:SWI{条件} 24位的立即数
SWI指令用于产生软件中断,以便用户程序能调用操作系统的系统例程。操作系统在SWI的异常处理程序中提供相应的系统服务,指令中24位的立即数指定用户程序调用系统例程的类型,相关参数通过通用寄存器传递,当指令中24位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器R0的内容决定,同时,参数通过其他通用寄存器传递。
指令示例:

SWI   0x02                @该指令调用操作系统编号位02的系统例程。

2、BKPT
格式:BKPT 16位的立即数
BKPT指令产生软件断点中断,可用于程序的调试。

ARM汇编伪指令

这部分暂时就不写了,以后看的代码多了再返回来整理吧!

发布了123 篇原创文章 · 获赞 598 · 访问量 34万+

猜你喜欢

转载自blog.csdn.net/a568713197/article/details/102604774