GNU/Linux >> Linux の 問題 >  >> Linux

Linux/gnu リンカーがアドレス 0x400000 を選択したのはなぜですか?

通常、開始アドレスはリンカー スクリプトによって設定されます。

たとえば、GNU/Linux では、/usr/lib/ldscripts/elf_x86_64.x を調べます。

...
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); \
    . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;

0x400000 SEGMENT_START() のデフォルト値です このプラットフォームで機能します。

リンカー マニュアルを参照すると、リンカー スクリプトの詳細を確認できます。

% info ld Scripts

ld のデフォルトのリンカー スクリプトには 0x400000 があります 非 PIE 実行可能ファイルに組み込まれている値。

PIE (Position Independent Executables) にはデフォルトのベース アドレスがありません。 カーネルとともに、常にカーネルによって再配置されます。 デフォルトは 0x0000555... です ASLR がこのプロセスまたはシステム全体で無効にされていない限り、プラスの ASLR オフセット。 ld これを制御することはできません。最新のシステムでは、GCC が -fPIE -pie を使用するように構成されていることに注意してください。 デフォルトでは -pie を渡します ld まで 、および C を位置に依存しない asm に変換します。そのようにリンクする場合、手書きの asm は同じ規則に従う必要があります。

しかし、0x400000 の原因は何ですか (4 MiB) 良いデフォルト?

mmap_min_addr より上でなければなりません =65536 =デフォルトで 64K。

そして、0 から十分に離れていると、.text を読み取るオフセットで NULL 参照解除を防ぐ十分な余地が与えられます。 または .data /.bss メモリ (array[i] どこで array 無効です)。 mmap_min_addr を増やさなくても (これにより、実行可能ファイルを壊さずに余裕ができます)、通常は mmap 上位アドレスをランダムに選択するため、実際には、NULL deref に対して少なくとも 4MiB の保護があります。

2M アラインが良い

これにより、ページ テーブルの次のレベルのページ ディレクトリの先頭に配置され、同じ数の 4K ページ テーブル エントリが少数の 2M ページ ディレクトリ エントリに分割され、カーネル ページ テーブル メモリが節約され、ページが支援されます。 - ハードウェア キャッシュをより適切にウォークします。大きな静的配列の場合、次のレベルアップの 1G サブツリーの開始点に近いことも適切です。

IDK なぜ 2MiB ではなく 4MiB なのか、または開発者の理由は何だったのか。 4MiB は、PAE を使用しない 32 ビットのラージページ サイズです (4 バイトの PTE では、レベルごとに 9 ビットではなく 10 ビット)、CPU は x86-64 ページ テーブルを使用して 64 ビット モードにする必要があります。

低い開始アドレスにより、約 2 GiB の静的配列が可能になります

(より大きなコード モデルを使用せずに、少なくとも大きな配列を場合によっては非効率的な方法で処理する必要がある場合。コード モデルの詳細については、x86-64 System V ABI ドキュメントのセクション 3.5.1 アーキテクチャの制約を参照してください。)

非 PIE 実行可能ファイル (「小さい」) のデフォルトのコード モデルでは、プログラムは静的アドレスが仮想アドレス空間の下位 2GiB にあると想定できます。したがって、.text 内の任意の絶対アドレス /.rodata.data.bss より効率的なマシンコードで 32 ビット符号拡張即値として使用できます。

(これは、PIE や共有ライブラリには当てはまりません。x86-64 Linux で許可されなくなった 32 ビット絶対アドレスを参照してください。 あなた/コンパイラが結果としてx86-64 asmで実行できないこと、特に addss xmm0, [foo + rdi*4] 代わりに、RIP 相対 LEA を使用してアレイの開始アドレスをレジスタに取得する必要があります。 x86-64 の唯一の RIP 相対アドレッシング モードは [RIP+rel32] であり、汎用レジスタはありません。)

実行可能ファイルのセクション/セグメントを仮想アドレス空間の下部近くから開始すると、テキスト + データ + bss に使用できる 2GiB のほぼ全体がそれほど大きくなります。 (より高いデフォルト値を設定し、大きな実行可能ファイルが収まるように ld がより低いアドレスを選択するようにすることも可能だったかもしれませんが、それはより複雑なリンカ スクリプトになります。)

これには、実行可能ファイルを作成しない .bss 内のゼロで初期化された配列が含まれます メモリ内のプロセス イメージだけです。実際には、Fortran プログラマーは C や C++ よりもこれに遭遇する傾向があります。これは、そこでは静的配列が一般的であるためです。例:gfortran for dummies:mcmodel=medium は正確には何をしますか? デフォルトの small でのビルド エラーの適切な説明があります。 モデル、およびその結果の medium の x86-64 asm の違い (特定のサイズのしきい値を超えるオブジェクトは、コードの下位 2G または +-2G 内にあるとは見なされません。ただし、コードとより小さい静的データは依然として存在するため、速度のペナルティはわずかです。)

