Chinaunix首页 | 论坛 | 博客
  • 博客访问: 171020
  • 博文数量: 33
  • 博客积分: 2143
  • 博客等级: 大尉
  • 技术积分: 807
  • 用 户 组: 普通用户
  • 注册时间: 2005-12-31 10:24
个人简介

Show me the money

文章分类

全部博文(33)

文章存档

2015年(1)

2013年(1)

2011年(12)

2010年(14)

2009年(2)

2008年(2)

2005年(1)

我的朋友

分类: C/C++

2010-07-21 19:45:22

下面是官方文档(Executable and Linkable Format (ELF))对于PLT的描述,有点晦涩。

1 .  When first creating the memory image of the program, the dynamic linker sets the second and the third entries in the global offset table to special values.  Steps below explain more about these values.

2 .  If the procedure linkage table is position-independent, the address of the global offset table must reside in %ebx.  Each shared object file in the process image has its own procedure linkage table, and control transfers to a procedure linkage table entry only from within the same object file. Con-sequently, the calling function is responsible for setting the global offset table base register before calling the procedure linkage table entry.

3 .  For illustration, assume the program calls name1, which transfers control to the label .PLT1.

4 .  The first instruction jumps to the address in the global offset table entry for name1. Initially, the global offset table holds the address of the following pushl instruction, not the real address of

name1.

5 .  Consequently, the program pushes a relocation offset (offset) on the stack.  The relocation offset is a 32-bit, non-negative byte offset into the relocation table.  The designated relocation entry will have type R_386_JMP_SLOT, and its offset will specify the global offset table entry used in the previous jmp instruction.  The relocation entry also contains a symbol table index, thus telling the dynamic linker what symbol is being referenced, name1 in this case.

6 .  After pushing the relocation offset, the program then jumps to .PLT0, the first entry in the pro-cedure linkage table.  The pushl instruction places the value of the second global offset table entry (got_plus_4 or 4(%ebx)) on the stack, thus giving the dynamic linker one word of identifying information.  The program then jumps to the address in the third global offset table entry 2-18 Portable Formats Specification, Version 1.1  Tool Interface Standards (TIS)ELF: Executable and Linkable Format (got_plus_8 or 8(%ebx)), which transfers control to the dynamic linker.

7 .  When the dynamic linker receives control, it unwinds the stack, looks at the designated relocation entry, finds the symbol’s value, stores the ‘‘real’’ address for name1 in its global offset table entry, and transfers control to the desired destination.

8 .  Subsequent executions of the procedure linkage table entry will transfer directly to name1, without calling the dynamic linker a second time.  That is, the jmp instruction at .PLT1 will transfer to name1, instead of ‘‘falling through’’ to the pushl instruction

下面我们通过一个例子来进行更直观的了解:

#include

#include

#include

int main (int argc, char *argv[])

{

    srand(time(NULL));

    printf("The red quick fox ");

    fprintf(stdout, "jumps over a lazy brown goat.\n");

    return 0;

}

 

查看.plt段的内容:

Disassembly of section .plt:

 

08048378 :

 8048378:   ff 35 6c 97 04 08        pushl  0x804976c

 804837e:   ff 25 70 97 04 08        jmp    *0x8049770

 8048384:   00 00                add    %al,(%eax)

    ...

 

08048388 :

 8048388:   ff 25 74 97 04 08        jmp    *0x8049774

 804838e:   68 00 00 00 00           push   $0x0

 8048393:   e9 e0 ff ff ff           jmp    8048378 <_init+0x30>

 

08048398 :

 8048398:   ff 25 78 97 04 08        jmp    *0x8049778

 804839e:   68 08 00 00 00           push   $0x8

 80483a3:   e9 d0 ff ff ff           jmp    8048378 <_init+0x30>

 

080483a8 <__gmon_start__@plt>:

 80483a8:   ff 25 7c 97 04 08        jmp    *0x804977c

 80483ae:   68 10 00 00 00           push   $0x10

 80483b3:   e9 c0 ff ff ff           jmp    8048378 <_init+0x30>

 

080483b8 <__libc_start_main@plt>:

 80483b8:   ff 25 80 97 04 08        jmp    *0x8049780

 80483be:   68 18 00 00 00           push   $0x18

 80483c3:   e9 b0 ff ff ff           jmp    8048378 <_init+0x30>

 

080483c8 :

 80483c8:   ff 25 84 97 04 08        jmp    *0x8049784

 80483ce:   68 20 00 00 00           push   $0x20

 80483d3:   e9 a0 ff ff ff           jmp    8048378 <_init+0x30>

 

