程序员自我修养-总结 (2)

  1. Segment 是从装载的角度重新划分了 ELF 的各个段,在将目标文件链接成可执行文件的时候,链接器会尽量把相同权限属性的段分配在同一空间。比如可读可执行的段都放一起,如代码段。系统正是按 Segment 而不是 Section 来映射可执行文件的。总的来说,“Segment” 和 “Section” 是从执行视图与链接视图来划分同一个 ELF 文件。

  2. ELF 可执行文件中有一个专门的数据结构叫做程序头表 (Program Header Table)用来保存 “Segment” 的信息,由于 ELF 目标文件不需要被装载,所以没有程序头表,而 ELF 的可执行文件和共享库文件都有。在数据段里面建立一个指向全局变量的指针数组,称为全局偏移表(Global Offset Table, GOT)当代码需要引用该全局变量时,可能通过 GOT 中相对应的项间接引用。

  3. 地址无关代码技术(PIC,Position-independent Code): 把指令中那些需要修改的部分分离出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本。延迟绑定(Lazy Binding): 就是当函数第一次被用到时才进行绑定(符号查找、重定位等),如果没有用到则不进行绑定。所以程序开始执行时,模块间的函数调用都没有进行绑定,而是需要用到时才由动态链接器来负责绑定,这样的做法可以大大加快程序的启动速度,特别有利于一些有大量函数引用和大量模块的程序。

  4. 动态链接比静态链接慢的主要原因是:1.动态链接下对于全局和静态的数据访问都要进行复杂的 GOT 定位,然后间接寻址;对于模块间的调用也要先定位 GOT, 然后再进行间接跳转。2.动态链接的链接工作在运行时完成,即程序开始执行时,动态链接器都要进行一次链接工作,其会寻找并装载所需要的共享对象,然后进行符号查找地址重定位等工作,这些工作势必会减慢程序的启动速度。

  5. ELF 将 GOT 拆分成了两个表叫做 “.got” 和 “.got.plt”。其中 “.got” 用来保存全局变量引用的地址,".got.plt" 用来保存函数引用的地址,也就是所有对于外部函数的引用全部被分离出来放到了 “.got.plt” 中。另外 “.got.plt” 还有一个特殊的地方是它的前三项是有特殊意义的,分别含义是:第一项保存的是".dynamic" 段的地址,这个段描述了本模块动态链接相关的信息;第二项保存的是本模块的 ID; 第三项保存的是 __dl_runtime_resolve() 的地址。其中后面两项由动态链接器在装载共享模块的时候负责将它们初始化。

  6. 动态链接情况下,可执行文件的装载与静态链接情况基本一样。首先操作系统会读取可执行文件的头部,检查文件的合法性,然后从头部的 “Program Header” 中读取每个 “Segment” 的虚拟地址、文件地址和属性,并将它们映射到进程虚拟空间的相应位置,这些步聚与静态链接基本无异,在静态链接情况下,操作系统接着就可以把控制权转给可执行文件的入口地址,然后程序开始执行。但是在动态链接情况下,由于可执行文件里对于很多外部符号的引用还处于无效地址的状态,即还没有跟相应的共享对象中的实际位置链接起来。所以在映射完可执行文件之后,操作系统会先启动一个动态链接器(Dynamic Linker)。

  7. 在 Linux 下,操作系统在加载完动态链接器 ld.so 之后,就将控制权交给动态链接器的入口地址,当动态链接器得到控制权之后,它开始执行一系列自身的初始化操作,然后根据当前的环境参数,开始对可执行文件进行动态链接工作。当所有动态链接工作完成以后,动态链接器会将控制权转交到可执行文件的入口地址,程序开始正执行。

  8. 动态链接 ELF 中的 “.dynamic” 段,这个段里面保存了动态链接器所需要的基本信息,比如依赖于哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址等。动态链接的文件中,也有类似的重定位表分别叫做 “.rel.dyn” 和 “.rel.plt”。其中 “.rel.dyn” 实际上是对数据引用的修正,它修正的位置位于 “.got” 以及数据段;而 “.rel.plt” 是对函数引用的修正,它所修正的位置位于 “.got.plt”。

  9. 动态链接的自举(Bootstrap)代码满足两个条件:(1)其本身不依赖于其他任何共享对象 (2)其本身需要的全局和静态变量的重定位工作由它本身完成。动态库的装载主要是通过: dlopen、dlsym、dlerror、dlclose 这 4 个函数来完成的。

  10. dlopen 第一个参数 filename 如果是相对路径,那么由以下三个顺序去查找该动态库文件:(1)查找由环境变量 LD_LIBRARY_PATH 指定一系列目录 (2)查找由 /etc/ld.so.cache 里面所指定的共享库路径。 (3)/lib、/usr/lib。如果将第一个参数 filename 设置为 0,那么 dlopen 返回的将是全局符号表的句柄,其中这全局符号表包括了程序的可执行文件本身、被动态链接器加载到进程中的所有共享模块以及在运行时通过 dlopen 打开并且使用了 RTLD_GLOBAL 方式的模块中的符号。

  11. dlopen 的第二个参数,表示函数符号的解析方式,常量 RTLD_LAZY 表示使用延迟绑定,当函数第一次被用到时才进行绑定,即 PLT 机制;而 RTLD_GLOBAL 它表示将被加载的模块的全局符号合并到进程的全局符号表中,使得以后加载的模块可以使用这些符号。dlopen 的加载过程基本跟动态链接器一致,在完成装载,映射和重定位以后,就会执行 “.init” 段的代码然后返回。

  12. dlerror 如果返回 NULL , 则表示上一次调用成功;如果不是,则返回相应的错误的信息。dlclose 它的作用就是将一个已经加载的模块卸载,系统会维持一个加载引用计数器,每次使用 dlopen 加载某模块时,相应的计数器加一;每次使用 dlclose 卸载某模块时,相应计数器减一。只有当计算器值减到 0 时,模块才被真正地卸载掉。卸载的过程跟加载刚好相反,先执行 “.finit” 段的代码,然后将相应的符号从符号表中去除,取消进程空间跟模块的映射关系,然后关闭模块文件。

  13. libname.so.x.y.z,其中 x 表示主版本号,y 表示次版本号,z 表示发布版本号。总体来说,/lib 和 /usr/lib 是一些很常用的、成熟的,一般是系统本身所需要的库; 而 /usr/local/lib 是非系统所需的第三方程序的共享库。任务一个动态链接的模块所依赖的模块路径保存在 “.dynamic” 段里面,由 DT_NEED 类型的项表示。动态链接器对于模块的查找有一定的规则:如果 DT_NEED 里面保存的是绝对路径,那么动态链接器就按照这个路径去查找;如果 DT_NEED 里面保存的是相对路径,那么动态链接器会在 /lib、/usr/lib 和由 /etc/ld.so.conf 配置文件指定的目录中查找共享库。为了程序的可移植性和兼容性,共享库的路径往往是相对的。

  14. LD_LIBRARY_PATH 环境变量,可以临时改变这个应用程序的共享库查找路径,而不会影响系统中的其他程序。在 LD_PRELOAD 里面指定的文件会在动态链接器按照固定规则搜索共享库之前装载,它比 LD_LIBRARY_PATH 里面所指定的目录中的共享库还要优先。无论程序是否依赖于它们,LD_PRELOAD 里面指定的共享库或目标文件都会被装载。

  15. 由于全局符号介入这个机制的存在,LD_PRELOAD 里面指定的共享库或目标文件中的全局符号就会覆盖后面加载的同名全局符号,这使得我们可以很方便地做到改写标准 C 库中的某个或某几个函数而不影响其他函数,对于程序的调试或测试非常有用。

  16. strip 工具可以清除掉共享库或可执行文件的所有符号和调试信息。程序的环境由内存、运行库、系统调用三个部分组成,其中系统调用充当了程序与内核交互的中介。Linux 默认情况下将高地址的 1GB 空间分配给内核。栈,用于维护函数调用的上下文,离开了栈函数调用就没有实现;堆,用来容纳应用程序动态分配的内存区域;可执行文件映像,由装载器在装载时将可执行文件的内存读取或映射到这里;保留区,是对内存中受到保护而禁止访问的内存区域的总称。

  17. 在经典的操作系统里,栈向低地址增长,堆向高地址增长。压栈的操作使栈顶的地址减少,弹出的操作使栈顶地址增大。malloc 申请的内存,当进和结束以后就不会存在了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值