在学习Linux的过程中,调试必不可少。下面的步骤就是一步一步如何使用Arm 的FVP平台来设置Linux的开发和调试环境。 如下所有的步骤最终都汇聚在我的github boot-tf-a-u-boot-linux ,如果仅仅是使用,不需要知道它的具体细节的,建议直接使用github仓库提供的Makefile。
开发环境准备
- 在Ububtu上安装必要的包
sudo apt-get install make autoconf build-essential git wget fuseext2 tmux
- 下载交叉编译工具
Arm gcc 可以在 arm-gnu-toolchain-downloads, 这里可以根据自己的系统来选择下载对应的gcc.
一般我们使用的是x86_64 Linux host, 所以下载 AArch64 GNU/Linux target for x86_64 Linux host
可以在Ubuntu 终端中,使用如下命令:
cd $(workspace)/tools
wget https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
tar -xvf arm-gnu-toolchain-13.2.rel1-x86_64-aarch64-none-linux-gnu.tar.xz
- 下载Arm Fixed Virtual Platform (FVP)
Arm Base FVP 平台现在是不需要License 就可以运行了,最新的FVP平台可以在 Arm的网站上 下载 , 这里使用 Armv-A Base RevC AEM FVP (x86 Linux)
在Ubuntu 终端中,使用如下命令:
cd $(workspace)/tools
wget https://armkeil.blob.core.windows.net/developer/Files/downloads/ecosystem-models/FM_11_25/FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz
tar -zxvf FVP_Base_RevC-2xAEMvA_11.25_15_Linux64.tgz
- 下载Arm DS
如果需要使用Arm DS来调试Linux的话,这就需要下载Arm DS,可以在 arm 的网站 下载 . 也可以申请试用的license。
下载代码和rootfs
- 下载 TF-A,u-boot和Linux
要把linux boot起来,这里需要使用到TF-A,u-boot和Linux,这里使用的都是最新的代码,如果在使用过程中发现问题,可能需要使用某个比较稳定的版本。
这里使用如下命令来下载代码:
cd $(workspace)/src
git clone https://git.denx.de/u-boot
git clone https://git.trustedfirmware.org/TF-A/trusted-firmware-a tf-a
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
- 下载rootfs
制作一个rootfs不难,这里使用准备好的一个rootfs,里面已经有一些必要的包,包括vi, gcc 和gdb,如果需要调试应用程序,这里rootfs也是可以调试的。
cd $(workspace)/rootfs
wget https://github.com/geesun/boot-tf-a-u-boot-linux/raw/master/rootfs/rootfs.tar.bz2
如果我们想往这个rootfs里面加入一些别的文件,在下面的步骤里面会详细说明如何做。
编译代码
- 编译u-boot代码
在编译u-boot代码之前,需要根据Linux kernel和rootfs的信息来配置u-boot的BOOTARGS和BOOTCOMMAND。 这里有几个比较关键的参数:
- BOOTARGS中的root参数
这里使用了 root=/dev/vda1, 这是因为后面做rootfs的时候,使用了FVP的virtioblockdevice这个参数:
-C bp.virtioblockdevice.image_path=rootfs/grub-busybox.img
而制作rootfs 的时候,rootfs会放到第一个分区,所以就是vda1。
- BOOTCOMMAND中kernel Image 和device tree的地址
这里使用了 booti 0x80080000 - 0x83000000,这个是因为在启动FVP的时候,有如下参数:
--data cluster0.cpu0=linux/arch/arm64/boot/Image@0x80080000
--data cluster0.cpu0=linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb@0x83000000
根据上面的描述,使用如下命令来配置u-boot:
cd $(workspace)/src/u-boot
echo "CONFIG_BOOTARGS=\"console=ttyAMA0 earlycon=pl011,0x1c090000 root=/dev/vda1 rw ip=dhcp debug user_debug=31 loglevel=9\"" > fvp.cfg
echo "CONFIG_BOOTCOMMAND=\"booti 0x80080000 - 0x83000000\"" >> fvp.cfg
export ARCH=aarch64 ;
export CROSS_COMPILE=$(CROSS_COMPILE) ;
make vexpress_aemv8a_semi_config;
scripts/kconfig/merge_config.sh -m -O ./ .config fvp.cfg;
这里的CROSS_COMPILE 可以根据前面下载的gcc来决定,这里可以把他设置成:
export CROSS_COMPILE=$(workspace)/tools/arm-gnu-toolchain-13.2.Rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
配置完成之后,可以打开u-boot目录下的.config 来确保CONFIG_BOOTARGS 和 CONFIG_BOOTCOMMAND已经设置成期望的值。
CONFIG_BOOTARGS="console=ttyAMA0 earlycon=pl011,0x1c090000 root=/dev/vda1 rw ip=dhcp debug user_debug=31 loglevel=9"
CONFIG_BOOTCOMMAND="booti 0x80080000 - 0x83000000"
接下来使用如下命令来编译u-boot:
cd $(workspace)/src/u-boot
export ARCH=aarch64 ;
export CROSS_COMPILE=$(CROSS_COMPILE) ;
make
这一步做完,最终生成 src/u-boot/u-boot.bin.
- 编译tf-a代码
编译在tf-a的过程中,u-boot是作为tf-a的BL33的image,所以需要先编译前面的u-boot后,才能编译tf-a,使用如下命令:
export CROSS_COMPILE=$(CROSS_COMPILE)
cd $(workspace)/src/tf-a
make PLAT=fvp DEBUG=1 BL33=$(workspace)/src/u-boot/u-boot.bin all fip V=1
这里面仅仅是使用了tf-a的默认配置,如TTBR等feature 都有没有使能。 如果需要使能更多feature,可以根据自己的需求添加。
从上面的命令可以看出, u-boot 是被打包到fib.bin里面,所以如果更改了u-boot,必须重新运行tf-a的编译过程,把u-boot重新打包到fib.bin里面。
- 编译Linux kernel代码
编译Linux kernel,这里使用kernel中的默认配置,即defconfig。即使用如下命令来配置kernel:
cd $(workspace)/src/linux
make -C $(SRC_DIR)/linux ARCH=arm64 defconfig CROSS_COMPILE=$(CROSS_COMPILE)
如果想要对kernel 进行额外的配置,使用如下命令:
cd $(workspace)/src/linux
make -C $(SRC_DIR)/linux ARCH=arm64 menuconfig CROSS_COMPILE=$(CROSS_COMPILE)
配置完成之后,如下命令可以用来进行编译:
cd $(workspace)/src/linux
make -C $(SRC_DIR)/linux ARCH=arm64 -j 24 Image CROSS_COMPILE=$(CROSS_COMPILE) Image dtbs
制作rootfs
这里不想讲如何从头开始制作一个rootfs,其实用buildroot,busybox或者yocto制作出的rootfs是可以通用的,这里使用已经打包好的一个rootfs的tar包来重制rootfs。
这里主要的目的是要明白如何在已有tar基础上,如何添加一些文件到想要的rootfs里面去。
- 把rootfs解包
在准备阶段,已经在rootfs目录下载了rootfs.tar.bz2,这一步先把它解压一下:
mkdir -p $(workspace)/src/rootfs/tmp
cd $(workspace)/src/rootfs/tmp
tar -jxvf ../rootfs.tar.bz2
- 修改rootfs
在目录 $(workspace)/src/rootfs/tmp/rootfs ,可以根据自己的需求来增加或者删减文件。
这一步不是必须的,是根据需求来决定的,如果没有改动需求,这一步可以跳过。
- 生成rootfs的partition文件
cd $(workspace)/src/rootfs/tmp
export BLOCK_SIZE=512
export SEC_PER_MB=$((1024*2))
export EXT3_SIZE_MB=512
export PART_START=$((1*SEC_PER_MB))
export EXT3_SIZE=$((EXT3_SIZE_MB*SEC_PER_MB))
dd if=/dev/zero of=ext3_part bs=$BLOCK_SIZE count=$EXT3_SIZE
mkdir -p mnt
mkfs.ext3 -F ext3_part
fuse-ext2 ext3_part mnt -o rw+
cp -rf rootfs/* mnt/
sync
fusermount -u mnt
rm -rf mnt
这里rootfs的最大大小是512M,如果需要调整大小,可以调整EXT3_SIZE_MB=512的值。
完成这一步,就生成了一个ext3的rootfs partition文件ext3_part,下一步会把这个分区文件放在磁盘映像文件的第一个分区。
- 使用gdisk生成rootfs的磁盘映像文件
cd $(workspace)/src/rootfs/tmp
export BLOCK_SIZE=512
export SEC_PER_MB=$((1024*2))
export EXT3_SIZE_MB=512
export PART_START=$((1*SEC_PER_MB))
export EXT3_SIZE=$((EXT3_SIZE_MB*SEC_PER_MB))
export IMG_BB=../rootfs.img
dd if=/dev/zero of=part_table bs=$BLOCK_SIZE count=$PART_START
cat part_table > $IMG_BB
cat ext3_part >> $IMG_BB
cat part_table >> $IMG_BB
(echo n; echo 1; echo $PART_START; echo +$((EXT3_SIZE)); echo 8300; echo w; echo y) | gdisk $IMG_BB
这里就完成了把上一步生成的ext3_part放在$(workspace)/src/rootfs/rootfs.img 的第一个分区。
文件$(workspace)/src/rootfs/rootfs.img就是最终的要传给FVP的rootfs。
在FVP上运行Linux
在准备FVP那一节,FVP已经被解压到:
$(workspace)/tools/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA
确保前面几节已经准备了如下的images:
$(workspace)/src/tf-a/build/fvp/debug/bl1.bin
$(workspace)/src/tf-a/build/fvp/debug/fip.bin
$(workspace)/src/linux/arch/arm64/boot/Image
$(workspace)/src/linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb
$(workspace)/rootfs/rootfs.img
接下来就可以使用下面的命令来运行Linux:
cd $(workspace)
tools/Base_RevC_AEMvA_pkg/models/Linux64_GCC-9.3/FVP_Base_RevC-2xAEMvA \
-C cluster0.NUM_CORES=4 -C cluster1.NUM_CORES=4 \
-C cluster0.has_arm_v8-3=1 -C cluster1.has_arm_v8-3=1 \
-C cluster0.has_arm_v8-5=1 -C cluster1.has_arm_v8-5=1 \
-C cluster0.has_branch_target_exception=1 -C cluster1.has_branch_target_exception=1 \
-C cache_state_modelled=0 \
-C pctl.startup=0.0.0.0 \
-C bp.secure_memory=1 \
-C bp.ve_sysregs.exit_on_shutdown=1 \
-C bp.secureflashloader.fname=$(workspace)/src/tf-a/build/fvp/debug/bl1.bin \
-C bp.flashloader0.fname=$(workspace)/src/tf-a/build/fvp/debug/fip.bin \
--data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/Image@0x80080000 \
--data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb@0x83000000 \
-C bp.ve_sysregs.mmbSiteDefault=0 \
-C bp.virtioblockdevice.image_path=$(workspace)/rootfs/rootfs.img
如果是在ssh 的terminal里面运行FVP,因为没有图形界面,所以建议使用tmux 在增加如下参数运行FVP:
-C bp.terminal_3.terminal_command="tmux split-window -d telnet localhost %port" \
-C bp.terminal_0.terminal_command="tmux split-window -h telnet localhost %port" \
在Arm DS上调试Linux
在开始使用armdbg命令调试Linux之前,需要先去下载Arm DS和申请试用license。 这个过程这里就不赘述了。具体可以 参考 .
在安装好Arm DS之后,可以在图形界面来import base model 到Arm DS。 可以参考 Arm 网站
准备一个Arm DS的脚本ap.ds,来自动加载符号表,如下:
$ cat ap.ds
add-symbol-file "src/tf-a/build/fvp/debug/bl1/bl1.elf" EL3:0
add-symbol-file "src/tf-a/build/fvp/debug/bl2/bl2.elf" EL1S:0
add-symbol-file "src/tf-a/build/fvp/debug/bl31/bl31.elf" EL3:0
add-symbol-file "src/linux/vmlinux" EL2N:0x800000200000
add-symbol-file "src/linux/vmlinux" EL1N:0x800000200000
add-symbol-file "src/linux/vmlinux" EL1N:0
add-symbol-file "src/linux/vmlinux" EL2N:0
break bl31_main
break primary_entry
c
接下来就可以使用如下命令开始调试:
/opt/arm/developmentstudio_platinum-0.a/bin/armdbg \
--cdb-entry="Imported::FVP_Base_RevC_2xAEMvA::Bare Metal Debug::Bare Metal Debug::ARM_AEM-A_MPx4 SMP Cluster 0" \
--cdb-root ~/developmentstudio-workspace/RevC \
-cdb-entry-param model_params=" \
-C cluster0.NUM_CORES=4 -C cluster1.NUM_CORES=4 \
-C cluster0.has_arm_v8-3=1 -C cluster1.has_arm_v8-3=1 \
-C cluster0.has_arm_v8-5=1 -C cluster1.has_arm_v8-5=1 \
-C cluster0.has_branch_target_exception=1 -C cluster1.has_branch_target_exception=1 \
-C cache_state_modelled=0 \
-C pctl.startup=0.0.0.0 \
-C bp.secure_memory=1 \
-C bp.ve_sysregs.exit_on_shutdown=1 \
-C bp.secureflashloader.fname=$(workspace)/src/tf-a/build/fvp/debug/bl1.bin \
-C bp.flashloader0.fname=$(workspace)/src/tf-a/build/fvp/debug/fip.bin \
--data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/Image@0x80080000 \
--data cluster0.cpu0=$(workspace)/src/linux/arch/arm64/boot/dts/arm/fvp-base-revc.dtb@0x83000000 \
-C bp.ve_sysregs.mmbSiteDefault=0 \
-C bp.virtioblockdevice.image_path=$(workspace)/rootfs/rootfs.img \
-C bp.terminal_3.terminal_command=\"tmux split-window -d telnet localhost %port\" \
-C bp.terminal_0.terminal_command=\"tmux split-window -h telnet localhost %port\" \
" -s ap.ds --interactive
如果使用的是tmux,可以看到如下调试界面,armdbg的命令是跟gdb差不多:
EL3:0x020000000-0x02C1FFFFF | SP:0x020000000-0x02C1FFFFF | Device-nGnRE | RW | False | False | False │INFO: Entry point address = 0x4003000
EL3:0x02C200000-0x02EFFFFFF | <unmapped> | | | | | │INFO: SPSR = 0x3cd
EL3:0x02F000000-0x02F1FFFFF | SP:0x02F000000-0x02F1FFFFF | Device-nGnRE | RW | False | False | False │INFO: BL31 FCONF: FW_CONFIG address = 4001010
EL3:0x02F200000-0x0FFDFFFFF | <unmapped> | | | | | │INFO: FCONF: Reading FW_CONFIG firmware configuration file from: 0x4001010
EL3:0x0FFE00000-0x0FFFFFFFF | SP:0x0FFE00000-0x0FFFFFFFF | Normal | RW | True | True | False │INFO: FCONF: Reading firmware configuration information for: dyn_cfg
EL3:0x100000000-0xFFFFFFFFF | <unmapped> | | | | | │INFO: FCONF: Reading HW_CONFIG firmware configuration file from: 0x7f00000
>c │INFO: FCONF: Reading firmware configuration information for: dram_layout
ERROR(?): [model] xterm: Xt error: Can't open display: │INFO: FCONF: Reading firmware configuration information for: cpu_timer
ERROR(?): [model] xterm: DISPLAY is not set │INFO: FCONF: Reading firmware configuration information for: uart_config
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │INFO: FCONF: Reading firmware configuration information for: topology
On core ARM_AEM-A_MP_0 (ID 0) │INFO: FCONF: Reading firmware configuration information for: gicv3_config
In primary_entry (no debug info) │NOTICE: BL31: v2.11.0(debug):v2.11.0-2-g3e8f9fd87
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │NOTICE: BL31: Built : 13:24:53, May 29 2024
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │INFO: GICv3 with legacy support detected.
On core ARM_AEM-A_MP_0 (ID 0) │INFO: ARM GICv3 driver initialized in EL3
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │INFO: Maximum SPI INTID supported: 255
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │INFO: BL31: Initializing runtime services
On core ARM_AEM-A_MP_0 (ID 0) │INFO: BL31: Preparing for EL3 exit to normal world
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │INFO: Entry point address = 0x88000000
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │INFO: SPSR = 0x3c9
On core ARM_AEM-A_MP_0 (ID 0) │
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │U-Boot 2024.07-rc3-00013-g46ff00bea5 (May 29 2024 - 09:14:54 +0800) vexpress_aemv8a
On core ARM_AEM-A_MP_0 (ID 0) │
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │Model: FVP Base
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │DRAM: 2 GiB (effective 4 GiB)
On core ARM_AEM-A_MP_0 (ID 0) │Core: 26 devices, 12 uclasses, devicetree: board
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │Flash: 64 MiB
Execution stopped in EL2h mode at breakpoint 2.1: EL2N:0x0000000081E200E0 │Loading Environment from Flash... *** Warning - bad CRC, using default environment
On core ARM_AEM-A_MP_0 (ID 0) │
EL2N:0x0000000081E200E0 BL record_mmu_state ; 0x81E39CD8 │In: serial@90000
>p/x $x0 │Out: serial@90000
$3 = 0xFD6BE000 │Err: serial@90000
>disass │Net: No ethernet found.
primary_entry: │
EL2N:0x0000000081E200E0 : BL record_mmu_state ; 0x81E39CD8 │Hit any key to stop autoboot: 0
EL2N:0x0000000081E200E4 : BL preserve_boot_args ; 0x81E39D2C │Moving Image from 0x80080000 to 0x80200000, end=82db0000
EL2N:0x0000000081E200E8 : ADRP x1,{pc}+0xf86000 ; 0x82DA60E8 │## Flattened Device Tree blob at 83000000
EL2N:0x0000000081E200EC : ADD sp,x1,#0 │ Booting using the fdt blob at 0x83000000
EL2N:0x0000000081E200F0 : MOV x29,xzr │Working FDT set to 83000000
EL2N:0x0000000081E200F4 : ADRP x0,{pc}+0x110000 ; 0x81F300F4 │ Loading Device Tree to 00000000fd6be000, end 00000000fd6c3d78 ... OK
EL2N:0x0000000081E200F8 : MOV x1,xzr │Working FDT set to fd6be000
EL2N:0x0000000081E200FC : BL __pi_create_init_idmap ; 0x81E37518 │
EL2N:0x0000000081E20100 : CBNZ x19,primary_entry+64 ; 0x81E20120 │Starting kernel ...
EL2N:0x0000000081E20104 : DMB SY │
Comments !