VOOZH about

URL: https://qiita.com/knknkn1162/items/c67ae7c2ef71a713adf8

⇱ assemblyからhello world programを追いかける #初心者 - Qiita


👁 Image
18

Go to list of users who liked

10

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 5 years have passed since last update.

@knknkn1162(Kenta Nakajima)

assemblyからhello world programを追いかける

18
Last updated at Posted at 2018-06-14

はじめに

機械語読みます。(Linux OSのsourceまでは追いません。ローダについては次回のTODO)

何がわかるのか?

  • 機械語(Executable and Linkable Format: ELF)の読み方
  • アドレスの割り当て方
  • linkerの挙動

リンカ、ローダ実践開発テクニックという本をhello worldで理解してみました的な内容です。1

本記事で書いてないこと

  • ローダの詳細
  • macの実行形式について(linuxのと違う)

対象者

環境

Dockerfile
FROM ubuntu:18.04

RUN apt-get update -y && apt-get install bsdmainutils binutils gdb nasm -y
WORKDIR /usr/src

で、Dockerfileのあるディレクトリに対して、

# When using docker itself
$ docker build -t nasm_study . # t .. tagged
# `--privileged` option avoids `Operation not permitted` error
$ docker run -it --privileged --rm -v $(pwd):/usr/src nasm_study # --rm : remote automatically when exit container.

とする。2

$ uname -a
Linux b7f1908d2a63 4.9.87-linuxkit-aufs #1 SMP Wed Mar 14 15:12:16 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

参考文献

準備

アセンブルとは

  • アセンブリ命令を機械語に置き換え、単一のアセンブリファイル(.asm)をオブジェクトファイル(.o)に変換する
  • このファイルは、先頭部分にヘッダ情報を持ち、ある特定のフォーマットになっている

リンカの役割

  1. 複数のオブジェクトファイルをセクション単位にまとめる
  2. 1で作成された複数のセクションをセグメント単位にまとめる
  3. まとめたセグメントに実際のアドレスを割り当てる
  4. この段階で各シンボルが配置されるアドレスが決定する(relocate: 再配置)
  5. シンボルのアドレスが未定のために未解決であった部分に、実際のアドレスが挿入される(名前(シンボル)解決)
  6. 実行形式として出力する

用語解説

  • ダンプファイル ..下のような感じのやつ
$ hexdump -vC hello.o # v.. verbose, C .. hex+ASCII 表示
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 |..>.............|
00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 |........@.......|
00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |....@.....@.....|
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
  • ELF .. Executable and Linkable Format. オブジェクトファイルのフォーマット。nasm -hfでフォーマット一覧を確認できる。

  • Relocatable object file .. assembleした直後のオブジェクトファイル。 assembleした直後は、関数呼び出しや変数のアドレスを割り当てていない。リンク(ld)により、初めてアドレスが割り当てられる。

    なんでそうなっているのかというと、ファイルが複数にまたがっている場合、単一のファイルのみでは、別ファイルにある関数呼び出しの実体がどこにあるかわからないため、アドレスを割り当てていないからだ。リンクによりld -o ./hello prog1.o prog2.o prog3.oみたいな感じでまとめることにより、外部からよばれている関数や変数のアドレスを決定できる。

  • linker script .. リンカの役割の1~5を実行する際の設定スクリプト.ld --verboseで使用されているlink scriptを見れる。ld -s [linker_script]で自前のリンカスクリプトを読み込める。

  • raw binary file .. ヘッダやフッタ情報などが含まれないデータのみが含まれているファイル(objcopy -O binary -S [inputfile] [outputfile]で作成できる)

その他用語に関しては、Binary Hacksの各章のはじめにいろいろ載っているので、気になる人は参考に。

構造

あんまり詳細に走りすぎてしまうと、森が全く見えなくなるので、雑に追いかけつつ、自分なりにまとめてみる。

今回扱う例

hello.asm
section .data
 hello_world db "Hello world!", 10
 ; See also https://www.tutorialspoint.com/assembly_programming/assembly_strings.htm
 hello_world_len equ $ - hello_world ; $(points to the byte after the last character of the string variable msg. ) - hello_world

section .text
 global _start

_start:
 mov rax, 1 ; sys_write
 mov rdi, 1
 mov rsi, hello_world
 mov rdx, hello_world_len
 syscall

 mov rax, 60 ; sys_exit
 mov rdi, 0
 syscall

