跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第一节 ELF文件
 
 

        ELF的全称为Executable and Linkable Format即:可执行与可链接格式。我们知道,使用C语言编译的可执行程序中常量和全局变量通常存放在数据段中,程序在使用这些常量和变量时,需要通过使用它们的地址来访问他们的内容,例如:

int global_var = 0x11111111;
int main(int argc, char **args)
{
        global_var = 0x22222222;
        return 0;
}

        对上面程序反汇编:

00000000 <.text>:
  ...
  4c:        55                           push   %ebp
  4d:        89 e5                        mov    %esp,%ebp
  4f:        51                           push   %ecx
  50:        83 ec 14                     sub    $0x14,%esp
  53:        c7 05 00 20 00 00 22         movl   $0x22222222,0x2000

00002000 <.data>:
    2000:        11 11                    adc    %edx,(%ecx)
    2002:        11 11                    adc    %edx,(%ecx)

        可以看到,全局变量global_var的地址为0x2000,为其赋值也是操作的这个地址movl $0x22222222, 0x2000。于是问题出现了,这此地址都是相对于程序的代码段地址、数据段地址而言。当程序被读入内存准备运行时,可执行程序被内核程序载入到一个物理地址中,那么程序内部的这些相对地址也就失去了本来的意义。这些地址在物理内存中是错误的地址。解决的办法是将这些全局的或是常量所在的地址通过一些特殊的标记记录下来,当操作系统载入程序时,将这些地址转为一个实际可用的物理地址。这一过程被称为重定位。在Linux下,使用gcc编译的C程序都是ELF文件格式,它的格式定义如下:

        ELF文件头:

字段

大小

描述

e_ident[0]

1字节

0x7f

固定的开始内容

e_ident[1]

1字节

0x45

字符E

e_ident[2]

1字节

0x4c

字符L

e_ident[3]

1字节

0x46

字符F

e_ident[4]

1字节

0x1

文件类别:

0:非法文件

1:32位目标文件

2:64位目录文件

e_ident[5]

1字节

0x1

编码方式:

0:非法编码

1:小尾编码方式

2:大尾编码方式

e_ident[6]

1字节

0x1

版本号

e_ident[7 ~ 15]

9字节

0x0

保留

e_type

2字节

0x1

文件类型:

0:未知类型

1:可重定位文件

2:可执行文件

3:动态链接库

4:CORE文件

0xff00:特定处理器文件扩展下边界

0xffff:特定处理器文件扩展上边界

e_machine

2字节

0x3

体系结构类型:Intel 80386

e_version

4字节

0x1

当前版本号

e_entry

4字节

0x0

程序入口地址

e_phoff

4字节

0x0

程序头偏移地址

e_shoff

4字节

0xfc

节区头偏移地址

e_flags

4字节

0x0

标志

e_ehsize

2字节

0x34

elf头大小

e_phentsize

2字节

0x0

程序头大小

e_phnum

2字节

0x0

程序段数量

e_shentsize

2字节

0x28

节区头大小

e_shnum

2字节

0x1b

节区头数量

e_shstrndx

2字节

0x8

节区索引


 

        程序头:

字段

大小

描述

p_type

4字节

0x000034

类型

p_offset

4字节

0x08048034

程序偏移地址

p_vaddr

4字节

0x08048034

段加载后在进程空间中占用的内存起始地址

p_paddr

4字节

0x08048034

在支持paging的OS中该字段被忽略

p_filesz

4字节

0x00120

该段在文件中占用的字节大小

p_memsz

4字节

0x00120

该段在内存中占用的字节大小

p_flags

4字节

0x0

标志

p_align

4字节

0x4

对齐

 

        节区头:

字段

大小

描述

sh_name

4字节

0x0

节区名(实际是在shstrtab中的索引号)

sh_type

4字节

0x0

节区类型

sh_flags

4字节

0x0

标志

sh_addr

4字节

0x26

相对可执行地址

sh_offset

4字节

0x48

偏移地址

sh_size

4字节

0x28

节区大小

sh_link

4字节

0x0

其它节区索引

sh_info

4字节

0x0

扩展信息

sh_addralign

4字节

0x0

节区对齐

sh_entsize

4字节

0x0

入口表大小

 

        符号表:

字段

大小

描述

st_name

4字节

0x0

在字符串中的索引号

st_value

4字节

0x00000008

值,用于重定位

st_size

4字节

0x4

