linux可执行文件格式

1. 可执行文件的任务:

  1. 可执 行文件的创建:
    • 编译(compile): 源程序文件被编译成目标文件,
    • 连接(link): 多个目标文件 被连接成一个最终的可执行文件,
  2. 可执行文件的运行: 可执行文件被加载(load)到内存中执行。

2. a.out

assembler and link editor output-汇编器和链接编辑器的输出格式(简述)
a.out 是一种古老的文件格式,简单,紧凑, 但其精华犹在,略作研究

标准 a.out 文件包含 7 个 块,1个头部+6个段
格式如下:

a.out 文件格式
exec header(执行头部,也可理解为文件头部)
text segment(文本段)
data segment(数据段)
text relocations(文本重定位段)
data relocations(数据重定位段)
symbol table(符号表)
string table(字符串表)

头部的数据结构:

struct exec {
unsigned long   a_midmag;    /* 魔数和其它信息 */
unsigned long   a_text;      /* 文本段的长度 */
unsigned long   a_data;      /* 数据段的长度 */
unsigned long   a_bss;       /* BSS段的长度 */
unsigned long   a_syms;      /* 符号表的长度 */
unsigned long   a_entry;     /* 程序进入点 */
unsigned long   a_trsize;    /* 文本重定位表的长度 */
unsigned long   a_drsize;    /* 数据重定位表的长度 */
};

头部中有标识本文件类型为a.out 的魔数, 程序入口点. 占8byte
还有6个段的长度,24byte, 共32个bytes.
这6个段的长度,是文件体中各个段的长度, 没有string table表长度,因为它在最后,但添加了BSS 段长度.

我们看到, a.out 格式非常紧凑, 头部只记录了段的长度, 没有记录段的地址. 所以
它要求各个段的排序位置是固定的, 各段的开始地址通过前面段长度累加得到.

由a.out 可以看出可执行文件的基本要素:
1. 代码和数据是必要的。
2. 因为文件会引用外部文件定义的符号(变量和函数),因此重定位信息和符号信息也是需要的
3. ascii 字符串, 把它作为一个单独的表放到最后.

举例: hello.c 程序

printf("hello world\n");
printf 就是一个外部符号,
"hello world" 是只读数据,
这个格式的文件太古老了, 已经难以见到它的身影, 就让它过去吧.

3. ELF

(Executable and Linking Format 可执行和链接格式)

网上内容不少,但我要用最简单的语言和例子来描述清楚问题.
elf文件格式由elf头, program 表头+program, setion 表头 + section 來构成,
但一个实际的elf文件可能并不包含以上所有项,其中elf头位置及大小是固定的.
section 体与 program体可以有交叉.

1. elf文件头

这个文件是对elf文件整体信息的描述,在32位系统下是56的字节,在64位系统下是64个字节。

对于可执行文件来说,文件头的完整描述参考后面描述.
文件头包含的以下信息与进程启动相关

e_entry     程序入口地址
e_phoff     program表偏移
e_phnum     program 数量

2. program 表

表中每一项叫program 头

typedef struct
{
Elf64_Word p_type;
Elf64_Word p_flags; /* 权限: 6表示可读写,5表示可读可执行*/
Elf64_Off p_offset; /* 段在文件中的偏移*/
Elf64_Addr p_vaddr; /* 虚拟内存地址,??*/
Elf64_Addr p_paddr; /* 物理内存地址,??*/
Elf64_Xword p_filesz; /* file size ,段在文件中的长度*/
Elf64_Xword p_memsz; /* memory size 在内存中的长度,一般和p_filesz的值一样*/
Elf64_Xword p_align; /* 段对齐*/

} Elf64_Phdr;

3. 节表及节头

我这里就忽略了, 它们与运行无关,只与链接有关.

4. 汇编级别的elf64 实例,

用汇编,因为生成的文件小

cat t1.s

.section .data
.global data_item
data_item:
.long 0x12345678,0x90abcdef

.section .text
.global _start
_start:
    mov $1,%eax
    mov $4,%ebx
    int $0x80

as t1.s -o t1.o   //编译及链接
ld t1.o -o t1
strip t1   //顺便剔除符号信息:
观察其16进制导出文件: xxd t1 
就看到其全貌了.

