你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

采用 Qemu + GDB 进行单步调试Linux 内核

[复制链接]
gaosmile 发布时间:2021-3-1 14:54
1. 概述/ L2 ?0 H  F0 N2 m4 ?  Q
/ H$ q5 d* P' H3 W9 F8 y

在某些情况下,我们需要对于内核中的流程进行分析,虽然通过 BPF 的技术可以对于函数传入的参数和返回结果进行展示,但是在流程的调试上还是不如直接 GDB 单步调试来的直接。本文采用的编译方式如下,在一台 16 核 CentOS 7.7 的机器上进行内核源码相关的编译(主要是考虑编译效率),调试则是基于 VirtualBox 的 Ubuntu 20.04 系统中,采用 Qemu + GDB 进行单步调试,网上查看了很多文章,在最终进行单步跟踪的时候,始终不能够在断点处停止,进行过多次尝试和查询文档,最终发现需要在内核启动参数上添加 nokaslr ,本文是对整个搭建过程的总结。

微信图片_20210301145151.png
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"
+ h. o: G7 ?) x: _$ yum install ncurses-devel bison flex elfutils-libelf-devel openssl-devel
8 \* H. M) S( h1 |) C6 Q& J, q( V% G. N0 ]/ D8 U
$ wget http://ftp.sjtu.edu.cn/sites/ftp ... nux-4.19.172.tar.gz6 Q9 e; A/ o8 H( F
$ tar xzvf linux-4.19.172.tar.gz: S  p3 e5 e  f4 _2 w: b
$ cd linux-4.19.172/
; }1 c1 C9 Y% v) m2 e8 `5 J$ make menuconfig$ L) L7 s0 R& G+ {' ?

在内核编译选项中,开启如下 “Compile the kernel with debug info”, 4.19.172 中默认已经选中:

Kernel hacking —> Compile-time checks and compiler options —> [ ] Compile the kernel with debug info

微信图片_20210301145155.png

以上配置完成后会在当前目录生成 .config 文件,我们可以使用 grep 进行验证:

# grep CONFIG_DEBUG_INFO .config" n+ Q/ G4 u  ?3 t9 y
CONFIG_DEBUG_INFO=y
% R$ H/ i* f) C6 h6 R7 o

接着我们进行内核编译:

$ nproc       # 查看当前的系统核数
6 H9 y; ]! ?4 R7 O4 g: G( n$ make -j 12  # 或者采用 make bzImage 进行编译, -j N,表示使用多少核并行编译, ]$ y: v# }* x7 \
0 l# T4 |9 R$ v4 S) q7 p- c$ ?
# 未压缩的内核文件,这个在 gdb 的时候需要加载,用于读取 symbol 符号信息,由于包含调试信息所以比较大
3 y% ?- c5 o! N" F' B. Y$ I$ ls -hl vmlinux
( Z; r5 T; {6 _6 t4 J-rwxr-xr-x 1 root root 449M Feb  3 14:46 vmlinux
0 h- H, x, h% ~& m( g/ a" q: T$ \9 U; M4 p( M) M4 D
# 压缩后的镜像文件
8 J6 c0 H4 R- u: l& a$ ls -hl ./arch/x86_64/boot/bzImage
" V8 k3 R, ]4 Q# l" N) l& }lrwxrwxrwx 1 root root 22 Feb  3 14:47 ./arch/x86_64/boot/bzImage -> ../../x86/boot/bzImage
% n* T8 L! ?2 m- X7 i. z+ y6 I4 I' h1 @% p9 b# V
$ ls -hl ./arch/x86/boot/bzImage
6 s+ q' ~2 _. W/ m$ R-rw-r--r-- 1 root root 7.6M Feb  3 14:47 ./arch/x86/boot/bzImage
5 l$ }7 A8 S. d. z

不同发行版本下的内核的详细编译文档可以参考这里[1]。

启动内存文件系统制作# 首先安装静态依赖,否则会有报错,参见后续的排错章节$ ]& B0 M, h( W% U/ @9 J9 ^) _
$ yum install -y glibc-static.x86_64 -y2 l$ }8 z$ _) z; A

8 e1 T; L7 j( y2 B/ J. V$ s0 d$ wget https://busybox.net/downloads/busybox-1.32.1.tar.bz2
# y' X, K! c# Z$ tar -xvf busybox-1.32.1.tar.bz21 B; R4 f3 S- k# u
$ cd busybox-1.32.1/5 @% f* Q" q7 S* |
( l3 x, O) s: D0 L% r2 x8 o
$ make menuconfig
; W) H, A7 O" h1 B+ _
$ ^# J  u6 s! p; }  J# 安装完成后生成的相关文件会在 _install 目录下
$ G4 T3 p# i& F- q4 M& R( R$ make && make install
8 I( X3 }. W$ {5 G* s) K; W3 w! \0 c: F9 e; r2 _) E
$ cd _install
6 u1 S* s' [1 H1 ?  v4 U' Z# }$ mkdir proc
9 n& N+ f* g/ [8 ], ~$ mkdir sys3 e) u8 z3 D# Q# `3 Q
$ touch init
5 J& L9 C  q8 C$ ~% f" K$ ?7 D7 R; U! l1 \
#  init 内容见后续章节,为内核启动的初始化程序: v* P* g6 l: T$ u2 f$ w1 Z
$ vim init
2 N* _# e+ Q& R; ]7 E4 K+ Q- I' B- V- j2 q2 M
# 必须设置成可执行文件/ i8 I: {& |( F& `
$ chmod +x init- w3 [# m' @7 p( F" f' E$ \

( o8 h2 L' R9 w, J- a" Y$ find . | cpio -o --format=newc > ./rootfs.img
- V. i; H$ D) U; s8 g8 z% f2 kcpio: File ./rootfs.img grew, 2758144 new bytes not copied
9 @+ i5 [0 O5 p4 b) _! Q2 |0 A10777 blocks
3 S, J$ ]; _& j1 y
) ~6 ~4 w1 R, t7 |2 @# N$ ls -hl rootfs.img9 U7 f# h" Y/ p7 c. x% l
-rw-r--r-- 1 root root 5.3M Feb  2 11:23 rootfs.img
/ @6 T6 L& E2 m

