26

プログラムの実行前にプリロードできる次のライブラリを検討してください。

// g++ -std=c++11 -shared -fPIC preload.cpp -o preload.so
// LD_PRELOAD=./preload.so <command>
#include <iostream>

struct Goodbye {
    Goodbye() {std::cout << "Hello\n";}
    ~Goodbye() {std::cout << "Goodbye!\n";}
} goodbye;

問題は、グローバル変数のコンストラクターgoodbyeは常に呼び出されますが、次のような一部のプログラムではデストラクタが呼び出されないことですls

$ LD_PRELOAD=./preload.so ls
Hello

他の一部のプログラムでは、デストラクタが期待どおりに呼び出されます。

$ LD_PRELOAD=./preload.so man
Hello
What manual page do you want?
Goodbye!

最初のケースでデストラクタが呼び出されない理由を説明できますか? EDIT:上記の質問はすでに回答されています。つまり、プログラムは_exit()、abort()を使用して終了する可能性があります。

でも:

プリロードされたプログラムの終了時に特定の関数を強制的に呼び出す方法はありますか?

4

3 に答える 3

32

lsatexit (close_stdout);初期化コードとして持っています。完了すると、stdout (つまりclose(1)) が閉じられるため、cout,printfまたはwrite(1, ...操作は何も出力しません。デストラクタが呼び出されないという意味ではありません。これは、デストラクタで新しいファイルを作成するなどして確認できます。

http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c#n1285これは GNU coreutils ls の行です。

だけでなくls、ほとんどの coreutils がそれを行います。残念ながら、彼らがそれを閉じることを好む正確な理由はわかりません.

これを見つける方法(または少なくとも私がしたこと)に関する補足事項-次回またはソースコードにアクセスできないプログラムで役立つ場合があります:

デストラクタ メッセージは/bin/true(考えられる最も単純なプログラム) で出力されますが、lsorでは出力されませんdf。私は最新のシステムコールから始めて比較strace /bin/trueしました。strace /bin/lsには と が表示されましたが、 には表示されませclose(1)んでした。その後、物事が理にかなっているようになり、デストラクタが呼び出されていることを確認する必要がありました.close(2)lstrue

于 2014-05-27T16:35:26.180 に答える
9

プログラムが_exit(POSIX) または_Exit(C99) またはプログラムの異常終了 ( abort、致命的なシグナルなど) によって終了する場合、デストラクタを呼び出す方法はありません。これを回避する方法はありません。

于 2014-05-25T00:05:22.830 に答える
2

他の人が言ったように、プログラムは_exit(), _Exit()orabort()を介して呼び出すことができ、デストラクタは気付かないでしょう。これらのケースを解決するには、以下の例のようにラッパーを記述するだけで、これらの関数をオーバーライドできます。

void
_exit(int status)
{
    void (*real__exit)(int) __attribute__((noreturn));
    const char *errmsg;

    /* Here you should call your "destructor" function. */
    destruct();

    (void)dlerror();
    real__exit = (void(*)(int))dlsym(RTLD_NEXT, "_exit");
    errmsg = dlerror();
    if (errmsg) {
        fprintf(stderr, "dlsym: _exit: %s\n", errmsg);
        abort();
    }

    real__exit(status);
}

しかし、これは、アプリケーションが持つことができる唯一の出口点ではないため、ライブラリの知識なしにプログラムがエスケープする可能性をすべて解決するわけではありません。exitまた、関数を介してシステムコールをトリガーする可能性があり、syscall()それを回避するには、それもラップする必要があります。

プログラムが終了する別の方法は、未処理のシグナルを受信することです。したがって、プログラムの終了をトリガーする可能性のあるすべてのシグナルも処理 (またはラップ) する必要があります。詳細についてはsignal(2)man ページを参照してください。ただし、SIGKILL(9) のようなシグナルは処理できず、アプリケーションは を呼び出すことによって自身を破壊する可能性があることに注意してくださいkill()。そうは言っても、狂ったサルによって書かれた非常識なアプリケーションを処理することを期待していない限り、ラップする必要がありkill()ます。

ラップする必要がある別のシステム コールはexecve().

とにかく、システム コール ( など) は、アセンブリ命令または廃止されたマクロ_exitを介して直接トリガーすることもできます。アプリケーションの外部からではない場合 (または など)、どのようにラップしましたか? プログラムでこの種の動作が予想される場合は、テクニックをやめて、(別のプロセスから使用して) like and do を実行するか、Linux カーネル モジュールを作成してトレースすることを検討することをお勧めします。int 0x80_syscallX() stracevalgrindLD_PRELOADstracevalgrindptrace()

于 2014-05-30T19:01:39.873 に答える