杂项 – 使用qemu/gdb进行内核调试

杂项 – 使用qemu/gdb进行内核调试直接阅读源代码是一件很枯燥的事情,如果可以直接进行内核调试那么阅读源代码将事半功倍,很久以前的调试工作都是直接在机器上调试,多亏了虚拟化的发展,

大家好,欢迎来到IT知识分享网。

直接阅读源代码是一件很枯燥的事情,如果可以直接进行内核调试那么阅读源代码将事半功倍,很久以前的调试工作都是直接在机器上调试,多亏了虚拟化的发展,现在可以直接在模拟环境进行调试。在这里我们将学习如何使用 Qemu 模拟器建立一个可调式的内核环境。

  • 原文大部分来源于 medium.com/@daeseok.youn/prepare-the-environment-for-developing-linux-kernel-with-qemu-c55e37ba8ade

  • 使用的操作系统为Ubuntu

安装依赖包

我们需要使用 git 下载代码,用 Qemu 建立模拟环境,还有内核编译所依赖的包,首先我们安装git

$ sudo apt install git

然后,安装qemu

$ sudo apt install qemu qemu-system

安装内核编译所依赖的包

build-essential kernel-package fakeroot libncurses5-dev libssl-dev ccache flex bison libelf-dev

最后,安装gdb

sudo apt install gdb

下载内核源代码

在这里我们使用github上的源码

$ git clone https://hub.fastgit.org/torvalds/linux.git

找到你所感兴趣的分支

$ git checkout $branch

这样我们准备好。

编译内核

我通常将源代码和编译的结果放到同一个目录,您也可以自定义编译目录,内核编译需要准备一个配置文件,我通常会将运行的主机上的配置文件作为编译的配置文件,例如在ubuntu上

$ cp /boot/config-*-generic $kernel_source/

然后根据自己的需要重新选择需要配置的项目,这里我们使用 make ARCH=x86_64 menuconfig 图形化的界面进行配置

$ make ARCH=x86_64 menuconfig

然后会出现一个文本GUI界面,这个界面可以帮助我们进行内核的配置

杂项 - 使用qemu/gdb进行内核调试

通过键盘的上下左右移动光标,选中,”Kernel hacking”选项。然后通过使用”Enter“进入子菜单

杂项 - 使用qemu/gdb进行内核调试

然后进入”Compile-time checks and compiler options“子菜单,通过使用空格键选中”Compile the kernel with debug info“子菜单下的”Provide GDB scripts for kernel debugging“。

然后使用 Ext 键退出,根据提示保存我们的配置信息。

提供配置文件后,我们就可以编译我们自己的内核代码了

$ make -j8

-j 选项指定可以并行编译的数量,这个值最好和您的CPU核心数保持一致。