080483d8 :

 80483d8:   ff 25 88 97 04 08        jmp    *0x8049788

 80483de:   68 28 00 00 00           push   $0x28

 80483e3:   e9 90 ff ff ff           jmp    8048378 <_init+0x30>

 

查看.got.plt段的内容:

Disassembly of section .got.plt:

 

08049768 <_GLOBAL_OFFSET_TABLE_>:

08049768:  08049690 00000000 00000000 0804838e

08049778:  0804839e 080483ae 080483be 080483ce

08049788:  080483de

 

参照文档的说明,假设程序调用函数第一次执行srand@plt,那么将会发生

1.       0x8049778处取出函数地址(等于0x0804839e),然后跳转到0x0804839e

2.       执行 push   $0x8

3.       跳转到0x08048378

4.       执行pushl  0x804976c (_GLOBAL_OFFSET_TABLE_+4)

5.       0x8049770处取出函数fix_plt地址(在程序初始化阶段,dynamic linker会在0x8049770处填入fix_plt函数的地址),然后跳转到函数fix_plt

6.       fix_plt函数根据压入堆栈中的两个参数,定位到srand@plt对应的got表项(0x8049778),填入函数srand的绝对地址;然后跳转到srand去执行。

如果第二次执行函数srand@plt,从地址0x8049778取出的就已经是函数srand的地址了,那么就不会再执行以上的6个步骤了。

 

为了更直观的了解上述过程,我们修改一下测试程序:

#include

#include

#include

#include

static void dump_got(void)

{

    int i;

    extern unsigned int _GLOBAL_OFFSET_TABLE_[];

    printf("_GLOBAL_OFFSET_TABLE_ at %p\n", _GLOBAL_OFFSET_TABLE_);

    for(i=0; i<12; i++)

        printf(" %08x 0x%08x\n", &_GLOBAL_OFFSET_TABLE_[i], _GLOBAL_OFFSET_TABLE_[i]);

}

static void get_func_addr(const char *soname, const char *sym)

{

    void *handle;

    handle = dlopen(soname, RTLD_NOW);

    if( handle != NULL ) {

        void *func = dlsym(handle, sym);

        if(func != NULL)

            printf("%s@%p\n", sym, func);

        dlclose(handle);

    }

}

int main (int argc, char *argv[])

{

    void *handle;

    dump_got();

    srand(time(NULL));

    printf("The red quick fox ");

    fprintf(stdout, "jumps over a lazy brown goat.\n");

    dump_got();

    get_func_addr("libc.so.6", "srand");

    get_func_addr("libc.so.6", "time");

    return 0;

}

$gcc -fpic -Os study-plt.c –ldl

$./a.out

_GLOBAL_OFFSET_TABLE_ at 0x8049954

 08049954 0x08049878

 08049958 0x0070e658

 0804995c 0x007054c0

 08049960 0x08048426

 08049964 0x08048436

 08049968 0x08048446

 0804996c 0x08048456

 08049970 0x007274f0

 08049974 0x08048476

 08049978 0x08048486

 0804997c 0x00759670

 08049980 0x080484a6

The red quick fox jumps over a lazy brown goat.

_GLOBAL_OFFSET_TABLE_ at 0x8049954

 08049954 0x08049878

 08049958 0x0070e658

 0804995c 0x007054c0

 08049960 0x0076f4a0

 08049964 0x0073f5a0

 08049968 0x08048446

 0804996c 0x08048456

 08049970 0x007274f0

 08049974 0x08048476

 08049978 0x08048486

 0804997c 0x00759670

 08049980 0x0079acc0

srand@0x73f5a0

time@0x79acc0

$objdump -R a.out

a.out:     file format elf32-i386

 

DYNAMIC RELOCATION RECORDS

OFFSET   TYPE              VALUE

08049948 R_386_GLOB_DAT    __gmon_start__

08049950 R_386_GLOB_DAT    stdout

08049960 R_386_JUMP_SLOT   fputs

08049964 R_386_JUMP_SLOT   srand

08049968 R_386_JUMP_SLOT   __gmon_start__

0804996c R_386_JUMP_SLOT   dlclose

08049970 R_386_JUMP_SLOT   __libc_start_main

08049974 R_386_JUMP_SLOT   dlsym

08049978 R_386_JUMP_SLOT   dlopen

0804997c R_386_JUMP_SLOT   printf

08049980 R_386_JUMP_SLOT   time

从上面的运行结果我们可以清楚的看到,在函数调用前后,got表项所发生的变化。从而加深了我们对于ELF根据PLT进行重定位的理解。

阅读(1216) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~