C程序运行原理篇-可执行二进制文件里有什么

ELF文件包含了头信息、Section和Segment,用于程序链接和执行。Section按功能组织数据,如.text存放代码,.data存放初始化变量,.bss存放未初始化变量。Segment描述运行时内存布局,LOAD类型的Segment会被加载到内存。动态视图由Segment组成,描述了数据在进程内存中的分布。readelf和objdump工具可用于分析ELF文件的详细信息。
摘要由CSDN通过智能技术生成

可执行二进制文件里有什么?

ELF 文件格式的基本组成结构可以被划分为 ELF 头、Section 和 Segment 三大主要部分。其中,各个 Section
中包含有按照功能类别划分好的、用于支撑 ELF 功能的各类数据。这些数据共同组成了 ELF 文件的静态视图,以用于支持 ELF
文件的链接过程。而众多的 Segment 则组成了 ELF 文件的动态视图,该视图描述了 ELF
文件在被操作系统加载和执行时,其依赖的相关数据在进程 VAS 内的分布情况。

// elf.c
#include <stdio.h>
int main (void) {
  const char* str = "Hello, world!";
  printf("%s", str);
  return 0;
}

ELF文件格式

使用统一的“头部(header)”来保存可执行文件的基本信息。而其他数据则按照功能被划分在了以 Section 或 Segment 形式组织的一系列单元中。

ELF 格式的可执行文件,64-bit 表示该文件采用的是 64 位地址空间,以及该 ELF 格式的版本,是否采用动态链接,以及使用的动态链接器地址等信息。

$ gcc elf.c -o elf.out
$ file ./elf.out
./elf.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=f594e0790a038413521eebf9ce41d98a69dbcaa9, not stripped

ELF头

[centos71 studyCode]$ readelf -h ./elf.out
ELF 头:
  Magic:  7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:              0x400440
  程序头起点:              64 (bytes into file)
  Start of section headers:          6480 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       56 (字节)
  Number of program headers:         9			//segment数量
  节头大小:         64 (字节)
  节头数量:         30							//section数量
  字符串表索引节头: 29

查看开头64字节的内容


-Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
-t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
-N 52:只需要读取 52 个字节;


第三行[地址000020],e_phoff值为0x40,表示程序头表。e_shoff值为0x1950,表示段表的偏移地址,保存 section header table 入口对文件头的偏移量,而每个 section 都会对应一个 section header。
第四行,e_ehsize值为0x0040,表示elf文件头大小(正好是64个字节)。e_phentsize表示一个program header表中的入口的长度,值为0x0038。e_phnum的值为0x0009,给出program header表中的入口数目。e_shentsize值为0x0040表示段头大小为64个字节。e_shnum值为0x001e,表示段表入口有30个。e_shstrndx值为0x001d,表示段名串表的在段表中的索引号

$ od -Ax -t x1 -N 64 elf
[liuxl@centos71 studyCode]$ od -Ax -t x1 -N 64 elf
000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
000010 02 00 3e 00 01 00 00 00 40 04 40 00 00 00 00 00
000020 40 00 00 00 00 00 00 00 50 19 00 00 00 00 00 00
000030 00 00 00 00 40 00 38 00 09 00 40 00 1e 00 1d 00
000040
ELF Section 头

查看Sections Header的结构体

vi  /usr/include/elf.h

readelf 命令指定 “-S” 参数,查看所有这些 Section 头的具体信息。

readelf -S ./elf.out

objdump命令指定“-s” 参数,我们可以查看某个 Section 的完整内容

[@centos71 studyCode]$ objdump -s ./elf.out --section=.rodata

Section 用于存放可执行文件中按照功能分类好的数据。ELF 将各个 Section 的相关信息都整理在了其各自对应的 Section 头部中,众多连续的 Section 头便组成了 Section 头表。

Section 头表中记录了各个 Section 结构的一些基本信息,例如 Section 的名称、长度、它在可执行文件中的偏移位置,以及具有的读写权限等。而操作系统在实际使用时,便可直接从 ELF 头部中获取到 Section 头表在整个二进制文件内的偏移位置,以及该表的大小

