目标文件格式

目标文件里有什么

目标文件格式

目标文件就是源代码编译后但未进行链接的中间文件(linux下的.o)。

ELF文件:从广义上看,目标文件与可执行文件的格式其实几乎是一样的,可以将目标文件与可执行文件看成是一种类型的文件(ELF文件)。

表1 ELF文件类型

ELF文件类型

说明

实例

可重定位文件

这类文件包含了代码和数据,可以被用来链接成可执行文件或共享目标文件,静态库也可以归类为这一类

Linux的.o

可执行文件

这类文件包含了可以直接执行的程序,它的代表就是ELF可执行文件,它们一般都没有扩展名

比如/bin/bash文件

共享目标文件

这种文件包含了代码和数据,可以在以下两种情况下使用。一种是链接器可以使用这种文件跟其他的可重定位文件和共享目标文件链接,产生新的目标文件。第二种是动态连接器可以将及格这种共享目标文件与可执行文件结合,作为进程映像的一部分来运行

Linux的.so,如/lib/glibc-2.5.so

核心转储文件

当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件

Linux下的core dump

目标文件是什么样的

目标文件中的内容至少有编译后的机器指令代码,数据,符号表,调试信息,字符串等。一般目标文件将这些信息按不同的属性,以“节”的形式存储,在一般情况下,它们都表示一个一定长度的区域。

程序源代码编译后的机器指令经常被放在代码段,代码段常见的名字有“.code”或“.text”;全局变量和局部静态变量数据经常放在数据段,数据段的一般名字都叫“.data”。让我们来看一个简单的程序被编译成目标文件后的结构,如下图所示。

图1

从上图可以看到,ELF文件的开头是一个“文件头”,它描述了整个文件属性,包括是否可执行,是静态链接还是动态链接以及入口地址(如果是可执行文件),目标硬件,目标操作系统等信息,文件头还包括一个段表,段表其实是一个描述文件中各个段的数组。

从上图可以看到,一般C语言的编译后可执行语句都编译成机器代码,保存在.text段;已初始化的全局变量和局部静态变量都保存在.data段;未初始化的全局变量和局部静态变量一般放在一个叫做“.bss”的段里。

总体来说,程序源代码被编译以后主要分成两种段:程序指令和程序数据。代码段属于程序指令,而数据段和.bss段属于程序数据。

为什么要把程序的指令和数据的存放分开?混杂地方在一个段里面不是更加简单?其实数据和指令分段的好处有很多。主要有如下几个方面

  1. 一方面是当程序被装载后,数据和指令分别被映射到两个虚存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置成可读写和只读。这样就可以防止程序的指令被有意或者无意的改写。
  2. 另外一方面是对于现代的CPU来说,它们有着极为强大的缓存体系。由于缓存在现代的计算机中地位非常重要,所以程序必须尽量提高缓存的命中率。指令区和数据区的分离有利于提高程序的局部性。现代CPU的缓存一般都被设计成数据缓存和指令缓存分离,所以程序的指令和数据被分开存放对CPU的缓存命中率提高有好处。
  3. 第三个原因,其实也是最重要的原因,就是当系统中运行着多个程序的副本时,他们的指令都是一样的,所以内存中只需要保存一份该程序的指令部分。对于指令这种只读的区域来说是这样,对于其他的只读数据也一样,比如很多程序里面带有的图标,图片等资源属于可以共享。当然每个副本进程的数据区域是不一样的,它们是进程私有的。

挖掘a.o

首先来看下a.c的代码清单:

图2

我们使用GCC来编译这个文件(参数-c表示只编译不链接):

gcc -c a.c

我们得到一个目标文件a.o,使用objdump来查看a.o内部的结构,该工具可以用来查看目标文件的结构和内容。运行以下命令:

objdump -h a.o

图3

