0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux程序内存越界定位分析

科技绿洲 来源:Linux开发架构之路 作者:Linux开发架构之路 2023-11-13 11:30 次阅读

问题描述:最近在工作中遇到这样一个奇葩问题,程序里需使用一个.so库,同份源码用我电脑编译的库放到程序使用出现各种异常问题,其他同事编译出来的没问题。刚开始以为是编译方式有问题,思来想去发现并不是。经分析发现是库源代码里一全局数组内存地址大面积越界到其他全局数组了。

问题现象:现象为触发某个业务条件,将导致程序逻辑运行不正常,异常log如下图,可看出“g_s32MaxFd”变量的值(文件句柄)被置0,正常情况应该是大于0,所以此时导致整个业务运行异常。

图片

初步分析肯定是其他地方对变量“g_s32MaxFd”有赋值才会导致值为0。那么到底是代码正常逻辑语句操作还是代码内存越界引起“g_s32MaxFd”值为0呢?这个倒好定位,只需要搜索下“g_s32MaxFd”变量在代码哪些地方有使用就知道了,得出结论是代码内存越界这种情况导致。

一:开始定位内存越界处

【1】定位内存越界处,因程序并没有因为内存越界而引发segment fault退出,所以准备使用Linux中mprotect()函数来设置指定内存区域的保护属性为只读,故意使程序引发segment fault退出从而产生core dumped文件来定位问题点。

分析下面问题前最好先熟悉下mprotect()函数

思路:使用mprotect()函数对被踩变量“g_s32MaxFd”内存地址设为只读属性,由于mprotect()函数的局限性(保护属性区域的起始地址必须为操作系统一个页大小的整数倍),结合实际情况多样性,分析情况如下表述:

1、当“g_s32MaxFd”数组起始地址刚好是页大小整数倍时,此时只需要将数组起始地址设置为mprotect()函数保护属性为只读的起始地址即可,但需要注意一点,当被保护地址区域被程序正常数据结构进行访问时,也会引发segment fault退出(简而言之就是当数组“g_s32MaxFd”内存地址被设置为只读后,如果是程序正常使用时也会引发段错误退出),这种情况就无法辨别是程序正常使用还是内存越界处使用,会影响分析真正的问题点。

解决方法:可利用GNU编译器对.bss地址分配特性(具体特性自行查阅其他资料),在“g_s32MaxFd”数组地址处定义一个为页大小整数倍大小的“g_debug_place”数组,这就相当于新增的“g_debug_place”数组占用之前“g_s32MaxFd”数组的地址。如下图所示在“Var5”和“g_s32MaxFd”之间定义一个动态数组“g_debug_place”,大小最好是页大小整数倍(如果小于一个页大小会导致锁定的区域越界到“g_s32MaxFd”地址,问题得不到解决),这样既可以保证新增的“g_debug_place”数组变量只在内存越界的地方才会被访问而且数组大小也满足mprotect()函数参数长度的取值要求(页大小整数倍)。

图片

2、 当“g_s32MaxFd”数组起始地址不是页大小整数倍时,要结合上面第1种方法后还需要计算出大于且最靠近“g_debug_place”数组起始地址的页大小整数倍地址。可套用公式:

设置保护属性起始地址=被踩内存变量起始地址+(页大小-(被踩内存变量起始地址%页大小)) 注意:(被踩内存变量起始地址%页大小)等于0时不适用以上公式,也就是被踩内存变量起始地址是页大小整数倍情况下

假设“g_debug_place”数组起始地址为0x7fd8985bf8c0代入公式可得设置保护属性起始地址为0x7fd8985c0000 ,理论上只需要将地址0x7fd8985c0000设置为mprotect()函数保护属性为只读的起始地址即可,但需要注意的是此时的0x7fd8985c0000地址并不是“g_debug_place”数组起始地址,由上面公式可知这个地址是为了满足mprotect()函数的局限性而计算出来的地址。

解决方法:可通过在.bss段(之所以强调.bss段是因为我实际出现问题的变量就是未初始化的全局数组变量)首个变量地址前增加动态数组来改变内存分配解决。举个例子,就好比是排队,本来小明是排第六个,突然在队伍最前面插一个小红进来,小明就排在第七了,而小明前面之前那五个人的顺序还是不变。而这个第七就是我们程序里要的那个0x7fd8985c0000地址。

