GDB分析数组索引错误导致Segmentation fault

GDB分析数组索引错误导致Segmentation fault

最近学习gdb调试的知识,使用gdb调试core文件分析C语言数组索引错误所引发的segmentation fault原因。

分析前准备

设置core dump的大小。默认的core dump大小是0字节,也就是出现了segmentation fault是看不到Core转储文件的。

查看core默认大小
ulimit -c

在这里插入图片描述
修改core dump的转储大小为无限制,也可以指定为固定大小,这里使用无限制的方式

ulimit -c unlimited
编写测试的程序,如下

GDB分析数组索引错误导致Segmentation fault_第1张图片
从源码中可以看到,这里的calc_index返回了负值的索引,根据数组的索引规则,下标从0开始,最大索引值为数组长度-1。这里的-7不符合数组索引的规则,因此程序运行起来将会导致内存错误。主要是数组访问或修改了不属于自己的内存信息,可能是修改了某地址或者栈数据导致。下面就通过gdb具体看看失败的原始是什么。

编译程序并执行
gcc -g source.c
执行
./a.out

使用-g 选项在适应gdb调试时可以获取符号信息。执行之后会得到segmentation fault错误信息,如下
在这里插入图片描述
查看当前目录下会生成一个core文件,如下

ls 
file core

GDB分析数组索引错误导致Segmentation fault_第2张图片

使用gdb调试core文件

使用gdb加载调试

gdb -c core ./a.out

加载成功后,会提示出现失败的原因,如下
GDB分析数组索引错误导致Segmentation fault_第3张图片
这时看一下栈回溯,发现gdb也不能识别到原因,如下
GDB分析数组索引错误导致Segmentation fault_第4张图片

调试看看具体原因

由于此时的栈回溯不能得到有用信息,说明栈的数据被破坏了,因此只能调试的方式分析看看是哪里导致的失败,如果能看到栈回溯信息,可以快速定位到具体位置查看。在main下设置断点,让程序运行起来,分析下汇编指令,如下
GDB分析数组索引错误导致Segmentation fault_第5张图片
由于不知道在哪里运行失败,通过单步执行的方式看一下修改了数组my_data时是否会造成内存程序运行失败,根据常识,一般数组访问了不属于它的内存可能会导致内存错误。关键指令
GDB分析数组索引错误导致Segmentation fault_第6张图片
当程序来到修改my_data的第一个位置,先看一下原始数组my_data的内存和eax的值,这里eax是index的值
GDB分析数组索引错误导致Segmentation fault_第7张图片
当继续往下后发现程序没有停止,再次观察my_data指向的内存,数据没有并改变。
那么0xa这个值是被写到了哪里?看到指令

movl 0xa,0x804a020(,%eax,4)

换算之后addr = 0x804a020+eax4,eax=-7,因此最后结果为
addr=0x804a020+(-7x4)=0xFFFFFFE4+0x804a020=0x804A004
可以使用p/x (0x804a020+$eax
4) 得到相同的值
看一下addr的内存,如下
GDB分析数组索引错误导致Segmentation fault_第8张图片
同样可以知道,0x08会写到0x804A008(addr=0x804a020+eax * 4,eax=-6)的内存,如下
GDB分析数组索引错误导致Segmentation fault_第9张图片
发现到目前为止,程序还没有出现失败,继续往下运行,来到call puts函数位置,继续执行后,程序出现了一开始看到的SIGSEGV信号导致的segmentation fault
GDB分析数组索引错误导致Segmentation fault_第10张图片
居然是在调用了call puts之后导致了,按照猜想,应该是在修改数组时可能诱发内存错误但是真实的错误是在这个函数内部。重新运行之后,直接单步来到这里,进入后如下
GDB分析数组索引错误导致Segmentation fault_第11张图片
进来后看到这里是got@plt函数表调用
GDB分析数组索引错误导致Segmentation fault_第12张图片
首先函数调用了0x804a00c,直接看一下这里的汇编指令,如下
GDB分析数组索引错误导致Segmentation fault_第13张图片
只是校验了一下数据。
继续单步执行,执行了push 0 之后,执行了跳转函数,观察另一个0x804a82e0地址处的指令时提示没有函数
GDB分析数组索引错误导致Segmentation fault_第14张图片
单步进入到0x804a82e0后,继续执行两次触发了SIGSEGV这个异常信号。此时出现了相同的栈信息
GDB分析数组索引错误导致Segmentation fault_第15张图片
到这里就能明确了,执行put函数时,程序上的某个位置栈信息被破坏掉,原本要修改数组的数据被写到了别的栈位置,导致了运行的数据栈数据被破坏导致segmentation fault。(好吧,和我预想的不太一样,原来程序运行起来后如果修改的不是自己当前内存区域,而是别的内存,在定位问题时就不太好明确具体位置。)

那么原本正确的数据该是如何

根据上面的调试可以发现,数据被写到了别的位置破坏了别的数据栈,刚好那个栈数据可以被写入,导致了运行失败,那么如果是正确的应该是如何的?这里做了一个对比,将上面calc_index函数返回0,然后观察call puts函数内部的指令时,如下
GDB分析数组索引错误导致Segmentation fault_第16张图片
编译运行后,正常修改的数组
GDB分析数组索引错误导致Segmentation fault_第17张图片
直接来到call指令位置,进入后如下
GDB分析数组索引错误导致Segmentation fault_第18张图片
从截图中可以看到,这里的指令和错误的情况一样,进入到0x804a82e0内部观察到如下
GDB分析数组索引错误导致Segmentation fault_第19张图片
如上可以看到,正常的程序会调用_dl_runtime_resolve函数,通过_dl_runtime_resolve获取到_IO_puts函数,最后输出"This is a message"如下
GDB分析数组索引错误导致Segmentation fault_第20张图片
执行了很多次单步运行后,看到了输出字符串信息
GDB分析数组索引错误导致Segmentation fault_第21张图片

总结

当程序发生了segmentation fault后,通过core dump文件可以得到运行时发生错误的信息,使用gdb的backtrace的方式看看函数调用关系,如果能得到有用信息就从backtrace中定位,如果没有得到有效的backtrace信息,则通过在函数入口设置断点方式调试分析。在调试过程中理解数组索引错误所引起的内存失败问题,逐步理解一个简单的Bug在实际的汇编指令运行原理。

你可能感兴趣的:(x86)