获取进程的coredump (内核转储)
启用内核转储
功能 | 命令 |
---|---|
查看coredump功能是否有效 | ulimit 和ulimit -c |
开启内核转储 | ulimit -c unlimited |
设置coredump大小上限 | ulimit -c 大小(Byte) |
gdb调试core文件 | gdb -c core.xxxx ./a. |
检查是否是core文件 | file xxx输出core file |
在专用目录中生成coredump
coredump保存位置的完整路径可以通过sysctl变量kernel.core_pattern
设置。
-
临时修改使用
echo /var/core/%t-%e-%p-%c.core > /proc/sys/kernel/core_pattern
-
或者
/etc/sysctl.conf
中设置kernel.core_pattern=/var/core/%t-%e-%p-%c.core
。然后sysctl -p
让配置生效。
kernel.core_uses_pid=1 作用是在生成的core文件名字结尾添加.PID
。
kernel.core_uses_pid=0则不添加。
kernel.core_pattern中可以设置的格式符
格式符 | 说明 |
---|---|
%% | %字符本身 |
%p | 进程PID |
%u | 进程的真实用户ID |
%g | 进程的真实组ID |
%s | 引发coredump的信号 |
%t | 转储时间戳 |
%h | 主机名 |
%e | 可执行文件名 |
%c | 转储文件大小上限 |
使用用户程序辅助自动压缩coredump文件
kernel.core_pattern
中可以加入管道符,管道符后面写程序名。
例如:
-
echo "|/usr/local/sbin/core_helper %t %e %p %c" > /proc/sys/kernel/core_pattern
-
或者 sudo sysctl -w 'kernel.core_pattern=%t-%e-%p-%c.core'
-
或者 /etc/sysctl.conf
中设置
kernel.core_pattern=|/usr/local/sbin/core_helper %t %e %p %c。然后sysctl -p
让配置生效。
core_heaper 文件内容很简单:
cat core_heaper
#!/bin/sh
exec gzip - > /var/core/$1-$2-$3-$4.core
启用整个系统的coredump功能
-
在 service 文件
[Service]
中添加LimitCORE=infinity, 不制service服务core文件的大小 -
使SUID 程序也转储
sysctl -w 'fs.suid_dumpable=1
systemd用户的coredumpctl
-
安装systemd-coredump && reboot
-
sudo coredumpctl list 列出当前corefile 文件
-
sudo coredumpctl list 'smplayer' | tail -n 5
-
/var/lib/systemd/coredump/ 这是corefile 默认保存位置
-
coredumpctl -o core dump PID # 获得转储信息,输出到文件core 中
-
man systemd-coredump; man core 查看手册
利用内核转储掩码排除共享内存
有些程序会使用多个进程和几个G的共享内存,coredmp时间过长。
在使用各个共享内存的进程中,共享内存的内容是相同的,没必要所有进程都转储共享内存。这种应用程序应当设置成只在某个进程中转储共享内存,其他进程无须转储。
设置方法:echo 1 > /proc/<PID>/coredump_filter
(16进制)
比特掩码对应的内存类型:
- (bit 0) anonymous private memory(匿名私有内存段)
- (bit 1) anonymous shared memory(匿名共享内存段)
- (bit 2) file-backed private memory(file-backed 私有内存段)
- (bit 3) file-backed shared memory(file-bakced 共享内存段)
- (bit 4) ELF header pages in file-backed private memory areas (ELF 文件映射, it is effective only if the bit 2 is cleared)
- (bit 5) hugetlb private memory(大页面私有内存)
- (bit 6) hugetlb shared memory(大页面共享内存)
默认的coredump_filter 的值一般是0x33
要跳过所有的共享内存,可以设置为 echo 1 > /proc//coredump_filter
调试器的基本使用方法
调试前的准备:修改编译参数
- 通过gcc的-g选项生成调试信息。
gcc -Wall -O2 -g source.c
-
-ggdb 选项允许使用只有GDB才能使用的额外调试信息,包括gdb扩展
-
-glevel | -ggdblevel `level取值范围 0 1 2 3。-g0不生成调试信息,否定-g。-g1生成调试信息最少,没有局部变量。-g2生成默认级别的调试信息。-g3包括额外的调试信息,支持宏定义。
-
如果使用Makefile构建,一般要求给CFLAGS中指定-g选项。
CFLAGS = -Wall -O2 -g
- 如果用configure脚本生成Makefile,可以这样用。
./configure CFLAGS="-Wall -O2 -g"
构建方法通常会写在INTALL, README等文件中。
-Wall -Werror 选项可以在警告发生时,当成错误来处理
-Wl,-Map=output.map #-Wl,的意思是把后面的参数传递给连接器,传递给连接器的参数列表需要紧跟-Wl,用逗号分隔,不能有带空格。-Wl,-Map=output.map的作用是生成map文件。map文件包含函数地址入口等信息,如果gcc编译时候没有带调试信息,可以用map来定位调用栈中的函数,也可以通过函数地址入口信息通过gdb或者代码调用函数。
如何知道程序带调试信息
readelf -S ./a.out | grep \.debug
如果带.debug段说明有调试信息。
启动调试
-
gdb 可执行文件名
-
gdb --args ./a.out 可执行文件参数列表
-
gdb -p PID
-
gdb -c corefile 可执行文件
设置断点
程序运行后,到达断点会自动暂停运行。
断点命令break,简写b。
break命令格式 | 例子 |
---|---|
break (在下一行代码上设置断点) | break |
break 函数名 | break sendto |
break 行号 | break 516 |
break 文件名:行号 | break main.c:120 |
break +相对暂停位置的偏移量(行数) | break +3 |
break -相对暂停位置的偏移量(行数) | break -5 |
break *地址 | break *0x08116fd3 |
设置好的断点可以通过 info break [n]
查看。
使用delete [breakpoints] [n]
命令删除段店,breakpoints可有可无,n为断点编号,如果省略n则删除所有断点。
运行
命令 | 说明 |
---|---|
run | 开始运行 |
start | 开始运行,并在main上暂停,相当于自动给main设置断点然后开始运行 |
set args [Arguments] | 设置进程启动参数 |
show args | 查看进程参数列表 |
显示代码
list
命令向下查看10行代码
list -
向上查看10行代码
list LINENUM
查看行号的代码
list FUNCTION
查看指定函数代码
list FILE:LINENUM
list FILE:FUNCTION
list *ADDRESS
按地址查看代码
显示调用栈
命令 | 说明 |
---|---|
backtrace | 显示调用栈栈帧 |
bt | breaktrace的简写(别名) |
backtrace N | 只显示开头N个栈帧 |
bt N | 只显示开头N个栈帧 |
backtrack -N | 只显示最后N个栈帧 |
bt -N | 只显示最后N个栈帧 |
backtrace full | 不仅显示backtrace,还显示局部变量 |
bt full | 不仅显示backtrace,还显示局部变量 |
backtrace full N | 不仅显示backtrace,还显示局部变量,N的含义同上 |
bt full N | 不仅显示backtrace,还显示局部变量,N的含义同上 |
backtrace full N | 不仅显示backtrace,还显示局部变量,N的含义同上 |
bt full -N | 不仅显示backtrace,还显示局部变量,N的含义同上 |
info stack | backtrace的别名 |
where | backtrace的别名 |
显示局部变量和切换调用栈
info locals
显示局部变量
info args
查看函数参数
info frame
查看调用栈帧相关信息
up [N]
查看上一层函数调用栈,N为层数,默认是1
down [N]
查看下一层函数调用栈,N为层数,默认是1
frame <栈帧N>
N为bt命令显示的编号,查看第N个调用栈
frame
切换到当前线程正在执行的调用栈
显示变量
print命令可以显示变量,简写p。
格式: print 变量。
例子 | 显示 |
---|---|
p argv | (char**)0xbf9cd714 |
p *argv | "./a.out" |
p argv[0] | "./a.out" |
p argv[1] | "hello" |
自动显示变量的值
display
命令用于监视变量或者内存的值,每次 gdb 中断,都会自动输出这些被监视变量或内存的值。
-
display命令的使用格式是
display 变量名/内存地址/寄存器名
。 -
info display
# 查看display设置的自动显示的信息。 -
undisplay <编号> # 取消自动显示变量(info display时显示的编号)
-
delete display range… # range为编号,或者空格分割的编号,或者 编号-编号,用来删除一个范围
查看寄存器
info registers可以查看寄存器,简写为info reg。
在寄存器名称前加上$, 可以用p命令查看寄存器。
p $rax
程序指针可以写为 $pc
, 也可以写为 $eip
。
p $pc
能够常看当前执行的程序地址。
print 命令支持选择显示的格式。print/c $rax
p/u $rdx
print命令可以使用的显示格式
格式 | 说明 |
---|---|
x | 显示为16进制数字 |
d | 显示为十进制数字 |
u | 显示为无符号十进制数 |
o | 显示为8进制数字 |
t | 显示为2进制数字,t的由来是two |
a | 显示地址 |
c | 显示为字符(ASCII) |
f | 显示为浮点小数 |
s | 显示为字符串 |
i | 显示为汇编语言。 |
查看内存
x命令可以显示内存中的内容。x这个名字的由来是eXamining.
格式: x/格式 地址
x /格式 寄存器
一般使用x命令时,格式为 x/NFU ADDR
。addr为希望显示的地址。N为重复次数,F为上一节中的显示格式(x,d,u,o,t,a,c,f,s,i)。U下是面的单位。
U代表的单位 | 说明 |
---|---|
b | 字节 |
h | 半字(2字节) |
w | 字(4字节,默认值) |
g | 双字(8字节) |
显示$pc
所指地址开头的10条指令: x /10i $pc
反汇编指令 disassemble
反汇编指令 disassemble,简写为disas。
格式 | 说明 | 例子 |
---|---|---|
disas | 反汇编当前整个函数 | disas |
disas 程序计数器 | 反汇编程序计数器所在的函数整个函数 | disas $pc |
disas 开始地址 结束地址 | 反汇编从开始地址到结束地址之间的部分 | disas $pc $pc+50 |
单步执行
单步执行的意思是根据代码一行一行的执行。
单步执行命令为next,简写n.
进入函数内部的单步命令为step,简写为p.
命令 | 简写 | 含义 |
---|---|---|
next | n | 执行下一行。执行完毕后显示再下一行的源码。不会进入下一行中的函数内部。 |
step | s | 执行下一行,但会进入下一行中函数内部。 |
nexti | - | 逐条执行汇编指令 ,不会进入函数内部。 |
stepi | - | 逐条执行汇编指令 ,会进入函数内部。 |
继续运行
continue命令(简写c)可以继续运行程序。程序会在遇到断点后再次暂停运行。如果没有断点,就会一直运行到结束。
命令 | 说明 |
---|---|
continue | 继续运行 |
continue 次数 | 指定次数忽略遇到的断点。例如continue 5,遇到5次断点不暂停运行。 |
监视点
watch命令可以找到变量或内存在何时被改变或访问。
命令 | 说明 |
---|---|
awatch <表达式> | 表达式被访问,改变时暂停运行。即watch和rwatch。 |
watch <表达式> | 表达式发生变化是暂停运行。 |
rwatch <表达式> | 表达式被访问是暂停运行。 |
表达式的的意思是常量或者变量。例如watch name
watch argv[0]
删除断点和监视点
命令 | 说明 |
---|---|
info b | 显示断点和监视点 |
delete <编号> | 删除指定的断点或监视点 |
delete | 删除所有断点和监视点 |
其他断点
-
硬件断点
hbreak
,适用于ROM空间等无法修改的内存区域中的程序,在有些架构中无法使用。 -
临时断点
tbreak
和临时硬件断点thbreak
,与断点(硬件断点)基本相同,不同之处是临时断点会在到达断点程序暂停时被删除,所以只需要暂停一次时候使用起来很方便。 -
遗憾的是没有临时监视点。
改变变量的值
set variable <变量>=<表达式>
set variable num=0
将num的值设置为0
生成coredump文件
gdb中 generate-core-file
命令在当前目录下生成coredump文件,可以写为gcore
。
linux shell中 gcore [-a] pid1 [pid2...pidN]
命令可以根据进程PID生成coredump文件。
例如gcore $(pidof emacs)
attach到进程
attachh
detach 命令可以脱离调试的进程。
attach后,进程暂停运行,此时可以通过bt命令查看backtrace, 通过continue命令继续运行程序。
进程信息可以用info proc
命令查看。
条件断点
有一种断点仅在特定条件下中断。
格式:break 断点 if 条件
这条命令将测试给定的条件,如果为真则暂停运行。
例break iseq_compile if node==0
condition 断点编号
#这条命令可以删除指定断点的出发条件。
condition 断点编号 条件
# 这条命令给指定的断点增加出发条件。
反复执行
命令 | 说明 |
---|---|
ignore <断点编号> <次数> | 在指定的断点,监视点,或捕获点忽略指定的次数 |
continue <次数> | 忽略指定次数,到达断点,监视点,捕获点不暂停运行 |
step 次数 | 单步运行次数(进入函数) |
stepi 次数 | 单步运行汇编代码,进入函数 |
next 次数 | 单步运行次数(不进入函数) |
nexti 次数 | 单步运行汇编代码,不进入函数 |
finish | 执行完当前的函数后暂停 |
until | 执行完当前的代码块,或者执行完循环后暂停,常用于跳出循环 |
return 表达式 | 结束当前函数调用并返回指定的值,回到上一层函数调用 |
jump LINE | 跳转到指定的行执行 |
jump *ADDR | 跳转到指定的地址执行 |
删除断点和禁用断点
命令 | 说明 |
---|---|
clear 函数名 | 删除函数中的断点 |
clear 行号 | 删除源码行号上的断点 |
clear 文件名:函数名 | 删除指定文件函数中的断点 |
clear 文件名:行号 | 删除源码行号上的断点 |
clear *程序地址 | 删除位于地址的断点 |
delete [breakpoints] 断点编号 | 删除指定编号的断点, breakpoints关键字可以省略 |
delete [breakpoints] | 删除所有断点 |
disable [breakpoints] | 临时禁用所有断点,后面还能再启用 |
disable [breakpoints] 断点编号 | 临时禁用指定的断点,后面还能再启用 |
disable diaplay 显示编号 | 禁用display命令定义的自动显示 |
disable mem 内存区域 | 禁用mem命令定义的内存区域 |
enable [breakpoints] | 启用被禁用的所有断点。 |
enable [breakpoints] 断点编号 | 启用指定断点 |
enable [breakpoints] once 断点编号 | 启用一次断点,随后禁用断点。 |
enable [breakpoints] delete 断点编号 | 启用一次断点,随后删除断点。 |
enable diaplay 显示编号 | 启用display命令定义的自动显示 |
enable mem 内存区域 | 启用mem命令定义的内存区域 |
显示变量类型和定义变量的文件
whatis 表达式
# 显示变量类型声明
ptype 表达式
# 显示变量类型定义
调试多线程
info threads
查看所有线程列表
thread N
切换到指定线程
参考资料&其他学习资料
-
《DEBUG HACKS中文版》
-
https://visualgdb.com/gdbreference
-
man gdb