下图蓝色区域为新增动态数组(插队小红),大小为0x740字节。增加后可使“g_debug_place”数组起始地址为0x7fd8985c0000(小明第七的位置),这时将0x7fd8985c0000地址作为mprotect()函数保护属性为只读的起始地址就可以了,接下来就可以复现问题等着程序内存越界产生段错误退出吧。

注意:如果增加动态数组后并没有直观发现内存越界时,这可能是由于内存越界的字节数太小(可能只踩到一个字节或几个字节),导致调整过后的内存地址刚好踩到一个未使用的地址,这时需要微调动态数组大小来保证地址间隔及分配顺序不变,具体问题具体分析。我是没有出现这种情况,只是觉得通过这种方法分析可能会存在此风险,如果有小伙伴遇到可以留言探讨。

图片

bss段变量地址结构分布简要展示如下图(展示的是测试代码,非实际工程代码):

图片

【2】gdb分析core文件,编译可执行程序时编译选项需加-g参数,不要strip优化,否则可能会导致调试信息不是很完整。

检查core dumped是否打开

/home # ulimit -c
0
/home # ulimit -c unlimited
/home # ulimit -c
unlimited

如果找不到ulimit命令,可以用busybox sh -c 'ulimit -a’指令测试ulimit是否存在,(ulimit是busybox的内置命令,往往我们想使用tab键快捷调用ulimit时可能不会弹出)有如下log输出证明命令存在,后续直接执行ulimit -c unlimited,不要再执行busybox sh -c ‘ulimit -c unlimited’,这样是打不开core的,我就这么傻的操作过,当时还以为内核没有打开这个功能。

/home # busybox sh -c 'ulimit -a'
-f: file size (blocks)             unlimited
-t: cpu time (seconds)             unlimited
-d: data seg size (kb)             unlimited
-s: stack size (kb)                8192
-c: core file size (blocks)        unlimited
-m: resident set size (kb)         unlimited
-l: locked memory (kb)             64
-p: processes                      1982
-n: file descriptors               1024
-v: address space (kb)             unlimited
-w: locks                          unlimited
-e: scheduling priority            0
-r: real-time priority             0

分析core文件过程,如下图所示。当输出log信息不完整时,需要检查下源码和相关库文件路径是否设置好,可根据图片中标注处进行设置。(展示的是测试代码,非实际工程代码)

图片

实际代码gdb分析log如下

