はじめに
もう少し複雑なCファイルを題材にELFファイル(今回もオブジェクトファイル)を読み解いていく。
題材(特に理由はないが、zero.c)
char first_word[] = "あいうえお";
char *second_word = "banana";
char zero;
char one = '1';
int two;
コマンド
gcc -c zero.c -o zero.o
xxd ./zero.o
結果
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0100 3e00 0100 0000 0000 0000 0000 0000 ..>.............
00000020: 0000 0000 0000 0000 3802 0000 0000 0000 ........8.......
00000030: 0000 0000 4000 0000 0000 4000 0d00 0c00 ....@.....@.....
00000040: e381 82e3 8184 e381 86e3 8188 e381 8a00 ................
00000050: 3100 0000 6261 6e61 6e61 0000 0000 0000 1...banana......
00000060: 0000 0000 0000 0000 0047 4343 3a20 2855 .........GCC: (U
00000070: 6275 6e74 7520 3133 2e33 2e30 2d36 7562 buntu 13.3.0-6ub
00000080: 756e 7475 327e 3234 2e30 3429 2031 332e untu2~24.04) 13.
00000090: 332e 3000 0000 0000 0400 0000 1000 0000 3.0.............
000000a0: 0500 0000 474e 5500 0200 00c0 0400 0000 ....GNU.........
000000b0: 0300 0000 0000 0000 0000 0000 0000 0000 ................
000000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000d0: 0100 0000 0400 f1ff 0000 0000 0000 0000 ................
000000e0: 0000 0000 0000 0000 0000 0000 0300 0400 ................
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000100: 0800 0000 1100 0200 0000 0000 0000 0000 ................
00000110: 1000 0000 0000 0000 1300 0000 1100 0500 ................
00000120: 0000 0000 0000 0000 0800 0000 0000 0000 ................
00000130: 1f00 0000 1100 0300 0000 0000 0000 0000 ................
00000140: 0100 0000 0000 0000 2400 0000 1100 0200 ........$.......
00000150: 1000 0000 0000 0000 0100 0000 0000 0000 ................
00000160: 2800 0000 1100 0300 0400 0000 0000 0000 (...............
00000170: 0400 0000 0000 0000 007a 6572 6f2e 6300 .........zero.c.
00000180: 6669 7273 745f 776f 7264 0073 6563 6f6e first_word.secon
00000190: 645f 776f 7264 007a 6572 6f00 6f6e 6500 d_word.zero.one.
000001a0: 7477 6f00 0000 0000 0000 0000 0000 0000 two.............
000001b0: 0100 0000 0200 0000 0000 0000 0000 0000 ................
000001c0: 002e 7379 6d74 6162 002e 7374 7274 6162 ..symtab..strtab
000001d0: 002e 7368 7374 7274 6162 002e 7465 7874 ..shstrtab..text
000001e0: 002e 6461 7461 002e 6273 7300 2e72 6f64 ..data..bss..rod
000001f0: 6174 6100 2e72 656c 612e 6461 7461 2e72 ata..rela.data.r
00000200: 656c 2e6c 6f63 616c 002e 636f 6d6d 656e el.local..commen
00000210: 7400 2e6e 6f74 652e 474e 552d 7374 6163 t..note.GNU-stac
00000220: 6b00 2e6e 6f74 652e 676e 752e 7072 6f70 k..note.gnu.prop
00000230: 6572 7479 0000 0000 0000 0000 0000 0000 erty............
00000240: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000260: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000270: 0000 0000 0000 0000 1b00 0000 0100 0000 ................
00000280: 0600 0000 0000 0000 0000 0000 0000 0000 ................
00000290: 4000 0000 0000 0000 0000 0000 0000 0000 @...............
000002a0: 0000 0000 0000 0000 0100 0000 0000 0000 ................
000002b0: 0000 0000 0000 0000 2100 0000 0100 0000 ........!.......
000002c0: 0300 0000 0000 0000 0000 0000 0000 0000 ................
000002d0: 4000 0000 0000 0000 1100 0000 0000 0000 @...............
000002e0: 0000 0000 0000 0000 1000 0000 0000 0000 ................
000002f0: 0000 0000 0000 0000 2700 0000 0800 0000 ........'.......
00000300: 0300 0000 0000 0000 0000 0000 0000 0000 ................
00000310: 5400 0000 0000 0000 0800 0000 0000 0000 T...............
00000320: 0000 0000 0000 0000 0400 0000 0000 0000 ................
00000330: 0000 0000 0000 0000 2c00 0000 0100 0000 ........,.......
00000340: 0200 0000 0000 0000 0000 0000 0000 0000 ................
00000350: 5400 0000 0000 0000 0700 0000 0000 0000 T...............
00000360: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000370: 0000 0000 0000 0000 3900 0000 0100 0000 ........9.......
00000380: 0300 0000 0000 0000 0000 0000 0000 0000 ................
00000390: 6000 0000 0000 0000 0800 0000 0000 0000 `...............
000003a0: 0000 0000 0000 0000 0800 0000 0000 0000 ................
000003b0: 0000 0000 0000 0000 3400 0000 0400 0000 ........4.......
000003c0: 4000 0000 0000 0000 0000 0000 0000 0000 @...............
000003d0: a801 0000 0000 0000 1800 0000 0000 0000 ................
000003e0: 0a00 0000 0500 0000 0800 0000 0000 0000 ................
000003f0: 1800 0000 0000 0000 4900 0000 0100 0000 ........I.......
00000400: 3000 0000 0000 0000 0000 0000 0000 0000 0...............
00000410: 6800 0000 0000 0000 2c00 0000 0000 0000 h.......,.......
00000420: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000430: 0100 0000 0000 0000 5200 0000 0100 0000 ........R.......
00000440: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000450: 9400 0000 0000 0000 0000 0000 0000 0000 ................
00000460: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000470: 0000 0000 0000 0000 6200 0000 0700 0000 ........b.......
00000480: 0200 0000 0000 0000 0000 0000 0000 0000 ................
00000490: 9800 0000 0000 0000 2000 0000 0000 0000 ........ .......
000004a0: 0000 0000 0000 0000 0800 0000 0000 0000 ................
000004b0: 0000 0000 0000 0000 0100 0000 0200 0000 ................
000004c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000004d0: b800 0000 0000 0000 c000 0000 0000 0000 ................
000004e0: 0b00 0000 0300 0000 0800 0000 0000 0000 ................
000004f0: 1800 0000 0000 0000 0900 0000 0300 0000 ................
00000500: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000510: 7801 0000 0000 0000 2c00 0000 0000 0000 x.......,.......
00000520: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000530: 0000 0000 0000 0000 1100 0000 0300 0000 ................
00000540: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000550: c001 0000 0000 0000 7500 0000 0000 0000 ........u.......
00000560: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000570: 0000 0000 0000 0000 ........
program ヘッダ
例によって今回もオブジェクトファイルのため、特にないので省略。
section ヘッダ
ubuntuの/usr/lib/include/elf.hより引用。
このヘッダ(サイズは64byte)が連続している。
sh_ で始まっているのは、Section Headerのアクロニムと思われる。
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
/* 確かめたところ、やはり64byteであった。 */
sizeof(Elf64_Shdr) == 64
| attribute名 | 説明 |
|---|---|
| sh_name | セクション名。.shstrtabのデータ部における、配列のインデクス。 |
| sh_type | セクションタイプ。NOBITSやSTRTABなど。 |
| sh_flags | 細かいセクションに関するフラグ。例えば.textはExecute(実行可能)フラグが立っているなど。 |
| sh_addr | 実行時にセクションが配置されるはずの仮想アドレス。OSが実行時にこの値を読み取ることはない(読み取るのはプログラムヘッダ)のであくまで希望。 |
| sh_offset | セクションの具体的なデータがこのELFファイルのどこにあるのかを示す2要素の1つのoffset。 |
| sh_size | セクションの具体的なデータがこのELFファイルのどこにあるのかを示す2要素の1つのsize。 |
| sh_link | このセクションの補足情報があるセクションへのインデクス。典型的には、どうやら.symtabセクションからの.strtabへのlinkのようなもの。 |
| sh_info | 付属情報。sh_typeによって異なる意味を持つ。たとえば、今回の例だと.symtabの場合、要素が3つあることが3で表現されている。 |
| sh_addralign | メモリやファイル上に表現される場合、アドレスとして開始位置がこの倍数である制約を表す。2のべき乗で表される。 |
| sh_entsize | サイズが決まった構造体の配列をデータとして持つヘッダの場合、その構造体のsizeが入るらしい。 |
コマンド
readelf --section-headers --wide ./zero.o
結果
There are 13 section headers, starting at offset 0x238:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 0000000000000000 000040 000000 00 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 000040 000011 00 WA 0 0 16
[ 3] .bss NOBITS 0000000000000000 000054 000008 00 WA 0 0 4
[ 4] .rodata PROGBITS 0000000000000000 000054 000007 00 A 0 0 1
[ 5] .data.rel.local PROGBITS 0000000000000000 000060 000008 00 WA 0 0 8
[ 6] .rela.data.rel.local RELA 0000000000000000 0001a8 000018 18 I 10 5 8
[ 7] .comment PROGBITS 0000000000000000 000068 00002c 01 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 0000000000000000 000094 000000 00 0 0 1
[ 9] .note.gnu.property NOTE 0000000000000000 000098 000020 00 A 0 0 8
[10] .symtab SYMTAB 0000000000000000 0000b8 0000c0 18 11 3 8
[11] .strtab STRTAB 0000000000000000 000178 00002c 00 0 0 1
[12] .shstrtab STRTAB 0000000000000000 0001c0 000075 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
.text セクション
関数の定義があればあっただろうが、今回もないのでなし。
.data セクション
コマンド
xxd -seek 0x40 -len 0x11 ./zero.o
結果
00000040: e381 82e3 8184 e381 86e3 8188 e381 8a00 ................
00000050: 31 1
前回と同じく可変データが連続してNULL終端で入っている。
UTF8でちゃんとデータが入っていることが分かる。
| UTF8文字 | UTF8コード |
|---|---|
| あ | e3 81 82 |
| い | e3 81 84 |
| う | e3 81 86 |
| え | e3 81 88 |
| お | e3 81 8a |
| 1 | 31 |
.bss セクション
コマンド
xxd -seek 0x54 -len 0x08 ./zero.o
結果
00000054: 6261 6e61 6e61 0000 banana..
一見、bananaの文字列が入っているように見える(確かに.bssセクションヘッダのoffsetとlenの指定のみに従えば入っている)が、
Type=NOBITSなので、実際はELFファイル内に対応するデータはない。
BSSセクションは、未定義変数のために確保するべきメモリサイズを表す。
なのでこの8byteは、Cコードのzeroとtwoの変数用に確保するべきメモリの大きさを表したもの。
大事なのはlenの0x08だけで、このサイズのデータ領域をメモリに確保するようOSに指示しているだけ
(値は0埋めなので、具体的なデータがなくてよい)。
オフセットの0x54は、直前までのオフセット+サイズの値が0x54だからというだけで、次の.rodataセクションが実際は0x54から始まるデータに対応している。
0x08は、ちょっと分かりづらいが実験したところ、charの1byteとintの4byteの合計。
ただし、charは通常1byteだが、上のreadelfの出力でいうところのAl=4なので、4byte区切りで保持されるため、4byte+4byteの8byteになっている。
.rodataセクション
コマンド
xxd -seek 0x54 -len 0x07 ./zero.o
結果
00000054: 6261 6e61 6e61 00 banana.
banana の文字列が入っている。
C言語における文字列リテラルは、char[]とchar*に格納する場合で仕様が下記のように異なる振る舞いをするらしい。
| 型 | 挙動 |
|---|---|
| char[] | データは可変 |
| char* | データはイミュータブル |
つまり、second_word[0] = 'B'というようなコードは仕様外らしい。
したがって、メモリにおけるデータの配置領域も異なるため、read only dataセクションに置かれるらしい。
今回だと、second_wordの方はポインタ変数に対する格納のため、こちらのデータの"banana"のみが.rodataセクションに格納されている。
.data.rel.local セクション
コマンド
xxd -seek 0x60 -len 0x08 ./zero.o
結果
00000060: 0000 0000 0000 0000 ........
gccはデフォルトで-pieオプションが付けられる。
.data.rel.localは-pieに関連するセクションらしいが、一旦スキップ。
.rela.data.rel.local セクション
リロケーションに関するセクション。こちらも一旦スキップ。
.comment セクション
前回と同様なのでスキップ。
コマンド
xxd -seek 0x68 -len 0x2c ./zero.o
結果
00000068: 0047 4343 3a20 2855 6275 6e74 7520 3133 .GCC: (Ubuntu 13
00000078: 2e33 2e30 2d36 7562 756e 7475 327e 3234 .3.0-6ubuntu2~24
00000088: 2e30 3429 2031 332e 332e 3000 .04) 13.3.0.
.note.GNU-stack セクション
dataなしなのでスキップ。
.note.gnu.property セクション
デバッグ情報。スキップ。
dataなしなのでスキップ。
.symtab セクション
.symtab セクションには、そのオブジェクトファイルが提供するシンボルのプロパティ一覧が格納される。
readelf --symbols <filepath> で確認できる。
なお、readelfコマンドにおいてNameは後述の .strtab セクションと組み合わせて表示していることに注意。
コマンド
readelf --symbols ./zero.o
結果
Symbol table '.symtab' contains 8 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS zero.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 4 .rodata
3: 0000000000000000 16 OBJECT GLOBAL DEFAULT 2 first_word
4: 0000000000000000 8 OBJECT GLOBAL DEFAULT 5 second_word
5: 0000000000000000 1 OBJECT GLOBAL DEFAULT 3 zero
6: 0000000000000010 1 OBJECT GLOBAL DEFAULT 2 one
7: 0000000000000004 4 OBJECT GLOBAL DEFAULT 3 two
0番目は例によって未定義を表すため、00が格納されている。
| プロパティ名 | 意味 |
|---|---|
| st_name | strtab セクション( シンボルの文字列テーブル )のデータ領域開始位置(オフセット)。 |
| st_shndx | このELFファイルのどのセクションに属するシンボルかを表すインデクス(0,1,2..)。 |
下記より、1なら.textセクション、2なら.dataセクション、などのようになっていることがわかる。
section headers一覧を再掲
コマンド
readelf --section-headers ./zero.o
結果
There are 13 section headers, starting at offset 0x230:
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 0000000000000000 00000040
0000000000000000 0000000000000000 AX 0 0 1
[ 2] .data PROGBITS 0000000000000000 00000040
0000000000000011 0000000000000000 WA 0 0 16
[ 3] .bss NOBITS 0000000000000000 00000054
0000000000000008 0000000000000000 WA 0 0 4
[ 4] .rodata PROGBITS 0000000000000000 00000054
0000000000000004 0000000000000000 A 0 0 1
[ 5] .data.rel.local PROGBITS 0000000000000000 00000058
0000000000000008 0000000000000000 WA 0 0 8
[ 6] .rela.data.r[...] RELA 0000000000000000 000001a0
0000000000000018 0000000000000018 I 10 5 8
[ 7] .comment PROGBITS 0000000000000000 00000060
000000000000002c 0000000000000001 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 0000000000000000 0000008c
0000000000000000 0000000000000000 0 0 1
[ 9] .note.gnu.pr[...] NOTE 0000000000000000 00000090
0000000000000020 0000000000000000 A 0 0 8
[10] .symtab SYMTAB 0000000000000000 000000b0
00000000000000c0 0000000000000018 11 3 8
[11] .strtab STRTAB 0000000000000000 00000170
000000000000002c 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 000001b8
0000000000000075 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), l (large), p (processor specific)
strtab セクションのデータは、単にNULLで区切られた連続したデータなので、st_nameオフセットから開始してNULLまでがこのシンボルの文字列となる。
したがって、シンボル文字列にNULLを含めることはできない。
.strtab セクション
文字列テーブル。直前の、.symtabセクション(あるいは.dynsym)における、このオブジェクトファイルが提供するシンボルの名前が格納されている。
前述の通りNULL区切り以外の区切り情報が存在しないので、NULLをシンボルに含めることはできない。
コマンド
xxd -seek 0x178 -len 0x2c ./zero.o
結果
00000178: 007a 6572 6f2e 6300 6669 7273 745f 776f .zero.c.first_wo
00000188: 7264 0073 6563 6f6e 645f 776f 7264 007a rd.second_word.z
00000198: 6572 6f00 6f6e 6500 7477 6f00 ero.one.two.
.shstrtab セクション
section header文字列テーブル。
.strtab はシンボルの名前が格納されているが、.shstrtabセクションには、セクションヘッダの名前が格納されている。
各セクションヘッダのsh_nameプロパティは、この.shstrtabセクションデータにおけるインデクスを表す。
気づいたこと
.strtab セクションは、文字列に対する開始オフセット(byte単位)だったのに、この.shstrtabはインデックス(0,1,2..)なのが不思議。
どうやら、セクションは基本的にインデクスで管理されるため、文字列もインデクスで管理しているらしい。
確かに、セクションはどれも、64byteで固定であった。
おわりに
セクションヘッダの理解が深まった。次回はこのCファイルから共有オブジェクトを作ってみたいと思う。