ELF 実行エントリ ポイントの仮想アドレスが 0x0 ではなく 0x80xxxxx 形式なのはなぜですか? 質問する

ELF 実行エントリ ポイントの仮想アドレスが 0x0 ではなく 0x80xxxxx 形式なのはなぜですか? 質問する

実行されると、プログラムは仮想アドレス 0x80482c0 から実行を開始します。このアドレスは、プロシージャを指すのではなく、リンカーによって作成された というmain()名前のプロシージャを指します。_start

これまでの Google での調査では、次のような (漠然とした) 歴史的推測がいくつか見つかりました。

カリフォルニア州サンタクルーズのグループによって広められた *NIX の i386 への移植版では、0x08048000 がかつて STACK_TOP であった (つまり、スタックは 0x08048000 付近から 0 に向かって下向きに成長した) という伝説があります。当時は 128 MB の RAM が高価で、4 GB の RAM は考えられませんでした。

誰かこれを確認/否定できますか?

ベストアンサー1

Mads が指摘したように、ほとんどのアクセスをヌル ポインタ経由でキャッチするために、Unix 系システムではアドレス 0 のページを「マップされていない」状態にする傾向があります。したがって、アクセスは直ちに CPU 例外、つまりセグメント違反を引き起こします。これは、アプリケーションが暴走するのを許すよりはるかに優れています。ただし、例外ベクター テーブルは、少なくとも x86 プロセッサでは、任意のアドレスに配置できます (そのための特別なレジスタがあり、オペコードがロードされますlidt)。

開始点アドレスは、メモリのレイアウト方法を記述する一連の規則の一部です。リンカーは、実行可能バイナリを生成するときにこれらの規則を認識している必要があるため、規則が変更される可能性は低くなります。基本的に、Linux の場合、メモリ レイアウト規則は 90 年代初期の Linux の最初のバージョンから継承されています。プロセスは、いくつかの領域にアクセスできる必要があります。

  • コードは開始点を含む範囲内になければなりません。
  • スタックがあるはずです。
  • ヒープが存在する必要があり、その制限は およびbrk()システムsbrk()コールによって増加されます。
  • mmap()共有ライブラリの読み込みを含むシステムコールのための余裕が必要です。

現在、ヒープは、カーネルが適切と判断するアドレスでメモリのチャンクを取得する呼び出しmalloc()によってサポートされていますmmap()。しかし、昔の Linux は以前の Unix 系システムと似ており、ヒープは 1 つの途切れないチャンク内に大きな領域を必要とし、アドレスが増加するにつれて大きくなる可能性がありました。そのため、慣例が何であれ、コードとスタックを低いアドレスに詰め込み、特定のポイント以降のアドレス空間のすべてのチャンクをヒープに割り当てなければなりませんでした。

しかし、スタックもあります。これは通常は非常に小さいですが、場合によっては非常に劇的に大きくなることがあります。スタックは小さくなり、スタックがいっぱいになると、一部のデータを上書きするのではなく、プロセスが予測どおりにクラッシュすることを本当に望んでいます。そのため、スタック用の広い領域が必要であり、その領域の下端にはマップされていないページがありました。そしてなんと、アドレス 0 にマップされていないページがあり、ヌル ポインターの参照をキャッチします。したがって、スタックは最初のページを除く最初の 128 MB のアドレス空間を取得するように定義されました。つまり、コードは 0x080xxxxx のようなアドレスで、その 128 MB をたどる必要がありました。

Michael が指摘しているように、実際に使用できる範囲に比べてアドレス空間は非常に広かったため、128 MB のアドレス空間を「失う」ことは大した問題ではありませんでした。当時、Linux カーネルは単一プロセスのアドレス空間を 1 GB に制限していましたが、これはハードウェアで許可されている最大 4 GB を超えていたため、大きな問題とは考えられていませんでした。

おすすめ記事