Linux程序内存越界定位分析总结
0 2023-03-20

问题描述:最近在工作中遇到这样一个奇葩问题,程序里需使用一个.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=0 ‘\000’) at SdkLogic/xxx3520dSample/xxx3520dOsd.c:621 #3 0xb6e4dc14 in xxxSdkAl_OsdShowGpsSpeed (pstRegAttr=0x32f9e9c, u8Speed=0 ‘\000’) 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刚好踩到的是一段不常用的地址,导致问题没有及时暴露出来。



声明: 本文转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们及时删除。(联系我们,邮箱:evan.li@aspencore.com )
0
评论
  • 相关技术文库
  • 单片机
  • 嵌入式
  • MCU
  • STM
  • 怎样才能快速学习8051单片机

    [导读]单片机是微机的一种,现时有8051、AVR、ARM7、ARM9等系列,其中 “8051”是源自于Intel公司的MCS-51系列芯片,但目前不同生产厂商

    前天
  • 什么是DDR?DDR的分类有哪些?

    存储器可分为易失性存储器和非易失性存储器两类,前者在掉电后会失去记忆的数据,后者即使在切断电源也可以保持数据

    05-31
  • 51单片机RAM 数据存储区、位寻址区、数据缓冲区

    [导读]1.RAM keil C语言编程RAM是程序运行中存放随机变量的数据空间。在keil中编写程序,如果当前模式为small模式,如果总的变量大小未超过12

    05-30
  • STM32使用库函数驱动LED灯编写程序步骤

    [导读] 一、熟悉GPIO结构体以下这个结构体是我从官方手册中获取的:[cpp] view plain copy print?typedef struct{u1

    05-30
  • 你知道Linux下的ds18b20驱动吗?

    [导读]今天在各位前辈已有成就的基础上花了两天时间终于把这个驱动给搞定了,从开始编译成模块看效果,进行调试,再到编译进内核,最后又编译了一个界面出来,虽说大多数

    05-29
  • 一文区分AT89C51和AT89C52

    [导读]AT89C51和AT89C52是单片机的两种型号。主要区别是容量不同。at89c51最多支持4KB的程序,at89c52则最多支持8KB的程序。

    05-29
  • 功能强大的时钟中断应用分析

    [导读]在单片机程序设计中,设置一个好的时钟中断,将能使一个CPU发挥两个CPU的功效,大大方便和简化程序的编制,提高系统的效率与可操作性。我们可以把一些例行的

    05-29
  • 一文告诉你MCS-51单片机有几个工作寄存器?

    [导读]工作寄存器有4组,每组都是8个工作寄存器R0~R7,通过PSW中的RS1、RS0两位来选择使用哪一组,如果不选,默认是选择第0组。

    05-29
  • 一文详解STC89C52单片机

    [导读]STC89C52单片机简介

    05-29
  • AVR单片机时熔丝位配置出现的一些问题分析

    [导读]AVR单片机的熔丝位配置是AVR单片机初学者很容易出错的地方,其实只要注意一些事项,还是能够尽量避免单片机被锁死,即使单片机被锁死,也可以使用一些方法解

    05-29
  • 一文详解STC89C52处理芯片

    [导读]主要性能: 与MCS-51单片机产品兼容 、8K字节在系统可编程Flash存储器、 1000次擦写周期、全静态操作:0Hz~33Hz 、三级加密程序存储

    05-29
  • 一文详细分析51单片机中断问题

    [导读]该寄存器用于设置定时/计数器的工作方式,低四位用于定时器0,高四位用于定时器1。 GATE:门控位。GATE=0时,只要用软件使TCON中的TR0或

    05-29
下载排行榜
更多
广告