从上面的结果来看,a.o的段包括了代码段(.text),数据段(.data),BSS段(.bss),只读数据段(.rodata),注释信息段(.comment)和堆栈提示段(.note.GNU-stack)等,再来看看段的属性,其中最容易理解的是段的长度(Size)和段所在的位置(File Offset),每个段的第2行的“CONTENTS”,“ALLOC”等表示段的各种属性,“CONTENTS”表示段在文件中存在。我们可以看到BSS段没有“CONTENTS”,表示它实际上再ELF文件中不存在内容,我们用下图来表示“.text”,“.data”,“.rodata”和“.comment”段在ELF文件的长度和文件中的偏移位置。

图4

代码段

挖掘各个段的内容,我们还是用objdump这个工具。Objdump的“-s”参数可以将所有段的内容以十六进制的方式打印出来,“-d”参数可以将所有包含指令的段反汇编,下图是使用objdump输出a.o中有关代码段内容的提取:

$ objdump -s -d a.o

a.o:     文件格式 elf64-x86-64

Contents of section .text(代码段):

 0000 554889e5 4883ec10 897dfc8b 45fc89c6  UH..H....}..E...

 0010 bf000000 00b80000 0000e800 000000c9  ................

 0020 c3554889 e54883ec 10c745f8 01000000  .UH..H....E.....

 0030 8b150000 00008b05 00000000 01c28b45  ...............E

 0040 f801c28b 45fc01d0 89c7e800 000000b8  ....E...........

 0050 00000000 c9c3                        ......          

Contents of section .data(数据段):

 0000 54000000 55000000                    T...U...        

Contents of section .rodata(只读数据段):

 0000 25640a00                             %d..            

Contents of section .comment:

 0000 00474343 3a202855 62756e74 7520342e  .GCC: (Ubuntu 4.

 0010 382e342d 32756275 6e747531 7e31342e  8.4-2ubuntu1~14.

 0020 30342e33 2920342e 382e3400           04.3) 4.8.4.    

Contents of section .eh_frame:

 0000 14000000 00000000 017a5200 01781001  .........zR..x..

 0010 1b0c0708 90010000 1c000000 1c000000  ................

 0020 00000000 21000000 00410e10 8602430d  ....!....A....C.

 0030 065c0c07 08000000 1c000000 3c000000  .\..........<...

 0040 00000000 35000000 00410e10 8602430d  ....5....A....C.

 0050 06700c07 08000000                    .p......        

Disassembly of section .text:

0000000000000000 <func1>:

   0: 55                    push   %rbp

   1: 48 89 e5              mov    %rsp,%rbp

   4: 48 83 ec 10           sub    $0x10,%rsp

   8: 89 7d fc              mov    %edi,-0x4(%rbp)

   b: 8b 45 fc              mov    -0x4(%rbp),%eax

   e: 89 c6                 mov    %eax,%esi

  10: bf 00 00 00 00        mov    $0x0,%edi

  15: b8 00 00 00 00        mov    $0x0,%eax

  1a: e8 00 00 00 00        callq  1f <func1+0x1f>

  1f: c9                    leaveq

  20: c3                    retq   

0000000000000021 <main>:

  21: 55                    push   %rbp对应Contents of section .text的第一个字节

  22: 48 89 e5              mov    %rsp,%rbp

  25: 48 83 ec 10           sub    $0x10,%rsp

  29: c7 45 f8 01 00 00 00 movl   $0x1,-0x8(%rbp)

  30: 8b 15 00 00 00 00     mov    0x0(%rip),%edx        # 36 <main+0x15>

  36: 8b 05 00 00 00 00     mov    0x0(%rip),%eax        # 3c <main+0x1b>

  3c: 01 c2                 add    %eax,%edx

  3e: 8b 45 f8              mov    -0x8(%rbp),%eax

  41: 01 c2                 add    %eax,%edx

  43: 8b 45 fc              mov    -0x4(%rbp),%eax

  46: 01 d0                 add    %edx,%eax

  48: 89 c7                 mov    %eax,%edi

  4a: e8 00 00 00 00        callq  4f <main+0x2e>

  4f: b8 00 00 00 00        mov    $0x0,%eax

  54: c9                    leaveq

  55: c3                    retq  对应Contents of section .text的最后一个字节

