首页 > 系统相关 >使用mprotect定位踩内存故障

使用mprotect定位踩内存故障

时间:2023-04-19 18:13:01浏览次数:44  
标签:gInst mprotect unsigned 故障 Inst 内存 subInst

前言

对于 C 语言来说,内存被踩是比较常见的问题,轻则普通变量被改写程序逻辑出错,重则指针变量被改写引发指针解引用出现未定义行为风险;

定位内存被踩一直是棘手的难题,如果出现程序跑死,一般可以通过堆栈信息来定位:
1)查看跑死的调用链,确定跑死代码的位置;
2)根据pc指针找到具体代码;
3)走查代码分析问题;

但是这种方法有个先天的劣势:程序跑死的点和内存被踩的点往往不在同一个地方,需要分析代码寻找真正的问题点。如果程序只是逻辑出错没有跑死,定位起来会更加困难。

有没有方法可以让程序告诉我们是谁踩了内存呢?

这里分享一种借助 mprotect 函数定位内存被踩的方法。

1.mprotect介绍

mprotect 是 linux 系统中用于修改一段指定内存区域保护属性的函数,其原型是:

#include <unistd.h>
#include <sys/mman.h>

int mprotect(const void* start, size_t len, int prot)

其中 start 是被保护内存的起始地址,len 是被保护内存的长度,prot 是内存的保护属性,常见的属性有:

保护属性 说明
PROT_READ 内存可读
PROT_WRITE 内存可写
PROT_EXEC 内存可执行
PROT_NONE 内存不可访问

需要注意的是,mprotect 函数在使用上有限制:

  • start 指向的内存地址要求是一个内存页的首地址;
  • len 需要是内存页的整数倍;

关于内存页的这里不多做介绍,有兴趣的可以看看其他博文的介绍,需要知道的是一般内存页是按 4096 字节(4KB)为单位对齐的。

2.举个栗子

下面以一个实际的例子来说明 mprotect 的使用方法。
定义以下结构体、变量和函数:

#define  MAX_ARRAY_SIZE (4096)

typedef struct SubInst
{
	unsigned char flag;
}SubInst;

typedef struct Inst
{
	unsigned char array[MAX_ARRAY_SIZE];
	SubInst*      subInst;
}Inst;

Inst* gInst = NULL;

void CreateInst()
{
	// 假设 malloc 不会失败,假设 gInst 和 gInst->subInst 不会为 NULL;
	gInst = (Inst*)malloc(sizeof(Inst));
	gInst->subInst = (SubInst*)malloc(sizeof(SubInst)); 
}

void DoSomething()
{
	unsigned char* ptr1 = (unsigned char*)gInst;
	unsigned int*  ptr2 = (unsigned int*)(ptr1 + MAX_ARRAY_SIZE);
	*(ptr2) = 0;
}

void PrintInst()
{
	printf("[Inst] flag : %u\n", gInst->subInst->flag);
}

int main()
{
	CreateInst();
	DoSomething();
	PrintInst();
	return 0;
}

很容易就可以看出在 DoSomething() 函数中由于指针偏移错误,改写了指针 subInst 的值为 0, 所以在 PrintInst() 中打印时出现空指针访问,引起程序跑死。

根据调用链可以得到以下段错误信息:

显然,根据 coredump 信息只能看到程序跑死在 subInst 解引用时出现问题。

如果提示缺少 glibc 但安装不上,需要修改一下 etc/yum.repos.d/CentOS-Linux-Debuginfo.repo 中enabled 的值为1。

coredump 信息缺失的话请检查 ulimit -c,可以修改 etc/profile,添加 ulimit -S -c 0 > /dev/null 2>&1,记得 source etc/profile;

3.mprotect使用方法

下面来看看 mprotect 是如何帮助我们找到问题点的。

首先改写代码如下

#define  MAX_ARRAY_SIZE (4096)

typedef struct SubInst
{
	unsigned char flag;
}SubInst;

typedef struct Inst
{
	unsigned char array[MAX_ARRAY_SIZE];
	unsigned char pzone[MAX_ARRAY_SIZE];
	SubInst*      subInst;
}Inst;

Inst* gInst = NULL;

void CreateInst()
{
	// 假设 posix_memalign, malloc, mprotect 不会失败
	// 假设 gInst 和 gInst->subInst 不会为 NULL;
	size_t pagesize = sysconf(_SC_PAGESIZE);
	posix_memalign((void**)gInst, pagesize, sizeof(Inst));
	gInst->subInst = (SubInst*)malloc(sizeof(SubInst)); 
	
	mprotect(gInst->pzone, pagesize, PROT_READ);
}

void DoSomething()
{
	unsigned char* ptr1 = (unsigned char*)gInst;
	unsigned int*  ptr2 = (unsigned int*)(ptr + MAX_ARRAY_SIZE);
	*(ptr2) = 0;
}

void PrintInst()
{
	printf("[Inst] flag : %u\n", gInst->subInst->flag);
}

int main()
{
	CreateInst();
	DoSomething();
	PrintInst();
	return 0;
}

解释一下几个关键点

sysconf(_SC_PAGESIZE) 返回当前操作系统的内存页大小,一般是 4096 字节;

posix_memalign 函数申请内存,它与 malloc 的区别是会将申请的内存按要求的长度对齐并且返回的内存地址是一个内存页的首地址,函数原型:

#include <stdlib.h>
int posix_memalign(void** memptr, size_t alignment, size_t size);

