Linux下ELF文件类型分为以下几种:

1、可重定位文件,比如SimpleSection.o;

2、可运行文件,比如/bin/bash。

3、共享目标文件,比如/lib/libc.so。

Linux 可重定位文件 ELF结构一文中,我们已经分析了可重定位文件ELF结构。

本文分析可运行文件的ELF结构。

首先附上源码:

SectionMapping.c

#include <stdlib.h>

int main()
{
while(1)
{
sleep(1000);
}
return 0;
}

使用命令gcc -static SectionMapping.c -o SectionMapping.elf。静态链接为可运行文件。

接着使用命令readelf -S SectionMapping.elf得到Section Table。例如以下:

There are 33 section headers, starting at offset 0xc3878:

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.ABI-tag NOTE 0000000000400190 00000190
0000000000000020 0000000000000000 A 0 0 4
[ 2] .note.gnu.build-i NOTE 00000000004001b0 000001b0
0000000000000024 0000000000000000 A 0 0 4
[ 3] .rela.plt RELA 00000000004001d8 000001d8
0000000000000120 0000000000000018 A 0 5 8
[ 4] .init PROGBITS 00000000004002f8 000002f8
0000000000000018 0000000000000000 AX 0 0 4
[ 5] .plt PROGBITS 0000000000400310 00000310
00000000000000c0 0000000000000000 AX 0 0 16
[ 6] .text PROGBITS 00000000004003d0 000003d0
0000000000094988 0000000000000000 AX 0 0 16
[ 7] __libc_thread_fre PROGBITS 0000000000494d60 00094d60
00000000000000a8 0000000000000000 AX 0 0 16
[ 8] __libc_freeres_fn PROGBITS 0000000000494e10 00094e10
000000000000181c 0000000000000000 AX 0 0 16
[ 9] .fini PROGBITS 000000000049662c 0009662c
000000000000000e 0000000000000000 AX 0 0 4
[10] .rodata PROGBITS 0000000000496640 00096640
000000000001d344 0000000000000000 A 0 0 32
[11] __libc_thread_sub PROGBITS 00000000004b3988 000b3988
0000000000000008 0000000000000000 A 0 0 8
[12] __libc_subfreeres PROGBITS 00000000004b3990 000b3990
0000000000000058 0000000000000000 A 0 0 8
[13] __libc_atexit PROGBITS 00000000004b39e8 000b39e8
0000000000000008 0000000000000000 A 0 0 8
[14] .eh_frame PROGBITS 00000000004b39f0 000b39f0
000000000000d4c4 0000000000000000 A 0 0 8
[15] .gcc_except_table PROGBITS 00000000004c0eb4 000c0eb4
0000000000000172 0000000000000000 A 0 0 1
[16] .tdata PROGBITS 00000000006c1ef0 000c1ef0
0000000000000020 0000000000000000 WAT 0 0 16
[17] .tbss NOBITS 00000000006c1f10 000c1f10
0000000000000038 0000000000000000 WAT 0 0 16
[18] .init_array INIT_ARRAY 00000000006c1f10 000c1f10
0000000000000008 0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 00000000006c1f18 000c1f18
0000000000000008 0000000000000000 WA 0 0 8
[20] .ctors PROGBITS 00000000006c1f20 000c1f20
0000000000000010 0000000000000000 WA 0 0 8
[21] .dtors PROGBITS 00000000006c1f30 000c1f30
0000000000000010 0000000000000000 WA 0 0 8
[22] .jcr PROGBITS 00000000006c1f40 000c1f40
0000000000000008 0000000000000000 WA 0 0 8
[23] .data.rel.ro PROGBITS 00000000006c1f50 000c1f50
0000000000000080 0000000000000000 WA 0 0 16
[24] .got PROGBITS 00000000006c1fd0 000c1fd0
0000000000000010 0000000000000008 WA 0 0 8
[25] .got.plt PROGBITS 00000000006c1fe8 000c1fe8
0000000000000078 0000000000000008 WA 0 0 8
[26] .data PROGBITS 00000000006c2060 000c2060
0000000000001690 0000000000000000 WA 0 0 32
[27] .bss NOBITS 00000000006c3700 000c36f0
0000000000002ba8 0000000000000000 WA 0 0 32
[28] __libc_freeres_pt NOBITS 00000000006c62b0 000c36f0
0000000000000048 0000000000000000 WA 0 0 16
[29] .comment PROGBITS 0000000000000000 000c36f0
000000000000002a 0000000000000001 MS 0 0 1
[30] .shstrtab STRTAB 0000000000000000 000c371a
000000000000015b 0000000000000000 0 0 1
[31] .symtab SYMTAB 0000000000000000 000c40b8
000000000000c168 0000000000000018 32 870 8
[32] .strtab STRTAB 0000000000000000 000d0220
0000000000007a26 0000000000000000 0 0 1

