gdb入门指令
gcc -g hello.c //加入调试信息
gdb ./a.out
l // list,显示函数
start // 开始逮捕调试,显示即将运行的行
n // next,运行到下一行,到函数时不会进入函数
s // step,运行到下一行,到函数时进入函数
p a // 显示函数内a的值
bt // 查看函数堆栈,当前栈序号为0,main栈序号为1
f 1 // 切换到main的栈
p a // 显示main中a的值
p &a // 打印a的地址
q // 退出调试
gdb调试运行中的程序
gdb中忽略信号处理 SIGPIPE
GDB调试网络程序时,会遇到SIGPIPE信息,默认GDB会把程序停下来,用handle命令设置一下缺省的signal的处理行为即可:(在gdb模式下运行下面的命令)
handle SIGPIPE nostop print
如果连信息提示都不想看见,可以这样设置:
handle SIGPIPE nostop noprint
命令小记:
GDB进入正在运行的进程
1,基本操作
gdb -p pid // 在线调试已经存在的进程
gdb attach // 同上
b fun // 设置断点函数
c // 继续执行程序,直到下个断点
2.断点管理
//设置断点
break n,break 函数名(b n, b 函数名)
//查看断点
info breakpoints(i breakpoints)
//删除断点,其中n为断点的序列号,可以用info breakpoints查看
delete breakpoints n
//使断点失效
disable breakpoints n
//使断点生效
enable breakpoints n
GDB提示符
1. 变量信息管理
info 变量名(i 变量名)//查看一个变量的值
info locals //查看所有局部变量的值
info args
2. 查看、设置变量
p 变量
p 变量 = 新值
set 变量 = 新值
3. 查看内存
x/<n/f/u> <addr>
x/nbx 变量名 //查看从变量名开始的n个字节,例x/7bx input 表示查看从变量input开始的7个内存单元的内容
4. 线程调试
info thread
thread n
gdb调试Core Dump
测试代码:
#include <stdio.h>
int main()
{
int *p = NULL;
*p = 0;
printf("bad\n");
return 0;
}
编译运行,会发生Segmentation fault (core dumped) 错误,使用gdb调试:
[taoge@localhost test]$ gcc -g main.c
[taoge@localhost test]$ ./a.out
Segmentation fault (core dumped)
[taoge@localhost test]$ gdb a.out
(gdb) r
Starting program: /home/taoge/test/a.out
Program received signal SIGSEGV, Segmentation fault.
0x080483c9 in main () at main.c:6
6 *p = 0;
可以看到, 在gdb调试的时候, 用r命令让程序再跑起来, 可以定位到问题出在第6行。 我们再来看看bt命令:
(gdb) bt
#0 0x080483c9 in main () at main.c:6
(gdb)
可以看到,通过bt查看函数堆栈, 也能定位到第6行。
我们知道,在实际中, 有很多问题是概率发生的, 很难重现。 此时, 如果用gdb的r命令(实际相当于重新运行程序)则是不可能的。 所以,对于概率性问题, 如果程序出现堆栈错误时, 产生了core文件, 我们一定要视之为宝贝, 记得保存, 否则很可能被冲掉。 拿到core文件后, 我们可以用gdb调试core。
1,什么是Core Dump?
Core的意思是内存, Dump的意思是扔出来, 堆出来.
开发和使用Unix程序时, 有时程序莫名其妙的down了, 却没有任何的提示(有时候会提示core dumped). 这时候可以查看一下有没有形如core.进程号的文件生成, 这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考.
core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump.
2,怎么生成Core Dump?
在linux平台下,设置core dump文件生成的方法:
-
在终端中输入ulimit -c 如果结果为0,说明当程序崩溃时,系统并不能生成core dump。
-
使用ulimit -c unlimited命令,开启core dump功能,并且不限制生成core dump文件的大小。如果需要限制,加数字限制即可:ulimit - c 1024,如果生成的信息超过此大小,将会被裁剪,最终生成一个不完整的core文件。在调试此core文件的时候,gdb会提示错误。
永久设置, 修改/etc/security/limits.conf文件:
soft core 0
修改成:
soft core unlimited
- 默认情况下,core dump生成的文件名为core,而且就在程序当前目录下。新的core会覆盖已存在的core。通过修改/proc/sys/kernel/core_uses_pid文件,可以将进程的pid作为作为扩展名,生成的core文件格式为core.xxx,其中xxx即为pid。为0则表示生成的core文件统一命名为core。可通过以下命令修改此文件:
echo "1" > /proc/sys/kernel/core_uses_pid
注意 proc 是内存文件,使用vi 是不能 编译的,所有只能使用append 的方式
/proc 这个目录是虚拟在内存中的,不在硬盘保存,
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。
- 通过修改/proc/sys/kernel/core_pattern可以控制core文件保存位置和文件格式。例如:
# 将所有core文件生成到/corefile下,格式为core-命令名-pid-时间戳.
echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
# 以下是参数列表:
%% 单个%字符
%p dump进程的进程ID
%u dump进程的用户ID
%g dump进程的组ID
%s 导致core dump的信号
%t core dump的时间
%h 主机名
%e 程序文件名
永久设置, 修改/etc/sysctl.conf配置文件,添加:
kernel.core_pattern = /corefile/core-%e-%p-%t
kernel.core_uses_pid = 1 #即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID
/* 执行以下命令立即生效 */
# sysctl -p
3,如何使用core文件?
gdb [exec file] [core file]
在编译程序的时候加入选项-g,下面我们可以在发生运行时信号引起的错误时发生core dump了。发生core dump之后, 用gdb进行查看core文件的内容, 以定位文件中引发core dump的行。比如:
gdb /usr/sbin/ovs-vswitchd vat/log/idump/core-3219-1512819153
bt //查看其调用函数的信息
frame n (f n)//n为栈的层次,然后可以用其他命令(info)查看此级别的变量信息
补充:
// 查看core生成详细时间
date --date='@1512819153'
在进入gdb后, 用bt命令查看backtrace以检查发生程序运行到哪里, 来定位core dump的文件->行.
进去后输入where回车, 也可以显示程序在哪一行down掉的, 在哪个函数中.
如果不知道coredump对应的执行文件,可以使用以下方式查找:
gdb -c corefile # 使用gdb调试core文件
info auxv # 索引31对应的是core文件的应用程序exec_file
find / -name exec_file
core dump原因分析
造成程序core dump的原因很多,这里根据以往的经验总结一下:
1 内存访问越界
a) 由于使用错误的下标,导致数组访问越界
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
2 多线程程序使用了线程不安全的函数。
应该使用下面这些可重入的函数,尤其注意红色标示出来的函数,它们很容易被用错:
asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n) ctermid_r(3s) gethostent_r(3n) getservbyport_r(3n) ctime_r(3c) getlogin_r(3c) getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n) getspent_r(3c) fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c) fgetspent_r(3c) getnetent_r(3n) gmtime_r(3c) gamma_r(3m) getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3) getprotobyname_r(3n) localtime_r(3c) getauclassnam_r(3) etprotobynumber_r(3n) nis_sperror_r(3n) getauevent_r(3) getprotoent_r(3n) rand_r(3c) getauevnam_r(3) getpwent_r(3c) readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c) getgrent_r(3c) getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c) getrpcbyname_r(3n) ttyname_r(3c) getgrnam_r(3c) getrpcbynumber_r(3n) gethostbyaddr_r(3n) getrpcent_r(3n)
3 多线程读写的数据未加锁保护。
对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump
4 非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.
5 堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。