分类: Linux内核调试

  • 使用Clang LLVM工具链编译Linux内核

    在编译Linux 新版本内核时可以使用clang LLVM工具链进行编译

    make LLVM=1 menuconfig

    启用LLVM工具链编译,可以在Linux内核启用 LTO=thin链接时优化

    在编译randstruct时,clang内置了数据结构随机化功能,不再需要gcc 插件编译使能randstruct

    CONFIG_RANDSTRUCT_FULL=y
    CONFIG_RANDSTRUCT=y
    

    GCC使能randstruct需要手动编译位于内核代码树的

    linux-7.0/scripts/gcc-plugins/randomize_layout_plugin.c

    然后使用此插件完成对内核所有打标签的数据结构进行随机化例如

    struct task_struct {
        	/*
    	 * This begins the randomizable portion of task_struct. Only
    	 * scheduling-critical items should be added above here.
    	 */
    	randomized_struct_fields_start
    
    
            随机化编译器指令之间的数据结构字段
    
             .....
    
    
    
    
             /*
    	 * New fields for task_struct should be added above here, so that
    	 * they are included in the randomized portion of task_struct.
    	 */
    	randomized_struct_fields_end
    } 

    或者整个数据结构随机化

    struct linux_binprm {
         ......
    
    } __randomize_layout;

    在完成编译之后需要使用

    make LLVM=1 modules_install
    make LLVM=1 install

    安装内核

  • 配置kdump和内核dump分析

    • 启用pstore保存系统在shutdown之前的log

    添加启动参数 printk.always_kmsg_dump=Y reserve_mem=2M:4096:oops pstore.backend=ramoops ramoops.mem_name=oops

    在配置了systemd-pstore.service的情况下

    pstore的数据保存在 /var/lib/systemd/pstore/*

    原始数据保存在 /sys/kernel/pstore/

    • 配置kdump

    配置kdump内核启动参数

    crashkernel=2G-64G:256M,64G-:512M 

    在启动时内核根据此配置预留kexec加载内核和initramfs内存

    此参数会被kdump.service自动配置覆盖,需要修改/etc/kdump.conf 在不使用自动配置的情况下,需要禁用auto_reset_crashkernel

    ....
    auto_reset_crashkernel no
    path /var/crash
    core_collector makedumpfile -l --message-level 7 -d 31
    failure_action shell
    final_action reboot
    ....

    kdump.service 默认使用dracut生成kexec 启动内核使用的initramfs, 和安装内核时使用的方法相同, dracut会根据配置文件从根文件系统收集启动时需要的ramdisk内容和内核模块

    配置完成使用

    systemctl restart kdump.service

    充气kdump服务

    • 使用kdumpctl 测试配置是否成功
    kdumpctl test

    测试会触发panic,由于kdump会使用kexec 将内核加载到内存中,在panic时会自动加载新内核并在kexec的内核运行makedumpfile 将/proc/vmcore 的内容保存在磁盘或者收集kdump的服务器

  • 搭建glibc调试环境

    • 搭建glibc等底层库启动和初始化调试环境
    1. 在编译完成的环境使用 devtool modify glibc 会将glibc代码展开在
    ${yocto根目录}/build/workspace/glibc
    

    2. 使用virtiofsd(qemu的配套工具)启动virtiofs,使qemu可以访问host的glibc代码目录

    sudo virtiofsd --socket-path=/tmp/vhostqemu -o source=${yocto根目录}/build/workspace/glibc -o cache=none

    3. 使用qemu启动虚拟机,并访问virtiofs

    qemu-system-x86_64 -device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:02 -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 -drive file=${yocto根目录}/build/tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.rootfs.ext4,if=virtio,format=raw -usb -device usb-tablet -usb -device usb-kbd -chardev socket,id=char0,path=/tmp/vhostqemu -device vhost-user-fs-pci,queue-size=1024,chardev=char0,tag=glibc -object memory-backend-file,id=mem,size=1G,mem-path=/dev/shm,share=on -numa node,memdev=mem -cpu Skylake-Client -machine q35,i8042=off -smp 4 -m 1024 -vga std -serial mon:stdio -display sdl,show-cursor=on -gdb tcp::2345 -kernel ${yocto根目录}/build/tmp/deploy/images/qemux86-64/bzImage -append 'root=/dev/vda rw console=tty0 console=ttyS0,115200 ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8 net.ifnames=0 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 nokaslr'

    4. 虚拟机启动完成,进入虚拟机,挂载 virtiofs 到虚拟机内的目录

    mkdir -p /mnt/glibc
    
    mount -t virtiofs glibc /mnt/glibc

    5. 在虚拟机内使用gdb调试glibc

    使用gdb调试应用程序加载libc.so,(大部分程序会使用libc.so)

    加载glibc所在的文件夹

    6. 无法启动systemd,可以使用 Linux启动参数 init=/bin/bash 启动shell 来提供调试环境

  • 使用qemu虚拟机搭建一个Linux内核调试环境

    • 配置Linux内核参数

    调试内核需要配置defconfig或.config

    defconfig文件需要放置在内核的receipt目录,需要在配置文件中使能

    CONFIG_GDB_SCRIPTS=y
    
    CONFIG_DEBUG_INFO_DWARF5=y

    在配置调试Linux内核参数时可以使用 bitbake virtual/kernel -c menuconfig 使用Linux的menuconfig对内核参数进行配置

    • 使用yocto的编译系统对内核进行编译
    bitbake virtual/kernel

    编译完成之后内核编译目录:

    ${yocto根目录}/build/tmp/work/qemux86_64-oe-linux/linux-yocto/7.0/build/

    在此目录内有带有调试信息的 vmlinux

    内核源代码保存在目录

    ${yocto根目录}/build/tmp/work-shared/qemux86-64/kernel-source/

    在编译完成之后进入目录

    ${yocto根目录}/build/tmp/work/qemux86_64-oe-linux/linux-yocto/7.0/build/

    使用 make scripts_gdb 编译调试脚本,在使用gdb配合qemu虚拟机调试内核时需要 vmlinux-gdb.py python调试脚本

    • 使用qemu虚拟机启动yocto编译的内核和rootfs
    qemu-system-x86_64 -device virtio-net-pci,netdev=net0,mac=52:54:00:12:34:02 -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 -drive file=${yocto根目录}/build/tmp/deploy/images/qemux86-64/core-image-minimal-qemux86-64.rootfs.ext4,if=virtio,format=raw -usb -device usb-tablet -usb -device usb-kbd -cpu Skylake-Client -machine q35,i8042=off -smp 4 -m 1024 -vga std -serial mon:stdio -display sdl,show-cursor=on -gdb tcp::2345 -kernel ${yocto根目录}/build/tmp/deploy/images/qemux86-64/bzImage -append 'root=/dev/vda rw console=tty0 console=ttyS0,115200 ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8 net.ifnames=0 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 nokaslr'

    此命令行中,制定gdb远程调试接口的指令为:

    -gdb tcp::2345

    如需debug启动,可以使用 -S 指定在启动之后停止

    此命令行中,指定内核启动参数的指令为:

    -append 'root=/dev/vda rw console=tty0 console=ttyS0,115200 ip=192.168.7.2::192.168.7.1:255.255.255.0::eth0:off:8.8.8.8 net.ifnames=0 oprofile.timer=1 tsc=reliable no_timer_check rcupdate.rcu_expedited=1 swiotlb=0 nokaslr'

    需要关闭kaslr,即在启动参数末尾使用 nokaslr 禁用内核加载地址随机化,否则在使用GDB调试时需要先从/proc/kallsymc查询到内核的偏移地址,然后再使用调试修改内核起始地址

    • 使用gdb 连接qemu虚拟机

    进入目录

    ${yocto根目录}/build/tmp/work/qemux86_64-oe-linux/linux-yocto/7.0/build/

    使用命令

    gdb ./vmlinux

    打开gdb调试器

    在gdb界面,使用命令:

    a. 配置可加载的路径

    (gdb) add-auto-load-safe-path ${yocto根目录}/build/tmp/work-shared/qemux86-64/kernel-source

    b. 使能vmlinux-gdb.py调试插件

    (gdb) source vmlinux-gdb.py

    c. 配置当前内核的目录,gdb会从/usr/src/kernel 查找内核代码

    (gdb) set substitute-path /usr/src/kernel ${yocto根目录}/build/tmp/work-shared/qemux86-64/kernel-source

    d. 连接虚拟机调试器

    (gdb) target remote :2345

    e. 加载内核符号表

    (gdb) lx-symbols

    • 在未关闭kaslr的情况下可以重新定向内核的符号位置

    未关闭kaslr,内核会启用内核加载地址随机化,重新定位内核符号的偏移

    使用/proc/kallsyms 查找内核符号的实际内存位置

    cat /proc/kallsyms
    
    ffffffffadc00000 T _stext
    
    ffffffffadc00000 T _text
    
    ffffffffadc00000 T __pi__text
    
    ......

    将内核的.text段重新定位到新的偏移,而不是直接使用vmlinux中定义的偏移

    (gdb) add-symbol-file vmlinux 0xffffffffadc00000