linux 下C/C++程序常用调试方法(gdb)



不管是在开发或者运行过程中,调试保证程序正常运行最基本的手段,熟悉这些调试方式,方便我们更快的定位程序问题所在,提高开发效率。

一 程序正常运行调试

(1)  直接使用gdb

开发过程中最常用的方式,我们可以在其过程中给程序添加断点,监视等辅助手段,监控其行为是否与我们设计相符,比如:

(2)      程序已经运行,通过attach附加到进程

二 程序中断后调试

首先简单介绍下linux 下的信号:

软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。进程之间可以互相通过系统调用kill发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。注意,信号只是用来通知某进程发生了什么事件,并不给该进程传递任何数据。

收到信号的进程对各种信号有不同的处理方法。处理方法可以分为三类:第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信 号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。

我们正是利用linux的信号机制,按第一种方法进行处理。

以SIGSEGV为例,其他类似。触发信号而触发此信号最根本的原因是试图访问未分配的内存,或者试图往没有写权限的地址写入数据。

(1)    通过backtrace,backtrace_symbols输出函数调用堆栈;

void dump(int __signal)
{
	const int __max_stack_flow = 20;
	void* __array[__max_stack_flow];
	char** __strings;
	size_t __size = backtrace(__array,__max_stack_flow);
	printf("backtrace() returned %d addresses\n", (int)__size);
	__strings = backtrace_symbols(__array,__size);
	if(NULL == __strings)
	{
		perror("backtrace_symbols");
		exit(EXIT_FAILURE);
	}
	fprintf (stderr,"obtained %zd stack frames.nm", __size);
	for (size_t __i = 0; __i < __size; ++__i)
	{
		printf("%s\n", __strings[__i]);
	}
	//	This __strings is malloc(3)ed by backtrace_symbols(), and must be freed here
	free (__strings);
    exit(0);
}

·    比如说,我们需要对SIGSEGV进行处理,那么只需要调用signal(SIGSEGV, dump),那么当产生SIGSEGV中断时,就会触发dump调用,打印出堆栈,默认设置最大堆栈数为20,如果实际的堆栈大于20,仅仅显示最近的20层。

测试代码:

void segv_fun()
{
 unsigned char* __ptr = 0x00;
 *__ptr = 0x00;
}
void register_signal(int __signal)
{
 signal(__signal, dump);
}
void TestDump::test_signal_segv()
{
 printf("TestDump::test_signal SIGSEGV\n");
 register_signal(SIGSEGV);
 segv_fun();
}

运行输出:

从运行结果可以看到,程序SIGSEGV中断触发了dump函数,dump打印出了6帧,

第一帧:/easy_main(_Z4dumpi+0x26) [0x402896] 是在执行dump;

第二帧:/lib64/libc.so.6() [0x332ae329a0] 是调用libc的库函数;

第三帧:./easy_main(_ZN8TestDump5myRunEPKcb+0x5f) [0x402d2f],这个看起来有点奇怪,这一串貌似可以看出来一些信息,TestDump?不能很快定位。不用急,如果出现这个请看看,我们可以借助addr2line,通过地址转换到对应文件的行数。

再看看源代码文件,

void segv_fun()
{
	unsigned char* __ptr = 0x00;
	*__ptr = 0x00;
}

这下就知道问题所在之处了吧!

第N帧

......

(2) 分析core文件

如果不对信息进行任何接管,那么程序中断后,会产生一个core文件,core文件是当时程序中断时的内存的一个镜像,利用它可以还原场景。如果没有产生core  文件,请检查ulimit 参数,比如我的设置是这样,

那么需要设置它的太小,单位为blocks,一般来说1 blocks = 1k,也就是1024bytes.

现在我已将它设置为1M,那么如果程序占用内存小于1M的话,core文件是一个完整的内存镜像,大于1M也会保留最近的1M的内存信息。

由输出结果可知,第一次执行时,没有生成core, 设置ulimit 值后,便产生了core dump了(segmentation fault core dumped).