“Contents of section .text”就是.text段的数据以十六进制方式打印出来的内容,总共0x56字节(黄色部分),跟我们上图的“.text”段长度相符合,最左边一列是偏移量,中间4列是十六进制内容,最右面一列是.text的ASCII码形式。对照下面的反汇编结果,可以很明显的看到,.text段里所包含的正是a.c里两个函数func1()和main()的指令。.text段的第一个字节”0x55”就是函数“func1()”函数的第一条“push %rbp”指令,而最后一个字节0xc3正是main()函数的最后一条指令”ret”。

数据段和只读数据段

.data段保存的是那些已经初始化了的全局静态变量和局部静态变量。从上文源代码a.c里面一共有两个这样的变量,分别是global_int_varabal和static_var。这两个变量每个4个字节,一共刚好8个字节,所以“.data”这个段的大小为8个字节(上文紫色部分),0x54是84的十六进制,0x55是85的十六进制。

  1. c里面我们在调用“printf”的时候,用到了一个字符串常量“%d\n”,它是一种只读数据,所以它被放到了“.rodata”段,我们可以从输出结果看到“.rodata”这个段的4个字节刚好是这个字符串常量的ASCII字节序,最后以\0结尾(蓝色部分)。

BSS段

.bss段存放的是未初始化的全局变量和局部静态变量。

ELF文件结构描述

上文通过a.o的结构大致了解了ELF文件的轮廓 ,接着就来看看ELF文件的结构格式。下图描述了ELF目标文件的总体结构。

图5 ELF结构

ELF Header

.text

.data

.bss

Other sections

Section Header table

String Tables

Symbol Tables

从图5可以看出ELF目标文件格式的最前部是ELF文件头(ELF Header),它包含了描述整个文件的基本属性,比如ELF文件版本,目标机器型号,程序入口地址等,紧接着是ELF文件各个段。其中ELF文件中与段有关的重要结构就是段表(Section Header Table),该表描述了ELF文件包含的所有段的信息,比如每个段的段名,段的长度,在文件中的偏移,读写权限以及段的其他属性。

文件头

我们可以用readelf命令来详细查看ELF文件,如下图6所示

图6 ELF文件头

从上面输出的结果可以看到,ELF的文件头定义了ELF魔数,文件机器字节长度,数据存储方式,版本,运行平台,ABI版本,ELF重定位类型,硬件平台,硬件平台版本,入口地址,程序头入口和长度,段表的位置和长度以及段的数量等。

ELF文件头结构以及相关常数被定义在”/usr/include/elf.h”中,因为ELF文件在各种版本平台下都通用,ELF文件有32位版本和64位版本,分别叫做”Elf32_Endr”和”Elf64_Ehdr”,如图7所示。

图7 ELF文件头结构体成员

段表
我们知道ELF文件中有很多各种各样的段,通过段表来保存这些段的基本属性。段表是ELF文件中除了文件头以外最重要的结构,它描述了EFL的各个段的信息,比如每个段的段名,段的长度(棕色圈矩形框),在文件中的偏移(红色圈矩形框),读写权限以及段的其他属性。也就是说,ELF文件的段结构就是有段表决定的,编译器,连接器和装载器都是依靠段表来定位和访问各个段的属性的。段表在ELF文件中的位置有EFL文件头的”e_shoff”成员决定,比如在a.o中,段表位于偏移0x190。

图8 表段信息

Readelf输出的结构就是ELF文件段表的内容,那么就让我们对着这个输出来看看段表的结构。段表的结构比较简单,它是一个以”Elf64_Shdr”(在/usr/include/elf.h,占64个字节)结构为元素的数组。数组元素的个数等于段的个数,每个”Elf64_Shdr”结构体对应一个段。”Elf64_Shdr”又称为段描述符(Section Descriptor)。对于a.o来说,段表就是有13个元素的数组。ELF段表的这个数组的第一个元素是无效的段描述符,它的类型是”NULL”,除此之外每个段描述符都对应一个段。在a.o共有12个有效的段。

