《程序员自我修养》读书笔记 第四章 静态链接

书中举了个例子:
 
在经过编译之后 接下来就要把这两个a.o,b.o文件合并在一起。
 

 
4.1 空间与地址分配
 
方法有很多种,最简单的就是:按序叠加
就是把不同目标文件,一个接一个连在一起。
 
这样的话,如果有上百个目标文件,那么最终的文件中就有上百个.text段,.data段,等等,由于他们每个都要对齐,浪费空间。(在x86中,段的对齐是按页对齐的,每个段都要至少有4KB)。
 
那么可以通过    相似段合并    的方法解决。就是把不同目标文件的text,data放在一起。
为了实现这一方法,链接器一般使用    两步链接  (Two-pass Linking)  的方法。
第一步    空间与地址分配
    扫描所有输入文件,获得各个段的长度,属性,位置,建立统一的符号表;
第二步    符号解析与重定位
    读取重定位信息,数据,符号解析,重定位。
 
我们来尝试一下书上的例子。用
ld a.o b.o -e main -o ab
来链接,发现结果和书上的类似:
a.o 和 b.o 中VMA为0,ab中VMA不为0(虚拟空间在ab中才分配)
 


4.2 符号解析和重定位
 
我们按照书上的介绍查看一下a.o 和 ab的反汇编情况:
 
注意,这个a.o 是通过gcc -c 来编译而成的,-c就是只编译和汇编,不进行链接!
movl $0x64,0x1c(%esp) 对应的就是 int a = 100
接下来,调用swap函数,进行的是参数的右向左压栈,先压shared的地址,这里面的压栈并没有esp-4的过程,都在最开始的esp-0x20里面一起减掉了,一种编译器优化吧。shared的地址是0x0!这是因为没有进行链接,不知道shared的地址!用0x0代替!不仅如此,我们可以看到,main的入口的地址是0000000,从0开始,明显不是真正的装载的地址。
再来看看ab的情况:
 
这里面,main的地址已经是0x080408094 了,并且这里面的shared的地址已经从0,变为0x080490f8
swap函数的地址也是变为 80480bc
 
重定位表
可见,从a.o b.o 变为 ab,链接器调整了一些地址,但是哪些地址需要调整?这就是重定位表的工作!
之前在ELF文件格式中,有提到过这个重定位表,比如用objdump -r 命令来查看a.o
 
 
我们可以看出,有两个重定位表项,shared的偏移量是0x15,0x15就是代码段中,0x0的位置。
 
符号解析
通过 readelf -s 命令来查看a.o 文件
 
我们看到,shared 和 swap 是UND ,没有定义的!并且后面三个的Bind方式是全局的,不是LOCAL的~
 
 

 
4.3 COMMON块
 
关于这个问题,在这个文章中有充分的介绍:
 


4.4 C++ 相关问题
 
C++ 有两个特性,重复代码消除 和 全局构造和析构
需要编译器和链接器共同完成!
 
重复代码消除:
c++ 的模板等特性,会产生很多重复的代码。
 
全局构造和析构
C++的全局对象在main之前生成,在main结束之后被析构。
ELF中定义了两个特殊的段
.init
进程的初始化代码
.finl
进程的终止指令
 

4.5 静态库链接
 
最简单的c语言程序,输出Hello world ,也并不是那么理所当然,其中要调用printf函数,printf函数在Linux平台下,是通过glibc库来提供的。
 
wiki: 
GNU C 库(GNU C Library,又称为glibc)是一种按照LGPL许可协议发布的,公开源代码的,免费的,方便从网络下载的C的编译程序。GNU C运行期库,是一种C函数库,是程序运行时使用到的一些API集合,它们一般是已预先编译好,以二进制代码形式存在Linux类系统中,GNU C运行期库通常作为GNU C编译程序的一个部分发布。

Glibc最初是自由软件基金会为其GNU操作系统所写,但目前最主要的应用是配合Linux内核,成为GNU/Linux操作系统一个重要的组成部分。

 
 
 

一个静态库可以看成是一组目标文件的集合
 
利用ar -t libc.a 可以查看libc.a 中包含了哪些目标文件,这里面有很多的文件,其中和printf相关的有:
 
这么一大堆,这令人想起JOS实习中的库函数,在JOS中也有不少和这里命名相关的函数,估计JOS的实现者就是参考了linux,glibc的实现。
 
书中还尝试能不能只用printf.o去链接Hello world程序,显然是不行的。
 
利用  gcc -static --verbose 来展示整个编译,链接的中间过程.
 


4.6 链接过程控制
 
对于普通的程序,在链接的时候通过默认的链接规则是可以成功的,但是对于操作系统内核,BIOS程序,Boot Loader程序,内核驱动程序等等,它们对链接有着特殊的要求,比如每个段的地址可能有一些硬性的要求等等。
 
链接控制脚本
链接器ld可以通过链接脚本控制链接过程。
ld -verbose 查看默认的链接脚本
 
这个就是脚本的一部分,后面还有很多。
可以用 ld -T link.script 来更改控制脚本。
 
最小的程序
这里面尝试不用printf函数来输出信息,并且一般程序的入口在库的_start,由库进行初始化后调用main函数来执行程序的主体部分,这里尝试不用main这个函数。
另外,经典的Hello world程序会分为.text,.data等很多段,这里尝试把所有的信息都放在tinytext段中。
在Linux系统下,想要通过汇编语言在控制台中输出,要进行系统调用,Linux的系统调用是通过int 0x80中断实现的,这个当时在做JOS的时候,好像在Lab 3 中有实现系统调用的分配和处理什么的,以前在上微机原理实验的时候记得DOS的系统调用也是类似的,但是DOS的int号种类比较多吧?
Linux下,int 0x80 , 用eax存调用号,ebx,ecx,edx存参数
这里面用 WRITE 和EXIT 两个系统调用:
int write(int filedesc,char*buffer,int size)
 
自己动手尝试一下:
 
 
发现可以正常运行!
 
同时,我们可以使用一个简单的链接脚本:
 
这里面,ENTRY表示入口函数
. = 0x
 
 
 
 
 
 
 





posted @ 2013-07-07 14:36  二哥拉手网  阅读(202)  评论(0编辑  收藏  举报