首页 > 系统相关 >Linux平台下的ELF文件结构探索

Linux平台下的ELF文件结构探索

时间:2023-06-11 14:01:55浏览次数:53  
标签:文件 探索 符号 ELF Linux 字符串 链接

本文以目标文件的结构为引子,通过探索在Linux环境下,一个具体的目标文件的结构来窥探ELF文件的结构。了解ELF文件的结构,对于加深对链接的理解、认识操作系统背后机理都有很大好处。

编译和链接

在正式讨论目标文件的结构之前,需要先对一个C/C++程序从源代码到可执行程序的构建过程有所了解。由于这部分不是本文重点,所以只做简要介绍。

平常使用IDE进行程序设计时,这些工具往往会将编译和链接的过程一步完成,这个过程叫做构建(build)。在Linux环境下,一句gcc或者g++指令就包含复杂的编译和链接过程。事实上,一个C/C++程序从源代码到可执行程序,需要经过预编译(preprocess)、编译(compile)、汇编(assembly)和链接(link)的过程。这些过程中各自依赖不同的工具,以hello.cpp为例,预处理器对该源文件进行各种文本替换操作,此时生成hello.i文件;编译器对hello.i文件进行编译,这个过程包含词法分析、语法分析、语义分析直至汇编代码的生成,此时hello.i文件被处理为hello.s文件;汇编器负责将汇编语言翻译为二进制机器指令,生成hello.o文件,这种文件即目标文件,是本文即是所深入探讨的对象。当各个源文件被分别编译成不同的目标文件后,链接器将这些不同的目标文件与动态库或静态库一齐进行链接,生成一个单独的可执行程序。


目标文件的格式

讨论目标文件的格式之前,需要先了解可执行文件(excutable)的结构。目前PC流行的可执行文件格式主要是Windows平台下的PE和Linux平台下的ELF。目标文件本质是经过编译而未经链接的中间文件,这些中间文件的结构与可执行文件的结构相似,在Windows下,这些文件的格式统称为PE-COFF格式,在Linux下,统称为ELF格式。本文的剖析主要准对ELF格式。

除此之外,动态链接库(Windows的.dll和Linux的.so)和静态链接库(Windows的.lib和Linux的.a)也以对应的格式存储。


ELF文件结构

目标文件中除了有编译后的机器指令代码、数据,还包括链接时所需要的一些信息,例如符号表、字符串等,这些信息按照不同的属性,以节(Section)段(Segment)的形式被存储在目标文件中。节或段都表示一个一定长度的区域,基本不加以区别。

大体上,程序源代码被编译以后主要分成两种段:程序指令和程序数据。将指令和数据分开存放,主要有以下几点好处:

  • 数据区域对于进程来说是可读可写的,而程序区域对进行来说是只读的,装载时,将数据和指令分别映射到两个虚拟内存区域,分别设置区域权限为可读写和可读,可以防止程序指令被有意或无意改写
  • 程序区和数据区分离有利于提高程序的局部性,以提高CPU的缓存命中率
  • 当系统中运行多个该程序的副本时,只需要在内存中保存一份程序的指令部分,可以节省大量的内存

现给出一个 example.c 示例程序,下文会逐步探索将其编译为 example.o 目标文件后的结构和各个段的信息。

/*
Name:example.c

gcc -c example -o example.o
*/
#include <stdio.h>

int global_init_var = 84;
int global_uninit_var;

void func1(int i)
{
	printf("%d\n", i);
}

int main()
{
	static int static_var = 85;
	static int static_var2;
	int a = 1;
	int b;
	func1(static_var + static_var2 + a + b);
	return a;
}

文件头(ELF Header)

ELF目标文件格式最前部是ELF文件头(ELF Header),文件头描述了整个文件的基本属性,包括ELF文件版本、目标机器信息和程序入口等。在Linux中,使用 readelf 工具的 -h 选项详细查看 ELF文件头:

Linux平台下的ELF文件结构探索_段表

各个信息的意义标注如下:

Linux平台下的ELF文件结构探索_文件头_02

这里对ELF魔数做一个简要说明,在Magic一栏中,这16个字节被规定用来标识ELF文件的平台属性:最开始的前4个字节是所有ELF文件都必须相同的标识码,被称为ELF文件的魔数,用来确认文件的类型;第 5 个字节被用来标识ELF文件类型;第 6 个字节被用来标识文件的存储字节序;第 7 个字节被用来标识ELF文件的主版本号,因为ELF标准自 1.2 版本以后再未更新,所以该数一般是 1;最后的 9 个字节ELF标准没有定义,一般填 0。

在Linux平台下,ELF文件头结构和相关常数被定义在 /usr/include/elf.h 中。ELF文件在各种平台下都通用,有32位版本(Elf32_Ehdr)和64位版本(Elf64_Ehdr),两种版本的文件头内容相同,只是有些成员的大小不同。Elf64_Ehdr 的定义如下:

Linux平台下的ELF文件结构探索_文件头_03