...OBJCOPY arch/x86/boot/setup.bin  BUILD   arch/x86/boot/bzImageSetup is 16124 bytes (padded to 16384 bytes).System is 8673 kBCRC f5ca994bKernel: arch/x86/boot/bzImage is ready  (#5)

出现这个信息表明我们的编译成功了。

建立Qemu模拟环境

得到上面的 bzImage 内核文件,就可以使用 Qemu 运行了

qemu-system-x86_64 -no-kvm -kernel arch/x86/boot/bzImage -hda /dev/zero -append "root=/dev/zero console=ttyS0" -serial stdio -display none

然后屏幕中会出现系统启动的信息

qemu-system-x86_64 -no-kvm -kernel arch/x86/boot/bzImage -hda /dev/zero -append "root=/dev/zero console=ttyS0" -serial stdio -display none[    0.000000] Linux version 5.3.0-next-20190920 (daeseok@AD01277334) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #5 SMP Thu Sep 26 19:29:00 KST 2019[    0.000000] Command line: root=/dev/zero console=ttyS0[    0.000000] x86/fpu: x87 FPU will use FXSAVE[    0.000000] BIOS-provided physical RAM map:[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved[    0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x0000000007fdffff] usable[    0.000000] BIOS-e820: [mem 0x0000000007fe0000-0x0000000007ffffff] reserved[    0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved[    0.000000] NX (Execute Disable) protection: active[    0.000000] SMBIOS 2.8 present.[    0.000000] DMI: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014[    0.000000] tsc: Fast TSC calibration using PIT[    0.000000] tsc: Detected 3599.980 MHz processor[    0.004163] last_pfn = 0x7fe0 max_arch_pfn = 0x400000000[    0.004784] x86/PAT: Configuration [0-7]: WB  WC  UC- UC  WB  WP  UC- WT[    0.013624] found SMP MP-table at [mem 0x000f6aa0-0x000f6aaf][    0.014744] check: Scanning 1 areas for low memory corruption[    0.017488] ACPI: Early table checksum verification disabled[    0.017710] ACPI: RSDP 0x00000000000F68C0 000014 (v00 BOCHS )[    0.017855] ACPI: RSDT 0x0000000007FE15FC 000030 (v01 BOCHS  BXPCRSDT 00000001 BXPC 00000001)[    0.018231] ACPI: FACP 0x0000000007FE1458 000074 (v01 BOCHS  BXPCFACP 00000001 BXPC 00000001)[    0.018638] ACPI: DSDT 0x0000000007FE0040 001418 (v01 BOCHS  BXPCDSDT 00000001 BXPC 00000001)[    0.018699] ACPI: FACS 0x0000000007FE0000 000040.....

但由于我们没有提供文件系统镜像,直接启动会导致系统无法直接启动,但对我们调试内核启动前的过程已经可以使用了。

使用GDB进行调试

现在我们可以使用安装的gdb进行调试了。qemu 提供了两个选项用于调试,-s 选项表明使用默认端口1234(tcp::1234)进行远程调试。-S 选项表示在没有gdb连接过来之前不启动系统。

qemu-system-x86_64 -s -S -no-kvm -kernel arch/x86/boot/bzImage -hda /dev/zero -append "root=/dev/zero console=ttyS0 nokaslr" -serial stdio -display none

在这里我们需要注意个是 nokaslr 这个内核启动参数,这个参数告诉内核不要使用内核地址随机化。为了保证安全,内核会将内核代码加载到一个按照指定规则随机的地址上,让非法侵入者无法直接定位内核代码位置,这就是内核地址随机化。默认内核启动随机化是打开的,和编译的代码指定的地址不一致,会导致gdb无法定位源代码位置,所以需要加上这个选项,关闭内核地址随机化。

使用另外一个终端连接gdb

$ cd $kernel_source$ gdb ./vmlinuzGNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-gitCopyright (C) 2018 Free Software Foundation, Inc.....(gdb)

vmlinuz 文件中包含gdb所需要的信息。target remote localhost:1234 进行远程调试

(gdb) target remote localhost:1234Remote debugging using localhost:12340x000000000000fff0 in entry_stack_storage ()(gdb) break start_kernelbreak start_kernelBreakpoint 1 at 0xffffffff829abb5b: file init/main.c, line 576.

这里我们设置了start_kernel断点,键入continue就可以触发系统运行

(gdb) continueContinuing.Breakpoint 1, start_kernel () at init/main.c:576576 {(gdb)

此时模拟的环境就挂在start_kernel断点上,此时就可以使用gdb的调试技术进行调试了。

(gdb) list571 {572  rest_init();573 }574575 asmlinkage __visible void __init start_kernel(void)576 {577  char *command_line;578  char *after_dashes;579580  set_task_stack_end_magic(&init_task);(gdb)

这里我们使用了list命令查看当前运行的源码。此时,我们直接执行continue继续让内核运行,由于内核中没有其他断点,qemu模拟环境由于没有找到根文件系统,此环境将异常挂起。如何让启动运行起来进行调试呢,那么,需要一个根文件系统。

下面我们就使用buildroot创建根文件系统。

使用buildroot创建根文件系统

很多系统都有自己建立根文件系统的方法,这里我们使用Buildroot。Buildroot可以帮助我们建立根文件系统,通常用于嵌入式系统,在这里我们就使用Buildroot建立根文件系统来帮助我们的调试。

首先下载代码

$ git clone git://git.buildroot.net/buildroot$ cd buildroot

第二部,和Linux内核一样,buildroot也提供了文本式的GUI配置界面

$ make menuconfig

和之前配置内核一样进行配置,通过”Target Options” → “Target Architecture”选择`x86_64`架构

杂项 - 使用qemu/gdb进行内核调试

在这里文件系统我们通过路径”Filesystem images” → “ext2/3/4 root file system”选择ext兼容的文件系统。选择ext4文件系统。

杂项 - 使用qemu/gdb进行内核调试

然后,进行构建

$ make -j8....ln -sf rootfs.ext2 /home/daeseok/work/buildroot/output/images/rootfs.ext4>>>   Generating filesystem image rootfs.tarmkdir -p /home/daeseok/work/buildroot/output/imagesrm -rf /home/daeseok/work/buildroot/output/build/buildroot-fs/tarmkdir -p /home/daeseok/work/buildroot/output/build/buildroot-fs/tarrsync -auH --exclude=/THIS_IS_NOT_YOUR_ROOT_FILESYSTEM /home/daeseok/work/buildroot/output/target/ /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/targetecho '#!/bin/sh' > /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/fakerootecho "set -e" >> /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/fakerootecho "chown -h -R 0:0 /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/target" >> /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/fakerootPATH="/home/daeseok/work/buildroot/output/host/bin:/home/daeseok/work/buildroot/output/host/sbin:/home/daeseok/.nvm/versions/node/v10.16.0/bin:/home/daeseok/.local/bin:/home/daeseok/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/daeseok/bin" /home/daeseok/work/buildroot/support/scripts/mkusers /home/daeseok/work/buildroot/output/build/buildroot-fs/full_users_table.txt /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/target >> /home/daeseok/work/buildroot/output/build/buildroot-fs/tar/fakeroot....

执行完后,将会在buildroot/output/images/目录中得到rootfs.ext4文件,这个文件可以作为qemu中的根文件系统。

调试完整的内核

已经得到了rootfs.ext4文件,然后将其作为根文件系统运行

$ cd /path/to/linux-next$ qemu-system-x86_64 -kernel arch/x86/boot/bzImage \-boot c -m 2049M -hda ../buildroot/output/images/rootfs.ext4 \-append "root=/dev/sda rw console=ttyS0,115200 acpi=off nokaslr" \-serial stdio -display none

运行好后,就可以看到登录界面了

[    0.000000] Linux version 5.3.0-next-20190920 (daeseok@AD01277334) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #5 SMP Thu Sep 26 19:29:00 KST 2019[    0.000000] Command line: root=/dev/sda rw console=ttyS0,115200 acpi=off nokaslr[    0.000000] x86/fpu: x87 FPU will use FXSAVE[    0.000000] BIOS-provided physical RAM map:....skip[    2.913761] EXT4-fs (sda): mounted filesystem with ordered data mode. Opts: (null)[    2.915210] Mounted ext4 file system at /root supports timestamps until 2038 (0x7fffffff)[    2.916011] VFS: Mounted root (ext4 filesystem) on device 8:0.[    2.921939] devtmpfs: mounted[    2.962863] Freeing unused kernel image memory: 1332K[    2.963173] Write protecting the kernel read-only data: 20480k[    2.965482] Freeing unused kernel image memory: 2004K[    2.966324] Freeing unused kernel image memory: 956K[    2.966546] Run /sbin/init as init process[    3.062589] EXT4-fs (sda): re-mounted. Opts: (null)[    3.062871] Mounted ext4 file system at / supports timestamps until 2038 (0x7fffffff)Starting syslogd: OKStarting klogd: OKRunning sysctl: [    3.357887] find (116) used greatest stack depth: 13976 bytes left[    3.363719] S02sysctl (115) used greatest stack depth: 13880 bytes leftOKInitializing random number generator... [    3.461757] random: dd: uninitialized urandom read (512 bytes read)done.Starting network: [    3.553592] ip (132) used greatest stack depth: 13800 bytes leftOKWelcome to Buildrootbuildroot login:

root是默认的用户,这个用户在这个环境下默认没有密码,所以只要输入root,键入“Enter”就可以进入系统

Welcome to Buildrootbuildroot login: root# mount/dev/root on / type ext4 (rw,relatime)devtmpfs on /dev type devtmpfs (rw,relatime,size=1015368k,nr_inodes=253842,mode=755)proc on /proc type proc (rw,relatime)devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=666)tmpfs on /dev/shm type tmpfs (rw,relatime,mode=777)tmpfs on /tmp type tmpfs (rw,relatime)tmpfs on /run type tmpfs (rw,nosuid,nodev,relatime,mode=755)sysfs on /sys type sysfs (rw,relatime)# pwd/root#

这样,我们就获得了一个使用我们自己编译的内核的完整模拟环境。下面,我们使用这个环境进行gdb调试吧。

首先,运行Qemu

$ qemu-system-x86_64 -s -kernel arch/x86/boot/bzImage -boot c -m 2049M -hda ../buildroot/output/images/rootfs.ext2 -append "root=/dev/sda rw console=ttyS0,115200 acpi=off nokaslr" -serial stdio -display none...Welcome to Buildrootbuildroot login:

注意的是这里我们没有使用-S选项,说明系统会直接运行,而不会等着gdb进行连接后再运行。这里只是打开了远程调试端口。

第二步,我们使用gdb连接运行的内核环境

$ gdb ./vmlinux...(gdb) target remote :1234Remote debugging using :1234default_idle () at arch/x86/kernel/process.c:581581  trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, smp_processor_id());

连接好后,就可以设置断点执行调试工作了

(gdb) b mm/page_alloc.c:4767Breakpoint 1 at 0xffffffff811a5f60: file mm/page_alloc.c, line 4767.(gdb) cContinuing.

例如,这里使用了源代码的位置设置断点,这个位置是get_free_pages的代码位置,所以也可以使用也可以 b get_free_pages 进行设置断点。从名字上我们知道和分配内存有关系,所以在qemu模拟环境中我们键入root登录系统,此时就会触发内存分配的过程。

Welcome to Buildrootbuildroot login: root <enter>

然后,我们就会触发这个断点

Breakpoint 1, __get_free_pages (gfp_mask=4197824, order=1) at mm/page_alloc.c:47674767  page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order);(gdb)list4762  */4763 unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)4764 {4765  struct page *page;47664767  page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order);4768  if (!page)4769   return 0;4770  return (unsigned long) page_address(page);4771 }

总结

内核代码很复杂,但现在我们比开创者而言,幸福多了,我们可以根据模拟器进行内核调试,而不是一行行的进行物理环境模拟调试。通过以上的步骤,现在我们建立一个一个完整的内核模拟环境,那么,开始调试吧!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/36516.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信