ELF記号:グローバル+非表示

ELF記号:グローバル+非表示

例えばこのOracleの説明:

STB_GLOBAL グローバル記号です。このシンボルはすべてのオブジェクトファイルに表示されます。マージされます。あるファイルのグローバルシンボル定義は、同じグローバルシンボルに対する別のファイルの未定義参照を満たします。 ...

STV_HIDDEN 名前が非表示の場合、現在のコンポーネントで定義されているシンボル他のコンポーネントには見えない。これらの記号は保護する必要があります。このプロパティは、コンポーネントの外部インターフェイスを制御するために使用されます。これらのシンボルで指定されたオブジェクトは、そのアドレスが外部に渡される場合は、他のコンポーネントによって引き続き参照できます。
再配置可能オブジェクトに含まれる隠しシンボルは削除されるか、STB_LOCALバインディングに変換されます。再配置可能オブジェクトファイルが実行可能ファイルまたは共有オブジェクトファイルに含まれる場合、リンクエディタによって実行されます。

しかし、いくつかの簡単なテストプログラム(x86-64 LinuxでGCCにコンパイルされています)を見ると、readelf -sいくつかのグローバルな隠しシンボルがあります。

FUNC    GLOBAL HIDDEN    16 _fini
OBJECT  GLOBAL HIDDEN    25 __dso_handle
OBJECT  GLOBAL HIDDEN    25 __TMC_END__

上記の説明によると、これはとんでもないことであり、単に許可されていません。

この組み合わせにはどのような属性(可視性、挿入機能...)がありますか?

答え1

もう少し理解できますね。

GLOBAL + HIDDENは通常、.o関数を隠す非リンクファイル(静的ライブラリを含む)で発生します(一方、ユニットローカル静的関数はすでにLOCAL + DEFAULTです)。その後、接続中にGLOBAL + HIDDENはLOCAL + DEFAULTに変換され、これは常に一般的な機能に使用されます。

これは、共有ライブラリを作成するときに問題のGCC / libの内部でも発生します。ただし、ライブラリではなく実行可能ファイルの場合、これらの内部は純粋に便宜上の理由で完全に変換されず、グローバル+隠し状態に保たれます。どこにもリンクされたライブラリではないので、何の害もありません。

たぶん要約されるかもしれません。一般的なユーザー定義関数他の人にも役立ちます...これはLinuxの動作に関するものです。他のシステムでは若干異なる場合があります。

考慮すべき事項(正常機能)

バイナリを作成しましょうmybin。これには3つのファイルがあり、fila.cコードファイルの1つに関数があります。filb.cfilc.cfunc1

mybin静的ライブラリ(コンパイル.oおよびアーカイブされた場合のみ)、実行可能プログラム(リンクされている場合もあります)、または共有ライブラリ(リンクされている場合の場合)です。

