ELFファイルを作る part7
さて、前回のpart6でズルをする必要があったのは、分解する元のELFファイルがあり、各セクションの中身はその元のファイルのものをそのまま使用していた為である。
それ故、主に動的リンク関係のセクションについてのアドレスを全く同じになるよう調整する必要があった。翻せば、そのあたりを自分の理解の元に再構築出来れば問題はなくなる。
ということで、.dynamicセクションまわりがどうなってるのか調査してみる
目標
動的リンクに関するセクションの構造を把握する
調査
最初に
調査する対象は、part5で使っていたtest.out。
一応セクションの中身を再掲すると、以下の様になっている。
$ readelf -S test.out There are 15 section headers, starting at offset 0x4e8: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .interp PROGBITS 0000000000400190 00000190 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.gnu.build-i NOTE 00000000004001ac 000001ac 0000000000000024 0000000000000000 A 0 0 4 [ 3] .hash HASH 00000000004001d0 000001d0 0000000000000018 0000000000000004 A 4 0 8 [ 4] .dynsym DYNSYM 00000000004001e8 000001e8 0000000000000048 0000000000000018 A 5 1 8 [ 5] .dynstr STRTAB 0000000000400230 00000230 0000000000000024 0000000000000000 A 0 0 1 [ 6] .gnu.version VERSYM 0000000000400254 00000254 0000000000000006 0000000000000002 A 4 0 2 [ 7] .gnu.version_r VERNEED 0000000000400260 00000260 0000000000000020 0000000000000000 A 5 1 8 [ 8] .rela.plt RELA 0000000000400280 00000280 0000000000000030 0000000000000018 A 4 9 8 [ 9] .plt PROGBITS 00000000004002b0 000002b0 0000000000000030 0000000000000010 AX 0 0 16 [10] .text PROGBITS 00000000004002e0 000002e0 0000000000000014 0000000000000000 AX 0 0 16 [11] .eh_frame PROGBITS 00000000004002f8 000002f8 0000000000000000 0000000000000000 A 0 0 8 [12] .dynamic DYNAMIC 00000000006002f8 000002f8 0000000000000140 0000000000000010 WA 5 0 8 [13] .got.plt PROGBITS 0000000000600438 00000438 0000000000000028 0000000000000008 WA 0 0 8 [14] .shstrtab STRTAB 0000000000000000 00000460 0000000000000084 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
各セクションの把握
まずは各セクションがどういう役割の下に作成されているのかをさらってみる。
セクション | 役割 |
---|---|
.interp | インタプリタセクション。実際には主に動的リンカが指定される。このセクションがあると(正しくはこのセクションを元にしたINTERPセグメントがあると)実行時にファイルを直接実行するのではなく、ここで指定される文字列の指すファイルへ引数として渡すことになり、実行させる。イメージとしてはシェルスクリプトのshebangのようなもの。ちなみにここで指定するファイルがld.soとかって感じならa.out形式。ld-linux.soという感じならELF形式の動的リンカ。 |
.note.gnu.build_id | ビルドされたファイルに対するユニークなIDが入っている。また、このセクションはプロセスイメージにロードされ、kernelがサポートしていればcore dumpに含まれることになる。つまり、core dumpから適切なバージョンのバイナリを探せるようになる。 |
.hash | 再配置時に動的リンカが使用するセクション。このhashはハッシュテーブルの意味。これにより.dynsymセクション内のシンボルを、線型検索をせず、迅速に見つけられる様になる。 |
.dynsym | 動的リンク用のシンボルテーブル。動的リンカはこのセクションの中身を元にリンクを行う。 |
.dynstr | 動的リンクに関連するシンボル名などを格納する文字列 |
.gnu.version | .dynsymで定義されているシンボルに対応するバージョンの一覧 |
.gnu.version_r | .gnu.versionが指すバージョン値についての情報が示されているセクション |
.rela.plt | .pltセクション用の再配置テーブル |
.plt | procedure linkage table。遅延リンクのために使われる。関数本体へのジャンプコードの集合。これにより関数呼び出しのシンボル解決を起動時ではなく実行時にすることで、起動時間が短縮される。 |
.text | プログラム本体 |
.eh_frame | 例外をサポートしている言語の場合、情報を保持しておくセクション。形式はDWARFのdebug_frameに似ているらしい。詳細はこちらを見てもらった方が。 |
.dynamic | 動的リンク用の諸々の情報 |
.got.plt | GOT(global offset table)は変数本体へのポインタの配列。.text領域に変数があるとシンボル解決時に.textの変更が必要になってしまうので、GOTにシンボル解決が必要なものを集めさせておく。これにより、.text領域を変更する必要を無くしている。.got.pltということで、.gotの関数版ということか? |
.shstrtbl | セクション名の文字列 |
dynamicセクションの中身
どう考えても動的リンクに関する最たるセクションと思われる、dynamicセクションについて中身を調べてみる。この中はreadelfすると以下の様になっている
$ readelf -d test.out Dynamic section at offset 0x2f8 contains 15 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x0000000000000004 (HASH) 0x4001d0 0x0000000000000005 (STRTAB) 0x400230 0x0000000000000006 (SYMTAB) 0x4001e8 0x000000000000000a (STRSZ) 36 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000015 (DEBUG) 0x0 0x0000000000000003 (PLTGOT) 0x600438 0x0000000000000002 (PLTRELSZ) 48 (bytes) 0x0000000000000014 (PLTREL) RELA 0x0000000000000017 (JMPREL) 0x400280 0x000000006ffffffe (VERNEED) 0x400260 0x000000006fffffff (VERNEEDNUM) 1 0x000000006ffffff0 (VERSYM) 0x400254 0x0000000000000000 (NULL) 0x0
ぱっと見た感じでも、動的リンクに必要な情報を記したセクションのアドレスやサイズ等の情報をまとめたセクションであることが見て取れる。これらの各要素は
typedef struct { Elf64_Sxword d_tag; /* Dynamic entry type */ union { Elf64_Xword d_val; /* Integer value */ Elf64_Addr d_ptr; /* Address value */ } d_un; } Elf64_Dyn;
という構造体から成る(/usr/include/elf.hより)。d_tagでこの要素のタイプを表し、d_un共用体がこの要素の値として整数値かアドレスを保持することとなる。
また、この構造体の記述のすぐ下にd_tagの値が定義されており、それらは以下の通り
/* Legal values for d_tag (dynamic entry type). */ #define DT_NULL 0 /* Marks end of dynamic section */ #define DT_NEEDED 1 /* Name of needed library */ #define DT_PLTRELSZ 2 /* Size in bytes of PLT relocs */ #define DT_PLTGOT 3 /* Processor defined value */ #define DT_HASH 4 /* Address of symbol hash table */ #define DT_STRTAB 5 /* Address of string table */ #define DT_SYMTAB 6 /* Address of symbol table */ #define DT_RELA 7 /* Address of Rela relocs */ #define DT_RELASZ 8 /* Total size of Rela relocs */ #define DT_RELAENT 9 /* Size of one Rela reloc */ #define DT_STRSZ 10 /* Size of string table */ #define DT_SYMENT 11 /* Size of one symbol table entry */ #define DT_INIT 12 /* Address of init function */ #define DT_FINI 13 /* Address of termination function */ #define DT_SONAME 14 /* Name of shared object */ #define DT_RPATH 15 /* Library search path (deprecated) */ #define DT_SYMBOLIC 16 /* Start symbol search here */ #define DT_REL 17 /* Address of Rel relocs */ #define DT_RELSZ 18 /* Total size of Rel relocs */ #define DT_RELENT 19 /* Size of one Rel reloc */ #define DT_PLTREL 20 /* Type of reloc in PLT */ #define DT_DEBUG 21 /* For debugging; unspecified */ #define DT_TEXTREL 22 /* Reloc might modify .text */ #define DT_JMPREL 23 /* Address of PLT relocs */ #define DT_BIND_NOW 24 /* Process relocations of object */ #define DT_INIT_ARRAY 25 /* Array with addresses of init fct */ #define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */ #define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */ #define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */ #define DT_RUNPATH 29 /* Library search path */ #define DT_FLAGS 30 /* Flags for the object being loaded */ #define DT_ENCODING 32 /* Start of encoded range */ #define DT_PREINIT_ARRAY 32 /* Array with addresses of preinit fct*/ #define DT_PREINIT_ARRAYSZ 33 /* size in bytes of DT_PREINIT_ARRAY */ #define DT_NUM 34 /* Number used */ #define DT_LOOS 0x6000000d /* Start of OS-specific */ #define DT_HIOS 0x6ffff000 /* End of OS-specific */ #define DT_LOPROC 0x70000000 /* Start of processor-specific */ #define DT_HIPROC 0x7fffffff /* End of processor-specific */ #define DT_PROCNUM DT_MIPS_NUM /* Most used by any processor */
まだまだあるが、全部載せると長くなるのでとりあえずここまで。
今回test.outから得られたタイプそれぞれについての意味は以下のとおり。
タイプ | 説明 |
---|---|
NEEDED | このファイルで必要とされる動的リンク対象の動的ライブラリ。この要素の値には、動的ライブラリの名前は.dynstrセクションが保持する文字列のどこから始まるかのオフセットを保持している。 |
HASH | .hashセクションのアドレス |
STRTAB | .dynstrセクションのアドレス |
SYMTAB | .dynsymセクションのアドレス |
STRSZ | .dynstrセクションが保持する文字列のサイズ |
SYMENT | .dynsymセクションが持つエントリ1つ分のサイズ |
DEBUG | デバッグ用。今回は未使用? |
PLTGOT | .got.pltセクションのアドレス |
PLTRELSZ | .pltセクションのサイズ |
PLTREL | .pltセクションのタイプ |
JMPREL | .rela.pltセクションのアドレス |
VERNEED | .gnu.version_rセクションのアドレス |
VERNEEDNUM | バージョン情報の数 |
VERSYM | .gnu.versionセクションのアドレス |
NULL | .dynamicセクションが保持する構造体の列はここで終端であることを示すための要素 |
専用セグメントの役割
test.outのプログラムヘッダーの部分を見てみると
readelf -l test.out Elf file type is EXEC (Executable file) Entry point 0x4002e0 There are 6 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x0000000000000150 0x0000000000000150 R E 8 INTERP 0x0000000000000190 0x0000000000400190 0x0000000000400190 0x000000000000001c 0x000000000000001c R 1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000002f8 0x00000000000002f8 R E 200000 LOAD 0x00000000000002f8 0x00000000006002f8 0x00000000006002f8 0x0000000000000168 0x0000000000000168 RW 200000 DYNAMIC 0x00000000000002f8 0x00000000006002f8 0x00000000006002f8 0x0000000000000140 0x0000000000000140 RW 8 NOTE 0x00000000000001ac 0x00000000004001ac 0x00000000004001ac 0x0000000000000024 0x0000000000000024 R 4
となっており、INTERPとDYNAMICは専用のセグメントを持っていることがわかる。実際の実行時、ローダーによって把握されるのはセグメントの単位であるため(セクション単位では見てくれないため)、動的リンクに関わってくるINTERPやDYNAMICセグメントをローダーから扱う為に設けられている。
ローダーが実行形式のファイルをロードした際、INTERPセグメントがあるならば、実行形式ファイルそのものを引数としてインタプリタを起動する。この場合は/lib64/ld-linux-x86-64.so.2がインタプリタである。
動的リンカがDYNAMICセグメントを読みに行くまでの流れは大体こんな感じ。4の段階で、既にtest.outは引数として渡されてる筈なんで、この図のように外へ見に行くのはちょっと違うと思うが。
動的リンク
では実際にtest.out実行時にはどのようにして必要とする共有ライブラリとリンクするのか?
動的リンカがDYNAMICセグメントを見つけるまでは先に示した通り。そして、このセグメント(=.dynamicセクション)の情報を元に動的リンクが行われていくことになる。
その挙動はこんな感じ。
再配置
さて、立ち返って考えてみると、そもそも動的リンクは何故動的にリンクしてるのだったか。そう、プログラム中から外部の共有ライブラリにある処理を呼び出す際に必要だから動的リンクしているんだった。
ということで、textセクションから考えてみる。
まずはtest.outのtextセクション部分を見てみよう。
$ objdump -S test.out test.out: file format elf64-x86-64 Disassembly of section .plt: 00000000004002b0 <putchar@plt-0x10>: 4002b0: ff 35 8a 01 20 00 pushq 0x20018a(%rip) # 600440 <exit@plt+0x200170> 4002b6: ff 25 8c 01 20 00 jmpq *0x20018c(%rip) # 600448 <exit@plt+0x200178> 4002bc: 0f 1f 40 00 nopl 0x0(%rax) 00000000004002c0 <putchar@plt>: 4002c0: ff 25 8a 01 20 00 jmpq *0x20018a(%rip) # 600450 <exit@plt+0x200180> 4002c6: 68 00 00 00 00 pushq $0x0 4002cb: e9 e0 ff ff ff jmpq 4002b0 <putchar@plt-0x10> 00000000004002d0 <exit@plt>: 4002d0: ff 25 82 01 20 00 jmpq *0x200182(%rip) # 600458 <exit@plt+0x200188> 4002d6: 68 01 00 00 00 pushq $0x1 4002db: e9 d0 ff ff ff jmpq 4002b0 <putchar@plt-0x10> Disassembly of section .text: 00000000004002e0 <.text>: 4002e0: bf 54 00 00 00 mov $0x54,%edi 4002e5: e8 d6 ff ff ff callq 4002c0 <putchar@plt> 4002ea: bf 2a 00 00 00 mov $0x2a,%edi 4002ef: e8 dc ff ff ff callq 4002d0 <exit@plt>
見ての通り、textセクションの処理ではputcharとexitをcallしている事がわかる。そして、そのcall先は0x4002c0と0x4002da。上記objdumpの結果にも書いてあるが、セクション一覧の方を確認してもこれらのアドレスは.pltセクションに位置していることがわかる。
先に示したとおり、.pltセクションは関数本体へのジャンプコードの集合である。これもまたobjdumpの結果より、exitは0x600458でputcharは0x600450へジャンプする指定がされていることを読み取れる。そしてこれらのアドレスは、.got.pltセクションに位置している。
ならば次は.got.pltセクションの中身。このセクションを見てみると
$ readelf -x 13 test.out Hex dump of section '.got.plt': 0x00600438 f8026000 00000000 00000000 00000000 ..`............. 0x00600448 00000000 00000000 c6024000 00000000 ..........@..... 0x00600458 d6024000 00000000 ..@.....
となっている。一番左の段落がアドレスで、右四つの段落がデータの16進表現。この表示ではリトルエンディアンになっていることに注意。
.pltでexitがジャンプ指定していた0x600458を見ると、d6024000つまり、0x4002d6のアドレスを得られる。このアドレスは先に見た.pltセクションにあった
4002d6: 68 01 00 00 00 pushq $0x1
という箇所に相当する。ここでpushしている1という値は.rela.pltセクションの要素を指すオフセット値であり、以下に示す.rela.pltセクションの中身を鑑みると0x1をpushすることによってexitの再配置エントリを表している。
Relocation section '.rela.plt' at offset 0x280 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000600450 000100000007 R_X86_64_JUMP_SLO 0000000000000000 putchar + 0 000000600458 000200000007 R_X86_64_JUMP_SLO 0000000000000000 exit + 0
ここでoffset要素の値は0x600458と、.got.pltセクション中のexitの飛び先を示すアドレスを格納したスロットを指している。
先ほどの1をpushしている命令の次は0x4002b0
とまあ、この最後の動的リンカのアドレスが埋め込まれれば動的ライブラリとの関連付けは完了することになる。
.dynsym
さて、再配置やらリンクやらとは言うものの、動的リンクした共有ライブラリの全てを使うわけではもちろんない。ではどうやって必要な対象を判断しているのか?そこで.dynsymセクションの出番になる。
.dynamicセクションの要素には.dynsymセクションを示すアドレスを持つ要素(SYMTAB)が存在し、.dynsymセクションにはこのファイルがインポート、エクスポートしているシンボルすべての情報が含まれている。また、その要素はElf64_Symと同様で、次の構造体で定義されている。
typedef struct { Elf64_Word st_name; /* Symbol name (string tbl index) */ unsigned char st_info; /* Symbol type and binding */ unsigned char st_other; /* Symbol visibility */ Elf64_Section st_shndx; /* Section index */ Elf64_Addr st_value; /* Symbol value */ Elf64_Xword st_size; /* Symbol size */ } Elf64_Sym;
.dynsymセクションの情報をreadelfから閲覧してみると
Symbol table '.dynsym' contains 3 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putchar@GLIBC_2.2.5 (2) 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2)
となっている。また、16進表示で閲覧してみると
Hex dump of section '.dynsym': 0x004001e8 00000000 00000000 00000000 00000000 ................ 0x004001f8 00000000 00000000 10000000 12000000 ................ 0x00400208 00000000 00000000 00000000 00000000 ................ 0x00400218 0b000000 12000000 00000000 00000000 ................ 0x00400228 00000000 00000000 ........
である。このセクションはエントリサイズが0x18なので、putcharについての情報は0x400200から0x400217であり、exitについての情報は0x400218から0x40022fまでが該当する。
これより、putcharについてのエントリを見ると、各値は
st_name : 0x10 st_info : 0x12 st_other : 0x00 st_shndx : 0x00 st_value : 0x00 st_size : 0x00
となっている。
- st_name
shstrtabと同じような指定でst_nameの値をオフセットとして.dynstrから取得してくる。
- st_info
elf.hより
/* How to extract and insert information held in the st_info field. */ #define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4) #define ELF32_ST_TYPE(val) ((val) & 0xf) #define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) /* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field. */ #define ELF64_ST_BIND(val) ELF32_ST_BIND (val) #define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val) #define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type))
というように解釈され、Bind, Typeの値を取得するのに使われる。
- st_other
/* How to extract and insert information held in the st_other field. */ #define ELF32_ST_VISIBILITY(o) ((o) & 0x03)/* For ELF64 the definitions are the same. */ #define ELF64_ST_VISIBILITY(o) ELF32_ST_VISIBILITY (o) /* Symbol visibility specification encoded in the st_other field. */ #define STV_DEFAULT 0 /* Default symbol visibility rules */ #define STV_INTERNAL 1 /* Processor specific hidden class */ #define STV_HIDDEN 2 /* Sym unavailable in other modules */ #define STV_PROTECTED 3 /* Not preemptible, not exported */
というように解釈され、今回の場合はreadelfした情報でVisの項目がDEFAULTとなっているので、ELF64_ST_VISIBILITYした結果が0になっているということになる
- st_shndx
全てのシンボルテーブルはいずれかのセクションと関連を持つことになり、また、全てのセクションは型を持ち、その型を表す値を保持している(SHT_SYMTAB、SHT_DYNSYMとか)。その型を示すインデックス値。
- st_value
関連付けられているシンボルの値。アドレスなど、状況によって保持する値が意味するものは変わる。
- st_size
シンボルに関連付けられる何かの大きさ。シンボルが大きさを持っていない場合または大きさが不明な場合、0を保持する。
また、複数の共有ライブラリをリンクするときにはどのファイルにお目当てのシンボルがあるのか、その判断が必要となるが、これについては以前に調査した.gnu.version周りが担当してくれてるのではないかな。多分。中身からしてそうっぽいので特に調べてないけど。
ということで
動的リンクに必要な所を追ってみたけど、結構ややこしくなってるのであとでもう一度見直してみようと思う。