歡迎您光臨本站 註冊首頁

Linux動態鏈接技術

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
   Linux動態鏈接技術

引自:http://qiuker.spaces.live.com/blog/cns!7B9B144C8022E90D!111.entry?_c11_blogpart_blogpart=blogview&_c=blogpart



Linux動態鏈接技術(zz)
  在動態鏈接的應用程序或共享庫中,ELF的程序頭描述表具有一個PT_DYNAMIC類型的描述符,它指出了.dynamic段的位置,dynamic段用來描述動態鏈接過程。當應用程序調用的共享庫函數時,要通過.plt段進行跳轉。plt段又稱為過程連接表,它是連接器ld所生成的一組靜態的 trampline,是只讀的可執行的段,包含在.text段一起映射到內存。plt每16個位元組為一個槽位,plt的第1個槽位保留給動態解析器使用,其餘的槽位表示對不同共享庫函數的調用。plt依賴於全局偏移量表(.got段),GOT表是一可寫的數據段,包含在.data段中一起映射到內存,用來存放共享符號的絕對地址。應用程序調用共享庫函數就是通過plt槽位上的一條jmp指令跳轉到GOT表所指的一個共享函數指針。這樣,共享庫的重定位就化為對GOT表項的重定位。GOT表的第1個指針指向.dynamic段,第2、3個指針與plt段的第1個槽位對應,用來安裝動態解析器。為了少做無用功,Linux採用了動態解析技術,就是說在載入共享庫時,並不進行函數的解析,而是安裝動態解析器,讓共享庫調用指向解析器,只有當函數調用發生時才進行解析。為此,在ld在生成可執行程序時,讓其GOT共享函數指針指向各自plt槽位上的兩條指令,一條是pushl指令,將該函數所對應GOT重定位表的索引作為參數壓入堆棧,然後通過另一條jmp指令跳轉到plt槽位1,它再跳轉到GOT表第3個指針所表示的動態解析器入口。這樣當發生並成功解析目標函數在共享庫中的地址時,該函數在程序GOT表中的指針就被實際的地址刷新。


下面是基於動態解釋器ld.so-1.9.9版本的簡單分析

簡單的測試文件testso.c:

int x = 0;

int test()
{
 return x;
}

用gcc -S -fPIC testso.c編繹成的彙編代碼:

.globl x
.data
 .align 4
 .type  x,@object
 .size  x,4
x:
 .long 0
.text
 .align 4
.globl test
 .type  test,@function
test:
 pushl %ebp
 movl %esp,%ebp
 pushl %ebx
 call .L2
.L2:
 popl %ebx # 取標號.L2所在的地址
 addl $_GLOBAL_OFFSET_TABLE_+[.-.L2],%ebx # _GLOBAL_OFFSET_TABLE_為當前地址到GOT表的偏移
 movl x@GOT(%ebx),%eax # ebx在-fPIC編繹的函數中用於指向本模塊的GOT表
 movl (%eax),%eax # x@GOT表示符號x在GOT表中的索引
 movl -4(%ebp),%ebx
 leave
 ret
.Lfe1:
 .size  test,.Lfe1-test

用gcc -shared testso.s -o testso.so生成共享庫的反彙編的有關輸出:

Disassembly of section .plt:

00000258 <.plt>:
 258: ff b3 04 00 00  pushl  0x4(%ebx) # GOT表的第2個指針,對-fPIC編繹的函數,ebx總是指向GOT表
 25d: 00
 25e: ff a3 08 00 00  jmp    *0x8(%ebx) # 跳轉到GOT表的第3個指針,調用__dl_linux_resolover
 263: 00
 264: 00 00           addb   %al,(%eax)
 266: 00 00           addb   %al,(%eax)
 268: ff a3 0c 00 00  jmp    *0xc(%ebx) # 跳轉到共享函數test()所在的GOT的指針
 26d: 00
 26e: 68 00 00 00 00  pushl  $0x0  # test()所在GOT指針的初始入口
 273: e9 e0 ff ff ff  jmp    258 <_init+0x8> # 跳轉到plt槽位1

