阅读笔记-程序员的自我修养7

7.1 为什么要动态链接
要解决空间浪费和更新困难这两个问题最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不再将它们静态地链接在一起。简单地讲,就是不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。也就是说,把链接这个过程推迟到了运行时再进行,这就是动态链接(Dynamic Linking)的基本思想

磁盘和内存中只存在一份Lib.o,而不是两份。另外在内存中共享一个目标文件

7.2简单的动态链接例子
如果foobar()是一个定义与其他静态目标模块中的函数,那么链接器将会按照静态链接的规则,将Program1.o中的foobar地址引用重定位;如果foobar()是一个定义在某个动态共享对象中的函数,那么链接器就会将这个符号的引用标记为一个动态链接的符号,不对它进行地址重定位,把这个过程留到装载时再进行。
Lib.so中保存了完整的符号信息(因为运行时进行动态链接还须使用符号信息),把Lib.so也作为链接的输入文件之一,链接器在解析符号时就可以知道:foobar是一个定义在Lib.so的动态符号。这样链接器就可以对foobar的引用做特殊的处理,使它成为一个对动态符号的引用。

动态链接程序运行时地址空间分布
Lib.so与Program1一样,它们都是被操作系统用同样的方法映射至进程的虚拟地址空间,
Program1除了使用Lib.so以外,它还用到了动态链接形式的C语言运行库libc-2.6.1.so。
ld-2.6.so,它实际上是Linux下的动态链接器。动态链接器与普通共享对象一样被映射到了进程的地址空间,在系统开始运行Program1之前,首先会把控制权交给动态链接器,由它完成所有的动态链接工作以后再把控制权交给Program1,然后开始执行。

readelf -l Lib.so 可以看到
动态链接模块的装载地址是从地址0x00000000开始的。我们知道这个地址是无效地址
共享对象的最终装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况,动态分配一块足够大小的虚拟地址空间

7.3 地址无关代码 SO的代码
7.3.2 装载时重定位

7.3.3 地址无关代码 PIC, Position-independent Code
四类地址引用方式。
共享对象模块中的地址引用按照是否为跨模块分成两类:模块内部引用和模块外部引用;
按照不同的引用方式:指令引用和数据访问
在这里插入图片描述

第一种是模块内部的函数调用、跳转等。
第二种是模块内部的数据访问,比如模块中定义的全局变量、静态变量。
第三种是模块外部的函数调用、跳转等
第四种是模块外部的数据访问,比如其他模块中定义的全局变量。

产生的汇编码
第一种 模块内部调用–call 函数
因为被调用的函数与调用者都处于同一个模块,它们之间的相对位置是固定的。模块内部的跳转、函数调用都可以是相对地址调用,或者是基于寄存器的相对调用,所以对于这种指令是不需要重定位的
call 函数
e8 偏移 ( 偏移看大小端和负数(补码))

类型二 模块内部数据访问–相对寻址。PC+偏移量
我们知道,一个模块前面一般是若干个页的代码,后面紧跟着若干个页的数据,这些页之间的相对位置是固定的

类型三 模块间数据访问 —GOT Global Offset Table
模块间的数据访问目标地址要等到装载时才决定。我们前面提到要使得代码地址无关,基本的思想就是把跟地址相关的部分放到数据段里面,很明显,这些其他模块的全局变量的地址是跟模块装载地址有关的。ELF的做法是在数据段里面建立一个指向这些变量的指针数组,也被称为全局偏移表(Global Offset Table,GOT),当代码需要引用该全局变量时,可以通过GOT中相对应的项间接引用,它的基本机制如图7-7所示
在这里插入图片描述当指令中需要访问变量b时,程序会先找到GOT,然后根据GOT中变量所对应的项找到变量的目标地址。每个变量都对应一个4个字节的地址,链接器在装载模块的时候会查找每个变量所在的地址,然后填充GOT中的各个项,以确保每个指针所指向的地址正确。由于GOT本身是放在数据段的,所以它可以在模块装载时被修改,并且每个进程都可以有独立的副本,相互不受影响。

GOT如何做到指令的地址无关性
可以在编译时确定GOT相对于当前指令的偏移。
然后我们根据变量地址在GOT中的偏移就可以得到变量的地址
GOT中的各个项在连接时填充

