四、GDB实战
下面是一个使用了上述命令的实战例子:
[root@www.linuxidc.com bufbomb]# gdb bufbomb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6) Copyright (C) 2010 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 "x86_64-RedHat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /root/Temp/bufbomb/bufbomb...done. (gdb) b getbuf Breakpoint 1 at 0x8048ad6 (gdb) run -t cdai Starting program: /root/Temp/bufbomb/bufbomb -t cdai Team: cdai Cookie: 0x5e5ee04e Breakpoint 1, 0x08048ad6 in getbuf () Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686 (gdb) bt #0 0x08048ad6 in getbuf () #1 0x08048db2 in test () #2 0x08049085 in launch () #3 0x08049257 in main () (gdb) info frame 0 Stack frame at 0xffffb540: eip = 0x8048ad6 in getbuf; saved eip 0x8048db2 called by frame at 0xffffb560 Arglist at 0xffffb538, args: Locals at 0xffffb538, Previous frame's sp is 0xffffb540 Saved registers: ebp at 0xffffb538, eip at 0xffffb53c (gdb) info registers eax 0xc 12 ecx 0xffffb548 -19128 edx 0xc8c340 13157184 ebx 0x0 0 esp 0xffffb510 0xffffb510 ebp 0xffffb538 0xffffb538 esi 0x804b018 134524952 edi 0xffffffff -1 eip 0x8048ad6 0x8048ad6 <getbuf+6> eflags 0x282 [ SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) x/10x $sp 0xffffb510: 0xf7ffc6b0 0x00000001 0x00000001 0xffffb564 0xffffb520: 0x08048448 0x0804a12c 0xffffb548 0x00c8aff4 0xffffb530: 0x0804b018 0xffffffff (gdb) si 0x08048ad9 in getbuf () (gdb) si 0x08048adc in getbuf () (gdb) si 0x080489c0 in Gets () (gdb) n Single stepping until exit from function Gets, which has no line number information. Type string:123 0x08048ae1 in getbuf () (gdb) si 0x08048ae2 in getbuf () (gdb) c Continuing. Dud: getbuf returned 0x1 Better luck next time Program exited normally. (gdb) quit
4.1逆向调试
GDB 7.0后加入了Reversal Debugging功能。具体来说,比如我在getbuf()和main()上设置了断点,当启动程序时会停在main()函数的断点上。此时敲入record后continue到下一断点getbuf(),GDB就会记录从main()到getbuf()的运行时信息。现在用rn就可以逆向地从getbuf()调试到main()。就像《X战警:逆转未来》里一样,挺神奇吧!
这种方式适合从bug处反向去找引起bug的代码,实用性因情况而异。当然,它也是有局限性的。像程序假如有I/O输出等外部条件改变时,GDB是没法“逆转”的。
[root@www.linuxidc.com bufbomb]# gdb bufbomb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6) Copyright (C) 2010 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 "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /root/Temp/bufbomb/bufbomb...done. (gdb) b getbuf Breakpoint 1 at 0x8048ad6 (gdb) b main Breakpoint 2 at 0x80490c6 (gdb) run -t cdai The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/Temp/bufbomb/bufbomb -t cdai Breakpoint 2, 0x080490c6 in main () (gdb) record (gdb) c Continuing. Team: cdai Cookie: 0x5e5ee04e Breakpoint 1, 0x08048ad6 in getbuf () (gdb) rn Single stepping until exit from function getbuf, which has no line number information. 0x08048dad in test () (gdb) rn Single stepping until exit from function test, which has no line number information. 0x08049080 in launch () (gdb) rn Single stepping until exit from function launch, which has no line number information. 0x08049252 in main ()
4.2VSCode+GDB+Qemu调试ARM64 linux内核
linux kernel是一个非常复杂的系统,初学者会很难入门。如果有一个方便的调试环境,学习效率至少能有5-10倍的提升。
为了学习linux内核,通常有这两个需要:
- 可以摆脱硬件,方便的编译和运行linux
- 可以使用图形化的工具来调试linux
笔者使用VSCode+GDB+Qemu完成了这两个需求:
- qemu作为虚拟机,用来启动linux。
- VSCode+GDB作为调试工具,用来图形化地DEBUG。
最终效果大致如下:
qemu运行界面:
vscode调试界面:
下面将一步一步介绍如何搭建上述环境。本文所有操作都在Vmware Ubuntu16虚拟机上进行。
安装编译工具链
由于Ubuntu是X86架构,为了编译arm64的文件,需要安装交叉编译工具链
sudo apt-get install gcc-aarch64-linux-gnu sudo apt-get install libncurses5-dev build-essential git bison flex libssl-dev
制作根文件系统
linux的启动需要配合根文件系统,这里我们利用busybox来制作一个简单的根文件系统
编译busybox
wget https://busybox.net/downloads/busybox-1.33.1.tar.bz2 tar -xjf busybox-1.33.1.tar.bz2 cd busybox-1.33.1
打开静态库编译选项
make menuconfig Settings ---> [*] Build static binary (no shared libs)
指定编译工具
export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu-
编译
make make install
编译完成,在busybox目录下生成_install目录
定制文件系统
为了init进程能正常启动, 需要再额外进行一些配置
根目录添加etc、dev和lib目录
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17] $ mkdir etc dev lib # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17] $ ls bin dev etc lib linuxrc sbin usr
在etc分别创建文件:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13] $ cat profile #!/bin/sh export HOSTNAME=bryant export USER=root export HOME=/home export PS1="[$USER@$HOSTNAME \W]\# " PATH=/bin:/sbin:/usr/bin:/usr/sbin LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH export PATH LD_LIBRARY_PATH # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16] $ cat inittab ::sysinit:/etc/init.d/rcS ::respawn:-/bin/sh ::askfirst:-/bin/sh ::ctrlaltdel:/bin/umount -a -r # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19] $ cat fstab #device mount-point type options dump fsck order proc /proc proc defaults 0 0 tmpfs /tmp tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 tmpfs /dev tmpfs defaults 0 0 debugfs /sys/kernel/debug debugfs defaults 0 0 kmod_mount /mnt 9p trans=virtio 0 0 # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26] $ ls init.d rcS # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30] $ cat init.d/rcS mkdir -p /sys mkdir -p /tmp mkdir -p /proc mkdir -p /mnt /bin/mount -a mkdir -p /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s
这里对这几个文件做一点说明:
- busybox 作为linuxrc启动后, 会读取/etc/profile, 这里面设置了一些环境变量和shell的属性
- 根据/etc/fstab提供的挂载信息, 进行文件系统的挂载
- busybox 会从 /etc/inittab中读取sysinit并执行, 这里sysinit指向了/etc/init.d/rcS
- /etc/init.d/rcS 中 ,mdev -s 这条命令很重要, 它会扫描/sys目录,查找字符设备和块设备,并在/dev下mknod
dev目录:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/dev [1:17:36] $ sudo mknod console c 5 1
这一步很重要, 没有console这个文件, 用户态的输出没法打印到串口上
lib目录:拷贝lib库,支持动态编译的应用程序运行:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/lib [1:18:43] $ cp /usr/aarch64-linux-gnu/lib/*.so* -a .
编译内核
配置内核
linux内核源码可以在github上直接下载。
根据arch/arm64/configs/defconfig 文件生成.config
make defconfig ARCH=arm64
将下面的配置加入.config文件中
CONFIG_DEBUG_INFO=y CONFIG_INITRAMFS_SOURCE="./root" CONFIG_INITRAMFS_ROOT_UID=0 CONFIG_INITRAMFS_ROOT_GID=0
CONFIG_DEBUG_INFO是为了方便调试
CONFIG_INITRAMFS_SOURCE是指定kernel ramdisk的位置,这样指定之后ramdisk会直接被编译到kernel 镜像中。
我们将之前制作好的根文件系统cp到root目录下:
# bryant @ ubuntu in ~/Downloads/linux-arm64 on git:main x [1:26:56] $ cp -r ../busybox-1.33.1/_install root
执行编译
make ARCH=arm64 Image -j8 CROSS_COMPILE=aarch64-linux-gnu-
这里指定target为Image 会只编译kernel, 不会编译modules, 这样会增加编译速度
启动qemu
下载qemu
需要注意的,qemu最好源码编译, 用apt-get直接安装的qemu可能版本过低,导致无法启动arm64内核。笔者是使用4.2.1版本的qemu
apt-get install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv wget https://download.qemu.org/qemu-4.2.1.tar.xz tar xvJf qemu-4.2.1.tar.xz cd qemu-4.2.1 ./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm make sudo make install
编译完成之后,qemu在 /usr/local/bin目录下
$ /usr/local/bin/qemu-system-aarch64 --version QEMU emulator version 4.2.1 Copyright (c) 2003-2019 Fabrice Bellard and the QEMU Project developers
启动linux内核
/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel
这里对于参数做一些解释:
-m 512M
内存为512M-smp 4
4核-cpu cortex-a57
cpu 为cortex-a57-kernel
kernel镜像文件-append
传给kernel 的cmdline参数。其中rdinit指定了init进程;nokaslr 禁止内核起始地址随机化,这个很重要, 否则GDB调试可能有问题;console=ttyAMA0指定了串口,没有这一步就看不到linux的输出;-nographic
禁止图形输出-s
监听gdb端口, gdb程序可以通过1234这个端口连上来。
这里说明一下console=ttyAMA0是怎么生效的。
查看linux源码可知ttyAMA0对应的是AMBA_PL011
这个驱动:
config SERIAL_AMBA_PL011_CONSOLE bool "Support for console on AMBA serial port" depends on SERIAL_AMBA_PL011=y select SERIAL_CORE_CONSOLE select SERIAL_EARLYCON help Say Y here if you wish to use an AMBA PrimeCell UART as the system console (the system console is the device which receives all kernel messages and warnings and which allows logins in single user mode). Even if you say Y here, the currently visible framebuffer console (/dev/tty0) will still be used as the system console by default, but you can alter that using a kernel command line option such as "console=ttyAMA0". (Try "man bootparam" or see the documentation of your boot loader (lilo or loadlin) about how to pass options to the kernel at boot time.)
AMBA_PL011是arm的一个标准串口设备, qemu 的输出就是模拟的这个串口。
在qemu的源码文件中,也可以看到PL011的相关文件:
# bryant @ ubuntu in ~/Downloads/qemu-4.2.1 [1:46:54] $ find . -name "*pl011*" ./hw/char/pl011.c
成功启动Linux后, 串口打印如下:
[ 3.401567] usbcore: registered new interface driver usbhid [ 3.404445] usbhid: USB HID core driver [ 3.425030] NET: Registered protocol family 17 [ 3.429743] 9pnet: Installing 9P2000 support [ 3.435439] Key type dns_resolver registered [ 3.440299] registered taskstats version 1 [ 3.443685] Loading compiled-in X.509 certificates [ 3.461041] input: gpio-keys as /devices/platform/gpio-keys/input/input0 [ 3.473163] ALSA device list: [ 3.474432] No soundcards found. [ 3.485283] uart-pl011 9000000.pl011: no DMA platform data [ 3.541376] Freeing unused kernel memory: 10752K [ 3.545897] Run /linuxrc as init process [ 3.548390] with arguments: [ 3.550279] /linuxrc [ 3.551073] nokaslr [ 3.552216] with environment: [ 3.554396] HOME=/ [ 3.555898] TERM=linux [ 3.985835] 9pnet_virtio: no channels available for device kmod_mount mount: mounting kmod_mount on /mnt failed: No such file or directory /etc/init.d/rcS: line 8: can't create /proc/sys/kernel/hotplug: nonexistent directory Please press Enter to activate this console. [root@bryant ]# [root@bryant ]#
VSCode+GDB
vscode中集成了GDB功能,我们可以用它来图形化的调试linux kernel
首先我们添加vscode的gdb配置文件(.vscode/launch.json):
{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "kernel debug", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/vmlinux", "cwd": "${workspaceFolder}", "MIMode": "gdb", "miDebuggerPath":"/usr/bin/gdb-multiarch", "miDebuggerServerAddress": "localhost:1234" } ] }
这里对几个重点参数做一些说明:
program
: 调试的符号文件miDebuggerPath
:gdb的路径, 这里需要注意的是,由于我们是arm64内核,因此需要用gdb-multiarch来进行调试miDebuggerServerAddress
:对端地址,qemu会默认使用1234这个端口
配置完成之后,可以直接启动GDB, 连接上linux kernel
在vscode中,可以设置断点,进行单步调试
精选文章推荐阅读: