概述
在个别情况下,我们须要对于内核中的流程进行剖析,即使通过BPF的技术可以对于函数传入的参数和返回结果进行展示linux内核编译步骤,并且在流程的调试上还是不如直接GDB单步调试来得直接。本文采用的编译形式如下,在一台16核CentOS7.7的机器上进行内核源码相关的编译(主要是考虑编译效率),调试则是基于VirtualBox的Ubuntu20.04系统中,采用Qemu+GDB进行单步调试,网上查看了好多文章,在最终进行单步跟踪的时侯linux认证,仍然不能否在断点处停止,进行过多次尝试和查询文档,最终发觉须要在内核启动参数上添加nokaslr,本文是对整个搭建过程的总结。
嵌入式进阶教程分门别类整理好了,看的时侯非常便捷,因为内容较多,这儿就截取一部份图吧。
须要的同事私信【内核】即可申领。
内核学习地址:Linux内核源码/显存调优/文件系统/进程管理/设备驱动/网路合同栈-学习视频教程-腾讯课堂
2.Linux内核编译和文件系统制做Linux内核编译
编译内核和制做文件系统在运行CentOS7.7的机器上。源码从国外北大的源下载:,此处选择linux-4.19.172.tar.gz版本。详尽编译步骤如下:
$ sudo yum group install "Development Tools"
$ yum install ncurses-devel bison flex elfutils-libelf-devel openssl-devel
$ wget http://ftp.sjtu.edu.cn/sites/ftp.kernel.org/pub/linux/kernel/v4.x/linux-4.19.172.tar.gz
$ tar xzvf linux-4.19.172.tar.gz
$ cd linux-4.19.172/
$ make menuconfig
在内核编译选项中,开启如下“Compilethekernelwithdebuginfo”,4.19.172中默认早已选中:
Kernelhacking—>Compile-timechecksandcompileroptions—>[]Compilethekernelwithdebuginfo
以上配置完成后会在当前目录生成.config文件,我们可以使用grep进行验证:
# grep CONFIG_DEBUG_INFO .config
CONFIG_DEBUG_INFO=y
接着我们进行内核编译:
$ nproc # 查看当前的系统核数
$ make -j 12 # 或者采用 make bzImage 进行编译, -j N,表示使用多少核并行编译
# 未压缩的内核文件,这个在 gdb 的时候需要加载,用于读取 symbol 符号信息,由于包含调试信息所以比较大
$ ls -hl vmlinux
-rwxr-xr-x 1 root root 449M Feb 3 14:46 vmlinux
# 压缩后的镜像文件
$ ls -hl ./arch/x86_64/boot/bzImage
lrwxrwxrwx 1 root root 22 Feb 3 14:47 ./arch/x86_64/boot/bzImage -> ../../x86/boot/bzImage
$ ls -hl ./arch/x86/boot/bzImage
-rw-r--r-- 1 root root 7.6M Feb 3 14:47 ./arch/x86/boot/bzImage
启动显存文件系统制做
# 首先安装静态依赖,否则会有报错,参见后续的排错章节
$ 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/
$ make menuconfig
# 安装完成后生成的相关文件会在 _install 目录下
$ make && make install
$ cd _install
$ mkdir proc
$ mkdir sys
$ touch init
# init 内容见后续章节,为内核启动的初始化程序
$ vim init
# 必须设置成可执行文件
$ chmod +x init
$ find . | cpio -o --format=newc > ./rootfs.img
cpio: File ./rootfs.img grew, 2758144 new bytes not copied
10777 blocks
$ ls -hl rootfs.img
-rw-r--r-- 1 root root 5.3M Feb 2 11:23 rootfs.img
其中上述的init文件内容如下,复印启动日志和系统的整个启动过程耗费的时间:
#!/bin/sh
echo "{==DBG==} INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
mdev -s
echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
# normal user
setsid /bin/cttyhack setuidgid 1000 /bin/sh
到此为止我们早已编译了好了Linux内核(vmlinux和bzImage)和启动的显存文件系统(rootfs.img)。
错误排查
在编译过程中出现以下报错:
/bin/ld: cannot find -lcrypt
/bin/ld: cannot find -lm
/bin/ld: cannot find -lresolv
/bin/ld: cannot find -lrt
collect2: error: ld returned 1 exit status
Note: if build needs additional libraries, put them in CONFIG_EXTRA_LDLIBS.
Example: CONFIG_EXTRA_LDLIBS="pthread dl tirpc audit pam"
出错的缘由是由于我们采用静态编译依赖的底层库没有安装查看系统版本linux,假若不清楚这种库有什么rpm安装包提供,则可以通过yumprovides命令查看,之后安装相关依赖包重新编译即可。
$ yum provides */libm.a
// ...
glibc-static-2.17-317.el7.x86_64 : C library static libraries for -static linking.
Repo : base
Matched from:
Filename : /usr/lib64/libm.a
3.Qemu启动内核
在上述步骤打算好之后linux内核编译步骤,我们须要在调试的Ubuntu20.04的系统中安装Qemu工具,其中调测的Ubuntu系统使用VirtualBox安装。
$ apt install qemu qemu-utils qemu-kvm virt-manager libvirt-daemon-system libvirt-clients bridge-utils
把上述编译好的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
其中命令行中各参数如下:
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
(gdb) break start_kernel # 有些文档建议使用 hb 硬件断点,我在本地测试使用 break 也是 ok 的
(gdb) c # 启动调试,则内核会停止在 start_kernel 函数处
整体运行界面如下:
5.Eclipse图象化调试
我们可以通过eclipse-cdt进行可视化项目调试。
”File“->“New”->“Project”,之后选择”MakefileProjectwithExistingCode“选项,后续根据向导导出代码。
在“Run”->“DebugConfigurations”选项中,创建一个”C/C++AttachtoApplication“的调试选项。
启动Debug调试,即可看见与gdb类似的窗口。
启动”Debug“调试之后的窗口如下,在Debug窗口栏中,设置与gdb调试相同的步骤即可。