文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>结合实例解读ELF文件

结合实例解读ELF文件

时间:2009-04-28  来源:engine_chen

结合实例解读ELF文件

来源:Linux联盟收集整理  作者:
<IFRAME id=google_ads_frame1 style="LEFT: 0px; POSITION: absolute; TOP: 0px" name=google_ads_frame marginWidth=0 marginHeight=0 src="http://googleads.g.doubleclick.net/pagead/ads?client=ca-pub-3701573918329010&amp;dt=1240922759625&amp;lmt=1239602403&amp;output=html&amp;slotname=1645276672&amp;correlator=1240922759625&amp;url=http%3A%2F%2Fwww.xxlinux.com%2Flinux%2Farticle%2Faccidence%2Finstall%2F20070409%2F8124.html&amp;ref=http%3A%2F%2F203.208.39.99%2Fsearch%3Fq%3Dcache%3ANL6naQoZekEJ%3Awww.91linux.com%2Fhtml%2Farticle%2Fprogram%2Fcpp%2F20080901%2F13369_2.html%2Bout%2B%25E6%25A0%25BC%25E5%25BC%258F%26cd%3D6%26hl%3Dzh-CN%26ct%3Dclnk%26gl%3Dcn%26st_usg%3DALhdy28uVNM8zA_tq0oKPY7P6sbGSURDxQ&amp;frm=0&amp;ga_vid=616159317.1240922760&amp;ga_sid=1240922760&amp;ga_hid=1412005881&amp;flash=9.0.124.0&amp;u_h=768&amp;u_w=1366&amp;u_ah=738&amp;u_aw=1366&amp;u_cd=32&amp;u_tz=480&amp;u_java=true&amp;dtd=421&amp;w=300&amp;h=250&amp;xpc=n6K5vh8c5W&amp;p=http%3A//www.xxlinux.com" frameBorder=0 width=300 scrolling=no height=250 allowTransparency></IFRAME> 一. 预备知识
网上有很多文章讲叙了ELF文件的格式, 加载过程等, 其中我觉得比较实用的是这几篇:
1.    breadbox 的< EXECUTABLE AND LINKABLE FORMAT (ELF)> 英文文档很多地方都有下载, alert7主页里面有中文翻译和英文原文.
英文原文: http://elfhack.whitecell.org/mydocs/elf.txt
alert7翻译整理的中文: http://elfhack.whitecell.org/mydocs/ELF_chinese.txt
2.    alert7的<ELF动态解析符号过程(修订版)>
http://elfhack.whitecell.org/myd ... esolve_process1.txt
但2只是阐述了一下动态解析符号过程, 1 是列举了一大堆的参数结构, 其中的联系, 结构等如果你开始看肯定会摸不着头脑, 我自己因为要写一个程序, 用到ELF文件的一些东西, 想想肯定还有很多人有和我一样的困惑, 本文权当做我写的笔记, 如果同时也能给你带来帮助, 就不枉此文了.
本文假设你已经看过了1或者2等文章, 熟悉linux系统, gdb等.
二. 分析平台
[netconf@linux1 elf]$ uname -a
Linux linux1 2.4.18-14 #1 Wed Sep 4 13:35:50 EDT 2002 i686 i686 i386 GNU/Linux
[netconf@linux1 elf]$ cat /proc/version
Linux version 2.4.18-14 ([email protected]) (gcc version 3.2 20020903 (Red Hat Linux 8.0 3.2-7)) #1 Wed Sep 4 13:35:50 EDT 2002
[netconf@linux1 elf]$ rpm -qf /usr/bin/readelf /usr/bin/hexdump
binutils-2.13.90.0.2-2
util-linux-2.11r-10
[netconf@linux1 elf]$
三. 分析的例子/程序
[netconf@linux1 elf]$ cat elf8.c
#include <elf.h>
int foo1()
{
printf([+] foo1 addr:%p\n,foo1);
foo2();
}
int foo2()
{
printf([+] foo2 addr:%p\n,foo2);
foo3();
}
int foo3()
{
printf([+] foo3 addr:%p\n,foo3);
foo4();
}
int foo4()
{
printf([+] foo4 addr:%p\n,foo4);
}
main()
{
foo1();
}
[netconf@linux1 elf]$ gcc -o elf8 elf8.c
[netconf@linux1 elf]$ ./elf8
[+] foo1 addr:0x8048328
[+] foo2 addr:0x804834a
[+] foo3 addr:0x804836c
[+] foo4 addr:0x804838e
四.分析过程
1. ELF header
首先, elf文件的开头是一个Ehdr的结构. 该结构定义在/usr/include/elf.h中, 我们看看该结构:
typedef struct
{
unsigned char    e_ident[EI_NIDENT];    /* Magic number and other info */
Elf32_Half    e_type;            /* 目标文件类型 */
Elf32_Half    e_machine;        /* Architecture */
Elf32_Word    e_version;        /* Object file version */
Elf32_Addr    e_entry;        /* 入口地址 */
Elf32_Off    e_phoff;        /* Program header table文件偏移 */
Elf32_Off    e_shoff;        /* Section header table 文件偏移 */
Elf32_Word    e_flags;        /* Processor-specific flags */
Elf32_Half    e_ehsize;        /* ELF header 大小 */
Elf32_Half    e_phentsize;        /* 每个Program header大小 */
Elf32_Half    e_phnum;        /* 一共多少个Program header */
Elf32_Half    e_shentsize;        /* 每个Section header大小 */
Elf32_Half    e_shnum;        /* 一共多少个 Section header */
Elf32_Half    e_shstrndx;        /* Section的字符表在section header table的索引值 */
} Elf32_Ehdr;