类型四 模块间调用、跳转
GOT中相应的项保存的是目标函数的地址

7.3.4 共享模块的全局变量问题
–每个主程序一个副本,如果变量在共享模块中被初始化,复制数据段
–进程私有,线程共享
–R_X86_64_COPY
所有的使用这个变量的指令都指向位于可执行文件中的那个副本。
ELF共享库在编译时,默认都把定义在模块内部的全局变量当作定义在其他模块的全局变量,也就是说当作前面的类型四,通过GOT来实现变量的访问。
当共享模块被装载时,如果某个全局变量在可执行文件中拥有副本,那么动态链接器就会把GOT中的相应地址指向该副本,这样该变量在运行时实际上最终就只有一个实例。
如果变量在共享模块中被初始化,那么动态链接器还需要将该初始化值复制到程序主模块中的变量副本;如果该全局变量在程序主模块中没有副本,那么GOT中的相应地址就指向模块内部的该变量副本。

其他需求情况
共享数据段
多进程共享全局变量又被叫做“共享数据段”,在介绍Windows DLL的时候会碰到它。
线程私有存储
而多个线程访问不同的全局变量副本又被叫做“线程私有存储”(Thread Local Storage)

7.3.5 数据段地址无关性

7.4 延迟绑定(PLT)Procedure Linkage Table
延迟绑定(Lazy Binding)的做法,基本的思想就是当函数第一次被用到时才进行绑定(符号查找、重定位等),如果没有用到则不进行绑定。

bar@plt:
jmp *(bar@GOT)   //.got.plt中存没存bar?没存就是下一条指令地址
push n           //符号引用在重定位表“.rel.plt”中的下标。
push moduleID    //将模块的ID压入到堆栈
jump _dl_runtime_resolve

7.5
7.5.1 “.interp”段
7.5.2 “.dynamic”段
“.dynamic”段可以看成是动态链接下ELF文件的“文件头”
readelf -d Lib.so
7.5.3 动态符号表
“.symtab”(Symbol Table) 静态的 readelf -s
“.dynsym”只保存了与动态链接相关的符号
“.symtab”中往往保存了所有符号,包括“.dynsym”中的符号

7.5.4 动态链接重定位表
静态链接:“.rel.text”代码段的重定位表,“.rel.data”数据段的重定位表。
动态链接:
.rel.dyn 数据 修正“.got”以及数据段
.rel.plt 函数
其实就是许多xxx@plt的代码,跳转真正函数或修正(首次)“.got.plt”
在这里插入图片描述

R_386_RELATIVE比较特殊
static int a;
static int* p = &a; p就是relative
共享对象被装载到地址A,那么实际上该变量a的地址为A+B,
即p的值需要加上一个装载地址A

7.5.5 动态链接时进程堆栈初始化信息

7.6 动态链接的步骤和实现
共享对象全局符号介入(Global Symbol Interpose)

7.7显示运行时链接

总结


静态链接 的外部符号 绝对地址

  1. 生成的xx.o 外部符号地址都是0
  2. 数据段的重定位表,每个重定位入口是一个外部符号的引用,表明在汇编码偏移位置,要把0修改
  3. ld时,全局符号表UND的,要根据重定向入口改汇编码
  4. 最后生成可执行文件

动态链接 的地址无关代码
模块内

  1. 不知道装载地址
  2. 汇编码为:本模块的pc(隐含模块装载的位置+指令在模块的偏移)+目标符号与指令的偏移

外模块的地址无关代码 通过GOT间接寻址

  1. .data的GOT地址:PC+偏移 ; 符号:符号在GOT的偏移
  2. GOT在装载时填上
  3. GOT间接寻址