x86_64 assemblyについては、 https://www.youtube.com/watch?v=VQAKkuLL31g のシリーズ が一番とっつきやすかった。 その次に、Low-Level Programming の前半(後半はC言語が中心)読みましょう。
システムコールの一覧は、https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tblhttp://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/ を参照に。

Note) 今回C言語でやっていないのは、main関数がよばれる前後でいくつか前処理、後処理関数が挟まっているから。リンカ、ローダ実践開発テクニック 4章の図4.50(p114)に表がある。また、下記の一見なんにもしなさそうなC fileでさえも、gcc int_main.c -vとすることにより、/usr/lib/gcc/x86_64-linux-gnu/7/collect2 -plugin /usr/lib/gcc/x86_64-linu...みたいに、いろんなライブラリが読み込まれていることがわかる。(objdump -d [file]でlink前のobject fileとlink後のexecutable object fileの命令数を確認した3

int_main.c
int main(void) {
 return 0;
}

大枠の構造4

バイナリダンプ自体を見るのなら、hexdump -vC ./hello.oもしくは、hexdump -vC ./helloとしましょう(v: verbose, C: 標準的な 16進数 + ASCII での表示。)

hello.o
+-------------------------------------------------------+ 0x00000000
|ELF header (check with `readelf -h hello.o`) |
+-------------------------------------------------------+ 0x00000040
|7 Section headers Index 0(SHT_NULL) header |
|(check with `readelf -S hello.o`) .data section header |
| .text section header |
| .shstrtab section header |
| .symtab section header |
| .strtab section header |
| .rela.text section header |
+-------------------------------------------------------+ 0x00000200
|6 Section (.data, .text, .shstrtab, |
| .symtab, .strtab, .rela.text) |
|(Each section can be checked |
| with `readelf -x .data helo.o) |
| |
| |
| |
+-------------------------------------------------------+ 0x00000380
# Note) There are no program headers in this file. Try `readelf -l hello.o`
hello
+-------------------------------------------------------+ 0x00000000
|ELF header (`readelf -h hello.o`) |
+-------------------------------------------------------+ 0x00000040
|2 program headers(.text, .data) |
|(`readelf -l ./hello`) |
+-------------------------------------------------------+ 0x000000b0
|.text section |
|.data section, .shstrtab section |
+-------------------------------------------------------+ 0x00000110
|6 Section headers Index 0(SHT_NULL) header 
| .data section header |
| .text section header |
| .shstrtab section header |
| .symtab section header |
| .strtab section header |
+-------------------------------------------------------+ 0x00000280
|.symtab section & .strtab section |
| |
| |
| |
| |
| |
| |
+-------------------------------------------------------+ 0x000003d0
# .rela.text sectionがないが、リンカが.textセクションの文字列のアドレスを再配置(rellocation)したので、テーブル不要になり消滅している。後述。

object file

hello.o ./hello
生成コマンド nasm -felf64 hello.asm ld -o hello hello.o
object file type Relocatable Executable
ELF Header 必要 必要
Program Header table 存在しない(Segmentがないため) 必要
Section Header table 必要 存在するが実行には不要
Section 必要 使われていないSectionなら不要
Symbol table 必要 存在するが再配置(relocation)し終わったので不要
Relocation table(rela.* section) 必要 シンボル解決し終わったので存在しない
  • SegmentとSectionの違いは、Sectionはリンク時に使用されて、Segmentは実行時に使用される。複数のSectionがリンク時にSegmentにまとめられる(linker scriptにどうまとめるかの記載がある)。Segmentが異なれば、異なる仮想メモリにマッピングされる。5 リンク前のファイルには、Segmentはない。

Note) 実行形式ファイルのほうがfile size大きくってぎょっとするんだけれど、実際に使われているのは、ELF header,Program Header, .data, .text section である。Section headerはリンク時に使用されるので、実行時には使われない。具体的には

dump
# ELF header
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 |@...............|
00000030 00 00 00 00 40 00 38 00 02 00 40 00 04 00 03 00 |....@.8...@.....|
# 2 program headers(.text, .data)
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................|
00000070 00 00 20 00 00 00 00 00 01 00 00 00 06 00 00 00 |.. .............|
00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....|
00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............|
000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....|
# 2 Section(.text, .data)
000000b0 b8 01 00 00 00 bf 01 00 00 00 48 be d8 00 60 00 |..........H...`.|
000000c0 00 00 00 00 ba 0d 00 00 00 0f 05 b8 3c 00 00 00 |............<...|
000000d0 bf 00 00 00 00 0f 05 00 48 65 6c 6c 6f 20 77 6f |........Hello wo|
000000e0 72 6c 64 21 0a 00 |rld!..|

で実行可能。(#はコメントのつもり)6

さらに詳細の構造に関しては、 https://gist.github.com/knknkn1162/58325c37aa9f6554ca73c931012b0f1dhttps://gist.github.com/knknkn1162/ea1ab38081ceadc0d0c3d71db9418ca2 に自分なりにまとめてみた。(もしくは、http://www.cirosantilli.com/elf-hello-world/ を丹念に読む。)

コマンド確認

コマンド 備考
dump確認 hexdump -vC [file] or xxd [file]
ELF Header readelf -h [file]
Program Header readelf -l [file] or objdump -p [file](briefly) Section to Segment mappingが記載されてる
Section Header readelf -S [file] or objdump -h [file](briefly)
Section readelf -x.data [file] .dataは個別のSection名を指す。(他には、.text, .bss, .symtabとか)
disassemble objdump -d [file] .text section(実行命令列)がどんなふうになっているのか確認できる
raw binary file objcopy -O binary -S hello.o hello_raw.o
relocation table readelf -r [file] .rel.* sectionをパースしてる
Symbol table readelf -s [file] or nm [file] .symtab sectionをパースしてる
linker script ld --verbose
link map ld -M -o hello hello.o

いろいろ本やサイトを見ていると、readelfやらobjdumpやらnmやらバイナリ解析コマンドが入り組んでいるが、大体の場合は、readelfが上位互換。(disassembleしたいときだけobjdump使えばよい)

hello worldの確認

$ ./hello
Hello world!

文字列は

$ readelf -x.data ./hello

Hex dump of section '.data':
 0x006000d8 48656c6c 6f20776f 726c6421 0a Hello world!.

で、命令の中で、hello_world文字列の先頭アドレスに当たる0x006000d8が参照されている。

$ readelf -x.text hello

Hex dump of section '.text':
 0x004000b0 b8010000 00bf0100 000048be d8006000 ..........H...`.
 0x004000c0 00000000 ba0d0000 000f05b8 3c000000 ............<...
 0x004000d0 bf000000 000f05 .......

# 上記ではよくわからないと思うので、disassembleする
$ objdump -d ./hello

hello: file format elf64-x86-64


Disassembly of section .text:

00000000004000b0 <_start>:
 4000b0:	b8 01 00 00 00 	mov $0x1,%eax
 4000b5:	bf 01 00 00 00 	mov $0x1,%edi
 4000ba:	48 be d8 00 60 00 00 	movabs $0x6000d8,%rsi # mov rsi, hello_world
 4000c1:	00 00 00
 4000c4:	ba 0d 00 00 00 	mov $0xd,%edx
 4000c9:	0f 05 	syscall
 4000cb:	b8 3c 00 00 00 	mov $0x3c,%eax
 4000d0:	bf 00 00 00 00 	mov $0x0,%edi
 4000d5:	0f 05 	syscall

リンクの役割確認

リンカの役割

  1. 複数のオブジェクトファイルをセクション単位にまとめる
  2. 1で作成された複数のセクションをセグメント単位にまとめる
  3. まとめたセグメントに実際のアドレスを割り当て、各シンボルが配置されるアドレスが決定する(relocate: 再配置)
  4. シンボルのアドレスが未定のために未解決であった部分に、実際のアドレスが挿入される(名前(シンボル)解決)
    (5. 実行形式として出力する)

だった。一つづつ確認していく。


複数のオブジェクトファイルをセクション単位にまとめる

今回、単一のオブジェクトファイルをリンクするだけなので、まとめるもなにも無い7には、が、セクション自体はreadelf -S ./hello(各Section headerをパースしたもの)で確認すると良い:

$ readelf -S hello.o
There are 7 section headers, starting at offset 0x40:

Section Headers:
 [Nr] Name Type Address Offset
 Size EntSize Flags Link Info Align
 [ 0] NULL 0000000000000000 00000000
 0000000000000000 0000000000000000 0 0 0
 [ 1] .data PROGBITS 0000000000000000 00000200
 000000000000000d 0000000000000000 WA 0 0 4
 [ 2] .text PROGBITS 0000000000000000 00000210
 0000000000000027 0000000000000000 AX 0 0 16
 [ 3] .shstrtab STRTAB 0000000000000000 00000240
 0000000000000032 0000000000000000 0 0 1
 [ 4] .symtab SYMTAB 0000000000000000 00000280
 00000000000000a8 0000000000000018 5 6 8
 [ 5] .strtab STRTAB 0000000000000000 00000330
 000000000000002e 0000000000000000 0 0 1
 [ 6] .rela.text RELA 0000000000000000 00000360
 0000000000000018 0000000000000018 4 2 8

$ readelf -S hello
There are 6 section headers, starting at offset 0x240:

Section Headers:
 [Nr] Name Type Address Offset
 Size EntSize Flags Link Info Align
 [ 0] NULL 0000000000000000 00000000
 0000000000000000 0000000000000000 0 0 0
 [ 1] .text PROGBITS 00000000004000b0 000000b0
 0000000000000027 0000000000000000 AX 0 0 16
 [ 2] .data PROGBITS 00000000006000d8 000000d8
 000000000000000d 0000000000000000 WA 0 0 4
 [ 3] .symtab SYMTAB 0000000000000000 000000e8
 00000000000000f0 0000000000000018 4 6 8
 [ 4] .strtab STRTAB 0000000000000000 000001d8
 000000000000003f 0000000000000000 0 0 1
 [ 5] .shstrtab STRTAB 0000000000000000 00000217
 0000000000000027 0000000000000000 0 0 1

Offsetは起点から0x000000b0のところに、.text sectionがあるよ〜っていうのを表す。最初のセクションはNULL固定。

バイトコードから直接読み取るのは、http://www.cirosantilli.com/elf-hello-world/#section-header-table を見よう。
.rela.textがないが、これは、linkされたことによって、役目を終えた(アドレスを再配置し終わった)ため存在しない。
(ちなみに、section Nameは'.shstrtab section'から引っ張ってきている(配置規則については、http://refspecs.linux-foundation.org/elf/gabi4+/ch4.strtab.html を参照のこと)


複数のセクションをセグメント単位にまとめる

readelf -l ./hello(Program headerをパースしてる) で確認できる8

$ readelf -l hello

Elf file type is EXEC (Executable file)
Entry point 0x4000b0
There are 2 program headers, starting at offset 64

Program Headers:
 Type Offset VirtAddr PhysAddr
 FileSiz MemSiz Flags Align
 LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
 0x00000000000000d7 0x00000000000000d7 R E 0x200000
 LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
 0x000000000000000d 0x000000000000000d RW 0x200000

 Section to Segment mapping:
 Segment Sections...
 00 .text
 01 .data
 Section to Segment mapping:
 Segment Sections...
 00 .text
 01 .data

はこのプログラムでは単に1:1対応しているように見えるが、もうちょっと複雑な場合だと、下のようにSegment:Sectionが1:多対応となる9

 Section to Segment mapping:
 Segment Sections...
 00
 01 .interp
 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
 03 .init_array .fini_array .dynamic .got .data .bss
 04 .dynamic
 05 .note.ABI-tag .note.gnu.build-id
 06 .eh_frame_hdr
 07
 08 .init_array .fini_array .dynamic .got

まとめたセグメントに実際のアドレスを割り当て、各シンボルが配置されるアドレスが決定する(relocate: 再配置)

readelf -s hello.oを見る(.symtab section をパースするコマンド)
リンク前のファイルは下のようにアドレスが定まっていない。

$ readelf -s hello.o

Symbol table '.symtab' contains 7 entries:
 Num: Value Size Type Bind Vis Ndx Name
 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.asm
 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2
 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world
 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
 6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 2 _start

リンクされると、アドレスが決定される。

$ readelf -s hello

Symbol table '.symtab' contains 10 entries:
 Num: Value Size Type Bind Vis Ndx Name
 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND # 最初のentryはnullに相当する
 1: 00000000004000b0 0 SECTION LOCAL DEFAULT 1
 2: 00000000006000d8 0 SECTION LOCAL DEFAULT 2
 3: 0000000000000000 0 FILE LOCAL DEFAULT ABS hello.asm #ファイル名なので、アドレス割り当てられない
 4: 00000000006000d8 0 NOTYPE LOCAL DEFAULT 2 hello_world
 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len
 6: 00000000004000b0 0 NOTYPE GLOBAL DEFAULT 1 _start
 7: 00000000006000e5 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
 8: 00000000006000e5 0 NOTYPE GLOBAL DEFAULT 2 _edata
 9: 00000000006000e8 0 NOTYPE GLOBAL DEFAULT 2 _end

Ndxは(readelf -S helloから得られる)Section header Indexを表す(本記事の場合、1なら.text section, 2なら、.data section, UND=UNDEF, ABS=an absolute value that will not change because of relocation.)
(ちなみに、Nameは'.strtab section'から引っ張ってきている(配置規則については、http://refspecs.linux-foundation.org/elf/gabi4+/ch4.strtab.html を参照のこと)

Note) _edata, __bss_start, _endとリンク前のファイルにはないsymbol nameが追加されているが、コレはlinker scriptによって定義されている。意味はそれぞれ、データ領域終端アドレス、BSS領域の先頭アドレス, BSS領域の終端アドレスである。(今回BSS領域内が、alignmentの関係で00000000006000e500000000006000e8で値が違っている)詳しくは、linker scriptの章も参照すること。


シンボルのアドレスが未定のために未解決であった部分に、実際のアドレスが挿入される(名前解決)10

まず、リンク前のオブジェクトファイルをdisassembleしよう11

# dump relocatable object file
$ objdump -d hello.o

hello.o: file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_start>:
 0:	b8 01 00 00 00 	mov $0x1,%eax
 5:	bf 01 00 00 00 	mov $0x1,%edi
 a:	48 be 00 00 00 00 00 	movabs $0x0,%rsi # mov rsi, hello_world
 11:	00 00 00
 14:	ba 0d 00 00 00 	mov $0xd,%edx
 19:	0f 05 	syscall
 1b:	b8 3c 00 00 00 	mov $0x3c,%eax
 20:	bf 00 00 00 00 	mov $0x0,%edi
 25:	0f 05 	syscall

このバイトコードの詳細は補足の記事( https://qiita.com/knknkn1162/items/bea6d06d6b6009a9773d )で確認してほしいんだけど、いちばん重要なのが、0x0c~0x13まで(00 00 00 00 00 00 00 00の部分)(はアドレス(mov rsi, hello_worldのhello_worldの参照先)が入るはずだが、未決定のままになっている。
リンクされると、アドレスが決定される。(前節の4: 00000000006000d8 0 NOTYPE LOCAL DEFAULT 2 hello_worldの部分)

# リンク後のobject file(executable object file)
$ objdump -d ./hello

hello: file format elf64-x86-64


Disassembly of section .text:

00000000004000b0 <_start>:
 4000b0:	b8 01 00 00 00 	mov $0x1,%eax
 4000b5:	bf 01 00 00 00 	mov $0x1,%edi
 4000ba:	48 be d8 00 60 00 00 	movabs $0x6000d8,%rsi # mov rsi, hello_world
 4000c1:	00 00 00
 4000c4:	ba 0d 00 00 00 	mov $0xd,%edx
 4000c9:	0f 05 	syscall
 4000cb:	b8 3c 00 00 00 	mov $0x3c,%eax
 4000d0:	bf 00 00 00 00 	mov $0x0,%edi
 4000d5:	0f 05 	syscall

$0x6000d8となっているのが確認できる。(d8 00 60 00 00 00 00 00はintel x86_64はリトルエンディアン12なので、0x00000000006000d8であることに注意。)

置き換えるべきアドレスは分かっているが、置き換える位置(0x4000bc)をどうやって知っているのだろうか?
リンク前のrela.text section がその情報を持っている。
readelf -r hello.o(rela.text sectionをパースしてる) で確認できる13

$ readelf -r hello.o

Relocation section '.rela.text' at offset 0x360 contains 1 entry:
 Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0

どう見るかというと、.data(Sym.Name)の0x00000000000c(Offset)番目なので、下のvvの部分を指す。

$ objdump -d ./hello
// skip
 vv
 a:	48 be 00 00 00 00 00 	movabs $0x0,%rsi # mov rsi, hello_world
 11:	00 00 00

linker script

とりあえず、前節までで、readelf, objdumpを用いて、大体のELF(Executable and Linkable Format)の構造を見てきた。
リンクされる時に、アドレスが決定されることまでわかった。(まとめたセグメントに実際のアドレスを割り当て、各シンボルが配置されるアドレスが決定する(relocate: 再配置)の節を参照)
では、アドレスの番号(0x06000d8とか0x04000b0)ってどう決まっているの? というのが気になる。(もちろん、ランダムに決まっているわけではない。)
アドレスを決定するのに大事な役目を果たすのが、linker scriptというやつである。

ld --verboseでどんな感じか確認できる -> https://gist.github.com/knknkn1162/66ddd485e8fda0ad0f7b595d4a48e5c0

で、この期に及んでlinker scriptの文法規則をいろいろ説明するのは大変なので、link mapという、アドレスとscriptのマッピングで確認したい。リンクの際にMオプションをつけて、ld -M -o hello hello.oでexecutable object fileを作成すると、それが出力される。
-> 長いので、全体は https://gist.github.com/knknkn1162/aea089a66e879876ba6ead0697b55a28 で確認するとよい

このファイルに、アドレスに関する情報が詰まっているので、関係する部分のみ読み解こう!


まず、

sample.ld
# PROVIDE: 他のobject fileのシンボル名と衝突したとき、リンカよりもobject fileのシンボルを優先させるために使う。
LOAD hello.o
 [!provide] PROVIDE (__executable_start = SEGMENT_START ("text-segment", 0x400000))
 0x00000000004000b0 . = (SEGMENT_START ("text-segment", 0x400000) + SIZEOF_HEADERS)

から見る14。なぜ、0x400000なのかは、https://stackoverflow.com/questions/14314021/why-linux-gnu-linker-chose-address-0x400000 とか https://teratail.com/questions/48366 を見れば良いと思う。

text-segmentは初期値が、0x400000と決められている。カスタマイズしたければ、ld -Ttext-segment [ADDRESS]とすれば良い。SIZEOF_HEADERSはELFヘッダとProgram Headerの合計値。今回の場合、

$ hexdump -vC hello
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| #<= ELF Header
00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 |..>.......@.....|
00000020 40 00 00 00 00 00 00 00 40 02 00 00 00 00 00 00 |@.......@.......|
00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 05 00 |....@.8...@.....|
00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................| #<= 2 Program Headers
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 d7 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 |................|
00000070 00 00 20 00 00 00 00 00 01 00 00 00 06 00 00 00 |.. .............|
00000080 d8 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 |..........`.....|
00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 |..`.............|
000000a0 0d 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 |.......... .....|
...

の部分に当たる。(http://www.cirosantilli.com/elf-hello-world/#program-header-table も見ると良さそう)

.textセッション自体は、https://gist.github.com/knknkn1162/aea089a66e879876ba6ead0697b55a28#file-ld_m-txt-L79-L87 の部分:

# 0x27 はサイズ
.text 0x00000000004000b0 0x27
 *(.text.unlikely .text.*_unlikely .text.unlikely.*)
 *(.text.exit .text.exit.*)
 *(.text.startup .text.startup.*)
 *(.text.hot .text.hot.*)
 *(.text .stub .text.* .gnu.linkonce.t.*)
 .text 0x00000000004000b0 0x27 hello.o
 0x00000000004000b0 _start
 *(.gnu.warning)

linker script自体は、

script.ld
SECTIONS
{
 /* Read-only sections, merged into text segment: */
 PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
// skip
 .text :
 {
 *(.text.unlikely .text.*_unlikely .text.unlikely.*)
 *(.text.exit .text.exit.*)
 *(.text.startup .text.startup.*)
 *(.text.hot .text.hot.*)
 *(.text .stub .text.* .gnu.linkonce.t.*)
 /* .gnu.warning sections are handled specially by elf32.em. */
 *(.gnu.warning)
 }

みたいになっている。

.data sectionも見よう。

 // . : location counter( current address)
 0x00000000006000d7 . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE))
// skip
.data 0x00000000006000d8 0xd
 *(.data .data.* .gnu.linkonce.d.*)
 .data 0x00000000006000d8 0xd hello.o

MAXPAGESIZE, COMMONPAGESIZEは、https://github.com/bminor/binutils-gdb/blob/92e68c1d65f844c0027f471a0c9e1722d781ef7d/bfd/elf64-x86-64.c#L4991-L4994 に定義があり、DATA_SEGMENT_ALIGNの定義15から、は0x4000d7(=0x4000b0+0x27(.text sectionのサイズ))+0x200000=0x6000d7となる。16

$ readelf -s hello
## skip
 Num: Value Size Type Bind Vis Ndx Name
## skip
 1: 00000000004000b0 0 SECTION LOCAL DEFAULT 1 # .text section
 2: 00000000006000d8 0 SECTION LOCAL DEFAULT 2 # .data section

ということで、それぞれのsectionの先頭アドレスがわかった。

 7: 00000000006000e5 0 NOTYPE GLOBAL DEFAULT 2 __bss_start
 8: 00000000006000e5 0 NOTYPE GLOBAL DEFAULT 2 _edata
 9: 00000000006000e8 0 NOTYPE GLOBAL DEFAULT 2 _end

とかもlink mapを参考にしてください。

最後に

実行ファイルがどうやってできているのかをhello world使って追ってみた。この後、実行ファイルを起動するためには、ローダが必要である:

ローダの役割

  1. 実行形式を読み込み、そのフォーマットにしたがってメモリ配置する
  2. BSS領域の0クリアを行う
  3. レジスタやスタック、引数の設定を行う
  4. 実行開始アドレス(Entry point)にジャンプする(ld --verboseで調べると、ENTRY(_start)となっているので、_start関数にジャンプ)

「リンカ、ローダ実践開発テクニック」の8章に基本的な仕組みとソースコードがある。
ここらあたりも解説したかったが、linux kernelのコード追いながらの説明が良さそう & 自分が完全に把握できてない ため、機が熟したら書こうと思う。

TODO

  1. http://www.cirosantilli.com/elf-hello-world/ がすごかったので、自分なりに理解した内容を忘れないうちにまとめておきたかった、というのが本音。

  2. macOS X はsystem call (https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master) がlinuxのやつ https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl と違ったり、http://thexploit.com/secdev/mac-os-x-64-bit-assembly-system-calls/ のように、システムコールを0×2000000 + unix syscallで呼ぶ必要があったり、object fileがmacho64ELF(Executable and Linkable file format)と形式違ったりするので、扱わない。

  3. https://gist.github.com/knknkn1162/a183b323ff11f70eef9f424665dea3cf においた

  4. 図は手動でまとめました。

  5. https://stackoverflow.com/questions/14361248/whats-the-difference-of-section-and-segment-in-elf-file-format とか

  6. vimでの編集は、https://qiita.com/urakarin/items/337a0433a41443731ad0 を見れば良い

  7. 複数のオブジェクトファイルをリンクするには、ld -o hello hello.o lib1.o lib2.oみたいにすると良い。

  8. readelf -l hello.oとすると、There are no program headers in this file.と出力される。

  9. ちなみにこの対応表はProgram headerには書いていなく、Sectionに割り振られたアドレスからreadelf自身が計算しているみたい。https://stackoverflow.com/questions/23018496/where-is-the-section-to-segment-mapping-stored-in-elf-files も参照。

  10. 名前解決もひっくるめてrelocation(再配置)するという言い方もしているみたい。

  11. objdump -dでは標準でAT&T記法になっているので、intel記法に直したければ、-M intel-mnemonicを追加する

  12. little endianかどうかは、readelf -H hello.o2's complement, little endianで確認できる。

  13. readelf -r helloの場合は、There are no relocations in this file.の表示のみ。

  14. https://gist.github.com/knknkn1162/aea089a66e879876ba6ead0697b55a28#file-ld_m-txt-L9-L11

  15. https://sourceware.org/binutils/docs/ld/Builtin-Functions.html

  16. ちなみに、MAXPAGESIZE、COMMONPAGESIZEはld -z common-page-size=[MAX_SIZE] -z max-page-size=[COMMON_SIZE]で変えられる。ld -helpを参照

18

Go to list of users who liked

10
0

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18

Go to list of users who liked

10