Debug Hacks
启动内核转储(core dump)
ulimit -c // 查看内核转储功能是否有效 ulimit -c unlimited // 不限制大小 ulimit -c 1073741824 // 设置1G大小
对于转储文件 core*可以用GDB进行调试
未设置时,转出文件都在运行目录下,可以设置到固定位置
# cat /etc/systol.conf kernel.core_pattern = /var/core/%t-%e-%p-%c.core kernel.core_uses_pid = 0 # sysctl -p
将会把文件放到 /var/core/下,文件名为%t-%e-%p-%c.core。含义如表2-1所示。
对于kernel.core_uses_pid设置成1,那么文件名末尾就会加 .PID。
自动压缩内核转储文件
// 可以设置进行压缩 kernel.core_pattern = |/usr/local/sbin/core_helper %t %e %p %c // 看看core_helper到底是啥 # cat /usr/local/sbin/core_helper #!/bin/bash exec gzip - > /var/core/$1-$2-$3-$4.core.gz // 直接压缩了
有时进程多,全都转储会有一定压力,可以通过掩码屏蔽一些类型。如表2-2
# cat /proc/<PID>/coredump_filter 00000003 // 转储所有匿名内存区段,若改为1则跳过共享内存区段
GDB基本使用方法 一
gdb 文件名 // 启动调试该文件 b 断点(函数名、行号、文件名:行号、文件名:函数名、+偏移量、-偏移量、*地址) // 设置断点,直接b则在下一行设置断点 info break // 查看设置断点处 run -a // 开始运行,运行到断点后暂停。 backtrace/bt // 显示栈帧, p 变量 // 显示值 p/格式 变量 // 如表2-3所示,按照格式显示值 info reg // 显示寄存器 x/格式 地址 // 可以显示地址下的数据,若格式为i则显示机器语言的语句 x/NFU ADDR // ADDR为显示的地址, N为连续显示的个数,F为2-3的格式,U为2-4的单位 next(n) // 执行源代码的一行,nexti则会执行汇编语言的一行 step(p) // 对于函数调用,可以进入到函数内部,stepi则会执行汇编语言的一行 continue(c) 次数 // 继续运行程序,遇到断点暂停 watch <表达式> // 监视点,表达式则是常量或者变量。表达式变化时暂停 awatch <表达式> // 表达式被访问、改变时暂停 rwatch <表达式> // 表达式被访问时暂停 delete <编号> //删除断点或监视点 set variable <变量>=<表达式> // 修改变量的值 generate-core-file // 生成内核转储文件
GDB基本使用方法二
attach到进程:用于调试守护进程等已经启动的进程。
attach pid // attach到pid进程上(在gdb里面进行attach) ps aux | grep sleep // 可以查看sleep命令 info proc // 可以用来查看进程信息 break 断点 if 条件 // 只有条件为真,断点才暂停 clear // 删除断点 disable // 禁用断点 enable // 开启disable禁用的断点 comand 断点编号 // 断点命令 在遇到该断点编号后自动执行,可以用来显示一些变量 命令... end
GDB基本使用方法三
p argc // 可以显示argc的值,如表2-6可以显示历史输出的值。 set $i = 0 // 可以随意定义变量,并设置值 show history // 可以显示历史命令,默认保存在./.gdb_history文件 define 命令名 // 可以自定义命令 命令... end 命令名 // 通过命令名可以直接运行 document 命令名 // 可以为定义的命令添加说明 说明... end help 命令名 // 可以查看命令名的说明
字节序:低位在低地址就是小端(Little Endian),否则是大端。
ESP:栈指针
CS:代码段。DS:数据段。SS:堆栈段。ES/FS/GS:数据段。
EAX:操作数,结果。EBX:DS段的指针。ECX:计数器。EDX:输入输出指针。
ESI:DS下的数据指针,作为源。EDI:ES下的数据指针,作为目的。ESP:栈指针(栈顶)。EBP:栈上数据的指针(栈底)
地址:平坦模式,如图2-9,内存是线性空间。LInux采用该方式。
分段模式,如图2-10,先寻找段,再找段内偏移。
------------------------------TODO 函数调用前后栈的状态 P72-------------------------
使用GDB操作栈帧
bt // 查看栈 frame // 选择栈帧 up/down // 选择上一层或者下一层栈帧 i frame 1 // 查看详细的栈帧信息 i proc mapping // 查看attach的进程的内存映射信息,包含栈的空间 // 对于内核转储的文件,可以info /proc/<PID>/maps主动增加该文件
b func // 调试函数
---------------------------------- TODO 函数调试 P79-------------------------
crash可以进行反汇编
crash /boot/vmlinux-2.6.19 // 首先进入crash(常用于分析vmcore) crash> dis 文件名 // 反汇编该文件 crash> bt // 打印函数栈 crash> ps // 查看崩溃时,运行的所有进程
----------------------------------TODO oops 105~108--------------------
netconsole:可以让printk信息通过网络(UDP)发送到远端主机。
如下图,发送端为10.1.1.100,接收端为10.1.0.200
文件 /etc/sysconfig/syslog中
将 SYSLOGD_OPTIONS="-m 0" 修改为 "-m 0 -r" 可用于接收主机日志。
修改/etc/sysctl.conf
将 kernel.sysrq = 0修改为 1。并执行sysctl -p可以用于启动SysRq
SysRq:用于调试内核。SysRq使用了中断,即使无法登陆,按键无响应也可以使用,但内核禁止中断时无法使用。
make menuconfig
sysctl -w kernel.sysrq=1 // 1代表所有命令键都可使用,如表3-1为其他配置。48则是允许Sync和重新挂载。
SysRq : show memory // 显示内存占用
SysRq : show State // 显示状态
disdump:为发生kernel panic 获取转出文件功能。
/etc/sysconfig/diskdump加入:
DEVICE = /dev/sda3 // 需要准备一个分区,分区要大于内存大小。
service disdump initialformat // 准备启动diskdump chkconfig diskdump on service diskdump start cat /proc/diskdump // 查看diskdump是否生效
kdump:获取内核崩溃转储
makedumpfile:缩小转储文件
crash常用命令
// 启动crash crash vmlinux vmcore // vmlinux未压缩的镜像,vmcore为转储文件。 crash vmlinux // 查看当前实时系统。 // crash内部命令 set 命令 // 指定进程,显示其上下文。不指定则为当前运行进程。 ascii 命令 // 将16进制转为字符串。 h 命令 // 显示输入过的命令历史 bt 命令 // 输出backtrace dev命令 // 显示字符设备列表 dis 命令 // 输出反汇编 files 命令 // 显示进程打开的文件 irq 命令 // 显示打开的中断 kmem 命令 // 显示内存信息 list 命令 // 按顺序显示地址 mod 命令 // 加载模块信息,符号信息,调试信息。 net 命令 // 列出网络设备 ps 命令 // 显示进程信息 rd 命令 // 读取内存地址 runq 命令 // 显示进程调度的运行队列 .....
------------------------- TODO IPMI NMI获取崩溃转储---------------------------
内核独有汇编指令:
ud2 // CPU引发的异常,内核执行ud2指令,使CPU接收该异常。 cli // 禁止中断 sli // 允许中断
current 宏:获取当前运行进程的task_struct
task_struct和 thread_finfo :管理进程task_struct 的结构位于SLAB中。
task_struct和thread_info都有对方指针。如图3-6.
发生SIGSEGV:应用程序非法访问了内存。
NULL指针访问。指针被破坏,导致非法地址访问。栈溢出,访问超出了分配的空间。
数据非法访问导致内存破坏:缓冲区溢出后,bt不会显示符号名
运行地址的改变有:指定地址并调用。指定了内存区域,保存了跳转地址、ret命令返回时的栈指针被破坏。
由于第一类使用的地址都被保存在只读空间,所以通常是后两类。
确定破坏跳转地址值的位置(栈被破坏),如:对于复制字符串的目的地址空间过小,缓冲区溢出。原本是保存返回地址的,保存成了错误地址。如图4-4所示。
监视点:对于越界的地址可以用监视点,看什么时候访问了该错误的地址。
malloc和free:双重释放、非法区域释放可能导致问题。
应用程序停止响应(死锁):对于停止响应,如果用ps看到状态是R,说明还在运行,可能是失控。对于S,说明睡眠,可能是死锁。
attach到该进程。i thr(可以看有哪些线程)。thr 2(跳转到2线程)
应用程序停止响应(死循环):通常有大量相同信息,可以通过top命令、vmstat命令查看cpu利用率。
kernel panic(空指针引用):首先可以通过crash下的disas进行反汇编,并加载符号需要的模块。
kernel panic(链表被破坏):在删除时,prev和next有了错误数据。
-------------------------------------------------TODO 遇到的各种BUG---------------------------
strace:跟踪进程使用的系统调用,并显示内容。
strace 可执行文件:可以看到,下图2处,由于没有打开文件权限,导致失败。
strace -i 可执行文件:可以看到哪里执行了系统调用,有利于GDB调试去设置断点。
strace -p ·pidof st2· :(pidof st2就获得了进程的pid),attach到正在运行的pid进程上。
strace -o output.log command :可以将内容输出到文件
objdump:可以处理带有调试信息的二进制文件。
valgrind:用于检测内存的非法使用。
如图所示代码,malloc 20的空间没有释放,HEAP SUMMARY内部显示20字节,分配1次,释放0次。
除此之外,还能检测:非法内存地址的访问,读取未初始化区域,访问已释放区域,内存双重释放,非法栈操作
valgrind对于检测非法使用内存很有效果,但不是万能的。
valigrind:内存泄漏会根据时间增长,内存使用(VIRT和RES)逐渐增长。VIRT和RES的区别,VIRT包含了被交换(swap)的空间,而RES不包括。
kprobes:动态插入侦测器获取内核内部信息。
无需重启内核,就可以执行printk在内的各种调试工作。
jprobes:将侦测器插入到内核函数的开头,获取内核内部信息。
KAHO:获取被编译器优化掉的变量的值
systemtap:调试运行中的内核。
/proc/<PID>/mem:读取进程的内存内容
OOM(Out of Memory) Killer:内存和交换区用尽时的强制结束信号。
参考:Debug Hacks 中文版