首页 > 编程语言 >3.程序员的自我修养 - 完结

3.程序员的自我修养 - 完结

时间:2023-04-10 16:04:53浏览次数:40  
标签:调用 函数 DLL 程序员 地址 修养 完结 共享 链接


目录

第七章 动态链接

7.1动态链接的实现过程

7.2动态链接的步骤和实现

第八章 Linux共享库的组织

第九章 Windows 下的动态链接

9.1DLL简介

9.2DLL优化

9.3DLL HELL

第十章 内存

10.1程序的内存布局

10.2栈的调用惯例

10.3堆与内存管理

第十一章 运行库

11.1入口函数和程序初始化

11.2C/C++运行库

11.3运行库与多线程

11.4C++全局构造与析构

第十二章 系统调用与API

12.1系统调用介绍

12.2系统调用原理


第七章 动态链接

静态链接的不足:每个程序内部除了公用库函数还有相当数量的其它库函数和它们所需的辅助数据结构,浪费内存和磁盘空间(尤其是多进程情况下)、模块更新困难等,此外其对程序的更新、部署和发布带来麻烦。

动态链接:把程序的模块相互分割开,不对那些组成程序的目标文件进行链接,等到程序要运行时才进行链接。如当某程序加载时发现还依赖其它目标文件系统会接着将所需依赖文件(内存中有副本的不需重新加载)全部加载至内存,等所有依赖目标文件加载完毕,系统开始进行链接工作。链接原理与静态链接类似,包括符号解析、地址重定位等。

动态链接文件:Linux中ELF动态链接文件被称为动态共享对象(DSO)简称共享对象,以.so为扩展名;Windows中,动态链接文件被称为动态链接库,以.dll为扩展名。

固定装载存在的问题:采用静态库共享(与静态库有明显的区别)的方式将程序的各个模块统一交给操作系统管理,操作系统在某个特定的地址划分出一些地址块,为那些已知的模块预留足够的空间。存在地址冲突、升级困难等问题。

装载时重定位:在链接时对所有绝对地址的引用不做重定位,而把这一步推迟到装载时再完成,一旦模块装载地址确定,那么系统就对程序中所有的绝对地址引用进行重定位。适用于动态链接库中的可修改数据(可修改数据在不同进程间具有多个副本)部分,而不适用于指令(指令部分无法在多个进程之间共享)部分。

地址无关代码:针对装载时重定位无法解决指令部分在多个进程间共享的问题,将指令中那些需要修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本,该技术即地址无关代码。其4种地址引用方式如下:

3.程序员的自我修养 - 完结_系统调用

共享模块的全局变量问题:为了使得链接过程正常运行,链接起会在创建可执行文件时在他的.bss段创建一个全局变量的副本,所有使用这个变量的指令都位于可执行文件中的那个副本,共享库编译时默认都把定义在模块内部的全局变量当作定义在其它模块的全局变量。

延迟绑定:当函数第一次被用到时才进行绑定,如果没有用到则不进行绑定,这样大大加快程序的启动速度,有利于那些有大量函数应用和大量模块的程序。

7.1动态链接的实现过程

动态链接过程:操作系统读取可执行文件的头部并检查文件的合法性,然后从头部的 Program Header 中读取每个 Segment 的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置,装载完可执行文件后采用动态链接器将外部符号的引用与相应共享对象的实际位置链接,当所有动态链接工作完成后东岱链接器将控制权转交到可执行文件的入口地址,程序执行。

 

7.2动态链接的步骤和实现

动态链接器自举:动态链器本身不可以依赖于其它任何共享对象(编写动态链接器时保证不适用任何系统库、运行库),且动态连接器本身所需的全局和静态变量的重定位工作由它本身完成(启动时通过一段精巧代码控制,同时不用全局、静态变量和函数调用)。这种具有一定限制条件的启动代码往往被称为自举。

自举步骤:动态连接器入口地址即自举代码入口,操作系统将控制权交给动态链接器后,自举代码开始执行。自举代码先找到它自己的GOT(第一个入口保存了.dynamic段偏移地址,由此找到了动态链接器本身的.dynamic段),通过.dynamic中的信息,自举代码可获得动态链接器本身重定位表和符号表等,从而得到动态链接器本身的重定位入口,先将它们全部重定位,此后动态连接器才可以开始使用自己的全局变量和静态变量。

 

