使用readelf、objdump分析一个elf文件完整结构

7 篇文章 0 订阅

编译器编译源代码后生成的文件叫目标文件

从结构上来说与可执行文件一致,只是还没有经过动态链接的过程,有符号还没有被调整。与真正可执行文件稍有区别。

可执行文件格式涵盖了程序的编译、链接、装载和执行的各个方面。

windows下的PE和Linux下的ELF,都是COFF格式的变种。

目标文件(Linux下的.o win下的.obj)与可执行文件就差了个链接过程,一般存储格式是一样的,在Linux下为ELF文件。

动态链接库(DLL .dll和.so )以及静态链接库(Static Linking Library .lib和.a)也都是按照可执行文件格式存储的。

ELF文件类型

可重定位文件(Relocatable File)包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态库也可以归为此类

可执行文件(Executable File)ELF可执行文件  已经重定位的文件

共享目标文件(Shared Object File)包含了代码和数据,两种情况:一种是静态链接器可以使用进行重定位产生新的目标文件,二是动态链接库,例如.so 和.lib。

核心转储文件(Core Dump File) 就是程序crash之后产生的dump文件,保存了程序crash时的各种现场,可以用于分析和调试程序crash的原因。

COFF的主要贡献是在目标文件中引入段的机制,不同的目标文件可以拥有不同数量以及不同类型的段,另外还定义了调试数据格式。

目标文件内容

内容包括编译后的机器指令代码、数据,以及一些链接时需要的信息:符号表、调试信息、字符串等。按照不同的属性,以“节”的形式进行存储,有时候也称“段”。

文件头:File Header  (描述了整个文件的文件属性,包括文件是否可执行、是静态链接还是动态链接以及入口地址(如果是可执行文件)、目标硬件、目标操作系统 )

(段表:属于文件头中一部分,段表是一个描述文件中各个段的数组,描述了文件中各段在文件的偏移位置以及段的属性)

代码段:.code  .text   机器代码 汇编

数据段:.data   以及初始化的全局变量和局部静态变量

BSS(Block Started by Symbol)未初始化段: .bss 未初始化的全局变量和局部静态变量预留位置,  默认值为0, 在.data段中分配空间并存放数据0是没有必要的,elf文件中只是记录了需要分配内存的总和,该段不占用空间。

可执行文件必须记录所有全局变量和局部静态变量的大小总和。

此外还有.rodata  .common等。

程序代码编译后主要分为两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。

数据和指令分开的好处:1:读写权限;2:CPU cache缓存问题,提升cache命中率;3:指令可以复用。

----------------------------------------------------------------------------------------------

下面以64位Ubuntu系统为例进行演示

代码示例

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 -h SimpleSection.o

 代码段、数据段、.bss段、只读数据段(.rodata)、注释信息段(.comment)和堆栈提示段(.note.GNU-stack)

size  SimpleSection.o

 objdump -s -d SimpleSection.o

一般而言bss是未初始化的全局变量和局部静态变量预留位置 ,这里有两个global_uninit_var和 static_var2,应该是8,实际才是4。实际这里只存了static_var2;

这跟强符号和弱符号有关系,global_uninit_var是等到最终链接成可执行文件时才在bss段分配标记。

自定义段:

有时候可能希望变量或者部分代码能够放到你所指定的段中去,以实现某些特定的功能。比如为了满足某些硬件的内存和I/O的地址布局,或者像Linux内核中用来完成一些初始化和用户空间复制时出现页错误异常等。

__attribute__((section("FOO"))) int global = 42;

在全局变量或函数之前加上“__attribute__((section("name")))”属性就可以把相应的变量或者函数放在以name作为段名的段中。

ELF文件的总体结构:

ELF Header

.text

.data

.bss

Other sections

Section header Table

String Tables

Symbol Tables

readelf -h 查看命令的帮助信息,需要正确区分文件头、section头、程序头(可执行程序才有)。

程序头是可执行程序load和exec时使用的,在汇编和链接过程中用不到;

相应的Section头在load时也用不上。

 ELF file Header : 描述了整个文件的基本属性,比如ELF文件版本,目标芯片类型,程序入口地址(Entry point address)

readelf -S  显示段表(Section Header Table)段表是elf文件除了文件头以外最重要的结构,描述了各个段的信息,比如各个段的段名,段的长度,在文件中的偏移、读写权限及段的其他属性。

编译器、链接器和装载器都是依靠段来定位和访问各个段的属性的,对比objdump -h也可以查看段表信息,但是不是完整的。

这里起始地址0x430等于上面的1072。

ELF相关的结构体定义在 “usr/include/elf.h”中,对应ndk的位置:./toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/linux/elf.h

关于段表的描述为一个结构体(Elf64_Shdr sizeof=64 (size of section header))数组,如上,对应长度为13的数组;

 最终的文件结构顺序以及长度如下:

序号

段内容

长度

偏移

对齐

ELF file Header

0x40

0

1

.text

0x55

0x40

1

2

.data

0x8

0x98

4

3

.bss

0

0xa0

4

.rodata

0x4

0xa0

1

5

.comment

0x36

0xa4

1

6

.note.GNU-stack

0

0xda

7

.eh_frame

0x58

0xe0

8

8

.symtab 符号表

0x180

0x138

8

9

.strtab

0x66

0x2b8

1

10

.rela.text

0x78

0x320

8

11

.rela.eh_frame

0x30

0x398

8

12

.shstrtab

0x61

0x3c8

1

段表

Section header

0x340 = 0x40*13

0x430

4?

文件长度:

0x770=1904

这与文件大小是可以对应上的:

readelf参考:

readelf 命令,Linux readelf 命令详解:用于显示elf格式文件的信息 - Linux 命令搜索引擎

参考书籍:

《程序员的自我修养》

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值