Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1068625
  • 博文数量: 277
  • 博客积分: 8313
  • 博客等级: 中将
  • 技术积分: 2976
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-22 11:25
文章分类

全部博文(277)

文章存档

2013年(17)

2012年(66)

2011年(104)

2010年(90)

我的朋友

分类: LINUX

2011-03-26 11:12:08

前几天一个师弟问了一个动态链接库变量的问题,突然答不上来,才发现原来知道的东西都忘了,于是找到以前看的东西,大概扫了一下,了解了,觉得有必要对elf文件格式记录下来,以加深了解。

估计用过linux的人都知道elf文件格式吧,其实在嵌入式领域也是用到elf文件格式,比如ads软件编译生成的就是axf文件格式的文件,这个其实是elf文件格式的扩展, 用gcc在编译时包含dwarf-2格式的调试信息,就可被ads识别,其实像arm公司的最新软件,realview等可直接支持elf文件的调试。

      下面就来讲下elf文件格式:

为了更好的完整的表述elf,我以一个例子来说:

my.clibmy.so的源文件:

vi my.c

int i=1;

void show() {

    printf("share lib test:%d\n",i);

}

gcc -fPIC -shared -o libmy.so my.c

 

test.c是使用这个共享库的测试文件:

vi test.c

extern int i;

int main(){

    i++;

    show();

    printf("%d",i);

}

gcc -o test test.c ./libmy.so

 

讲到elf文件格式,不得不提到三个概念。

elf文件头,程序头表,节区头部表,

1.elf文件头。

      这个顾明思议就是在文件的开始位置。

      文件的最开始几个字节给出如何解释文件的提示信息。

typedef struct{

unsigned char e_ident[EI_NIDENT]; //magic,就是elf文件标识,是elf

Elf32_Half e_type;   //用来标示是可重定位文件(1,可执行文件(2),还是共享文件(3

Elf32_Half e_machine; //那种cpu的文件,SPARC(2),i386(3)

Elf32_Word e_version;  //版本EV_NONE 0 非法版本 V_CURRENT 1 当前版本

Elf32_Addr e_entry;  //程序入口

Elf32_Off e_phoff;  //程序头表在文件中的偏移量

Elf32_Off e_shoff; //节区头部表在文件中的偏移量

Elf32_Word e_flags; //保存与文件相关的,特定于处理器的标志

Elf32_Half e_ehsize; //ELF 头部的大小(以字节计算)

Elf32_Half e_phentsize;//程序头部表格的表项大小(按字节计算

Elf32_Half e_phnum;//程序头部表格的表项的数量

Elf32_Half e_shentsize;//节区头部表格的表项大小(按字节计算)。

Elf32_Half e_shnum;//节区头部表格的表项数目。可以为 0

Elf32_Half e_shstrndx;//节区头部表格中与节区名称字符串表相关的表项的索引。

}Elf32_Ehdr;

从上可见elf文件头是灵魂,有它才可以找到真正描述文件的 程序头表, 节区头部表的数据

上面的test的文件头如下:

jivin@jivin-desktop:~/test/dym_so$ readelf test -h

ELF Header:

  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

  Class:                             ELF32

  Data:                              2's complement, little endian

  Version:                           1 (current)

  OS/ABI:                            UNIX - System V

  ABI Version:                       0

  Type:                              EXEC (Executable file)

  Machine:                           Intel 80386

  Version:                           0x1

  Entry point address:               0x8048440  //入口是__start

  Start of program headers:          52 (bytes into file) //elf头部大小为52btye

                                              所以program headerself header

  Start of section headers:          6004 (bytes into file)

  Flags:                             0x0

  Size of this header:               52 (bytes)//elf头部大小为52btye

  Size of program headers:           32 (bytes)

  Number of program headers:         8

  Size of section headers:           40 (bytes)

  Number of section headers:         36

  Section header string table index: 33

 

2.节区头部表。

 节区头部表是给链接器用的,在编译的时候,和重定位的时候用的。

(1).目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意味着有节区。

(2).每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。

(3).文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。

(4).目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容未指定。

 

所谓表就是一个数组,每个元素是一个节区描述结构。

