2015年12月24日 星期四

如何解讀 dynamic section


.dynamic section 提供動態鏈結 (dynamic linking)需要的資訊,其中包含shared library的路徑。
本篇文章參考 oracle dynamic section文件,有興趣的人可以看看。

我會用底下的例子來說明,可以從這下載所需要的.c和.h檔。這些檔案的內容如下

  1. Lib_b.c : 會呼叫 libc內的 printf()
  2. Lib_a.c : 會呼叫 Lib_b.c內的function
  3. main.c : 會呼叫Lib_a.c的function,本身會產生執行檔

  • Overview

先編譯並反組譯這些檔案 (實驗的環境是 arm cortex-a7 32bits、gcc 4.6.3)。首先產生 Lib_a.o 和 Lib_b.o

$ gcc -g -shared -fPIC Lib_a.c -o Lib_a.o
$ gcc -g -shared -fPIC Lib_b.c -o Lib_b.o

接著產生執行檔

$ gcc -g main.c ./Lib_a.o ./Lib_b.o

然後反組譯 Lib_a.o、Lib_b.o、a.out,來產生 objdump.txt

$ objdump -sSdD a.out > objdump.txt

在解讀前,先偷看解答,看看.dynamic 和 .dynstr 會產生什麼結果,先打底下的指令

$ readelf -d a.out > dynamic_section.txt 


 dynamic_section.txt裡頭的東西長得就是這樣。除了前面第4行~第6行的shared library (Lib_a.o、Lib_b.o、libc.so.6)還看得懂外,其他的都不知道。不過沒關係,就一步步的看。
  1 
  2 Dynamic section at offset 0x5ec contains 27 entries:
  3   Tag        Type                         Name/Value
  4  0x00000001 (NEEDED)                     Shared library: [./Lib_a.o]
  5  0x00000001 (NEEDED)                     Shared library: [./Lib_b.o]
  6  0x00000001 (NEEDED)                     Shared library: [libc.so.6]
  7  0x0000000c (INIT)                       0x8444
  8  0x0000000d (FINI)                       0x85c8
  9  0x00000019 (INIT_ARRAY)                 0x105e0
 10  0x0000001b (INIT_ARRAYSZ)               4 (bytes)
 11  0x0000001a (FINI_ARRAY)                 0x105e4
 12  0x0000001c (FINI_ARRAYSZ)               4 (bytes)
 13  0x00000004 (HASH)                       0x8194
 14  0x6ffffef5 (GNU_HASH)                   0x81e4
 15  0x00000005 (STRTAB)                     0x8330
 16  0x00000006 (SYMTAB)                     0x8240
 17  0x0000000a (STRSZ)                      174 (bytes)
 18  0x0000000b (SYMENT)                     16 (bytes)
 19  0x00000015 (DEBUG)                      0x0
 20  0x00000003 (PLTGOT)                     0x106ec
 21  0x00000002 (PLTRELSZ)                   32 (bytes)
 22  0x00000014 (PLTREL)                     REL
 23  0x00000017 (JMPREL)                     0x8424
 24  0x00000011 (REL)                        0x841c
 25  0x00000012 (RELSZ)                      8 (bytes)
 26  0x00000013 (RELENT)                     8 (bytes)
 27  0x6ffffffe (VERNEED)                    0x83fc
 28  0x6fffffff (VERNEEDNUM)                 1
 29  0x6ffffff0 (VERSYM)                     0x83de

 30  0x00000000 (NULL)                       0x0

  • 如何解讀 .dynamic 反組譯內容

.dynamic section 反組譯後的內容如下,這其實是一個 structure table,存放的是 struct Elf32_Dyn,大小是8 bytes。注意,如果cpu是64bits的話,這個struct就是Elf64_Dyn,而大小則變為16bytes。

$ vim objdump.txt
  1 
  2 a.out:     file format elf32-littlearm
...
109 Contents of section .dynamic:
110  105ec 01000000 01000000 01000000 3e000000  ............>...
111  105fc 01000000 48000000 0c000000 44840000  ....H.......D...

