問題描述:最近在工作中遇到這樣一個(gè)奇葩問題,程序里需使用一個(gè).so庫,同份源碼用我電腦編譯的庫放到程序使用出現(xiàn)各種異常問題,其他同事編譯出來的沒問題。剛開始以為是編譯方式有問題,思來想去發(fā)現(xiàn)并不是。經(jīng)分析發(fā)現(xiàn)是庫源代碼里一全局?jǐn)?shù)組內(nèi)存地址大面積越界到其他全局?jǐn)?shù)組了。
問題現(xiàn)象:現(xiàn)象為觸發(fā)某個(gè)業(yè)務(wù)條件,將導(dǎo)致程序邏輯運(yùn)行不正常,異常log如下圖,可看出“g_s32MaxFd”變量的值(文件句柄)被置0,正常情況應(yīng)該是大于0,所以此時(shí)導(dǎo)致整個(gè)業(yè)務(wù)運(yùn)行異常。
初步分析肯定是其他地方對(duì)變量“g_s32MaxFd”有賦值才會(huì)導(dǎo)致值為0。那么到底是代碼正常邏輯語句操作還是代碼內(nèi)存越界引起“g_s32MaxFd”值為0呢?這個(gè)倒好定位,只需要搜索下“g_s32MaxFd”變量在代碼哪些地方有使用就知道了,得出結(jié)論是代碼內(nèi)存越界這種情況導(dǎo)致。
一:開始定位內(nèi)存越界處
【1】定位內(nèi)存越界處,因程序并沒有因?yàn)閮?nèi)存越界而引發(fā)segment fault退出,所以準(zhǔn)備使用Linux中mprotect()函數(shù)來設(shè)置指定內(nèi)存區(qū)域的保護(hù)屬性為只讀,故意使程序引發(fā)segment fault退出從而產(chǎn)生core dumped文件來定位問題點(diǎn)。
分析下面問題前最好先熟悉下mprotect()函數(shù)
思路:使用mprotect()函數(shù)對(duì)被踩變量“g_s32MaxFd”內(nèi)存地址設(shè)為只讀屬性,由于mprotect()函數(shù)的局限性(保護(hù)屬性區(qū)域的起始地址必須為操作系統(tǒng)一個(gè)頁大小的整數(shù)倍),結(jié)合實(shí)際情況多樣性,分析情況如下表述:
1、當(dāng)“g_s32MaxFd”數(shù)組起始地址剛好是頁大小整數(shù)倍時(shí),此時(shí)只需要將數(shù)組起始地址設(shè)置為mprotect()函數(shù)保護(hù)屬性為只讀的起始地址即可,但需要注意一點(diǎn),當(dāng)被保護(hù)地址區(qū)域被程序正常數(shù)據(jù)結(jié)構(gòu)進(jìn)行訪問時(shí),也會(huì)引發(fā)segment fault退出(簡而言之就是當(dāng)數(shù)組“g_s32MaxFd”內(nèi)存地址被設(shè)置為只讀后,如果是程序正常使用時(shí)也會(huì)引發(fā)段錯(cuò)誤退出),這種情況就無法辨別是程序正常使用還是內(nèi)存越界處使用,會(huì)影響分析真正的問題點(diǎn)。
解決方法:可利用GNU編譯器對(duì).bss地址分配特性(具體特性自行查閱其他資料),在“g_s32MaxFd”數(shù)組地址處定義一個(gè)為頁大小整數(shù)倍大小的“g_debug_place”數(shù)組,這就相當(dāng)于新增的“g_debug_place”數(shù)組占用之前“g_s32MaxFd”數(shù)組的地址。如下圖所示在“Var5”和“g_s32MaxFd”之間定義一個(gè)動(dòng)態(tài)數(shù)組“g_debug_place”,大小最好是頁大小整數(shù)倍(如果小于一個(gè)頁大小會(huì)導(dǎo)致鎖定的區(qū)域越界到“g_s32MaxFd”地址,問題得不到解決),這樣既可以保證新增的“g_debug_place”數(shù)組變量只在內(nèi)存越界的地方才會(huì)被訪問而且數(shù)組大小也滿足mprotect()函數(shù)參數(shù)長度的取值要求(頁大小整數(shù)倍)。
2、 當(dāng)“g_s32MaxFd”數(shù)組起始地址不是頁大小整數(shù)倍時(shí),要結(jié)合上面第1種方法后還需要計(jì)算出大于且最靠近“g_debug_place”數(shù)組起始地址的頁大小整數(shù)倍地址??商子霉剑?/p>
設(shè)置保護(hù)屬性起始地址=被踩內(nèi)存變量起始地址+(頁大小-(被踩內(nèi)存變量起始地址%頁大小)) 注意:(被踩內(nèi)存變量起始地址%頁大小)等于0時(shí)不適用以上公式,也就是被踩內(nèi)存變量起始地址是頁大小整數(shù)倍情況下
假設(shè)“g_debug_place”數(shù)組起始地址為0x7fd8985bf8c0代入公式可得設(shè)置保護(hù)屬性起始地址為0x7fd8985c0000 ,理論上只需要將地址0x7fd8985c0000設(shè)置為mprotect()函數(shù)保護(hù)屬性為只讀的起始地址即可,但需要注意的是此時(shí)的0x7fd8985c0000地址并不是“g_debug_place”數(shù)組起始地址,由上面公式可知這個(gè)地址是為了滿足mprotect()函數(shù)的局限性而計(jì)算出來的地址。
解決方法:可通過在.bss段(之所以強(qiáng)調(diào).bss段是因?yàn)槲覍?shí)際出現(xiàn)問題的變量就是未初始化的全局?jǐn)?shù)組變量)首個(gè)變量地址前增加動(dòng)態(tài)數(shù)組來改變內(nèi)存分配解決。舉個(gè)例子,就好比是排隊(duì),本來小明是排第六個(gè),突然在隊(duì)伍最前面插一個(gè)小紅進(jìn)來,小明就排在第七了,而小明前面之前那五個(gè)人的順序還是不變。而這個(gè)第七就是我們程序里要的那個(gè)0x7fd8985c0000地址。
下圖藍(lán)色區(qū)域?yàn)樾略鰟?dòng)態(tài)數(shù)組(插隊(duì)小紅),大小為0x740字節(jié)。增加后可使“g_debug_place”數(shù)組起始地址為0x7fd8985c0000(小明第七的位置),這時(shí)將0x7fd8985c0000地址作為mprotect()函數(shù)保護(hù)屬性為只讀的起始地址就可以了,接下來就可以復(fù)現(xiàn)問題等著程序內(nèi)存越界產(chǎn)生段錯(cuò)誤退出吧。
注意:如果增加動(dòng)態(tài)數(shù)組后并沒有直觀發(fā)現(xiàn)內(nèi)存越界時(shí),這可能是由于內(nèi)存越界的字節(jié)數(shù)太小(可能只踩到一個(gè)字節(jié)或幾個(gè)字節(jié)),導(dǎo)致調(diào)整過后的內(nèi)存地址剛好踩到一個(gè)未使用的地址,這時(shí)需要微調(diào)動(dòng)態(tài)數(shù)組大小來保證地址間隔及分配順序不變,具體問題具體分析。我是沒有出現(xiàn)這種情況,只是覺得通過這種方法分析可能會(huì)存在此風(fēng)險(xiǎn),如果有小伙伴遇到可以留言探討。
bss段變量地址結(jié)構(gòu)分布簡要展示如下圖(展示的是測(cè)試代碼,非實(shí)際工程代碼):
【2】gdb分析core文件,編譯可執(zhí)行程序時(shí)編譯選項(xiàng)需加-g參數(shù),不要strip優(yōu)化,否則可能會(huì)導(dǎo)致調(diào)試信息不是很完整。
檢查core dumped是否打開
/home # ulimit -c
0
/home # ulimit -c unlimited
/home # ulimit -c
unlimited
如果找不到ulimit命令,可以用busybox sh -c 'ulimit -a’指令測(cè)試ulimit是否存在,(ulimit是busybox的內(nèi)置命令,往往我們想使用tab鍵快捷調(diào)用ulimit時(shí)可能不會(huì)彈出)有如下log輸出證明命令存在,后續(xù)直接執(zhí)行ulimit -c unlimited,不要再執(zhí)行busybox sh -c ‘ulimit -c unlimited’,這樣是打不開core的,我就這么傻的操作過,當(dāng)時(shí)還以為內(nèi)核沒有打開這個(gè)功能。
/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文件過程,如下圖所示。當(dāng)輸出log信息不完整時(shí),需要檢查下源碼和相關(guān)庫文件路徑是否設(shè)置好,可根據(jù)圖片中標(biāo)注處進(jìn)行設(shè)置。(展示的是測(cè)試代碼,非實(shí)際工程代碼)
實(shí)際代碼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)
小結(jié):以上定位內(nèi)存越界只是一個(gè)大體思路,實(shí)際情況多樣性,具體問題還需要具體分析,個(gè)人認(rèn)為如果只需要定位程序異常退出的話,用backtrace相關(guān)函數(shù)來代替gdb分析問題要輕量化很多。上述之所以使用gdb去分析問題是由于使用的交叉編譯是uclibc環(huán)境(uclibc環(huán)境下backtrace函數(shù)是沒實(shí)現(xiàn)的),就只能使用sdk提供的gdb工具了
二:為什么我電腦編譯出來的庫就暴露這個(gè)問題呢?
通過上面的方法已經(jīng)定位到是哪行代碼有bug,所以想再分析下我編譯出來的庫為啥就暴露這個(gè)問題了呢?分析得知是在生成.so庫時(shí)由于鏈接.o的順序不同導(dǎo)致庫里面全局變量數(shù)組的地址分布也有所不同。下面分析下log文件里具體不同點(diǎn),截圖貼上:
qiuhui@ubuntu:/mnt/hgfs/qh/work/app/SVN/?????$ arm-hisiv300-linux-objdump -t ???/lib?????.so > log
【圖一為我電腦編譯的】
【圖二為同事電腦編譯的】
由上圖可以觀察到兩個(gè)全局?jǐn)?shù)組變量“gs_s8Contenx”與“g_s32MaxFd”它們的地址有前后順序差異,圖一:“gs_s8Contenx地址0xfd9e4”小于“g_s32MaxFd地址0xfed34”,圖二:“gs_s8Contenx地址0xfdfd4”大于“g_s32MaxFd地址0xfdbd4”。正是由于這兩個(gè)地址的前后順序才導(dǎo)致我編的庫暴露了問題,因?yàn)槲揖幍膅s_s8Contenx地址小于g_s32MaxFd,代碼里剛好使用gs_s8Contenx數(shù)組時(shí)以超過數(shù)組元素最大值做賦值操作,從而引發(fā)大面積內(nèi)存越界,導(dǎo)致越界地址直接就踩到g_s32MaxFd變量地址了(踩到很多全局變量了),所以g_s32MaxFd數(shù)組的值被莫名修改,從而產(chǎn)生各種異常。當(dāng)然同事編譯的同樣也會(huì)使gs_s8Contenx越界,但由于gs_s8Contenx地址大于g_s32MaxFd,所以gs_s8Contenx剛好踩到的是一段不常用的地址,導(dǎo)致問題沒有及時(shí)暴露出來。
-
Linux
+關(guān)注
關(guān)注
87文章
11213瀏覽量
208735 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
2980瀏覽量
73824 -
程序
+關(guān)注
關(guān)注
116文章
3766瀏覽量
80763 -
源碼
+關(guān)注
關(guān)注
8文章
632瀏覽量
29116
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論