设为首页 加入收藏

TOP

应用程序调试总结(二)
2017-10-12 17:41:04 】 浏览:6513
Tags:应用程序 调试 总结

  另一种方法是利用env MALLOC_CHECK_=1 ./a.out 来运行程序,但有的情况下不指定环境变量,在双重释放指针时也会打印出堆栈信息,反而加了环境变量没有打印出堆栈信息。但个人觉得这只是说明原因是双重释放,还是坚持前一种方法,找到释放的两个位置,只保留一个释放点。

5.死锁

  当造成死锁时,先使用ps命令查看下线程状态,如果状态是S的话,就有可能说明是死锁了。

  这个时候再使用gdb attch上去,查看各个线程的堆栈,看卡在哪一个线程中。

  然后再利用gdb设置断点和脚本,打印出同一把锁被操作的过程。下面看个例子

   源码:

  

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int cnt = 0;
void cnt_reset(void)
{
pthread_mutex_lock(&mutex);
cnt = 0;
pthread_mutex_unlock(&mutex);
}

void *th(void *p)
{
while(1){
pthread_mutex_lock(&mutex);
if(cnt > 2)
cnt_reset();
else
cnt++;
pthread_mutex_unlock(&mutex);

printf("%d\n",cnt);
sleep(1);
}
}

int main()
{
pthread_t id;
pthread_create(&id,0,th,0);
pthread_join(id,0);

return 0;
}

  运行结果:

[root@ubuntu: deadlock]./a.out
1
2
3

发现程序不跑了,根据程序接下来应该打印出0。

[root@ubuntu: deadlock]ps -x | grep a.out
Warning: bad ps syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html
26418 pts/9 Sl+ 0:00 ./a.out

可以看出程序现在处于睡眠状态,那么使用gdb attch上去,查看是哪一个线程在睡眠或者说导致了死锁。

可以看出主线程是处在睡眠中,在等待子线程的结束,而子线程睡眠在了等待锁的释放上,那么现在问题就在于为什么锁是在哪一步或者哪个线程先拿到了,而导致当前线程拿不到锁。

使用gdb重新调试程序,并且在加锁和释放锁的位置设置断点,打印出堆栈,可以发现前面一直都是加锁解锁对应的,而在最后一对打印中两个操作都是加锁

根据这个堆栈信息可以知道th函数先加了一次锁,然后th函数本身调用了cnt_reset函数,该函数再一次加锁导致了死锁。

所以现在就找到原因了。

这是一个较为简洁的例子,我在工作中遇到过一次较为麻烦的问题,如下:多线程之间对于一个数据结构的访问,需要首先拿到保护该结构的锁,问题出在了当某一个线程拿到锁之后还没有释放锁,该线程就被杀死了,而此时其他线程就再也无法获取到该锁,导致所有线程堵死。同样通过上述方式可以找到原因。

6.死循环

  这个情况我自己模仿书上的例子,创建了一个类似的例子

  源码:

  

#include <stdio.h>

int fun(char *p,int len)
{
while(len > 0){
int version = *(int *)p;
int msgtype = *(int *)(p+sizeof(int));
int length = *(int *)(p+sizeof(int)+sizeof(int));
/*do something*/
len = len - length;
p = p + length;
}
}

int main()
{
char p[100];
int len = 0;
int version = 1;
int type = 10;
int length = 0;
memset(p,0,100);
memcpy(p,&version ,4);
memcpy(&p[4],&type,4);
memcpy(&p[8],&length,4);
length = 10;
memcpy(&p[12],&version ,4);
memcpy(&p[16],&type,4);
memcpy(&p[20],&length,4);

fun(p,30);
return 0;
}

fun函数是用来解析消息的一个函数。有些类似于tcp,是基于流的方式来解析数据包。

但是现在在运行时发生了死循环。即执行程序之后就不会退出。

gdb attach上该进程之后,发现是在fun函数里面,那么查看源码知道fun就只有一个循环。那么现在使用debug版本的可执行程序,单步调试该程序。

可以发现,消息体的长度一直为0,这个问题导致了,一直在解析同一个消息。那么问题就确定了,发送的消息长度有问题,所以在函数中解析到长度字段时,应该比较长度字段至少大于多少。

 三。总结

  首先要熟练运用gdb中的各种工具,包括查看寄存器,堆栈,断点,监视点和脚本等。

  一般来讲调试过程是,收集信息,包括现象和dump信息。分析dump信息,复现bug,修复bug。

  栈溢出:结合sp和程序map信息。

  返回地址被修改:堆栈异常基本属于返回地址被修改,将sp中的内容打印出来,以各种方式打印,字符型或者十六进制等等。可能会发现比较眼熟的结果打印,比如明显是一个字符串,这时候对错误的定位就很容易了。

  非法内存访问:某个跳转地址是存放在一个指针中的,这个指针中的值被修改了,也就导致了后续的跳转出现了非法。这个时候可以在这个指针上设置监视点,打印访问该监视点时的堆栈。

  双重释放:还是利用监视点或者断点,确定哪两次释放。

  死锁:同上,确定哪两步拿锁冲突。

  死循环:确定当前死循环位置,最好使用debug版本单步调试。

  

 

首页 上一页 1 2 下一页 尾页 2/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇61. Rotate List 下一篇nginx新增模块

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目