Disassembly of section .text:

000002d8 :
 2d8: 55              pushl  %ebp
 2d9: 89 e5           movl   %esp,%ebp
 2db: 53              pushl  %ebx
 2dc: e8 00 00 00 00  call   2e1
 2e1: 5b              popl   %ebx
 2e2: 81 c3 ab 10 00  addl   $0x10ab,%ebx # 取GOT表指針
 2e7: 00
 2e8: 8b 83 10 00 00  movl   0x10(%ebx),%eax  # 從GOT表中取變數x的地址
 2ed: 00
 2ee: 8b 00           movl   (%eax),%eax
 2f0: 8b 5d fc        movl   0xfffffffc(%ebp),%ebx
 2f3: c9              leave  
 2f4: c3              ret    


引用testso的應用程序文件test.c:
main()
{
 printf("%d\n",test());
}
用gcc test.c testso.so -o test生成可執行文件的反彙編輸出:

Disassembly of section .plt:

08048398 <.plt>:
 8048398: ff 35 54 95 04  pushl  0x8049554 # GOT表的第2個指針
 804839d: 08
 804839e: ff 25 58 95 04  jmp    *0x8049558 # GOT表的第3個指針,運行_dl_linux_resolver
 80483a3: 08
 80483a4: 00 00           addb   %al,(%eax)
 80483a6: 00 00           addb   %al,(%eax)
 80483a8: ff 25 5c 95 04  jmp    *0x804955c # printf()在plt的入口
 80483ad: 08
 80483ae: 68 00 00 00 00  pushl  $0x0
 80483b3: e9 e0 ff ff ff  jmp    8048398 <_init+0x8>
 80483b8: ff 25 60 95 04  jmp    *0x8049560
 80483bd: 08
 80483be: 68 08 00 00 00  pushl  $0x8
 80483c3: e9 d0 ff ff ff  jmp    8048398 <_init+0x8>
 80483c8: ff 25 64 95 04  jmp    *0x8049564 # test()在plt段的調用點
 80483cd: 08    # [0x8048564]初始時指向0x80483ce
 80483ce: 68 10 00 00 00  pushl  $0x10 # test()在GOT表重定位表.rel.got中的索引
 80483d3: e9 c0 ff ff ff  jmp    8048398 <_init+0x8> # 跳轉到plt的第1槽位
 80483d8: ff 25 68 95 04  jmp    *0x8049568
 80483dd: 08
 80483de: 68 18 00 00 00  pushl  $0x18
 80483e3: e9 b0 ff ff ff  jmp    8048398 <_init+0x8>
 80483e8: ff 25 6c 95 04  jmp    *0x804956c
 80483ed: 08
 80483ee: 68 20 00 00 00  pushl  $0x20
 80483f3: e9 a0 ff ff ff  jmp    8048398 <_init+0x8>
 80483f8: ff 25 70 95 04  jmp    *0x8049570
 80483fd: 08
 80483fe: 68 28 00 00 00  pushl  $0x28
 8048403: e9 90 ff ff ff  jmp    8048398 <_init+0x8>

Disassembly of section .text:

080484c8 :
 80484c8: 55              pushl  %ebp
 80484c9: 89 e5           movl   %esp,%ebp
 80484cb: e8 f8 fe ff ff  call   80483c8 <_init+0x38> #
 80484d0: 89 c0           movl   %eax,%eax
 80484d2: 50              pushl  %eax
 80484d3: 68 38 85 04 08  pushl  $0x8048538
 80484d8: e8 cb fe ff ff  call   80483a8 <_init+0x18>
 80484dd: 83 c4 08        addl   $0x8,%esp
 80484e0: c9              leave  
 80484e1: c3              ret    

ld.so-1.9.9/d-link/i386/resolve.S