接下来我们通过core 文件来定位错误信息。

显然,结果显示了产生中断的详细代码以及文件所在行数,这样是不是更方便呢!

(3) 结合(1),中断时启动gdb调试,

稍微改变下代码,捕捉到信号后,获取当前进程的参数,执行命令行。

void dump_for_gdb(int __signal)
{
	const int __max_buf_size = 512;
	char __buf[__max_buf_size] = {};
	char __cmd[__max_buf_size] = {};
	FILE* __file;
	snprintf(__buf, sizeof(__buf), "/proc/%d/cmdline", getpid());
	if(!(__file = fopen(__buf, "r")))
	{
		exit(0);
	}
	fclose(__file);
	if(__buf[strlen(__buf) - 1] == '\n')	//	warning: multi-character character constant [-Wmultichar]
	{
		__buf[strlen(__buf) - 1] = '\0';	//	warning: multi-character character constant [-Wmultichar]
	}
	snprintf(__cmd, sizeof(__cmd), "gdb %s %d",__buf, getpid());
	system(__cmd);
	exit(0);
}
void register_signal_for_gdb(int __signal)
{
	signal(__signal, dump_for_gdb);
}
void TestDump::test_dump_for_gdb()
{
	printf("TestDump::test_dump_for_gdb SIGSEGV\n");
	register_signal_for_gdb(SIGSEGV);
	segv_fun2();
}

输出结果

当然,我们可以把这些东西整合起来,比如在项目最终上线后,我们希望这个操作更加简单,因为到了运营阶段,操作者可能不是开发者,而是运维人员,我们希望用更简单,直接的方式,把这些信息提取出来,那就需要更进一步的工作了。我们之前采用的方法是:把dump的堆栈信息写的文件中,然后使用shell读取这些堆栈信息,病使用addr2line转化到具体的文件行数或者函数并保存最终文件。这样运维人员只需要把最终的文件给开发人员,便可分析定位问题了。

目前所常用的就这些了,如果有更好的方式,欢迎补充。

引用代码:

[email protected]:yuyunliuhen/easy.git

https://github.com/yuyunliuhen/easy/blob/master/src/base/easy_dump.h

https://github.com/yuyunliuhen/easy/blob/master/src/test/easy_test_dump.cc

参考:

Linux
信号signal处理机制

Linux下的段错误产生的原因及调试方法

通过PID获取进程相关信息,如cmdline



时间: 2024-08-05 07:06:53

linux 下C/C++程序常用调试方法(gdb)的相关文章

linux下coredump的产生及调试方法

什么是coredump 通常情况下coredmp包括了程序执行时的内存,寄存器状态,堆栈指针,内存管理信息等.能够理解为把程序工作的当前状态存储成一个文件.很多程序和操作系统出错时会自己主动生成一个core文件. 怎样使用coredump coredump能够用在非常多场合,使用Linux,或者solaris的人可能都有过这样的经历,系统在跑一些压力測试或者系统负载一大的话,系统就hang住了或者干脆system panic.这时唯一能帮助你分析和解决这个问题的就是coredump了. 如今非常

两种在linux下创建应用程序快捷方式的方法

两种在linux下创建应用程序快捷方式的方法: A. 在桌面上创建快捷方式 B. 在应用程序菜单中添加快捷方式 在桌面上创建快捷方式 这是最简单的一种方法,在桌面上单击鼠标右键,会有一个“创建启动器”栏.这里我以为mplayer创建快捷方式为例说明: 名称-mplayer(或者你喜欢的任何名称,这个名称会出现在快捷图标的 下方) 命令-/usr/bin/gmplayer(这个是mplayer的gui应用程序的执行文件,跟 安装路径相关,可以通过which gmplayer找到) 图标-一般应用程

Linux下C/C++程序调试基础(GCC,G++,GDB,CGDB,DDD)

