《装载、链接与库》前七章学习笔记

今天偶然翻到前面几章,发现诸多“新鲜点”,造成这样的原因有一部分就是读过后没有即时沉淀先来,形成自己的知识体系。我觉得有必要写一下读书笔记了。读书不写笔记的陋习产生原因是读书少,没有发现这样学习是不行的。没有碰过南墙,所以不会读书。而现在步入大学需要学习的东西太多了,如果不及时沉淀,学习如走马观花,那就意味着很快会遗忘,漏洞百出,日后定会加倍偿还。反思结束,开始笔记。
吐槽一下,我markdown目录那么体系的分类,转进csdn里,目录只能显示到最大的几个,再排版耗费精力没必要了,就这样了。

壹·温故知新

1.计算机软件程序体系

1.框架

大致可以分为:应用程序-操作系统应用程序接口(运行库)-系统调用接口-操作系统内核/驱动程序-硬件接口(硬件规格)-硬件

每一个接口由下一层定义。操作系统提供多任务系统,所有程序以进程的方式运行在比操作系统权限更低的级别。系统分配资源,让cpu在多进程下不断切换,从而造成多个程序同时运行的假象。

当前成熟的操作系统出现后,硬件被抽象成了一系列概念。在Unix中,硬件的访问形式跟访问普通的文件形式一样;在Windows中,图形硬件被抽象成GDI,声音和多媒体设备被抽象成了DirectX对象,磁盘被抽象成了普通文件系统等等。操作系统开发者提供为硬件厂商提供了一系列接口和框架,硬件厂商开发驱动程序来符合这些要求,我们可以看到,操作系统是多么的重要。

2.文件系统

提到硬件那么不得不提到文件系统,文件系统管理着磁盘中文件的存储方式。文件系统保存了这些文件的存储结构,负责维护这些数据结构并且保证磁盘能够有效地组织利用起来。

例如我们在linux中读取文件,通常会使用一个read的系统调用(读到这里思考了一下,我现在理解成read是从外文件读入内存,而write是从内存读到外文件,平常的键盘输入与终端输出也被抽象成了类似磁盘文件,不知道这样理解对不对)文件系统收到read请求后,判断文件的内容分布在磁盘的那个位置,如判断一个文件的4096字节位于第1000号逻辑扇区到1007号逻辑扇区。在判断完毕位置后就向磁盘驱动程序发出请求,磁盘驱动程序收到请求后向硬件发出硬件命令。

向硬件发送I/O命令的方式有很多种,其中最为常见的一种是通过读写I/O端口寄存器实现。在x86平台上共有65536个硬件端口寄存器,不同的硬件被分配到了不同的I/O端口地址。CPU提供了两条专门的指令in和out来实现对硬件端口的读和写。

对IDE接口的举例在p13。实际情况下,驱动程序那一步还十分复杂,这个程序还会考虑硬件状态。

内存通过段映射与分页的方式高效地将物理地址与虚拟地址分隔开。未来章节会再提到,现在不展开。

虚拟映射需要硬件的支持,对不同的cpu来说有所不同,但几乎都用到了MMU(Memory Management Unit)的部件。

CPU发出虚拟地址,经过MMU变成物理地址,一般MMU集成在CPU的内部。

3.线程

在linux中,实际线程和进程的在系统里数据结构是一样的,结构里有参数来分辨其是否够有从属关系。因此线程有时也被称为轻量级进程。

访问权限关系:

​ 线程私有:局部变量,函数的参数,TLS数据(线程局部存储)

​ 线程共享:全局变量,堆上的数据,函数里的静态变量,程序代码,打开的文件

线程调度与优先级

线程通常至少拥有三种状态,分别为:

​ 运行:此时线程正在执行

​ 就绪:此时线程可以立刻运行,但cpu已经被占用

​ 等待:此时线程正在等待某一事件(通常是I/O或同步)发生,无法执行。

线程调动的方式又包括优先级调度和轮换法。

linux多线程

​ 系统调用:

​ fork:复制当前进程

​ exec:使用新的可执行文件映像覆盖当前可执行映像

