熟悉和掌握一个处理器体系结构最有效的方法是多做练习、多做实验。本书采用树莓派4B作为硬件实验平台。
本章需要准备的实验设备如图2.2所示。
●硬件开发平台:树莓派4B开发板。
●处理器体系结构:ARMv8体系结构。
●开发主机:Ubuntu Linux 20.04。
●MicroSD卡一张以及读卡器。
●USB转串口模块。
●杜邦线若干。
●Type-C USB线一根。
●J-Link EDU仿真器 ,如图2.3所示。J-Link EDU是针对高校教育的版本。
▲图2.2 硬件实验平台
▲图2.3 J-Link EDU仿真器
要在树莓派4B上运行实验代码,我们需要一根USB转串口线,这样在系统启动时便可通过串口输出信息来协助调试。读者可从网上商店购买USB转串口线,图2.4所示是某个厂商售卖的一款USB转串口线。串口一般有3根线。另外,串口还有一根额外的电源线(可选)。
▲图2.4 USB转串口线
●电源线(红色 ):5 V或3.3 V电源线(可选)。
●地线(黑色)。
●接收线(白色):串口的接收线RXD。
●发送线(绿色):串口的发送线TXD。
树莓派4B支持包含40个GPIO引脚的扩展接口,这些扩展接口的定义如图2.5所示。根据扩展接口的定义,我们需要把串口的三根线连接到扩展接口,如图 2.6所示。
●地线:连接到第6个引脚。
●RXD线:连接到第8个引脚。
●TXD线:连接到第10个引脚。
在Windows 10操作系统中需要在设备管理器中查看串口号,如图2.7所示。你还需要在Windows 10操作系统中安装用于USB转串口的驱动。
▲图2.5 树莓派扩展接口的定义
▲图2.6 将串口线连接到树莓派扩展接口
▲图2.7 在设备管理器中查看串口号
接上USB电源,在串口终端软件(如PuTTY或MobaXterm等)中查看是否有输出,如图2.8所示。即使没有插入MicroSD卡,串口也能输出信息,如果能看到串口输出信息,那么说明串口设备已经配置。这些信息是树莓派固件输出的。图 2.8 中的日志信息显示系统没有找到MicroSD卡。
▲图2.8 在串口终端软件中查看是否有输出
树莓派的映像文件需要安装(烧录)到MicroSD卡里。第一次使用树莓派时,我们先给树莓派安装一个官方的OS——Raspberry Pi OS(简称树莓派OS),用来验证开发板是否正常工作。另外,要在树莓派上运行BenOS,也需要准备一张格式化好的MicroSD卡。格式化的要求如下。
●使用MBR分区表。
●格式化boot分区为FAT32文件系统。
下面是安装树莓派OS的步骤。
(1)到树莓派官方网站上下载ARM64版本的树莓派OS映像文件,例如2021-03-04-raspios-buster-arm64.img。
(2)为了将映像文件烧录到 MicroSD 卡中,将MicroSD卡插入 USB 读卡器。在 Windows 主机上,安装Win32DiskImager软件来进行烧录,如图 2.9 所示。而在Linux主机上通过简单地执行dd命令将映像文件烧录至MicroSD卡。
▲图2.9 烧录映像文件
#dd if= 2021-03-04-raspios-buster-arm64.img of=/dev/sdX status=progress
其中,/dev/sdX中的X需要修改为存储卡实际的映射值,可以通过“fdisk -l”命令来查看。
(3)把MicroSD卡重新插入主机,此时会看到有一个名为“boot”的分区。修改boot分区里面的config.txt配置文件,在这个文件中新增两行,目的是使能串口输出功能。
uart_2ndstage=1 enable_uart=1
(4)启动树莓派。把MicroSD卡插入树莓派中,通过USB线给树莓派供电。树莓派OS的用户名为pi,密码为raspberry。
(5)配置树莓派 4B 上的 Wi-Fi。使用树莓派上的配置工具来配置,在串口中输入如下命令。
$ sudo raspi-config
(6)选择System Options→S1 Wireless LAN,配置SSID和密码,如图2.10所示。
(7)更新系统,这样会自动更新树莓派4B上的固件。
sudo apt update sudo apt full-upgrade sudo reboot
▲图2.10 配置SSID和密码
经过上面的步骤,我们得到格式化好的boot分区和最新版本的树莓派固件。boot分区主要包括如下几个文件。
●bootcode.bin:引导程序。树莓派复位上电时,CPU处于复位状态,由GPU负责启动系统。GPU首先会启动固化在芯片内部的固件(BootROM代码),读取MicroSD卡中的bootcode.bin文件,并装载和运行bootcode.bin中的引导程序。树莓派4B已经把bootcode.bin引导程序固化到BootROM里。
●start4.elf:树莓派4B上的GPU固件。bootcode.bin引导程序检索MicroSD卡中的GPU固件,加载固件并启动GPU。
●start.elf:树莓派3B上的GPU固件。
●config.txt:配置文件。GPU启动后读取config.txt配置文件,读取Linux内核映像(比如kernel8.img等)以及内核运行参数等,然后把内核映像加载到共享内存中并启动CPU,CPU结束复位状态后开始运行Linux内核。
了解和熟悉如何在树莓派4B上运行最简单的BenOS程序。
首先,在Linux主机中安装相关工具 。
$ sudo apt-get install apt-get install qemu-system-arm libncurses5-dev gcc-aarch64-linux-gnu build-essential git bison flex libssl-dev
然后,在Linux主机上使用make命令编译BenOS。
$ cd benos $ make
编译完成之后会生成benos.bin可执行文件以及benos.elf文件。在把benos.bin可执行文件放到树莓派4B上之前,我们可以使用QEMU虚拟机来模拟树莓派运行,可直接输入“make run”命令。
$ make run qemu-system-aarch64 -machine raspi4 -nographic -kernel benos.bin Welcome BenOS!
把benos.bin文件复制到MicroSD卡的boot分区(可以通过USB的MicroSD读卡器进行复制),并且修改boot分区里面的config.txt文件。
<config.txt文件> [pi4] kernel=benos.bin max_framebuffers=2 [pi3] kernel=benos.bin [all] arm_64bit=1 enable_uart=1 kernel_old=1 disable_commandline_tags=1
插入MicroSD卡到树莓派,连接USB电源线,使用Windows端的串口软件可以看到输出,如图2.11所示。
▲图2.11 输出欢迎语句
我们可以使用GDB和QEMU虚拟机单步调试裸机程序。
本节以实验2-1为例,在终端启动QEMU虚拟机的gdbserver。
$ qemu-system-aarch64 -machine raspi4 -serial null -serial mon:stdio -nographic -kernel benos.bin -S -s
在另一个终端输入如下命令来启动GDB,可以使用aarch64-linux-gnu-gdb命令或者gdb-multiarch命令。
$ aarch64-linux-gnu-gdb --tui build/benos.elf
在GDB的命令行中输入如下命令。
(gdb) target remote localhost:1234 (gdb) b _start Breakpoint 1 at 0x0: file src/boot.S, line 7. (gdb) c
此时,可以使用GDB命令来进行单步调试,如图2.12所示。
▲图2.12 使用GDB调试裸机程序
调试BenOS是通过QEMU虚拟机中内置的gdbserver来实现的,但gdbserver只能调试在QEMU虚拟机上运行的程序。如果需要调试在硬件开发板上运行的程序,例如把BenOS放到树莓派上运行,那么GDB与QEMU虚拟机就无能为力了。如果我们编写的程序在QEMU虚拟机上能运行,而在实际的硬件开发板上无法运行,那就只能借助硬件仿真器来调试和定位问题。
硬件仿真器指的是使用仿真头完全取代目标板(例如树莓派4B开发板)上的CPU,通过完全仿真目标开发板上的芯片行为,提供更加深入的调试功能。目前流行的硬件仿真器是JTAG仿真器。JTAG(Joint Test Action Group)是一种国际标准测试协议,主要用于芯片内部测试。JTAG仿真器通过现有的JTAG边界扫描口与CPU进行通信,实现对CPU和外设的调试功能。
▲图2.13 J-Link仿真器的JTAG接口
目前市面上支持ARM芯片调试的仿真器主要有ARM公司的DSTREAM仿真器、德国Lauterbach公司的Trace32仿真器以及SEGGER公司的J-Link仿真器。J-Link EDU是SEGGER公司推出的面向高校和教育的版本,本章提到的J-Link仿真器指的是J-Link EDU版本。本节介绍如何使用J-Link仿真器 调试树莓派4B。
为了在树莓派4B上使用J-Link仿真器,首先需要把J-Link仿真器的JTAG接口连接到树莓派4B的扩展板。树莓派4B的扩展接口已经内置了JTAG接口。我们可以使用杜邦线来连接。
J-Link仿真器提供20引脚的JTAG接口,如图2.13所示。
JTAG接口引脚的说明如表2.2所示。
表2.2 JTAG接口引脚的说明
树莓派与J-Link仿真器的连接需要8根线,如表2.3所示。读者可以参考图2.5和图2.13来仔细连接线路。
表2.3 树莓派与J-Link仿真器的连接
在实验2-1的基础上,复制loop.bin程序到MicroSD卡。另外,还需要修改config.txt配置文件,打开树莓派对JTAG接口的支持。
完整的config.txt文件如下。
# BenOS for JLINK debug [pi4] kernel=loop.bin [pi3] kernel=loop.bin [all] arm_64bit=1 enable_uart=1 uart_2ndstage=1 enable_jtag_gpio=1 gpio=22-27=a4 init_uart_clock=48000000 init_uart_baud=115200
●uart_2ndstage=1:打开固件的调试日志。
●enable_jtag_gpio =1:使能JTAG接口。
●gpio=22-27=a4:表示GPIO22~GPIO27使用可选功能配置4。
●init_uart_clock=48000000:设置串口的时钟。
●init_uart_baud=115200:设置串口的波特率。
复制完之后,把MicroSD卡插入树莓派中,接上电源。
OpenOCD(Open On-Chip Debugger,开源片上调试器)是一款开源的调试软件。OpenOCD提供针对嵌入式设备的调试、系统编程和边界扫描功能。OpenOCD需要使用硬件仿真器来配合完成调试,例如J-Link仿真器等。OpenOCD内置了GDB server模块,可以通过GDB命令来调试硬件。
首先,通过git clone命令下载OpenOCD软件 。
然后,安装如下依赖包。
$ sudo apt install make libtool pkg-config autoconf automake texinfo
接下来,编译和安装。
$ cd openocd $ ./ bootstrap $ ./configure $ make $ sudo make install
另外,也可以从xPack OpenOCD项目中下载编译好的二进制文件。
为了使用openocd命令连接J-Link仿真器,需要指定配置文件。OpenOCD的安装包里内置了jlink.cfg文件,该文件保存在/usr/local/share/openocd/scripts/interface/目录下。jlink.cfg配置文件比较简单,可通过“adapter”命令连接J-Link仿真器。
<jlink.conf配置文件> # SEGGER J-Link adapter driver jlink
下面通过openocd命令来连接J-Link仿真器,可使用“-f”选项来指定配置文件。
$ openocd -f jlink.cfg Open On-Chip Debugger 0.10.0+dev-01266-gd8ac0086-dirty (2020-05-30-17:23) Licensed under GNU GPL v2 For bug reports, read ****://openocd.***/doc/doxygen/bugs.html Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : J-Link V11 compiled Jan 7 2020 16:52:13 Info : Hardware version: 11.00 Info : VTarget = 3.341 V
从上述日志可以看到,OpenOCD已经检测到J-Link仿真器,版本为11。
接下来,使用J-Link仿真器连接树莓派,这里需要描述树莓派的配置文件raspi4.cfg。树莓派的这个配置文件的主要内容如下。
<raspi4.cfg配置文件> set _CHIPNAME bcm2711 set _DAP_TAPID 0x4ba00477 adapter speed 1000 transport select jtag reset_config trst_and_srst telnet_port 4444 # 创建 tap jtag newtap auto0 tap -irlen 4 -expected-id $_DAP_TAPID # 创建 dap dap create auto0.dap -chain-position auto0.tap set CTIBASE {0x80420000 0x80520000 0x80620000 0x80720000} set DBGBASE {0x80410000 0x80510000 0x80610000 0x80710000} set _cores 4 set _TARGETNAME $_CHIPNAME.a72 set _CTINAME $_CHIPNAME.cti set _smp_command "" for {set _core 0} {$_core < $_cores} { incr _core} { cti create $_CTINAME.$_core -dap auto0.dap -ap-num 0 -ctibase [lindex $CTIBASE $_core] set _command "target create ${_TARGETNAME}.$_core aarch64 \ -dap auto0.dap -dbgbase [lindex $DBGBASE $_core] \ -coreid $_core -cti $_CTINAME.$_core" if {$_core != 0} { set _smp_command "$_smp_command $_TARGETNAME.$_core" } else { set _smp_command "target smp $_TARGETNAME.$_core" } eval $_command } eval $_smp_command targets $_TARGETNAME.0
使用如下命令连接树莓派,结果如图2.14所示。
$ openocd -f jlink.cfg -f raspi4.cfg
如图2.14所示,OpenOCD已经成功连接J-Link仿真器,并且找到了树莓派的主芯片BCM2711。OpenOCD开启了几个服务,其中Telnet服务的端口号为4444,GDB服务的端口号为3333。
▲图2.14 使用J-Link仿真器连接树莓派
在Linux主机中新建终端,输入如下命令以登录OpenOCD的Telnet服务。
$ telnet localhost 4444 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Open On-Chip Debugger >
在Telnet服务的提示符下输入“halt”命令以暂停树莓派的CPU,等待调试请求。
> halt bcm2711.a72.0 cluster 0 core 0 multi core bcm2711.a72.1 cluster 0 core 1 multi core target halted in AArch64 state due to debug-request, current mode: EL2H cpsr: 0x000003c9 pc: 0x78 MMU: disabled, D-Cache: disabled, I-Cache: disabled bcm2711.a72.2 cluster 0 core 2 multi core target halted in AArch64 state due to debug-request, current mode: EL2H cpsr: 0x000003c9 pc: 0x78 MMU: disabled, D-Cache: disabled, I-Cache: disabled bcm2711.a72.3 cluster 0 core 3 multi core target halted in AArch64 state due to debug-request, current mode: EL2H cpsr: 0x000003c9 pc: 0x78 MMU: disabled, D-Cache: disabled, I-Cache: disabled target halted in AArch64 state due to debug-request, current mode: EL2H cpsr: 0x000003c9 pc: 0x80000 MMU: disabled, D-Cache: disabled, I-Cache: disabled >
接下来,使用load_image命令加载BenOS可执行程序,这里把benos.bin加载到内存的0x80000地址处,因为在链接脚本中设置的链接地址为0x80000。
> load_image /home/rlk/rlk/lab01/benos.bin 0x80000 936 bytes written at address 0x80000 downloaded 936 bytes in 0.101610s (8.996 KiB/s)
下面使用step命令让树莓派的CPU停在链接地址(此时的链接地址为0x80000)处,等待用户输入命令。
> step 0x80000 target halted in AArch64 state due to single-step, current mode: EL2H cpsr: 0x000003c9 pc: 0x4 MMU: disabled, D-Cache: disabled, I-Cache: disabled
现在可以使用GDB调试代码了。首先使用aarch64-linux-gnu-gdb命令(或者gdb-multiarch命令)启动GDB,并且使用端口号3333连接OpenOCD的GDB服务。
$ aarch64-linux-gnu-gdb --tui build/benos.elf (gdb) target remote localhost:3333 <=连接OpenOCD的GDB服务
当连接成功之后,我们可以看到GDB停在BenOS程序的入口点(_start),如图2.15所示。
▲图2.15 连接OpenOCD的GDB服务
此时,我们可以使用GDB的“step”命令单步调试程序,也可以使用“info reg”命令查看树莓派上的CPU寄存器的值。
使用“layout reg”命令打开GDB的寄存器窗口,这样就可以很方便地查看寄存器的值。如图2.16所示,当单步执行完第16行的“adr x0, bss_begin”汇编语句后,寄存器窗口中马上显示了X0寄存器的值。
▲图2.16 单步调试和查看寄存器的值