表 1

这个可运行文件共同拥有33个Section。

接着我们使用readelf -h SectionMapping.elf。读取elf可运行文件头部信息。

例如以下图:

图 1

能够对照,Linux 可重定位文件 ELF结构,这里多了program header。

Entry point address:程序的入口地址是0x401058,使用objdump -d SectionMapping.elf | less,能够查看到程序的入口地址是<_start>。

例如以下图:

图 2

Start of program headers:program headers的偏移。由于头文件大小为64,所以program headers紧挨着头文件存放。

Size of program headers:program headers的大小。为56个字节。

Number of section headers:program headers的数量。

为6个。

在表1中。第一个section在文件里的偏移是0x190。头文件大小为64 + program header大小为56 * program header数量6 = 400 = 0x190。

然后,我们使用命令readelf -l SectionMapping.elf。我们会得到program header部分。例如以下图:

图  3

从图中可见,分为6个Segment。

注意表1中每一个段叫Section。

Offset:这个Segment在文件里偏移。

VirtAddr:这个Segment在虚拟地址的偏移。

FileSiz:在ELF文件里所占的长度。

MemSiz:在进程虚拟空间所占的长度。

我们发现第二个Segment,MemSiz > FileSiz,表示在内存中分配的空间大小超过文件实际大小。

超过的部分所有初始化为0。作为BSS段。由于数据段和BSS段的唯一差别是,数据段从文件里初始化内容,BSS段内容所有初始化为0。

我们主要关心前两个Segment。第一个是代码段,虚拟地址从0x00400000到0x004c1026。文件偏移从0x00000000到0x000c1026。

第二个是数据段。虚拟地址为从0x006c1ef0到0x006c1ef0+0x4408=0x6c62f8。

文件偏移从0x000c1ef0到0x000c1ef0+0x1800=0x000C36f0。

结合表1和两个Segment的文件偏移。能够得出:

第一个Segment从第0个Section到第15个Section。(0x00000000-0x000c1026)

  [Nr] Name              Type             Address           Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.ABI-tag NOTE 0000000000400190 00000190
0000000000000020 0000000000000000 A 0 0 4
[ 2] .note.gnu.build-i NOTE 00000000004001b0 000001b0
0000000000000024 0000000000000000 A 0 0 4
[ 3] .rela.plt RELA 00000000004001d8 000001d8
0000000000000120 0000000000000018 A 0 5 8
[ 4] .init PROGBITS 00000000004002f8 000002f8
0000000000000018 0000000000000000 AX 0 0 4
[ 5] .plt PROGBITS 0000000000400310 00000310
00000000000000c0 0000000000000000 AX 0 0 16
[ 6] .text PROGBITS 00000000004003d0 000003d0
0000000000094988 0000000000000000 AX 0 0 16
[ 7] __libc_thread_fre PROGBITS 0000000000494d60 00094d60
00000000000000a8 0000000000000000 AX 0 0 16
[ 8] __libc_freeres_fn PROGBITS 0000000000494e10 00094e10
000000000000181c 0000000000000000 AX 0 0 16
[ 9] .fini PROGBITS 000000000049662c 0009662c
000000000000000e 0000000000000000 AX 0 0 4
[10] .rodata PROGBITS 0000000000496640 00096640
000000000001d344 0000000000000000 A 0 0 32
[11] __libc_thread_sub PROGBITS 00000000004b3988 000b3988
0000000000000008 0000000000000000 A 0 0 8
[12] __libc_subfreeres PROGBITS 00000000004b3990 000b3990
0000000000000058 0000000000000000 A 0 0 8
[13] __libc_atexit PROGBITS 00000000004b39e8 000b39e8
0000000000000008 0000000000000000 A 0 0 8
[14] .eh_frame PROGBITS 00000000004b39f0 000b39f0
000000000000d4c4 0000000000000000 A 0 0 8
[15] .gcc_except_table PROGBITS 00000000004c0eb4 000c0eb4
0000000000000172 0000000000000000 A 0 0 1