​ clone:创建子进程并从指定位置开始执行

同步与锁:

​ 相关概念:二元信息量,信息量,互斥量,临界量,读写锁,条件变量。

多线程内部情况

用户级线程(多对一)内核级线程(一对一)组合(多对多)

如下图:文章链接http://t.csdn.cn/2VDvt


贰·静态链接

二、编译与链接

gcc hello.c

这条指令分为四个步骤:预处理-编译-汇编-链接 .cpp .i .s .o .out

1.编译链接过程总览

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

主要处理内容:

​ 1.将所有的#define删除,并展开所有的宏定义

​ 2.处理所有条件预编译指令,比如#if #ifdef #elif #else #endif

​ 3.处理#include预编译指令,将所包含的文件插入到该预编译指令的位置。注意,这个过程为递归进行的,也就是说被包含的文件可能 还包含其他文件

​ 4.删除注释

​ 5.添加行号和文件名,以便于编译时编译器产生调试用的行号信息及用于编译时产生错误警告显示行号

​ 6.保留所有的#program编译器指令,因为编译器需要它们

2.编译
gcc -S hello.i -o hello.s

编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后产生相应的汇编代码文件

先行GCC把预编译和编译两个步骤合并成一个步骤,并使用一个叫做cc1的程序来完成两个步骤。

或者也可以

gcc -S hello.c -o hello.s

实际上,gcc这个指令使这些后台程序,如cc1的包装,它会根据不同的参数要求去调用预编译编译程序cc1、汇编器as、链接器ld

3.汇编
as hello.s -o hello.o
或
gcc -c hello.s -o hello.o

或者直接从源文件变成目标文件

gcc -c hello.c -o hello.o

汇编器是将汇编代码转变成机器可执行的指令,每一个汇编语句几乎都对应一条机器指令(若有所思…),该步骤只是将汇编指令与机器指令对应翻译。

4.链接

后续会详细讲到。其实就是将多文件链接一起,产生可执行文件。

2.编译器

1.词法分析

通过扫描器(lex),用算法将字符分割成一系列记号。如我们常说的标识符,数字,运算符号。

一般分为如下几类:关键字、标识符、字面量(数字、字符串等)和特殊符号(如加号、等号)

2.语法分析

由语法分析器(yacc)对扫描器产生的记号进行语法分析,生成语法树。简单来讲,语法树就是以表达式为节点的树。

如果出现表达式不合法,如符号不匹配,表达式缺少操作都会进行报错。

3.语义分析

由语义分析器对静态语义进行分析。

静态语义通常包括声明和类型的匹配,类型的转换。这一步对表达式标识了类型,对符号表中的符号类型进行了更新。

4.中间语言生成

由源码级优化器进行。语法树被转换成中间代码,如常见的三地址吗形式,例:t1=1+2。之后再进行优化,如计算数字,替换位置。

中间代码使编译器分为前端和后端。前端负责生成与目标机器无关的中间代码。

5.目标代码生成与优化

编译器后端包括代码生成器,目标代码优化器。这一步将其转换成了可阅读的汇编代码。这里也可以体现一下不同标准的汇编代码区别,如mov与movl。

3.模块拼装–静态链接

链接过程包括地址和空间分配、符号决议和重定位等。这里一点小思考。我们平常写的头文件其实是一堆符号定义,也就是之后会被原封include进的,一个工程包括多个源文件,这多个源文件事实上都会被编译成多个对应的目标文件,而这些目标文件,最后进行静态链接产生了我们熟知的可执行文件。(暂且不提动态链接与运行库)

三、目标文件

目标文件从结构上讲,它是已编译后的可执行文件的格式,只是还没经过链接。

1.目标文件的格式

先行pc平台流行的可执行文件格式主要是windows下的PE和linux下的ELF。它们都是COFF文件的变种。

ELF文件类型包括:

​ 1.可重定位文件

​ 2.可执行文件

​ 3.共享目标文件

​ 4.核心转储文件

我们可以在Linux中使用file命令来查看相应的文件格式

file hello.o
file /ld-2.6.1.so

2.目标文件的内容

