GDB调试段错误导致应用程序异常终止

本文详细介绍了如何使用GDB工具调试因堆栈溢出和缓冲区溢出导致的段错误。内容包括设置核心转储文件、分析栈帧信息、检查CPU寄存器状态以及通过反汇编理解函数调用过程。通过对递归调用和栈区缓冲区溢出的实例,展示了如何找出触发段错误的原因,并探讨了栈区数据如何影响程序执行流程。
摘要由CSDN通过智能技术生成

使用gdb工具调试几种发生段错误导致应用程序异常终止的现象

设置生成核心转储文件

保存崩溃时的进程映像,用于结合程序复现崩溃场景。
ulimit为shell内建指令,可用来控制shell执行程序的资源。

在这里插入图片描述
我们需要修改的是核心转储文件大小限制:
ulimit -c unlimited
注意程序堆栈资源为8M,下面会针对堆栈溢出场景做分析,当然这个值也可以使用-s参数手动修改。

gdb调试堆栈溢出导致应用程序段错误

局部变量消耗栈区空间

当栈区资源消耗>8M时会发生段错误,触发核心转储。在这里插入图片描述

$gdb sof_localvar -c core
启动gdb结合core文件对应用程序崩溃进行调试,bt查看下栈帧信息,i r( info registers 的简写)查看下cpu寄存器的值。
在这里插入图片描述

很明显rbp和rsp之间的差值过大,表示当前栈帧消耗很多栈区资源,具体消耗多少可以打印查看。在这里插入图片描述

定义局部变量消耗栈区空间,记得要对变量进行初始化,否则不会写到实际的物理页面,也不会触发段错误。
在这里插入图片描述

函数递归调用消耗栈区空间

以下代码实现的功能是递归调用sum_till_max实现0到程序输入参数的连续自然数求和。
没有参数输入或输入不为数字默认为1UL << 20。

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

#define MAX (1UL << 20)

typedef unsigned long long u64;
typedef unsigned int u32;
u32 max_addend = MAX;

u64 sum_till_max(u32 n)
{
        u64 sum;
        n++;
        sum = n;

        if (n < max_addend)
                sum += sum_till_max(n);
        return sum;
}


int main(int argc, char **argv)
{
        u64 sum = 0;
        if ((argc == 2) && isdigit(*argv[1]))
                max_addend = strtoul(argv[1], NULL, 0);
        sum = sum_till_max(0);
        printf("sum = %llu\n", sum);
                
        return 0;
}

编译执行后触发核心转储,使用gdb结合core file对此问题进行调试分析,先查看下堆栈信息。(因为递归调用输出的调用信息太多,简单输出最近的调用信息)
在这里插入图片描述
n会在增加到哪个值触发段错误,可以结合单个栈帧所消耗的栈区空间大致估算。
查看frame 0:
(gdb) p $rbp
$1 = (void *) 0x7ffd9b941020
(gdb) p $rsp
$2 = (void *) 0x7ffd9b941000
查看frame 1:
(gdb) f 1
#1 0x000000000040116d in sum_till_max (n=174552) at sof_digui.c:18
18 sum += sum_till_max(n);
(gdb) p $rsp
$3 = (void *) 0x7ffd9b941030
(gdb) p $rbp
$4 = (void *) 0x7ffd9b941050
如此来看每一次函数调用消耗0x30的栈区空间
p/x 174553 * 48
$5 = 0x7fd8b0
嗯,接近8M了,算上函数执行前的一些初始化函数调用,也会消耗一些栈区空间。

下面通过gdb详细分析下函数调用,看看栈区存储了哪些值。
可以看出在20-30,50-60之间先将函数返回地址压栈(40116d),再保存上一级函数的栈帧(push rbp)
在这里插入图片描述
至于栈区存放的数据是如何产生,我们结合具体函数的反汇编看一看。
根据x86_64函数调用时传递参数的规律(整型和指针型的参数会从左到右依次保存到 rdi rsi rdx rcx r8 r9中,浮点型参数保存在xmm0 xmm1…中,多余这些寄存器的参数保存在栈上),函数参数u32 n通过edi寄存器传入。
简述汇编代码执行流程:保存当前栈环境->拓展0x20字节栈空间->将传入参数拷贝到栈区空间并自增(参考frame 0的栈帧信息,移动至栈区0x7ffd9b941020 - 0x14 = 0x7ffd9b94100c,对应的栈区数据为0x0002a9d9,即参数n=174553的16进制)->转为u64类型存放到u64 sum变量对应的栈区空间(0x7ffd9b941018存放0x000000000002a9d9)->比较全局变量max_addend和n的大小,若大于等于则将sum保存至rax返回,若小于则将自增后的n作为参数传入下一次函数调用。
在这里插入图片描述

gdb调试堆栈缓冲区溢出导致应用程序段错误

以下代码在栈区创建了一个四字节的buff缓冲区,将hello world!填充到缓冲区,造成栈空间缓冲区溢出,但却不会导致段错误的发生,让我们先来分析下原因。

#include <stdio.h>
#include <string.h>

int main()
{
        char buff[4];
        char *p = "hello world!";
        char *rec = strcpy(buff, p);
        return 0;
}

在这里插入图片描述
先看下main函数的反汇编代码

在这里插入图片描述
简述汇编代码执行流程:将栈区申请的buff空间地址存放在rdi,将helloworld位于代码段中的地址存放在rsi,作为参数传给下级函数调用strcpy。
先看下代码段中存放的字符串内容:
(gdb) x/s 0x402004
0x402004: “hello world!”
栈区空间的内容:
(gdb) p/x $rbp - 0x14
$4 = 0x7fffffffcf3c
(gdb) x/s 0x7fffffffcf3c
0x7fffffffcf3c: “”
函数调用前的栈空间内容:
在这里插入图片描述
将断点设置在strcpy函数之后获取之后的栈空间内容b *0x0000000000401145
在这里插入图片描述
我们可以看的到缓冲区溢出确实对当前栈空间中的内容进行了修改,但却不会打乱函数执行的流程。
正常调用下一级函数时,硬件会自动先将指令返回地址压栈,下一级函数将本级函数的栈帧保存至栈区空间。但此处0x7fffffffcf50 对应的rbp=0x0000000000401150 rip=0x00007ffff7e1109b,乍看着实奇怪,0x0000000000401150更像是代码段而非堆栈段,0x00007ffff7e1109b更像是堆栈段而非代码段。反汇编看了下0x00007ffff7e1109b中的内容,对照进程空间映射表,原来是C库被重定位到了这个地方…
在这里插入图片描述
在这里插入图片描述
而我们只需要破坏这个地址,就可以破坏程序的运行流程。现在增大缓冲区溢出的长度,再试一次,找找界限在哪里。
我在本地机器上验证,当覆盖到0x7fffffffcf50-0x7fffffffcf58,还没有覆盖到rip时就会触发段错误。
在这里插入图片描述
利用栈区缓冲区溢出,我们可以修改函数返回的指令地址,或者修改上一级函数栈基址,注入我们的自己的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值