Linux调试工具

GDB

  • 调试应用程序coredump文件
    gdb app core.dump
  • 遇到SIGUSR1信号不停止
    handle SIGUSR1 nostop
  • 连接gdbserve
    target remote :port
  • 在指定虚拟地址处下断点
    b * 0x7c00
  • 逐条指令执行
    ni/si
  • 查看所有寄存器值
    info register
  • 查看指定寄存器值
    info register dh
  • 查看标志寄存器
    info register eflags
  • 查看内存的内容
    x/16xb 0x9011a
    查看内存单元0x9011a开始的16个字节内容
    x:内存查看命令
    16:要查看的内存单元个数
    x:显示16进程
    b:显示字节
  • 将标号转换成地址
    info address check_one_fd
    Symbol “check_one_fd” is a function at address 0x7ffff7a2d940.
  • 将地址转换成标号
    info symbol 0x7ffff7a2d940
    check_one_fd.part.0 in section .text of /lib64/libc.so.6
  • 查看应用程序加载的共享库
    info sharedlibrary
  • 打印结构体struct a中成员b的偏移量
    p &((struct a*)0)->b
  • 设置环境变量
gdb qemuxml2argvtest
(gdb) set environment VIR_TEST_DEBUG=1
(gdb) set environment VIR_TEST_RANGE=5
(gdb) r
  • 动态打印断点上下文信息
b {symbol}		设置断点,完成后假设断点为2
commands 2		针对断点2进行设置
> p {value}		打印相关变量
> XXXX			其它操作,比如自己设置循环变量,断住50次后自动停止等
> c				让停住的断点继续运行
> end			退出断点设置

READELF

  • 查看共享库或者二进制文件是否包含符号,可以检查gdb是否能设置断点
    readelf -s {binary/so}

OBJDUMP

  • 反汇编elf中的所有代码
    objdump -D app

NASM

  • TODO

QEMU

trace event

  • 编译qemu添加参数–enable-trace-backends=log
  • 源码添加event,如果源码文件中之前为添加任何event,则需要增加如下头文件:
#include "trace.h"
  • 命令行增加trace参数后,对应的事件默认输出到qemu的log中,配置如下:
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<qemu:commandline>
    <qemu:arg value='-d'/>
    <qemu:arg value='trace:kvm_set_user_memory'/>
</qemu:commandline>
  • 在命令行中可以指定要跟踪的event以及event输出到的日志,如下:
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<qemu:commandline>
  <qemu:arg value='-D'/>
  <qemu:arg value='/var/log/libvirt/qemu/qemu.log'/>	/* 指定事件输出到的日志 */
   <qemu:arg value='-trace'/>
   <qemu:arg value='events=/home/work/events'/>			/* 指定要trace的事件 */
</qemu:commandline>
  • 动态打开trace event
  • qemu-system-x86_64 -trace ?
  • virsh qemu-monitor-command CentOS_Stream_8 --hmp trace-event virtio_gpu_cmd_res_create_2d on

添加环境变量

<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
<qemu:commandline>
<qemu:env name='VREND_DEBUG' value='cmd guestallow feat copyres'/>
<qemu:env name='VIRGL_LOG_FILE' value='/var/log/virglrender%PID%.log'/>
<qemu:env name='LIBGL_DEBUG' value='verbose'/>
<qemu:env name='MESA_DEBUG' value='context'/>
<qemu:env name='MESA_LOG_FILE' value='/var/log/mesa.log'/>
</qemu:commandline>

添加命令行参数

  • 设置ID等于virtio-disk0的device,将ioeventfd属性设置为off
<qemu:commandline>
  <qemu:arg value='-set'/>
  <qemu:arg value='device.virtio-disk0.ioeventfd=off'/>
</qemu:commandline>
  • 设置machine对象的dirty-gfn-count属性为1024
<qemu:commandline>
  <qemu:arg value='-set'/>
  <qemu:arg value='machine.dirty-gfn-count=1024'/>
</qemu:commandline>

qmp命令行调试

  • qemu启动时可以指定qmp server为unix socket,该socket监听客户端连接,接收qmp命令
 - qemu命令行指定qmp server socket
-qmp unix:/tmp/qmp-sock,server,nowait 
 - qemu源码下的客户端脚本连接该socket,进入交互界面
python3.6 scripts/qmp/qmp-shell -v -p /tmp/qmp-sock

hmp命令行调试

  • qemu启动时可以指定monitor为stdio,开启hmp命令行调试
 -monitor stdio
  • 通过hmp还可以dump 虚机的内存镜像。通过crash工具对虚机进行分析
virsh qemu-monitor-command {vm} --hmp dump-guest-memory -z /path/to/crash.img
crash vmlinux /path/to/crash.img

参数调试

  • 通过下列命令行调试qemu命令行参数的解析细节
qemu-system-x86_64 -S -no-user-config -nodefaults -nographic -machine none,accel=kvm

协程调试

  • 通过qemu项目自带的脚本工具 qemu-gdb.py调试qemu中的协程:
gdb -p {qemu_pid}
source /path/to/qemu-gdb.py
help qemu
  • gdb调试qemu协程支持以下子命令:
qemu bt -- Display backtrace including coroutine switches
qemu coroutine -- Display coroutine backtrace
qemu handlers -- Display aio handlers
qemu mtree -- Display the memory tree hierarchy
qemu tcg-lock-status -- Display TCG Execution Status
qemu timers -- Display the current QEMU timers

KERNEL

trace

  • trace跟踪kvm模块trace_kvm_ioapic_set_irq
  • 查看kvm_ioapic_set_irq的输出格式
    cat /sys/kernel/debug/tracing/events/kvm/kvm_ioapic_set_irq/format
  • 打开kvm_ioapic_set_irq开关
    echo 1 > /sys/kernel/debug/tracing/events/kvm/kvm_ioapic_set_irq/enable
  • 查看kvm_ioapic_set_irq信息
    cat /sys/kernel/debug/tracing/trace | grep kvm_ioapic_set_irq
  • 查看trace函数的宏
    TRACE_EVENT(kvm_ioapic_set_irq......)
  • 分析宏含义,以kvm:kvm_exit输出的打印信息为例:
kvm_exit: reason EPT_MISCONFIG rip 0x55555596c364 info 0 0
 - reason: exit_reason	—— EPT_MISCONFIG 
intel vmx手册中定义的VM-exit退出原因
 - rip: guest_rip —— 0x55555596c364 
vcpu 下一次要执行的指令地址,通过gdb可以查看,如下
 - info: info1、info2 —— 0,0
VMCS中VM-exit结构体中的exit exit和exit qualification 字段
  • guest初始化virtio设备时要写common_cfg的device_status字段,以下是具体的函数封装
static void modern_set_status(struct virtio_crypto_hw *hw, uint8_t status)
{
	rte_write8(status, &hw->common_cfg->device_status);
}
  • gdb反汇编该函数,rsi中保存了status的值,0x14(%rax)指向的内存地址保存了hw->common_cfg->device_status指向的地址。因此第二条指令的意思就是将status的值写入hw->common_cfg->device_status表示的内存地址。该内存地址为virtio pci配置空间的地址,因此,一定会引发VM-exit,在后端kvm开启kvm_exit trace event,会抓取到该事件。
  • 执行mov %sil,0x14(%rax)指令前后virtio-pci对应的内存空间(0x7ffff6801014)
    在这里插入图片描述
/*
 * Tracepoint for kvm guest exit:
 */
TRACE_EVENT(kvm_exit,
	TP_PROTO(unsigned int exit_reason, struct kvm_vcpu *vcpu, u32 isa),
	TP_ARGS(exit_reason, vcpu, isa),

	TP_STRUCT__entry(
		__field(	unsigned int,	exit_reason	)
		__field(	unsigned long,	guest_rip	)
		__field(	u32,	        isa             )
		__field(	u64,	        info1           )
		__field(	u64,	        info2           )
	),

	TP_fast_assign(
		__entry->exit_reason	= exit_reason;
		__entry->guest_rip	= kvm_rip_read(vcpu);
		__entry->isa            = isa;
		kvm_x86_ops->get_exit_info(vcpu, &__entry->info1,
					   &__entry->info2);
	),

	TP_printk("reason %s rip 0x%lx info %llx %llx",
		 (__entry->isa == KVM_ISA_VMX) ?
		 __print_symbolic(__entry->exit_reason, VMX_EXIT_REASONS) :
		 __print_symbolic(__entry->exit_reason, SVM_EXIT_REASONS),
		 __entry->guest_rip, __entry->info1, __entry->info2)
);

log level

  • /proc/sys/kernel/printk中每一列表示不同输出下的日志等级,只有第二列与内核message日志有关
 int console_printk[4] = {
        DEFAULT_CONSOLE_LOGLEVEL,       /* console_loglevel */
        DEFAULT_MESSAGE_LOGLEVEL,       /* default_message_loglevel */
        MINIMUM_CONSOLE_LOGLEVEL,       /* minimum_console_loglevel */
        DEFAULT_CONSOLE_LOGLEVEL,       /* default_console_loglevel */
};

cat /proc/sys/kernel/printk
A B C D
A: 控制台默认等级
B: 内核日志默认等级
C: 控制台最小等级
D: 控制台默认等级
  • /proc/sys/kernel/printk第二列取值有7个,值越高越日志详细,当内核日志等级小于等于该值时,有输出,否则没有。假设内核打印为info级别,那第二列只能时info或者debug级别才有输出
#define KERN_EMERG  KERN_SOH "0"    /* system is unusable */  
#define KERN_ALERT  KERN_SOH "1"    /* action must be taken immediately */  
#define KERN_CRIT   KERN_SOH "2"    /* critical conditions */  
#define KERN_ERR    KERN_SOH "3"    /* error conditions */  
#define KERN_WARNING    KERN_SOH "4"    /* warning conditions */  
#define KERN_NOTICE KERN_SOH "5"    /* normal but significant condition */  
#define KERN_INFO   KERN_SOH "6"    /* informational */  
#define KERN_DEBUG  KERN_SOH "7"    /* debug-level messages */  