objdump -h hello.o   //查看目标文件结构,-x会把更多信息打印出来
size hello.o //查看elf文件的代码段、数据段和bss段的长度,dec十进制,hex十六进制
objdump -s -d hello.o //-s可以将内容以16进制打印出来,-d可以将所有包含指令的段反汇编,用来查看代码段
1.代码段

不用多说,就是代码,但是目标文件没有重定位。

2.数据段和只读数据段

如我们的字符串会被放到只读数据段。数据段保存了已经初始的全局变量和局部静态变量。

3.bss段

存放未初始化的全局变量和局部静态变量。因为未初始化默认为0,只定义符号存在,但暂时不分配空间。

未初始化全局变量可能还会放在COMMON块,这与强弱符号有关,后面再说。

4.其他段

如.rodatal .comment .debug .dynamic .hash .line .note .strtab .symtab .shstrtab .plt.got .init .fini

这些段的名字是系统保留的,相对的,也有一些非系统保留的如music,但不可以带”.“前缀,以防冲突。

elf文件中可以拥有多个相同段名的段。

p68有个有意思的Q&A

3.ELF文件结构描述

elf大致结构是这样的,program header table结构先不用在意,这是可执行文件才有的,后续映射关系部分再说。其实这是一个最简单的静态链接可执行文件,实际上要复杂得多。

elf目标文件格式最前部是elf文件头,它包含了整个文件的基本属性,比如elf文件版号、目标机器型号、程序入口地址等。紧接着的就是一系列段。其中与段有关的重要结构是段表,该表描述了elf文件包含的所有段的信息,比如段的段名、段的长度、在文件中的偏移、读写权限及段的其他属性。

后面关于其的具体内容,因为表比较多,不方便打出,我将对一些关键点进行论述补充。

1.文件头
readelf -h hello.o

elf文件头定义了elf魔数、文件机器字节长度、数据存储方式、版本、运行平台、ABI版本、elf重定位类型、硬件平台、硬件平台版本、入口地址、程序头入口和长度、段表的位置和长度及段的数量等

elf文件头结构及相关常数被定义在”/usr/include/elf.h"里。

elf.h使用typedef定义了一套自己的变量体系,p70详细。

2.段表

描述了elf各个段的信息,比如段名、段的长度、在文件中的偏移、读写权限及段的其他属性。也就是说elf文件段结构就是由段表决定的,编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。

前文中我们用objdump -h来查看elf文件中包含的段,但是其实并不完整,省略了很多辅助性的段。我们也可以使用readelf来查看

readelf -S hello.o

示例内容参照p75

elf的结构是以“ELF32_Shdr"结构体为元素的数组。每个结构体对应一个段。这个数组下标0是个无效段。

3.重定位表

如”.rel.text"是针对.text段的重定位表。具体结构暂不展开,后面静态链接再说。

4.字符串表

字符串表中字符串连续以\0分割,引用字符串只需要给出下标即可。

常见符号表包括字符串表,段表字符串表。

字符串表用来保存普通字符串,比如符号的名字;段表字符串用来保存段表中用到的字符串,比如段名。

由此我们得出结论,只要分析elf文件头就可以得到段表和段表字符串的位置,从而破解整个elf文件。

4.链接的接口–符号

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

链接中需要符号充当桥梁。每一个目标文件都有一个相应的符号表。而每个符号又有一个符号值,其就是它们的地址。

符号有多种情况:

​ 1.定义在本目标文件的全局符号,可以被其他目标文件引用

​ 2.本目标文件引用却没有定义在本文件,一般称为“外部符号”

​ 3.段名,它的值就是该段的起始地址

​ 4.局部符号,这类符号只在编译单元内部可见

​ 5.行号信息,即目标文件指令与源代码中代码行的对应关系,它是可选的

1.elf符号表结构

段名一般叫“.symtab"。是以Elf32_Sym为结构的数组。下标0无效。

其中包括:详细p83

​ 符号类型与绑定信息

​ 符号所在的段

​ 符号值

​ 符号大小

​ 符号名(下标)

​ other