段表(Section Header Table)

上文说道,ELF文件是分段的,其中有很多各种各样的段,段表(Section Header Table)是保存这些段的基本属性的结构,它描述了各个段的信息,可以说,ELF文件的段结构就是由段表决定的。在Linux中,使用 readelf 工具的 -S 选项来查看 example.o 的段表内容:

Linux平台下的ELF文件结构探索_段表_04

可见,段表描述了各个段的段名(Name)段的类型(Type)段虚拟地址段偏移(Offset)段的大小(Size)段中的项的长度、段的标志位(Flags)、链接信息和对齐数(Align)。段表的存储结构是一个结构体(Elf32_Shdr或Elf64_Shdr)数组,每个结构体元素又被称为段描述符(Section Descriptor),除第一个元素表示无效段描述符外,其他每个元素都是一个有效段。

可以注意到,下标为 4 的 .bss 段和下标为 5 的 .rodata 段的段偏移和大小都相同,且在空间上是一个段,所以这里将其看做一个段,如此,加上 ELF Header 和 Section Header Table 本身,该ELF文件共 13 个段,与ELF Header中的描述一致。结合 ELF Header 和 段表 中的信息,并加以计算,我们可以得到该ELF文件的整体结构如下:

Linux平台下的ELF文件结构探索_符号表_05

下面我们择几个较重要的段进行详细说明,分析这些段的内容和意义。

代码段、数据段、只读数据段和.bss段

代码段记录了程序的所有指令,通过 objdump 工具的 -s-d 选项可以查看代码段及其将其反汇编的内容:

Linux平台下的ELF文件结构探索_文件头_06

数据段(.data)保存的是已经被初始化的全局变量和静态变量(局部和全局),在 example.c 中一共有2个这样的变量,即 global_init_varabal 和 static_var,这两个变量共8个字节,所以 .data 的大小为 8 字节。

只读数据段(.rodata)保存的是只读数据,一般是程序中的只读变脸,例如const 修饰的变量,有时字符串常量也被放在 .rodata 中。单独设立 .rodata段,不仅在语义上支持了C/C++的const关键字,而且操作系统在加载时可以将 .rodata 段的属性映射成只读,保证了程序的安全性

.bss段 为未被初始化的全局变量和静态变量预留空间。需要注意的是,有些语言和编译器会为未初始化的全局变量预留一个未定义的全局变量符号,而不将其放在任何段,等到链接时再为其在 .bss 段分配空间,例如 example.c  中的global_uninit_var。在 example.c 中只有 static_var2 在 .bss 被预留了空间,所以这里 .bss 段的大小为 4 字节。

字符串表(String Table)

字符串表存放了ELF文件中用到的字符串,例如段名、变量名等。因为字符串的长度是不确定的,所以字符串表中存放的是所有字符串的集合,使用时根据字符串在表中的偏移来引用字符串,不用考虑字符串长度的问题。字符串表(.strtab)用来保存普通的字符串,比如符号名;段表字符串表(.shstrtab)用来保存段表中用到的字符串,比如段名。

符号表(Symbol Table)

符号表汇总了链接时ELF文件中所需的符号(Symbol),包括函数和变量。在64位机器下,符号表是一个Elf64_Sym结构体的数组,除了第一个元素以外,每个元素都代表了一个有效符号。

typedef struct
 {
 		Elf64_Word  st_name;    /* Symbol name (string tbl index) */
 		unsigned char st_info;    /* Symbol type and binding */                
 		unsigned char st_other;   /* Symbol visibility */               
 		Elf64_Section st_shndx;   /* Section index */          
 		Elf64_Addr  st_value;   /* Symbol value */               
 		Elf64_Xword st_size;    /* Symbol size */                   
 } Elf64_Sym;

使用 readelf 工具的 -s 选项查看ELF文件的符号表:

Linux平台下的ELF文件结构探索_文件头_07

可见,符号表保存了各个符号的在符号表中的索引(Num)、符号值(Value)、符号大小(Size)、符号类型(Type)、符号绑定信息(Bind)、符号所在段(Ndx)和符号名(Name)

每个符号都有一个对应的值,如果一个符号是一个函数或者变量的定义,那么该符号值就是这个函数或变量的地址;符号类型包括五种,分别是未知类型符号(NOTYPE)、数据对象(OBJECT)、函数或可执行代码(FUNC)、段(SECTION)和文件名(FILE)。对于SECTION类型的符号,它们的符号名未显示,因为它们的符号名即是它们的段名。符号绑定信息有3种:局部符号(LOCAL)、全局符号(GLOBAL)和弱引用(WEAK)。符号所在段表示该符号所在的段在段表中的下标,如果该符号未被定义在本目标文件中,或者对于一些特殊符号,符号所在段有些特殊:ABS表示该符号包含一个绝对的值,例如文件名的符号;COMMON表示该符号是一个COMMON块类型的符号;UNDEF表示该符号在该文件中未被定义。

