GitHub BiscuitOS
Email: BuddyZhang1 buddy.zhang@aliyun.com
目录
ARM zImage 重定位前 gdb 调试方法
Linux 内核源码经过编译链接生成 ELF 目标文件 vmlinux,vmlinux 使用 OBJCOPY
工具去除不必要的段之后,生成 Image 二进制文件。Image 经过压缩之后生成 piggy_data,
在把 piggy_data 压缩文件放到汇编文件 piggy.s 里面经过汇编生成 piggy.o,该目标文件
与 arch/arm/boot/compressed 目录下的多个目标文件链接生成 vmlinux ELF 文件,这里
的 vmlinux 与 Linux 源码编译链接生成的 vmlinux 不是同一个。这个阶段的 vmlinux
是带了 bootstrap 的 vmlinux,vmlinux 在经过 OBJCOPY 工具之后,生成二进制文件
zImage,这个 zImage 是可以直接加载到内存直接运行的。其原理如下图:
正如上图,Uboot 将 zImage 加载到内存之后,zImage 就开始运行 Linux 早期代码,
这个阶段,zImage 主要任务就是将压缩的内核解压到制定的位置,然后将运行权转交给
解压出来的内核。由于 zImage 运行的范围会与解压之后的内核存在重叠,所以在 zImage
完成简单的初始化之后,就重定位 zImage 到安全的位置继续完成解压内核的任务。在解压
内核之前就会存在两个阶段,第一个阶段就是 zImage 重定位之前;第二个阶段就是 zImage
从定位之后,真正内核运行之前。两个阶段 GDB 调试存在一定的差异,因此将这两个阶段
独立拆开进行介绍。本节主要介绍 zImage 重定位之前,GDB 调试方法。
本文基于 ARM 32 Linux 5.0 进行讲解,如果还没有搭建 Linux 5.0 ,可以参考下面教程:
Linux 5.0 arm 32 开发环境搭建手册
搭建完上面的教程之后,参考 BiscuitOS/output/linux-5.0-arm32/README.md ,其中
关于 zImage 重定位之前的调试介绍如下:
根据上面的介绍,开发者首先打开一个终端,在中断中输入如下命令:
然后再打开第二个终端,第二个终端中输入如下命令:
此时第二个终端进入了 GDB 模式,开发者此时输入如下命令进行调试:
其中 XXX_bk 是断点的名字。运行如下:
打断点
在实际调试过程中需要对不同的代码段打断点,以此提高调试效率。在 zImage 重定位之前的阶段
打断点请参考如下步骤:
zImage 重定位之前的阶段的代码大多位于 arch/arm/boot/compressed/ 目录下,其中这个阶段
的入口函数位于 arch/arm/boot/compressed/head.S 里面,如下:
这上面的函数中,开发者可以使用 ENTRY() 宏来添加一个断点,例如:
在上面的代码中,添加了一个名为 BS_debug 的标签,可以再 GDB 中利用这个标签打
断点。调试方法如下所述,在进入 GDB 模式后,使用如下命令:
实际运行情况如下图:
拓展
由于 zImage 阶段的汇编代码调试都需要在 GDB 中进行符号表的重定位,BiscuitOS 在该阶段
默认使用的 .gdbinit 脚本位于 BiscuitOS/output/linux-5.0-arm32/package/gdb/gdb_zImage,
其内容如下:
上面脚本中使用了 gdb 的 add-symbol-file 动态加载了 vmlinux 的符号表,但后面的数值
是根据实际 vmlinux 加载偏移所设置的。
ARM zImage 重定位后 gdb 调试方法
zImage 完成基本的初始化之后,由于 zImage 的运行地址空间和解压之后的内核运行空间
存在重叠的位置,因此需要将 zImage 整体拷贝到一个安全的地址上运行,这里成为 zImage
的重定位。拷贝工作在 zImage 初始化阶段已经完成,本节所讨论的阶段是 zImage 从重
定位的地址继续运行,继续完成内核解压的任务。由于重定位之后,zImage 的符号表需要
重新加载,因此对该阶段的调试,请参考本节。本节的所有内容都是基于 Linux 5.0 进行
讲解的,如果还未搭建 Linux 5.0 开发环境,请参看如下教程:
Linux 5.0 arm 32 开发环境搭建手册
搭建完上面的教程之后,参考 BiscuitOS/output/linux-5.0-arm32/README.md ,其中
关于 zImage 重定位之后的调试介绍如下:
根据上面的介绍,开发者首先打开一个终端,在中断中输入如下命令:
然后再打开第二个终端,第二个终端中输入如下命令:
此时第二个终端进入了 GDB 模式,开发者此时输入如下命令进行调试:
其中 XXX_bk 是断点的名字。运行如下:
打断点
在实际调试过程中需要对不同的代码段打断点,以此提高调试效率。在 zImage 重定位之后的阶段
打断点请参考如下步骤:
zImage 重定位之后的阶段的代码大多位于 arch/arm/boot/compressed/ 目录下,其中这个阶段
的入口函数位于 arch/arm/boot/compressed/head.S 里面,且重定位的代码从 head.S 的
restart 之后执行。如下:
这上面的函数中,开发者可以使用 ENTRY() 宏来添加一个断点,例如:
在上面的代码中,添加了一个名为 BS_debug 的标签,可以再 GDB 中利用这个标签打
断点。调试方法如下所述,在进入 GDB 模式后,使用如下命令:
实际运行情况如下图:
拓展
由于 zImage 阶段的汇编代码调试都需要在 GDB 中进行符号表的重定位,BiscuitOS 在该阶段
默认使用的 .gdbinit 脚本位于 BiscuitOS/output/linux-5.0-arm32/package/gdb/gdb_RzImage,
其内容如下:
上面脚本中使用了 gdb 的 add-symbol-file 动态加载了 vmlinux 的符号表,但后面的数值
是根据实际情况动态计算出来的,开发者不同担心这些问题,BiscuitOS 使用了如下脚本自动
计算加载地址 (脚本位于 BiscuitOS/output/linux-5.0-arm32/package/gdb/gdb.pl):
感兴趣的开发者可以自行研究脚本的原理。
内核解压后 (MMU OFF) start_kernel 之前 gdb 调试方法
zImage 将压缩的内核解压到指定位置之后,然后将 CPU 的执行权移交给解压之后的内核。内核
获得 CPU 之后,就开始真正的初始化内核,由于此时 MMU 并未开启,内核没有将内存映射
到 vmlinux 的链接地址,因此在运行到 start_kernel 之前,需要使用特殊的方法调试
这个阶段的代码,本节用于介绍如何调试这个阶段的代码。本节的所有内容都是基于 Linux 5.0
进行讲解的,如果还未搭建 Linux 5.0 开发环境,请参看如下教程:
Linux 5.0 arm 32 开发环境搭建手册
搭建完上面的教程之后,参考 BiscuitOS/output/linux-5.0-arm32/README.md ,其中
关于 Image 解压运行开始到 start_kernel 之前的调试介绍如下:
根据上面的介绍,开发者首先打开一个终端,在中断中输入如下命令:
然后再打开第二个终端,第二个终端中输入如下命令:
此时第二个终端进入了 GDB 模式,开发者此时输入如下命令进行调试:
其中 BS_debug 是断点的名字。运行如下:
打断点
在实际调试过程中需要对不同的代码段打断点,以此提高调试效率。在 Image 初始化的阶段
打断点请参考如下步骤:
Image 初始化阶段的代码大多位于 arch/arm/kernel/ 目录下,其中这个阶段
的入口函数位于 arch/arm/kernel/head.S 里面,且 Image 初始化的代码从 head.S 的
ENTRY(stext) 之后执行。如下:
在上面的函数中,开发者可以使用 ENTRY() 宏来添加一个断点,例如:
在上面的代码中,添加了一个名为 BS_debug 的标签,可以再 GDB 中利用这个标签打
断点。调试方法如下所述,在进入 GDB 模式后,使用如下命令:
实际运行情况如下图:
拓展
由于 Image 初始化阶段,MMU 尚未开启,所有内存地址还没有与 vmlinux 链接地址关联,因此
该阶段使用 GDB 调试需要重定位符号表。 BiscuitOS 在该阶段
默认使用的 .gdbinit 脚本位于 BiscuitOS/output/linux-5.0-arm32/package/gdb/gdb_Image,
其内容如下:
上面脚本中使用了 gdb 的 add-symbol-file 动态加载了 vmlinux 的符号表,但后面的数值
是根据实际情况动态计算出来的,其计算的基本思路是:Image 从压缩文件解压到 0x60008000
的位置开始执行,此时是 Image 最前端的位置,也就是 .head.text 的位置。开发者可以按
如下步骤进行计算。首先使用平台对应的 readelf 文件获得 vmlinux 的符号表,具体命令
如下:
获得数据如下:
从上面的数据可知,.head.text Addr 项对应的地址是 80008000, 但由于 Image 开始执行
地址是 0x60008000, 因此 GDB 使用 add-symbol-file 重定位 vmlinux 的时候,需要使用
-s 选项重新指定 .head.text section 的地址是 0x60008000; 同理,.rodata 的 Addr
项是 80800000, 因此需要重新指定 .rodata section 的地址是 0x60800000. 最后,也是
最关键的,在 GDB 中使用 add-symbol-file 命令重定位 vmlinux 的地址,这个地址就是
vmlinux ELF 文件的 .text section 的地址,从图中可以看出,.text Addr 项是
0x80100000, 因此调整之后的地址就是 0x60100000, 通过这样的调整之后,vmlinux
符号表就重定位到 Image 对应的位置上了。
BiscuitOS 已经自动生成 gdb_Image 文件,开发者只需按照教程 README 提示的步骤,
就可以简单完成对该阶段代码的调试。
内核解压后 (MMU ON) start_kernel 之前 gdb 调试方法
Image 已经进行基础的初始化,但 MMU 并未开启,所以使用的都是物理地址。但 Image
初始化到后期,页表等寄存器设置完毕之后,内核启用 MMU 之后,开始使用虚拟地址,但
按 1:1 仅仅映射了内核镜像到 .bss 段的地址,其他地址并未映射。在启用 MMU 之后,
地址发生改变,所以之前重定位的符号表此处需要重新进行定位,因此从 MMU 启用到
start_kernel 这个阶段的调试需要按如下的步骤。本节的所有内容都是基于 Linux 5.0
进行讲解的,如果还未搭建 Linux 5.0 开发环境,请参看如下教程:
Linux 5.0 arm 32 开发环境搭建手册
搭建完上面的教程之后,参考 BiscuitOS/output/linux-5.0-arm32/README.md ,其中
关于 Image 解压运行开始 (MMU ON) 到 start_kernel 之前的调试介绍如下:
根据上面的介绍,开发者首先打开一个终端,在中断中输入如下命令:
然后再打开第二个终端,第二个终端中输入如下命令:
此时第二个终端进入了 GDB 模式,开发者此时输入如下命令进行调试:
其中 BS_debug 是断点的名字。运行如下:
打断点
在实际调试过程中需要对不同的代码段打断点,以此提高调试效率。在 Image 初始化的阶段
打断点请参考如下步骤:
Image 初始化阶段 (MMU ON) 的代码大多位于 arch/arm/kernel/ 目录下,其中这个阶段
的入口函数位于 arch/arm/kernel/head.S 里面。如下:
在上面的函数中,开发者可以使用 ENTRY() 宏来添加一个断点,例如:
在上面的代码中,添加了一个名为 BS_debug 的标签,可以再 GDB 中利用这个标签打
断点。调试方法如下所述,在进入 GDB 模式后,使用如下命令:
实际运行情况如下图:
拓展
由于 Image 初始化阶段,MMU 已经开启,需要将内核符号表加载到指定位置。 BiscuitOS 在该阶段
默认使用的 .gdbinit 脚本位于 BiscuitOS/output/linux-5.0-arm32/package/gdb/gdb_RImage,
其内容如下:
获得数据如下:
MMU 启用后,内核开始使用虚拟地址。从上面的数据可知,.head.text Addr 项对应的地址
是 80008000, 因此 GDB 使用 add-symbol-file 重定位 vmlinux 的时候,需要使用
-s 选项重新指定 .head.text section 的地址是 0x80008000; 同理,.rodata 的 Addr
项是 80800000, 因此需要重新指定 .rodata section 的地址是 0x80800000. 最后,也是
最关键的,在 GDB 中使用 add-symbol-file 命令重定位 vmlinux 的地址,这个地址就是
vmlinux ELF 文件的 .text section 的地址,从图中可以看出,.text Addr 项是
0x80100000, 因此加载地址就是 0x80100000, 通过这样的调整之后,vmlinux
符号表就重定位到 Image 对应的位置上了。这里还要设计到 .init.text 的位置,和其他
section 一样的设置方法。
BiscuitOS 已经自动生成 gdb_RImage 文件,开发者只需按照教程 README 提示的步骤,
就可以简单完成对该阶段代码的调试。
内核解压后 start_kernel 之后 gdb 调试方法
内核运行到 start_kernel 之后,MMU 已经开启,内存地址已经与 vmlinux 映射,因此
可以直接调试内核而不需要重定位 vmlinux 的符号表。本节的所有内容都是基于 Linux 5.0
进行讲解的,如果还未搭建 Linux 5.0 开发环境,请参看如下教程:
Linux 5.0 arm 32 开发环境搭建手册
搭建完上面的教程之后,参考 BiscuitOS/output/linux-5.0-arm32/README.md ,其中
关于 start_kernel 之后的调试介绍如下:
根据上面的介绍,开发者首先打开一个终端,在中断中输入如下命令:
然后再打开第二个终端,第二个终端中输入如下命令:
此时第二个终端进入了 GDB 模式,开发者此时输入如下命令进行调试:
其中在 start_kernel 处大断点。运行如下:
start_kernel 之后的 kernel GDB 调试都可以使用通用的 GDB 进行断点,函数,寄存器等
调试,开发者可以参考 GDB 手册进行调试。
附录
ARM inline-assembly usermanual
BiscuitOS Home
BiscuitOS Driver
BiscuitOS Kernel Build
Linux Kernel
Bootlin: Elixir Cross Referencer
搭建高效的 Linux 开发环境
赞赏一下吧 🙂