前言

对于 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

使用mprotect定位踩内存故障相关推荐

  1. 用mprotect定位踩内存问题

    mprotect note -v0.1 2019.6.4 Sherlock init Linux用户态程序踩内存时可以用mprotect定位,mprotect本身是linux系统上的一个系统 调用,这 ...

  2. linux 定位 踩内存_记录一次用户态踩内存问题

    这几天在做总结,把三年前写的一个定位案例,翻了出来.回想起定位这个问题时的场景,领导催得紧,自己对很多东西又不熟悉,所以当时面临的压力还是很大的.现在回想起来感慨还是很多的,我们在遇到任何一个问题,一 ...

  3. linux 定位 踩内存_运维必备的问题定位工具及案例分析

    [摘要]本文主要介绍各种问题定位的工具,并结合案例分析问题. 1. 背景 有时候会遇到一些疑难杂症,并且监控插件并不能一眼立马发现问题的根源.这时候就需要登录服务器进一步深入分析问题的根源.那么分析问 ...

  4. linux 定位 踩内存_互联网线上系统故障定位方法论

    背景 有时候会遇到一些疑难杂症,并且监控插件并不能一眼立马发现问题的根源.这时候就需要登录服务器进一步深入分析问题的根源.那么分析问题需要有一定的技术经验积累,并且有些问题涉及到的领域非常广,才能定位 ...

  5. 一种踩内存的定位方法(C++)

    在嵌入式应用开发过程中,踩内存的问题常常让人束手无策.使用gdb调试工具,可以大幅加快问题的定位.不过,对于某些踩内存的问题,它的表现是间接的,应用崩溃的位置也是不固定的,这就给问题定位带来了更大的困 ...

  6. linux踩内存怎么定位,问题定位:内存泄漏,踩内存。

    1.内存泄漏 确定现象: linux 内存泄漏,可以查看slabinfo 和另外一个proc下(貌似meminfo),关于内存的信息,可以看到内存是否在不断减少,以及减少的速度. vxworks系统, ...

  7. 记一次《C语言踩内存》问题定位有感

    踩内存问题,个人认为算是比较容易出现但是有很难定位的问题,被踩者轻者功能瘫痪,重者一命呜呼,直接诱发死机.产生踩内存的的原因也比较多样,比较典型的有如下几种: 数组越界访问 字符串越界操作 直接操作野 ...

  8. 踩内存是什么意思啊_面试|搬了这么久的砖,居然还不知道什么“踩内存”

    摘要:你是否在总是听到"内存越界","指针指向了非法地址"等常见问题呢?但是在面试过程中总有一些学术严谨(装13)的面试官给这一类问题取个名字-踩内存.如果你没 ...

  9. linux 程序收到sigsegv信号_linux下定位多线程内存越界问题实践总结

    最近定位了在一个多线程服务器程序(OceanBase MergeServer)中,一个线程非法篡改另一个线程的内存而导致程序core掉的问题.定位这个问题历经曲折,尝试了各种内存调试的办法.往往感觉就 ...

最新文章

  1. 使用Emit的TypeBUilder动态创建接口程序集的性能报告。
  2. 虚拟主机跟php,php虚拟主机和服务器(云服务器跟虚拟主机)
  3. Python:列表、集合等交集、并集、差集、非集简介及其代码实现之详细攻略
  4. 2.2 Spring属性注入-构造方法
  5. bzoj 3456: 城市规划【NTT+多项式求逆】
  6. C#读写xml文件应用
  7. Spark官方调优文档翻译(转载)
  8. 给不会调用C++STL库中二分函数lower_bound,upper_bound,binary_search同学的一些话!
  9. [flask 优化] 由flask-bootstrap,flask-moment引起的访问速度慢的原因及解决办法
  10. [Prodinner项目]学习分享_第二部分_Entity到DB表的映射
  11. 深入浅出 Golang 协程池设计
  12. Linux运维基础入门(二):网络基础知识梳理02
  13. HTTP1.0/1.1/2.0特性对比_转
  14. AVEVA InTouch安全网关 AccessAnywhere 任意文件读取漏洞 CVE-2022-23854
  15. 网页html代码大全
  16. 北京超级云计算中心操作训练指南
  17. 数据库概念设计与逻辑设计
  18. 2022第十一届PMO大会(线上会议)成功召开
  19. 袁春风老师:计算机系统基础(一) 第一章
  20. 简单介绍控制理论(经典、现代)

热门文章

  1. DRL实战:DDPG A3C | Gym环境中经典控制问题Pendulum-v0
  2. 谷歌浏览器无法打开localhost:3000,打开localhost就跳转测试地址问题
  3. Netscreen + Squid (Transparent) + c-icap + ClamAV
  4. v-model的实现原理
  5. iPhone/iPad使用A-Shell免越狱运行SQLmap等渗透工具【持续更新】【A-Shell使用】【渗透工具】【待完善】
  6. VB封装的WebSocket模块,拿来即用
  7. IP数据库的比较和选择
  8. [Android]-SDK QQ微信登入
  9. 数据挖掘与python实践心得体会_2年数据挖掘服务工作心得体会
  10. Translatium for Mac(Google在线翻译工具)的使用说明