readelf -s hello.o   //查看符号表
2.特殊符号

ld链接器链接时定义,详细p85

3.符号修饰与符号签名
1.c++符号修饰

c++符号修饰,避免重复。详细p88

2.extern”C”

其实就是解决符号搜索的问题。

平常没用过,现在都直接c++直接include cmath而不是math.h,我猜原理就在这里。

3.强弱符号

p92 强弱符号与强弱引用 有点东西

四、静态链接

1.空间与地址分配

对相似段合并

第一步 空间与地址分配

获得各段长度、属性和位置,并将它们合并,计算合并后的长度与位置,并建立映射关系。所有符号收集起来放入到全局符号表,方便下面使用。

第二步 符号解析与重定位

使用收集到的信息,读取段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。

ld a.o b.o -e main -o ab  //将a、b链接,以main函数作为入口,ld链接器默认的程序入口为_stat,输出文件名为ab,默认a.out

2.符号解析与重定位

objdump -d a.o   //查看a.o的反汇编代码
1. 重定位
2.重定位表

也可以叫重定位段,每一段有对应的重定位段(如果需要重定位)

objdump -r a.o  //查看重定位表

是一个Elf32_Rel结构的数组。结构包括重定位入口的偏移和重定位入口的类型和符号 。

重定位的过程中,每个重定位的入口都对应一个符号的引用,那么当链接器需要对某个符号进行重定位时,它就要确定这个符号的目标地址。这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表。

3.指令修正方式

32位x86平台下的elf文件的重定位入口所修正的指令寻址方式只有两种:

​ 绝对近址32位寻址

​ 相对近址32位寻址

4.COMMON块

静态链接时,大小大的若符号替代大小小的弱符号(本质上来说,链接器并不知道符号的类型,只知道符号的大小)

bss段在目标文件中,只有定义而无实际分配。在可执行文件中才会有虚拟的地址的分配(虽然实际上还是不会为其文件中实际分配),因为在链接中弱符号的大小已经确定。

3.c++相关问题

p113 很有意思

1.重复代码消除

涉及到模板、外部内联函数、虚函数表等

可选项:函数级别链接 其实就是用到谁,塞入谁。

2.全局构造与析构

相关段:.init .fini

3.c++与ABI

p115 涉及到API与ABI的区分

4.静态库链接与链接脚本

ar -t libc.a //查看libc.a这个静态库文件包含那些目标文件
ar -x libc.a //把libc.a解压到当前目录

只链接需要的语言库是完全不够的,还需要一些辅助性的目标文件和库,这些都被隐藏在了GCC命令中

1.链接脚本
ld -verbose //查看ld默认的链接脚本

默认的链接脚本被放在了“/usr/lib/ldscripts/”。不同的机器平台、输出文件格式都有相应的链接脚本,以lds为拓展名。

链接器控制链接过程有三种方法:

​ 1.使用命令行来给链接器指定参数,如-o、-e

​ 2.将链接指令存放在目标文件里面,编译器通常会通过这种方式来向链接器传递指令

​ 3.使用链接脚本

为了更精准控制链接过程,我们自己也可以指定脚本

ld -T link.script

p125介绍了一个示例,其中有些知识点:

1.GCC内嵌汇编

2.32位系统调用通过0x80中断实现,其中eax为调用号,ebx、ecx、edx传参

3.对于可执行文件来说,符号表和字符串表是可选的,但段字符串表必须保存

p129介绍了链接脚本的语法

2.BFD库

通过一种统一的接口来处理不同的目标文件格式。BFD把目标文件抽象成统一的模型,这个模型中有“文件头”以及一系列段和抽象化的符号表、重定位表之类的。使得BFD库的程序只要通过操作这个抽象的目标文件模型就可以实现操作所有的BFD支持的目标文件格式。现在的GCC、ld、GDB及其他的binutils工具都通过BFD处理目标文件。当需要支持新的目标文件格式时,只需要添加格式,而不是改变编译器,链接器。

第5章windowsPE结构先跳过。

叁·装载与动态链接

六、可执行文件的装载与进程

1.进程虚拟空间

