IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 读书计划--程序员的自我修养(二) -> 正文阅读

[系统运维]读书计划--程序员的自我修养(二)

目录

一、引言

二、第二章

------> 2.1、编译过程
------> 2.2、编译器

三、第三章

------> 3.1、目标文件的类型
------> 3.2、目标文件的格式
------> 3.3、反汇编来查看elf文件
------> 3.4、ELF结构详细描述
------> 3.5、链接的接口–符号

一、引言

本篇文章会和大家介绍《程序员的自我修养》中的第二、三章内容。

二、第二章

1、编译过程

一个程序,从代码到可在某个平台上执行的二进制文件,其中中间还有很多流程,需要用到很多工具,而现在的IDE,将编译器、链接器等都集成进去了,所以给我们的假象就是程序写好编一下就能运行了

预处理

主要处理代码中’#‘开始的预编译指令,如’#include’、’#define’等
同时还会删除所有的注释,添加行号

gcc -E hello.c -o hello.i
编译

将预处理完的文件进行词法分析、语法分析等操作,最终产生汇编文件,是最复杂的部分之一,由编译器完成

gcc -S hello.i -o hello.s

现在的GCC框预编译和编译合成一个步骤,叫做cc1的程序

cc1 hello.c

所以实际上gcc命令只是这些后台程序的包装,它会根据不同的参数去调用不同的程序,如预编译编译程序cc1、汇编器as、链接器ld等

有兴趣的朋友可以去看下交叉编译器目录下的bin,就有如下命令
在这里插入图片描述

汇编

汇编器是将汇编代码转变成机器可以执行的指令,这步骤比较简单,通常只需要根据汇编指令和机器指令的对照表翻译即可

gcc -c hello.s -o hello.o

也可以用如下命令

as hello.s -o hello.o

也有命令可以直接从源文件开始生成目标文件

gcc -c hello.c -o hello.o
链接

链接是将目标文件与库链接为可执行程序或库文件,通常我们的程序都会依赖很多库文件,包括系统库与外部库等

2、编译器

编译器的工作就是将高级语言翻译成机器语言,为什么要有这个流程呢。
其实核心也是分层的思想,让我们在编写程序的时候可以忽略硬件(CPU)的不同,用统一的高级语言去编写,同时高级语言也更加容易理解。
至于需要在哪个平台上运行,就选用哪个编译器翻译,就像我们经常使用到的交叉编译器一样

具体的编译过程我这里就不详细展开了,有兴趣的小伙伴可以自己去看一下

静态链接

静态链接其实就是告诉目标文件调用外部接口的地址,让我们可以直接引用其他模块的函数、全局变量而无需关系地址
静态链接是在形成可执行程序前,就是将多个目标文件链接在一起并最终形成一个可执行文件

静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
在这里插入图片描述

动态链接

动态链接的进行则是在程序执行时

动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。

而且如果在程序运行的时候找不到这个动态库,程序会无法继续往下运行

gcc的相关参数

-L选项告诉编译器去哪里找需要的库文件,-lstack告诉编译器要链 接libstack库,-I(大写的i)选项告诉编译器去哪里找头文件

三、第三章

源代码经过编译器编译后生成的文件叫做目标文件,此时的目标文件已经是可执行文件格式,但是还没有经过链接过程,其中有些符号和地址还没有被调整,剩余部分和真正的可执行文件已经一样了

1、目标文件的类型

主流PC平台的可执行文件
Windows:PE
Linux:ELF

除了可执行文件,动态链接库、静态链接库、core文件也是按照这个格式来储存的,其中静态链接库是将很多目标文件捆绑在一起再加上一些缩影形成的文件

linux下可以使用file来查看文件的文件格式,我们随便来看一个交叉编译器下的文件

可执行文件
在这里插入图片描述
.o文件
在这里插入图片描述
.so动态库
在这里插入图片描述
core转储文件
在这里插入图片描述

2、目标文件的格式

目标文件中的内容需要包含代码、数据、链接时所需要的的一些信息(符号表、调试信息)等
一般目标文件将这些信息按不同的属性,以段的形式存放,通常一个目标文件包含以下几个段

代码段(.code 或 .text)

源代码编译后的机器指令通常放在代码段