到了这一步,我们才把a.o的所有段的位置和长度给分析清楚了。在下图9中,SectionTable长度为0x340,也就是832个字节,它包含了13个段描述符,每个段描述符为64个字节,这个长度刚好等于sizeof(Elf64_Shdr),符合段描述符的结构体长度,整个文件最后一个段”.real_ch_frame”结束后,长度为0x58,即1880字节,刚好等于a.o的文件长度,如图10所示。

图9 a.0的section table以及所有段的位置和长度

图10 a.o的总长度

重定位表

我们注意到,a.o中有一个叫做“.rel.text”的段,也就是说它是一个重定位表。正如我们最开始所说的,连接器在处理目标文件时,需要对目标文件某些部分进行重定位,即代码段和数据段中哪些对绝对地址的引用的位置。这些重定位的信息都记录在ELF文件的重定位表里面,对于每个需要重定位的代码段和数据段,都会有一个相应的重定位表。比如在a.o中,“.rel.text”就是针对“.text”段的重定位表,因为“.text”段中至少有一个绝对地址的引用,那就是对“printf”的调用;而“.data”段则没有绝对地址的引用,它只包含了几个常量,所以a.o中没有针对“.data”段的重定位表“.rel.data”。

链接的接口---符号

链接过程的本质就是要把多个不同的目标文件之间相互“粘”到一起。为了使不同目标文件之间能够相互粘合,这些目标文件之间必须有固定的规则才行。在链接中,目标文件之间相互拼合实际上是目标文件之间对地址的引用,即对函数和变量的地址的引用。比如目标文件B要用到目标文件A的函数“foo”,那么我们就称目标文件A定义了函数“foo”,称目标文件B引用了目标文件A的函数“foo”。这两个概念同意也适用于变量。每个函数或变量都有自己独特的名字,才能避免链接过程中不同变量和函数之间混淆。在链接中,我们将函数和变量统称为符号,函数名或变量名就是符号名。

我们可以将符号看作是链接中的粘合剂,整个链接过程正是基于符号才能正确完成。链接过程中很关键的一部分就是符号的管理,每一个目标文件都会有一个相应的符号表,这个表里面记录了目标文件中所用到的所有符号。每个定义的符号有一个对应的值,叫做符号值,对于变量和函数来说,符号值就是它们的地址。除了函数和变量之外,还存在其他几种不常用的符号:

  1. 定义在目标文件的全局符号,可以被其他目标文件引用。
  2. 在本目标文件中引用的全局符号,却没有定义在本目标文件,这一般叫做外部符号。
  3. 段名,这种符号往往由编译器产生,它的值就是该段的起始地址。
  4. 局部符号,这类符号往往只在编译单元内部可见。
  5. 行号信息,即目标文件指令与源代码中代码行的对应关系。

ELF符号表结构

ELF文件中的符号表往往是文件中的一个段,段名一般叫“.symtab”。符号表的结构很简单,它是一个Elf32_Sym(32位系统)/Elf64_Sym(64位系统)的数组,每个Elf64_Sym结构对应一个符号。

图11 符号表结构信息

st_name:符号名。这个成员包含了该符号名在字符串表的下标

st_value:符号相对应的值。这个值跟符号有关,可能是一个绝对值,也可能是一个地址等,不同的符号,它所对应的值的含义不同

st_size:符号大小。对于包含数据的符号,这个值是该数据类型的大小

st_info:符号类型和绑定信息

st_other:该成员目前位0

st_shndx:符号所在的段

特殊符号