空间大小由寻址能力决定(p152有介绍特殊的扩展方式PAE),一部分划分给操作系统,一部分划分给应用程序。

动态装载方法:覆盖装入(淘汰)和页映射

在前面提到过硬件MMU提供地址转换功能。有了硬件的地址转换和页映射机制,操作系统动态加载可执行文件的方式跟静态加载有了很大的区别。

2.可执行文件的装载

1.进程的建立

建立分三步:

​ 创建一个独立的虚拟地址空间

​ 读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系

​ 将CPU的指令寄存器设置成可执行文件的入口地址,启动运行

1.创建虚拟地址空间

一个虚拟空间由一组页映射函数将虚拟空间的各个页映射至相应的物理空间,那么创建一个虚拟空间实际上并不是创建空间而是创建映射函数所需要的相应的数据结构,在i386的linux下,创建虚拟地址空间实际上只是分配一个页目录就可以了,甚至不设置页映射关系,这些映射关系等到后面程序发生页映射错误时再进行设置,这句话划重点!

2.读取可执行文件头,并且建立虚拟空间与可执行文件的映射关系

上一步的页映射关系函数是虚拟空间到物理内存的映射关系,这一步所做的是虚拟空间与可执行文件的映射关系。这个也划重点。

当程序执行发生页错误时,操作系统将从物理内存中分配一个物理页(照应第一步最后一句话,当时读的时候不仔细,导致后面逻辑没法自洽,晕晕乎乎),然后将“缺页”从磁盘中读取到内存中,再设置缺页的虚拟页与物理页的映射关系,这样程序才能正常运行。

而这第二步与“缺页”读取到内存密切相关。当操作系统捕获到错误时它应当知道程序当前在可执行文件的哪一个位置。这就是虚拟空间与可执行文件的映射关系。从某种意义上讲,这也是整个过程最重要的一步。

tip:可执行文件又被称为映射文件,名字就来源于此

可执行文件与进程虚拟空间的映射关系保存在操作系统内部的一个数据结构上。Linux将进程虚拟空间中的一个段叫做虚拟空间区域(VMA),win叫虚拟段,实际是同一概念。

3.将cpu指令寄存器设置成可执行文件入口,启动运行
2.页错误

通俗来讲,就是cpu照着虚拟地址读,读着读着发现没有对应的内存物理地址了(此时未分配,不理解再理一遍上面三步关系),这时cpu就把操作权限给操作系统,操作系统有专门的页错误处理程序,这时程序就查第二步提的那个数据结构,看看读到哪了(VMA),然后从物理页分配一个页面(第一步),然后把后面要读的存到这个物理页里面,物理页再和虚拟页确定对应关系。最后再切回用户态,进程接着继续。

如果物理页不够了甚至有可能将已分配的内存收回,这就涉及到了操作系统的虚拟存储管理。

3.进程虚拟空间分布

1.Segment

为了避免资源浪费,引入新的概念,“segment”,对于权限相同的段可以把它们合并到一起当作一个segment处理

1个segment可以对应多个段,1个segment对应一个VMA

在将目标文件链接成可执行文件的时候,链接器会尽量把相同权限属性的段分配在一个空间,也就形成了segment。section是静态存储的单位,而segment是动态映射的单位(自己瞎总结的哈哈)

readelf -S hello.elf //查看section
readelf -l hello.elf //查看segment,示例在p163

描述segment的结构被称为了程序头(Program Header),还记得上面给的那张图吧,奇怪的program header table(程序头表,也是个数组结构)。它描述了ELF文件该如何被操作系统映射到进程的虚拟空间。

这些segment也会分类型,如那些是“LOAD”那些是“NOTE",“TLS”,"GNU_STACK”。LOAD类型意味着会映射到内存中。

p165详细对照结构。

其中包括:

​ 1.segment的类型

​ 2.segment在文件中的偏移

​ 3.segment的第一个字节在虚拟地址空间的起始位置

​ 4.segment的物理装载地址,一般与3相同

​ 5.segment在文件中占的空间(可能为0)

​ 6.segment在虚拟地址空间所占的空间(可能为0)

