395|1

106

帖子

1

TA的资源

一粒金砂(高级)

楼主
 

《Linux内核深度解析》 ---- ELF文件解析 [复制链接]

背景

       Linux应用层存在大量的应用启动过程,这个过程一般是通过以下两个函数实现的。

  • int execve(const char *filename, char *const argv[], char *const envp[]);
  • int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags);

       而这两个函数的入口参数filename和pathname,其实指向的都是ELF格式的文件(.o,.so,和可执行文件)。也就是说,本质上来说,程序的加载运行就是对ELF格式文件的解析,找到程序内部资源信息和函数入口,并依据此信息初始化内存后,在内存相对封闭的环境内进行程序执行,此时若程序跑飞,仅仅需要对此程序占用的内存做销毁处理即可(不考虑函数内部存在动态内存申请释放的情况下),并不会影响整个系统的稳定性。

ELF文件解析

       和wav等文件类型一样,elf文件也由文件头(表明文件类型和文件的核心参数)和内容部分(程序首部(Program header table),节(Section)和节首部(Section header table))组成。其在数据存储分布上存在以下规律:

 

       可以通过readelf -a 来查看elf文件中的所有信息。

ELF头解析

       ELF头长度固定,总共占用32字节(32位系统)或64字节(64位系统),其各段分布如下:

名称

长度(字节)

功能描述

e_ident

4

第一个字节为0x7F,表示可删除的ASCII编码

第二至四字节为ELF(0x45,0x4C,0x46),代表此文件为ELF文件

1

1表示32为的ELF,2代表64位的ELF

1

字节序(大端还是小端对齐等)

1

版本

1

表示应用二进制接口(ABI)的类型

8

暂未使用,填充0

e_type

2

ELF文件类型,值的含义如下:

0表示没有文件类型

1表示可重定位文件(目标文件)

2表示可执行文件

3表示动态库(.so)

4表示核心转存储文件

0xFF00表示用于特定处理器的语义

0xFFFF表示用于特定处理器的语义

e_machine

2

机器类别,区分执行平台,X86,ARM,ARM64等

e_version

4

版本,用于区分不同的ELF变体,目前的规范文件只定义了版本1

e_entry

4/8

程序入口的虚拟地址,0代表这个elf没有关联的入口

e_phoff

4/8

程序(Program)首部表的文件偏移

e_shoff

4/8

节(Section)首部表的文件偏移

e_flags

4

处理器特定的标记

e_ehsize

2

ELF首部的长度,值为arm32为 0x34(52字节),arm64为0x40(64字节)

e_phentsize

2

程序首部表中表项(Segment)的长度,单位是字节

e_phnum

2

程序首部表中表项的数量

e_shensize

2

节首部表中表项的长度,单位是字节

e_shnum

2

节首部表中表项的数量

e_shstrndx

2

节名称字符串表(.shstrtab)在节首部表中的索引,即代表elf文件中的一个section(字符串表),里面存放了section name

       可以通过readelf -h 来查看elf文件中的ELF头信息

程序首部表解析

       程序首部表中存在多个表项,每个表项的长度都为32字节(32位系统)或48字节(64位系统)

名称

长度(字节)

功能描述

p_type

4

段的类型,常见的如下:

1表示可加载段(PT_LOAD),表示可被加载到内存的段,如代码和数据

3表示解释器段(PT_INTERP),指定把可执行文件映射到虚拟地址空间以后必须调用的解释器,解释器负责链接动态库和解析没有解析的符号。解释器通常是动态链接器,即ld共享库,负责把程序以来的动态库映射到虚拟地址空间

p_flags

4

段的标志,常用的3个权限标志是读、写和执行

p_offset

4/8

段在ELF文件中的偏移

p_vaddrp_vaddr

4/8

段映射到内存后的虚拟地址

p_paddr

4/8

段映射到内存后的物理地址

p_filez

4

段在ELF文件中占用的长度

p_memsz

