1. 概述 8 C- q; Z7 J5 [ W% R 在某些情况下,我们需要对于内核中的流程进行分析,虽然通过 BPF 的技术可以对于函数传入的参数和返回结果进行展示,但是在流程的调试上还是不如直接 GDB 单步调试来的直接。本文采用的编译方式如下,在一台 16 核 CentOS 7.7 的机器上进行内核源码相关的编译(主要是考虑编译效率),调试则是基于 VirtualBox 的 Ubuntu 20.04 系统中,采用 Qemu + GDB 进行单步调试,网上查看了很多文章,在最终进行单步跟踪的时候,始终不能够在断点处停止,进行过多次尝试和查询文档,最终发现需要在内核启动参数上添加 nokaslr ,本文是对整个搭建过程的总结。 2. Linux 内核编译和文件系统制作 Linux 内核编译编译内核和制作文件系统在 CentOS 7.7 的机器上。源码从国内清华的源下载:http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/, 此处选择 linux-4.19.172.tar.gz 版本。详细编译步骤如下: $ sudo yum group install "Development Tools"4 p+ l* B3 j9 y0 c& D8 v$ yum install ncurses-devel bison flex elfutils-libelf-devel openssl-devel $ wget http://ftp.sjtu.edu.cn/sites/ftp ... nux-4.19.172.tar.gz4 v+ c& u7 C9 `( ] $ tar xzvf linux-4.19.172.tar.gz R/ s, v3 J. W* v! z+ t $ cd linux-4.19.172/" U- }, Z' [ d1 x' E6 o; i $ make menuconfig. p: j( Y6 A3 P p* @) B% d: _ 在内核编译选项中,开启如下 “Compile the kernel with debug info”, 4.19.172 中默认已经选中: Kernel hacking —> Compile-time checks and compiler options —> [ ] Compile the kernel with debug info
以上配置完成后会在当前目录生成 .config 文件,我们可以使用 grep 进行验证: # grep CONFIG_DEBUG_INFO .config, A: \* k2 H+ `$ M% x6 QCONFIG_DEBUG_INFO=y 接着我们进行内核编译: $ nproc # 查看当前的系统核数$ make -j 12 # 或者采用 make bzImage 进行编译, -j N,表示使用多少核并行编译 # 未压缩的内核文件,这个在 gdb 的时候需要加载,用于读取 symbol 符号信息,由于包含调试信息所以比较大 $ ls -hl vmlinux& _5 l+ [; d7 W. b+ ` ^1 K o; w -rwxr-xr-x 1 root root 449M Feb 3 14:46 vmlinux9 L, z9 W$ M3 J1 L- X # 压缩后的镜像文件 $ ls -hl ./arch/x86_64/boot/bzImage- G! b/ c% ]7 h/ A lrwxrwxrwx 1 root root 22 Feb 3 14:47 ./arch/x86_64/boot/bzImage -> ../../x86/boot/bzImage% H) |# {4 R, D" P2 I' ?! J4 T$ C1 Q $ ls -hl ./arch/x86/boot/bzImage -rw-r--r-- 1 root root 7.6M Feb 3 14:47 ./arch/x86/boot/bzImage 不同发行版本下的内核的详细编译文档可以参考这里[1]。 启动内存文件系统制作# 首先安装静态依赖,否则会有报错,参见后续的排错章节$ yum install -y glibc-static.x86_64 -y $ wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2 $ tar -xvf busybox-1.32.1.tar.bz2 $ cd busybox-1.32.1/ % ?8 T" W2 B- ~& f# X$ R; Z $ make menuconfig # 安装完成后生成的相关文件会在 _install 目录下* E* Z$ s' O+ [! K $ make && make install $ cd _install3 ]- C0 r5 A5 C$ ? $ mkdir proc$ z2 a% V3 M/ p7 {0 o. @ $ mkdir sys $ touch init # init 内容见后续章节,为内核启动的初始化程序 $ vim init . O# q* l* q; t; Z! k # 必须设置成可执行文件 $ chmod +x init2 W. h) `4 p% e2 _* j& h; r. j1 B 5 O- m1 z" x& D! c' i0 |, f1 g: B $ find . | cpio -o --format=newc > ./rootfs.img cpio: File ./rootfs.img grew, 2758144 new bytes not copied0 |4 |0 T) M+ K/ D Y Z! c 10777 blocks ( _3 H- I2 }% t, y+ S1 }5 a $ ls -hl rootfs.img -rw-r--r-- 1 root root 5.3M Feb 2 11:23 rootfs.img1 D( J! c* y& h2 N1 ~ 其中上述的 init 文件内容如下,打印启动日志和系统的整个启动过程花费的时间: #!/bin/sh# c1 @8 T; ? C5 D% M! n+ becho "{==DBG==} INIT SCRIPT" mkdir /tmp; T0 R! j3 e9 v1 A mount -t proc none /proc8 u- f9 k4 i' s mount -t sysfs none /sys+ N* Z( g2 A5 Z0 K9 P9 ]( n I mount -t debugfs none /sys/kernel/debug: D$ E8 D/ Q8 U3 R, p/ {' ] mount -t tmpfs none /tmp mdev -s echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds" ; u$ _- ~& E$ Q) v; l4 Y6 w5 m # normal user setsid /bin/cttyhack setuidgid 1000 /bin/sh 到此为止我们已经编译了好了 Linux 内核(vmlinux 和 bzImage)和启动的内存文件系统(rootfs.img)。 错误排查在编译过程中出现以下报错: /bin/ld: cannot find -lcrypt" x5 C: ~# W# Y1 u% F# y* h3 r/bin/ld: cannot find -lm7 t$ q, Y) `0 @2 K' M /bin/ld: cannot find -lresolv9 u4 K O' f$ l, |: k# m+ I4 N /bin/ld: cannot find -lrt& t! L3 B7 X6 @5 S& ~" m' M+ F/ y collect2: error: ld returned 1 exit status5 u: [ g0 u/ O% y. f Note: if build needs additional libraries, put them in CONFIG_EXTRA_LDLIBS. Example: CONFIG_EXTRA_LDLIBS="pthread dl tirpc audit pam" 出错的原因是因为我们采用静态编译依赖的底层库没有安装,如果不清楚这些库有哪些 rpm 安装包提供,则可以通过 yum provides 命令查看,然后安装相关依赖包重新编译即可。 $ yum provides */libm.a# P3 x! n0 ?) I2 R9 C8 K6 n// ...$ @8 Z+ W. y4 C0 g" Y) v3 K& ?; i- U glibc-static-2.17-317.el7.x86_64 : C library static libraries for -static linking. Repo : base, S& }) ~* y* J1 A: X Matched from: Filename : /usr/lib64/libm.a 3. Qemu 启动内核 在上述步骤准备好以后,我们需要在调试的 Ubuntu 20.04 的系统中安装 Qemu 工具,其中调测的 Ubuntu 系统使用 VirtualBox 安装。 $ apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils- Y& @" X4 X: B$ K# E, h把上述编译好的 vmlinux、bzImage、rootfs.img 和编译的源码拷贝到我们当前 Unbuntu 机器中。 拷贝 Linux 编译的源码主要是在 gdb 的调试过程中查看源码,其中 vmlinux 和 linux 源码处于相同的目录,本例中 vmlinux 位于 linux-4.19.172 源目录中。 $ qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.img -append "nokaslr console=ttyS0" -s -S -nographic使用上述命令启动调试,启动后会停止在界面处,并等待远程 gdb 进行调试,在使用 GDB 调试之前,可以先使用以下命令进程测试内核启动是否正常。 $ qemu-system-x86_64 -kernel ./bzImage -initrd ./rootfs.img -append "nokaslr console=ttyS0" -nographic- v4 u- t; n1 @: m6 ^8 c k9 z其中命令行中各参数如下:
4. GDB 调试 在使用 qemu-system-x86_64 命令启动内核以后,进入到我们从编译机器上拷贝过来的 Linux 内核源代码目录中,在另外一个终端我们来启动 gdb 命令: [linux-4.19.172]$ gdb(gdb) file vmlinux # vmlinux 位于目录 linux-4.19.172 中 (gdb) target remote :1234' i/ _# @3 Z8 s: B. }, I (gdb) break start_kernel # 有些文档建议使用 hb 硬件断点,我在本地测试使用 break 也是 ok 的$ w2 Z3 R8 I& X, `; O; E (gdb) c # 启动调试,则内核会停止在 start_kernel 函数处! G' `( V5 C* p9 ]6 S 整体运行界面如下: 5. Eclipse 图像化调试 我们可以通过 eclipse-cdt 进行可视化项目调试。 ”File“ -> “New” -> “Project” ,然后选择 ”Makefile Project with Existing Code“ 选项,后续按照向导导入代码。
在 “Run” -> “Debug Configurations” 选项中,创建一个 ”C/C++ Attach to Application“ 的调试选项。
启动 Debug 调试,即可看到与 gdb 类似的窗口。
启动 ”Debug“ 调试以后的窗口如下,在 Debug 窗口栏中,设置与 gdb 调试相同的步骤即可。 |