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查看手册,得到下面的信息:

rpmrcConfiguration
/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
Theoptflagsentrylookslikethis:
optflags:<architecture><value>
Forexample,assumethefollowingoptflagsentrieswereplacedinanrpmrcfile:
optflags:i386-O2-m486-fno-strength-reduce
optflags:sparc-O2
通过rpmbuid命令编译出的libuuid的rpm安装包会被抽取掉debug 信息( extractingdebuginfo),为此需要在 util-linux-ng.spec的头部加入
%global_enable_debug_package0
%globaldebug_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了,Centosglibc-2.12-1.149已经修复了这个问题。glibc升级的时候需要考虑abi 兼容性的问题,可以使用abidiff或abipkgdiff比较新老glibc兼容性。如果glibc兼容,则可以直接升级,否则要连同系统一起整体升级,并重新编译上层应用。

相关文章

Centos下搭建性能监控Spotlight
CentOS 6.3下Strongswan搭建IPSec VPN
在CentOS6.5上安装Skype与QQ
阿里云基于centos6.5主机VPN配置
CentOS 6.3下配置multipah
CentOS安装、配置APR和tomcat-native