typedef struct{

Elf32_Word sh_name;  //节区的名字,比如.text,.data,.symbol,.dynmatic

Elf32_Word sh_type;  //节区类型,SHT_SYMTABSHT_PROGBITSSHT_STRTABSHT_RELA

Elf32_Word sh_flags;//一个节区中包含的内容是否可以修改、是否可以执行

Elf32_Addr sh_addr;  //如果节区将在进程的内存映像中,此成员给出节区的第一个字节应处的位置这个一般在可执行文件中才不为0,因为可重定位文件(.o)没法确定地址.

Elf32_Off sh_offset; //节区在文件中的偏移

Elf32_Word sh_size; //节区大小

Elf32_Word sh_link;//

Elf32_Word sh_info;//

Elf32_Word sh_addralign;//对齐

Elf32_Word sh_entsize;//节区每个元素数据结构的大小,比如一个.symbol的大小

}Elf32_Shdr;

 

jivin@jivin-desktop:~/test/dym_so$ readelf test -S

Section Headers:

(这个处于6004偏移处)

  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al

  [ 0]                   NULL            00000000 000000 000000 00      0   0  0

//要链接如内存映像的节区开始处

  [ 1] .interp           PROGBITS        08048134 000134 000013 00   A  0   0  1

  [ 2] .note.ABI-tag     NOTE            08048148 000148 000020 00   A  0   0  4

  [ 3] .note.gnu.build-i NOTE            08048168 000168 000024 00   A  0   0  4

  [ 4] .hash             HASH            0804818c 00018c 000048 04   A  6   0  4

  [ 5] .gnu.hash         GNU_HASH        080481d4 0001d4 000040 04   A  6   0  4

//紫色的这些节区和动态链接库链接相关的节区

  [ 6] .dynsym           DYNSYM          08048214 000214 0000d0 10   A  7   1  4

  [ 7] .dynstr           STRTAB          080482e4 0002e4 000094 00   A  0   0  1

  [ 8] .gnu.version      VERSYM          08048378 000378 00001a 02   A  6   0  2

  [ 9] .gnu.version_r    VERNEED         08048394 000394 000020 00   A  7   1  4

  [10] .rel.dyn          REL             080483b4 0003b4 000010 08   A  6   0  4

  [11] .rel.plt          REL             080483c4 0003c4 000020 08   A  6  13  4

  [12] .init             PROGBITS        080483e4 0003e4 000030 00  AX  0   0  4

  [13] .plt              PROGBITS        08048414 000414 000050 04  AX  0   0  4

  [14] .text             PROGBITS        08048470 000470 00018c 00  AX  0   0 16

  [15] .fini             PROGBITS        080485fc 0005fc 00001c 00  AX  0   0  4

  [16] .rodata           PROGBITS        08048618 000618 00000b 00   A  0   0  4

  [17] .eh_frame         PROGBITS        08048624 000624 000004 00   A  0   0  4

  [18] .ctors            PROGBITS        08049f04 000f04 000008 00  WA  0   0  4

  [19] .dtors            PROGBITS        08049f0c 000f0c 000008 00  WA  0   0  4

[20] .jcr              PROGBITS        08049f14 000f14 000004 00  WA  0   0  4

[21] .dynamic          DYNAMIC         08049f18 000f18 0000d8 08  WA  7   0  4

[22] .got              PROGBITS        08049ff0 000ff0 000004 04  WA  0   0  4

  [23] .got.plt          PROGBITS        08049ff4 000ff4 00001c 04  WA  0   0  4

  [24] .data             PROGBITS        0804a010 001010 000008 00  WA  0   0  4

  [25] .bss              NOBITS          0804a018 001018 00000c 00  WA  0   0  4

  //要链接如内存映像的节区结束处

  [26] .comment          PROGBITS        00000000 001018 000046 01  MS  0   0  1

  [27] .debug_aranges    PROGBITS        00000000 001060 000020 00      0   0  8

  [28] .debug_pubnames   PROGBITS        00000000 001080 000025 00      0   0  1

  [29] .debug_info       PROGBITS        00000000 0010a5 0000ef 00      0   0  1

  [30] .debug_abbrev     PROGBITS        00000000 001194 00005f 00      0   0  1

  [31] .debug_line       PROGBITS        00000000 0011f3 000082 00      0   0  1

  [32] .debug_str        PROGBITS        00000000 001275 000092 01  MS  0   0  1

  [33] .shstrtab         STRTAB          00000000 001307 00013e 00      0   0  1

  [34] .symtab           SYMTAB          00000000 0019e8 0004a0 10     35  52  4

  [35] .strtab           STRTAB          00000000 001e88 00020a 00      0   0  1

  