当我们使用ld作为链接器来链接生产可执行文件时,它会为我们定义很多特殊的符号,这些符号并没有在你的程序中定义,但是你可以直接声明并且引用它,我们称之为特殊符号。其实这些符号是被定义在ld链接器的链接脚本中的。目前你只需认为这些符号是特殊的,你无须定义它们,但可以声明它们并且使用。链接器会在将程序最终链接成可执行文件的时候将其解析成正确的值,注意,只有使用ld链接生产最终可执行文件的时候这些符号才会存在。几个很有代表性的特色符号如下:

  1. __executable_start:该符号为程序起始地址,注意,不是入口地址,是程序的最开始的地址。
  2. __exext或_exext或etext:该符号为代码段结束地址,即代码段最末尾的地址
  3. _edata或edata:该符号为数据段结束地址,即数据段最末尾的地址
  4. _end或end:该符号为程序结束地址
  5. 以上地址都为程序被装载时的虚拟地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/158085.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

忘记开机密码啦!我教你用ventoy找回密码

文章目录 一、前言二、实战过程三、动态演示四、原理解析五、总结 一、前言 当你有一天突然忘记了开机密码怎么办&#xff1f;又要上电脑店花个几十块让人弄&#xff1f;在上一章《你该自己学学安装操作系统了&#xff0c;小白一学就会&#xff08;ventoy装系统超详细&#xff…

Unity设计模式——建造者模式

Product类——产品类&#xff0c;由多个部件组成。 class Product {IList<string> parts new List<string>();//添加产品部件public void Add(string part){parts.Add(part);}public void Show(){foreach (string part in parts){Debug.Log("产品:"pa…

python+深度学习+opencv实现植物识别算法系统 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的植物识别算法研究与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;4分工作量&#xff1a;4分创新点&#xff1a;4分 &#x1f9ff; 更多…

Android+Appium自动化测试环境搭建及实操

1、Appium简介1.1 Appium概念1.2 Appium工作原理 2、Appium Server环境搭建2.1 Java JDK2.1.1 下载JDK2.1.2 运行exe安装JDK&#xff0c;设置安装路径2.1.3 设置环境变量2.1.4 验证安装结果 2.2 Android SDK2.2.1 下载安装Android SDK安装包2.2.2 下载platform-tools&#xff0…

Linux下将驱动编译进内核

在开发的过程中&#xff0c;一般都是将驱动编译成模块&#xff0c;然后将其发送到开发板加载驱动进行功能验证&#xff0c;驱动的功能验证没有问题后就可以将其编译进内核了。本文将介绍如何把上一篇文章Linux下设备树、pinctrl和gpio子系统、LED灯驱动实验中的LED驱动编译到内…

【LeetCode】9. 回文数

1 问题 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向左&#xff09;读都是一样的整数。 例如&#xff0c;121 是回文&…

【计算机网络】IP协议详解

文章目录 一、引入 二、简单认识IP协议 2、1 IP协议基本概念 2、2 IP协议报文格式 2、3 分片与组装 2、3、1 MTU 与 MSS 2、4 网段划分 2、4、1 简单理解路由 2、4、2 IP地址 2、4、3 IP地址的划分 2、4、4 CIDR&#xff08;无类别域间路由&#xff09; 2、4、5 特殊的IP地址 …

Redis微服务架构

Redis微服务架构 缓存设计 缓存穿透 缓存穿透是指查询一个根本不存在的数据&#xff0c;缓存层和存储层都不会命中&#xff0c;通常出于容错的考虑&#xff0c;如果从存储层查不到数据则不写入缓层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询&#xff0c;失去…

【Nginx32】Nginx学习:随机索引、真实IP处理与来源处理模块

Nginx学习&#xff1a;随机索引、真实IP处理与来源处理模块 完成了代理这个大模块的学习&#xff0c;我们继续其它 Nginx 中 HTTP 相关的模块学习。今天的内容都比较简单&#xff0c;不过最后的来源处理非常有用&#xff0c;可以帮我们解决外链问题。另外两个其实大家了解一下就…

Java IO流

IO 即 Input / Output &#xff0c;输入输出流。IO流在Java中分为输入流和输出流&#xff0c;而根据数据的处理方式又分为字节流和字符流。 Java IO 流的 40 多个类都是从如下 4 个 抽象类基类中派生出来的。 InputStream /Reader : 所有的输入流的基类&#xff0c;前者是字节…

