私は GCCのコード生成規約のオプション について読んだことがありますが、「Generate position-independent code(PIC)」の機能を理解できませんでした。それが何を意味するのかを説明するために例を挙げてください。
位置独立コードとは、生成されたマシンコードが機能するために特定のアドレスにあることに依存しないことを意味します。
例えば。ジャンプは絶対的ではなく相対的に生成されます。
疑似アセンブリ:
PIC:コードが100番地か1000番地かには関係ありません。
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL CURRENT+10
...
111: NOP
非PIC:コードがアドレス100にある場合にのみ機能します。
100: COMPARE REG1, REG2
101: JUMP_IF_EQUAL 111
...
111: NOP
編集:コメントに対応して。
コードが-fPICでコンパイルされている場合は、ライブラリに含めるのに適しています。ライブラリはメモリ内の適切な場所から別のアドレスに移動できなければなりません。
すでに述べたことをもっと簡単な方法で説明しようと思います。
共有ライブラリがロードされるたびに、ローダ(実行するプログラムをロードするOS上のコード)は、オブジェクトのロード先に応じてコード内のアドレスをいくつか変更します。
上記の例では、非PICコードの "111"は、最初にロードされたときにローダによって書き込まれます。
共有されていないオブジェクトの場合は、コンパイラがそのコードに対していくつかの最適化を行うことができるので、そのようにしたいと思うかもしれません。
共有オブジェクトの場合、別のプロセスがそのコードに「リンク」したい場合、同じ仮想アドレスにそれを読み取る必要があります。そうしないと、「111」は意味がありません。しかし、その仮想空間は2番目のプロセスで既に使用されている可能性があります。
共有ライブラリに組み込まれるコードは通常、位置に依存しないコードである必要があります。これにより、共有ライブラリはメモリ内の(ほぼ)任意のアドレスに容易にロードできます。 -fPIC
オプションはGCCがそのようなコードを生成することを保証します。
さらに追加しています...
すべてのプロセスは同じ仮想アドレス空間を持っています(Linux OSでフラグを使って仮想アドレスのランダム化を停止した場合)(詳細は 自分自身だけでアドレス空間レイアウトのランダム化を無効にしてから有効にする ))
そのため、共有リンクのない1つのexe(仮説シナリオ)の場合、同じasm命令に同じ仮想アドレスを常に害を与えることなく与えることができます。
しかし、共有オブジェクトをexeにリンクしたい場合は、共有オブジェクトがリンクされている順序に依存するため、共有オブジェクトに割り当てられた開始アドレスがわかりません。リンク先のプロセスによって異なる仮想アドレス。
したがって、1つのプロセスが自身の仮想空間で開始アドレスを0x45678910にし、他のプロセスが開始アドレスを0x12131415にすることができます。相対アドレス指定を使用しない場合、.soはまったく機能しません。
したがって、それらは常に相対アドレッシングモード、したがってfpicオプションを使用する必要があります。
動的ライブラリ内の関数へのリンクは、ライブラリのロード時または実行時に解決されます。したがって、プログラムの実行時に実行可能ファイルと動的ライブラリの両方がメモリにロードされます。動的ライブラリがロードされるメモリアドレスは事前に決定することはできません。固定アドレスが同じアドレスを必要とする別の動的ライブラリと衝突する可能性があるためです。
この問題に対処するために一般的に使用される2つの方法があります。
1.リロケーション。コード内のすべてのポインタとアドレスは、必要に応じて実際のロードアドレスに合うように修正されます。再配置は、リンカとローダによって行われます。
2.位置に依存しないコードコード内のアドレスはすべて現在位置からの相対位置です。 Unix系システムの共有オブジェクトは、デフォルトで位置に依存しないコードを使用します。特に32ビットモードでプログラムを長時間実行した場合、これは再配置よりも非効率的です。
「位置独立コード」という名前は、実際には次のことを意味します。
コードセクションには、再配置が必要な絶対アドレスは含まれず、自己相対アドレスだけが含まれます。したがって、コードセクションを任意のメモリアドレスにロードし、複数のプロセス間で共有することができます。
データセクションには書き込み可能なデータが含まれることが多いため、データセクションは複数のプロセス間で共有されません。したがって、データセクションには、再配置が必要なポインタまたはアドレスが含まれる可能性があります。
すべてのパブリック関数とパブリックデータはLinuxで上書きすることができます。メイン実行可能ファイル内の関数が共有オブジェクト内の関数と同じ名前を持つ場合、mainから呼び出されたときだけでなく、共有オブジェクトから呼び出されたときにも、main内のバージョンが優先されます。同様に、mainのグローバル変数が共有オブジェクトのグローバル変数と同じ名前を持つ場合、共有オブジェクトからアクセスした場合でもmainのインスタンスが使用されます。
このいわゆるシンボルインターポジションは、静的ライブラリの動作を模倣することを目的としています。
共有オブジェクトには、この「オーバーライド」機能を実装するために、プロシージャリンケージテーブル(PLT)と呼ばれる関数へのポインタのテーブルと、グローバルオフセットテーブル(GOT)と呼ばれるその変数へのポインタのテーブルがあります。関数とパブリック変数へのアクセスはすべてこのテーブルを通過します。
pS動的リンクが避けられない場合は、位置に依存しないコードの時間を消費する機能を避けるためのさまざまな方法があります。
あなたはこの記事からもっと読むことができます: http://www.agner.org/optimize/optimizing_cpp.pdf
すでに投稿された回答へのマイナーな追加:位置に依存しないようにコンパイルされていないオブジェクトファイルは再配置可能です。それらは再配置テーブルエントリを含みます。
これらのエントリにより、ローダ(プログラムをメモリにロードするコードのそのビット)は、仮想アドレス空間の実際のロードアドレスに合わせて絶対アドレスを書き換えることができます。
オペレーティングシステムは、メモリにロードされた「共有オブジェクトライブラリ」の単一コピーを、その同じ共有オブジェクトライブラリにリンクされているすべてのプログラムと共有しようとします。
コードアドレス空間は(データ空間のセクションとは異なり)連続している必要はなく、また特定のライブラリにリンクするほとんどのプログラムはかなり固定されたライブラリ依存関係ツリーを持っているので、これはほとんどの場合成功します。矛盾があるようなまれなケースでは、はい、共有オブジェクトライブラリの2つ以上のコピーをメモリに保存する必要があるかもしれません。
明らかに、悪用可能なパターンを作成する可能性を減らすために、プログラムやプログラムインスタンス間でライブラリのロードアドレスをランダム化しようとする試みは、珍しいことではなく一般的になります。すべての共有オブジェクトライブラリをコンパイルして、位置に依存しないようにする必要があります。
メインプログラムの本体からこれらのライブラリへの呼び出しも再配置可能になるので、共有ライブラリをコピーする必要が生じる可能性ははるかに低くなります。