第八章 Linux共享库的组织

 

共享库管理问题:大量程序使用动态链接机制导致系统里面存在数量极为庞大的共享对象,若没有很好的方法将这些共享对象组织起来,整个系统中的共享对象文件则会散落在各个目录下,给长期维护和升级造成困难。

共享库版本命名:命名规则为 libname.so.x.y.z ,最前面使用前缀 lib 、中间式库的名字和后缀 .so,最后面跟着的式三个数字组成的版本号,x为主版本号(重大升级与旧版本不兼容)、y为次版本号(增量升级增加新接口,向后兼容)、z表示发布版本号(错误修正和性能改进)。

共享库的存放位置:Linux遵顼FHS标准进行系统文件的存放,包括各个目录的结构、组织和作用。FHS规定一个系统中主要有两个存放共享库的位置(1)/lib 主要存放系统最关键和基础的共享库,如动态链接器、C语言运行库、数学库等;(2)/usr/lib 主要保存一些非系统运行时所需的关键性共享库,主要是开发时用到的共享库;(3)/usr/local/lib 主要存放一些跟操作系统本身不相关的库,主要是一些第三方的应用程序库,如python按章后的共享库。

环境变量:改变共享库查找路径最简单的方法是使用LD_LIBRARY_PATH环境变量,这个方法可以临时改变某个应用程序的共享库查找路径,而不会影响系统中的其它程序,若某个进程设置了该变量则进程启动时动态链接器在查找共享库时会首先查找由该变量指定的目录。LD_PRELOAD里面指定的文件会在动态链接器按照固定规则搜索共享库之前装载,他比LD_LIBRARY_PATH优先级更高,正常情况下应该避免使用LD_PRELOAD。

第九章 Windows 下的动态链接

9.1DLL简介

DLL:动态链接库缩写,相当于Linux 下的共享对象。

符号导出:当一个PE需要将一些函数或变量提供给其它PE文件使用时,我们把这种行为叫做符号导出,如一个DLL将符号导出给EXE文件使用,Windows PE中所有导出的符号被集中存放在导出表(提供一个符号名与符号地址的映射关系)结构中。

EPX文件:创建DLL的同时会得到一个EXP文件,这个文件是链接器在创建DLL时的临时文件,链接器在第一遍遍历所有目标文件并收集所有导出符号信息创建DLL导出表时会将导出表放在临时目标文件EXP中。

 导出重定向:将某个导出符号重定向到另外一个DLL。

导入表:在某个程序中使用了来自DLL的函数或者变量,将该行为称做符号导入。 当某PE文件被加载,加载器将所有需要导入的函数地址确定并将导入表中的元素调整到正确的地址,以实现动态链接过程。

延迟载入:当链接一个支持延迟载入的DLL时,链接器会产生与普通DLL导入非常类似的数据,但操作系统会忽略这些数据,当延迟载入的API第一次被调用时,由链接起添加的特殊的桩代码就会启动,这个桩代码负责对DLL的装载工作。

9.2DLL优化

DLL导入导出问题:DLL的代码段和数据段并非地址无关,它默认被装载到由ImageBase指定的目标地址中,若目标地址被占用,则需要装载到其它地址,从而引起整个DLL的Rebase,对于拥有大量DLL程序,频繁Rebase会造成程序启动速度减慢,此时查找符号的过程也会比较耗时。

DLL优化:(1)重定基地址(所有需要重定位的地方只需加上一个固定差值);(2)对每个导出函数提供一个唯一对应的序号(仅供内部使用的函数可以采用只有序号没有函数名的方法,这样外部使用者就无法推测其含义和使用方法,该方法比函数名导入方法稍快,但不推荐使用该方法作为导入导出的手段);(3)导入函数绑定(大多情况下这些DLL都以同样顺序被装载到同样的内存地址,将导出函数地址保存到模块的导入表中可以省去每次启动时符号解析的过程,DLL更新和DLL发生重定基址会导致绑定地址失效)。

9.3DLL HELL

DLL HELL概述:DLL可能出现版本更新时发生不兼容的问题,三种可能原因导致该问题,(1)使用旧版本的DLL替代原来一个新版本的DLL而引起;(2)新版本DLL中的函数无意发生该表而引起,即不向下兼容;(3)新版本DLL安装引入新BUG。