下面讲下特殊节区:

1..symtab,.dynsym;(符号表节区)

这个节区也是一个数组,每个元素描述一个符号:

 

其结构如下:

typedef struct {

Elf32_Word st_name; //符号名字,比如变量i,b

Elf32_Addr st_value;//符号的取值。依赖于具体的上下文,它可能是一个绝对值、一个地址等等

Elf32_Word st_size;//这个符号的对应的数据的大小(int,long等),如没有大小此成员为 0

unsigned char st_info;//此成员给出符号的类型和绑定属性。下面给出若干取值和含义的绑定关系。

unsigned char st_other;//预留的,没有使用

Elf32_Half st_shndx;每个符号表项都以和其他节区间的关系的方式给出定义。此成员给出相关的节区                头部表索引。某些索引具有特殊含义。

} Elf32_sym;

 

st_info这个内容比较丰富:

#define ELF32_ST_BIND(i) ((i)>>4)//高四位用于绑定信息

#define ELF32_ST_TYPE(i) ((i)&0xf) //第四位用于符号类型

#define ELF32_ST_INFO(b, t) (((b)<<4) + ((t)&0xf))

 

绑定信息主要是局部变量,全局变量等描述:

STB_LOCAL 0

局部符号在包含该符号定义的目标文件以外不可见。相同名称的局部符号可以存在于多个文件中,互不影响。

STB_GLOBAL 1

全局符号对所有将组合的目标文件都是可见的。一个文件中对某个全局符号的定义将满足另一个文件对相同全局符号的未定义引用。

STB_WEAK 弱符号与全局符号类似,不过他们的定义优先级比较低。

STB_LOPROC 13

STB_HIPROC 15  处于13-15这个范围的取值是保留给处理器专用语义的。

 

符号类型主要是说是变量,还是函数,还是其他的东西:

STT_OBJECT  1

符号与某个数据对象相关,比如一个变量、数组等等

STT_FUNC   2

符号与某个函数或者其他可执行代码相关

STT_SECTION 3

符号与某个节区相关。这种类型的符号表项主要用于重定位,通常具有 STB_LOCAL 绑定。

STT_FILE 4

传统上,符号的名称给出了与目标文件相关的源文件的名称。文件符号具有 STB_LOCAL 绑定,其节区索引是

jivin@jivin-desktop:~/test/dym_so$ readelf test -s

Symbol table '.dynsym' contains 13 entries:

  Num:    Value  Size Type    Bind   Vis      Ndx Name

     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND

     5: 00000000     0 FUNC    GLOBAL DEFAULT  UND show

     6: 0804a018     4 OBJECT  GLOBAL DEFAULT   25 i

    10: 0804a018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start

    11: 080483e4     0 FUNC    GLOBAL DEFAULT   12 _init

    12: 080485fc     0 FUNC    GLOBAL DEFAULT   15 _fini

 

 

Symbol table '.symtab' contains 74 entries:

   Num:    Value  Size Type    Bind   Vis      Ndx Name

     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND

    33: 00000000     0 FILE    LOCAL  DEFAULT  ABS init.c  

    62: 0804a018     4 OBJECT  GLOBAL DEFAULT   25 i

    63: 0804a014     0 OBJECT  GLOBAL HIDDEN   24 __dso_handle

     67: 0804a018     0 NOTYPE  GLOBAL DEFAULT  ABS __bss_start

    68: 00000000     0 FUNC    GLOBAL DEFAULT  UND show

    69: 0804a024     0 NOTYPE  GLOBAL DEFAULT  ABS _end

    72: 08048524    50 FUNC    GLOBAL DEFAULT   14 main

    73: 080483e4     0 FUNC    GLOBAL DEFAULT   12 _init

2..strtab节区:

     这个节区包含字符串,不在是一个结构的数组

其组成形式如下:

      sjj0412\0 rtlab\0

那么如果我们使用sjj0412这个字符串,则是使用了这个节区的0索引的字符串,而rtlab就是1索引的字符串。