​ 7.权限,如可写可读

​ 8.对齐属性

虚拟地址可能大于文件实际占用空间,如bss区初始化为0,多的区域正好给它(啊,又是它)

2.堆和栈

操作系统通过VMA来对进程的地址空间进行管理,VMA除了与segment一一对应外,还会拿来处理堆和栈。一个进程中堆和栈都会对应一个VMA

p166的那个/proc查看进程示例有点意思。那段数字应该就是进程号吧。让我想起了pwndbg调试时的vmmap,同样可以达到这个效果。

书里还提到了一个VMA叫“vdso”,它的地址已经位于内核空间,事实上它是一个内核的模块,进程可以通过访问这个VMA来跟内核进行一些通信,这里留个疑点,为后面学内核做准备。

VMA不一定于segment完全对应,包括所有段。p168又一次提到了bss的问题(他真的很爱bss,我哭死)

3.段地址对齐

简而言之,是使物理地址充分利用,从文件到物理空间采用直接页分配,并不分割。而从物理到虚拟则对两(多)个segment共用的页映射两遍。p171的图较为清晰。书里那个对齐属性其实说的不太清晰,还举了一个式子,不如直接说从文件开始就是对齐的。

有一点需要强调,这里提到了文件头也会被映射。

4.进程栈初始化

进程刚启动的时候需知道一些进程运行的环境,最基本的就是系统环境变量和进程的运行参数。最常用的办法就是在进程启动前将信息保存到进程的虚拟空间中的栈中。

这次可算搞懂了命令行参数和命令行参数字符串指针数组的意思了,经过程序测试,其实就是启动程序时那行参数,因此argv[0]默认是./文件名。

4.Linux内核装载ELF过程简介

过程:

用户层面,bash进程调用fork()系统调用创建一个新进程,新进程调用execve()系统调用执行指定的ELF文件,原先的bash进程等待刚才启动的新进程结束,然后继续等待用户输入命令。

进入execve()系统调用之后,Linux内核就开始进行真正的装载工作。在内核中,execve()系统调用相应的入口是sys_execve(),再里面调用do_execve()查找可执行文件,找到后读取128字节查看其的可执行文件格式(调用search_binary_handle()),之后调用相应的转载处理过程,例如elf文件:

​ 1.检查elf文件格式的有效性

​ 2.寻找动态链接的“.interp"段,设置动态链接器路径

​ 3.根据程序表头描述,对elf文件进行映射

​ 4.初始化elf进程环境

​ 5.将系统调用的返回地址修改成elf可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的就是文件头中所指的;对 于动态链接的elf可执行文件,程序入口点是动态链接器

上述执行完毕返回do_execve(),返回sys_execve()。从内核态返回用户态,调用上述的入口点。于是elf可执行文件装载完成。

这一章大致讲了进程映射关系,但我觉得后面讲到对齐的时候,关于虚拟空间与可执行文件的映射关系好像就不太清晰了,想要细致研究未来还需要再花精力。

七、动态链接

Linux系统中,elf动态链接文件被称为动态共享对象,一般以”.so"为扩展名,而win则被称为动态链接库,以“.dll"为扩展名

程序与libc.so之间的链接工作由动态链接器完成。

gcc -o hello hello.c ./libc.so

1.地址无关代码

链接时重定位与装载时重定位的概念

1.地址无关代码

通过”-fPIC“开关。指把指令中那些需要修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在进程中拥有一个副本,这种方案被称为地址无关技术。

p192详细举了4种情况

2.共享模块的全局变量问题与数据段地址无关性

p198

2.延迟绑定(PLT)

这个问题我在之前写过一篇,引用一下http://t.csdn.cn/BwSJa

3.动态链接相关结构

操作系统在执行可执行文件前会先启动动态链接器

1.".interp"段

一段字符串,是所需要的动态链接器的路径

2.”.dynamic“段

一个Elf32_Dyn结构数组,其中有这些情况:

​ 1.动态链接符号表的地址

​ 2.动态链接字符串表地址

​ 3.动态链接字符串表大小

​ 4.动态链接哈希表地址