解决DLL HELL的方法:(1)静态链接,在编译产生应用程序时使用静态链接的方法链接它所需要的运行库;(2)防止DLL覆盖,Windows可使用文件保护足式未授权应用覆盖系统DLL;(3)避免DLL冲突,让每个应用程序拥有一份自己依赖的DLL;(4).NET下的ELL HELL解决方案,.NET框架下一个程序集有应用程序集和库程序集(即DLL动态链接库)两种类型,利用Manifest文件描述程序集名字、版本号及程序集的各种资源。

 

第十章 内存

10.1程序的内存布局

平坦内存模型:整个内存是一个统一的地址空间,用户可以使用一个32位的指针访问任意内存位置。尽管内存空间被称为平坦的,但实际上内存在不同地址区间有不同地位,如大部分系统会将内存空间中的一部分留给内核使用,应用程序无法访问这一段内存(Linux默认将高地址的1GB分配给内核、Windows则分配2GB)

用户空间默认内存空间区域:(维护函数调用上下文,常在最高地址处分配,向低地址增长)、(动态分配的内存区域,通常存在栈的下方,向高地址增长,一般比栈大很多)、可执行映像文件(存储着可执行文件内存在内存的映像)、保留区(内存中受保护而禁止访问的内存区域的总称)等

10.2栈的调用惯例

堆栈帧:栈保存了一个函数调用所需维护的信息,常被称为堆栈帧或活动记录,它一般记录(1)函数的返回地址和参数;(2)临时变量,包括非静态局部变量及编译器自动生成的其它变量;(3)上下文,包括函数调用前后需保持不变的寄存器。

调用惯例:函数调用方和被调用方对于函数如何调用需有一个明确的约定,双方都遵循该约定函数才能被正确调用,其包含(1)函数参数的传递顺序和方式;(2)栈的维护方式,函数将参数压栈后需将被压入的参数全部弹出,该工作可由函数调用方或函数本身完成;(3)名字修饰的策略,不同调用惯例采用不同的名字进行修饰,以进行区分。主要函数调用惯例如下:

3.程序员的自我修养 - 完结_运行库_02

函数返回值传递:函数将返回值存储在eax(4字)中,返回后函数的的调用方再读取eax,当返回值超过4字节时会在调用方函数的栈上额外开辟空间并将该空间的一部分作为传递返回值的临时对象,并在调用时将临时对象的地址作为隐藏参数传递给被调用函数,被调用函数将数据拷贝给临时变量用eax传出,被调用函数返回后调用方函数读取eax指向的临时对象内容。c++返回较大对象会产生非常多的额外开销,对象要经过两次拷贝构造函数的调用才能完成返回对象的传递。

10.3堆与内存管理

堆的作用:栈上的数据在函数返回时会被释放掉,所以无法将数据传递至函数外部,而全局变量没办法动态地产生,只能在编译的时候定义,缺乏表现力,此时堆是唯一的选择。

堆空间:程序向操作系统申请一块适当大小的堆空间,然后由程序自己管理这块空间,管理堆空间分配的往往是程序的运行库,运行库通过堆的分配算法对堆空间进行管理。

Linux进程堆管理:两种堆空间分配方法,brk()系统调用,实际上是设置进程数据段结束地址,可以扩大或缩小数据段;mmap(),向操作系统申请一段虚拟地址空间,该虚拟空间可映射到某文件,当没映射到某文件我们称其为匿名空间。

Windows进程堆管理:用VirtualAlloc()向系统申请空间,与Linux的mmap()相似。通过对管理器实现创建、分配、释放和销毁堆空间,若堆空间不足VirtualAlloc会向操作系统申请更多的内存,直到操作系统没有空间可分配为止。

堆分配算法:管理一大块连续的内存空间,按照需求分配和释放空间,有3种常见的方法:,(1)空闲链表,将堆中各空闲的块按照链表的方式连接起来,用户请求时遍历整个链表直到找到何时大小的块并将其拆分,释放空间时将它们合并到空闲链表中,缺点是一旦链表被破坏堆就无法正常工作;(2)位图,将整个堆划分为大量大小相同的块,当用户请求内存时将整数个块分配给用户,第一个块被称为已分配区域的头,其余的被称为已分配区域的主体,每个块只有头、主体和空闲3种状态,可以在数组中用两位表示一个块,其速度快、稳定性好、无需额外信息便于管理,但易产生内存碎片且位图很大的时候将降低缓存命中率;(3)对象池,一些场合被分配对象的大小是较为固定的几个值,若每次分配空间大小都一样就可以按照这个每次请求分配的大小作为一个单位,把整个堆空间划分为大量的小块,每次请求只需找到一个小块就可以,实现可采用空间链表或位图法。

