1. 程式人生 > >記錄一場沒有勝利的區域性戰鬥

記錄一場沒有勝利的區域性戰鬥

這個問題, 早在1年前就遇到了, 當時因為沒有在意一直沒有跟進.

最近團隊來了個新人, 又一次觸發了這個問題, 所以終於下定決定要好好找找這個BUG的原因..

首先,這篇文章是個流水賬, 沒啥意思, 其次最終我其實也沒能真正找到原因, 只是找到了一個規避的方法, 和一個猜測的原因. 萬一遇到類似情況的同學可以部分借鑑, 當然, 最後我是希望有人對glibc原始碼熟悉的同學, 可以真正的指點下這個bug的真實原因是啥.

問題是這樣的, 我們的一個服務, 依賴於Yar C庫, 但是valgrind檢測會報告一個問題:

==25279== Invalid free() / delete / delete[] / realloc()
==25279==    at 0x4A072BA: free (vg_replace_malloc.c:446)
==25279==    by 0x34DC10ADAA: free_mem (in /lib64/libc-2.5.so)
==25279==    by 0x34DC10A9A1: __libc_freeres (in /lib64/libc-2.5.so)
==25279==    by 0x48025E9: _vgnU_freeres (vg_preloaded.c:62)
==25279==    by 0x34DC0334E4: exit (in /lib64/libc-2.5.so)
==25279==    by 0x34DC01D99A: (below main) (in /lib64/libc-2.5.so)
==25279==  Address 0x4e265c8 is not stack'd, malloc'd or (recently) free'd

當然首先遇到這個問題, 第一反映就是先Google了, Google以後很多答案指向說這個是低版本glibc(我們的是glibc-2.5)的libc_freeres的bug, 傳遞–run-libc-freeres=no 就可以避免這個警告. 通過驗證發現確實管用.

好的吧, 到這裡我首先放心了這不是我們程式碼的問題, 但是, 這肯定不符合我們打破沙鍋問到底的精神啊, 那到底是我們的什麼觸發了這個Bug的警告呢? 我能不能找到原因, 從而規避這個問題呢, 因為我試過一些其他的庫是不會有這個問題的.

首先我們來嘗試找找看這個地址到底是什麼東西, 通過一些精簡, 我得到如下的一個可重現的方法, 首先main.c, 啥事都不幹:

//file main.c
int main(int argc, char **argv) {
    return 0;
}

然後編譯, 連結yar庫:

gcc -Wl,-rpath,/home/huixinchen/local/yar/lib/ -L/home/huixinchen/local/yar/lib/ -lyar main.c

測試:

$ valgrind --run-libc-freeres=yes ./a.out
==7008== Memcheck, a memory error detector
==7008== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==7008== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==7008== Command: ./a.out
==7008==
==7008== Invalid free() / delete / delete[] / realloc()
==7008==    at 0x4A072BA: free (vg_replace_malloc.c:446)
==7008==    by 0x34DC10ADAA: free_mem (in /lib64/libc-2.5.so)
==7008==    by 0x34DC10A9A1: __libc_freeres (in /lib64/libc-2.5.so)
==7008==    by 0x48025E9: _vgnU_freeres (vg_preloaded.c:62)
==7008==    by 0x34DC0334E4: exit (in /lib64/libc-2.5.so)
==7008==    by 0x34DC01D99A: (below main) (in /lib64/libc-2.5.so)
==7008==  Address 0x4e265c8 is not stack'd, malloc'd or (recently) free'd
==7008==

0x4e265c8在X86 64系統的記憶體佈局結構中, 非常像一個全域性變數的地址, 但是可能是什麼呢? 會不會是.bss的一個全域性變數呢? 通過sleep, 然後cat /proc/pid/maps, 發現valgrind會影響載入共享庫的佈局, 所以我們要想辦法首先不用valgrind也能重現這個問題.

考慮到這個是__libc_freeres的問題(據稱), 那麼我們是不是可以直接呼叫從而浮現呢?

//file main.c
extern void __libc_freeres();
int main(int argc, char **argv) {
    __libc_freeres();
    return 0;
}

測試執行:

$ ./a.out
*** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x00002ab9e8a21770 ***
======= Backtrace: =========
/lib64/libc.so.6(cfree+0x166)[0x34dc071756]
/lib64/libc.so.6[0x34dc10adab]
/lib64/libc.so.6(__libc_freeres+0x42)[0x34dc10a9a2]
./a.out[0x4005d1]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x34dc01d994]
./a.out[0x400509]

果然可以直接觸發, 不需要valgrind了, 但是yar庫很龐大, 除錯起來很複雜, 能不能進一步簡化呢?

於是通過一個漫長痛苦的過程(這個過程按下不表), 終於得到了一個更加容易復現的lib庫, 你猜他是什麼樣子呢?

#共享庫

是的, 你沒看錯, 這個庫沒有任何內容, 好的編譯這個庫(編譯為libtest.so):

gcc -shared -fPIC empty.c -Wl,-rpath,/home/huixinchen/dev/ -lm -o libtest.so

( 不要問, 為啥引數一定要有rpath和-lm, 這是因為這都是我在那個漫長痛苦的過程中總結出來的啊, 但如果你一定要問, 好吧, 這是因為Yar也有類似用法, 通過排除法以及Google試出來的…汗..)