大小

st_info

1字节

0x0

符号信息

st_other

1字节

0x0

其它信息

st_shndx

2字节

0x6

节区索引

 

        重定位:

字段

大小

描述

r_offset

4字节

0x0000001b

需要重定位的偏移地址

r_info

4字节

0x00000b01

(符号 << 8) | 重定位类型

0:R_386_NONE 计算方式:无

1:R_386_32 计算方式:S + A

2:R_386_PC32 计算方式:S + A - P

 

        在Linux中可以使用readelf命令来查看elf文件中的相关信息。首先编写一个C程序:

start_main.c
void start_main()
{
        main(1, 0);
        for(;;){}
}

system.c
unsigned int g1 = 0x1111;
unsigned int g2 = 0x2222;
int *p1 = &g1;
int *p2 = &g2;

int main(int argc, char **args)
{
        int g3 = 0x3333;
        int g4 = 0x4444;
        int *p3 = &g1;
        int *p4 = &g2;
        p1 = &g3;
        p2 = &g4;
        __asm__ volatile("int        $0x80" :: "a"(p1));
        __asm__ volatile("int        $0x80" :: "a"(p2));
        __asm__ volatile("int        $0x80" :: "a"(p3));
        __asm__ volatile("int        $0x80" :: "a"(p4));
        for (;;)
        {
        }
        return 0;
}

        使用gcc命令编译start_main.c和system.c这里我们使用gcc时要使用它的-c参数,让gcc为我们编译源代码,但并不链接重定位成可执行文件。然后使用ld命令将start_main.o和system.o链接在一起,让start_main()函数作为程序的入口函数,即:让start_main()函数处于.text节区的起始地址,也就是0x0:

gcc -m32 -c start_main.c -o start_main.o
gcc -m32 -c system.c -o system.o
ld  -m elf_i386 -Ttext 0x0 -e start_main -r start_main.o system.o -o system.ecc

        生成了system.ecc文件之后我们就可以通过使用readelf命令来查看它内部的详细信息,并可以找到其所有需要重定位的地址和内容。

        elf文件头:

readelf system.ecc -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:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          836 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         13
  Section header string table index: 10
There are 13 section headers, starting at offset 0x344:

        节区信息:

readelf system.ecc -S
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000063 00  AX  0   0  1
  [ 2] .rel.text         REL             00000000 0002ec 000038 08   I 11   1  4
  [ 3] .eh_frame         PROGBITS        00000000 000098 000068 00   A  0   0  4
  [ 4] .rel.eh_frame     REL             00000000 000324 000010 08   I 11   3  4
  [ 5] .data             PROGBITS        00000000 000100 000010 00  WA  0   0  4
  [ 6] .rel.data         REL             00000000 000334 000010 08   I 11   5  4
  [ 7] .bss              NOBITS          00000000 000110 000000 00  WA  0   0  1
  [ 8] .comment          PROGBITS        00000000 000110 00005a 01  MS  0   0  1
  [ 9] .note.GNU-stack   PROGBITS        00000000 00016a 000000 00      0   0  1
  [10] .shstrtab         STRTAB          00000000 00016a 00005b 00      0   0  1
  [11] .symtab           SYMTAB          00000000 0001c8 0000f0 10     12   9  4
  [12] .strtab           STRTAB          00000000 0002b8 000033 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

        符号表:

readelf system.ecc -s
Symbol table '.symtab' contains 15 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000     0 SECTION LOCAL  DEFAULT    3 
     3: 00000000     0 SECTION LOCAL  DEFAULT    5 
     4: 00000000     0 SECTION LOCAL  DEFAULT    7 
     5: 00000000     0 SECTION LOCAL  DEFAULT    8 
     6: 00000000     0 SECTION LOCAL  DEFAULT    9 
     7: 00000000     0 FILE    LOCAL  DEFAULT  ABS start_main.c
     8: 00000000     0 FILE    LOCAL  DEFAULT  ABS system.c
     9: 0000000c     4 OBJECT  GLOBAL DEFAULT    5 p2
    10: 00000000     4 OBJECT  GLOBAL DEFAULT    5 g1
    11: 00000008     4 OBJECT  GLOBAL DEFAULT    5 p1
    12: 00000004     4 OBJECT  GLOBAL DEFAULT    5 g2
    13: 00000000    23 FUNC    GLOBAL DEFAULT    1 start_main
    14: 00000017    76 FUNC    GLOBAL DEFAULT    1 main

        重定位内容:

readelf system.ecc -r
Relocation section '.rel.text' at offset 0x2ec contains 7 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0000000e  00000e02 R_386_PC32        00000017   main
0000002e  00000a01 R_386_32          00000000   g1
00000035  00000c01 R_386_32          00000004   g2
0000003d  00000b01 R_386_32          00000008   p1
00000045  00000901 R_386_32          0000000c   p2
0000004a  00000b01 R_386_32          00000008   p1
00000051  00000901 R_386_32          0000000c   p2

Relocation section '.rel.eh_frame' at offset 0x324 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000020  00000102 R_386_PC32        00000000   .text
00000054  00000102 R_386_PC32        00000000   .text

Relocation section '.rel.data' at offset 0x334 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000008  00000a01 R_386_32          00000000   g1
0000000c  00000c01 R_386_32          00000004   g2

        可以看到在elf文件头中的type字段中的值为1(Relocatable file),也就是说这只是一个可重定位文件,它并不是一个可执行的文件。如果直接生成在Linux下可执行的文件,这些重定位地址将会被替换成一系列的虚拟地址,比如:0x80482f0、0x08048168、0x0804a028等等。另外,gcc还会给这个可执行文件加入一些系统函数,用于处理程序执行前的预处理和调用main函数,比如:_init函数__gmon_start__函数、__libc_start_main函数以及_start函数等等。这些在我们的操作系统中是无用的,我们需要将一个可重定位文件载入到内存中,并读取它的重定位信息。通过重定位信息修改程序中的相关内容,最后执行程序。所以内核程序载入一个可重定位文件就可以让其运行。下面来看一下elf相关的数据结构,与前面所述的elf格式一致:

//ELF头前16字节固定长
#define EI_NIDENT 16
//ELF文件头
typedef struct elf32_hdr
{
        //固定的开始内容
        u8 e_ident[EI_NIDENT];
        //文件类型
        u16 e_type;
        //体系结构类型
        u16 e_machine;
        //当前版本号
        u32 e_version;
        //程序入口地址
        u32 e_entry;
        //程序头偏移地址
        u32 e_phoff;
        //节区头偏移地址
        u32 e_shoff;
        //标志
        u32 e_flags;
        //elf头大小
        u16 e_ehsize;
        //程序头大小
        u16 e_phentsize;
        //程序段数量
        u16 e_phnum;
        //节区头大小
        u16 e_shentsize;
        //节区头数量
        u16 e_shnum;
        //节区索引
        u16 e_shstrndx;
} Elf32_Ehdr;

//程序头
typedef struct elf32_phdr
{
        //类型
        u32 p_type;
        //程序偏移地址
        u32 p_offset;
        //段加载后在进程空间中占用的内存起始地址
        u32 p_vaddr;
        //在支持paging的OS中该字段被忽略
        u32 p_paddr;
        //该段在文件中占用的字节大小
        u32 p_filesz;
        //该段在内存中占用的字节大小
        u32 p_memsz;
        //标志
        u32 p_flags;
        //对齐
        u32 p_align;
} Elf32_Phdr;

//节区头
typedef struct elf32_shdr
{
        //节区名(实际是在shstrtab中的索引号)
        u32 sh_name;
        //节区类型
        u32 sh_type;
        //标志
        u32 sh_flags;
        //相对可执行地址
        u32 sh_addr;
        //偏移地址
        u32 sh_offset;
        //节区大小
        u32 sh_size;
        //其它节区索引
        u32 sh_link;
        //扩展信息
        u32 sh_info;
        //节区对齐
        u32 sh_addralign;
        //入口表大小
        u32 sh_entsize;
} Elf32_Shdr;

//符号表
typedef struct elf32_sym
{
        //在字符串中的索引号
        u32 st_name;
        //值,用于重定位
        u32 st_value;
        //大小
        u32 st_size;
        //符号信息
        u8 st_info;
        //其它信息
        u8 st_other;
        //节区索引
        u16 st_shndx;
} Elf32_Sym;

//重定位
typedef struct elf32_rel
{
        //需要重定位的偏移地址
        u32 r_offset;
        //其值为:(符号 << 8) | 重定位类型
        u32 r_info;
} Elf32_Rel;

 

    返回首页    返回顶部
  看不清?点击刷新

 

  Copyright © 2015-2023 问渠网 辽ICP备15013245号