数据段(.data)

全局变量和局部静态变量存放在数据段

bss段(.code 或 .text)

未初始化的全局变量和局部静态变量存放在bss段
bss段只是为没有初始化的全局、局部静态变量预留位置,并没有内容,在文件中也不占空间

整体的框图如下
在这里插入图片描述

文件头

如上所示,ELF文件的开头还会有一个文件头,描述了整个文件的属性、是否可执行、静/动态链接、入口地址、目标硬件、目标操作系统等
同时,文件头中还会有一个段表,段表描述文件中各个段的数组,其在文件中的偏移位置及段的属性,可以得到每个段的信息。
头文件后面就是各个段的内容

运行时的文件内存分布

这里找到一张可以很形象描述运行时程序的内存分布
在这里插入图片描述
局部变量在函数中运行中动态的分配和释放,在elf中并没有对应的段。
使用malloc和free函数动态申请的空间在运行时在堆中分配,elf中也没有对应的段。

其他部分

在elf文件中还有其他段,这里就简单介绍下,主要都是和链接有关

.interp段
动态链接器在操作系统中的位置不是由系统配置决定,也不是由环境参数指定,而是由 ELF 文件中的 .interp 段指定。该段里保存的是一个字符串,这个字符串就是可执行文件所需要的动态链接器的位置,常位于 /lib/ld-linux.so.2。(通常是软链接)

dynamic 段
该段中保存了动态链接器所需要的基本信息,是一个结构数组,可以看做动态链接下 ELF 文件的“文件头”。存储了动态链接会用到的各个表的位置等信息。

dynsym 段
该段与 “.symtab”段类似,但只保存了与动态链接相关的符号,很多时候,ELF文件同时拥有 .symtab 与 .synsym段,其中 .symtab 将包含 .synsym 中的符号。该符号表中记录了动态链接符号在动态符号字符串表中的偏移,与.symtab中记录对应。

.got(全局偏移表)和.plt(过程链接表)是由动态链接器操纵的两个主要结构。.got是变量的间接表,.plt是函数的间接表。每个可执行文件或库(称为“共享库”)都有其自己的.got和.plt,这些是该共享库所引用的符号的表,这些表实际上包含在其他共享库中。

.dynsyn段:
包含有关共享库中符号的所有信息(定义的符号和需要引用的外部符号。).dynsyn不包含实际的符号名称。这些包含在.dynstr中,而.dynsyn具有指向.dynstr的指针。.gnu.hash是用于按名称快速查找符号的哈希表。它还仅包含指针(指向.dynstr的指针和用于创建存储桶链的指针。)

书上也有相关的介绍,如下图
在这里插入图片描述

查看elf格式的命令

可以通过"readlf"或"objdump"来查看elf文件

在这里插入图片描述
其中 “objdump -h” 就是想ELF文件中各个段的基本信息打印出来
CONTENTS :表示该段在文件中存在,这里可以看到 BSS段在文件中并没有存在
VMA :(Virtual Memory Address):运行地址,程序运行前,要把程序的内容,拷贝到对应的内存地址处,然后才能运行的。
LMA :(Load Memory Address): 加载地址. 因为我们知道程序运行前要经过:编译,链接,装载,运行等过程。装载到哪呢?没错,就是LMA对应的地址里。

LMA与VMA的区别
如果将程序运行比作吃饭,LMA就好比是厨房,食物需要在厨房准备好。VMA好比是餐厅,在餐厅享受食物。
大多数情况下,那么LMA和VMA是一样的,也就是,程序被加载到内存的什么地方,也就在什么地方运行。

其中除了我们之前提过的几个部分,还有很多其他段
.rodata:只读数据段
.comment:注释信息端
.note.GNU-stack:堆栈提示段

最终得出实际的分布
在这里插入图片描述

size命令

size 命令可以专门来查看ELF文件的代码段、数据段、BSS段
在这里插入图片描述

3、反汇编来查看elf文件

代码段
objdump -s -d file	//-d可以将所有包含指令的段反汇编   -s是以十六进制打印出来

在这里插入图片描述
可以看到和上面用 -h打印出来的大小是一样的,至于详细的汇编指令,这里就不说啦,毕竟自己也忘的差不多了,后面需要在补习一下吧

