Press "Enter" to skip to content

记录一场没有胜利的局部战斗

这个问题, 早在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

然后, 你猜这么着?
问题消失了..... 哈哈哈哈 (经历了从昨天下午, 昨天晚上, 到今天早上:<) 那么看起来是, glibc的低版本中, 在有rpath, 有依赖库的时候, freeres没有正确的判断是否有soname就尝试了释放.. 导致了这个错误... 当然, 这个支持从表象猜测的, 如果人知道真正的原因, 还请不吝留言指教, 或者也许再过一年, 我会再次深入研究吧.... 🙂 (后记: 总结俩句吧, 学无止境啊, 对于一个没有读过glibc的人来说, 这个过程是很痛苦的, 只能靠大胆推测, 小心求证. 然而人的精力又有限, 怎么才能把每一个你涉及到的知识点都搞清楚, 只能靠无止尽的学习... ) thanks

19 Comments

  1. jane
    jane April 30, 2019

    天呢,真是厉害,到最后把文章看完,我都不知道文章在讲什么。。。

  2. cool math games
    cool math games December 20, 2018

    How To Play Favorite Cool Math Games? Here are some of the exciting options for you to start a better game play according to your choice!

  3. xuwendian
    xuwendian October 24, 2018

    膜拜鸟哥

  4. Jeadon
    Jeadon October 4, 2018

    膜拜

  5. cool math games
    cool math games September 24, 2018

    Your articles are really amazing and I got a lot of information and guidance reading them.

  6. 西顿家居照明
    西顿家居照明 April 11, 2018

    真心牛逼,到最后我居然把全文都看完了,都不知道在说啥。这里真是卧虎藏龙之地

  7. LelandSlave
    LelandSlave January 15, 2018

    It’s remarkable to pay a visit this web page and reading the views of all colleagues about this paragraph, while I am also zealous of getting knowledge.

  8. dream11
    dream11 September 12, 2017

    Very good write-up. I certainly love this website. Thanks!

  9. antonioitalia
    antonioitalia March 7, 2017

    Erettile scaduto rx farmacia italia alla consegna disfunzione desiderio anche levitra manca il cialis

  10. I've never heard of it, but that video was the best thing I've watched all day. Could you get me to do it? …probably not, but feel free to put that video at the end of everything you post…LOL

  11. kilvn
    kilvn July 5, 2016

    天呢,真是厉害,到最后把文章看完,我都不知道文章在讲什么。。。

  12. Rocky
    Rocky May 4, 2016

    Eihän 11,30e 30ml:n voiteesta edes ole kamala hinta verrattuna noihin tavallisiin sarjoihin! Erisania saa alle kympillä, mutta esim. suurin osa Lumenen rasvoista menee jo reippaasti yli Laveran litrahinnan, eikä Lumene edes ole mikään luksussarja.Eli ihan hyvällä omatunnolla Laveraa ostoskoriin vaan, samalla kun ohjaa kritiikin elmistikosietiikatta Lumenelta muuta kuin perussinistä ostaville, puhumattakaan noista luksussarjoista! 😀

  13. php程序员的笔记
    php程序员的笔记 December 15, 2015

    天呢,真是厉害,到最后把文章看完,我都不知道文章在讲什么。。。

  14. coach factory
    coach factory December 12, 2015

    People who are ambitious and work hard for years and still don’t reach their goals most likely did the wrong things or they did the right things but didn’t stick with it long enough. It could also be because they were missing some crucial pieces to the puzzle. You could have a great product but without a great marketing plan, not enough people are going to know your product even exists. Sometimes, it could just be that you don’t currently know the right people.
    coach factory http://coachoutletstores.hobo2015.com/

  15. wanghuiqing15202
    wanghuiqing15202 October 21, 2015

    链接动态库,又没有调用动态库中的函数等,会报次错误

  16. Anonymous
    Anonymous September 24, 2015

    天呢,真是厉害,到最后把文章看完,我都不知道文章在讲什么。。。

  17. 围脖杨
    围脖杨 May 14, 2015

    -lm链接的是C的数学函数库,诸如平方等的数学函数

  18. 坑长
    坑长 May 14, 2015

    看到鸟哥博客再次更新,激动膜拜ing.

Comments are closed.