web-dev-qa-db-ja.com

__libc_init_arrayを理解する

http://newlib.sourcearchive.com/documentation/1.18.0/init_8c-source.html から__libc_init_arrayのソースコードを表示しました。
しかし、この関数が何をするのかよくわかりません。

私はこれらのシンボルが

/* These magic symbols are provided by the linker.  */
extern void (*__preinit_array_start []) (void) __attribute__((weak));
extern void (*__preinit_array_end []) (void) __attribute__((weak));
extern void (*__init_array_start []) (void) __attribute__((weak));
extern void (*__init_array_end []) (void) __attribute__((weak));
extern void (*__fini_array_start []) (void) __attribute__((weak));
extern void (*__fini_array_end []) (void) __attribute__((weak));

リンカスクリプトで定義されています。
リンカースクリプトの一部は次のようになります。

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  ...

次に、ELF-v1.1、gcc 4.7.2、ld、およびcodesourcery(codesourcery g ++ liteを使用しています)のドキュメントでキー「init_array」を使用して検索したところ、何も得られませんでした。

これらの記号の仕様はどこにありますか?

12
Pony279

これらのシンボルは、main()の前後に呼び出されるC/C++コンストラクタおよびデストラクタの起動と破棄のコードに関連しています。 _.init_、_.ctors_、_.preinit_array_、および_.init_array_という名前のセクションは、C/C++オブジェクトの初期化に関係し、セクション_.fini_、_.fini_array_は、および_.dtors_は破棄用です。開始記号と終了記号は、そのような操作に関連するコードセクションの開始と終了を定義し、ランタイムサポートコードの他の部分から参照される場合があります。

_.preinit_array_セクションと_.init_array_セクションには、初期化時に呼び出される関数へのポインターの配列が含まれています。 _.fini_array_は、破棄時に呼び出される関数の配列です。おそらく、開始ラベルと終了ラベルは、これらのリストをウォークするために使用されます。

これらのシンボルを使用するコードの良い例は、ここにあります libc source for _initfini.c_。起動時に__libc_init_array()が呼び出され、これが最初に開始ラベルと終了ラベルを参照してセクション_.preinit_array_のすべての関数ポインターを呼び出すことがわかります。次に、_.init_セクションの_init()関数を呼び出します。最後に、セクション_.init_array_のすべての関数ポインターを呼び出します。 main()が完了した後、__libc_fini_array()へのティアダウン呼び出しにより、_.fini_array_内のすべての関数が呼び出され、最後に_fini()が呼び出されます。ティアダウン時に呼び出す関数の数を計算するときに、このコードにカットアンドペーストのバグがあるように見えることに注意してください。おそらく、彼らはリアルタイムのマイクロコントローラーOSを扱っていて、このセクションに遭遇したことはありません。

13
Robotbugs

これらの特別なシンボルは、生成されたライブラリのPT_DYNAMICセクションによって参照されることになります。 PT_DYNAMICは、ダイナミックリンクを成功させるために必要なさまざまなリソース(ライブラリの依存関係、エクスポートされたシンボル、シンボルハッシュテーブル、init/fini配列など)を定義します。

したがって、これらのリスト内のすべての関数は、最終的にPT_DYNAMICセクションにリンクされ、動的リンクプロセス中の適切なタイミングで呼び出されます。詳細については、lddのソースを参照することをお勧めします。

3
nneonneo

これらのオブジェクトの仕様は、elfヘッダーファイル形式の仕様です。少なくとも彼らがそこにいる理由。

それらは[〜#〜] not [〜#〜] glic libとそれが話すすべてのものを書き直すことを計画していない限り、形の手段や形で動作するように作られています。つまり、elfヘッダーには_start関数が必要です。バイナリがないと起動しません。

Libcライブラリの大部分は、これを考慮しないCではなくアセンブリで記述されています。 pre array関数は、このヘッダーを追加する方法です。

例については、glibcまたはteeny-efl.gitgnu-csフォルダーを確認してください。また、配列をスラッシュ形式の文字列として設定します。両方の要素を静的、argvの配列、およびinit_arrayとして設定します。後でそれらが一致することを確認します。また、このプロセスを中断したり、そのままにしておくことを目的とした以外のことを行うには、この種の関数に追加する必要があるよりも多くのコードが必要です。冷蔵庫で遊んでください。

1
user9177922

@Robotbugsからの回答は興味深いものですが、他の人の好奇心を満たすかもしれないいくつかの追加情報を見つけました。

System Vアプリケーションバイナリインターフェイス はgccによって生成された実行可能ファイルに適用されるようです(そして他のコンパイラもあると思います-clangが思い浮かびます)。

特別セクションの章 状態(関連する部分のみ、私が並べ替え):

.preinit_array:

このセクションは、セクションを含む実行可能オブジェクトまたは共有オブジェクトの単一の事前初期化配列に寄与する関数ポインターの配列を保持します。

.init_array

このセクションは、セクションを含む実行可能オブジェクトまたは共有オブジェクトの単一の初期化配列に寄与する関数ポインターの配列を保持します。

.fini_array

このセクションは、セクションを含む実行可能オブジェクトまたは共有オブジェクトの単一の終了配列に寄与する関数ポインターの配列を保持します。

newlibのファイルinit.c 含まれるもの:

/* Iterate over all the init routines.  */
void
__libc_init_array (void)
{
    size_t count;
    size_t i;

    count = __preinit_array_end - __preinit_array_start;
    for (i = 0; i < count; i++)
        __preinit_array_start[i] ();

#ifdef HAVE_INIT_FINI
    _init ();
#endif

    count = __init_array_end - __init_array_start;
    for (i = 0; i < count; i++)
    __init_array_start[i] ();
}

これは、STM32プロセッサの標準的なリンカスクリプトソリューションに対応します(例として)。

.preinit_array     :
{
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH

そのリンカースクリプト部分は非常に明確です。Newlibがpreinitおよびinitに対して System Vアプリケーションバイナリインターフェイス で指定された配列関数を実行するために必要なシンボルを定義します。これは、C++の静的コンストラクターの標準ソリューションのようです。そして、finiは静的デストラクタに対応します。

もちろん、この話の最も皮肉な部分は、 最初の使用イディオムで構築 なしで静的C++オブジェクトを使用することが、 静的初期化順序の問題 を取得するための最良の方法であるということです。つまりC++オブジェクトは、実際には上記のpreinit/init配列を介して構築されるべきではありません。

0
nilo