设为首页 加入收藏

TOP

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

总结一下对应用程序出现segment fault时的基础和调试方法,知识来自debug hacks一书

环境,x86 32位linux

一.基础

1.熟悉参数的传递方式。

  在进入被调用函数之前,程序会按照参数,返回地址,fp指针(帧指针),被调用函数的局部变量,的次序压栈。

  源码:

  #include <stdio.h>

  int fun(int a,char c)

  {

    printf("%d\n%c\n",a,c);

    return a;

  }

 

  int main()

  {

    fun(1,'a');

    return 0;

  }

  使用gdb调试该程序:

  在函数名前加上*号,程序遇到断点时,会卡在函数汇编语言层次的开头。如果不加*,会停在函数的第一句话。
  函数在跳转之前会把需要传递的变量和返回地址压入栈,而剩余的变量由被调用函数压栈。所以此时,sp指针指向的是返回地址,另一个我们知道栈是向下增长的,所以sp+4就是压入的第2个参数(a),sp+8是压入的第1个参数(c),如下图。
  
2.core文件的生成
  一般linux系统默认是不生成core文件的,可以通过ulimit -c查看。如果显示0,则调用ulimit -c unlimited 设置为没有上限,当然也可以设置一个具体的值,单位为blocks。
  注意:必须确保有权限在该目录下生成core文件,因为我们很多工作的时候是将本地文件挂载到linux服务器上或者虚拟机上,如果不是有权限的用户登录的话,是不会在该目录下生成core文件的,或者生成的core文件大小为0。
3.gdb的常用命令
  可以查看我的上一篇总结。
二.调试实践
1.栈溢出
  源码:

#include <stdio.h>
int fun()
{
int a = 10;
fun();
printf("%d\n",a);
return 1;
}

int main(int argc,char **argv)
{
fun();
return 0;
}

  发生段错误,利用生成的core文件,查看sp的指针大小
     可以看到sp=0xbf45a000;
     再查看各个段的大小,使用i files命令,虽然看不出哪个段是stack,ps:不知道为何无法上传图片。那我就打字了。
  如下:

Local core dump file:
`/root/core', file type elf32-i386.
0x0084e000 - 0x0084e000 is load1
0x009a1000 - 0x009a1000 is load2
0x009a2000 - 0x009a4000 is load3
0x009a4000 - 0x009a5000 is load4
0x009a5000 - 0x009a8000 is load5
0x00d68000 - 0x00d69000 is load6
0x00d87000 - 0x00d87000 is load7
0x00da2000 - 0x00da3000 is load8
0x00da3000 - 0x00da4000 is load9
0x08048000 - 0x08048000 is load10
0x08049000 - 0x0804a000 is load11
0x0804a000 - 0x0804b000 is load12
0xb775e000 - 0xb775f000 is load13
0xb776d000 - 0xb776f000 is load14
0xbf45a000 - 0xbfe5a000 is load15

      可以看出0xbf45a000 属于段15,明显已经位于了这个段的末尾,因为sp自减时并不检查sp是否超过了范围,当访问时才会知道这个地址是否合法,所以可以确定是栈溢出。

  很多大型的程序,当程序抛出段错误的信号时,会有处理程序接收这个信号,但是这个时候栈上已经没有空间了,是不可能让这个处理函数正常结束的,所以需要提前为这个函数申请好栈空间,确保能把当时的情形保留下来,可以使用sigaltstack函数在堆上申请备用栈。具体的用法请man一下

2.返回地址被修改

  返回地址被修改的情况很多,根据之前的栈空间压栈顺序,如果被调用函数的局部数组越界就可以将返回地址覆盖,导致段错误的发生,这是一种。重点是我们要怎么知道发生了返回地址被修改,而且此时的局部变量也可能是不正确的,很难调试。一般来讲如果发生返回地址被修改,bt中的信息会是这样的。

  我们知道正常情况下,应该是显示函数名称而不是问号,(如果修改之后的地址还是指向某个函数的话,那就只能一步步查看下去,是否存在这么一个调用顺序)。此时是可以确定返回地址被修改了的。

  具体将一个如果是数组越界导致的返回地址被修改的情形。

  源码:

#include <stdio.h>
#include <string.h>
char names[] = "book cat dog building vagetable curry";
void fun()
{
char buf[5];
strcpy(buf,names);
}

int main(int argc,char **argv)
{
fun();
return 0;
}

      调试过程:首先查看当前运行在哪句话上。

可以看出当前运行到了ret这句话,也就是返回,那么看下sp中的值是多少。

这步有些多余,就是堆栈信息中的下一帧地址。

因堆栈信息目前怀疑的是返回地址被修改,所以查看esp中的内容,先用字符串的形式查看里面的内容

比较明显可以看出现在堆栈中的信息就是book cat dog building vagetable curry 显然是一个字符串,搜索这个字符串被引用的地方,可以发现就在源代码的第8行,复制字符串时超出了数组的长度。

 

3.利用监视点检测非法内存访问

  这个我在linux系统中无法复现出,因为越界之后的地址值是非法的,模拟出这个情况比较困难。所以这边就语言描述下。

  源程序:

  

int data[2]= {1,2};

int calc(void)
{
return -7;
}

int main()
{
int index = calc();
data[index] = 0x0a;
data[index+1] = 0x08;

printf("ssssss\n");
return 0;
}

错误发生在printf那句话中。通过查看堆栈找到main函数中的返回地址,而在这个返回地址之前的语句可能就导致了这个段错误,然后查看到之前的语句中有一句call跟踪该语句,最终会跳转到一个指针中的地址,而实际上这个指针中的地址就是0x08,也就是被程序中的语句所修改了,那么重点就在怎么确定是这句话导致的错误。

既然知道了这个指针所指向的地址,那么就可以在这个地址值出设置监视点,当这个地址处的值被修改时gdb就会停住,运行时会发现就是printf的前一句话,也就是找到了原因所在。

4.双重释放指针导致的bug

  这种错误我觉得可以设置监视点或者断点的方式,利用gdb的脚本,打印出free时的堆栈信息,然后查看哪个地址有被多重释放。

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

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目