字符串表索引可以引用节区中任意字节。

􀂄 字符串可以出现多次

�� 可以存在对子字符串的引用

􀂄 同一个字符串可以被引用多次。

􀂄 字符串表中也可以存在未引用的字符串

 

3..rel从定位节区

     这个一般在可重定位文件中(.o)或使用了共享文件的可执行文件中。

 

对于可执行文件,只有使用动态链接库的才有:

jivin@jivin-desktop:~/test/dym_so$ readelf test -r

Relocation section '.rel.dyn' at offset 0x3b4 contains 2 entries:

Offset     Info    Type            Sym.Value  Sym. Name

08049ff0  00000106 R_386_GLOB_DAT    00000000   __gmon_start__

0804a018  00000605 R_386_COPY        0804a018   i

//动态库中的变量重定位信息在rel.dyn节区里,是R_386_COPY

Relocation section '.rel.plt' at offset 0x3c4 contains 4 entries:

 Offset     Info    Type            Sym.Value  Sym. Name

0804a000  00000107 R_386_JUMP_SLOT   00000000   __gmon_start__

0804a004  00000307 R_386_JUMP_SLOT   00000000   __libc_start_main

0804a008  00000407 R_386_JUMP_SLOT   00000000   printf

0804a00c  00000507 R_386_JUMP_SLOT   00000000   show

//动态库中的函数重定位信息在rel.plt节区里,是R_386_JUMP_SLOT

 

下面来分析下他们的偏移:

[24] .bss              NOBITS          0804a018 001018 00000c 00  WA  0   0  4

0804a018  00000605 R_386_COPY        0804a018   i

//可知动态库中的变量(i)在使用者中分配在了.bbs节区。

[21] .got              PROGBITS        08049ff0 000ff0 000004 04  WA  0   0  4

[22] .got.plt          PROGBITS        08049ff4 000ff4 00001c 04  WA  0   0  4

0804a00c  00000507 R_386_JUMP_SLOT   00000000   show

//可知动态库中的函数(show)在使用者中分配在了.got.plt节区。

 

下面来差下这些变量在共享库中如何分配的:

jivin@jivin-desktop:~/test/dym_so$ readelf libmy.so -r

 

 

Relocation section '.rel.dyn' at offset 0x2e4 contains 5 entries:

 Offset     Info    Type            Sym.Value  Sym. Name

0000200c  00000008 R_386_RELATIVE  

00001fe4  00000106 R_386_GLOB_DAT    00000000   __gmon_start__

00001fe8  00000206 R_386_GLOB_DAT    00000000   _Jv_RegisterClasses

00001fec  00000506 R_386_GLOB_DAT    00002010   i

00001ff0  00000406 R_386_GLOB_DAT    00000000   __cxa_finalize

Relocation section '.rel.plt' at offset 0x30c contains 3 entries:

 Offset     Info    Type            Sym.Value  Sym. Name

00002000  00000107 R_386_JUMP_SLOT   00000000   __gmon_start__

00002004  00000307 R_386_JUMP_SLOT   00000000   printf

00002008  00000407 R_386_JUMP_SLOT   00000000   __cxa_finalize

可知show函数在libmy.so不需要重定位:

 

i变量要重定位且在.got节区

jivin@jivin-desktop:~/test/dym_so$ readelf libmy.so -S

Section Headers:

  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al

  [ 0]                   NULL            00000000 000000 000000 00      0   0  0

  [19] .got              PROGBITS        00001fe4 000fe4 000010 04  WA  0   0  4

  [20] .got.plt          PROGBITS        00001ff4 000ff4 000018 04  WA  0   0  4

 

既然说到got,plt节区,也就谈谈.plt节区:

 

这个就是插桩代码节区,每个共享库中的函数调用要插一个桩:

 [13] .plt              PROGBITS        08048414 000414 000050 04  AX  0   0  4

 

再看下代码:

jivin@jivin-desktop:~/test/dym_so$ objdump -S test

  Disassembly of section .plt:

 08048414 <>:                //GOT[0].dynamic节区地址

 8048414:  ff 35 f8 9f 04 08        pushl  0x8049ff8 //GOT1]动态标识信息在got.plt节区

 804841a:  ff 25 fc 9f 04 08        jmp    *0x8049ffc //GOT2]动态链接器入口在.got.plt

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

    ...

 

