天天看點

libuuid.so 崩潰問題

前段時間使用的Cetos 6.3有程式崩潰在了uuid_generate () from /lib64/libuuid.so.1,現象很是詭異,libuuid是Centos util-linux工具包中自帶的一個系統庫,怎麼可能在這裡出問題。首先懷疑是上層應用不正确的使用libuuid庫導緻的問題,對應用代碼進行review和debug也沒有發現問題。最為奇怪的是,這個問題在使用官方ISO鏡像安裝的Centos 6.5上也會出現,但是在我們一直使用的另外的基于Centos 6.5系統做的系統鏡像上不會有問題。排查許久,終于找到了根本原因,這裡記錄一下。

1 首先重新編譯libuuid添加調試資訊

    下載下傳到的util-linux-ng-2.17.2-12.24.el6.src.rpm安裝後會在rpmbuild/SPECS下生成一個util-linux-ng.spec檔案,裡面有一行:

export CFLAGS="-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $RPM_OPT_FLAGS"

RPM_OPT_FLAGS 是控制編譯選項,編譯器-g3選項就要在這裡加入。man rpmbuild檢視手冊,得到下面的資訊:

rpmrc Configuration        /usr/lib/rpm/rpmrc        /usr/lib/rpm/redhat/rpmrc        /etc/rpmrc        ~/.rpmrc The first three files are read in the order listed, such that if a given rmprc entry 

is present in each file, the value of the entry read last is the one used by RPM. 

This means, for example, that an entry in .rpmrc in the user's login directory will 

always override the same entry in /etc/rpmrc. Likewise, an entry in /etc/rpmrc 

will always override the same entry in /usr/lib/rpmrc.

可以通過上面4個檔案控制 RPM_OPT_FLAGS。這裡着重談一下~/.rpmrc的格式: ~/.rpmrc The optflags entry looks like this: optflags: <architecture> <value> For example, assume the following optflags entries were placed in an rpmrc file: optflags: i386 -O2 -m486 -fno-strength-reduce optflags: sparc -O2    通過rpmbuid指令編譯出的libuuid的rpm安裝包會被抽取掉debug 資訊( extracting debug info),為此需要在 util-linux-ng.spec的頭部加入 %global _enable_debug_package 0 %global debug_package %{nil} %global __os_install_post /usr/lib/rpm/brp-compress %{nil}

2 GDB調試定位

#ifdef HAVE_TLS
#define THREAD_LOCAL static __thread
#else
#define THREAD_LOCAL static
#endif


#if defined(__linux__) && defined(__NR_gettid) && defined(HAVE_JRAND48)
#define DO_JRAND_MIX
THREAD_LOCAL unsigned short jrand_seed[3];
#endif

static int get_random_fd(void)
{
        struct timeval  tv;
        static int      fd = -2;
        int             i;

        if (fd == -2) {
                gettimeofday(&tv, 0);
#ifndef _WIN32
                fd = open("/dev/urandom", O_RDONLY);
                if (fd == -1)
                        fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
                if (fd >= 0) {
                        i = fcntl(fd, F_GETFD);
                        if (i >= 0)
                                fcntl(fd, F_SETFD, i | FD_CLOEXEC);
                }
#endif
                srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec);
#ifdef DO_JRAND_MIX
                jrand_seed[0] = getpid() ^ (tv.tv_sec & 0xFFFF);  <=== 崩潰在對thread local 變量 jrand_seed的指派上
           

GDB 跟蹤調試的時候發現,jrand_seed的位址是一個非法位址,首先想到大概是GCC或GlibC對TLS的支援的問題,如果将jrand_seed修改為非TLS變量或者使用老一點的不使用TLS變量的libuuid,都不會崩潰。最後找到一篇類似問題的報告https://github.com/mkoppanen/php-zmq/issues/11,并且裡面提到了一個規避方法:即預先加載libuuid動态庫export LD_PRELOAD="/lib64/libuuid.so.1"。持續追查下去,最後發現,這的确是glibc的一個bug,在使用“使用動态加載技術(dlopen),并且使用TLS的時候出現”。為什麼我們基于Centos 6.5系統做的系統鏡像上沒有這個問題呢,原因是:我們後期使用yum upgrade/update更新了系統,glibc也被附帶更新了,而新的glibc fix了這個問題。

3 解決辦法

  碰到這個問題,解決的辦法就是更新glibc了,Centos glibc-2.12-1.149已經修複了這個問題。glibc更新的時候需要考慮abi 相容性的問題,可以使用abidiff或abipkgdiff比較新老glibc相容性。如果glibc相容,則可以直接更新,否則要連同系統一起整體更新,并重新編譯上層應用。

繼續閱讀