将符号表中的符号进行分类,它们大致是下面类型中的一种:

  • 定义(define)在本文件中的全局符号;
  • 在本文件中引用(reference)的全局符号;
  • 段名;
  • 局部符号;
  • 行号信息,即目标文件指令与源代码中代码行的对应关系。

链接的接口

链接的本质即是将多个不同的目标文件互相结合在一起,形成一个大的模块,这个相互拼合的过程实际上是目标文件之间对函数和变量的地址的引用,因此每个函数或变量都需要有自己独特的名字,才能避免链接过程中的混淆。函数和变量统称为符号(symbol),函数名和变量名为符号名(symbol name)。

符号是链接中的接口,链接过程正是基于符号正确完成的。

标签:文件,探索,符号,ELF,Linux,字符串,链接
From: https://blog.51cto.com/158SHI/6457665

相关文章

  • linux学习笔记:网路诊断工具-mtr命令
    网络诊断工具-mtr命令最近在面试的过程中,被问到了mtr命令,一脸懵逼,据面试官了解,该命令在公司里是经常使用的,借此我也来学习一下!网络诊断的背景网络诊断工具包括ping,traceroute和mtr,它们使用Internet控制消息协议(ICMP)数据包来测试Internet上两点之间的连接和传输。当用户在Intern......
  • 【Linux的高级应用编程】TCP/IP网络编程函数解析
    TCP/IP网络编程函数解析 Sailor_forever socket(建立一个socket通信)相关函数accept,bind,connect,listen表头文件#include<sys/types.h>#include<sys/socket.h>定义函数intsocket(intdomain,inttype,intprotocol);函数说明socket()用来建立一个新的socket,也就是向系统注......
  • 【Linux内核及驱动编程】Linux信号机制分析
           Linux信号机制分析 Sailor_forever  【摘要】本文分析了Linux内核对于信号的实现机制和应用层的相关处理。首先介绍了软中断信号的本质及信号的两种不同分类方法尤其是不可靠信号的原理。接着分析了内核对于信号的处理流程包括信号的触发/注册/执行及注销等。最后......
  • LINUX 部分命令
    1.[root@testroot]#grep[-acinv]'搜寻字符串'filename参数说明:-a:将binary档案以text档案的方式搜寻数据-c:计算找到'搜寻字符串'的次数-i:忽略大小写的不同,所以大小写视为相同-n:顺便输出行号-v:反向选择,亦即显示出没有'搜寻字符串'内容的那一行!eg://[]以及......
  • Linux 磁盘空间莫名被吃掉,该怎么查?
    在服务器运维过程中,我们时常会遇到这样的情况,收到服务器磁盘空间告警: 登录服务器,通过df-Hl查看 和告警信息一致,接着我们就是要找到导致磁盘空间满的目录或文件。如何找到占用空间大的目录或文件?一种比较笨的方法是,在根目录下,通过du-hs命令,列出各目录所占空间大小 之后再用同样......
  • Linux服务器配置SSH免密码登录后,登录仍提示输入密码(一次真实的问题排查解决记录)
    我们知道两台Linux服务器机器之间如果使用ssh命令登录或scp/rsync命令传输文件每一次都需要输入用户名相对应的密码,如果要免密码,则需要对两台Linux服务器机器之间进行SSH互信。一.SSH介绍1.SSH互信原理虽然这是废话,也希望大家了解一下。SSH(SecureShell)是一种安全的传输协议,它可以......
  • 【Linux中断】中断下半部-软中断softirq的原理与使用
    软中断软中断是中断下半部的典型处理机制,是随着SMP的出现应运而生的,也是tasklet实现的基础,软中断的出现是为了满足中断上半部和下半部的区别,使得对时间不敏感的任务延后执行,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。软中断有以下特性:产生后并不是马上可以执行,必......
  • linux防火墙相关命令
    查看防火墙状态:systemctlstatusfirewalld启动防火墙:systemctlstartfirewalld关闭防火墙:systemctlstopfirewalld禁用防火墙:systemctldisablefirewalld重启防火墙(修改配置后要重启防火墙):firewall-cmd--reload开放指定端口:firewall-cmd--permanent--a......
  • 在Windows上无docker直接将基于Solon的jar包通过IDEA部署到Linux的docker上
    为何会选择学习solon?springboot对于我开发小企业应用太重,启动太慢,下班太晚!为何都用windows,还想着不安装dockerdesktop洁癖,运行路径能短就短。步骤(以solon官网的helloword为例)1、下载helloworld代码传送阵:点击我2、通过IDEA打开代码,并运行它(我是下载基于maven版本的)。3......
  • Linux系统下配置Nginx服务器
    Nginx是一个高性能的开源HTTP和反向代理服务器,也可以作为电子邮件(SMTP/POP3/IMAP)代理服务器、负载均衡器和HTTP缓存服务器,使用在安装Nginx之前,需要安装一些其他软件依赖,如gcc、pcre、zlib和openssl。1、yum installgcc-ygcc是GNUCompilerCollection的简称,包含编译器和其他编......