Skip to main content

詳細を知りたくない 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;
}
//----------------------------------------------------------------------

Comments

Popular posts from this blog

共有メモリによるプロセス間通信

Unix の共有メモリを使ったプロセス間通信について調べて実験をしてみた.対象は1つのホスト上での複数のプロセスである.ネット上でいくつか例題はないかと探したが,どうも良い例となるコードが見当たらなかった.結局はある解説記事と,Stack Overflow の議論と,man page を見て作ってみたものになったので,例をここに置くのも有用かと考え,この記事を書く.(もしかしたら探し方が悪くて良いコード例をみつけられなかっただけかもしれない.) mmap を使うかどうかという話がいくつもでていたが,POSIX の方向としては,shmem_open と mmap を使うという方向があるということだったので,それを信じてその形での実装を試してみた. 基本的なコードの流れは次のようになる. 共有メモリ領域を1つのプロセスが shm_open() を使って作成する.その際に,プロセス間で共通の文字列を識別子(``identifier'')とする.(Linux ではこれが /dev/shm/identifier のように見える.) 共有メモリ領域を mmap() でメモリにマップする.共有メモリポインター (shared_ptr)が得られる. shared_ptr を使って複数のプロセスで通信をする. 利用終了後は munmap() をつかってマップを消す. 共有メモリオブジェクトを shm_unlink() によって消す. 以下に示すプログラムは,server と client の2つのプロセスが共有メモリを使って通信をするものである.ここで,server プロセス数と client プロセス数は共に 1 を仮定する.server と client は自分の領域にしか値を書き込まないことで,ロックを避けている.互いに相手の値を読み,それよりも1大きい数を一定の期間ごとに自分の領域に書くという例題である.シンプルではあるが,共有メモリで通信をする基本としては十分なものだと思う.ソースコード(shmem_test.cpp)を以下に付加する.ソースコードのコメントにコンパイル方法とどのように利用するかを書いておく. /*   Shared memory inter process communication minimal exa

複数の線を持つ線グラフを Jenkins の plot plugin で描く方法

私は毎夜のソフトウェアテストを自動化するために Jenkins というツールを使っています.今回は, valgrind  を使ってメモリーリークのテストを自動化することにし ました.その際,エラーの数の結果をグラフとして表そうと思って, Plot plugin  を使うことにしました. Plot plugin の例図からは,複数のデータラインを描くことができるのは明らかなのですが,どうやったらいいのかは参照のページや,例としてあった Perl script,plugin 中の help からは私にはよくわからなかったのです. ここで重要な考えは,それぞれのデータラインにはそれぞれの出力ファイルが必要ということでした.私はこれを誤解していました. 例として,ビルドの時に次の property データファイルを出力します.それぞれのファイルが1つのデータラインを表します. valgrind_trunk_result.definitely.property valgrind_trunk_result.indirectly.property valgrind_trunk_result.possibly.property それぞれのデータの中身は1行のデータ点です.たとえば, valgrind_trunk_result.definitely.property ファイルの中身は次のような1行 です. YVALUE=0 このファイルを ${WORKSPACE} ディレクトリ以下に出力します.ここで," WORKSPACE " は jenkins が提供する環境変数です. 図1が私の plot plugin の設定を示しています.これは jenkins の config 画面です.3つの data series があって,それぞれにデータファイルがあります. Figure 1: Plot plugin configuration in Jenkins 図2が結果です.複数の線が描かれているのがわかります.(実際には 3 本の線がありますが,最初の線と2番目の線が同じデータなので,重ねって見えません.) Fugure 2: Plot data with multiple data lines

ソニーのカメラ (α 5000) の 30 分のビデオ録画時間の制限を外す方法

私は Sony の Alpha 5000 を気にいって使っています。しかし一つだけ問題がありました。それはビデオの録画時間の制限が 30 分というものです。 今日,ちょっと気になって探したらこの制限を解除できることがわかりました。以下のビデオがその紹介です。 https://youtu.be/7cstA_PuRIg このビデオの作者によれば,ほとんどのソニーのカメラのビデオの制限はなくせるそうです。ただし私が試したのは,Alpha 5000 のみです。 手順 カメラ側 スイッチ On Menu -- Setup --- USB connection を MTP にする スイッチ Off and On USB ケーブルでカメラをコンピュータに接続する (以下接続したままにする) コンピュータ側でソフトのダウンロードとインストール (私は Windows 10 で試しました) 次の URL に行く https://sony-pmca.appspot.com/apps ただし,Internet Explorer か Safari のみサポートということでした。Chrome では上手くいきませんでした。私が試したのは Windows 10,Internet Explore 11 です。 注意事項: このサイトは Sony のサイトですが,ここにあるソフトウェアは Sony のものとは限らないので保証はありません。御自分でリスクを判断してご利用下さい。当方も何も責任を負えません。 上記の URL から,OpenMemories のページに移動する。 このページにある PMCADownloader plugin (PMCADownloader.msi) をダウンロードする PMCADownloader をインストールする 私はいちどここでページを閉じてもう一度 https://sony-pmca.appspot.com/apps を開き,OpenMemories のページに移動しました ここで log に Loading plugin Plugin loaded と表示されます。PMCADownloader の Install がされていない時には,``Plugin loaded'&#