2012-11-18

Bugs you never want to know about the details: How to call a global static destructor twice.

Abstract

One of my favorite column is ACM's Kode Vicious (e.g., http://doi.acm.org/10.1145/1364782.1364791). I am also a developer, and I am sure that I encounter the bugs that makes his blood pressure high. This bug was ``a global static destructor is called twice.''

Contents

I basically avoid to write a code that has static objects. Because static objects has a side effect and also their construction and destruction order is not under the programmer's control in C++ language. Then, why I encounter this bug? Well, I didn't write it at the first  place. But I can not avoid since it is a part of my job.

I found my crash is caused by a static destructor is called twice. I first thought, the stack is corrupted, since I didn't know it is possible except the compiler bug. In C++, static object constructed and destructed only once, the constructor is called before the main function starts, and the destructor is called after main function finished. The number of call is guaranteed by the compiler.

But, actually it is possible to trick the compiler, depends on how to link the objects. The idea is using dlopen() as the following:

Link a static object A to the application that has main() function.
Create a shared library S which contains the static object A.
The application's main() loads the shared library S dynamically, and unload.

Some may got the idea. The rest is details in my code example.

  • example_factory.h is in the shared library and provides the interface of IExample_class for the application.
  • example_factory_impl.h, example_factory_impl.cpp is the implementation of the interface and in the shared library. There is a static object in here.
  • factory_interface.cpp provides an access point to the shared library.
  • call_static_destructor_twice.cpp is the application that uses the shared library.

These code themselves are not enough to call the static destructor twice. You need the following build script to create the application and the shared library.

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



In the ``evil build,'' we link the object file that contains the static object. This is wrong and this was the bug in our build system. These kind of bug is hard to debug. The execution result is the following. The execution environment is Kubuntu 12.04, g++ 4.6.3. As you see, static object's constructed and destructed twice. In our real bug, this caused a crash on exit.

Execution result

% ./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_impl.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: