赞
踩
目录
在正式开始之前,需要思考一个问题,如何搭建一个便捷的Linux内核调试环境。
当然,你可以直接求助于搜索引擎。这没有错。但是,如果让搜索引擎代替了自己的思考,那么,其实是得不偿失的。这样一来,你就会永远处于低水平重复的境地,而别人则不断的给你提供着解决方案。
在开始标题所述的工作之前,我也从搜索引擎找了很多现成方案,基本上大同小异。但是,一番搜索下来,大家提供的都是结果,对于为啥可以这样做,却鲜有提及。很可能第一个人提供了解决方案后,其他人都在上一个人的思路或者路线上,重新进行了微小的修改,并将其发布出来。
这次,我们思考着来处理这个问题。
要调试Linux内核,大家都知道,这是一个OS,所以跟硬件是强绑定的。这样一来,你可以有三种选择:
1 在自己的硬件上调试。就是在你工作的主机环境下调试。这个选择,风险比较大,很容易将机器整死机。从学习的角度来看,没必要冒这个风险。
2 在其他硬件上调试。你可以选择另一台电脑来调测,也可以选择一个开发板来调测。开发板相对而言,似乎是较好的选择,你可以真实的感受硬件部分的变化。但是,为此需要购买一个板子,并且每次修改后,需要重新刷写内核。有点繁琐。不过,总的来讲,这也是一个不错的选择。
3 在模拟硬件上调试。选择这个方案,你可以省去购买硬件的money。因为是模拟器,所以,你可以不用刷机,反而可以将修改到生效的一系列命令整合成脚本。这能提高效率。进一步的,模拟方案可以有多种选择。比如虚拟机方案,模拟器方案。这里,我们选择模拟器方案。模拟器方式,可以用一个较为轻量级的进程完成上述工作,而选择虚拟机,则显得较为笨重。当然了,萝卜白菜,各有所爱。这里的重点是知道有哪些选择。如果实在不知道该怎么做,那就遍历一遍,你就会有适配自己需求的主意了。
这里,我将大方向选择为模拟器方案。这时候,我们需要了解一个东西,QEMU。这是法国大牛Fabrice Bellard开发的软件。十年前我在做家庭媒体网关项目时,当时外请了一个指导专家,就是利用QEMU调试内核。现在想来,当时真应该好好学习学习。一晃十年过去了,这玩意还是那么有生命力。
提到Fabrice Bellard,不得不提这位大牛的另一番杰作:FFMPEG。当我等NORMAL考虑怎么养家糊口时,大牛们思考的是如何改变世界。关于这位大牛的事迹,各位看官感兴趣的话,就去搜搜吧。
提到虚拟机,想到一个超级简洁的虚拟机:BPF。伯克利包过滤器里面就是基于一个虚拟机实现包过滤策略的解析处理。这里算是插叙一段。如果让你去实现一个虚拟机,该如何做呢?首先,你要定义一套指令集及访存机制,然后进入一个死循环,不断的读取指令,执行指令。这里模拟CPU工作的核心就是一个死循环。再模拟中断的话,就在死循环里加上事件处理机制。每次执行一条指令前,先看看有没有事件存在。大的思路就是这样。如果真要实现,难在细节。
好了,回归主线。我们先看看QEMU是什么?
了解一个东西,最直接最有效最准确的方式是访问官网。QEMU的官网为:QEMU
QEMU是一个通用的,开放源码的机器模拟器和虚拟化工具。它目前提供三种版本:
1 全系统模拟。目标是在任何架构的任何机器上,运行操作系统。这就是普通虚拟机模式。
2 用户模式模拟。目标是在任何支持的架构上,运行另一个目标平台(LINUX/BSD)的程序。
3 虚拟化。目标是以接近原生指令性能的方式运行KVM和XEN这类虚拟机。计算机领域里解决复杂问题经常采用分层的方式,但是分层必然带来执行路径的延长,从而降低性能。所以,这个目标的要求还是挺高的。
这里,我们采用第二种方式,也就是通过一个程序来构建目标架构和机器环境。这里的架构,多指CPU的核心架构,可能影响到指令集的差异。机器则是指代支持该架构CPU的处理器。比如,对于ARM而言,架构可以是ARM CortexA17,机器可以是三星的S3C24XX。
QEMU的源码托管在GITHUB上:https://github.com/qemu/qemu
了解QEMU后,你可以选择下载官方编译号的二进制包,也可以选择自己下载源码编译。
这里,我们选择下载现成的。Ubuntu平台通过如下命令下载安装:
sudo apt-get install qemu
到这里,我们有了模拟器环境。到时候,Linux内核就在该模拟器上运行。
有了模拟器后,我们需要解决第二个问题,就是内核。同前,可以从Linux官网上了解内核的最新情况。The Linux Kernel Archives
当然,我们也可以从官网下载内核源码。但是官网存在一个问题就是速度慢。特别是国内,大家都懂得。这里,推荐采用国内的镜像。各个大厂似乎都有这类镜像,感谢各大厂提供的镜像服务,为开发者提供了很多遍历。这里提供两个镜像点,一个是阿里的,一个是华为的。
阿里镜像点:阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区
华为镜像点:华为开源镜像站_软件开发服务_华为云
再次感谢!
下载内核源码后,下一步就是编译了。
如果你仍然基于X86平台搞这件事,那么你可以直接在主机环境下编译即可。很多时候,有这方面学习需求的开发者,是基于ARM平台的。所以,这里我们介绍一下ARM平台的方式。
将下载的Linux源码编译为ARM平台的目标文件,那么就需要ARM编译器。由因为主机环境是X86,所以,其实你需要的是一个交叉编译工具链。这个工具链,如果搞嵌入式开发,一般CPU厂家的SDK会提供,当然你也可以通过别的途径获取工具链,甚至是自己编译。自己编译的话,稍微复杂点,我们这里介绍一下采用Linaro工具链。
Linaro的官方网址:Arm Software Experts | Linaro
Linaro是ARM生态中的一环。致力于推动ARM软件生态的标准化,包括内核、工具链和安全相关。此举减少工程师在各个平台迁移的困难,加速创新。它是一个协作平台,跟成员公司和社区一起维护ARM软件生态系统。海思、谷歌、微软、三星等都是其会员。
通过其下载页面,我们可以下载到ARM平台的工具链:Downloads | GNU Arm Embedded Toolchain Downloads – Arm Developer
我们需要注意,当前是要在X86平台运行ARM指令,所以需要的是X86工具链。如果我们的主机环境就是ARM平台,则需要的是ARM工具链。比如麒麟这类系统,就是纯ARM的平台,所以,在其上对于ARM而言,就不再是交叉编译了。
这里我们选择X86平台包下载。
如果你不需要跟主机环境深入关联,只是测试调试用,那么只需找个地方解压就可以了。
之后,导出环境变量,编译前面下载的内核。
export PATH="/home/work/KernelStudy/GNU/gcc-arm-none-eabi-10.3-2021.10/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games"
- make ARCH=arm defconfig
-
- make bzImage -j4 ARCH=arm CROSS_COMPILE=arm-none-eabi-
此时,模拟器和内核都有了,我们就可以简单的调试看看效果。
- root@ubuntu:/home/work/KernelStudy/Qemu# qemu-system-arm -nographic -m 512M -M virt -kernel /home/work/KernelStudy/Kernel/linux-4.19.244/arch/arm/boot/zImage -append "init=/linuxrc root=/dev/mmcblk0p1 console=ttyAMA0 loglevel=8"
- [ 0.000000] Booting Linux on physical CPU 0x0
- [ 0.000000] Linux version 4.19.244 (root@ubuntu) (gcc version 10.3.1 20210824 (release) (GNU Arm Embedded Toolchain 10.3-2021.10)) #1 SMP PREEMPT Sat May 28 22:06:44 CST 2022
- [ 0.000000] CPU: ARMv7 Processor [412fc0f1] revision 1 (ARMv7), cr=10c5387d
- [ 0.000000] CPU: div instructions available: patching division code
- [ 0.000000] CPU: PIPT / VIPT nonaliasing data cache, PIPT instruction cache
- [ 0.000000] OF: fdt: Machine model: linux,dummy-virt
- [ 0.000000] Memory policy: Data cache writealloc
- [ 0.000000] efi: Getting EFI parameters from FDT:
- [ 0.000000] efi: UEFI not found.
- [ 0.000000] cma: Reserved 32 MiB at 0x5e000000
- [ 0.000000] On node 0 totalpages: 131072
- [ 0.000000] Normal zone: 1152 pages used for memmap
- [ 0.000000] Normal zone: 0 pages reserved
- [ 0.000000] Normal zone: 131072 pages, LIFO batch:31
- [ 0.000000] psci: probing for conduit method from DT.
- [ 0.000000] psci: PSCIv0.2 detected in firmware.
- [ 0.000000] psci: Using standard PSCI v0.2 function IDs

