1.静态链接和静态库
静态链接是由链接器在链接时,将库的内容加入到示例程序中的过程。其中使用的库就是我们通常所说的静态库。
静态库可以简单的看成成一组目标文件的集合,即多个目标文件经过压缩打包形成的一个文件。(通过ar工具)
2.静态链接过程
现在的链接器空间分配策略基本上采用一种叫两步链接的方法。然后整个链接过程分为两步:
- 第一步:空间与地址分配。
扫描所有的输入目标文件,获得他们的各个段的长度,属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个位数符号表。
这一步中,链接器将能够获得所有输入目标文件的段长,变为他们合并,计算出输出文件中各个段合并后的长度和位置,并建立映射关系。
- 第二步:符号解析与重定位。
使用初步中收集到的所有信息,读取输入文件中段的数据,重定位信息,并进行符号解析和重定位,调整代码中的地址等。
3.静态库的制作和使用
查看静态库包含的目标文件
-t表示列出包含的所有目标文件
ar -t libc.a
截取部分。可以看到glibc库包含了很多目标文件。
解压目标文件
-x表示解压库文件,交替解压所有
ar -x libc.a //解压指定文件 ar -x libc.a printf.o //从libc.a中解压指定printf.o
制作目标文件
-r:替换替换文件中已有的文件或加入新文件;-c:没有创建库的时候发出警告;-s:创建索引
ar -rcs libab.a a.o b.o ar -rcs *.o libc.a
静态库使用
-L:指定链接链接;-l后面跟随链接的库名(去掉lib)
gcc -Llinkpath -lname gcc -L./ -lab (libab.a 恰头去尾)
4.实例分析:最小的程序
知道了静态链接过程,以及程序大部分运行时都离不开libc,那么可不可以绕开glibc,写一个最小程序呢?
另外程序可以没有main 函数吗?
答案是可以,这里涉及到操作系统的系统调用和底层知识,如果对操作系统和汇编体系比较熟悉,当然可以绕开。系统调用留待后续介绍。
如图,为一个最小的程序,并且没有 main 函数入口:
手动链接
编译:gcc -c -fno-builtin nomain.c -m32
-fno-builtin:关闭 GCC 内置函数功能
-m32:指定 32 为系统。默认是 64 位的,不加会报错。
链接:ld -m elf_i386 -static -e nomain -o helloworld nomain.o
-m :设置文件输出格式为 elf_i386。
-static:表示静态链接.
-e nomain:设置程序入口函数为 nomain
使用链接脚本
事实上,链接器在链接时默认有个链接脚本。函数的入口地址,以及各段分部,都可以通过链接脚本控制。我们程序当然也可以指定链接脚本。链接脚本的语法规则本文暂不介绍。
链接:ld -m elf_i386 -static -T nomain.lds -o helloworld nomain.o
通过该脚本指定入口符号为nomain、将代码段、数据段和只读数据段合并为tinytext,同时舍弃.comment,编出来的程序只有几百个字节,大大缩小了空间。
-ENTRY:指定符号的值为入口地址。
-. = 0x80480000 + SIZEOF_HEADERS 。将当前虚拟地址设置为 0x80480000 + SIZEOF_HEADERS(为输出文件头大小)。
-tinytext : {*(.text) *(.data) *(.rodata)}。将代码段、数据段或者只读数据段合并为 tinytext 段。
-/DISCARD/ : {*(.comment)}。将.comment 丢弃
5.总结
本文主要介绍了,静态链接的过程以及静态库的制作和使用。旨在帮助大家理解库文件和目标文件的关系。
了解静态链接过程,有助于我们调试和分析问题。尤其是在没有源码的情况下,可以通过objdump找到符号表,对应的目标文件位置,然后通过ar将对应目标文件解压出来,最后通过反汇编来分析问题。
另外,main函数作为程序入口已经成为约定俗称,修改入口函数,仅作娱乐使用,切勿使用正式项目,毕竟代码还是要有可读性。
细心的朋友已经发现了,静态链接占的空间大,而且如果多个程序都用了同一份静态库,会极大的浪费资源。那么它存在的价值在哪里呢?