2016-08-09 15 views
3

Linuxのプログラムのメモリレイアウトについていくつか質問があります。私は様々な情報源から知っています(私は「プログラミングを地上から」を読んでいます)、各セクションはそれ自身のメモリ領域にロードされています。テキストセクションは仮想アドレス0x8048000で最初にロードされ、データセクションはその直後にロードされ、次にbssセクションが続き、ヒープとスタックが続きます。Linuxのプログラムのメモリレイアウトについて

レイアウトを試してみるために、私はこのプログラムを組み立てました。まず、いくつかのラベルのアドレスを出力し、システムブレークポイントを計算します。その後、無限ループに入ります。ループはポインタをインクリメントし、そのアドレスのメモリにアクセスしようとします。ある時点でセグメント化エラーがプログラムを終了します(これは意図的に行いました)。

これはプログラムです:

.section .data 

start_data: 
str_mem_access: 
.ascii "Accessing address: 0x%x\n\0" 
str_data_start: 
.ascii "Data section start at: 0x%x\n\0" 
str_data_end: 
.ascii "Data section ends at: 0x%x\n\0" 
str_bss_start: 
.ascii "bss section starts at: 0x%x\n\0" 
str_bss_end: 
.ascii "bss section ends at: 0x%x\n\0" 
str_text_start: 
.ascii "text section starts at: 0x%x\n\0" 
str_text_end: 
.ascii "text section ends at: 0x%x\n\0" 
str_break: 
.ascii "break at: 0x%x\n\0" 
end_data: 

.section .bss 

start_bss: 
.lcomm buffer, 500 
.lcomm buffer2, 250 
end_bss: 

.section .text 
start_text: 

.globl _start 
_start: 

# print address of start_text label 
pushl $start_text 
pushl $str_text_start 
call printf 
addl $8, %esp 
# print address of end_text label 
pushl $end_text 
pushl $str_text_end 
call printf 
addl $8, %esp 
# print address of start_data label 
pushl $start_data 
pushl $str_data_start 
call printf 
addl $8, %esp 
# print address of end_data label 
pushl $end_data 
pushl $str_data_end 
call printf 
addl $8, %esp 
# print address of start_bss label 
pushl $start_bss 
pushl $str_bss_start 
call printf 
addl $8, %esp 
# print address of end_bss label 
pushl $end_bss 
pushl $str_bss_end 
call printf 
addl $8, %esp 
# get last usable virtual memory address 
movl $45, %eax 
movl $0, %ebx 
int $0x80 

incl %eax # system break address 
# print system break 
pushl %eax 
pushl $str_break 
call printf 
addl $4, %esp 

movl $start_text, %ebx 

loop: 
# print address 
pushl %ebx 
pushl $str_mem_access 
call printf 
addl $8, %esp 

# access address 
# segmentation fault here 
movb (%ebx), %dl 

incl %ebx 

jmp loop 

end_loop: 
movl $1, %eax 
movl $0, %ebx 
int $0x80 

end_text: 

そして、この出力の関連する部分(これはDebianの32ビットです):

text section starts at: 0x8048190 
text section ends at: 0x804823b 
Data section start at: 0x80492ec 
Data section ends at: 0x80493c0 
bss section starts at: 0x80493c0 
bss section ends at: 0x80493c0 
break at: 0x83b4001 
Accessing address: 0x8048190 
Accessing address: 0x8048191 
Accessing address: 0x8048192 
[...] 
Accessing address: 0x8049fff 
Accessing address: 0x804a000 
Violación de segmento 

私の質問は以下のとおりです。

1)なぜされます私のプログラムは0x8048000の代わりに0x8048190番地から始まっていますか?これで私は "_start"ラベルの命令が最初にロードされるものではないと推測します。したがって、アドレス0x8048000と0x8048190の間に何がありますか?

2)テキストセクションの終わりとデータセクションの開始の間にギャップがあるのはなぜですか?

3)bssの開始アドレスと終了アドレスは同じです。私は2つのバッファが別の場所に格納されていると仮定しますが、これは正しいですか?