第十一章 运行库

11.1入口函数和程序初始化

程序的运行步骤:(1)操作系统创建进程并把控制权交给程序入口,该入口一般是运行库中某个函数的入口;(2)入口函数对运行库和程序运行环境进行初始化,包括堆、I/O、线程、全局变量构造等;(3)入口函数初始化完成并表用main执行程序主体;(4)main执行完毕并返回入口函数,入口函数进行清理,包括全局变量析构、堆销毁、I/O关闭等,然后进行系统调用结束进程。

I/O的覆盖范围:包括文件、管道、网络、命令行、信号等,广义讲I/O指代任何操作系统理解为文件的事务。

文件操作:Linux叫做文件描述符,Windows叫做句柄,文件句柄总是和内核的文件对象关联,内核可通过句柄计算出内核里文件对象的地址,但并不对用户开放。

MSVCI/O初始化工作:(1)打开文件表;(2)若能够继承自父进程,则从父进程获取继承的句柄;(3)初始化标准输入输出。

11.2C/C++运行库

C运行库大致包含的功能:(1)启动与退出,包含入口函数和入口函数所依赖的其它函数;(2)标准函数,由C语言标准规定的C语言标准库所拥有的函数实现;(3)I/O;(4)堆;(5)语言实现;(6)调试。

11.3运行库与多线程

多线程运行库:提供两方面的服务(1)提供那些多线程操作的接口,比如创建线程、退出线程、设置线程优先级等函数接口;(2)C运行库本身要能够在多线程环境下正确运行。

线程局部存储TLS如果定义一个全局变量为TLS类型,只需在定义前面加上相应关键字即可,如GCC的_thread,一旦一个全局变量被定义成TLS的类型,那每个线程都会拥有这个变量的一个副本,任何线程对该变量的修改都不会影响其它线程中该变量的副本。

Windows TLS实现:Windows下一个全局变量或静态变量会被放到.data或.bss段,当我们使用_declspec(thread)定义一个线程私有变量时,编译器会把这些变量放到PE文件的.tls段,当新线程启动时,他会从进程堆中分配一块空间并将.tls段中内容复制到这块空间,因此每个线程都有自己独立的.tls副本。

CreateThread() _beginthread()_beginthread()是前者的包装,前者在动态链接下不会出现内存泄漏但在静态链接下会出现内存泄漏,因此在使用CRT时建议尽量使用 _beginthread()、_begintheradex()、_endthread()、_endthreadex() 等函数创建线程。

11.4C++全局构造与析构

 

第十二章 系统调用与API

12.1系统调用介绍

系统调用概念:操作系统将可能产生冲突的系统资源保护起来,阻止应用程序直接访问,这些资源包括文件、网络、IO、各种设备等,在操作系统提供的接口下这些应用程序可进行系统调用访问资源。

Linux系统调用:X86下,系统调用由080中断完成,各个通用寄存器用于传递参数,EAX寄存器用于表示系统调用的接口号,如EAX=1表示退出进程(exit)、EAX=2表示进程创建(fork)、EAX=3表示读取文件或IO(read)、EAX=4表示写文件或IO(write),每个系统调用对应内核源码中一个函数(以sys_开头),常见系统调用如下,位于/usr/include/unistd.h:

3.程序员的自我修养 - 完结_动态链接_03

3.程序员的自我修养 - 完结_动态链接_04

12.2系统调用原理

用户态到内核态的切换:操作系统一般通过中断来从用户态切换到内核态。

操作系统的系统调用:操作系统一般不会用一个中断号来对应一个系统调用,一般用一个或少数几个中断号来对应所有的系统调用。

Windows系统调用:Windows内核提供了数百个系统调用(被称为系统服务)但并没有公开这些系统调用,而是在这些系统调用上建立了一个API,Windows.h 包含了Windows API的核心部分,各类API如下:

3.程序员的自我修养 - 完结_运行库_05

3.程序员的自我修养 - 完结_动态链接_06

 

