从一个demo说elf文件

本文的demo是在linux环境下编译解析的,cpu是x86-64。本文大量参考《程序员的自我修养》一书

从一个demo说起

首先我们先写一个功能简单的demo-SimpleSection.c。这个demo中有一个func1函数用来打印数据,一个已经初始化的全局变量global_init_var和未初始化的全局变量global_uninit_var,一个已初始化的局部静态变量static_var和一个未初始化的局部静态变量static_var2。代码如下:

int printf(const char* format, ...);

int global_init_var = 84;
int global_uninit_var;

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

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

	return a;
}

下一步我们把代码文件编译成目标文件: gcc -c SimpleSection.c
接着使用objdump工具查看目标文件的段表信息:objdump -h SImpleSection.o。对应截图如下:

在这里插入图片描述

从上述截图中可以看出,objdump得到的 目标文件信息共有8项,前六项0~5分别是:

  1. 代码段
  2. 数据段
  3. BSS段
  4. 只读数据段
  5. 注释信息段
  6. 堆栈提示段

信息项共有5列,分别为SizeVMALMAFile off Algn。其中Size为段的长度,File off为段的偏移也就是段的位置。
下面我们用一张图来标识这几个段的相对位置:

在这里插入图片描述
从图中可以清晰的看到各个段在目标文件中的分布。值得注意的是.bss段并不存在于目标文件中,我们在上一张截图中就可以看到Size一列中.bss显示的是ALLOC而不是CONTENTS。这表示在ELF文件中不存在内容。因此.bss段中只记录需要分配的空间大小,而不是真正存在于ELF文件的空间大小。还有就是.text段的起始地址是0x 0000 0040,段size为0x5f,但是奇怪的是0x40+0x5f=0x9f而不是0xa0,这是因为对齐的缘故。

为了更加清晰的查看.text段的信息,我们使用objdump -s -d SimpleSection.o命令去反汇编ELF文件,从而与上面的表述相印证。
在这里插入图片描述
如上面截图所示,.text段的最后一个字节的偏移量,也就是位置是0x5f。这与前面我们看到的.text段的大小一致。

在这里插入图片描述

对于.data段,一共有8个字节,对应了代码中的int global_init_varstatic int static_var变量。前4个字节为0x 54 00 00 00 00,其中第一个字节为0x54,相应的十进制为84,对应了代码中int global_init_var变量。但是需要注意的是这里使用了小端字节序,所以0x54字节才会在第一个。类比一下就知道后4个字节表示的是static int static_var变量。

通常编译器会把字符串常量和其他的一些常量(在c++中用const修饰)放在.rodata段中,这样有一下几个好处:

  • 在语义上支持c++的const关键字
  • 可以在操作系统加载程序的时候将.rodata段的属性映射成只读,保证程序的安全性
  • 在某些嵌入式平台下,有些存储区采用只读存储器,如ROM,保证了程序访问存储器的正确性
    但是需要注意的是,某些编译器会把字符串常量放到.data段,如MSVC编译器。

段表

段表,保存各个段的基本属性的结构,是ELF文件中非常重要的结构。它描述了各个段的信息,如段名、段长度、在文件中的偏移、读写权限以及其他属性。
我们使用 readelf工具来查看目标文件的段表结构:readelf -S SimpleSection.o
目标文件的段表

从截图中我们可以看到,改段表中共有14个元素,其中第一个是NULL,为无效的,因此该段表中只有13个有效的元素。在这个段表中的每个元素都代表一个段,这样的元素对应Elf64_Shdr结构体,被称为段描述符

段描述符Elf64_Shdr被定义在/usr/include/elf.h中,代码如下:

typedef struct
{
  Elf64_Word    sh_name;                /* Section name (string tbl index) */
  Elf64_Word    sh_type;                /* Section type */
  Elf64_Xword   sh_flags;               /* Section flags */
  Elf64_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf64_Off     sh_offset;              /* Section file offset */
  Elf64_Xword   sh_size;                /* Section size in bytes */
  Elf64_Word    sh_link;                /* Link to another section */
  Elf64_Word    sh_info;                /* Additional section information */
  Elf64_Xword   sh_addralign;           /* Section alignment */
  Elf64_Xword   sh_entsize;             /* Entry size if section holds table */
} Elf64_Shdr;

段描述符中的每一项含义如下表:

成员含义
sh_name真正的段名是一个字符串,它位于一个叫做“.shstrtab"的字符串表中。sh_name是段名在“.shstrtab"中的偏移
sh_type段类型,对于系统来说真正决定段的属性的是段的类型和段的标志位
sh_flags段标志位,对于系统来说真正决定段的属性的是段的类型和段的标志位
sh_addr段虚拟地址
sh_offset段偏移
sh_size段长度
sh_link & sh_info段的链接信息
sh_addralign段地址对齐
sh_entsizeSection Entry Size项的长度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值