112  1060c 0d000000 c8850000 19000000 e0050100  ................


這個struct Elf32_Dyn 可以從 /usr/include/link.h內找到

typedef struct {
        Elf32_Sword d_tag;
        union {
                Elf32_Word      d_val;
                Elf32_Addr      d_ptr;
                Elf32_Off       d_off;
        } d_un;
} Elf32_Dyn;

其中
  • d_tag : 表達編碼過的類型,而類型的定義可以考這個文件,以上面第110行的內容為例。
110  105ec 01000000 01000000
0x01表示的類型是 DT_NEEDED,就是需要載入的shared library。
  • d_du: 依照d_tag的值來決定union是 d_val, d_ptr, 或是 d_off
    • d_val : 表示是值
    • d_ptr : 表示地址
第110行的內容為例,因為d_tag是0x1。從文件的定義。可以知道 d_un現在表示的是d_val。
Name
Value
d_un
Executable
Shared Object
DT_NEEDED
1
d_val
Optional
Optional
所以0x105f0標示的 0x00000001代表的就是 d_val。
整合上面的內容可以把第 110行的內容轉換成下面的 struct
{
    d_tag = 0x1 /* (DT_NEEDED) */
    d_val = 0x1 /* (offset of DT_STRTAB) */
}

  • Type: NEEDED, STRTAB, and STRSZ
這些值可以幫助我們從反組譯中產生底下的內容
  2 Dynamic section at offset 0x5ec contains 27 entries:
  3   Tag        Type                         Name/Value
  4  0x00000001 (NEEDED)                     Shared library: [./Lib_a.o]

讓我們先看看這些的定義
    • NEEDED: 存放的是DT_STRTAB 表格內的 offset

先看反組譯的內容,可以發現 d_val為1,代表在 DT_STRTAB的offset為 1
110  105ec 01000000 01000000


    • STRTAB: 存放字串表格的地址。

從"readelf -r"產生的結果去看DT_STRTAB的值如下
 15  0x00000005 (STRTAB)                     0x8330

反組譯去中可以知道這指到 .dynstr section。裡面存放一堆字串,而且已0x00當作字串結尾。
 43 Contents of section .dynstr:
 44  8330 002e2f4c 69625f61 2e6f005f 5f676d6f  ../Lib_a.o.__gmo
 45  8340 6e5f7374 6172745f 5f005f4a 765f5265  n_start__._Jv_Re
 46  8350 67697374 6572436c 61737365 7300666f  gisterClasses.fo
 47  8360 6f005f69 6e697400 5f66696e 69002e2f  o._init._fini../
 48  8370 4c69625f 622e6f00 6c696263 2e736f2e  Lib_b.o.libc.so.
 49  8380 36006162 6f727400 5f5f6c69 62635f73  6.abort.__libc_s


    • STRSZ : 是STRTAB表格的大小


有了上面的資訊,可以直接去看 .dynamic section的實際內容了

109 Contents of section .dynamic:
110  105ec 01000000 01000000 01000000 3e000000  ............>...

因為這是32位元的機器,可以把0x105ec這一行共4個words解讀成

第一個struct
{
    d_tag = 0x1 (DT_NEEDED)
    d_val = 0x1 (offset of DT_STRTAB)
}

第二個struct
{
    d_tag = 0x1 (DT_NEEDED)
    d_val = 0x33 (offset of DT_STRTAB)
}

先看第一個 struct的 d_val,這個值是 0x1,代表在 .dynstr section的offset 為 0x1。透過查詢可以發現這個字串是"./Lib_a.o" ,請注意尾端是用NULL  (0x00) 當字串結尾。
 43 Contents of section .dynstr:
 44  8330 002e2f4c 69625f61 2e6f005f 5f676d6f  ../Lib_a.o.__gmo
 45  8340 6e5f7374 6172745f 5f005f4a 765f5265  n_start__._Jv_Re
 46  8350 67697374 6572436c 61737365 7300666f  gisterClasses.fo
 47  8360 6f005f69 6e697400 5f66696e 69002e2f  o._init._fini../
 48  8370 4c69625f 622e6f00 6c696263 2e736f2e  Lib_b.o.libc.so.
 49  8380 36006162 6f727400 5f5f6c69 62635f73  6.abort.__libc_s


