文章目录
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变量
源码调试
虚拟机环境
- 安装centos虚机用于内核编译和调试
- 虚拟机下载对应版本内核源码并安装
yumdownloader --source kernel
rpm -ivh --root=`pwd` kernel.src.rpm
- 搭建内核编译环境
yum-builddep kernel
- 进入源码安装目录编译内核rpm包
rpmbuild -bb SPECS/kernel.spec --define "_topdir $(pwd)"
- 安装编译完成的内核rpm包
- 修改内核配置单独编译内核并安装
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
- 虚机内核关闭KASLR特性,关闭该特性有两个方法,内核不编译这个特性,或者内核命令行添加nokaslr参数禁用该特性
主机环境
- 拷贝整个虚机的内核编译目录到主机
- 编辑虚机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>
- 如果高版本的vmlinux使能了vmlinux-gdb.py脚本,提前准备其配置文件放到~/.gdbinit目录下,内容如下。该配置用来设置gdb python脚本的一些权限。低版本内核跳过这一步
- 进入内核目录调试vmlinux
cd /path/to/linux-build
gdb ./vmlinux
如果是高版本vmlinux,此时可以输入lx-symbols
加载符号,并使用该工具,参考内核文档Documentation/dev-tools/gdb-kernel-debugging.rst
- 连接虚拟机的gdbserver
(gdb) target remote :1234
- 设置内核源码目录
(gdb) dir .
- 设置断点,开始内核调试(注意,有些符号使用kvm加速是断不了的,只能使用tcg加速,比如start_kernel)
b start_kernel
c
虚机配置
- 在调试过程中可能更新内核,需要启动串口观察虚机启动过程中是否报错,增加串口配置如下:
- 主机上增加虚机串口的硬件配置
<serial type='pty'>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
- 虚机内部内核启动命令行增加
console=ttyS0
参数 - 虚机内部设置新编译的内核为下一次启动时的内核,只启动一次验证内核是否正常,
grub2-reboot "CentOS Linux (5.13.0-rc4+) 8 (Core)"
- 重启虚机,查看更新的内核是否顺利启动,如果正常,将该版本内核设置为默认启动项,
grub2-set-default "CentOS Linux (5.13.0-rc4+) 8 (Core)"