​ 5.本共享对象的”SO-NAME“

​ 6.动态链接共享对象搜索路径

​ 7.初始化代码地址

​ 8.结束代码地址

​ 9.依赖的共享对象文件

​ 10.动态链接重定位表地址

​ 11.动态重定位表入口数量

​ …

readelf -d Lib.so       //查看dynamic段
3.动态符号表

.symtab,类似于符号表,其也拥有动态符号字符串表”.dynstr“,同时拥有辅助的符号哈希表”.hash“

4.动态链接重定位表

”.rel.dyn"表示代码段(包括.got)的重定位表 “.rel.plt"表示函数引用(.got.plt)的重定位表

p210举例说明了一些重定位问题

5.动态链接是进程堆块初始化信息

堆栈里面除了之前提到的进程执行环境和命令行参数等信息,还保留了动态链接器需要的辅助信息数组

p212有定义表

它定义在环境变量指针的后面。p213有示例图。

4.动态链接的步骤和实现

1.动态链接器的自举

”Now life is sane“这句话有被笑到。

2.装载共享对象

通过可执行文件的dynamic段找到所有依赖的共享对象。链接器把名字放在装载集合中,然后依次取出,把其映射到虚拟空间,其符号被丢到全局符号表,为后续重定位做准备。

p215-p218讲述了一个有意思的东西,共享对象全局符号介入问题

3.重定位和初始化

走到这一步其实已经松口气了,全局符号表已经建立,剩下的只需要慢慢遍历可执行文件和每个共享文件的重定位表就可以了。

重定位完成后,如果某个共享对象有”.init“段,那么之后会进行初始化。同样,如果有”.finit"段,之后退出时也会执行。

4.Linux动态链接器的实现

前面静态链接讲到过可执行文件运行的过程,动态链接的区别就在于控制权交给可执行文件前交给了动态链接器。

interp段的路径实际上是个软链接。

动态链接器不仅是个共享对象还是个可执行程序。

5.显示运行时链接

也称为运行时加载,也就是让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载。能够这样运行的共享对象被称为动态装载库。

在Linux中,从文件格式看,动态库实际上与一般的共享对象没有区别。主要区别在于共享对象是由动态链接器在程序启动前负责装载和链接的,这系列步骤由动态链接器自动完成,对于程序本身是透明的;而动态库的装载是通过一系列由动态链接器提供的API,具体地讲共有4个函数,dlopen、dlsym、dlerror、dlclose,程序通过这几个api对动态库操作,它们的声明和相关常量被定义在系统标准头文件<dlfcn.h>。

1.dlopen()

与线程的用法类似,正常open会

2.dlsym()

p224有个符号优先级问题

3.dlerror()

返回NULL说明正常,不正常就返回错误信息

4.dlclose()

4个符号在p224有详细解释,p225有一个示例程序

gcc -o run run.c -ldl //-ldl表示使用DL库(Dynamical Loading)

2022/5/1