同樣的,第二個 struct的字串代表的是"./Lib_b.o"
 43 Contents of section .dynstr:
 44  8330 002e2f4c 69625f61 2e6f005f 5f676d6f  ../Lib_a.o.__gmo
 45  8340 6e5f7374 6172745f 5f005f4a 765f5265  n_start__._Jv_Re
 46  8350 67697374 6572436c 61737365 7300666f  gisterClasses.fo
 47  8360 6f005f69 6e697400 5f66696e 69002e2f  o._init._fini../
 48  8370 4c69625f 622e6f00 6c696263 2e736f2e  Lib_b.o.libc.so.
 49  8380 36006162 6f727400 5f5f6c69 62635f73  6.abort.__libc_s


有興趣的人可以參考英文的定義





DT_NEEDED
The DT_STRTAB string table offset of a null-terminated string, giving the name of a needed dependency. The dynamic array can contain multiple entries of this type. The relative order of these entries is significant, though their relation to entries of other types is not. See Shared Object Dependencies.
DT_STRTAB
The address of the string table. Symbol names, dependency names, and other strings required by the runtime linker reside in this table. See String Table Section.
DT_STRSZ
The total size, in bytes, of the DT_STRTAB string table.

  • Type: INIT 和  FINI
  • INIT : 存放 init function
  • FINI : 存放 termination function
當process載入或是移除"每個" shared object時,就會呼叫這兩個function。

再回頭看 .dynamic section的值,可以知道init function和termination地址分別是 0x8444和0x85c8
109 Contents of section .dynamic:
110  105ec 01000000 01000000 01000000 3e000000  ............>...
111  105fc 01000000 48000000 0c000000 44840000  ....H.......D...
112  1060c 0d000000 c8850000 19000000 e0050100  ................

再次透過反組譯確認是否是function
424 Disassembly of section .init:
425 
426 00008444 <_init>:
427     8444:   e92d4008    push    {r3, lr}
428     8448:   eb000020    bl  84d0 <call_gmon_start>
429     844c:   e8bd8008    pop {r3, pc}

560 Disassembly of section .fini:
561 
562 000085c8 <_fini>:
563     85c8:   e92d4008    push    {r3, lr}
564     85cc:   e8bd8008    pop {r3, pc}


有興趣的人可以參考英文的定義

Name
Value
d_un
Executable
Shared Object
DT_INIT
12
d_ptr
Optional
Optional
DT_FINI
13
d_ptr
Optional
Optional





DT_INIT
The address of an initialization function. See Initialization and Termination Sections.
DT_FINI
The address of a termination function. See Initialization and Termination Sections.


  • Type: INIT_ARRAY, INIT_ARRAYSZ, FINI_ARRAY, and FINI_ARRAYSZ
 這四個參數是標記當shared object載入或是卸載時,需要呼叫哪些function。

    • DT_INIT_ARRAY : 紀錄 initialization function表格的地址
    • DT_INIT_ARRAYSZ : 標注這些function地址一共佔據幾個byte
    • DF_FINI_ARRAY : 紀錄termination function表格的地址
    • DT_FINI_ARRAYSZ : 紀錄這些地址一共佔據幾個byte
    • 範例

用下面的例子來看這些數值,其中 __attribute__((constructor)) 的作用是在shared object載入前先呼叫的一的function。反之,__attribute((destructor))是在shared object卸載前先呼叫對應的function。

$ vim main.c

#include    <stdio.h>
                                                                                static __attribute__((constructor)) void foo_1()
{
        (void) printf("initializing: foo_1()\n");
}

static __attribute__((constructor)) void foo_2()
{
        (void) printf("finalizing: foo_2()\n");
}

static __attribute__((destructor)) void bar_1()
{
        (void) printf("finalizing: bar_1()\n");
}