子系统:又称Windows 环境子系统,他是Windows架设在API和应用程序之间的另一个中间层,使得各种平台的不同应用程序能够兼容运行环境。

标签:调用,函数,DLL,程序员,地址,修养,完结,共享,链接
From: https://blog.51cto.com/u_16063698/6181051

相关文章

  • 2.程序员的自我修养 - 静态链接与程序装载
    目录 第二章静态链接2.1程序编译的过程第三章目标文件的格式3.1目标文件的格式3.2目标文件具体内容3.3ELF文件结构3.4重定位表3.5字符串表3.6链接的接口3.7弱符号与强符号第四章静态链接4.1空间与地址分配4.2符号解析与重定位4.3COMMON块4.4C++相关问题4.5链接控制脚本第五章W......
  • 1.程序员自我修养 - 绪论1
    1.1南北桥      为了协调CPU、内存和高速的图形设备,设计了高速北桥芯片;同时为了协调磁盘、USB、键盘等低速设备,设计了南桥低速芯片。现代计算机北桥已集成到CPU内部,南桥一般指现在的芯片组,新名称为PCH(PlatformControllerHub)。北桥:适配高速设备。北桥芯片则主要是集成了......
  • 程序员面试金典---2
    回文排列思路:回文排列的特征之一就是如果字符串中每个字符的个数都是两个,或者只有只有一个字符个数为奇数个。只有上述两种结果。classSolution:defcanPermutePalindrome(self,s:str)->bool://将字符串转成个数字典s_dic=Counter(s)/......
  • 不要做一个傲慢的程序员
    万事通不会在这个职业中走得太远。昨天,另一位开发人员向我描述了一个问题。看起来应该很简单。所以,我这样告诉他。那是个大错误。问题,而不是陈述我以为我知道答案。但这个问题一直困扰着一位非常聪明的工程师。我应该更明智地考虑我的话。当您遇到新问题时,请在开始陈述之前......
  • 程序员的数学1-1 进制转换
    进制转换人们正常接触的数字为十进制格式,但是电脑读取的格式为0或1即二进制表示。  引用程序员的数学1-p3   引用程序员的数学1-p6 反之,求余求几进制的转换就取几进制的余数  引用程序员的数学1-p7C语言的实现  C语言进制转换代码二进制转换为十......
  • 程序员如何与ChatGPT携手作战
    黄昏将至,还是黎明到来?ChatGPT大火,使得程序员对于”35岁危机“的担忧又加一层:如何在35岁之前避免被AI淘汰?因为ChatGPT擅长语言逻辑类的工作,这不正是程序员擅长的事情么?这不是要先革了程序员的命么?哎,人生真难。实际上,ChatGPT并无意于淘汰任何人,它只是一种更为先进的工具。这......
  • #yyds干货盘点# LeetCode程序员面试金典:四数之和
    题目:给你一个由n个整数组成的数组 nums,和一个目标值target。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a],nums[b],nums[c],nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):0<=a,b,c,d <na、b、c和d互不相同nums[a]+nums[b]+nums[c]......
  • 做个清醒的程序员之努力工作为哪般
    阅读时长约10分钟,共计2268个字如果要问自己这样一个问题:“我们工作的意义到底是什么?”会得到怎样的答案?是为了安身立命?是为了满足别人的期待?是为了得到社会的认同?抑或是索性认为工作是无意义的?如果我说:工作的意义在于自我实现,你会同意吗?你会觉得这样的观点很片面吗?你会觉得这很......
  • 28 岁字节程序员退休,财务自由
    阅读本文大概需要2.9分钟。今天互联网热议最大的一个话题,莫过于字节跳动一28岁程序员大佬,实现财务自由退休了,准备旅居日本经营温泉酒店,让无数人羡慕嫉妒恨啊。这个程序员大佬的名字就不说了,给大家简单说下他的经历吧。1、深圳中学(05-08),高考之后,开始学习写代码;2、暨南大学(08-......
  • 程序员漫漫回乡路--我以前的一些想法
    职业/事业/理想去北京混5到10年,攒50万++,回璩湾,自由职业。电脑培训-软件咨询,网上找点活,开个店。去北京-武汉等城市混,做到技术总监这个级别,然后IT创业。去做零售,副食店-超市-衣服然后在璩湾或襄阳或武汉开店。架构师,技术顾问。IT培训,教学视频,技术写作。保研,混个专硕研究生......