gdb

gdb作为日常问题定位中不可缺少的一个工具,可以说和日常开发息息相关,本节想通过两部分对gdb介绍

  • gdb的工作原理: 理论学习,并不对gdb的代码过多探索
  • gdb常用命令: 根据实际工作场景不断补充

两种调试界面

我们都知道,内存中的执行程序,都是由二进制的指令和数据构成的,gdb可以把二进制指令翻译成为汇编,这并没有什么难度 但是对于更多开发人员,他们更关注自己的代码,而不是汇编,但是仅仅通过指令得到目标代码,是不现实的,为了完成这个目标,必须要 在编译的时候,通过建立代码和程序的关系,一般通过gcc -g 选项完成该工作

  • 基于目标语言(C)的调试:依赖源文件、依赖目标程序的调试信息(通过编译增加-g选项)
  • 基于汇编的调试:不依赖高级语言

常用调试命令

控制窗口显示

  • gdb -tui: 以窗口模式打开gdb ,配合layout 命令使用
  • layout asm/src: 窗口 显示汇编/源文件

控制程序

  • r :restart
  • s : 进入函数

断点

  • break/b:
  • delete

寄存器

  • p/x $rax

内存

  • x/{n}{f}{u} addr
  • n 表示要打印的数量
  • f 打印格式,支持d(decimal 十进制) x 十六进制 a 八进制
  • u 每个打印单元长度: b(byte) h(2byte) w(4 byte) g(8 byte)

历史命令查看

tui模式下 查看上一条 下一条命令

  • ctrl+p previous
  • ctrl+n next
  • ctrl+b back
  • ctrl+f forward

QEMU GDB调试内核

代码准备

$ make -p ~/code/
$ cd  ~/code/
$ git clone https://mirrors.tuna.tsinghua.edu.cn/git/linux.git
$ git remote add linux-next https://mirrors.tuna.tsinghua.edu.cn/git/linux-next.git
切换到next tree
$ git fetch  linux-next 
$ git fetch --tags linux-next
$ git tag -l "next-*" | tail
$ git checkout -b {my_local_branch} next-(latest)

代码编译

内核配置编译 需要关闭 CONFIG_RANDOMIZE_BASE 打开CONFIG_GDB_SCRIPTS

$ make ARCH=x86_64 x86_64_defconfig (配置内核)
$ make ARCH=x86_64 menuconfig 
$ make -j8
$ qemu-system-x86_64  -kernel arch/x86/boot/bzImage -hda /dev/zero -append "root=/dev/zero console=ttyS0" -serial stdio -display none
#aarch64
$ make CROSS_COMPILE=aarch64-none-linux-gnu-  ARCH=arm64    O=build   defconfig
$ qemu-system-aarch64 -M virt -cpu cortex-a57 -smp 1 -m 4G   -kernel build_qemu/arch/arm64/boot/Image  -nographic  -append " earlycon console=ttyAM0"

由于此时还没有提供根目录,内核在启动 执行到挂载根目录就会panic

代码调试

现在可以增加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选项,关于kaslr的介绍请看 https://lwn.net/Articles/569635/, 如果有机会,我们在内核安全章节可能会学习介绍他

这里我们增加了 -s -S 选项,该选项会让GDB 卡住,直到gdb client 连接

修改~/.gdbinit 设置自动加载内核提供的gdb 脚本

add-auto-load-safe-path /home/test/code/linux/scripts/gdb/vmlinux-gdb.py

下面命令是在gdb里面执行的

$ 在另外一个窗口执行
$ cd  /home/test/linux/
$ gdb ./vmlinux
$ target remote localhost:1234
$ lx-symbols
$ break start_kernel
$ layout src

现在可以单步调试了 其他命令参考

跨平台

$ gdb-multiarch vmlinux   
$ set architecture aarch64
$ target remote localhost:1234
$ lx-symbols
$ break start_kernel
$ layout src

根目录制作

$ cd  ~/code
$ git clone git://git.buildroot.net/buildroot
$ make menuconfig (Target Options -> Target Architecture →x86_64; Filesystem images → ext2/3/4 root file system )
$ make -j8
$ qemu-img convert -f raw -O qcow2 output/images/rootfs.ext2 output/images/rootfs.qcow2

又或者可以直接使用 ubuntu 最小rootfs

现在已经拥有

  • 内核image: arch/x86/boot/bzImage
  • rootfs: buildroot/output/images/rootfs.ext2
$ 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

利用busybox 只启动一个ramdisk 文件系统 
$ qemu-system-aarch64 -M virt -cpu cortex-a57 -smp 1 -m 4G   -kernel build_qemu/arch/arm64/boot/Image  -nographic  -initrd ../busybox-1.36.1/initramfs.cpio.gz  -append " earlycon root=/dev/ram rdinit=/bin/sh "

网络配置

虚拟机网络一般我们使用最简单的nat 转发即可

qemu-system-aarch64  -machine virt  -cpu cortex-a57  -nographic  -smp 2  -m 4096  -kernel build_qemu/arch/arm64/boot/Image -append "root=/dev/vda rw console=ttyAMA0"   -drive if=none,file=../busybox-1.36.1/rootfs.img,id=hd0,format=raw  -device virtio-blk-device,drive=hd0  -netdev user,id=net0  -device virtio-net-device,netdev=net0    

guest中配置为auto 或者默认dhcp即可

sudo nmcli connection add type ethernet ifname eth0 con-name eth0
sudo nmcli connection modify eth0 ipv4.method auto
sudo nmcli connection up eth0
sudo nmcli connection modify eth0 connection.autoconnect yes

内核参数

内核支持的参数文档说明

也可以在系统启动之后 通过 cat /proc/cmdline 查看