[@centos71 studyCode]$ readelf -S ./elf.out
共有 30 个节头,从偏移量 0x1950 开始:
节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000060  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400318  00000318
       000000000000003f  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400358  00000358
       0000000000000008  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400360  00000360
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400380  00000380
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400398  00000398
       0000000000000048  0000000000000018  AI       5    23     8
  [11] .init             PROGBITS         00000000004003e0  000003e0
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400400  00000400
       0000000000000040  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         0000000000400440  00000440
       0000000000000192  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         00000000004005d4  000005d4
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         00000000004005e0  000005e0
       0000000000000021  0000000000000000   A       0     0     8
  [16] .eh_frame_hdr     PROGBITS         0000000000400604  00000604
       0000000000000034  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000400638  00000638
       00000000000000f4  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000008  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000030  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000601030  00001030
       0000000000000004  0000000000000000  WA       0     0     1
  [25] .bss              NOBITS           0000000000601034  00001034
       0000000000000004  0000000000000000  WA       0     0     1
  [26] .comment          PROGBITS         0000000000000000  00001034
       000000000000005a  0000000000000001  MS       0     0     1
  [27] .symtab           SYMTAB           0000000000000000  00001090
       00000000000005e8  0000000000000018          28    46     8
  [28] .strtab           STRTAB           0000000000000000  00001678
       00000000000001ca  0000000000000000           0     0     1
  [29] .shstrtab         STRTAB           0000000000000000  00001842
       0000000000000108  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)
C语法形式数据存放位置说明
int x=10; /static int y =10;.data包含有初始化的全局变量或静态变量,本例值x和y
void foo(){int x 1=0;}寄存器、栈局部变量
register int y =10;寄存器、栈被register修饰的变量
calloc(256)申请的堆内存
int x=10; /static int y ;.bss存放初始化为0的全局变量或静态变量,,本例值x和y
foo(“hello world!”).rodata用于存放只读(Read-only Data)数据。本例指hello world!
foo(100).text存放程序本身的代码

查看某个 Section 的完整内容
在 C 代码中使用到的字符串数据 “Hello, world!”,便被放置在了该 Section 距离本section开头偏移 0x10 字节的位置上。[0x10 = 4005f0 =4005e0]

[@centos71 studyCode]$ objdump -s ./elf.out --section=.rodata

./elf.out:     文件格式 elf64-x86-64

Contents of section .rodata:
 4005e0 01000200 00000000 00000000 00000000  ................
 4005f0 48656c6c 6f2c2077 6f726c64 21002573  Hello, world!.%s
 400600 00          
ELF Program 头

描述该 Segment 的一些基本信息。包含着各个 Segment 的类型、偏移地址、大小、对齐情况,以及权限等信息。被标注为 “LOAD” 类型的 Segment 将会在程序运行时被真正载入到进程的 VAS 中,而其余 Segment 则主要用于辅助程序的正常运行(比如进行动态链接)。不仅如此,Program 头表的具体偏移位置和大小也被放置在了 ELF 头部中,因此操作系统可以在需要时,随时快速地得到这些信息。

由 Section 组成的静态视图外,众多的 Segment 则组成了描述可执行文件的动态视图。Segment 指定了应用程序在实际运行时,应该如何在进程的 VAS 内部组织数据。
readelf 命令指定 “-l” 参数,来观察程序对应可执行文件的 Segment 情况。

[@centos71 studyCode]$ readelf -l ./elf.out

Elf 文件类型为 EXEC (可执行文件)
入口点 0x400440
共有 9 个程序头,开始于偏移量64

程序头:	//描述了各个segment的情况
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000072c 0x000000000000072c  R E    200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x0000000000000224 0x0000000000000228  RW     200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000000604 0x0000000000400604 0x0000000000400604
                 0x0000000000000034 0x0000000000000034  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x00000000000001f0 0x00000000000001f0  R      1

 Section to Segment mapping:
  段节...	//列出了section和segment的对应情况
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 

第一个 LOAD 类型【对应段节02】的 Segment 便包含有 .text 和 .rodata 在内的多个 Section,具有的权限为 “RE”,可读可执行;
而第二个 LOAD【对应段节03】 类型的 Segment 包含.data。具有的权限为 “RW”,可读可写;
第一个 LOAD Segment 所包含的内容,对应到可执行文件内的偏移(Offset)为 0。这意味着,操作系统在执行该程序时,除了各个 Segment 对应的 Sections 外,它还会将该文件的 ELF 头,连同它的 Program 头表一同加载到内存中。
在这里插入图片描述

ELF 编程

参考链接:https://zhuanlan.zhihu.com/p/467232104
https://blog.csdn.net/nirendao/article/details/123883856
https://www.cnblogs.com/20135223heweiqin/p/5554922.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值