static __attribute__((destructor)) void bar_2()
{
        (void) printf("finalizing: bar_2()\n");
}

void main()
{
        (void) printf("main()\n");
}

接著編譯還有產生反組譯
$ gcc -o main main.c 
$ objdump -sSdD main > objdump.txt

這個程式的執行結果如下
$ ./main
initializing: foo_1()
finalizing: foo_2()
main()
finalizing: bar_2()
finalizing: bar_1()

先看 .dynamic section
$ readelf -d main 

Dynamic section at offset 0x534 contains 25 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x0000000c (INIT)                       0x82cc
 0x0000000d (FINI)                       0x84a4
 0x00000019 (INIT_ARRAY)                 0x10518
 0x0000001b (INIT_ARRAYSZ)               12 (bytes)
 0x0000001a (FINI_ARRAY)                 0x10524
 0x0000001c (FINI_ARRAYSZ)               12 (bytes)

然後看看 INIT_ARRAY和FINI_ARRAY的內容
 91 Contents of section .init_array:
 92  10518 98830000 c8830000 e0830000           ............    
 93 Contents of section .fini_array:
 94  10524 7c830000 f8830000 10840000           |...........    

其中 INIT_ARRAY中對應到的 function分別為 frame_dummy(), foo_1()和foo_2()。而佔的記憶體大小為12 bytes,和INIT_ARRAYSZ所標示的一樣

340 00008398 <frame_dummy>:
...
354 000083c8 <foo_1>:
...
362 000083e0 <foo_2>:

相同的FINI_ARRAY記載的function為

331 0000837c <__do_global_dtors_aux>:
...
370 000083f8 <bar_1>:
...
378 00008410 <bar_2>:

可以發現,function是照main.c的位置,依序放入array,如果是destructor,就高位元組先執行。

有興趣的人可看英文定義

Name
Value
d_un
Executable
Shared Object
DT_INIT_ARRAY
25
d_ptr
Optional
Optional
DT_FINI_ARRAY
26
d_ptr
Optional
Optional
DT_INIT_ARRAYSZ
27
d_val
Optional
Optional
DT_FINI_ARRAYSZ
28
d_val
Optional
Optional






DT_INIT_ARRAY
The address of an array of pointers to initialization functions. This element requires that a DT_INIT_ARRAYSZ element also be present. See Initialization and Termination Sections.
DT_FINI_ARRAY
The address of an array of pointers to termination functions. This element requires that a DT_FINI_ARRAYSZ element also be present. See Initialization and Termination Sections.
DT_INIT_ARRAYSZ
The total size, in bytes, of the DT_INIT_ARRAY array.
DT_FINI_ARRAYSZ
The total size, in bytes, of the DT_FINI_ARRAY array.

  • Type: SYMTAB and SYMENT
這些參數是Symbol table的重要值。之後symbol table會提供 .hash section用

    • SYMTAB : 指向 .dynsym section
裡面存放著 struct Elf32_Sym,定義可在 /usr/include/elf.h找到,把struct列出來看看

typedef struct {
        Elf32_Word      st_name;
        Elf32_Addr      st_value;
        Elf32_Word      st_size;
        unsigned char   st_info;
        unsigned char   st_other;
        Elf32_Half      st_shndx;
} Elf32_Sym;

      • st_name : 這是 .dynstr 的 byte offset
      • st_value : 在執行檔或是shared object內表示虛擬地址。詳細內容參考這
      • st_size : symbol size
      • st_info : 標示是否是 strong symbol, weak symbol, ... 。所有的型態可以參考這。另外,關於強弱符號的介紹可以參考這篇中文文章
      • st_other : symbol's visibility 
      • st_shndx : section header table index 



    • SYMENT : 每個 entry的大小
    • 範例
先看這些符號的值

 16  0x00000006 (SYMTAB)                     0x8240
...
 18  0x0000000b (SYMENT)                     16 (bytes)

