log4cxx: apr_pool_create_ex的分段错误

log4cxx: Segmentation fault in apr_pool_create_ex

本文关键字:分段 错误 ex pool apr log4cxx create      更新时间:2023-10-16

我需要在c++项目中使用log4cxx。然而,我不理解这个库的基本设置。下面是我的最小尝试:

$ cat logger.cpp 
#include <log4cxx/logger.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/properties.h>
static log4cxx::LoggerPtr ptr;
int main()
{
  log4cxx::helpers::Properties prop;
  prop.setProperty("log4j.rootLogger","DEBUG, A1");
  prop.setProperty("log4j.appender.A1","org.apache.log4j.ConsoleAppender");
  prop.setProperty("log4j.appender.A1.layout","org.apache.log4j.PatternLayout");
  prop.setProperty("log4j.appender.A1.layout.ConversionPattern","%d{ABSOLUTE} %-5p [%c] %m%n");
  log4cxx::PropertyConfigurator::configure(prop);
  ptr = log4cxx::Logger::getLogger("API");
  LOG4CXX_INFO(ptr , "test_info");
  return 0;
}

然后使用:

编译它
$ g++ -g -o logger logger.cpp -llog4cxx

$ gdb ./logger
[...]
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff5f69dc9 in apr_pool_create_ex () from /lib64/libapr-1.so.0
Missing separate debuginfos, use: debuginfo-install apr-1.5.1-3.fc21.x86_64 apr-util-1.5.4-1.fc21.x86_64 cyrus-sasl-lib-2.1.26-19.fc21.x86_64 expat-2.1.0-10.fc21.x86_64 libdb-5.3.28-9.fc21.x86_64 libgcc-4.9.2-6.fc21.x86_64 libstdc++-4.9.2-6.fc21.x86_64 libuuid-2.25.2-3.fc21.x86_64 log4cxx-0.10.0-17.fc21.x86_64 nspr-4.10.8-1.fc21.x86_64 nss-3.19.1-1.0.fc21.x86_64 nss-softokn-freebl-3.19.1-1.0.fc21.x86_64 nss-util-3.19.1-1.0.fc21.x86_64 openldap-2.4.40-3.fc21.x86_64 zlib-1.2.8-7.fc21.x86_64
(gdb) bt
#0  0x00007ffff5f69dc9 in apr_pool_create_ex () from /lib64/libapr-1.so.0
#1  0x00007ffff7b26b58 in log4cxx::helpers::Pool::Pool() () from /lib64/liblog4cxx.so.10
#2  0x00007ffff7ae06ea in log4cxx::helpers::MutexException::formatMessage(int) () from /lib64/liblog4cxx.so.10
#3  0x00007ffff7ae0786 in log4cxx::helpers::MutexException::MutexException(int) () from /lib64/liblog4cxx.so.10
#4  0x00007ffff7b4a310 in log4cxx::helpers::synchronized::synchronized(log4cxx::helpers::Mutex const&) () from /lib64/liblog4cxx.so.10
#5  0x00007ffff7b5d9c8 in log4cxx::WriterAppender::close() () from /lib64/liblog4cxx.so.10
#6  0x00007ffff7ac979c in log4cxx::ConsoleAppender::~ConsoleAppender() () from /lib64/liblog4cxx.so.10
#7  0x00007ffff7ac98b9 in log4cxx::ConsoleAppender::~ConsoleAppender() () from /lib64/liblog4cxx.so.10
#8  0x00007ffff7aba247 in log4cxx::helpers::AppenderAttachableImpl::~AppenderAttachableImpl() () from /lib64/liblog4cxx.so.10
#9  0x00007ffff7b0494c in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#10 0x00007ffff7b388b4 in log4cxx::spi::RootLogger::~RootLogger() () from /lib64/liblog4cxx.so.10
#11 0x00007ffff7b0429a in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#12 0x00007ffff7b04429 in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#13 0x0000000000401d6e in log4cxx::helpers::ObjectPtrT<log4cxx::Logger>::~ObjectPtrT (this=0x6031a0 <ptr>, __in_chrg=<optimized out>) at /usr/include/log4cxx/helpers/objectptr.h:100
#14 0x00007ffff6e38392 in __run_exit_handlers () from /lib64/libc.so.6
#15 0x00007ffff6e383e5 in exit () from /lib64/libc.so.6
#16 0x00007ffff6e1efe7 in __libc_start_main () from /lib64/libc.so.6
#17 0x0000000000401599 in _start ()

为记录器使用全局变量从根本上说是错误的吗?在这里使用单例模式对我来说是有意义的。

系统是:Fedora 21 with:

$ rpm -qv log4cxx
log4cxx-0.10.0-17.fc21.x86_64

:

$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.9.2/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC) 

再小的尝试也会以完全相同的方式崩溃:

#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
static log4cxx::LoggerPtr ptr;
int main()
{
  log4cxx::BasicConfigurator::configure();
  ptr = log4cxx::Logger::getLogger("API");
  return 0;
}

这个愚蠢的崩溃的最终原因是非常根深蒂固的,与log4cxx的基本设计缺陷有关(首先是错误的尝试-和哲学-使c++代码"外观和感觉都像java")。

近似的原因是由全局LoggerPtr ptr持有的Logger的析构函数被调用太晚。到那时,APR库(log4cxx代码库所依赖的库)将被取消,因此synchronized::synchronized构造函数调用(堆栈跟踪中的#4)必然会失败——之后某种崩溃将不可避免。为什么代码必须跳过这样的环来释放资源本身就是一个传奇,不值得在这里讨论。

这样的问题与静态反初始化的顺序有关:APR库被"太早"取消了。因为它实际上是通过静态单例初始化的,太晚了(在示例代码中,当LoggerPtr实际上被赋予一个Logger来保存时)。

示例代码可以是"fixed"在退出main()之前(例如,在return 0;之前)添加以下语句:

ptr = 0;

这会在APR库仍然"活着"的时候调用复杂的资源释放序列。

更通用的"解决方案"将涉及适当地控制APR库的生命周期。log4cxx代码库中到处都有静态的Meyers单例,包括一个用于包装对APR库的apr_initialize()apr_terminate()的调用的单例,但是众所周知的"静态初始化顺序"失败了。这样就很难安排那个特定的独生子成为"最老的"。(它必须从每个其他单例的构造函数中调用。)因此,实际的"答案"是,通过对apr_initialize()的额外调用——比如,在main()的早期——与匹配的apr_terminate()平衡,并在程序终止时泄漏特定的资源,从而使APR库永远存活。

请注意,该错误可以通过其他方式触发。事实上,这个bug阻碍了后续版本的发布,这可能就是为什么整个log4cxx项目基本上在十年前就夭折了。