数据段

数据段可以使用如下命令查看,这里就不详细说了

objdump -x -s -d file

4、ELF结构详细描述

先来看一张图,去掉了一些繁琐的结构,将最重要的结构提取出来
在这里插入图片描述
最前部是头,前面也说了,在头部中最重要的就是段表,除此之外还有符号表等,我们就先来看一下头部

文件头

先来看一下之前用objdump看到的elf段分布
在这里插入图片描述
可以看到,第一个段是代码段,偏移值是0x40,那么这0x40放的是什么呢?就是文件头

我们可以使用 readelf 来查看elf头部的信息
在这里插入图片描述
可以看到定义了文件机器长度、存储方式、运行平台、入口地址(Entry point address)等

更多详细的头文件解析这里就暂时不分析了,后面如果用到的话再展开

段表

ELF文件中有各种各样的段,段表就是用来描述各个段信息的,同样可以使用readelf -s来看,之前我们使用"objdump -h"将elf中关键的段显示出来,而忽略了其他辅助性的段,如符号表、段名字符串表等。
在这里插入图片描述
在这里插入图片描述
这里就可以看到头部了,可以更之前我们用 objdump打印出来的对比

我们再来看一下段表在elf文件中的位置
在这里插入图片描述
其中,段的类型都有段表中的对应字段决定,而和段是什么名字没有关系,所以段表中也有其他字段来表示段的属性,这里就不展开讲了

重定位

将重定位表前,先讲一下什么是重定位。

重定位的发生是由于一个作业装入到与其地址空间不一致的存储空间所引起的,对有关地址部分的调整过程称为地址的重定位。这个调整过程就是把作业地址空间中使用的逻辑地址变换成主存中物理地址的过程。这种地址变换也称为地址映射.

举个例子,我们程序在运行前,会将其中链接的静态库都放入内存中,当程序开始运行时,运行到了这个库中的外部接口,就需要跳转到外部库中的这个地址,去运行这个接口,地址就有程序内部的逻辑地址转换到实际的物理地址,这个过程就叫做重定位

重定位分为两种:静态重定位、动态重定位
1、静态重定位:即在程序装入内存的过程中完成,是指在程序开始运行前,程序中的各个地址有关的项均已完成重定位,地址变换通常是在装入时一次完成的,以后不再改变,故称为静态重定位。
2、动态重定位:它不是在程序装入内存时完成的,而是CPU每次访问内存时 由动态地址变换机构(硬件)自动进行把相对地址转换为绝对地址。动态重定位需要软件和硬件相互配合完成。

重定位表

我们前面用"readelf -s"时看到在代码段后有一个".rela.text",这就是重定位表,链接器在处理目标文件时,必须对引用绝对地址的地方进行重定位,.rela.text就是针对.text段的重定位表,.rela.data就是针对.data段的重定位表,关于重定位表,会在下一章详细将

字符串表

ELF文件中用到了很多字符串,如段名、变量名等。所以我们将字符串集中起来存放到一个表中,之后就可以根据在表中的偏移找到这个字符串

字符串表在ELF中也以段的形式保存,我们刚刚用readelf -S就可以看到
在这里插入图片描述
可以看到字符串表有两类
shstrtab:段表字符串表,存放段表中用到的字符串,如段名
symtab:字符串表,存放普通的字符串

5、链接的接口–符号

链接的本质就是将多个不同的目标文件拼接到一起,而这个拼接实质上就是目标文件之间对地址的引用,即对函数和变量地址的引用。
举个例子,目标文件B用到了目标文件A中的某个函数,我们称文件A定义了该函数,文件B引用了该函数。同理,变量也一样。
所以每个函数和变量都需要有自己独特的名字,才能避免被混淆

在链接中,我们将函数和变量统称为符号,函数名和变量名就是符号名。

每个目标文件中都有一个符号表,记录了这个目标文件中所用到的所有符号,每个符号都有一个对应的值,叫做符号值。
对于变量和函数来说,符号值就是它们的地址。当然,还存在几种不常用的符号,所有分类如下

符号的种类

1、定义在目标文件中的全局符号,可被其他文件引用,比如"main"等

