问题描述:最近在工作中遇到这样一个奇葩问题,程序里需使用一个.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段变量地址结构分布简要展示如下图(展示的是测试代码,非实际工程代码):
检查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 ‘