Abstract
ACM の Kode Vicious コラムは私の好きなコラムの一つである.(e.g., http://doi.acm.org/10.1145/1364782.1364791) プログラマとして働いていると時に彼の血圧を上昇させるバグに出会う.今回のバグは global な staticconstructor と destructor が二回呼ばれるというバグである.Contents
私は global な static object を作ることを基本的に避ける.static なobject はいくつかの副作用があるからである.特に C++ ではどのようにそれらが呼ば れ るかはプログラマの制御下にない.ではなぜ私はこんなバグにあうのか,それは私の書いたコードではないからである.しかし仕事であるからには文句ばかりも言っていられない.今回デバッグしていてわかったのは,ある static object の destructor が二回呼ばれていることである.私はそんなことが可能であることを知らなかった.C++ では static object は main が呼ばれる前に construt され,main が終了 した後に destruct され,それらの call の回数はコンパイラが一度だけに保 証 するはずだからである.
しかし,どのように binary を作成するかによっては以下のようにして二度呼ぶことが可能である.アイデアは dlopen を使う以下のようなものである.
- main のある application に static object A を link する.
- static object A のある shared library S を作成する.
- main は S を動的に load し,unload する.
- example_factory.h は shared library 内部にあって application 側で利用する IExample_class を作成する factory IExample_factoryの宣言である.
- example_factory_impl.h, example_factory_impl.cpp は shared library 内部にあり機能を提供する.ここに static object がある.
- factory_interface.cpp は shared library へのアクセスポイントを提供する.
- call_static_destructor_twice.cpp が shared library を利用する application である.
build script
#!/bin/bash -x # # How to call the static destructor twice. # Copyright (C) 2012 Hitoshi # # create a shared lib. LIB_SRC="factory_interface.cpp example_factory_impl.cpp" LIB_OBJ="factory_interface.o example_factory_impl.o" g++ -g -Wall -fPIC -c ${LIB_SRC} g++ -shared -Wl,-soname,libexample_factory.so -o libexample_factory.so ${LIB_OBJ} # create an application that dynamically loads the shared lib. APP_SRC="call_static_destructor_twice.cpp" APP_OBJ="call_static_destructor_twice.o" # # correct build # # g++ -g -rdynamic -o test_example ${APP_SRC} -ldl # # evil build. The same symbol, but two static objects are constructed # and destructed. If these objects looks up a system wide global # resource and delete twice, you have a difficult bug. # g++ -g -o test_example ${APP_SRC} -ldl example_factory_impl.o
Evil build にあるように,static global object のある object を link する.このようなことをすること自体が間違いであるのだが,実際にこのような bugが build system にまぎれこんでしまったのである.このようなバグはとてもデバッグが難しい.
このコードを evil build で生成すると,実行結果は以下のようになる.実験環境は Kubuntu 12.04, g++ 4.6.3 である.static の destructor が二度呼ばれている.この destructor が system wide な global な resource に access していた場合,crash on exit ということになる.
% ./test_example Example_class::Example_class() info: loading [./libexample_factory.so]... Example_class::Example_class() info: library [./libexample_factory.so] has loaded. Example_factory::Example_factory() info: new IExample_class. Example_class::Example_class() Example_class::~Example_class(): I am created by new_example_class(). info: unloading [./libexample_factory.so]... Example_class::~Example_class(): I am static since not changed. info: library [./libexample_factory.so] has been unloaded. Example_class::~Example_class(): I am static since not changed.
FILE example_factory.h
// ---------------------------------------------------------------------- // An example class for how to call static destructor twice demo. // Copyright (C) 2012 Hitoshi // ---------------------------------------------------------------------- /// \file example_factory_h /// \brief an example interface class. #ifndef EXAMPLE_FACTORY_H #define EXAMPLE_FACTORY_H #include//---------------------------------------------------------------------- /// Example class interface. This is created by the IExample_factory. /// class IExample_class { public: /// default constructor IExample_class() { // empty. This is needed since app only know this and the // constructor can not be pure virtual. } /// destructor virtual ~IExample_class() { // empty. This is needed since app only know this and the // destructor can not be pure virtual. }; /// print out message virtual void print() const = 0; private: // members private: /// copy constructor. prohibit until proved useful. IExample_class(IExample_class const & rhs); /// operator=. prohibit until proved useful. IExample_class const & operator=(IExample_class const & rhs); }; //---------------------------------------------------------------------- /// Example factory interface. The factory of all the shared library /// objects. class IExample_factory { public: /// default constructor IExample_factory() { // empty. This is needed since app only know this and the // constructor can not be pure virtual. } /// destructor virtual ~IExample_factory() { // empty. This is needed since app only know this and the // destructor can not be pure virtual. } /// print out message virtual void print() const = 0; /// new IExample instance virtual IExample_class * new_example_class() = 0; /// delete IExample instance virtual void delete_example_class(IExample_class * p_iec) = 0; private: /// copy constructor. prohibit until proved useful. IExample_factory(IExample_factory const & rhs); /// operator=. prohibit until proved useful. IExample_factory const & operator=(IExample_factory const & rhs); }; //---------------------------------------------------------------------- #endif // #ifndef EXAMPLE_FACTORY_H
FILE example_factory_impl.h
// ---------------------------------------------------------------------- // An example class for how to call static destructor twice demo. // Copyright (C) 2012 Hitoshi // ---------------------------------------------------------------------- /// \file example_factory_impl.h /// \brief an example class. Print a message when destruct. #include "example_factory.h" #include//---------------------------------------------------------------------- /// Example class implementation. /// class Example_class : public IExample_class { public: /// default constructor Example_class(); /// destructor virtual ~Example_class(); /// print out message virtual void print() const; public: /// set message /// \param[in] mes message for print(). void set_message(std::string const & mes); private: // message std::string m_mes; private: /// copy constructor. prohibit until proved useful. Example_class(Example_class const & rhs); /// operator=. prohibit until proved useful. Example_class const & operator=(Example_class const & rhs); }; //---------------------------------------------------------------------- /// Example factory implementation. The factory of all the shared /// library objects. /// class Example_factory : public IExample_factory { public: /// default constructor Example_factory(); /// destructor virtual ~Example_factory(); /// print out message virtual void print() const; /// new Example instance virtual IExample_class * new_example_class(); /// delete Example instance virtual void delete_example_class(IExample_class * p_iec); private: /// copy constructor. prohibit until proved useful. Example_factory(Example_factory const & rhs); /// operator=. prohibit until proved useful. Example_factory const & operator=(Example_factory const & rhs); }; //----------------------------------------------------------------------
FILE example_factory_impl.cpp
// ---------------------------------------------------------------------- // An example class for how to call static destructor twice demo. // Copyright (C) 2012 Hitoshi // ---------------------------------------------------------------------- /// \file example_factory.cpp /// \brief an example class. Print a message when destruct. #include "example_factory_impl.h" #include//---------------------------------------------------------------------- // default constructor Example_class::Example_class() : m_mes("I am static since not changed.") { std::cout << "Example_class::Example_class()" << std::endl; } //---------------------------------------------------------------------- // destructor Example_class::~Example_class() { std::cout << "Example_class::~Example_class(): " << m_mes << std::endl; } //---------------------------------------------------------------------- // print out message void Example_class::print() const { std::cout << "Example_class::print(): " << m_mes << std::endl; } //---------------------------------------------------------------------- // set message void Example_class::set_message(std::string const & mes) { m_mes = mes; } //====================================================================== // default constructor Example_factory::Example_factory() { std::cout << "Example_factory::Example_factory()" << std::endl; } //---------------------------------------------------------------------- // destructor Example_factory::~Example_factory() { std::cout << "Example_factory::~Example_factory(): " << std::endl; } //---------------------------------------------------------------------- // print out message void Example_factory::print() const { std::cout << "Example_factory::print() is called." << std::endl; } //---------------------------------------------------------------------- // new Example_class IExample_class * Example_factory::new_example_class() { Example_class * p_iec = new Example_class(); p_iec->set_message("I am created by new_example_class()."); return p_iec; } //---------------------------------------------------------------------- // create b void Example_factory::delete_example_class(IExample_class * p_ec) { delete p_ec; } //---------------------------------------------------------------------- // Static global object! Example_class an_example_class; //----------------------------------------------------------------------
FILE factory_interface.cpp
// ---------------------------------------------------------------------- // An example class for how to call static destructor twice demo. // Copyright (C) 2012 Hitoshi // ---------------------------------------------------------------------- /// \file factory_interface.cpp /// \brief dynamically loaded class factory #include "example_factory_impl.h" //====================================================================== /// singleton for dynamic shared lib object tracking class Shared_lib_object_tracker { public: /// get the instance /// \return example factory static IExample_factory * instance() { if(G_p_example_factory == 0){ G_p_example_factory = new Example_factory(); } return G_p_example_factory; } /// peek the instance /// \return example factory, may 0 if not yet accessed. static IExample_factory * peek_instance() { return G_p_example_factory; } /// delete the singleton. static void delete_instance() { if(G_p_example_factory != 0){ delete G_p_example_factory; G_p_example_factory = 0; } } private: // singleton instance static IExample_factory * G_p_example_factory; private: /// default constructor Shared_lib_object_tracker() { // empty } private: /// copy constructor. prohibit until proved useful. Shared_lib_object_tracker(Shared_lib_object_tracker const &); /// operator=. prohibit until proved useful. Shared_lib_object_tracker const & operator=(Shared_lib_object_tracker const &); }; // singleton instance IExample_factory * Shared_lib_object_tracker::G_p_example_factory = 0; //====================================================================== extern "C" IExample_factory * shared_lib_factory() { if(Shared_lib_object_tracker::peek_instance() != 0){ std::cerr << "Double call of the shared_lib_factory." << std::endl; return 0; } return Shared_lib_object_tracker::instance(); } //======================================================================
FILE call_static_destructor_twice.cpp
//---------------------------------------------------------------------- // An example code for how to call static destructor twice demo. // Copyright (C) 2012 Hitoshi //---------------------------------------------------------------------- /// \file call_static_destructor_twice.cpp /// \brief how to call static destructor twice #include#include #include #include #include "example_factory.h" // globals for open and close static void * G_p_handle = 0; char const * const G_p_lib_name = "./libexample_factory.so"; //---------------------------------------------------------------------- /// load shared library IExample_factory * load_library() { fprintf(stdout, "info: loading [%s]...\n", G_p_lib_name); G_p_handle = dlopen(G_p_lib_name, RTLD_LAZY | RTLD_DEEPBIND); if(G_p_handle == 0){ // iostream seems has some static. Avoid to use it here. fprintf(stderr, "error: null handle: %s\n", dlerror()); return 0; } void * p_symbol = dlsym(G_p_handle, "shared_lib_factory"); if (p_symbol == 0) { fprintf(stderr, "error: symbol: %s\n", dlerror()); return NULL; } typedef IExample_factory* (Factory_func()); Factory_func * factory = (Factory_func*)p_symbol; fprintf(stdout, "info: library [%s] has loaded.\n", G_p_lib_name); return factory(); } //---------------------------------------------------------------------- /// unload shared library bool unload_library() { fprintf(stdout, "info: unloading [%s]...\n", G_p_lib_name); assert(G_p_handle != 0); int result = dlclose(G_p_handle); if(result != 0){ fprintf(stderr, "error: unload library: %s\n", dlerror()); } // unload check can only be done by reload with RTLD_NOLOAD, see // the manual. void* handle = dlopen(G_p_lib_name, RTLD_LAZY | RTLD_NOLOAD | RTLD_DEEPBIND); if(handle != 0){ fprintf(stderr, "error: Failed to unload %s\n", G_p_lib_name); } else{ fprintf(stdout, "info: library [%s] has been unloaded.\n", G_p_lib_name); } return (handle == 0); } //---------------------------------------------------------------------- void dynamic_loading() { // load IExample_factory * p_factory = load_library(); fprintf(stdout, "info: new IExample_class.\n"); IExample_class *p_exp = p_factory->new_example_class(); // p_exp->print(); p_factory->delete_example_class(p_exp); // unload unload_library(); } //---------------------------------------------------------------------- int main() { dynamic_loading(); return 0; } //----------------------------------------------------------------------
Comments
Post a Comment