4)システムブレークポイントが0x83b4001にある場合、なぜセグメンテーションフォルトが0x804a000にあるのですか?

+2

[読んだことがないなら、それを見てください](http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html) - ほぼ完全にオフトピックです。素晴らしい読書。 –

+0

ELFローダーは、実行可能ファイルの*セグメント*のみを扱うことに注意してください。 '.text'セクション(リンク後)がテキストセグメント内の唯一のものであるように、多くの場合1:1のマッピングがあります。リンカは '.rodata'のようなセクションを' .text'に結合します。また、「ヒープ」は実際には存在するものではなく、概念のものです(mmap(MAP_ANONYMOUS)による割り当ては 'brk'と連続していません)。ヒープの一部としてBSSと静的データを考慮するかどうかはわかりません。また、LinuxがBSSの直後に最初の 'brk'を置くかどうかはわかりません。 –

答えて

2

これは、gcc -m32 -nostartfiles segment-bounds.Sなどでビルドしていると仮定しているので、32ビットの動的バイナリがあります。 (実際に32ビットシステムを使用している場合は-m32は必要ありませんが、これをテストしたい人はほとんどが64ビットシステムです)

私の64ビットUbuntu 15.10システムは、あなたのプログラムからはいくつかのことがありますが、全体的な振る舞いは同じです。なぜ私のプログラムは、アドレス0x8048190の代わりに、0x8048000から始まるれる)


1を(別のカーネル、または単にASLRは、このことを説明しています。BRKアドレスは0x9354001または0x82a8001のような値で、例えば、乱暴に異なりますか)?

スタティックバイナリを作成する場合、_startは0x8048000になります。

readelf -a a.outから、0x8048190が.textセクションの先頭であることがわかります。しかし、ページにマップされているテキストセグメントの先頭にはありません。 (ページは4096Bであり、Linuxはファイルの位置の4096Bの境界に位置合わせする必要があるので、このように配置されたファイルではexecveはページの先頭に_startをマップすることはできません。列はファイル内の位置です。)

.textセクションの前のテキストセグメントの他のセクションは、ダイナミックリンカーに必要な読み取り専用データであるため、同じページのメモリにマップするのが理にかなっています。

## part of readelf -a output 
Section Headers: 
    [Nr] Name    Type   Addr  Off Size ES Flg Lk Inf Al 
    [ 0]     NULL   00000000 000000 000000 00  0 0 0 
    [ 1] .interp   PROGBITS  08048114 000114 000013 00 A 0 0 1 
    [ 2] .note.gnu.build-i NOTE   08048128 000128 000024 00 A 0 0 4 
    [ 3] .gnu.hash   GNU_HASH  0804814c 00014c 000018 04 A 4 0 4 
    [ 4] .dynsym   DYNSYM   08048164 000164 000020 10 A 5 1 4 
    [ 5] .dynstr   STRTAB   08048184 000184 00001c 00 A 0 0 1 
    [ 6] .gnu.version  VERSYM   080481a0 0001a0 000004 02 A 4 0 2 
    [ 7] .gnu.version_r VERNEED   080481a4 0001a4 000020 00 A 5 1 4 
    [ 8] .rel.plt   REL    080481c4 0001c4 000008 08 AI 4 9 4 
    [ 9] .plt    PROGBITS  080481d0 0001d0 000020 04 AX 0 0 16 
    [10] .text    PROGBITS  080481f0 0001f0 0000ad 00 AX 0 0 1   ########## The .text section 
    [11] .eh_frame   PROGBITS  080482a0 0002a0 000000 00 A 0 0 4 
    [12] .dynamic   DYNAMIC   08049f60 000f60 0000a0 08 WA 5 0 4 
    [13] .got.plt   PROGBITS  0804a000 001000 000010 04 WA 0 0 4 
    [14] .data    PROGBITS  0804a010 001010 0000d4 00 WA 0 0 1 
    [15] .bss    NOBITS   0804a0e8 0010e4 0002f4 00 WA 0 0 8 
    [16] .shstrtab   STRTAB   00000000 0010e4 0000a2 00  0 0 1 
    [17] .symtab   SYMTAB   00000000 001188 0002b0 10  18 38 4 
    [18] .strtab   STRTAB   00000000 001438 00