可以看出 symbol table的地址是0x8240,每個entry 大小是 16 bytes,先列出幾行看看
 27 Contents of section .dynsym:
 28  8240 00000000 00000000 00000000 00000000  ................
 29  8250 0b000000 00000000 00000000 20000000  ............ ...
 30  8260 1a000000 00000000 00000000 20000000  ............ ...

第28行指到 .dynstr的 null
第29行指到 .dynstr的 __gmon_start__,注意這以 0x00 做結尾。且st_info的值為0x2,表示為weak symbol (為初始化的全域變數)
 43 Contents of section .dynstr:
 44  8330 002e2f4c 69625f61 2e6f005f 5f676d6f  ../Lib_a.o.__gmo
 45  8340 6e5f7374 6172745f 5f005f4a 765f5265  n_start__._Jv_Re
 46  8350 67697374 6572436c 61737365 7300666f  gisterClasses.fo
 47  8360 6f005f69 6e697400 5f66696e 69002e2f  o._init._fini../
 48  8370 4c69625f 622e6f00 6c696263 2e736f2e  Lib_b.o.libc.so.
 49  8380 36006162 6f727400 5f5f6c69 62635f73  6.abort.__libc_s
 50  8390 74617274 5f6d6169 6e005f65 64617461  tart_main._edata
 51  83a0 005f5f62 73735f73 74617274 005f5f62  .__bss_start.__b
 52  83b0 73735f73 74617274 5f5f005f 5f627373  ss_start__.__bss
 53  83c0 5f656e64 5f5f005f 5f656e64 5f5f005f  _end__.__end__._
 54  83d0 656e6400 474c4942 435f322e 3400      end.GLIBC_2.4.  

另外可以透過底下指令看symbol table

$ readelf -s a.out 

Symbol table '.dynsym' contains 15 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     3: 00010718     0 NOTYPE  GLOBAL DEFAULT   25 _bss_end__
     4: 00010714     0 NOTYPE  GLOBAL DEFAULT   24 _edata
     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND foo
     6: 00010718     0 NOTYPE  GLOBAL DEFAULT   25 _end
     7: 00010718     0 NOTYPE  GLOBAL DEFAULT   25 __bss_end__
     8: 00010714     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start
     9: 00000000     0 FUNC    GLOBAL DEFAULT  UND abort@GLIBC_2.4 (2)
    10: 00008444     0 FUNC    GLOBAL DEFAULT   12 _init
    11: 00010714     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start__
    12: 000085c8     0 FUNC    GLOBAL DEFAULT   15 _fini
    13: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.4 (2)

    14: 00010718     0 NOTYPE  GLOBAL DEFAULT   25 __end__

定義




DT_SYMTAB
The address of the symbol table. See Symbol Table Section.




DT_SYMENT
            The size, in bytes, of the DT_SYMTAB symbol entry. 


  • Type: HASH and GNU_HASH
HASH指向 .hash section
GNU_HASH指向 .gnu.hash section

 13  0x00000004 (HASH)                       0x8194
 14  0x6ffffef5 (GNU_HASH)                   0x81e4




DT_HASH
The address of the symbol hash table. This table refers to the symbol table indicated by the DT_SYMTAB element. See Hash Table Section.
  • Type: PLTGOT and PLTRELSZ
PLTGOT就是紀錄 GOT的地址。在GCC內,PLT就是程式碼,不是table。而PLTRELSZ記錄著GOT的大小

直接看"readelf -d"的結果,GOT table一共占32 bytes,和反組譯的結果相符

 20  0x00000003 (PLTGOT)                     0x106ec
 21  0x00000002 (PLTRELSZ)                   32 (bytes)

反組譯結果
126 Contents of section .got:
127  106ec ec050100 00000000 00000000 50840000  ............P...
128  106fc 50840000 50840000 50840000 00000000  P...P...P.......


PLTGOT的定義
    An address associated with the procedure linkage table or the global offset table.

PLTRELSZ :
    The total size, in bytes, of the relocation entries associated with the procedure linkage table.
  • Type: PLTREL, JMPREL, REL, RELA, RELSZ, and RELENT