上面,我们只是用模拟器跑Linux内核,看看能否跑起来。许多参数都没有设置,可以看到,内核被引导起来。
到这里,说明基本的环境都OK了。严格来讲,我们就可以调试内核了。
为了在GDB起来之前,让内核等待连接,我们需要增加两个参数,-S和-s。前一个让内核等待,后一个让内核监听gdb连接。
root@ubuntu:/home/work/KernelStudy/Qemu# qemu-system-arm -nographic -s -S -m 512M -M virt -kernel /home/work/KernelStudy/Kernel/linux-4.19.244/arch/arm/boot/zImage -append "init=/linuxrc root=/dev/mmcblk0p1 console=ttyAMA0 loglevel=8"
这时候,另起一个终端,直接调试编译完成的vmlinux。这里我们使用前面下载的arm工具链里的gdb程序。
- root@ubuntu:/home/work/KernelStudy/Kernel/linux-4.19.244# arm-none-eabi-gdb ./vmlinux
- GNU gdb (GNU Arm Embedded Toolchain 10.3-2021.10) 10.2.90.20210621-git
- Copyright (C) 2021 Free Software Foundation, Inc.
- License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
- This is free software: you are free to change and redistribute it.
- There is NO WARRANTY, to the extent permitted by law.
- Type "show copying" and "show warranty" for details.
- This GDB was configured as "--host=x86_64-linux-gnu --target=arm-none-eabi".
- Type "show configuration" for configuration details.
- For bug reporting instructions, please see:
- <https://www.gnu.org/software/gdb/bugs/>.
- Find the GDB manual and other documentation resources online at:
- <http://www.gnu.org/software/gdb/documentation/>.
-
- For help, type "help".
- Type "apropos word" to search for commands related to "word"...
- Reading symbols from ./vmlinux...
- (gdb)

输入远程连接的地址和端口,并在start_kernel处设置断点
- (gdb) target remote 127.0.0.1:1234
- Remote debugging using 127.0.0.1:1234
- 0x40000000 in ?? ()
- (gdb) b start_kernel
- Breakpoint 1 at 0xc1000a84: file init/main.c, line 531.
- (gdb) c
- Continuing.
-
- Breakpoint 1, start_kernel () at init/main.c:531
- 531 {
- (gdb) bt
- #0 start_kernel () at init/main.c:531
- #1 0x00000000 in ?? ()
- Backtrace stopped: previous frame identical to this frame (corrupt stack?)
可以看到,内核在start_kernel地方停下来了。
好了,万事开头难。到此,我们已经开好头了,可以调试内核了。
但是,还有问题:
1 现在的内核启动是一过性的,如果我想在内核运行时调试内核该怎么办?
2 到目前我们只是编译了内核,并没有提供别的东西,比如根文件系统,内核启动参数中的append有什么用
3 内核在模拟器中运行,为啥gdb可以通过调试vmlinux的方式调试内核?
关于这几个问题,我们下一篇再论。读者也可以自己思考思考。今天就到这里。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。