読者です 読者をやめる 読者になる 読者になる

わらばんし仄聞記

南の国で引きこもってるWeb屋さん

ELFファイルを作る part7

ELF

さて、前回の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は引数として渡されてる筈なんで、この図のように外へ見に行くのはちょっと違うと思うが。
f:id:warabanshi21:20130514224017j:plain

動的リンク

では実際にtest.out実行時にはどのようにして必要とする共有ライブラリとリンクするのか?
動的リンカがDYNAMICセグメントを見つけるまでは先に示した通り。そして、このセグメント(=.dynamicセクション)の情報を元に動的リンクが行われていくことになる。
その挙動はこんな感じ。

f:id:warabanshi21:20130518223709j:plain

再配置

さて、立ち返って考えてみると、そもそも動的リンクは何故動的にリンクしてるのだったか。そう、プログラム中から外部の共有ライブラリにある処理を呼び出す際に必要だから動的リンクしているんだった。
ということで、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 へのジャンプ、つまりは.pltセクションの先頭へのジャンプを行う。そこでは0x20018aという値をpushして0x600448へジャンプする命令があり、この0x600448のアドレスの.got.pltスロットが動的リンカのアドレスとなるが、こればかりは動的リンカがどのようなアドレスに配置されるかは不明なので0が入っていて、実行時に埋め込まれる事になる。
とまあ、この最後の動的リンカのアドレスが埋め込まれれば動的ライブラリとの関連付けは完了することになる。

.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周りが担当してくれてるのではないかな。多分。中身からしてそうっぽいので特に調べてないけど。

ということで

動的リンクに必要な所を追ってみたけど、結構ややこしくなってるのであとでもう一度見直してみようと思う。