這幾個符號是用來描述GOT內的內容。


    • PLTREL : 用來標示 relocation 方式
    • JMPREL : 存放 .rel.plt section地址。而這個section存放GOT內function的定義
    • REL : 存放 .rel.dyn section地址。而這個section存放GOT內data的定義
    • RELSZ : REL的大小
    • RELENT: REL entry的大小


    • 範例

先從 dynamic section看這些符號的內容。
 22  0x00000014 (PLTREL)                     REL
 23  0x00000017 (JMPREL)                     0x8424
 24  0x00000011 (REL)                        0x841c
 25  0x00000012 (RELSZ)                      8 (bytes)
 26  0x00000013 (RELENT)                     8 (bytes)

      • 解讀 JMPREL

先看 .rel.plt 內第64行的前兩個words
 63 Contents of section .rel.plt:
 64  8424 f8060100 160d0000 fc060100 16010000  ................
 65  8434 00070100 16050000 04070100 16090000  ................


        • 第一個word "0x106f8"代表的是需要relocate 的地址,這個地址為在GOT內,關於GOT的介紹在此
        • 第二個word "0x0d16"中的"16"代表 type R_ARM_JUMP_SLOT。而 "0d"代表symbol table的index。至於 "0d"對應到的值是什麼? ,可以透過下面的指令查詢到為 "__libc_start_main"

$ readelf -s a.out 

Symbol table for image:
  Num Buc:    Value  Size   Type   Bind Vis      Ndx Name
...
    2   0: 00000000     0 NOTYPE  WEAK   DEFAULT UND _Jv_RegisterClasses
    5   0: 00000000     0 FUNC    GLOBAL DEFAULT UND foo
    1   0: 00000000     0 NOTYPE  WEAK   DEFAULT UND __gmon_start__
...
   13   1: 00000000     0 FUNC    GLOBAL DEFAULT UND __libc_start_main
...
    3   2: 00010718     0 NOTYPE  GLOBAL DEFAULT  25 _bss_end__

      • 解讀 REL : 使用JMPREL的同樣方式可以解讀為__gmon_start__
 61 Contents of section .rel.dyn:
 62  841c 08070100 15010000 
      • 花了這麼多力氣,其實可以從透過底下的指令看結果
$ readelf -r a.out 

Relocation section '.rel.dyn' at offset 0x41c contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00010708  00000115 R_ARM_GLOB_DAT    00000000   __gmon_start__

Relocation section '.rel.plt' at offset 0x424 contains 4 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
000106f8  00000d16 R_ARM_JUMP_SLOT   00000000   __libc_start_main
000106fc  00000116 R_ARM_JUMP_SLOT   00000000   __gmon_start__
00010700  00000516 R_ARM_JUMP_SLOT   00000000   foo
00010704  00000916 R_ARM_JUMP_SLOT   00000000   abort



定義 :
PTREL :
    Indicates the type of relocation entry to which the procedure linkage table refers, either DT_REL or DT_RELA. All relocations in a procedure linkage table must use the same relocation. This element requires a DT_JMPREL element also be present.

RELA :
    The address of a relocation table.
    An object file can have multiple relocation sections. When creating the relocation table for an executable or shared object file, the link-editor catenates those sections to form a single table. Although the sections can remain independent in the object file, the runtime linker sees a single table. When the runtime linker creates the process image for an executable file or adds a shared object to the process image, the runtime linker reads the relocation table and performs the associated actions.
    This element requires the DT_RELASZ and DT_RELAENT elements also be present. When relocation is mandatory for a file, either DT_RELA or DT_RELcan occur.
REL :
    Similar to DT_RELA, except its table has implicit addends. This element requires that the DT_RELSZ and DT_RELENT elements also be present.

RELSZ :
    The total size, in bytes, of the DT_REL relocation table.

RELENT :
    The size, in bytes, of the DT_REL relocation entry.

JMPREL :
    The address of relocation entries that are associated solely with the procedure linkage table. The separation of these relocation entries enables the runtime linker to ignore these entries when the object is loaded with lazy binding enabled. This element requires the DT_PLTRELSZ and DT_PLTREL elements also be present.