ELF格式解读-程序头与内存布局
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELF格式解读-程序头与内存布局相关的知识,希望对你有一定的参考价值。
前言
我们知道ELF
中有一个头部叫程序头,这个头部专门用于将ELF
加载内存中使用。
我们知道操作系统使用页表管理内存,如果一个ELF所有节(section)小于页表大小(一般4k),那么一个节一个页内存会极大浪费内存使用。因此我们会把具有相同属性(比如可读)节合并成一个段(segment
)在去加载,那么这个合并信息就在这个程序头中。
如下图中只读节.text .rodata
合并为一个段。
以下本文案例代码:
//main.c
#include <stdio.h>
static int mystaticVar = 3 ;
int myglobalvar=5;
int myglobalvar2=6;
extern void testfun();
int main()
testfun();
printf("hello world %d \\r\\n",mystaticVar);
return 0;
void hell()
testfun();
//test.c
__attribute__((visibility("default"))) void testfun()
__attribute__((visibility("hidden"))) void testfun2()
int libGLobal=2;
相关编译命令
gcc -fPIC -shared -o test.so test.c
gcc -no-pie -o main.out main.c test.so
实战
我们的程序头大小位置由ELF头部指定,具体可参考博主另一文章ELF格式解读-(1) elf头部与节头。
程序头其实是一个Elf64_Phdr
数组,每个Elf64_Phdr
就是一个段,但是不是所有的段都会被加载到内存中。
typedef struct
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
Elf32_Phdr;
typedef struct
Elf64_Word p_type;
Elf64_Word p_flags;
Elf64_Off p_offset;
Elf64_Addr p_vaddr;
Elf64_Addr p_paddr;
Elf64_Xword p_filesz;
Elf64_Xword p_memsz;
Elf64_Xword p_align;
Elf64_Phdr;
p_type
这个字段决定这个段是什么类型,下面是字段枚举表:
Name | value |
---|---|
PT_NULL | 0 |
PT_LOAD | 1 |
PT_DYNAMIC | 2 |
PT_INTERP | 3 |
PT_NOTE | 4 |
PT_SHLIB | 5 |
PT_PHDR | 6 |
PT_TLS | 7 |
PT_LOOS | 0x60000000 |
PT_Hios | 0x6fffffff |
PT_LOPROC | 0x70000000 |
PT_HIPROC | 0x7fffffff |
PT_GNU_EH_FRAME | 0x6474e550 |
PT_GNU_STACK | 0x6474e551 |
PT_GNU_RELRO | 0x6474e552 |
上面虽然类型很多,但是除了PT_LOAD
以及PT_GNU_RELRO
会被加载内存映射中,其他类别段是不会占内存的,只是提示性信息给加载器等。
PT_LOAD
:这个类型会进行内存映射,比如只读代码段,数据段等
PT_GNU_RELRO
:这个类型会进行内存映射,主要用于防止got
表篡改,这个段在加载前是可读写的,用于重定位got
,加载完成后变为只读。
p_flags
这个节是否可读可写可执行
以下是枚举值,可以进行组合表示可读可写可执行
Name | value |
---|---|
PF_X | 0x1 |
PF_W | 0x2 |
PF_R | 0x4 |
p_offset
这个段所在文件的偏移
p_vaddr
内存中的虚拟地址
p_paddr
一般和p_vaddr相同
p_filesz
这个段在文件中大小
p_memsz
这个段在内存中的大小(一定会大于等于p_filesz,如果大于一般为对齐原因)
p_align
对齐数值
我们可以使用readelf -l
去查看一个程序的程序头
我们来看看第二个LOAD
信息
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0x0000000000401000 0x0000000000401000 0x000225 0x000225 R E 0x1000
03 .init .plt .plt.sec .text .fini
这个段是 .init .plt .plt.sec .text .fini
这几个节组合体,段大小为0x000225
,我们再看看这些节信息
我们手动计算这些节大小总和:
.init 00001b
.plt 000030
.plt.sec 000020
.text 0001a5
.fini 00000d
00001b+000030+000020+0001a5+00000d = 21D
我们计算得到21D大小,但是段显示确实225
,那么多出的8字节来自哪?
其实部分个节区会间隔若干0填充的字节,这里正好是8
比如本例中.text
和.fini
间隔3字节,.init
和.plt
间隔5字节,一个8字节。如下图所示
因为段数据是连续的,所以包含上面的5字节。
我们运行本例程序查看实际内存布局:
使用gdb挂起程序:
我们linux的虚拟文件系统查看内存上的映射关系:
GUN_RELRO并没有按照参照程序头那样划分内存
参考
Why ELF program headers have two LOAD entries, while the program layout three sections
Hardening ELF binaries using Relocation Read-Only (RELRO)
以上是关于ELF格式解读-程序头与内存布局的主要内容,如果未能解决你的问题,请参考以下文章