コンピューティングにおいて、位置独立コード[ 1 ](PIC [ 1 ])または位置独立実行可能ファイル(PIE)[ 2 ]は、メモリ アドレスに関係なく正しく実行されるマシン コードの本体です。[ a ] PIC は共有ライブラリによく使用され、同じライブラリ コードを各プログラムのアドレス空間内の、たとえば他の共有ライブラリによって使用されている他のメモリと重複しない場所にロードできます。PIC は、メモリ管理ユニット(MMU)を持たない古いコンピュータ システムでも使用され、[ 3 ]オペレーティングシステムが、 MMU のないシステムの単一のアドレス空間内でもアプリケーションを互いに離しておくことができました。
位置独立コードは、変更を加えることなく任意のメモリ アドレスで実行できます。これは、正しく機能するために特定の場所にロードする必要がある絶対コード[ 1 ]や、リンカまたはプログラムローダが実行前にプログラムを変更して特定のメモリ位置からのみ実行できるようにするロード タイム ロケータ (LTL) コード [ 1 ] とは異なります。[ 1 ] 後者の用語は、位置依存コードと呼ばれることもあります。[ 4 ] 位置独立コードを生成することは、多くの場合コンパイラのデフォルトの動作ですが、コンパイラは、絶対アドレスの使用を禁止するなど、一部の言語機能の使用に制限を設ける場合があります (位置独立コードは相対アドレッシングを使用する必要があります)。特定のメモリ アドレスを直接参照する命令は、実行が速くなる場合があり、それらを同等の相対アドレッシング命令に置き換えると、実行がわずかに遅くなる場合がありますが、最新のプロセッサではその差は実質的に無視できる程度です。[ 5 ]
IBM 701 [ 6 ] (1952年4月29日) やUNIVAC I (1951年3月31日)のような初期のコンピュータでは、コードは位置独立ではありませんでした。各プログラムは、特定のアドレスにロードされ、そこから実行されるように構築されていました。これらの初期のコンピュータにはオペレーティングシステムがなく、マルチタスク機能もありませんでした。プログラムはメインストレージにロードされ (あるいは、そこから直接実行するために磁気ドラムに保存されることもありました)、一度に1つずつ実行されました。このような運用環境では、位置独立コードは不要でした。
CDC 6600、GE 625、UNIVAC 1107などのベースアンドバウンド[ b ]システムでも 、OSがコードをジョブのストレージにロードすると、ロードされた相対アドレスからしか実行できませんでした。
バロウズはセグメント化システムであるB5000 (1961年)を導入した。このシステムでは、プログラムはスタック上またはプログラム参照テーブル(PRT)上の制御ワードを介して間接的にセグメントにアドレス指定を行った。共有セグメントは、異なるプロセス内の異なるPRT位置を介してアドレス指定することができた。同様に、後のB6500では、すべてのセグメント参照はスタックフレーム内の位置を介して行われた。
IBM System/360 (1964 年 4 月 7 日) は、コード位置の独立性を念頭に置いて、UNIVAC III と同様の切り捨てアドレッシングで設計されました[ 7 ] 。切り捨てアドレッシングでは、メモリ アドレスはベース レジスタとオフセットから計算されます。プログラムの開始時に、プログラマはベース レジスタをロードしてアドレス指定可能を確立する必要があります。通常、プログラマはUSING擬似命令を使用してアセンブラにも通知します。プログラマは、エントリ ポイント アドレスを含むことがわかっているレジスタ (通常は R15) からベース レジスタをロードするか、BALR (分岐とリンク、レジスタ形式)命令 (R2 の値が 0 の場合) を使用して、次のシーケンシャル命令のアドレスをベース レジスタに格納し、プログラム内の記憶場所を参照する各命令に明示的または暗黙的にコード化することができます。コード用またはデータ用に複数のベース レジスタを使用することができました。このような命令は、24、31、32、または64ビットのアドレス(4バイトまたは8バイト)全体を保持する必要がなく、代わりにベースレジスタ番号(4ビットでエンコード)と12ビットのアドレスオフセット(12ビットでエンコード)のみを必要とするため、必要なメモリは少なくて済み、わずか2バイトで済みます。
このプログラミング手法は、IBM S/360タイプのシステムでは標準的なものであり、今日のIBM System/zに至るまで使用されています。アセンブリ言語でコーディングする場合、プログラマは上記のようにプログラムのアドレス指定を確立するとともに、動的に割り当てられた記憶領域のために他の基本レジスタを使用する必要があります。コンパイラはこの種のアドレス指定を自動的に処理します。
IBMの初期のオペレーティングシステムであるDOS/360(1966年)は仮想ストレージを使用していませんでした(System S/360の初期モデルはそれをサポートしていなかったため)が、PHASE name,* JCL(ジョブ制御言語)ステートメントを介して、ロード中にプログラムを任意の(または自動的に選択された)ストレージ場所に配置する機能がありました。
仮想ストレージのないS/360システムでは、プログラムは任意のストレージ場所にロードできましたが、そのためにはそのプログラムを格納するのに十分な大きさの連続したメモリ領域が必要でした。サイズの異なるモジュールをロードおよびアンロードすると、メモリの断片化が発生する場合がありました。仮想ストレージは、設計上、そのような制限がありません。
DOS/360とOS/360はPICをサポートしていませんでしたが、OS/360の一時的なSVCルーチンは再配置可能なアドレス定数を含むことができず、再配置なしで一時領域のいずれでも実行できました。
IBMは、IBM初のマルチタスクオペレーティングおよびタイムシェアリングオペレーティングシステムであるTSS/360をサポートするために、1965年にIBM System/360モデル67で仮想ストレージを初めて導入しました。DOS/360の後のバージョン(DOS/VSなど)や、それ以降のIBMオペレーティングシステムはすべて仮想ストレージを利用しました。切り捨てアドレス指定は基本アーキテクチャの一部として残っており、複数のモジュールを同じ仮想アドレス空間にロードする必要がある場合に依然として有利です。
比較のために、Burroughs B5000 (1961 年) およびMultics (1964 年)のBurroughs MCPのような初期のセグメントシステムや、IBM TSS/360 (1967 年) のようなページング システムでは、[ c ]コードは本質的に位置に依存しないものでした。これは、プログラム内のサブルーチンの仮想アドレスが、プログラム参照テーブル、リンケージ セグメント、プロトタイプ セクションなど、コードの外部のプライベート データに配置されていたためです。
動的アドレス変換( MMUが提供する機能)の発明により、当初は各プロセスが独自の独立したアドレス空間(アドレス範囲)を持つことができるようになったため、位置独立コードの必要性が軽減されました。しかし、同じコードを使用する複数のジョブが同時に実行されると、物理メモリが無駄になります。2つのジョブが完全に同一のプログラムを実行する場合、動的アドレス変換は、2つの異なるジョブのアドレス32Kを、プログラムの単一コピーを含む同じバイトの実メモリにマッピングすることで、問題を解決します。
異なるプログラム間で共通のコードが共有される場合があります。例えば、給与計算プログラムと売掛金管理プログラムの両方に、同一のソートサブルーチンが含まれている可能性があります。共有モジュール(共有ライブラリは共有モジュールの一種です)は一度ロードされ、2つのアドレス空間にマッピングされます。
共有ライブラリ内のプロシージャ呼び出しは、通常、小さなプロシージャリンケージテーブル(PLT)スタブを介して行われ、その後、決定的な関数が呼び出されます。これにより、共有ライブラリは独自のバージョンを使用するのではなく、以前にロードされたライブラリから特定の関数呼び出しを継承することができます。[ 8 ]
位置独立コードからのデータ参照は通常、アクセスされるすべてのグローバル変数のアドレスを格納するグローバルオフセットテーブル(GOT)を介して間接的に行われます。コンパイル単位またはオブジェクトモジュールごとに 1 つの GOT があり、コードから固定オフセットの位置にあります(ただし、このオフセットはライブラリがリンクされるまでわかりません)。リンカがモジュールをリンクして共有ライブラリを作成すると、GOT をマージしてコード内の最終的なオフセットを設定します。後で共有ライブラリをロードするときにオフセットを調整する必要はありません。[ 8 ]
グローバルデータにアクセスする位置独立コードは、GOT のエントリからグローバル変数のアドレスをフェッチすることによってアクセスします。GOT はコードから固定オフセットにあるため、コード内の特定の命令のアドレスと特定のグローバル変数の GOT エントリのアドレスとの間のオフセットも固定され、位置独立コードがロードされるアドレスに応じてオフセットを変更する必要はありません。グローバル変数の GOT エントリをフェッチする命令は、コード内の何らかの命令に対する相対オフセットを含むアドレッシング モードを使用します。これは、命令セット アーキテクチャがサポートしている場合はPC 相対アドレッシング モード、または関数プロローグ内の命令のアドレスでレジスタをロードする関数を持つレジスタ相対アドレッシング モードである可能性があります。[ 8 ] [ 9 ] [ 10 ] [ 11 ]
Microsoft Windowsのダイナミックリンクライブラリ(DLL)は、CALL命令のバリアントE8(次の命令に対して近接、相対、変位を指定して呼び出し)を使用します。これらの命令は、DLLをロードする際に変更する必要はありません。
グローバル変数(文字列リテラルの配列、仮想関数テーブルなど)には、ダイナミックライブラリのデータセクションまたはコードセクション内のオブジェクトのアドレスが格納されることが想定されています。そのため、グローバル変数に格納されているアドレスは、DLLがロードされたアドレスを反映するように更新する必要があります。ダイナミックローダーは、グローバル変数が参照するアドレスを計算し、その値をグローバル変数に格納します。これにより、グローバル変数を含むメモリページのコピーオンライトがトリガーされます。コードを含むページと、コードまたはグローバルデータへのポインタを含まないグローバル変数を含むページは、プロセス間で共有されます。この操作は、任意のアドレスにダイナミックライブラリをロードできるすべてのOSで実行する必要があります。
Windows Vista以降のバージョンのWindowsでは、 DLLと実行可能ファイルの再配置はカーネルメモリマネージャによって行われ、再配置されたバイナリは複数のプロセス間で共有されます。イメージは常に優先ベースアドレスから再配置され、アドレス空間レイアウトランダム化(ASLR)が実現されます。[ 12 ]
Windows Vistaより前のバージョンでは、実行時におけるイメージの再配置を避けるため、システムDLLをリンク時に競合しない固定アドレスに事前にリンクしておく必要がありました。これらの古いバージョンのWindowsでは、実行時におけるイメージの再配置は各プロセスのコンテキスト内でDLLローダーによって実行され、その結果として再配置されたイメージの部分はプロセス間で共有できなくなります。
WindowsにおけるDLLの扱いは、その起源となったOS/2の手順とは異なります。OS/2では、位置独立ではないDLLをメモリ内の専用の「共有領域」にロードし、ロード後にマッピングするという、3つ目の方法が採用されています。DLLを使用するすべてのユーザーは、同じメモリ上のコピーを使用できます。
Multicsでは、各プロシージャは概念的に[ d ]コードセグメントとリンケージセグメントを持ちます。[ 13 ] [ 14 ]コードセグメントにはコードのみが含まれ、リンケージセクションは新しいリンケージセグメントのテンプレートとして機能します。ポインタレジスタ 4 (PR4) はプロシージャのリンケージセグメントを指します。プロシージャの呼び出しでは、呼び出し先のリンケージセグメントへのポインタをロードする前に、PR4 をスタックに保存します。プロシージャ呼び出しでは、フラグ付きの間接ポインタペア[ 15 ]を使用して最初の呼び出しでトラップを発生させ、動的リンケージメカニズムが新しいプロシージャとそのリンケージセグメントを既知セグメントテーブル (KST) に追加し、新しいリンケージセグメントを構築し、呼び出し元のリンケージセクションにセグメント番号を配置し、間接ポインタペアのフラグをリセットできるようにします。
IBM S/360 タイムシェアリングシステム (TSS/360 および TSS/370) では、各プロシージャーは読み取り専用のパブリック CSECT と書き込み可能なプライベート プロトタイプセクション (PSECT) を持つことができます。呼び出し元はルーチンの V 定数を汎用レジスタ 15 (GR15) にロードし、ルーチンの PSECT の R 定数を GR13 が指す保存領域の 19 番目のワードにコピーします。[ 16 ]
ダイナミックローダー[ 17 ]は、最初のページフォルトが発生するまでプログラムページをロードしたり、アドレス定数を解決したりしません。
位置独立実行可能ファイル(PIE) は、位置独立コードのみで構成された実行可能バイナリです。一部のシステムでは PIC 実行可能ファイルのみを実行しますが、他にも使用される理由があります。セキュリティ重視のLinuxディストリビューションでは、 PaXまたはExec Shield がアドレス空間レイアウトランダム化(ASLR)を使用して、バイナリ内の実行可能コードのオフセットを知ることに依存するエクスプロイト(例えばreturn-to-libc 攻撃など)を使用したセキュリティ攻撃中に、攻撃者が既存の実行可能コードの位置を知ることができないようにするために、 PIE バイナリが使用されます。 (2005 年 2.6.12 以降の公式 Linux カーネルには、PIE でも動作する弱い ASLR があります。これは、ランダム性が ELF ファイル単位全体に適用されるため弱いものです。) [ 18 ]
AppleのmacOSとiOSは、それぞれバージョン10.7と4.3以降、PIE実行ファイルを完全にサポートしています。非PIEのiOS実行ファイルがAppleのApp Storeに承認申請されると警告が表示されますが、今のところ必須要件はなく、非PIEアプリケーションが拒否されることはありません。[ 19 ] [ 20 ]
OpenBSD は、2013 年 5 月 1 日にリリースされた OpenBSD 5.3 以降、ほとんどのアーキテクチャで PIE をデフォルトで有効にしています。[ 21 ]およびディレクトリの実行可能ファイルなどの静的にリンクされたバイナリ での PIE のサポートは、2014 年末近くに追加されました。 [ 22 ] openSUSE は 2015–02 で PIE をデフォルトとして追加しました。Fedora 23 以降、 Fedoraのメンテナーは、パッケージを PIE をデフォルトで有効にしてビルドすることを決定しました。[ 23 ] Ubuntu 17.10は、すべてのアーキテクチャで PIE をデフォルトで有効にしています。[ 24 ] Gentooの新しいプロファイルは、現在、デフォルトで PIE をサポートしています。[ 25 ] 2017 年 7 月頃、Debian はPIE をデフォルトで有効にしました。[ 26 ]/bin/sbin
AndroidはJelly BeanでPIEのサポートを有効にし[ 27 ] 、 Lollipopで非PIEリンカーのサポートを削除した[ 28 ]。
[…]絶対コード、および絶対オブジェクト モジュールは、LOC86 によって処理され、メモリ内の特定の場所でのみ実行されるコードです。ローダーは、絶対オブジェクト モジュールを、モジュールが占有する必要のある特定の場所にのみロードします。位置独立コード(一般に PIC と呼ばれます) は、任意のメモリ 位置にロードできるという点で絶対コードとは異なります。絶対コードに対する PIC の利点は、PIC では特定のメモリ ブロックを予約する必要がないことです。ローダーが PIC をロードすると、呼び出しタスクのジョブのプールからiRMX 86メモリ セグメントを取得し、PIC をセグメントにロードします。 PIC に関する制約は、PL/M-86 COMPACT モデルのセグメンテーションと同様に、コード セグメントとデータ セグメントをそれぞれ 1 つずつしか持つことができず、これらのセグメントのベース アドレス、ひいてはセグメント自体が動的に変化することはないという点です。これは、PIC プログラムの長さが 64K バイト未満になることを意味します。PIC コードは、LINK86 の BIND コントロールを使用して生成できます。ロード時位置指定可能コード(一般に LTL コードと呼ばれる) は、オブジェクト コードの 3 番目の形式です。LTL コードはメモリ内のどこにでもロードできるという点で PIC と似ています。ただし、LTL コードをロードすると、ローダはポインタのベース部分を変更して、ポインタがマイクロプロセッサのレジスタの初期内容に依存しないようにします。この修正 (ベース アドレスの調整) により、LTL コードは、複数のコード セグメントまたは複数のデータ セグメントを持つタスクで使用できます。これは、LTL プログラムの長さが 64K バイトを超える可能性があることを意味します。FORTRAN 86とPascal 86は、短いプログラムであっても自動的にLTLコードを生成します。LTLコードはLINK86のBIND制御によって生成できます。[…]
{{cite book}}: CS1 maint: 非推奨のアーカイブサービス (リンク)コード: [リンク削除済み]訂正:動的実行可能ファイル内のコードは通常位置依存であり、メモリ内の固定アドレスに結び付けられています。
[…] PIC非対応の直接アドレッシングは、PICアドレッシングよりも常に安価(つまり高速)です。 […]