其中
memptr 是个2级指针,指向存放申请内存地址的指针变量的指针;
alignment 是期望对齐的内存长度;
size 是申请的内存大小。

前面说过,mprotect 要求被保护的内存是完整的内存页且 4KB 对齐,所以我们在被踩的内存 subInst 指针前加入了一段 4KB 大小的内存 pzone,并且使用 mprotect 将这段内存设置为 只读

typedef struct Inst
{
	unsigned char array[MAX_ARRAY_SIZE];
	unsigned char pzone[MAX_ARRAY_SIZE];
	SubInst*      subInst;
}Inst;

再次执行上面的程序,这次程序很直接的就告诉了我们内存被踩的案发现场。

4.总结

上面结合例子分享了一种使用 mprotect 定位被踩的方法,例子举的比较简单,所以在一些更为复杂的代码中效果会更明显,核心思想是:

  • 在被踩的内存前添加一段“替死鬼”内存,并在上面设置“陷阱”揪出踩内存的罪魁祸首:被保护的内存是只读属性,发生内存被写则中断操作。

当然这种方法也有它的局限性:
1)对内存的分配更为严格,对于非动态申请的内存存在修改代码上的困难;
2)占用更多的内存;

根据不同情况选择合适的定位方法才是我们需要掌握的技巧,有方法总比没有方法好:D

 

标签:gInst,mprotect,unsigned,故障,Inst,内存,subInst
From: https://www.cnblogs.com/lidabo/p/17334207.html

相关文章

  • GE反射内存实时通讯网络解决方案
    时通讯网络是用于需要较高实时性要求的应用领域的专用网络通讯技术,一般采用基于高速网络的共享存储器技术实现。它除了具有严格的传输确定性和可预测性外,还具有速度高、通信协议简单、宿主机负载轻、软硬件平台适应性强、可靠的传输纠错能力、支持中断信号的传输等特点。本方案选......
  • Linux内存管理之mem_map对象.md
    在linux内核中,所有的物理内存都用structpage结构来描述,这些对象以数组形式存放,而这个数组的地址就是mem_map。内核以节点node为单位,每个node下的物理内存统一管理,也就是说在表示内存node的描述类型structpglist_data中,有node_mem_map这个成员,其针对平坦型内存进行描述(CONFIG_FL......
  • CANN开发实践:4个DVPP内存问题的典型案例解读
    摘要:由于DVPP媒体数据处理功能对存放输入、输出数据的内存有更高的要求(例如,内存首地址128字节对齐),因此需调用专用的内存申请接口,那么本期就分享几个关于DVPP内存问题的典型案例,并给出原因分析及解决方法。本文分享自华为云社区《FAQ_DVPP内存问题案例》,作者:昇腾CANN。DVPP是昇腾......
  • BFD故障双向检测
    配置静态路由多跳检测模拟搭建拓扑图路由器R1,R2,R3接口配置IP地址查看路由表在R1访问192.168.23.0/24网络配置静态路由,在R1配置静态路由访问192.168.20.0/24网络配置静态路由在R2配置静态路由,访问192.168.10/24在R3配置静态路由访问192.168.20.0/24测试全网互通在路由器R1和......
  • 实时查看Docker容器占用的CPU、内存状态
    安装Linux下安装方法:wgethttps://github.com/bcicen/ctop/releases/download/v0.5/ctop-0.5-linux-amd64-Octopsudomvctop/usr/local/bin/sudochmod+x/usr/local/bin/ctop执行命令:ctop使用ctop运行后,通过下面的按键可以实现不同的功能1)a-只查看运行状态的容器f-......
  • 在Go语言中,如何优化内存使用效率?
    在Go语言中,可以通过以下几种方式来优化内存使用效率:避免使用过多的内存尽可能地避免使用过多的内存是最有效的内存优化方法之一。在编写代码时,应该尽可能地避免使用全局变量和大量的临时变量。同时,可以使用常量、静态变量和缓存等方式来避免频繁地分配和释放内存。及时释放不......
  • 【valgrind】软件调试工具-valgrind内存调试工具
    valgrind工具安装Ubuntu环境安装sudoaptinstallvalgrind源码编译1.源码下载http://valgrind.org/downloads/valgrind-3.12.0.tar.bz22.valgrind编译安装tar-jxvfvalgrind-3.12.0.tar.bz2cdvalgrind-3.12.0./configuremakesudomakeinstallvalgrind运行分析程......
  • SQL Server占用内存不释放卡死问题
      最近项目中发现使用SQLServer的机器会出现10天左右占满内存卡死情况,百度后发现对应的原因如下:    即:SQLServer内存管理是分配了最大内存是多少,就会使用多少,在再次使用的时候,才会释放掉空闲的内存,它不会主动全部释放掉所有空闲内存。所以解决方式是:在sqlSe......
  • 内存屏障--- asm volatile("" ::: "memory")
    转载:(14条消息)内存屏障---asmvolatile("":::"memory")_"asm(:::\"memory\")"_咕噜咕噜斯基的博客-CSDN博客CompilermemorybarrierThesebarrierspreventacompilerfromreorderinginstructions,theydonotpreventreorderingbyCPU.T......
  • VM虚拟化学习四——虚拟机CPU和内存动态扩容
    1.CPU动态扩容  1.1创建虚拟机配置CPU数 [root@linux-node4~]#virt-install--help|grepcpu    #创建虚拟机的时候可以配置CPU --vcpusVCPUS    Numberofvcpustoconfigureforyourguest.Ex: --vcpus5          ......