除了Elf32_Half是2个字节(16位)外, 其他变量定义等都是4个字节(32位).
从上面的结构可以看出来sizeof(Elf32_Ehdr)=13*4=0x34
我们来看看文件elf8头52字节的内容:
[netconf@linux1 elf]$ hexdump -s 0 -n 52 -C elf8
00000000 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 03 00 01 00 00 00 78 82 04 08 34 00 00 00 |........x...4...|
00000020 78 21 00 00 00 00 00 00 34 00 20 00 06 00 28 00 |x!......4. ...(.|
00000030 22 00 1f 00 |...|
ok, 我们对照这个结构一个个来分析:
e_ident[EI_NIDENT]: 16字节: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
ELFMAG0 0x7f e_ident[0]
ELFMAG1 'E' e_ident[1]
ELFMAG2 'L' e_ident[2]
ELFMAG3 'F' e_ident[3]
ELFCLASS32 1 e_ident[4]
ELFDATA2LSB 1 e_ident[5]
EI_VERSION 1 e_ident[6]
剩下的全为0
e_type:2字节: 02 00 表示可执行文件(ET_EXEC 2 Executable file)
e_machine: 2字节: 03 00 表示386体系文件(EM_386 3 Intel 80386)
e_version: 4字节: 01 00 00 00 和e_ident里面的EI_VERSION含义一样.
e_entry: 4字节: 78 82 04 08 表示程序入口地址0x08048278
e_phoff: 4字节: 34 00 00 00 表示program head table在文件中的偏移量(开始位置)
e_shoff: 4字节: 78 21 00 00 表示section head table在文件中的偏移量(开始位置)
e_flags: 4字节: 00 00 00 00
e_ehsize: 2字节: 34 00 表示elf header大小, 其实就是sizeof(Elf32_Ehdr)
e_phentsize: 2字节: 20 00 表示每个program header 的大小(0x20)
e_phnum: 2字节: 06 00 表示一共多少个program header(0x06)
e_shentsize: 2字节: 28 00 表示每个section header的大小(0x28)
e_shnum: 2字节: 22 00, 表示有多少个section header
e_shstrndx: 2字节: 1f 00 表示section string table在section header table中的索引值.(即第几个section描述了section string table的位置和大小)
是不是很简单?我们来看看readelf的分析结果:
[netconf@linux1 elf]$ readelf -h elf8
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: 0x8048278
Start of program headers: 52 (bytes into file)
Start of section headers: 8568 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 6
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31
2. Program header
除了开始的elf header位置是固定的外, 其他位置都是相关联的, 或者说都是要到elf header里面读取的.
看elf.h里面关于这个结构的定义:
typedef struct
{
Elf32_Word    p_type;            /* Segment type */
Elf32_Off    p_offset;        /* Segment file offset */
Elf32_Addr    p_vaddr;        /* Segment virtual address */
Elf32_Addr    p_paddr;        /* Segment physical address */
Elf32_Word    p_filesz;        /* Segment size in file */
Elf32_Word    p_memsz;        /* Segment size in memory */
Elf32_Word    p_flags;        /* Segment flags */
Elf32_Word    p_align;        /* Segment alignment */
} Elf32_Phdr;
我们来计算一下这个结构的大小, sizeof(Elf32_Phdr)=0x20, 从前面的elf header信息我们也同样可以知道这个长度是0x20, elf文件中一共有6个这样的Phdr, 最开始的一个Phdr在文件的0x34偏移处. 我们可以这样读取:
lseek(FP,0x34,0);
fread(buffer,6,0x20,FP);
将所有的Phdr都读入到buffer里面.
我们分析其中的一个结构:
[netconf@linux1 elf]$ hexdump -s 52 -n 32 -C elf8
00000034 06 00 00 00 34 00 00 00 34 80 04 08 34 80 04 08 |....4...4...4...|
00000044 c0 00 00 00 c0 00 00 00 05 00 00 00 04 00 00 00 |?..?..........|
按结构进行分析:
    p_type: 4字节: 06 00 00 00, 表示该段是PT_PHDR类型(自己的入口)
p_offset: 4字节: 34 00 00 00 在文件中的偏移量.
p_vaddr: 4字节: 34 80 04 08 虚拟地址:0x08048034
p_paddr: 4字节: 34 80 04 08 物理地址: 0x08048034
p_filesz: 4字节: c0 00 00 00 段的大小:0xc0
p_memsz: 4字节: c0 00 00 00 在内存中的大小: 0xc0
p_flags: 4字节: 05 00 00 00 段标记
p_align: 4字节: 04 00 00 00
Phdr结构大概和上面差不多, 每一个结构都定义了该elf文件的特性以及每个segment的属性,大小等.
我们来看一看PT_INTERP段的信息:
[netconf@linux1 elf]$ hexdump -s 84 -n 32 -C elf8
00000054 03 00 00 00 f4 00 00 00 f4 80 04 08 f4 80 04 08 |....?..?..?..|
00000064 13 00 00 00 13 00 00 00 04 00 00 00 01 00 00 00 |................|
这个segment指出了程序依赖解释器的路径/文件名, 定义在0xf4的偏移量处, 大小是0x13字节.
[netconf@linux1 elf]$ hexdump -s 0xf4 -n 19 -C elf8
000000f4 2f 6c 69 62 2f 6c 64 2d 6c 69 6e 75 78 2e 73 6f |/lib/ld-linux.so|
00000104 2e 32 00 |.2.|
该elf文件依赖/lib/ld-linux.so.2来解释.
 
我们可以用readelf来检测看看:
[netconf@linux1 elf]$ readelf -l elf8
Elf file type is EXEC (Executable file)
Entry point 0x8048278
There are 6 program headers, starting at offset 52
Program Headers:
Type   Offset     VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
PHDR   0x000034   0x08048034 0x08048034 0x000c0 0x000c0 R E 0x4
INTERP 0x0000f4   0x080480f4 0x080480f4 0x00013 0x00013 R   0x1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD   0x000000   0x08048000 0x08048000 0x00454 0x00454 R E 0x1000
LOAD   0x000454   0x08049454 0x08049454 0x00104 0x00108 RW 0x1000
DYNAMIC 0x000464  0x08049464 0x08049464 0x000c8 0x000c8 RW 0x4
NOTE   0x000108   0x08048108 0x08048108 0x00020 0x00020 R  0x4
所有的代码都是从0X8048000处开始
ELF_header占52个字节(0x34)
PHDR表示可执行文件中除ELF_header开始的地方。因为ELF_header占52个字节(0x34),所以从0x08048034开始。
INTERP表示从0x080480f4开始读取0x00013个字节为程序解释器即/lib/ld-linux.so.2
两个LOAD,第一个为代码(因为是RE),从VMA可以看出是从0X8048000开始的,即整个可执行文件。
第二个是BSS和DATA段等 是接着代码段之后的。0x08049454=0x08048000+0x00454
DYNAMIC是分配的动态空间,接在BSS和data后面。
0x8048034后面的(即prog table表后面的section)段的详细分配请用 objdump -h elf8查看。   3. section
同样的, 我们先给出section的结构:
typedef struct
{
Elf32_Word    sh_name;        /* 在section string table中的索引 */
Elf32_Word    sh_type;        /* Section type */
Elf32_Word    sh_flags;        /* Section flags */
Elf32_Addr    sh_addr;        /* Section virtual addr at execution */
Elf32_Off    sh_offset;        /* Section file offset */
Elf32_Word    sh_size;        /* Section size in bytes */
Elf32_Word    sh_link;        /* Link to another section */
Elf32_Word    sh_info;        /* Additional section information */
Elf32_Word    sh_addralign;        /* Section alignment */
Elf32_Word    sh_entsize;        /* Entry size if section holds table */
} Elf32_Shdr;

计算一下大小是sizeof(Elf32_Shdr)=0x28 和前面elf header的e_shentsize大小刚好一样.
从elf header的e_shoff 值我们可以得到第一个section在文件中的位置/偏移量0x2178,
从e_shnum可以知道一共有0x22个section.用C语言的算法可以这样表示:
fseek(FP, 0x2178, 0);
fread(buffer,0x22,0x28,FP);
就可以把所有的section读入到buffer里面.
我们来对照文件结构看看一个section的含义(第4个section(注意:从第0个开始)在文件中的的偏移量应该是: 0x2178+4*0x28=0x2218,我们以第4个section为例子):
[netconf@linux1 elf]$ hexdump -s 0x2218 -n 40 -C elf8
00002218 37 00 00 00 0b 00 00 00 02 00 00 00 50 81 04 08 |7...........P...|
00002228 50 01 00 00 50 00 00 00 05 00 00 00 01 00 00 00 |P...P...........|
00002238 04 00 00 00 10 00 00 00 |........|
其中:
sh_name: 4字节: 37 00 00 00 表示该section的名称在section string table中的索引.
sh_type: 4字节: 0b 00 00 00 表示该section的类型, 从elf.h中可以看到定义0x0b=11的是:SHT_DYNSYM     11        /* Dynamic linker symbol table */ 表示是一个动态连接符号表.
sh_flags: 4字节: 02 00 00 00 表示该section的类型.0x02是一个ALLOC型的section.
sh_addr: 4字节: 50 81 04 08 执行的时候, 该section的虚拟地址0x08048150
sh_offset: 4字节: 50 01 00 00 表示该section内容在文件的位置/偏移量0x150
sh_size: 4字节: 50 00 00 00 section大小0x50
sh_link: 4字节 01 00 00 00 连接到其他section
sh_info: 4字节: 04 00 00 00 section的其他信息
sh_addralign: 4字节: 04 00 00 00 section的对齐调整值
sh_entsize: 4字节: 10 00 00 00 如果该section定义的是一个表,那么这个值表示该表里面每个结构的大小.

注意: sh_link和sh_info的值随着sh_type不同有不同的含义.
sh_type sh_link sh_info
======= ======= =======
SHT_DYNAMIC The section header index of 0
the string table used by
entries in the section.
SHT_HASH The section header index of 0
the symbol table to which the
hash table applies.
SHT_REL, The section header index of The section header index of
SHT_RELA the associated symbol table. the section to which the
relocation applies.
SHT_SYMTAB, The section header index of One greater than the symbol
SHT_DYNSYM the associated string table. table index of the last local
symbol (binding STB_LOCAL).
other SHN_UNDEF 0

section表是elf文件中最重要的一环, 基本上所有的东东都要查找它.
比如.dynsym, .symtab, .got, .bss等等. 后面我们就要提到的symbol表也要从这里获取相关信息.
我们来看一下elf header里面提到的e_shstrndx, 这个值定义了section string table的位置在第几个section里面的, 这里我们可以计算一下它的位置:
e_shoff+e_shstrndx* e_shentsize (起始位置+序号*每个的大小)
=0x2178+31*0x28= 0x2650
我们看看这个section的内容:
[netconf@linux1 elf]$ hexdump -s 0x2650 -n 40 -C elf8
00002650 11 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 |................|
00002660 4a 20 00 00 2b 01 00 00 00 00 00 00 00 00 00 00 |J ..+...........|
00002670 01 00 00 00 00 00 00 00 |........|
这里面可以给我们的信息是:
sh_name=0x11, sh_type=0x03,sh_offset=0x204a, sh_size=0x12b
sh_name目前没用.
Sh_type:0x03表示: SHT_STRTAB     3        /* String table */
Ok,我们找到了string table, 让我们看看string table里面有什么吧:
[netconf@linux1 elf]$ hexdump -s 0x204a -n 299 -C elf8
0000204a 00 2e 73 79 6d 74 61 62 00 2e 73 74 72 74 61 62 |..symtab..strtab|
0000205a 00 2e 73 68 73 74 72 74 61 62 00 2e 69 6e 74 65 |..shstrtab..inte|
0000206a 72 70 00 2e 6e 6f 74 65 2e 41 42 49 2d 74 61 67 |rp..note.ABI-tag|
0000207a 00 2e 68 61 73 68 00 2e 64 79 6e 73 79 6d 00 2e |..hash..dynsym..|
0000208a 64 79 6e 73 74 72 00 2e 67 6e 75 2e 76 65 72 73 |dynstr..gnu.vers|
0000209a 69 6f 6e 00 2e 67 6e 75 2e 76 65 72 73 69 6f 6e |ion..gnu.version|
000020aa 5f 72 00 2e 72 65 6c 2e 64 79 6e 00 2e 72 65 6c |_r..rel.dyn..rel|
000020ba 2e 70 6c 74 00 2e 69 6e 69 74 00 2e 74 65 78 74 |.plt..init..text|
000020ca 00 2e 66 69 6e 69 00 2e 72 6f 64 61 74 61 00 2e |..fini..rodata..|
000020da 64 61 74 61 00 2e 65 68 5f 66 72 61 6d 65 00 2e |data..eh_frame..|
000020ea 64 79 6e 61 6d 69 63 00 2e 63 74 6f 72 73 00 2e |dynamic..ctors..|
000020fa 64 74 6f 72 73 00 2e 6a 63 72 00 2e 67 6f 74 00 |dtors..jcr..got.|
0000210a 2e 62 73 73 00 2e 63 6f 6d 6d 65 6e 74 00 2e 64 |.bss..comment..d|
0000211a 65 62 75 67 5f 61 72 61 6e 67 65 73 00 2e 64 65 |ebug_aranges..de|
0000212a 62 75 67 5f 70 75 62 6e 61 6d 65 73 00 2e 64 65 |bug_pubnames..de|
0000213a 62 75 67 5f 69 6e 66 6f 00 2e 64 65 62 75 67 5f |bug_info..debug_|
0000214a 61 62 62 72 65 76 00 2e 64 65 62 75 67 5f 6c 69 |abbrev..debug_li|
0000215a 6e 65 00 2e 64 65 62 75 67 5f 66 72 61 6d 65 00 |ne..debug_frame.|
0000216a 2e 64 65 62 75 67 5f 73 74 72 00 |.debug_str.|

这里就是我们要找的东西了. 通过sh_name的值, 可以在这个string table里面找到自己对应的section name.
比如我们刚才看的第4个section的sh_name是0x37,我们找string table里面偏移量是0x37的内容(到0x00结束).
我们来看看:字符串起始位置0x204a+0x37=0x2081
就先读出10字节内容吧:
[netconf@linux1 elf]$ hexdump -s 0x2081 -n 10 -C elf8
00002081 2e 64 79 6e 73 79 6d 00 2e 64 |.dynsym..d|
0x00前面就是.dynsym了, 这个就是我们要找的section name了.
通过前面的过程我们就可以找到一一对应的section名字, 来看看readelf的结果:
[netconf@linux1 elf]$ readelf -S elf8
There are 34 section headers, starting at offset 0x2178:

Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 080480f4 0000f4 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048108 000108 000020 00 A 0 0 4
[ 3] .hash HASH 08048128 000128 000028 04 A 4 0 4
[ 4] .dynsym DYNSYM 08048150 000150 000050 10 A 5 1 4
[ 5] .dynstr STRTAB 080481a0 0001a0 00004c 00 A 0 0 1
[ 6] .gnu.version VERSYM 080481ec 0001ec 00000a 02 A 4 0 2
[ 7] .gnu.version_r VERNEED 080481f8 0001f8 000020 00 A 5 1 4
[ 8] .rel.dyn REL 08048218 000218 000008 08 A 4 0 4
[ 9] .rel.plt REL 08048220 000220 000010 08 A 4 b 4
[10] .init PROGBITS 08048230 000230 000018 00 AX 0 0 4
[11] .plt PROGBITS 08048248 000248 000030 04 AX 0 0 4
[12] .text PROGBITS 08048278 000278 000170 00 AX 0 0 4
[13] .fini PROGBITS 080483e8 0003e8 00001c 00 AX 0 0 4
[14] .rodata PROGBITS 08048404 000404 000050 00 A 0 0 4
[15] .data PROGBITS 08049454 000454 00000c 00 WA 0 0 4
[16] .eh_frame PROGBITS 08049460 000460 000004 00 WA 0 0 4
[17] .dynamic DYNAMIC 08049464 000464 0000c8 08 WA 5 0 4
[18] .ctors PROGBITS 0804952c 00052c 000008 00 WA 0 0 4
[19] .dtors PROGBITS 08049534 000534 000008 00 WA 0 0 4
[20] .jcr PROGBITS 0804953c 00053c 000004 00 WA 0 0 4
[21] .got PROGBITS 08049540 000540 000018 04 WA 0 0 4
[22] .bss NOBITS 08049558 000558 000004 00 WA 0 0 4
[23] .comment PROGBITS 00000000 000558 000132 00 0 0 1
[24] .debug_aranges PROGBITS 00000000 000690 000058 00 0 0 8
[25] .debug_pubnames PROGBITS 00000000 0006e8 000025 00 0 0 1
[26] .debug_info PROGBITS 00000000 00070d 000c85 00 0 0 1
[27] .debug_abbrev PROGBITS 00000000 001392 000127 00 0 0 1
[28] .debug_line PROGBITS 00000000 0014b9 0001f2 00 0 0 1
[29] .debug_frame PROGBITS 00000000 0016ac 000014 00 0 0 4
[30] .debug_str PROGBITS 00000000 0016c0 00098a 01 MS 0 0 1
[31] .shstrtab STRTAB 00000000 00204a 00012b 00 0 0 1
[32] .symtab SYMTAB 00000000 0026c8 0004c0 10 33 37 4
[33] .strtab STRTAB 00000000 002b88 0001dd 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
[netconf@linux1 elf]$

[32] .symtab SYMTAB 00000000 0026c8 0004c0 10 33 37 4这个section仍然是0X28大小,他指向section段中具体数据段的位置。从这看出一共是0x4c0大小。由下面Symbol结构可以知道每个符号信息是0x10,这样说明该段里面一共有76个重新定位/加载的符号。

4.Symbol
Symbol结构如下所示:
/* Symbol table entry. */

typedef struct
{
Elf32_Word    st_name;        /* Symbol name (string tbl index) */
Elf32_Addr    st_value;        /* Symbol value */
Elf32_Word    st_size;        /* Symbol size */
unsigned char    st_info;        /* Symbol type and binding */
unsigned char    st_other;        /* Symbol visibility */
Elf32_Section    st_shndx;        /* Section index */
} Elf32_Sym;
Symbol结构大小是: sizeof(Elf32_Sym)=0x10.
一个elf文件通常包含两个symbol表, 一个是.dynsym, 一个是.symtab, 前者表示程序运行时候需要重新定位/加载的符号(比如函数等), 后一个表示系统所有的符号列表。

St_name是符号的名称的index(详见下文)
St_value是该符号在内存中的地址(详见下文)
st_size是该符号的大小,以字节为单位
BIND说明符号是内部符号还是外部符号
TYPE说明符号的类型,是函数,还是变量,等等
st_other恒为0,保留字节
st_shndx是符号所在的section的index(详见下文)

符号的名称,是由st_name和符号名表决定。St_name是一个指向符号名表的索引值,通过“符号名表基地址”+st_name就可以得到符号名的地址。
之所以采用这种方法,是为了处理“变长”的符号名。
我们知道,不同符号名的长度也会有很大的不同。例如int I;的符号名只有1个字符。而int mMyLinkTypeofDoubleLoaderProgram却有34个字符。如果采用数组的方式,会浪费大量的空间,malloc出来的动态内存又不适合硬盘存储。
这是一个非常值得学习的技巧――在涉及硬盘存储时,可以采用索引+字符串表的形式存储变长的字符串(或者其它变长信息)。

符号的值(也就是符号在内存中的地址,我们要计算的东西)是由st_value和st_shndx决定的。
在relocatable文件中,st_value是符号相对于某个section起始地址的偏移,这个section是由st_shndex指定的(COMMON类型的section除外,它很少会被用到)。
在executable和shared object文件中,st_value包含一个虚拟地址。这个地址是和ELF文件的预定装载地址联系在一起的。在进行动态链接时,我们需要计算“当前装载地址”与“预定装载地址”之间的差。

该结构位置从上面的section表里面可以看出是: 0x26c8, 就是从文件0x26c8偏移初开始的.
我们读第68个结构看看(0x26c8+68*0x10=0x2b08):
[netconf@linux1 elf]$ hexdump -s 0x2b08 -n 16 -C ./elf8
00002b08 77 01 00 00 8e 83 04 08 1d 00 00 00 12 00 0c 00 |w...............|
00002b18
结合struct的定义,这样每个字节的内容和含义都可以分析出来.
st_name: 77 01 00 00 : 在symbol string table中的偏移量0x177
st_value: 8e 83 04 08 : 加载地址0x80438e8
st_size: 1d 00 00 00 : symbol大小
st_shndx: 0c 00 :如果st_shndx=SH_UNDEF, 那么表示该symbol不属于任何一个section, 需要重新定位.” 当链接器将该目标文件和另一个定义该符号的文件相装配的时候,该文件内对该符号的引用将链接到当前实际的定义” (from alert7)
像第68个结构的st_shndx=12, 表示属于第12个section, 从前面列表可以知道, 是定义在.text段.

当编译一个源文件生成目标文件时,会在目标文件中生成符号表和重定位表。

 

符号表包含在文件中定义的全局符号以及在文件中引用的外部符号(外部函数或变量)。

 

重定位表告诉链接器在哪些位置要进行重定位操作。

 

编译生成的目标文件在文件的开始处会有一个elf头,描绘了整个文件的组织结构。它还包括很多节(section)。这些节有的是系统定义好的,有些是用户在文件在通过.section命令自定义的,链接器会将各输入目标文件中的相同的节合并。

 

链接器对编译生成的目标文件进行链接时,首先进行符号解析,找出外部符号在哪定义。如果外部符号在一个静态库中定义,则直接将对应的定义代码复制到最终生成的目标文件中。接着链接器进行符号重定位。编译器在生成目标文件时,通常使用从0开始的相对地址,而在链接过程中,链接器从一个指定的地址开始,根据输入目标文件的顺序,以段(segment)为单位将它们拼装起来。其中每个段可以包括很多个节(section)。除了目标文件的拼装,重定位过程中还完成了下面两个任务:一是生成最终的符号表,二是对代码段中的某些位置进行修改,要修改的位置由编译器生成的重定位表指出。

 

链接过程中还会生成两个表:got表和plt表。

 

got表中每一项都是本运行模块要引用的全局变量或函数的地址,可以用got表来间接引用全局变量。函数也可以把got表的首地址作为一个基准,用相对该基准偏移量来引用静态函数。由于动态链接器(ld-linux.so)不会把运行模块加载到固定地址,在不同进程的地址空间中各运行模块的绝对地址、相对地址都不同。这种不同反映到got表上,京是每个进程的每个运行模块都有独立的got表,所以进程间不能共享got表。

 

plt表中第一项都是一小段代码,对应于本运行模块要引用的一个全局函数。当链接器发现某个符号引用是共享目标文件中的一个函数时,就在pltk 创建一个入口。

 

链接生成的目标文件在文件开头也有一个elf头号,描绘了整个文件的组织结构,这个文件中会有多个段(segment),每个段都由相应的节(section)拼装而成。

 

对由链接器链接生成的可执行目标文件进行加载运行时,内核首先读取elf头。根据头部数据指示分别读入各种数据结构,找出可加载的段闭并调用mmap()函数将其加载到内存。内核找到标记为PT_INTERP的段,这个段对应着动态链接器的名称,然后加载动态链接器。linux中通常是/lib/ld-linux.so.2.接着内核将控制权交给动态链接器。动态链接器检查程序对外部文件(共享库)的依赖性,并在需要时对其进行加载。之后动态链接器开始对程序中的外部引用进行重定位,即告诉程序其引用的外部变量/函数的地址。R_386_GLOB_DAT类型的入口涉及到got表。R_3862_JMP_SLOT类型的入口涉及到plt表。动态链接还有一个延迟(lazy)特性,即真正引用时才进行重定位(环境变量LD_BIND_NOW为空值NULL时)。接下来动态链接器执行elf文件中标记为.init节的代码,进行程序运行的初始化。最后动态链接器把控制权交给程序, 从elf头中定义的入口处开始执行程序。

相关阅读 更多 +
排行榜 更多 +
摧毁大厦游戏

摧毁大厦游戏

飞行射击 下载
合并动物城手游版

合并动物城手游版

休闲益智 下载
哈士奇大冒险

哈士奇大冒险

休闲益智 下载