crash工具

  • 打印per-cpu变量
1. kmem -o 
打印内核per-cpu变量计算时需要加上的偏移
2. p {value}
打印per-cpu变量的值
3. p (struct XXX *)
两者相加解析得到per-cpu变量

源码调试

虚拟机环境

  1. 安装centos虚机用于内核编译和调试
  2. 虚拟机下载对应版本内核源码并安装
yumdownloader --source kernel
rpm -ivh --root=`pwd` kernel.src.rpm
  1. 搭建内核编译环境
yum-builddep kernel
  1. 进入源码安装目录编译内核rpm包
rpmbuild -bb SPECS/kernel.spec --define "_topdir $(pwd)"
  1. 安装编译完成的内核rpm包
  2. 修改内核配置单独编译内核并安装
make menuconfig
make -jN
make modules_install 
make install

注意,内核调试需要打开的配置项没有明确的说法,可以参考Documentation/dev-tools下的文档,gdb+qemu调试内核同kgdb调试内核原理类似,因此可以把KGDB的配置都使能,以下配置可以都使能:

CONFIG_DEBUG_INFO			生成内核基本调试信息,即符号表
CONFIG_DEBUG_KERNEL			包含驱动的调试信息
CONFIG_KGDB					支持KGDB
CONFIG_FRAME_POINTER		支持gdb输出堆栈信息
CONFIG_GDB_SCRIPTS			生成vmlinux-gdb.py用于帮助gdb内核调试(高版本内核支持)

位置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述/
在这里插入图片描述
在这里插入图片描述
以下配置必须关闭:

CONFIG_RANDOMIZE_BASE		内核地址空间布局随机化,如果打开,gdb调试的时候会报错访问不了符号对应的内存空间
CONFIG_DEBUG_RODATA 		如果打开,内核只读地址空间无法设置断点
CONFIG_DEBUG_SET_MODULE_RONX	如果打开,内核模块只读空间无法设置断点

位置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后,对于一些想要调试的内核模块,也可以编译到内核,避免了在gdb调试的时候加载:

CONFIG_VIRTIO
CONFIG_VIRTIO_PCI
CONFIG_VIRTIO_PCI_LEGACY
CONFIG_VIRTIO_BALLON
CONFIG_VIRTIO_MMIO
CONFIG_VIRTIO_BLK
  1. 虚机内核关闭KASLR特性,关闭该特性有两个方法,内核不编译这个特性,或者内核命令行添加nokaslr参数禁用该特性
    在这里插入图片描述

主机环境

  1. 拷贝整个虚机的内核编译目录到主机
  2. 编辑虚机xml文件,在虚机启动时在qemu命令行中增加-s或者-gdb tcp::1234的参数,启动gdbserver。启动虚拟机
libvirt xml,增加如下标签,qemu命令行中,-s是-gdb tcp::1234缩写
<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
  <qemu:commandline>
    <qemu:arg value='-s'/>
  </qemu:commandline>
</domain>
  1. 如果高版本的vmlinux使能了vmlinux-gdb.py脚本,提前准备其配置文件放到~/.gdbinit目录下,内容如下。该配置用来设置gdb python脚本的一些权限。低版本内核跳过这一步
    在这里插入图片描述
  2. 进入内核目录调试vmlinux
cd /path/to/linux-build
gdb ./vmlinux

如果是高版本vmlinux,此时可以输入lx-symbols加载符号,并使用该工具,参考内核文档Documentation/dev-tools/gdb-kernel-debugging.rst

  1. 连接虚拟机的gdbserver
    (gdb) target remote :1234
  2. 设置内核源码目录
    (gdb) dir .
  3. 设置断点,开始内核调试(注意,有些符号使用kvm加速是断不了的,只能使用tcg加速,比如start_kernel)
b start_kernel
c

虚机配置

  • 在调试过程中可能更新内核,需要启动串口观察虚机启动过程中是否报错,增加串口配置如下:
  1. 主机上增加虚机串口的硬件配置
<serial type='pty'>
	<target type='isa-serial' port='0'>
    	<model name='isa-serial'/>
  	</target>
</serial>
<console type='pty'>
   	<target type='serial' port='0'/>
</console>
  1. 虚机内部内核启动命令行增加console=ttyS0参数
  2. 虚机内部设置新编译的内核为下一次启动时的内核,只启动一次验证内核是否正常,grub2-reboot "CentOS Linux (5.13.0-rc4+) 8 (Core)"
  3. 重启虚机,查看更新的内核是否顺利启动,如果正常,将该版本内核设置为默认启动项,grub2-set-default "CentOS Linux (5.13.0-rc4+) 8 (Core)"
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

享乐主

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值