2、在文件中引用到的全局符号,但在本文件中没有定义,如"printf"等

3、段名,由编译器产生,就是该段的起始地址,如".data",".text"等

4、局部符号,就是局部变量,只在编译单元内部使用,常用来分析转储文件,不用于链接

5、行号信息,即目标文件指令与源代码中代码行号的对应关系,为可选项

其中前面两项最为重要,链接过程只会用到前面两项,后面三项对于其他文件来说都是不可见的

符号表的查看

我们可以使用很多命令来查看符号表:readelf、objdump、nm等

这里举个例子,我写了一个有两个目标文件链接而成的可执行文件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/c4e21a07e1cb49c48e72d534a8d25b25.png

符号表结构

符号表在elf中为一个段,叫做".symtab",符号表为一个"Elf32_Sym"的数组,每个符号对应一个符号结构体"Elf32_Sym",定义如下
在这里插入图片描述
在这里插入图片描述
我们这里直接看直接写的实例来分析

在这里插入图片描述
在这里插入图片描述
我们看下面".symtab"就行了
Num:为符号表的数组下标
Value:符号值
Size:符号大小
Type:符号类型
在这里插入图片描述

Bind:绑定信息
在这里插入图片描述

Vis:没用到,暂时不讲
Ndx:符号所处的段
如果在本目标文件中定义,则为所在段的段表下标,如果不在本目标文件中定义,或者特殊符号,见下标
在这里插入图片描述

Name:符号名称

大家可以自己对照看一下

特殊符号

当我们使用ld作为链接器时,它会为我们定义很多特殊的符号,这些符号虽然没有在我们的程序中定义,但我们却可以直接声明并定义它们,下面讲几个比较有代表性的特殊符号
在这里插入图片描述

extern “C”

C++为了与C兼容,定义了一个声明用来处理C语言代码,

extern "C" {
	int a;
	int fnc(int);
}

C++编译器会将extern "C"大括号内的代码当作C来处理

弱符号与强符号

我们在编程中经常会遇到符号重复定义的问题,多个目标文件内含有相同名字全局符号的定义,链接时就会报符号重定义的错。
这种符号的定义就被成为强符号,而有些符号的定义被成为弱符号
对于C/C++语言来说,编译器默认函数和初始化了的全局变量被称为强符号,未初始化的全局变量被称为弱符号
同时我们也可以通过"attribute((weak))"来将任何一个强符号定义为弱符号。

针对强弱符号的概念,链接器会按照如下规则处理与选择被多次定义的符号
1、不允许强符号被多次定义,如果有,报重复定义的错
2、如果一个符号在某个目标文件中为强符号,在其他文件中为弱符号,则选择强符号
3、如果一个符号在所有目标文件中都为弱符号,则选择占用空间最大的一个

强引用与弱引用

强引用:我们对外部文件的符号引用在目标文件被链接成可执行文件时,如果没有找到该符号的定义,链接器就会报符号未定义的错
弱引用:如果找到该符号的定义,链接器就会引用该定义,如果没有也不会报错,一般对于未定义的弱引用,链接器会默认为0或者特殊值

弱引用的用途:
1、弱引用和弱符号常用于库的链接,比如库中定义的弱符号可以被用户定义的强符号所覆盖,使得程序可以使用自定义版本的库函数。
2、程序可以对某些拓展性功能的引用定义为弱引用,这样当我们去掉了某些功能,也能正常链接,只是缺少了某些功能,而不会报错

同时我们也可以只用编译器会将某些弱引用的值默认为0来做一些判断。

调试信息

我们在调试代码时,经常会使用gdb、addr2line等工具,有兴趣的小伙伴可以看我这篇文章
Linux内核学习–常见的几种调试方式(内核/应用层)
但如果我们想使用这些调试命令,那么在编译时,编译参数必须加上"-g",编译器就会将源代码与目标代码之间的关系(目标代码中的地址对应源代码中的哪一行)、函数和变量的类型、结构体的定义等保存到目标文件内。
编译出来的可执行文件就会含有很多调试信息,使用readelf等命令也可以看到其中多了很多debug相关的段
在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-11-22 12:45:44  更:2021-11-22 12:46:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 0:52:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码