Assembly Primer For Hackers (Part 2) Virtual Memory Organizationによると、Linux プログラム.text
セクションは から始まり0x0804800
、スタック トップは から始まり0xbffffff
ます。これらの数字の意味は何ですか? .text
で開始しないのはなぜですか0x0000000
(または0x0000020
、0x0000040
次の 32 ビットまたは 64 ビットを過ぎてからNULL
)? でスタックの一番上を開始しないのはなぜ0xfffffff
ですか?
2 に答える
これを言うことから始めましょう: ほとんどの場合、さまざまなセクションを特定の場所に配置する必要はありません。さらに重要なのはレイアウトです。現在、スタックトップは実際にはランダム化されています。こちらを参照してください。
0x08048000 は、ld がPT_LOAD
Linux/x86 で最初のセグメントを開始するデフォルトのアドレスです。Linux/amd64 では、デフォルトは 0x400000 で、カスタム リンカー スクリプトを使用してデフォルトを変更できます。-Wl,-Ttext,0xNNNNNNNN
.text セクションがフラグで始まる場所を gcc に変更することもできます。.text がアドレス 0 にマップされない理由を理解するために、NULL ポインターは通常、便宜上 ((void *) 0) にマップされることに注意してください。したがって、NULL ポインタの使用をトラップするために、ゼロ ページがアクセス不能にマップされると便利です。.text の開始前のメモリは、実際には多くのものによって使用されます。cat /proc/self/maps
例として取り上げます:
$ cat /proc/self/maps
001c0000-00317000 r-xp 00000000 08:01 245836 /lib/libc-2.12.1.so
00317000-00318000 ---p 00157000 08:01 245836 /lib/libc-2.12.1.so
00318000-0031a000 r--p 00157000 08:01 245836 /lib/libc-2.12.1.so
0031a000-0031b000 rw-p 00159000 08:01 245836 /lib/libc-2.12.1.so
0031b000-0031e000 rw-p 00000000 00:00 0
00376000-00377000 r-xp 00000000 00:00 0 [vdso]
00852000-0086e000 r-xp 00000000 08:01 245783 /lib/ld-2.12.1.so
0086e000-0086f000 r--p 0001b000 08:01 245783 /lib/ld-2.12.1.so
0086f000-00870000 rw-p 0001c000 08:01 245783 /lib/ld-2.12.1.so
08048000-08051000 r-xp 00000000 08:01 2244617 /bin/cat
08051000-08052000 r--p 00008000 08:01 2244617 /bin/cat
08052000-08053000 rw-p 00009000 08:01 2244617 /bin/cat
09ab5000-09ad6000 rw-p 00000000 00:00 0 [heap]
b7502000-b7702000 r--p 00000000 08:01 4456455 /usr/lib/locale/locale-archive
b7702000-b7703000 rw-p 00000000 00:00 0
b771b000-b771c000 r--p 002a1000 08:01 4456455 /usr/lib/locale/locale-archive
b771c000-b771e000 rw-p 00000000 00:00 0
bfbd9000-bfbfa000 rw-p 00000000 00:00 0 [stack]
ここにあるのは、C ライブラリ、ダイナミック ローダー ld.so、およびカーネル VDSO (カーネルへのいくつかのインターフェイスを提供するカーネル マップ ダイナミック コード ライブラリ) です。ヒープの開始もランダム化されることに注意してください。
あまり意味はありません。
通常、スタックは下方 (下位アドレス) に成長するため、上位アドレスに配置し、下位アドレスに向かって拡張する余地を確保することは、ある程度合理的です (必須ではありません)。
プログラム セクションにアドレス 0 を使用しないことに関しては、ここにいくつかのロジックがあります。まず、多くのソフトウェアは 0 を使用しますNULL
。これは、C および C++ では正当な無効なポインターであり、逆参照してはなりません。多くのソフトウェアにはバグがあり、適切なポインター検証なしで実際にアドレス 0 のメモリーを読み書きしようとします。プログラムがアドレス 0 周辺のメモリ領域にアクセスできないようにすると、これらのバグの一部を見つけることができます (プログラムがクラッシュするか、デバッガで停止します)。また、NULL
は正当な無効なポインターであるため、そのアドレスにはデータやコードがあってはなりません (存在する場合、それへのポインターと を区別することはできませんNULL
)。
x86 プラットフォームでは、通常、アドレス 0 周辺のメモリは、仮想アドレスから物理アドレスへの変換によってアクセスできなくなります。ページ テーブルは、仮想アドレス 0 のエントリが物理メモリのページによってバックアップされないように設定されます。ページは通常、ほんの一握りのバイトではなく、サイズが 4 KB です。そのため、アドレス 0 を取り出すと、アドレス 1 から 4095 も取り出すことになります。アドレス 0 で 4 KB を超えるアドレス空間を取り出すことも合理的です。その理由は、C および C++ の構造体へのポインターです。あなたが持つことができますNULL
構造体へのポインターであり、それを逆参照すると、ポインターに含まれるアドレス (0) に、アクセスしようとしている構造体メンバーと構造体の先頭との間の距離 (最初のメンバーの場合は 0) を加えた位置でメモリ アクセスが試行されます。 、残りは 0 より大きい)。
プログラム用に特定のアドレス範囲を選択する際には、他にも考慮すべき点がいくつかあるかもしれませんが、すべてを説明することはできません。OS は、プログラム自体にいくつかのプログラム関連のもの (データ構造) を保持したい場合があるため、アドレス空間のアクセス可能な部分の端の 1 つに近い固定位置を使用しないのはなぜですか?