Isa@Diary

ソフトウェア開発やってます。プログラミングとか、US生活とかについて書きます。

ELFについて学んだ

とりあえず忘れないように書いておく。

Object file

main.c

int main(){
    return 42;
}

をcompile(gcc -c main.c)してmain.oを作る

readlefでELF header, section headerを見る * ELF header, program header, section headerの構造はここ

ELF Header

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          528 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           64 (bytes)
  Number of section headers:         11
  Section header string table index: 8
  • object fileなのでprogram headerはない
  • ファイルサイズは1232Byte, これは 528 (section headerのoffset) + 64 (section header entry size) * 11 (# of section header)に等しい。(section headerはファイル末尾に置かれる、実行時には必要ないのでstripが簡単なように。)

Section header

There are 11 section headers, starting at offset 0x210:

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
       000000000000000b  0000000000000000  AX       0     0     1
  [ 2] .data             PROGBITS         0000000000000000  0000004b
       0000000000000000  0000000000000000  WA       0     0     1
  [ 3] .bss              NOBITS           0000000000000000  0000004b
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .comment          PROGBITS         0000000000000000  0000004b
       0000000000000035  0000000000000001  MS       0     0     1
  [ 5] .note.GNU-stack   PROGBITS         0000000000000000  00000080
       0000000000000000  0000000000000000           0     0     1
  [ 6] .eh_frame         PROGBITS         0000000000000000  00000080
       0000000000000038  0000000000000000   A       0     0     8
  [ 7] .rela.eh_frame    RELA             0000000000000000  000001a0
       0000000000000018  0000000000000018   I       9     6     8
  [ 8] .shstrtab         STRTAB           0000000000000000  000001b8
       0000000000000054  0000000000000000           0     0     1
  [ 9] .symtab           SYMTAB           0000000000000000  000000b8
       00000000000000d8  0000000000000018          10     8     8
  [10] .strtab           STRTAB           0000000000000000  00000190
       000000000000000d  0000000000000000           0     0     1
  • Headerに書かれている通り、8番目が.shstrtab (Section Header STRing TABle)で、各セクションの名前が格納されている
  • 各section header entryの先頭4バイトがそのsectionの名前への.shstrtabの先頭からのoffset
    • .shstrtabのoffset + section headerの先頭4バイト = section名のoffset
  • .symtab.strtabの関係も似ている
    • .symtabsymbol table entryの配列
    • Entryの先頭4バイトが.strtabの先頭からシンボル名へのoffset
  • .eh_frameについてはexceptionやdebug関連に使われるらしい。詳しくは知らない

Executable

gcc -o main main.cとしてexecutableを作る

ELF header

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4003e0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6568 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 28
  • Program headerができている

Shared library

gcc -c -fPIC foo.c
gcc -c -fPIC bar.c
gcc -shared -o libfoo.so

としてshared libraryを作る

foo.c

#include <stdio.h>

extern int bar_1;
extern int bar_2;

int foo_with_init_1 = 1;
double foo_with_init_2 = 2;
int foo_wo_init_1;
double foo_wo_init_2;

void print(int arg){
    printf("%lf", arg + bar_2 + foo_with_init_2);
    printf("%lf", arg + bar_2 + foo_with_init_2);
    printf("%lf", arg + bar_2 + foo_with_init_2);
}

bar.c

int bar_1 = 1;
int bar_2 = 2;

Relocation

printfの呼び出しはfoo.oでは

3c:   e8 00 00 00 00          callq  41 <print+0x41>

で、.soでは

842:   e8 69 fe ff ff          callq  6b0 <printf@plt>

になっている。これはfoo.o.rela.text sectionで

000000000000003d R_X86_64_PLT32    printf-0x0000000000000004

3dに対するrelocationが指定されているから。これは.soでは

0000000000201018 R_X86_64_JUMP_SLOT  printf@GLIBC_2.2.5

とされている。このoffsetは".got.plt" section内にある。

PLTとGOT

各プロセスからshared libraryをloadする際はrelocationによって書き換わる部分はWritableな必要があるが、なるべく共有する部分は増やしたい。 * PLT : 共有部分, read-only segment * GOT : プロセス固有部分, writable segment

ライブラリ関数を読んだ際の流れは(lazy symbol bindingの場合)

  1. func@PLTが呼ばれる
  2. func@PLTはGOTから飛び先を読む。初回はfunc@PLT内に戻ってくる
  3. func@PLTはGOT内のPT_DYNAMIC segmentのアドレスと、loaderのアドレスを取ってきてライブラリをloadする
  4. ロードされたらGOT内のアドレスを書き換える。これによって2回目以降は直接目的の関数が呼ばれる

.interp section

上記のPT_DYNAMIC segmentのアドレスと、loaderのアドレスはloader自身が書く。 これはプロセスが走り始める際(execve)にexecutableの.interp section (INTERP segment)で指定されるinterpreter(一般にはloader)がexecutableを引数として実行される。 これがexecutableとdynamic libraryの関連するsegmentを読み、適宜GOTの中身を書き換える。