其中上述的 init 文件内容如下,打印启动日志和系统的整个启动过程花费的时间:

#!/bin/sh3 o  L4 v* _7 s9 d7 x6 o
echo "{==DBG==} INIT SCRIPT"
7 P7 T; v; Y" @7 q8 M% Q7 n8 H2 M9 Cmkdir /tmp, P# `4 \0 S& s6 U9 c9 `! u; n! T
mount -t proc none /proc
) I6 p1 s* a3 b( x9 rmount -t sysfs none /sys3 W- ]' [2 ~, F
mount -t debugfs none /sys/kernel/debug# r1 s8 P1 F' t/ ~2 T/ K
mount -t tmpfs none /tmp
( `! @& Z' q! P4 |& D- J
3 F% w! x, m8 y; Zmdev -s/ C2 j  a  W! R
echo -e "{==DBG==} Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
3 ?$ y% ]' o3 Y5 y2 a/ P, ^: R: D3 o# Q1 a; {# w7 i) m" s) J
# normal user, Q& b9 l# D. G0 D7 O
setsid /bin/cttyhack setuidgid 1000 /bin/sh
" O- U0 T' _8 i

到此为止我们已经编译了好了 Linux 内核(vmlinux 和 bzImage)和启动的内存文件系统(rootfs.img)。

错误排查

在编译过程中出现以下报错:

/bin/ld: cannot find -lcrypt$ M5 H# l1 `7 L/ ^
/bin/ld: cannot find -lm# E+ v  }% G6 `7 @  ]& a8 O; H
/bin/ld: cannot find -lresolv  P( D7 {) }; ?5 U/ y3 b
/bin/ld: cannot find -lrt8 {* O7 i3 m3 I( k5 [7 g) M4 U
collect2: error: ld returned 1 exit status7 x! ]( A9 O; y/ `! z
Note: if build needs additional libraries, put them in CONFIG_EXTRA_LDLIBS.
" U! n7 T$ ~' o+ O- a2 LExample: CONFIG_EXTRA_LDLIBS="pthread dl tirpc audit pam"! X& e1 g* l" |

出错的原因是因为我们采用静态编译依赖的底层库没有安装,如果不清楚这些库有哪些 rpm 安装包提供,则可以通过 yum provides 命令查看,然后安装相关依赖包重新编译即可。

$ yum provides */libm.a
  \" f1 B) C9 x7 v8 ~, Z% q// .... w( X& X; E5 ?0 k  I, J! Q8 h8 z3 d
glibc-static-2.17-317.el7.x86_64 : C library static libraries for -static linking.9 ]4 {- `# r- C5 T6 ~
Repo        : base
( `- c' R6 b7 @Matched from:8 ?0 W8 |, F4 C% |- z
Filename    : /usr/lib64/libm.a
$ l0 E& z9 C2 e
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* \3 k1 y& p& V

把上述编译好的 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
" k9 q  p* e) ^$ |* d

使用上述命令启动调试,启动后会停止在界面处,并等待远程 gdb 进行调试,在使用 GDB 调试之前,可以先使用以下命令进程测试内核启动是否正常。

$ qemu-system-x86_64 -kernel ./bzImage -initrd  ./rootfs.img -append "nokaslr console=ttyS0" -nographic  a( ^, }3 m1 G" C& W2 \

其中命令行中各参数如下:

  • -kernel ./bzImage:指定启用的内核镜像;
  • -initrd ./rootfs.img:指定启动的内存文件系统;
  • -append "nokaslr console=ttyS0" :附加参数,其中 nokaslr 参数必须添加进来,防止内核起始地址随机化,这样会导致 gdb 断点不能命中;参数说明可以参见这里[2]。
  • -s :监听在 gdb 1234 端口;
  • -S :表示启动后就挂起,等待 gdb 连接;
  • -nographic:不启动图形界面,调试信息输出到终端与参数 console=ttyS0 组合使用;; b1 }. U5 q/ u% h* r
微信图片_20210301145158.png
4. GDB 调试

在使用 qemu-system-x86_64 命令启动内核以后,进入到我们从编译机器上拷贝过来的 Linux 内核源代码目录中,在另外一个终端我们来启动 gdb 命令:

[linux-4.19.172]$ gdb: C$ K$ W& k4 C% g1 _' w0 X9 B0 z
(gdb) file vmlinux           # vmlinux 位于目录 linux-4.19.172 中
! l8 g: E; x" j# S(gdb) target remote :1234
, Z" p. P' f6 u& d$ O1 ](gdb) break start_kernel     # 有些文档建议使用 hb 硬件断点,我在本地测试使用 break 也是 ok 的2 Z! Q! t: S* x  V
(gdb) c             # 启动调试,则内核会停止在 start_kernel 函数处
6 f4 k4 c$ ~1 b: c

整体运行界面如下:

微信图片_20210301145201.png
5. Eclipse 图像化调试

我们可以通过 eclipse-cdt 进行可视化项目调试。

”File“ -> “New” -> “Project” ,然后选择 ”Makefile Project with Existing Code“ 选项,后续按照向导导入代码。

微信图片_20210301145203.png

在 “Run” -> “Debug Configurations” 选项中,创建一个 ”C/C++ Attach to Application“ 的调试选项。

  • Project:选择我们刚才创建的项目名字;
  • C/C++ Application:选择编译 Linux 内核带符号信息表的 vmlinux;
  • Build before launching:选择 ”Disable auto build“;
  • Debugger:选择 gdbserver,具体设置如下图;
  • 在 Debugger 中的 Connection 信息中选择 ”TCP“,并填写端口为 ”1234“;
    + J/ P7 J) a5 A9 [8 n/ I* E

启动 Debug 调试,即可看到与 gdb 类似的窗口。

微信图片_20210301145206.png

启动 ”Debug“ 调试以后的窗口如下,在 Debug 窗口栏中,设置与 gdb 调试相同的步骤即可。

微信图片_20210301145209.png / H' T8 |+ P! n  V4 n4 `

$ Q, s- w' M7 ?) o0 M
赞 收藏 评论0 发布时间:2021-3-1 14:54

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版