#define ALIGN 4
#define RUN linux_run
#define RESOLVE _dl_linux_resolve
#define RESOLVER _dl_linux_resolver
#define EXIT _interpreter_exit
#define INIT __loader_bootstrap

.text
.align ALIGN
 .align 16

.globl RESOLVE
 .type RESOLVE,@function
RESOLVE:
 pusha
 lea 0x20(%esp),%eax  /* eax = tpnt and reloc_entry params */
 pushl 4(%eax)   /* push copy of reloc_entry param */
 pushl (%eax)   /* push copy of tpnt param */
 pushl %eax   /* _dl_linux_resolver expects a dummy
      * param - this could be removed */
#ifdef __PIC__
 call .L24
.L24:
 popl %ebx
 addl $_GLOBAL_OFFSET_TABLE_+[.-.L24],%ebx
 movl RESOLVER@GOT(%ebx),%ebx /* eax = resolved func */
 call *%ebx
#else
 call RESOLVER
#endif
 movl %eax,0x2C(%esp)  /* store func addr over original
      * tpnt param */
 addl $0xC,%esp  /* remove copy parameters */
 popa    /* restore regs */
 ret $4   /* jump to func removing original
      * reloc_entry param from stack */
.LFE2:
 .size RESOLVE,.LFE2-RESOLVE

d-link/i386/elfinterp.c:

unsigned int _dl_linux_resolver(int dummy, int i)
{
  unsigned int * sp;
  int reloc_entry;
  int reloc_type;
  struct elf32_rel * this_reloc;
  char * strtab;
  struct elf32_sym * symtab;
  struct elf32_rel * rel_addr;
  struct elf_resolve * tpnt;
  int symtab_index;
  char * new_addr;
  char ** got_addr;
  unsigned int instr_addr;
  sp = &i;  
  reloc_entry = sp[1];
  tpnt = (struct elf_resolve *) sp[0];

  rel_addr = (struct elf32_rel *) (tpnt->dynamic_info[DT_JMPREL] +
       tpnt->loadaddr); 取可執行程序的GOT重定位表

  this_reloc = rel_addr + (reloc_entry >> 3);
  reloc_type = ELF32_R_TYPE(this_reloc->r_info);
  symtab_index = ELF32_R_SYM(this_reloc->r_info);

  symtab =  (struct elf32_sym *) (tpnt->dynamic_info[DT_SYMTAB] + tpnt->loadaddr);
  strtab = (char *) (tpnt->dynamic_info[DT_STRTAB] + tpnt->loadaddr);


  if (reloc_type != R_386_JMP_SLOT) {
    _dl_fdprintf(2, "%s: Incorrect relocation type in jump relocations\n",
   _dl_progname);
    _dl_exit(1);
  };

  /* Address of jump instruction to fix up */
  instr_addr  = ((int)this_reloc->r_offset  + (int)tpnt->loadaddr);
  got_addr = (char **) instr_addr;

#ifdef DEBUG
  _dl_fdprintf(2, "Resolving symbol %s\n",
 strtab + symtab[symtab_index].st_name);
#endif

  /* Get the address of the GOT entry */
  new_addr = _dl_find_hash(strtab + symtab[symtab_index].st_name,
     tpnt->symbol_scope, (int) got_addr, tpnt, 0);
  if(!new_addr) {
    _dl_fdprintf(2, "%s: can『t resolve symbol 『%s『\n",
        _dl_progname, strtab + symtab[symtab_index].st_name);
    _dl_exit(1);
  };
/* #define DEBUG_LIBRARY */
#ifdef DEBUG_LIBRARY
  if((unsigned int) got_addr < 0x40000000) {
    _dl_fdprintf(2, "Calling library function: %s\n",
        strtab + symtab[symtab_index].st_name);
  } else {
    *got_addr = new_addr;
  }
#else
  *got_addr = new_addr; 更新GOT函數指針
#endif
  return (unsigned int) new_addr;
}



[火星人 ] Linux動態鏈接技術已經有479次圍觀

http://coctec.com/docs/program/show-post-71824.html