4

段在内存中占用的长度

p_align

4

段的对齐值,p_vaddr和p_paddr对p_align取模后为0

       可以通过readelf -l 来查看elf文件中的程序首部表信息

节首部表解析

       同样的,节首部表也是固定长度的,其占用40字节(32位系统)或48字节(64位系统)。

名称

长度(字节)

功能含义

sh_name

4

所指向的节的名字

sh_type

4

所指向的节的类型

sh_flags

4

所指向的节的属性

sh_addr

4/8

所指向节在执行时的虚拟地址

sh_offset

4/8

所指向的字节在ELF文件中的偏移量

sh_size

4

所指向的字节占用的字节数

sh_link

4

关联节头的下表索引

sh_info

4

附加的节信息

sh_addralign

4

节对齐值

sh_entsize

4

如果节包含一个表项长度固定的表,如符号表,那么这个成员存放表项的长度

       可以通过readelf -S 来查看elf文件中的节首部表信息

重要的节及说明

名称

说明

.text

代码节(也称文本节),通常称代码段,包含程序的机器指令

.data

数据节,也称数据段,包含已初始化的数据,程序在运行器件可以修改

.rodata

只读数据

.bss

没有初始化的数据,在程序开始运行前用0填充

.interp

保存解释器的名称,通常是动态链接库(ld共享库)

.shstrtab

节名称字符串表

.symtab

符号表。符号包括函数和全局变量,符号名称存放在字符串表中,符号表存储符号名称在字符串表里的偏移。可以通过readelf --symbols 查看

.strtab

字符串表,存放符号表所需的字符串

.init

程序初始化时执行的机器指令

.fini

程序结束时执行的机器指令

.dynamic

存放动态链接信息,包含程序依赖的所有动态库,这是动态链接器需要的信息,可以通过readelf --dynamic 查看

.dynsym

存放动态符号表,包含需要动态链接的所有符号,即程序所引用的动态库里面的函数和全局变量,这是动态链接器需要的信息,可以通过readelf --dyn-syms 查看

.dynstr

存放一个字符串表,包含动态链接需要的所有字符串,即动态库的名称、函数名称和全局变量的名称。.dynamic节不直接存储动态库的名称,而是存储库名称在该字符串表里的偏移

.rel.xxxx或.rela.xxxx

用于xxxx节区的重定位信息,记录了需要在链接时修改的指令

部分节内容格式

.symtab

       如果节是符号表类型,其内部存储的数据便遵循以下规则:

名称

长度(字节)

功能含义

st_name

4

符号的名字

st_value

4

符号相对于其所在Section偏移的相对地址

st_size

4

符号所占用的字节数

st_info

1

低四位表示符号的作用范围(bit0:全局或局部,bit1:是否是弱引用),高四位表示符号的类型(变量、函数等)

st_other

1

没有意义

st_shndx

2

该符号的值在哪个Section下存储

知识点确认

简单的测试程序编写

  • #include <stdio.h>
  • int main(char argc, char *argv)
  • {
  • int a = 0;
  • return a;
  • }

编写makefile文件

  • TARGET = test
  • CC = gcc
  • CFLAGS = -Wall -g
  • SRC = test.c
  • OBJ = $(SRC:.c=.o)
  • all: $(TARGET)
  • $(TARGET): $(OBJ)
  • $(CC) $(CFLAGS) -o $@ $^
  • %.o: %.c
  • $(CC) $(CFLAGS) -c $< -o $@
  • clean:
  • rm -f $(OBJ) $(TARGET)
  • .PHONY: all clean

        通过运行make后生成test文件,

        由于readelf -a命令输出的内容过多,虽然能够较为全面的看清楚elf文件内容,但不便于文档展开分析,因此直接查看具体细节的命令。

查看ELF头