《装载、链接与库》学习笔记相关推荐

  1. 第二行代码学习笔记——第六章:数据储存全方案——详解持久化技术

    本章要点 任何一个应用程序,总是不停的和数据打交道. 瞬时数据:指储存在内存当中,有可能因为程序关闭或其他原因导致内存被回收而丢失的数据. 数据持久化技术,为了解决关键性数据的丢失. 6.1 持久化技 ...

  2. 第一行代码学习笔记第二章——探究活动

    知识点目录 2.1 活动是什么 2.2 活动的基本用法 2.2.1 手动创建活动 2.2.2 创建和加载布局 2.2.3 在AndroidManifest文件中注册 2.2.4 在活动中使用Toast ...

  3. 第一行代码学习笔记第八章——运用手机多媒体

    知识点目录 8.1 将程序运行到手机上 8.2 使用通知 * 8.2.1 通知的基本使用 * 8.2.2 通知的进阶技巧 * 8.2.3 通知的高级功能 8.3 调用摄像头和相册 * 8.3.1 调用 ...

  4. 第一行代码学习笔记第六章——详解持久化技术

    知识点目录 6.1 持久化技术简介 6.2 文件存储 * 6.2.1 将数据存储到文件中 * 6.2.2 从文件中读取数据 6.3 SharedPreferences存储 * 6.3.1 将数据存储到 ...

  5. 第一行代码学习笔记第三章——UI开发的点点滴滴

    知识点目录 3.1 如何编写程序界面 3.2 常用控件的使用方法 * 3.2.1 TextView * 3.2.2 Button * 3.2.3 EditText * 3.2.4 ImageView ...

  6. 第一行代码学习笔记第十章——探究服务

    知识点目录 10.1 服务是什么 10.2 Android多线程编程 * 10.2.1 线程的基本用法 * 10.2.2 在子线程中更新UI * 10.2.3 解析异步消息处理机制 * 10.2.4 ...

  7. 第一行代码学习笔记第七章——探究内容提供器

    知识点目录 7.1 内容提供器简介 7.2 运行权限 * 7.2.1 Android权限机制详解 * 7.2.2 在程序运行时申请权限 7.3 访问其他程序中的数据 * 7.3.1 ContentRe ...

  8. 第一行代码学习笔记第五章——详解广播机制

    知识点目录 5.1 广播机制 5.2 接收系统广播 * 5.2.1 动态注册监听网络变化 * 5.2.2 静态注册实现开机广播 5.3 发送自定义广播 * 5.3.1 发送标准广播 * 5.3.2 发 ...

  9. 第一行代码学习笔记第九章——使用网络技术

    知识点目录 9.1 WebView的用法 9.2 使用HTTP协议访问网络 * 9.2.1 使用HttpURLConnection * 9.2.2 使用OkHttp 9.3 解析XML格式数据 * 9 ...

  10. 安卓教程----第一行代码学习笔记

    安卓概述 系统架构 Linux内核层,还包括各种底层驱动,如相机驱动.电源驱动等 系统运行库层,包含一些c/c++的库,如浏览器内核webkit.SQLlite.3D绘图openGL.用于java运行 ...

最新文章

  1. 谷歌「模型汤」靠微调屠了ImageNet的榜!方法竟然只有半页纸
  2. android x86 按键精灵,界面版按键精灵的使用【包含内置浏览器、打开程序的方法】...
  3. Linux下sed命令替换配置文件中某个变量的值(改变包含字符的一行的值)之二——只改变第一出现的那一行
  4. SSH连接服务器报错(WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED)的解决方案
  5. 盘点程序员必备的专业术语,值得看一看
  6. 为operamasks增加HTML扩展方式的组件调用
  7. WifiManager的getScanResults()返回列表为0
  8. ACM PKU 1111 Image Perimeters http://acm.pku.edu.cn/JudgeOnline/problem?id=1111
  9. 解决pycharm在ubuntu下搜狗输入法一直固定在左下角的问题
  10. python 模拟登录验证码_Python模拟登陆 —— 征服验证码 3 CSDN
  11. misc on starcraft----starcraft2
  12. 基于java高德地图经纬度转详细地址和GPS坐标转换为高德地图坐标
  13. 非线性数学模型线性化
  14. HTML META 元数据标签详解
  15. 做PPT只会直接插入图片?这样处理图片,让PPT的颜值瞬间提升几倍
  16. ASP+AJAX实现分页效果[Z]
  17. Android8.1 源码修改之插入SIM卡默认启用Volte功能
  18. 【C++11新特性】 nullptr关键字
  19. 编程语言基础知识点总结程序
  20. 昆明某饭店的师傅正在制作气锅鸡

热门文章

  1. 论文ai生成-一键生成论文的软件
  2. jquery电商分类导航js特效
  3. 大姨说:我外甥女薄荷就是修路由器和网络的。我不服
  4. phpstorm轻松激活
  5. SAPUI5 (05) - 主题 (Theming)
  6. 一文了解类加载机制--ClassLoader
  7. Torch not compiled with CUDA enabled 报错的归纳总结
  8. 艰难的一年!2020年计算机考研年度总结!
  9. NAP客户端计算机隔离测试之三
  10. mib - 管理信息库