08048424 <__gmon_start__@plt>:

 8048424:  ff 25 00 a0 04 08        jmp    *0x804a000

 804842a:  68 00 00 00 00           push   $0x0

 804842f:  e9 e0 ff ff ff           jmp    8048414 <_init+0x30>

 

08048434 <__libc_start_main@plt>:

 8048434:  ff 25 04 a0 04 08        jmp    *0x804a004

 804843a:  68 08 00 00 00           push   $0x8

 804843f:  e9 d0 ff ff ff           jmp    8048414 <_init+0x30>

 

08048444 :

 8048444:  ff 25 08 a0 04 08        jmp    *0x804a008

 804844a:  68 10 00 00 00           push   $0x10

 804844f:  e9 c0 ff ff ff           jmp    8048414 <_init+0x30>

 

08048454 :

 8048454:  ff 25 0c a0 04 08        jmp    *0x804a00c 对应got.plt节区的show

 804845a:  68 18 00 00 00           push   $0x18

 804845f:  e9 b0 ff ff ff           jmp    8048414 <_init+0x30>

 

从上面可以分析出:

        在共享库中:

            定义的变量会出现在.rel.dyn里,且占用一个.got节用来重定位到引用这个变量的应用程序的.bbs段中,装载时,动态链接器会修改动态库中的.got数据,从而导致cow,从而新的进程会有个自己的copy,即变量i私有了。。

        在使用共享库的程序中:

        对于引用的函数,重定位信息会出现在.rel.plt里,每个函数都要生成对应的plt代码(在.plt节区里),并且在.got.plt节区里有一个成员来指向共享库中函数的地址。

        对于引用的变量,重定位信息会出现在.rel.dyn,.bbs节中。

     当我们要调用show时,其实是调用08048454 ,然后第一次 会执行到804845f:    e9 b0 ff ff ff          jmp    8048414 <_init+0x30>,这个就会进入动态链接库程序,等出来的时候*0x804a00c指向真正的show函数地址

对可重定位文件:

      jivin@jivin-desktop:~/test/dym_so$ readelf my.o -r

 

 

Relocation section '.rel.text' at offset 0x35c contains 3 entries:

 Offset     Info    Type            Sym.Value  Sym. Name

00000007  00000801 R_386_32          00000000   i

00000012  00000501 R_386_32          00000000   .rodata

00000017  00000a02 R_386_PC32        00000000   printf

R_386_32 类型的重定位是:*(offset)=adr(sym);

 R_386_PC32  *(offset)=offset-sym.offset+adr(sym);

3.程序头部表:

      程序头部表是给装载器用的,在将程序装载到内存中要用的。

jivin@jivin-desktop:~/test/dym_so$ readelf test -l

Elf file type is EXEC (Executable file)

Entry point 0x8048470

There are 8 program headers, starting at offset 52

Program Headers:

  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align

  PHDR           0x000034 0x08048034 0x08048034 0x00100 0x00100 R E 0x4

  INTERP         0x000134 0x08048134 0x08048134 0x00013 0x00013 R   0x1

                     [Requesting program interpreter: /lib/ld-linux.so.2]

  LOAD           0x000000 0x08048000 0x08048000 0x00628 0x00628 R E 0x1000

  LOAD           0x000f04 0x08049f04 0x08049f04 0x00114 0x00120 RW  0x1000

  DYNAMIC        0x000f18 0x08049f18 0x08049f18 0x000d8 0x000d8 RW  0x4

  NOTE           0x000148 0x08048148 0x08048148 0x00044 0x00044 R   0x4

  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

  GNU_RELRO      0x000f04 0x08049f04 0x08049f04 0x000fc 0x000fc R   0x1

 

 

 Section to Segment mapping:

  Segment Sections...

   00    

   01     .interp

   02     .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame

   03     .ctors .dtors .jcr .dynamic .got .got.plt .data .bss

   04     .dynamic

   05     .note.ABI-tag .note.gnu.build-id

   06    

   07     .ctors .dtors .jcr .dynamic .got

   

从上面我们可以看出:

      一个程序段可以包含很多节区。

所以段和节区是从不同角度来表述程序。

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