例:static float arr[1UL<<28]; 1 GiB 配列です。 3 つ持っていたとしても、すべてが起動することはありません。 低 2 GiB 内 (手書きの asm に必要なのはこれだけかもしれません) であり、各要素にアクセスできることは言うまでもありません。

gcc -fno-pie float *p = &arr[size-1]; をコンパイルできることを期待しています mov $arr+1073741820, %edi へ 、5 バイトの mov $imm32 . RIP相対は、ターゲットアドレスがアドレスを生成するコードから2GiB以上離れている場合(またはmovss arr+1073741820(%rip), %xmm0でロードする場合)も機能しません; RIP 相対は、ランタイム変数インデックスがない場合でも、非 PIE でも静的データをロード/保存する通常の方法です。) そのため、小さな PIC モデルには、テキスト + データ + bss (さらにセグメント間のギャップ):すべての静的データとコードは、それに到達する可能性のある他のデータから 2GiB 以内である必要があります。

コードがランタイム変数インデックスを介して上位の要素またはそのアドレスにのみアクセスする場合、各配列の開始点であるシンボル自体のみが下位 2 GiB にある必要があります。リンカが bss の末尾を 2GiB 未満にすることを強制するかどうかを忘れました。リンカー スクリプトが、一部の CRT スタートアップ コードが参照する可能性のあるシンボルをそこに配置するためです。

脚注 1 :2GiB より小さいコード モデルに役立つ小さいサイズはありません。 x86-64 マシン コードは、即値とアドレッシング モードに 8 ビットまたは 32 ビットを使用します。 8 ビット (256 バイト) は小さすぎて使用できず、call rel32 のような多くの重要な命令 、 mov r32, imm32 、および [rip+rel32] いずれにしても、1 バイト定数ではなく 4 バイト定数でのみ使用できます。

低 2 GiB (4 ではなく) に制限することは、mov edi, OFFSET arr のように、アドレスを安全にゼロ拡張できることを意味します。 、または mov eax, [arr + rdi*4] のように符号拡張 . [reg + disp32] の使用例は住所だけではないことに注意してください アドレッシング モード; [rbp - 256] x86-64 マシンコードが disp8 と disp32 をゼロ拡張ではなく 64 ビットに符号拡張するのは良いことです。

mov のように、32 ビット レジスタを書き込むと、64 ビットへの暗黙的なゼロ拡張が発生します。 -アドレスをレジスタに入れる即時。ここで、32 ビットのオペランド サイズは、64 ビットのオペランド サイズよりも小さいマシン コード命令です。関数またはラベルのアドレスをレジスターにロードする方法を参照してください (RIP 相対 LEA もカバーしています)。

32 ビット Windows 関連

Raymond Chen は、同じ 0x400000 の理由について記事を書きました。 ベース アドレスは 32 ビット Windows のデフォルトです。 .

彼は、DLL はデフォルトで高いアドレスにロードされ、低いアドレスはそれとはかけ離れていると述べています。 x86-64 SysV 共有オブジェクトは、アドレス空間のギャップが十分に大きい場所ならどこでもロードできます。カーネルはデフォルトで、ユーザー空間の仮想アドレス空間の上部、つまり正規範囲の上部に近くなります。しかし、ELF 共有オブジェクトは完全に再配置可能である必要があるため、どこでも問題なく動作します。

32 ビット Windows の 4MiB の選択も、低い 64K (NULL deref) を回避し、従来の 32 ビット ページ テーブルのページ ディレクトリの開始を選択することによって動機付けられました。 (「ラージページ」のサイズは 4M であり、x86-64 または PAE の場合は 2M ではありません。) Win95 および Win3.1 のレガシーメモリマップの理由がいくつかあるため、少なくとも 1MiB または 4MiB が部分的に必要だった理由や、CPU の周りの作業などがあります。


Linux
  1. Linux –デフォルトルートの送信元アドレスを取得するためのポータブルな方法?

  2. デフォルトの住所

  3. LinuxでデフォルトゲートウェイIPを見つける方法

  1. Python:Linux でローカル インターフェイス/IP アドレスのデフォルト ゲートウェイを取得する

  2. GNU/Linux SUS v3+ に準拠していないのはなぜですか?

  3. BCRYPT - Linux ディストリビューションがデフォルトで使用しないのはなぜですか?

  1. Linuxがエッジコンピューティングにとって重要である理由

  2. xtermを使い続ける理由

  3. Nullglobがデフォルトではないのはなぜですか?