はじめに

もう少し複雑な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 細かいセクションに関するフラグ。例えば.textExecute(実行可能)フラグが立っているなど。
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セクションヘッダのoffsetlenの指定のみに従えば入っている)が、 Type=NOBITSなので、実際はELFファイル内に対応するデータはない。

BSSセクションは、未定義変数のために確保するべきメモリサイズを表す。 なのでこの8byteは、Cコードのzerotwoの変数用に確保するべきメモリの大きさを表したもの。

大事なのはlen0x08だけで、このサイズのデータ領域をメモリに確保するようOSに指示しているだけ (値は0埋めなので、具体的なデータがなくてよい)。

オフセットの0x54は、直前までのオフセット+サイズの値が0x54だからというだけで、次の.rodataセクションが実際は0x54から始まるデータに対応している。

0x08は、ちょっと分かりづらいが実験したところ、char1byteint4byteの合計。

ただし、charは通常1byteだが、上のreadelfの出力でいうところのAl=4なので、4byte区切りで保持されるため、4byte+4byte8byteになっている。

.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ファイルから共有オブジェクトを作ってみたいと思う。