目录
1 实验环境概述
1.1 实验环境种类
1.2 树莓派4b简介
2 实验代码分析
2.1 实验代码结构
2.2 Makefile文件分析
2.3 linker.ld文件分析
2.4 程序流程分析
2.4.1 启动代码
2.4.2 kernel_main函数
3 基于qemu的实验环境搭建
3.1 使用qemu + gdb调试
3.1.1 启动qemu调试
3.1.2 启动gdb
3.1.3 连接gdb server
3.1.4 开始调试
3.2 使用qemu + eclipse调试
3.2.1 创建新工程
3.2.2 创建调试配置
3.2.3 开始调试
4 基于树莓派的实验环境搭建
4.1 调试工具概述
4.1.1 JLink
4.1.2 OpenOCD
4.2 烧写树莓派基础镜像
4.3 更新树莓派SPI BootRom
4.3.1 配置WiFi
4.3.2 更新系统软件包
4.4 树莓派4b启动流程
4.5 更新测试镜像
4.6 更新调试镜像
4.7 连接JLink
4.7.1 JLink升级
4.7.2 连接JLink和树莓派
4.7.3 JLink接入虚拟机
4.8 开始调试
4.8.1 启动OpenOCD
4.8.2 连接OpenOCD
4.8.3 暂停CPU
4.8.4 加载调试镜像到树莓派
4.8.5 设置CPU执行位置
4.8.5 连接gdb调试
5 实验验证
5.1 currentEL验证
5.2 SP指针验证
《奔跑吧Linux第3季》课程共提供2种实验环境
1. 使用qemu模拟树莓派4b的实验环境
2. 实际基于树莓派4b的实验环境
我们的软硬件实验环境均基于树莓派4b,此处进行简要介绍
1. 使用BCM2711作为主芯片
2. 内置4核Cortex-A72
3. 内置GICv2中断控制器
4. 支持丰富的外设接口
说明:树莓派4b address map
介绍树莓派4b的address map主要是为了帮助理解下文种设置外设寄存器的操作
① BCM2711芯片手册中列出的外设地址是Legacy Master模式下的,Main peripherals的基地址为0x7c000000
以下文代码要设置GPFSEL寄存器为例,在BCM2711芯片手册中的地址为0x7e200000 + 0x04
② 树莓派4b启动后,根据config.txt中的设置,如果arm_peri_high为1,则使用High Peripheral模式,也就是图中的Full 35-bit Address Map模式
③ 如果不设置arm_peri_high,树莓派默认以Low Peripheral模式启动,我们的实验中就是这种情况,此时Main peripherals的基地址为0x0_fc000000
因此GPFSEL寄存器的地址就被映射到0xFC000000 + (0x7e200000 - 0x7c000000) + 0x04,即0xfc000000 + 0x02200000 + 0x04 = 0xfe200004
这与代码中的设置是一致的
说明:启动qemu调试参数
可见与启动qemu运行相比,进行调试时增加了-S和-s参数
① -S:freeze CPU at startup,此时CPU会停在我们要调试程序的第1条指令处
② -s:shorthand for -gdb tcp::1234,此时qemu会启动内部的gdbServer,并等待gdb调试器通过网络连接,默认TCP端口号为1234
1. 链接起始地址为0x80000
之所以选择该地址,是因为在树莓派4b配置中,会将编译生成的镜像加载到0x80000处运行
2. 首先链接.text.boot段的内容,这也是镜像中最先执行的部分,我们对程序流程的分析也从这里开始
启动代码位于boot.S文件中
说明1:mpidr_el1寄存
对于CPU0(Primary CPU),该寄存器的bit[7:0]为0
说明2:cbz指令
CBZ指令当
说明3:清空BSS段
首先获取bss_begin & bss_end的地址,之后调用memzero函数对该区域清零。调用时,x0中存储的是BSS段起始地址,x1中存储的是BSS段大小
说明4:栈指针设置
① SECTION_SHIFT = PAGE_SHIFT + TABLE_SHIFT = 12 + 9 = 21
② SECTION_SIZE = 1 << SECTION_SHIFT = 1 << 21 = 2MB
③ LOW_MEMORY = 2 * SECTION_SIZE = 2 * 1MB = 4MB
可见此时栈设置在内存地址4MB处
kernel_main函数的主要流程如下,
1. 初始化串口
2. 向串口发送"Welcon BenOS!"字符串
3. 在循环中等待用户输入并回显
说明1:实验代码中的串口使用轮询方式工作
说明2:benos.elf文件符号表
① _start标号位于镜像文件起始处
② BBS段此时为空
在一个端口启动qemu调试,命令如下
# 在实验源码Makefile所在目录
make debug
说明1:-machine raspi4参数
① -machine参数用于设置要模拟的设备
② 使用如下命令可以查看当前qemu支持的设备列表
qemu-system-aarch64 -machine help
说明2:-nographic参数
disable图像输出,将串口输入输出作为控制台(console)
说明3:-kernel benos.bin参数
将benos.bin作为内核镜像
在另一个端口启动gdb,命令如下
gdb-multiarch --tui build/benos.elf
说明1:--tui参数
① 使用终端界面(terminal user interface)
② 如果不加--tui参数,将不会有显示对应源码的窗口
说明2:gdb启动调试时,需要使用带有符号表的可执行文件,此处即为benos.elf
在gdb命令行中输入如下命令,用于连接qemu调试时启动的gdb server
target remote localhost:1234
1. 设置断点
使用如下命令将断点设置在_start标号处,也就是内核镜像的首条指令处
b _start
c # 运行到断点处
2. 打开寄存器观察窗口
使用如下命令打开寄存器观察窗口
layout reg
3. 调试程序
之后便可以单步调试程序,观察程序运行状态
我们以获取bss_begin & bss_end为例,可见x0 & x1寄存器中正确获取了BSS段的起止地址
说明1:adr指令
adr指令用于获取标号相对于PC的地址(地址无关指令),此处获取的就是bss_begin & bss_end标号的地址,也就是BSS段的范围
需要注意的是,此时链接地址和运行地址是匹配的,所以使用地址无关操作获取的标号地址,与实际的运行地址是一致的
说明2:如果只是使用qemu运行内核镜像,则使用如下名
# 在实验源码Makefile所在目录
make run
可见实验结果与之前的分析是一致的
说明3:此处调试的原理是使用了qemu内置的gdbserver
说明4:qemu不能100%模拟硬件行为
在使用qemu + eclipse调试时,仍然是在一个端口启动qemu调试,之后启动eclipse进行如下配置
工程创建完成后,可见工程中包含了之前调试的源码
配置完成Apply之后,就会生成新建的调试配置
点击Debug之后便进入调试界面,可见模拟的树莓派4b中有4个CPU核
之后在Debugger Console中加载符号表
此处要注意ELF文件的路径
之后在Debugger Console中将断点设置在_start标号处,并运行到断点处,此时就会出现调试的源码界面,此时便可以进行单步调试,并观察处理器状态
此时可以查看Registers窗口,观察寄存器状态
1. 硬件仿真器使用仿真头完全取代目标板上的CPU,从而完全仿真目标板上的芯片行为,提供更加深入的调试功能
2. JTAG(Joint Test Action Group)是一种国际标准测试协议,主要用于芯片内部测试。JTAG仿真器通过边界扫描接口与CPU进行通信,实现对CPU和外设的调试功能
3. JLink是SEGGER公司开发的基于JTAG协议的仿真器,其V11版本支持树莓派4b主芯片的Cortex-A72调试
说明:JTAG 20pin接口
1. OpenOCD(Open On-Chip Debbugger,开源片上调试器)是一款开源软件,提供针对嵌入式设备的调试、系统编程和边界扫描功能
2. OpenOCD的功能在仿真器的辅助下完成,仿真器是必须的,因为调试主机(运行OpenOCD的PC)通常不具备调试电信号的解析能力
首先使用一张SD卡烧写树莓派基础镜像
烧写工具:Win32DiskImager
镜像文件:2020-08-20-raspios-buster-arm64.img
注意:打开Win32DiskImager工具时,需要使用管理员权限
烧写完成后,SD卡中将自动生成FAT32格式的boot分区,其中的文件如下图所示
说明1:使能串口输出
修改boot分区中的config.txt配置文件,增加如下2行,用于使能串口输出
此时启动树莓派,可通过串口登录,其中树莓派镜像默认的用户名和密码如下
用户名:pi
密码:raspberry
说明2:使能串口配置项说明
① uart_2ndstage
设置该参数,可以使得树莓派从second-stage loader就开始打印调试信息
② enable_uart
设置该参数,可以配合cmdline.txt中的内核启动参数,使得内核创建控制台串口
通过WiFi更新树莓派4b全系统的软件包,这样会自动更新SPI BootRom固件
使用如下命令配置WiFi
sudo raspi-config
我这里指令该命令时,会显示乱码,而不是配置界面
这是因为串口工具的编码方式需要修改,我们将编码方式选为UTF-8,重新执行上述命令,即可显示配置界面
WiFi配置流程如下,
此时可见wlan0 interface已经分配到IP地址,且可以ping通百度,这说明网络配置已经成功
说明:树莓派4b只能连接2.4GHz的WiFi网络
使用如下命令更新整个系统的软件包
sudo apt update
sudo apt full-upgrade
之后重启树莓派即可
1. 当树莓派4b启动时,Cortex-A72处于standby状态,GPU将负责启动系统。GPU会加载片上ROM code并执行,ROM code的主要功能为初始化SD host controller,为后续读取SD卡上的文件做准备
2. SD host controller初始化完成后,ROM code会加载SD卡上的bootcode.bin文件并执行
3. bootcode.bin文件负责加载SD卡上的start.elf文件并执行
4. start.elf文件解析SD卡上的config.txt配置文件,加载SD卡上的kernel.img文件到内存指定地址,然后CPU结束standby状态开始执行内核
说明1:bootcode.bin和star.elf文件为树莓派官方提供,不开源
说明2:如何指定kernel文件并加载到内存指定地址
要完成这个目标,就需要用到config.txt配置文件中的2个参数
① kernel
kernel参数用于指定加载的内核镜像文件,如果不指定,对于树莓派4b,默认使用kernel7l.img文件
② kernel_old & kernel_address
这2个参数用于指定kernel文件的加载地址,如果这2个参数均不指定,则默认将64位kernel文件加载到0x200000地址处(但是根据树莓派4b上级验证情况,实际是加载到0x80000地址处)
在理解了树莓派4b的启动流程后,我们将课程提供的测试用的镜像拷贝到SD卡的boot分区
启动树莓派后,可见已经运行测试镜像
说明:测试镜像config.txt
此时启动使用的内核镜像为benos4.bin
下面我们更新调试镜像,该镜像是为了,也是将相关文件拷贝到SD卡的boot分区
同样分析一下调试镜像的config.txt配置文件
1. 启动的内核文件为loop.bin,我们查看该文件的二进制内容,对照ARMv8 AArch64指令集编码,该文件仅包含一条无条件跳转的B语句,用于实现无限循环,类似如下指令的效果
label:
b label
这么做的目的,是让CPU处于循环状态,等待JLink获取CPU运行的控制权
2. enable_jtag_gpio=1
该选项用于设置JTAG相关的GPIO功能,同时在SoC内部建立相应连接
3. gpio=22-27=a4
将GPIO22 ~ GPIO27的功能设置为Alt4,该配置项与上面的enable_jtag_gpio=1功能一样
4. init_uart_clock=48000000
该选项设置串口UART0的时钟为48MHz,也就是默认值
5. init_uart_baud=115200
该选项设置串口的波特率为115200,也是默认值
我们实验使用的JLink为V11版本,但是需要将固件更新到最新版本,才能支持Cortex-A72。可以使用J-Link Commander程序进行升级
如果当前固件版本不是最新,打开J-Link Commander程序后会自动提示升级,此时选择升级即可。当前使用的JLink固件版本已更新至2021年6月29日
要在树莓派上使用JLink仿真器,需要将JLink仿真器的JTAG接口连接到树莓派的扩展板上。树莓派的扩展接口中已经内置了JTAG接口,可以使用杜邦线连接
树莓派与JLink仿真器的连接如下,
1. 首先将JLink的连接接入虚拟机
2. 使用lsusb命令查看JLink是否已接入虚拟机
3. 使用如下命令,验证能否访问JLink
sudo openocd -f jlink.cfg # 在jlink.cfg文件所在目录执行
可见已经识别到JLink硬件版本为V11,且探测到目标CPU电压为3.244V,后续的错误是因为还没有指定要调试的设备信息
说明:jlink.cfg文件
可见该配置文件只是指定使用的调试接口类型为jlink(这里之所以要配置,是因为JLink硬件也可以使用SWD接口进行调试)
使用如下命令启动OpenOCD
sudo openocd -f jlink.cfg -f raspi4.cfg # 在配置文件所在目录执行
可见JLink已经识别到树莓派4b上的bcm2711芯片,且在3333端口启动了gdb server
说明:raspi4.cfg配置文件
在另一个shell中使用telnet连接上一步中启动的OpenOCD,且根据raspi4.cfg配置文件,telnet的端口号为4444
telnet localhost 4444
可见telnet连接成功
使用halt命令先让CPU暂停运行
可见bcm2711中的2个CPU核已因为JLink的debug请求暂停运行,且暂停前处于EL2异常等级,并使用SP_EL2栈指针
使用如下命令加载要调试的进行到树莓派,,此处加载的是可运行的二进制文件
load_image /home/rlk/rlk/armv8_trainning/lab01/benos.elf 0x80000
使用如下指令让CPU执行流停止在指定位置,此处停止在可执行镜像的加载位置
step 0x80000
说明:根据上级验证,要想调试bin文件中的第1条指令,step应该将CPU执行位置设置在0x7FFFC
step 0x7FFFC
其实从上面的截图也可以看出,当使用step 0x80000命令时,CPU halt时的PC值为0x80004,也就是bin文件的第2条指令处,这与实际测试结果也是一致的
而使用step 0x7FFFC命令时,CPU halt时的PC值为0x80000,这是我们期望的结果
在另一个端口启动gdb,命令如下,此处加载的是带有符号表的ELF文件
gdb-multiarch --tui build/benos.elf
# 在gdb命令行中
target remote localhost:3333
此时便可以进行单步调试,可见在实际的树莓派4b中,寄存器值与qemu虚拟机不同
执行完mrs x0, CurrentEL指令后,X0的值为0x8
对照CurrentEL寄存器定义,此时异常等级为EL2
1. SP寄存器值
在执行完mov sp, #LOW_MEMORY指令之后,可见SP指针被设置为0x400000,也就是4MB地址处
2. SPSel寄存器值
此时我们获取SPSel寄存器的值,该值为1,说明使用的是当前异常等级的栈指针,也就是SP_EL2