GOT+延迟绑定

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 程序员自我修养是一本技术类图书,通过以文字方式呈现,将核心内容以PDF文件的形式呈现给读者。 《程序员自我修养》一书是由俞甲子、俞甲子联合编写的,该书主要讲述了程序员在技术方面的自我提升与修养。书中详细介绍了程序员所需具备的技能与素质,并通过实例、案例等方式来进行详细解析。 首先,本书着重强调了程序员的技术素养。作为一名合格的程序员,必须具备扎实的编程基础知识,熟练掌握至少一种编程语言,并能够灵活应用于实际项目中。同时,还要不断学习新的编程技术和工具,提高开发效率和质量,不断追求技术的创新与突破。通过深入的技术讲解和实例分析,读者可以更好地理解和掌握这些关键技能。 其次,本书重视程序员的思维与方法。除了技术之外,合理的思维方式和解决问题的方法也是程序员必备的素养。本书通过探讨算法、数据结构、设计模式等内容,引导读者形成良好的编程思想和解决问题的思路。在实际开发过程中,程序员能够运用这些思维和方法,更加高效地解决实际问题。 此外,本书还提到了程序员的团队合作和沟通能力的重要性。现代软件开发往往需要多人合作完成,团队合作和沟通能力至关重要。本书通过讲解项目管理、代码规范、团队协作等方面的内容,帮助读者更好地适应团队开发的环境。 总之,《程序员自我修养》在技术、思维方式和团队合作等多个方面对程序员自我提升与修养进行了全面而详细的讲解。通过阅读文字版PDF,程序员可以更好地了解自身在技术和素养方面的不足,并通过实践和学习不断提升自己。 ### 回答2: 《程序员自我修养》是一本由俞甲子编写的程序员必读经典之一。这本书涵盖了计算机科学的基础知识和程序员在日常工作中所需要的修养和技能。 首先,这本书强调了程序员应具备的基本素质。不仅要有扎实的计算机基础知识,还要有广泛的知识储备和求知欲。书中指出,只有具备全面的知识背景,程序员才能适应不断变化的软件开发环境,并能够快速学习和掌握新的技术。 其次,书中提到了程序员的编程能力。编程是程序员的基本技能,而编程能力的提升不仅仅局限于语法和算法的掌握,更要关注代码的可维护性、可读性和可测试性。此外,书中还提到了代码风格规范、代码重构和代码调试等重要的编程技巧。 此外,这本书还介绍了程序员应具备的工程素养。程序员的工作不仅仅是写代码,还包括需求分析、架构设计、项目管理等方方面面。程序员应该具备良好的团队合作和沟通能力,能够与其他人合作完成一个项目。此外,书中还提到了软件工程的重要原则和方法,如模块化、面向对象设计、测试驱动开发等。 除此之外,书中还特别强调了程序员自我提升和学习能力。程序员应保持持续学习的态度,不断跟进行业的最新发展,并积极参与技术社区和开源项目,与其他程序员互动交流,共同进步。 总之,《程序员自我修养》是一本涵盖广泛知识和技能的书籍,其中介绍了程序员应具备的基本素质、编程能力、工程素养和自我提升等方面的内容。这本书对于程序员的职业发展和能力提升有着重要的指导作用,值得每位程序员认真阅读和实践。 ### 回答3: 《程序员自我修养》是一本由俞甲子编著的程序员修养指南,以PDF文字版的形式呈现。本书旨在帮助程序员们提升自己的理论基础和实践能力,进而追求个人的成长和职业发展。 《程序员自我修养》主要分为理论和实践两部分。在理论部分,作者深入浅出地介绍了计算机系统的基本原理、计算机网络的工作原理、软件工程中的常用设计模式等。通过这些理论知识的学习,程序员们能够更好地理解计算机系统的运行机制,从而编写更高效、稳定的程序。 而在实践部分,本书通过丰富的实例和案例,引导程序员们进行实践和项目开发。它包括了代码调试和优化的技巧、算法与数据结构的实践应用、多线程与并发编程的方法等。通过这些实践指导,程序员们能够更好地应对实际问题,并提高代码质量和效率。 《程序员自我修养》文字版的PDF形式具有以下优点:一是方便携带和阅读,读者可以随时随地进行学习和查阅;二是易于搜索和标注,可以快速定位到自己关注的内容,并进行个性化的学习笔记;三是多平台兼容,可以在各种设备上进行阅读,满足不同读者的需求。 总之,《程序员自我修养》文字版的PDF形式是一本帮助程序员们提升自己的重要工具,通过学习其中的理论知识和实践经验,程序员们能够更好地应对工作中的挑战,提升自身的竞争力,实现个人价值的最大化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值