$ xxd t1
0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
0000010: 0200 3e00 0100 0000 b000 4000 0000 0000  ..>.......@.....
0000020: 4000 0000 0000 0000 e000 0000 0000 0000  @...............
0000030: 0000 0000 4000 3800 0200 4000 0400 0300  ....@.8...@.....
0000040: 0100 0000 0500 0000 0000 0000 0000 0000  ................
0000050: 0000 4000 0000 0000 0000 4000 0000 0000  ..@.......@.....
0000060: bc00 0000 0000 0000 bc00 0000 0000 0000  ................
0000070: 0000 2000 0000 0000 0100 0000 0600 0000  .. .............
0000080: bc00 0000 0000 0000 bc00 6000 0000 0000  ..........`.....
0000090: bc00 6000 0000 0000 0800 0000 0000 0000  ..`.............
00000a0: 0800 0000 0000 0000 0000 2000 0000 0000  .......... .....
00000b0: b801 0000 00bb 0400 0000 cd80 7856 3412  ............xV4.
00000c0: efcd ab90 002e 7368 7374 7274 6162 002e  ......shstrtab..
00000d0: 7465 7874 002e 6461 7461 0000 0000 0000  text..data......
00000e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000100: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000110: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000120: 0b00 0000 0100 0000 0600 0000 0000 0000  ................
0000130: b000 4000 0000 0000 b000 0000 0000 0000  ..@.............
0000140: 0c00 0000 0000 0000 0000 0000 0000 0000  ................
0000150: 0100 0000 0000 0000 0000 0000 0000 0000  ................
0000160: 1100 0000 0100 0000 0300 0000 0000 0000  ................
0000170: bc00 6000 0000 0000 bc00 0000 0000 0000  ..`.............
0000180: 0800 0000 0000 0000 0000 0000 0000 0000  ................
0000190: 0100 0000 0000 0000 0000 0000 0000 0000  ................
00001a0: 0100 0000 0300 0000 0000 0000 0000 0000  ................
00001b0: 0000 0000 0000 0000 c400 0000 0000 0000  ................
00001c0: 1700 0000 0000 0000 0000 0000 0000 0000  ................
00001d0: 0100 0000 0000 0000 0000 0000 0000 0000  ................

/usr/include/elf.h 中有定义

typedef struct
    {
      unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ 16 bytes
      Elf64_Half    e_type;         /* Object file type */  uint16 ->0x2 , 可执行
      Elf64_Half    e_machine;      /* Architecture */      uint16 ->0x3e, x86_64
      Elf64_Word    e_version;      /* Object file version */ uint32 ->0x1
      Elf64_Addr    e_entry;        /* Entry point virtual address */ uint64-> 0x4000b0
      Elf64_Off     e_phoff;        /* Program header table file offset */ uint64-> 0x40
      Elf64_Off     e_shoff;        /* Section header table file offset */ uint64-> 0xe0
      Elf64_Word    e_flags;        /* Processor-specific flags */ uint32-> 0
      Elf64_Half    e_ehsize;       /* ELF header size in bytes */ uint16-> 0x40
      Elf64_Half    e_phentsize;        /* Program header table entry size */ uint16-> 0x38
      Elf64_Half    e_phnum;        /* Program header table entry count */ uint16-> 0x2
      Elf64_Half    e_shentsize;        /* Section header table entry size */ uint16-> 0x40
      Elf64_Half    e_shnum;        /* Section header table entry count */ uint16->0x4
      Elf64_Half    e_shstrndx;     /* Section header string table index */ uint16->0x3
    } Elf64_Ehdr;

后面的注释是根据二进制文件手工添加的.

5.用工具解读elf:

  1. $ objdump -x t2
t1:     file format elf64-x86-64
t1
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x00000000004000b0

Program Header:
    LOAD off    0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
         filesz 0x00000000000000bc memsz 0x00000000000000bc flags r-x
    LOAD off    0x00000000000000bc vaddr 0x00000000006000bc paddr 0x00000000006000bc align 2**21
         filesz 0x0000000000000008 memsz 0x0000000000000008 flags rw-

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         0000000c  00000000004000b0  00000000004000b0  000000b0  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .data         00000008  00000000006000bc  00000000006000bc  000000bc  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
no symbols

objdump -> elf header显示比较差, section 才显示2个 , 也许它指的是section映射到segment吧

  1. $ readelf -e t1
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              EXEC (Executable file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x4000b0
      Start of program headers:          64 (bytes into file)
      Start of section headers:          224 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         2
      Size of section headers:           64 (bytes)
      Number of section headers:         4
      Section header string table index: 3

    Section Headers:
      [Nr] Name              Type             Address           Offset
           Size              EntSize          Flags  Link  Info  Align
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .text             PROGBITS         00000000004000b0  000000b0
           000000000000000c  0000000000000000  AX       0     0     1
      [ 2] .data             PROGBITS         00000000006000bc  000000bc
           0000000000000008  0000000000000000  WA       0     0     1
      [ 3] .shstrtab         STRTAB           0000000000000000  000000c4
           0000000000000017  0000000000000000           0     0     1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
      I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)

    Program Headers:
      Type           Offset             VirtAddr           PhysAddr
                     FileSiz            MemSiz              Flags  Align
      LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                     0x00000000000000bc 0x00000000000000bc  R E    200000
      LOAD           0x00000000000000bc 0x00000000006000bc 0x00000000006000bc
                     0x0000000000000008 0x0000000000000008  RW     200000

     Section to Segment mapping:
      Segment Sections...
       00     .text 
       01     .data 

readelf: 内容显示的比较清晰!

6. 下面继续完成手工阅读elf 文件.

  1. 程序头
    typedef struct
    {
      Elf64_Word    p_type;         /* Segment type */ 0x01
      Elf64_Word    p_flags;        /* Segment flags */ 0x05
      Elf64_Off p_offset;       /* Segment file offset */ 0x0
      Elf64_Addr    p_vaddr;        /* Segment virtual address */ 0x400000
      Elf64_Addr    p_paddr;        /* Segment physical address */0x400000
      Elf64_Xword   p_filesz;       /* Segment size in file */ 0xbc
      Elf64_Xword   p_memsz;        /* Segment size in memory */ 0xbc
      Elf64_Xword   p_align;        /* Segment alignment */ 0x200000
    } Elf64_Phdr;

每个program header 占用56 byte, 注释上标记了一个, 跟程序输出可以进行对照!
我们看到:
- 1个可读可运行的段,大小0xbc, 位于文件偏移0, 内存地址0x400000处
- 1个可读可写的段,大小8bytes, 位于文件偏移0xbc, 内存地址0x6000bc处

  1. 节头
    typedef struct
    {
      Elf64_Word    sh_name;        /* Section name (string tbl index) */ 0x0b, 0x11,0x01
      Elf64_Word    sh_type;        /* Section type */ 0x1,0x1,0x3
      Elf64_Xword   sh_flags;       /* Section flags */ 0x06,0x3,0x0
      Elf64_Addr    sh_addr;        /* Section virtual addr at execution */ 0x4000b0,0x6000bc,0x0
      Elf64_Off     sh_offset;      /* Section file offset */ 0xb0,0xbc,0xc4
      Elf64_Xword   sh_size;        /* Section size in bytes */ 0x0c,0x03,0x17
      Elf64_Word    sh_link;        /* Link to another section */ 0x0,0x0,0x0
      Elf64_Word    sh_info;        /* Additional section information */ 0x0,0x0,0x0
      Elf64_Xword   sh_addralign;   /* Section alignment */ 0x1,0x1,0x1
      Elf64_Xword   sh_entsize;     /* Entry size if section holds table */ 0x0,0x0,0x0
    } Elf64_Shdr;

手工分析, 从0xe0开始, 存在包含4个节头的节表.每个节头0x40个byte, 谁说的? elf header 说的.
第一个节表头项闲置不用, 其它三个已经标注在注释里了. 依次可解释4个section header

这个表跟program 没有关系,不影响程序执行
这4个节(实际是3个,第0个为空项)是说:
- 第1个节,名字叫.text, 可分配可运行(0x6),文件位置0xb0,内存地址0x4000b0,大小0xc,
- 第2个节,名字叫.data,可分配可写(0x3),文件位置0xbc,内存地址0x6000bc,大小0x8,
- 第3个节,名字叫.shstrtab, 未分配内存,文件位置0xc4,内存地址给0(默认),大小0x17, 这个是字符串索引节
至此,这个简单的elf 数据已经被分割完毕!

看起来内容还是不少,几个简单的运行指令竟前呼后涌跟随着不少数据, 这是因为可执行的elf 不是给人看得,是给加载器看的,加载器总是按照固定的套路把数据加载到内存中.

你可能感兴趣的:(文件系统)