在写程序的时候,经常会遇到一些问题,比如某些变量计算结果不是我们预期的那样,这时我们需要对程序进行调试.本文主要介绍调试C/C++在Linux操作系统下主要的调试工具. 在Linux下写程序,C/C++主要的编译器有GCC/G++,ICC等,像我等穷码农,最喜欢GCC了,很大原因是他免费!所以,我们以GCC/G++为例介绍主要的调试工具. 分以下几个内容介绍: 1.调试之前的工作 2.选择调试工具 3.调试步骤 点我,请帮我投一票! 调试之前的工作 编译器在编译阶段需要产生可供调试的代码,才能被

VC的常用调试方法

前言 VS是非常强大的IDE,所以掌握VSVC的常用方法,将会使得我们找出问题解决问题事半功倍. 目录 VSVC的常用调试方法 前言 1. Watch窗口查看伪变量 2. 查看指针指向的一序列值 3. 内存泄露查找 4. 调试Release版本 5. 远程调试 6. 函数断点 7. 数据断点. 8. 代码执行时间 9. 格式化数据 10. 格式化内存 Watch窗口查看伪变量 按MSDN的介绍,伪变量就是用来查看特定信息的术语.例如当调用的API失败时,可以用GetLastError获取对应的错

linux下如何产生core,调试core

linux下如何产生core,调试core 摘自:http://blog.163.com/[email protected]/blog/static/19554784201131791239753/ 在程序不寻常退出时,内核会在当前工作目录下生成一个core文件(是一个内存映像,同时加上调试信息).使用gdb来查看core文件,可以指示出导致程序出错的代码所在文件和行数. 1.core文件的生成开关和大小限制 1)使用ulimit -c命令可查看core文件的生成开关.若结果为0,则表示关闭了此

[转] python程序的调试方法

qi09 原文 python程序的调试方法 本文讨论在没有方便的IDE工具可用的情况下,使用pdb调试python程序 源码例子 例如,有模拟税收计算的程序: #!/usr/bin/python def debug_demo(val): if val <= 1600 : print "level 1" print 0 elif val <= 3500 : print "level 2" print (val - 1600) * 0.05 elif val

linux下维护服务器之常用命令

linux下维护服务器之常用命令! 第1套如下: 正则表达式: 1.如何不要文件中的空白行和注释语句: [[email protected] ~]# grep -v '^$' 文件名 |grep -v '^#' 2.如何查阅系统上面正在运作当中的程序呢? 利用静态的 ps 或者是动态的top,还能以 pstree 来查阅程序树之间的关系! 一个是叧能查阅自己 bash 程序癿『 ps -l 』一个则是可以查阅 所有系统运作癿程序『 ps aux 』 [[email protected] ~]#

Linux下搭建iSCSI共享存储的方法 Linux-IO Target 方式CentOS7-1810下实现

iSCSI(internet SCSI)技术由IBM公司研究开发,是一个供硬件设备使用的.可以在IP协议的上层运行的SCSI指令集,这种指令集合可以实现在IP网络上运行SCSI协议,使其能够在诸如高速千兆以太网上进行路由选择.iSCSI技术是一种新储存技术,该技术是将现有SCSI接口与以太网络(Ethernet)技术结合,使服务器可与使用IP网络的储存装置互相交换资料. iSCSI分为服务端和客户端,服务端需要安装scsi target用来共享存储设备,客户端需要安装iscsi initiato

linux下查看用户及用户组的方法

whois 功能说明:查找并显示用户信息. 语 法:whois [帐号名称] 补充说明:whois指令会去查找并显示指定帐号的用户相关信息,因为它是到Network Solutions 的WHOIS数据库去查找,所以该帐号名称必须在上面注册方能寻获,且名称没有大小写的差别.    whois功能说明:查找并显示用户信息.语 法:whois [帐号名称]补充说明:whois指令会去查找并显示指定帐号的用户相关信息,因为它是到Network Solutions 的WHOIS数据库去查找,所以该帐号名