web-dev-qa-db-ja.com

initおよびfiniの実行

ELFファイルの initおよびfiniセクション について読んで、試してみました。

#include <stdio.h>
int main(){
  puts("main");
  return 0;
}

void init(){
  puts("init");
}
void fini(){
  puts("fini");
}

gcc -Wl,-init,init -Wl,-fini,fini foo.cを実行して結果を実行すると、「init」部分が出力されません。

$ ./a.out
main
fini

初期化部分が実行されなかったか、またはどういうわけか印刷できませんでしたか?

Init/finiに関する「公式」ドキュメントはありますか?

man ldさんのコメント:

 -init=name
     When creating an ELF executable or shared object, call
     NAME when the executable or shared object is loaded, by
     setting DT_INIT to the address of the function.  By
     default, the linker uses "_init" as the function to call.

それは、init関数に_initという名前を付ければ十分でしょうか? (gccを実行すると、複数の定義について不満が出ます。)

21
michas

それをしないでください。コンパイラーとリンカーに、適切と思われるセクションを入力させます。

代わりに、適切な 関数属性 で関数をマークして、コンパイラとリンカーが正しいセクションに配置できるようにします。

例えば、

static void before_main(void) __attribute__((constructor));
static void after_main(void) __attribute__((destructor));

static void before_main(void)
{
    /* This is run before main() */
}

static void after_main(void)
{
    /* This is run after main() returns (or exit() is called) */
}

優先度(たとえば、__attribute__((constructor (300))))を割り当てることもできます。これには、101〜65535の整数が含まれ、優先度の値が小さい関数が最初に実行されます。

説明のために、関数にstaticとマークしたことに注意してください。つまり、関数はファイルスコープの外では表示されません。関数は、自動的に呼び出されるようにエクスポートされたシンボルである必要はありません。


テストのために、以下を別のファイルに保存することをお勧めします。たとえば、tructor.c

#include <unistd.h>
#include <string.h>
#include <errno.h>

static int outfd = -1;

static void wrout(const char *const string)
{
    if (string && *string && outfd != -1) {
        const char       *p = string;
        const char *const q = string + strlen(string);

        while (p < q) {
            ssize_t n = write(outfd, p, (size_t)(q - p));
            if (n > (ssize_t)0)
                p += n;
            else
            if (n != (ssize_t)-1 || errno != EINTR)
                break;
        }
    }
}

void before_main(void) __attribute__((constructor (101)));
void before_main(void)
{
    int saved_errno = errno;

    /* This is run before main() */
    outfd = dup(STDERR_FILENO);
    wrout("Before main()\n");

    errno = saved_errno;
}

static void after_main(void) __attribute__((destructor (65535)));
static void after_main(void)
{
    int saved_errno = errno;

    /* This is run after main() returns (or exit() is called) */
    wrout("After main()\n");

    errno = saved_errno;
}

そのため、プログラムやライブラリの一部としてコンパイルおよびリンクできます。共有ライブラリとしてコンパイルするには、例えば.

gcc -Wall -Wextra -fPIC -shared tructor.c -Wl,-soname,libtructor.so -o libtructor.so

あなたはそれを動的にリンクされたコマンドやバイナリに挿入することができます

LD_PRELOAD=./libtructor.so some-command-or-binary

関数はerrnoを変更せずに保持しますが、実際には問題にならず、低レベルのwrite() syscallを使用してメッセージを標準エラーに出力します。多くの場合、標準エラー自体は最後のグローバルデストラクタ(ここではデストラクタ)が実行される前に閉じられるため、最初の標準エラーは新しい記述子に複製されます。

(一部の妄想的なバイナリ、通常はセキュリティの影響を受けやすいバイナリは、知らない記述子をすべて閉じるため、すべての場合にmightAfter main()メッセージが表示されません。)

13
Nominal Animal