Key to Flags: 
    W (write), A (alloc), X (execute), M (merge), S (strings) 
    I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) 
    O (extra OS processing required) o (OS specific), p (processor specific) 

2)なぜテキストセクションとデータセクションの開始の端部との間に隙間がありますか?

どうしてですか?実行可能ファイルの別のセグメントになければならないため、異なるページにマップされます。 (テキストは読み取り専用で実行可能で、MAP_SHAREDでもかまいません)データは読み書き可能でMAP_PRIVATEでなければなりません。Linuxでは、デフォルトではデータの実行も可能です)

ギャップを空けて実行可能ファイルのテキストの隣で共有ライブラリのテキストセグメントをマッピングするための動的リンカー。また、データセクションへの範囲外の配列インデックスは、セグメンテーションの可能性が高いことを意味します。 (以前の、よりノイズの多い障害は常にデバッグが容易です)。


3)bssの開始アドレスと終了アドレスは同じです。私は2つのバッファが別の場所に格納されていると仮定しますが、これは正しいですか?

これは興味深いことです。彼らはbssに入っていますが、現在の位置が.lcommラベルの影響を受けない理由をIDKに教えてください。おそらく、.commの代わりに.lcommを使用したため、リンクする前に別のサブセクションに入っている可能性があります。私はスペースを確保するために使用.skipまたは.zeroを使用している場合、私はあなたが期待どおりの結果を得る:

.section .bss 
start_bss: 
#.lcomm buffer, 500 
#.lcomm buffer2, 250 
buffer: .skip 500 
buffer2: .skip 250 
end_bss: 

.lcommは、あなたがそのセクションに切り替えていない場合でも、BSSで物事を置きます。つまり、現在のセクションが何であるかを気にせず、.bssセクションの現在の位置が何であるか気にしないか、または影響を与えない可能性があります。 TL:DR:.bssに手動で切り替えるときは、または.skipで、.commまたは.lcommではなく使用してください。


4)システムのブレークポイントが0x83b4001であれば、私は0x804a000でセグメンテーションフォールト以前の取得なぜ?

これは、テキストセグメントとbrkの間にマップされていないページがあることを示しています。 (あなたのループはebx = $start_textで始まるので、テキストセグメントの後の最初のマップされていないページでエラーになります)。テキストとデータの間の仮想アドレス空間の穴に加えて、おそらくデータセグメント以外の穴があります。

メモリ保護にはページ粒度(4096B)があるため、最初のアドレスは常にページの最初のバイトになります。

+0

Debian 3.5 i386仮想マシン内で 'as break -o break.o && ld -dynamic-linker /lib/ld-linux.so.2 -o break break.o -lc'を使ってビルドしていますUbuntu 15.10 64bit)です。 –

+0

@ saga.x:はい、これは 'gcc -m32 -nostartfiles'と同じです。どうして32ビットのVMを気にするのですか?ちょうど 'gcc -m32'、' as'と 'ld' [正しいargsで](http://stackoverflow.com/questions/36861903/assembling-32-bit-binaries-on-a-64bit- system-gnu-toolchain/36901649#36901649)をあなたのUbuntuシステムにインストールしました。 64ビットカーネルで32ビットコードを実行するのは完璧です。Ubuntuのマルチバイトパッケージには、必要なすべての32ビットライブラリが含まれています。 –

+0

私は 'gcc-multilib'パッケージをインストールし、' gcc -m32 -nostartfiles'をビルドしました。これは動作します。私はASLRについて何かも検索しました。もし 'sysctl -w kernel.randomize_va_space = 0'でブレークポイントアドレスが決して変更されない場合は、0x804a001に固定されています。私は、Linuxの仕組みやメモリー管理について、このトピックをよりよく理解するために、もっと興味深いことを読むべきですが、これは初めてのことです。あなたの答えをありがとう! –

関連する問題