func1コードは次のとおりです。

  • 通常の機能
  • 弱い(__attribute__((weak))
  • 隠し、mybin共有ライブラリを使用するプログラムでは使用できますが、共有ライブラリを使用するプログラムでは使用できない(存在する場合)を__attribute__ ((__visibility__ ("hidden")))意味します。
  • 静的、.c定義されたファイルでのみ使用可能(staticCでは)
  • (保護と内部も同様ですが、実際には重要ではありません)

これは、シンボルを完全に削除できる最適化(コンパイル時間および/またはリンク時間最適化(LTO))を考慮しません。

コンパイルステップ(〜.o

fila.c埋め込みファイルでは、関数func1はすべての種類(一般/弱/隠し/静的)に対して明確に表示され、利用可能です。

  • 正常な機能:
    • filb.cfilc.c他のユニット(および静的ライブラリ(存在する場合))にも表示されます。
    • すべてのユニットには、その名前を持つ汎用/強力関数が1つしかありません。それ以外の場合、リンカは後で文句を言うでしょう(これは実行可能/共有ライブラリ/静的ライブラリ)(リンカオプションなどの奇妙な項目は除くmuldefs)。
    • 生成されたファイルでは、関数は.oGLOBAL + DEFAULTです。
  • あまり強力ではない
    • 他のユニットにも表示
    • 1つの一般/強力な機能に加えて、それを囲むいくつかの弱い機能があるかもしれません。この場合、後で接続するとすべてのユニットに強いものが使用され、弱いものは消えます。
    • 弱い関数のみがあり、その名前の強い関数がない場合、最初の弱い関数が勝ちます(つまり、そのような弱い関数を持つコンパイラコマンドラインの最初のファイル)。
    • 生成されたファイルでは、関数は.oWEAK + DEFAULTです。
  • 隠された機能
    • GLOBAL+HIDDENになります(ファイルのみ.o
    • それ以外の場合は、通常の関数のように動作します。
  • 弱さ+隠し機能
    • 弱くなる+隠れる
    • それ以外の場合は、弱い機能のように動作します。
  • 静的関数
    • ローカル+デフォルトに切り替え
    • 他のユニットには表示されません。これを使用すると、ユニット全体で使用されるときにすべてのLOCALが無視されるため、リンカは後で文句を言います。
    • 複数のユニットがその名前を持つ固有の静的機能を持つことができ、すべて異なる場合があります。
  • .oファイルにLOCAL + HIDDENがありません。

コンパイル時の接続

静的ライブラリを作成すると、接続は発生しません。

共有ライブラリと実行ファイルの場合:

  • すべてのローカル(つまり、静的関数)は完全に無視され、他のユニットには表示されず、利用可能な共有ライブラリ関数にエクスポートされません。
  • 上記のようにGLOBAL / WEAK + DEFAULTを使用でき、共有ライブラリの場合は利用可能な関数のリストに入力されます()同じ種類があります。
  • GLOBAL/WEAK+HIDDEN は上記のバイナリ単位で使用できますが、リンク中は型が LOCAL+DEFAULT に変更されます (リンク前の静的関数と同じ)。共有ライブラリと利用可能な機能のリスト()、これはまったく入力されていないか、実行時に無視されることを意味します。

呼び出される関数名は、生成されたばかりのバイナリまたはリンカコマンドラインで指定された共有ライブラリ(およびGLOBAL / WEAK + DEFAULT)に通常存在する必要があります。
前述のように、完全にリンクされたバイナリ(共有ライブラリまたは実行可能ファイル)には、同じ名前の複数のGLOBAL / WEAK + DEFAULT関数がなく、最大1つしか含まれていません。バイナリファイルごとに1つです。ただし、異なるバイナリ間にはまだ重複がある可能性があります。つまり、両方の共有ライブラリ(または実行可能ファイルと共有ライブラリの両方)にこれがある可能性がありますfunc1。これは、以下の挿入セクションで説明されているようにルックアップを引き起こしません。

接続中に使用された関数名がどこにも実装されていない場合(実行ファイルまたは使用された共有ライブラリの両方)、通常エラーが発生します。ただし、定義(.hファイルなどでコードなし)がWEAKとマークされている場合、そのリンクはエラーを発生させず、実行時に定義が見つからない場合はNULLポインタとして評価されます(checkを使用できますif)。 。実行時に共有ライブラリが同時に変更されたか、LD_PRELOADより多くのライブラリが追加されたためです。

ランタイム介入と最適化

関数呼び出しはバイナリ命令を生成できます。

  • プラグ可能ではありません:これらの呼び出しは、同じバイナリ(同じ共有ライブラリまたは同じ実行可能ファイル、別のユニット、または同じユニットにある可能性があります)アドレス.oに存在するいくつかの事前計算された(絶対/相対)関数にジャンプします。これにより、関数インラインなどの最適化も可能になります。
  • interposable: 実行時に、実行時リンカーは、ld.soその名前を持つ関数がどこにあるかを尋ね、他のプログラム実行で異なる結果を生成できます。これはまた、関数インラインがないことを意味し、パフォーマンスがわずかに低下する可能性があります。ただし、以下を参照して関数をオーバーライドできます。

バイナリ(実行ファイル→共有ライブラリまたは共有ライブラリ1→共有ライブラリ2)間の呼び出しは常に接続可能です。
同じバイナリ内の2つの「自己」関数間の呼び出し:

  • ローカル機能(静的、非表示)はプラグできません。
  • GLOBAL/WEAK 関数の場合、このバイナリを生成するときにコンパイラ/リンカー オプションによって異なります (例: fPIC fPIE fno-semantic-interposition -Bsymbolic -rdynamic など)。

実行中のプログラム内の呼び出しにld.so検索が必要な場合func1

  • 常にプログラムの実行可能なバイナリを最初に確認してください。
  • デフォルトのexeに見つからない場合は、LD_PRELOAD共有ライブラリ(存在する場合)が考慮されます。
  • その後、プログラムで使用されるすべての共有ライブラリは、接続中に指定された順序(コマンド行の順序)に従って検査されます。
    GLOBAL/WEAK+DEFAULT 機能のみが考慮されます。この段階では、GLOBALとWEAKの間に違いはありません。以前の検査位置の WEAK は、その後のライブラリーの弱い機能よりも優先されます。 (歴史的な違いはずっと前に存在していましたが、Linuxではずっと前に消えました。)

これは、例えば、次のことを意味します。

  • バイナリ間の関数呼び出しは、常にLD_PRELOADライブラリによってオーバーライドできます。デフォルトの実行可能ファイルがfunc1一部の共有ライブラリから呼び出そうとし、事前ロードされたライブラリに別のライブラリがある場合、事前func1ロードされたライブラリが勝ちます。共有ライブラリ間の呼び出しは似ており、プリロードされたライブラリは常に最初に検索されます。
  • 共有ライブラリではなく、デフォルトの実行可能ファイルに存在する関数は上書きできません。したがって、LD_PRELOADプログラムのコンパイル中にプログラム内の関数への呼び出しがコンパイルされるかどうかは、実際には重要ではありません。ld.soそれ自体が常に勝利します。 (コンパイルモードは共有ライブラリにとって重要です。)
  • sharedlib2→sharedlib3間の呼び出しは、以前のsharedlib1またはデフォルトの実行可能ファイルによって上書きされる可能性があるため、sharedlib2はsharedlib3の代わりにそれを呼び出します。
  • コンパイル方法によっては、sharedlib2のグローバル関数への呼び出しは、デフォルトの実行可能ファイルと古いライブラリによって上書きされる可能性があります。または、さらに最適化できますが、上書きは認識されません。
  • コンパイラフラグなしでライブラリ内の特定の関数を上書きするのを防ぎ、より良い最適化とより高速な接続を可能にするには、その関数をコードで非表示(または静的)として宣言できます。ただし、これはその関数がデフォルトの実行可能ファイルで利用できないことを意味します(特にオーバーライドされていない場合)。関数固有のコンパイラオプションなしで動作する中間的なアプローチは、関数を非表示として宣言し、グローバルエイリアスを追加することです。内部ライブラリはオーバーライドされていない最適化された隠し関数を呼び出し、外部のすべてはエイリアスを使用します(他のライブラリによってオーバーライドされる可能性があります)。

実行ファイルに関する注意事項

他のバイナリでオーバーライド可能な呼び出しをオーバーライドする必要があるすべての関数は、独自のバイナリの一部です。すべてのGLOBAL / WEAK関数は共有ライブラリに自動的に提供されますが、デフォルトの実行可能ファイルには必ずしもそうではありません。

readelf -sリストの内容を表示できます。 GCCを使用したテストでは、リンカーは、コンパイル時に一部の共有ライブラリにもその名前の関数があることを発見した場合(コードの内容に関係なく)、関数を追加するように見えますが、関数が一意であるように見える場合はそうではありません。追加されます(これは何も上書きされないためです)。

場合によっては問題になることがあります。たとえば、コンパイル中にデフォルトの実行可能ファイルに1つしかないが、func1後で使用される共有ライブラリの1つが1つになるようにfunc1変更された場合(デフォルトプログラムを再コンパイルしない限り)、デフォルトプログラムはfunc1ライブラリの1つを上書きできません。

これを防ぐには、常にすべての関数を一覧表示して使用します-rdynamic(メインの実行可能ファイルでのみ、ライブラリでは役に立たない)。

最後に、前述のPROTECTEDモードの問題は何ですか?

原則として、ライブラリ関数のGLOBAL + PROTECTEDは、上記の隠し関数エイリアスのように機能する必要があります。ライブラリには、ライブラリ自体内で使用され最適化される非表示(LOCAL + DEFAULT)関数がありますが、それを上書きすることはできません。また、外部で使用(およびオーバーライド)できる公開エイリアスもあります。

PROTECTEDでは、HIDDENを設定してエイリアスを作成する必要はありません。

しかし、2つの問題があります。

  • ld.soPROTECTEDは、詳細とC ABI要件(同じ機能の同じアドレス)のため、HIDDEN + aliasよりも遅くなります。
  • GCC 19520のようなバグがあります。

関連情報