前段时间使用的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查看手册,得到下面的信息:
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.
#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兼容,则可以直接升级,否则要连同系统一起整体升级,并重新编译上层应用。
原文链接:https://www.f2er.com/centos/380342.html