/home/outapp/app # …/…/gdb xxx_capture core
GNU gdb (GDB) 7.6
Copyright © 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type “show copying”
and “show warranty” for details.
This GDB was configured as “arm-hisiv300-linux”.
For bug reporting instructions, please see:
http://www.gnu.org/software/gdb/bugs/…
Reading symbols from /home/outapp/app/xxx_capture…(no debugging symbols found)…done.
[New LWP 803]
[New LWP 789]
[New LWP 798]
[New LWP 807]
[New LWP 799]
[New LWP 791]
[New LWP 832]
[New LWP 797]
[New LWP 795]
[New LWP 802]
[New LWP 809]
[New LWP 790]
[New LWP 805]
[New LWP 804]
[New LWP 808]
[New LWP 796]
[New LWP 806]
[New LWP 810]
[New LWP 831]
[New LWP 833]
[Thread debugging using libthread_db enabled]
Using host libthread_db library “/lib/libthread_db.so.1”.
Core was generated by `xxx_capture capture 660’.
Program terminated with signal 11, Segmentation fault.
#0 0xb5e63b54 in memset () from /lib/libc.so.0
(gdb) bt
#0 0xb5e63b54 in memset () from /lib/libc.so.0
#1 0xb6e63064 in xxx3520D_Sample_OsdRegShowUpdata (ps8Contenx=0xb1dc2a70 " 000KM/H ", pstRegAttr=0x32f9e9c)
at SdkLogic/xxx3520dSample/xxx3520dOsd.c:436
#2 0xb6e63930 in xxx3520D_Sample_OsdShowGpsSpeed (pstRegAttr=0x32f9e9c, u8Speed=0000’) at SdkLogic/xxx3520dSample/xxx3520dOsd.c:621
#3 0xb6e4dc14 in xxxSdkAl_OsdShowGpsSpeed (pstRegAttr=0x32f9e9c, u8Speed=0000’) at SdkAppInt/xxxAHDSdkAL.c:474
#4 0xb6cb7b50 in OsdServiec::Osd_Reg_Show() () from /hi3520/lib/libxxxxxx_hi3520_AHDOsd.so
#5 0xb6cb726c in xxx_Osd_Display(void*) () from /hi3520/lib/libxxxxxx_hi3520_AHDOsd.so
#6 0xb6fc0f6c in start_thread () from /lib/libpthread.so.0
#7 0xb5e82134 in clone () from /lib/libc.so.0
#8 0xb5e82134 in clone () from /lib/libc.so.0
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb)

小结:以上定位内存越界只是一个大体思路,实际情况多样性,具体问题还需要具体分析,个人认为如果只需要定位程序异常退出的话,用backtrace相关函数来代替gdb分析问题要轻量化很多。上述之所以使用gdb去分析问题是由于使用的交叉编译是uclibc环境(uclibc环境下backtrace函数是没实现的),就只能使用sdk提供的gdb工具了

二:为什么我电脑编译出来的库就暴露这个问题呢?

通过上面的方法已经定位到是哪行代码有bug,所以想再分析下我编译出来的库为啥就暴露这个问题了呢?分析得知是在生成.so库时由于链接.o的顺序不同导致库里面全局变量数组的地址分布也有所不同。下面分析下log文件里具体不同点,截图贴上:

qiuhui@ubuntu:/mnt/hgfs/qh/work/app/SVN/?????$ arm-hisiv300-linux-objdump -t ???/lib?????.so > log

【图一为我电脑编译的】

图片

【图二为同事电脑编译的】

图片

由上图可以观察到两个全局数组变量“gs_s8Contenx”与“g_s32MaxFd”它们的地址有前后顺序差异,图一:“gs_s8Contenx地址0xfd9e4”小于“g_s32MaxFd地址0xfed34”,图二:“gs_s8Contenx地址0xfdfd4”大于“g_s32MaxFd地址0xfdbd4”。正是由于这两个地址的前后顺序才导致我编的库暴露了问题,因为我编的gs_s8Contenx地址小于g_s32MaxFd,代码里刚好使用gs_s8Contenx数组时以超过数组元素最大值做赋值操作,从而引发大面积内存越界,导致越界地址直接就踩到g_s32MaxFd变量地址了(踩到很多全局变量了),所以g_s32MaxFd数组的值被莫名修改,从而产生各种异常。当然同事编译的同样也会使gs_s8Contenx越界,但由于gs_s8Contenx地址大于g_s32MaxFd,所以gs_s8Contenx刚好踩到的是一段不常用的地址,导致问题没有及时暴露出来。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    11235

    浏览量

    209018
  • 内存
    +关注

    关注

    8

    文章

    3008

    浏览量

    73918
  • 程序
    +关注

    关注

    116

    文章

    3779

    浏览量

    80889
  • 源码
    +关注

    关注

    8

    文章

    633

    浏览量

    29159
收藏 人收藏

    评论

    相关推荐

    深度分析Linux内存使用方法

    一提到内存管理,我们头脑中闪出的两个概念,就是虚拟内存,与物理内存。这两个概念主要来自于linux内核的支持。
    的头像 发表于 08-20 09:00 7221次阅读

    Linux kernel内存管理模块结构分析

    基于上面章节的需求,Linux kernel从虚拟内存(VM)、DMA mapping以及DMA buffer sharing三个角度,对内存进行管理.
    发表于 09-19 11:55 1757次阅读
    <b class='flag-5'>Linux</b> kernel<b class='flag-5'>内存</b>管理模块结构<b class='flag-5'>分析</b>

    Linux内存管理是什么,Linux内存管理详解

    Linux内存管理 Linux内存管理是一个非常复杂的过程,主要分成两个大的部分:内核的内存管理和进程虚拟
    的头像 发表于 05-11 17:54 5998次阅读
    <b class='flag-5'>Linux</b>的<b class='flag-5'>内存</b>管理是什么,<b class='flag-5'>Linux</b>的<b class='flag-5'>内存</b>管理详解

    想问下大家有没有遇到过 时间消息没又响应 便执行的 不是不是程序内存管理越界了?

    想问下大家有没有遇到过 时间消息没又响应 便执行的 不是不是程序内存管理越界了??
    发表于 05-25 16:29

    Linux上对进程进行内存分析内存泄漏定位

    Linux产品开发过程中,通常需要注意系统内存使用量,和评估单一进程的内存使用情况,便于我们选取合适的机器配置,来部署我们的产品。Linux本身提供了一些工具方便我们达成这些需求,查
    发表于 07-09 08:15

    Linux ARM中断向量重定位硬件平台分析

    Linux ARM 中断向量重定位分析
    发表于 07-19 12:34

    嵌入式linux内存越界定位和解决的相关资料分享

    [url=https://blog.csdn.net/meejoy/article/details/41729585https://blog.csdn.net/killmice/article/details/38443343]https://blog.csdn.net/meejoy/article/details/41729585https://blog.csdn.net/killmice/article/details/38443343[/url]转载于:https://www.cnblogs.com/erhu-67786482/p/10289256.html
    发表于 12-20 07:33

    Java程序内存低效使用问题的分析

    Java程序内存的低效使用是导致其性能问题的主要因素。该文分析了泄漏对象、蚍蜉对象和空闲对象3类导致内存低效使用的情况,探讨解决上述问题的方法,并提出构造对象行为模式
    发表于 04-09 09:39 12次下载

    数组越界的故障模型及其检测方法研究

    数组越界是C 程序中的常见故障,该类故障可能造成系统的崩溃。首先针对常见的数组越界故障进行了分析,提出了检测数组越界的判定准则,建立了故障模
    发表于 09-24 10:49 16次下载

    一种改进的虹膜边界定位算法_汪良会

    一种改进的虹膜边界定位算法_汪良会
    发表于 03-14 17:38 5次下载

    基于SLUB的DEBUG功能,如何帮忙检测内存越界和访问已经释放的内存

    SLAB内存分配器-SLUB的DEBUG功能,如何帮忙检测内存越界(out-of-bounds)和访问已经释放的内存(use-after-free)。
    的头像 发表于 02-08 14:11 9664次阅读
    基于SLUB的DEBUG功能,如何帮忙检测<b class='flag-5'>内存</b><b class='flag-5'>越界</b>和访问已经释放的<b class='flag-5'>内存</b>

    分析Linux操作系统的内存

    前言:在Linux上不像在Windows上看内存那样方便,而且还有Swap这个新的概念,所以知道如何来看Linux内存还是有一定意义的
    的头像 发表于 03-31 16:43 1356次阅读

    Linux内核源码分析-进程的哪些内存类型容易引起内存泄漏?

    Linux内核主要学习内容可以分为三大块:进程、内存及协议栈。今天就说说内存泄露的问题。相信你在平时的工作中,应该遇到过下面这些场景: 伴随着服务器中的后台任务持续地运行,系统中可用内存
    发表于 01-14 13:02 6次下载
    <b class='flag-5'>Linux</b>内核源码<b class='flag-5'>分析</b>-进程的哪些<b class='flag-5'>内存</b>类型容易引起<b class='flag-5'>内存</b>泄漏?

    Linux 内存管理总结

    一、Linux内存管理概述 Linux内存管理是指对系统内存的分配、释放、映射、管理、交换、压缩等一系列操作的管理。在
    的头像 发表于 11-10 14:58 507次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>内存</b>管理总结

    jvm内存溢出该如何定位解决

    超出限制和堆空间不足。 定位JVM内存溢出问题是一个比较复杂的任务,需要结合工具和技术来进行分析和解决。本文将介绍一些常用的调试和解决内存溢出问题的工具和技术。 一、理解JVM
    的头像 发表于 12-05 11:05 1305次阅读