大数据flink篇之三-flink运行环境安装(一)单机Standalone安装

一、安装包下载地址 https://archive.apache.org/dist/flink/flink-1.15.0/ 二、安装配置流程 前提基础&#xff1a;Centos环境&#xff08;建议7以上&#xff09; 安装命令&#xff1a; 解压&#xff1a;tar -zxvf flink-xxxx.tar.gz 修改配置conf/flink-conf.yaml&#xff1…

IDEA通过Docker插件部署SpringBoot项目

1、配置Docker远程连接端口 找到并编辑服务器上的docker.service文件。 vim /usr/lib/systemd/system/docker.service在下面ExecStart替换成下面的 ExecStart/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock2.重启docker systemctl daemon-reload s…

交叉熵Loss多分类问题实战(手写数字)

1、import所需要的torch库和包 2、加载mnist手写数字数据集&#xff0c;划分训练集和测试集&#xff0c;转化数据格式&#xff0c;batch_size设置为200 3、定义三层线性网络参数w&#xff0c;b&#xff0c;设置求导信息 4、初始化参数&#xff0c;这一步比较关键&#xff0c;…

强化学习(Reinforcement Learning)与策略梯度(Policy Gradient)

写在前面&#xff1a;本篇博文的内容来自李宏毅机器学习课程与自己的理解&#xff0c;同时还参考了一些其他博客(懒得放链接)。博文的内容主要用于自己学习与记录。 1 强化学习的基本框架 强化学习(Reinforcement Learning, RL)主要由智能体(Agent/Actor)、环境(Environment)、…

【学习笔记】项目进行过程中遇到有关composer的问题

composer.json内容详解 以项目中的composer.json为例&#xff0c;参考文档。 name&#xff1a;composer包名type&#xff1a;包的类型&#xff0c;project和library两种keywords&#xff1a;关键词&#xff0c;方便别人在安装时通过关键词检索&#xff08;没试过&#xff0c;好…

《C++ Primer》练习9.52:使用栈实现四则运算

栈可以用来使用四则运算&#xff0c;是一个稍微有点复杂的题目&#xff0c;去学习了一下用栈实现四则运算的原理&#xff0c;用C实现了一下。首先要把常见的中缀表达式改成后缀表达式&#xff0c;然后通过后缀表达式计算&#xff0c;具体的原理可以参考这位博主的文章&#xff…

抖音直播招聘小程序可以增加职位展示,提升转化率,增加曝光度

抖音直播招聘报白是指进入抖音的白名单&#xff0c;允许在直播间或小视频中发布招聘或找工作等关键词。否则会断播、不推流、限流。抖音已成为短视频流量最大的平台&#xff0c;但招聘企业数量较少。抖音招聘的优势在于职位以视频、直播方式展示&#xff0c;留存联系方式更加精…

到底什么是5G-R?

近日&#xff0c;工信部向中国国家铁路集团有限公司&#xff08;以下简称“国铁集团”&#xff09;批复5G-R试验频率的消息&#xff0c;引起了行业内的广泛关注。 究竟什么是5G-R&#xff1f;为什么工信部会在此时批复5G-R的试验频率&#xff1f; 今天&#xff0c;小枣君就通过…

公司文件防泄密软件——「天锐绿盾」@德人合科技

天锐绿盾是一款企业级数据安全解决方案&#xff0c;主要用于保护企业的知识产权、客户资料、财务数据、技术图纸、应用系统等机密信息化数据不外泄。 PC访问地址&#xff1a; &#x1f517;isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 该软件解决方案…

vue单页面应用使用 history模式路由时刷新页面404的一种可能性

原先使用的是 hash模式路由&#xff0c;因为要结合qiankun进行微前端改造&#xff0c;改成了 history模式&#xff0c;结果页面刷新之后没有正确渲染组件。按照一般思路检查 nginx配置 try_files $uri $uri/ /index.html;也配置上了&#xff0c;还是有问题。 页面异常显示 问题…