第二个Segment从第16个Section到26个Section。

(0x000c1ef0-0x000C36f0)

  [16] .tdata            PROGBITS         00000000006c1ef0  000c1ef0
0000000000000020 0000000000000000 WAT 0 0 16
[17] .tbss NOBITS 00000000006c1f10 000c1f10
0000000000000038 0000000000000000 WAT 0 0 16
[18] .init_array INIT_ARRAY 00000000006c1f10 000c1f10
0000000000000008 0000000000000000 WA 0 0 8
[19] .fini_array FINI_ARRAY 00000000006c1f18 000c1f18
0000000000000008 0000000000000000 WA 0 0 8
[20] .ctors PROGBITS 00000000006c1f20 000c1f20
0000000000000010 0000000000000000 WA 0 0 8
[21] .dtors PROGBITS 00000000006c1f30 000c1f30
0000000000000010 0000000000000000 WA 0 0 8
[22] .jcr PROGBITS 00000000006c1f40 000c1f40
0000000000000008 0000000000000000 WA 0 0 8
[23] .data.rel.ro PROGBITS 00000000006c1f50 000c1f50
0000000000000080 0000000000000000 WA 0 0 16
[24] .got PROGBITS 00000000006c1fd0 000c1fd0
0000000000000010 0000000000000008 WA 0 0 8
[25] .got.plt PROGBITS 00000000006c1fe8 000c1fe8
0000000000000078 0000000000000008 WA 0 0 8
[26] .data PROGBITS 00000000006c2060 000c2060
0000000000001690 0000000000000000 WA 0 0 32

以上分析的都是静态状态下的程序,以下我们看看动态下的进程的空间是怎么分配的。

首先使用命令, ./SectionMapping.elf &,输出例如以下:

然后使用命令:cat /proc/2184/maps,输出例如以下:

图 4

静态时。我们计算出的两个Segment的虚拟空间的偏移分别为:

第一个是代码段。虚拟地址从0x00400000到0x004c1026。

在图4中,由于要页面对齐,所以分配了0x400000到0x4c2000。

第二个是数据段,虚拟地址为从0x006c1ef0到0x006c1ef0+0x4408=0x6c62f8。在图4中。由于要页面对齐,所以分配了0x6c1000到0x6c4000。注意。0x6c62f8大于0x6c4000。详细原因以后再分析。

第三个紧接着是堆。用于动态分配内存。

第四个是栈。用于存放局部变量。

总体的结构例如以下图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvamx0eGdjeQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

程序运行的过程:建立虚拟空间(分配一个页文件夹)-> 建立虚拟空间与可运行文件映射(页文件夹项指向磁盘的程序) -> 跳到程序入口 -> 缺页异常-> 在内存中寻找空暇页。将相应的页换入 -> 建立映射 -> 開始运行。

最新文章

  1. Android开发的菜鸟小记
  2. EasyUI ComboGrid的绑定,上下键和回车事件,输入条件查询
  3. org.apache.commons.lang3.ArrayUtils 学习笔记
  4. angular.js input
  5. js 微信分享
  6. System.Web.HttpContext.Current 跟踪分析
  7. 高性能网络I/O框架-netmap源码分析
  8. Android之路-------Activity的详解
  9. java判断用户输入的是否至少含有N位小数
  10. runtime统计页面数据或者统计按钮的点击次数
  11. 利用pandas对numpy数组进行简单的科学计算
  12. linux软件管理 YUM命令
  13. ASP.NET 多次点击button后事件执行多次 并发解决 频繁操作解决办法
  14. Angular4 —— NgModule
  15. 汇编 push ,pop指令
  16. 【Linux技术】BusyBox详解
  17. rabbitmq日志记录进出的每条消息
  18. MySQL学习【第一篇介绍】
  19. Android布局属性说明
  20. [AS3.0] 解决Number类型计算不精确问题

热门文章

  1. 【LeetCode】83 - Remove Duplicates from Sorted Array
  2. 面积最大的全1子矩阵--九度OJ 1497
  3. seo技巧-2015/10/05
  4. Red Hat Linux认证
  5. Windows8.1 安装office2013并激活
  6. effective c++:virtual函数的替代方案
  7. windows下执行build_native.sh报权限问题
  8. Struct2提交表单数据到Acion
  9. &lt;转&gt;Linux环境进程间通信(三)
  10. C++11能用智能指针