readelf -h test

  • 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: 0x400400
  • Start of program headers: 64 (bytes into file)
  • Start of section headers: 5128 (bytes into file)
  • Flags: 0x0
  • Size of this header: 64 (bytes)
  • Size of program headers: 56 (bytes)
  • Number of program headers: 9
  • Size of section headers: 64 (bytes)
  • Number of section headers: 35
  • Section header string table index: 32

其对应结构图下

 

        经过对比,会发现其实readelf的作用就是帮助我们把枯燥的数据解码成直观易懂的提示内容。

查看程序首部表

readelf -l test

  • Elf file type is EXEC (Executable file)
  • Entry point 0x400400
  • There are 9 program headers, starting at offset 64
  • Program Headers:
  • Type Offset VirtAddr PhysAddr
  • FileSiz MemSiz Flags Align
  • PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
  • 0x00000000000001f8 0x00000000000001f8 R E 8
  • INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
  • 0x000000000000001c 0x000000000000001c R 1
  • [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  • LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
  • 0x00000000000006bc 0x00000000000006bc R E 200000
  • LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
  • 0x0000000000000228 0x0000000000000230 RW 200000
  • DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
  • 0x00000000000001d0 0x00000000000001d0 RW 8
  • NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254
  • 0x0000000000000044 0x0000000000000044 R 4
  • GNU_EH_FRAME 0x0000000000000594 0x0000000000400594 0x0000000000400594
  • 0x0000000000000034 0x0000000000000034 R 4
  • GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
  • 0x0000000000000000 0x0000000000000000 RW 10
  • GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
  • 0x00000000000001f0 0x00000000000001f0 R 1
  • Section to Segment mapping:
  • Segment Sections...
  • 00
  • 01 .interp
  • 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
  • 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
  • 04 .dynamic
  • 05 .note.ABI-tag .note.gnu.build-id
  • 06 .eh_frame_hdr
  • 07
  • 08 .init_array .fini_array .jcr .dynamic .got

        以其中第一个程序首部为例,其对应数据结构如下(具体内部每段含义,可以对照程序首部表格式对照查看):

 

查看节首部表

  • readelf -S test
  • There are 35 section headers, starting at offset 0x1408:
  • Section Headers:
  • [Nr] Name Type Address Offset
  • Size EntSize Flags Link Info Align
  • [ 0] NULL 0000000000000000 00000000
  • 0000000000000000 0000000000000000 0 0 0
  • [ 1] .interp PROGBITS 0000000000400238 00000238
  • 000000000000001c 0000000000000000 A 0 0 1
  • [ 2] .note.ABI-tag NOTE 0000000000400254 00000254
  • 0000000000000020 0000000000000000 A 0 0 4
  • [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
  • 0000000000000024 0000000000000000 A 0 0 4
  • [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
  • 000000000000001c 0000000000000000 A 5 0 8
  • [ 5] .dynsym DYNSYM 00000000004002b8 000002b8
  • 0000000000000048 0000000000000018 A 6 1 8
  • [ 6] .dynstr STRTAB 0000000000400300 00000300
  • 0000000000000038 0000000000000000 A 0 0 1
  • [ 7] .gnu.version VERSYM 0000000000400338 00000338
  • 0000000000000006 0000000000000002 A 5 0 2
  • [ 8] .gnu.version_r VERNEED 0000000000400340 00000340
  • 0000000000000020 0000000000000000 A 6 1 8
  • [ 9] .rela.dyn RELA 0000000000400360 00000360
  • 0000000000000018 0000000000000018 A 5 0 8
  • [10] .rela.plt RELA 0000000000400378 00000378
  • 0000000000000030 0000000000000018 A 5 12 8
  • [11] .init PROGBITS 00000000004003a8 000003a8
  • 000000000000001a 0000000000000000 AX 0 0 4
  • [12] .plt PROGBITS 00000000004003d0 000003d0
  • 0000000000000030 0000000000000010 AX 0 0 16
  • [13] .text PROGBITS 0000000000400400 00000400
  • 0000000000000182 0000000000000000 AX 0 0 16
  • [14] .fini PROGBITS 0000000000400584 00000584
  • 0000000000000009 0000000000000000 AX 0 0 4
  • [15] .rodata PROGBITS 0000000000400590 00000590
  • 0000000000000004 0000000000000004 AM 0 0 4
  • [16] .eh_frame_hdr PROGBITS 0000000000400594 00000594
  • 0000000000000034 0000000000000000 A 0 0 4
  • [17] .eh_frame PROGBITS 00000000004005c8 000005c8
  • 00000000000000f4 0000000000000000 A 0 0 8
  • [18] .init_array INIT_ARRAY 0000000000600e10 00000e10
  • 0000000000000008 0000000000000000 WA 0 0 8
  • [19] .fini_array FINI_ARRAY 0000000000600e18 00000e18
  • 0000000000000008 0000000000000000 WA 0 0 8
  • [20] .jcr PROGBITS 0000000000600e20 00000e20
  • 0000000000000008 0000000000000000 WA 0 0 8
  • [21] .dynamic DYNAMIC 0000000000600e28 00000e28
  • 00000000000001d0 0000000000000010 WA 6 0 8
  • [22] .got PROGBITS 0000000000600ff8 00000ff8
  • 0000000000000008 0000000000000008 WA 0 0 8
  • [23] .got.plt PROGBITS 0000000000601000 00001000
  • 0000000000000028 0000000000000008 WA 0 0 8
  • [24] .data PROGBITS 0000000000601028 00001028
  • 0000000000000010 0000000000000000 WA 0 0 8
  • [25] .bss NOBITS 0000000000601038 00001038
  • 0000000000000008 0000000000000000 WA 0 0 1
  • [26] .comment PROGBITS 0000000000000000 00001038
  • 000000000000002b 0000000000000001 MS 0 0 1
  • [27] .debug_aranges PROGBITS 0000000000000000 00001063
  • 0000000000000030 0000000000000000 0 0 1
  • [28] .debug_info PROGBITS 0000000000000000 00001093
  • 00000000000000c0 0000000000000000 0 0 1
  • [29] .debug_abbrev PROGBITS 0000000000000000 00001153
  • 000000000000006b 0000000000000000 0 0 1
  • [30] .debug_line PROGBITS 0000000000000000 000011be
  • 000000000000003b 0000000000000000 0 0 1
  • [31] .debug_str PROGBITS 0000000000000000 000011f9
  • 00000000000000c7 0000000000000001 MS 0 0 1
  • [32] .shstrtab STRTAB 0000000000000000 000012c0
  • 0000000000000148 0000000000000000 0 0 1
  • [33] .symtab SYMTAB 0000000000000000 00001cc8
  • 0000000000000678 0000000000000018 34 50 8
  • [34] .strtab STRTAB 0000000000000000 00002340
  • 0000000000000224 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)

        以其中第二个节为例(具体内部每段含义,可以对照字节首部表格式对照查看):

 

总结

        至此,elf文件的文件格式基本上可以摸清。由于数据各种地址指来指去,数据长度也各种可变,很不便于直接分析固件,好在有对应的额readelf工具,可以程式化的辅助我们解析elf文件,而不必关心文件内某段内部的具体跳转细节。

查看本帖全部内容,请登录或者注册
此帖出自Linux开发论坛

最新回复

读过了。。。。。深入得不一般,确实很深入。。。。。。。。   详情 回复 发表于 2024-12-28 23:01
点赞 关注(1)
 

回复
举报

1437

帖子

1

TA的资源

五彩晶圆(初级)

沙发
 

读过了。。。。。深入得不一般,确实很深入。。。。。。。。

此帖出自Linux开发论坛
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条
艾睿电子& Silicon Labs 有奖直播 | 全新蓝牙信道探测:从技术创新到实际应用
直播时间:3月12日(周三)上午10:00
直播奖励:多功能榨汁机、蓝牙音箱、手机支架

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表