好的, 現在讓的main.c連結到這個新的庫:

$ gcc -g -o mem-error main.c ./libtest.so
$ ./mem-error

*** glibc detected *** ./mem-error: free(): invalid pointer: 0x00002b1a30252660 ***
======= Backtrace: =========
/lib64/libc.so.6[0x34dc0711df]
/lib64/libc.so.6(cfree+0x4b)[0x34dc07163b]
/lib64/libc.so.6[0x34dc10adab]
/lib64/libc.so.6(__libc_freeres+0x42)[0x34dc10a9a2]
./mem-error(main+0xe)[0x4005c6]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x34dc01d994]
./mem-error[0x400509]
======= Memory map: ========
00400000-00401000 r-xp 00000000 68:11 17932888                           /home/huixinchen/dev/mem-error
00600000-00601000 rw-p 00000000 68:11 17932888                           /home/huixinchen/dev/mem-error
04999000-049ba000 rw-p 04999000 00:00 0                                  [heap]
304a600000-304a60d000 r-xp 00000000 68:01 950291                         /lib64/libgcc_s-4.1.2-20080825.so.1
304a60d000-304a80d000 ---p 0000d000 68:01 950291                         /lib64/libgcc_s-4.1.2-20080825.so.1
304a80d000-304a80e000 rw-p 0000d000 68:01 950291                         /lib64/libgcc_s-4.1.2-20080825.so.1
31a6a00000-31a6a82000 r-xp 00000000 68:01 950274                         /lib64/libm-2.5.so
31a6a82000-31a6c81000 ---p 00082000 68:01 950274                         /lib64/libm-2.5.so
31a6c81000-31a6c82000 r--p 00081000 68:01 950274                         /lib64/libm-2.5.so
31a6c82000-31a6c83000 rw-p 00082000 68:01 950274                         /lib64/libm-2.5.so
34dbc00000-34dbc1c000 r-xp 00000000 68:01 950586                         /lib64/ld-2.5.so
34dbe1c000-34dbe1d000 r--p 0001c000 68:01 950586                         /lib64/ld-2.5.so
34dbe1d000-34dbe1e000 rw-p 0001d000 68:01 950586                         /lib64/ld-2.5.so
34dc000000-34dc14e000 r-xp 00000000 68:01 950587                         /lib64/libc-2.5.so
34dc14e000-34dc34d000 ---p 0014e000 68:01 950587                         /lib64/libc-2.5.so
34dc34d000-34dc351000 r--p 0014d000 68:01 950587                         /lib64/libc-2.5.so
34dc351000-34dc352000 rw-p 00151000 68:01 950587                         /lib64/libc-2.5.so
34dc352000-34dc357000 rw-p 34dc352000 00:00 0
2b1a30251000-2b1a30253000 rw-p 2b1a30251000 00:00 0
2b1a30253000-2b1a30254000 r-xp 00000000 68:11 17932574                   /home/huixinchen/dev/libtest.so
2b1a30254000-2b1a30453000 ---p 00001000 68:11 17932574                   /home/huixinchen/dev/libtest.so
2b1a30453000-2b1a30454000 rw-p 00000000 68:11 17932574                   /home/huixinchen/dev/libtest.so
 

好吧, 這樣看起來這個地址(0x00002b1a30252660) 落在了libtest.so之前. 不會是我們的全域性變數, 況且程式碼裡壓根就沒有object物件…. (之前的地址確實是受到了valgrind的影響)

事情到此, 基本上絕對可以排除是我們自己的程式碼導致的, 那麼到底會是什麼原因呢?

沒辦法, 只好再回到valgrind(把main.c改回成最初的版本, 不要直接呼叫freeres), 本著試試的態度, db-attach上去:

0x0000000004a072ba in _vgr10050ZU_libcZdsoZa_free (p=0x4e265c8) at m_replacemalloc/vg_replace_malloc.c:446
446	 FREE(VG_Z_LIBC_SONAME,       free,                 free );
(gdb) bt
#0  0x0000000004a072ba in _vgr10050ZU_libcZdsoZa_free (p=0x4e265c8) at m_replacemalloc/vg_replace_malloc.c:446
#1  0x00000034dc10adab in free_mem () from /lib64/libc.so.6
#2  0x00000034dc10a9a2 in __libc_freeres () from /lib64/libc.so.6
#3  0x00000000048025ea in _vgnU_freeres () at vg_preloaded.c:62
#4  0x00000034dc0334e5 in exit () from /lib64/libc.so.6
#5  0x00000034dc01d99b in __libc_start_main () from /lib64/libc.so.6
#6  0x00000000004004b9 in _start ()

咦, 發現了什麼不對的, 怎麼是貌似在free SONAME呢? 我壓根沒有指定SONAME啊?

那, 指定一個試試?

$ gcc -shared -fPIC empty.c -Wl,-rpath,/home/huixinchen/dev/  -Wl,-soname,libm.so -lm -o libtest.so
$ objdump -x libtest.so  | grep SONAME
  SONAME               libm.so

然後, 你猜這麼著?

問題消失了….. 哈哈哈哈 (經歷了從昨天下午, 昨天晚上, 到今天早上: