Sunday, November 18, 2012

詳細を知りたくない bug: どうやって global static destructor を二度呼ぶのか

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 を使う以下のようなものである.
  1. main のある application に static object A を link する.
  2. static object A のある shared library S を作成する.
  3. 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 である.
このコードだけでは実は global static destructor を二度呼ぶには足りない.次のように 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;
}
//----------------------------------------------------------------------

No comments:

Post a Comment