基本修养实战篇(五) 前期所学小结
前面一口气学了不少内容,中间遇到了一些问题,这里及时总结一下,一来是加深印象,巩固记忆
其次也可以把遗留问题明确一下,指导后续的知识点的学习。
- 不同编译器可能采用不同的名字修饰方法,导致产生的目标文件无法相互链接。这也是二进制不兼容问题的来源
- __attribute__((section("Foo"))) 可以把符号放到我们自己指定的段中,以达成一些特殊的目的 【具体用于什么目的?】
- 调试信息就是编译器(gcc -g)在目标文件中添加了很多.debug的段,占用大量的空间,可用strip去除
- 调试信息当前是有标准格式的,叫做DWARF (Debug With Arbitrary Record Format ),是另外一个大的话题
- 链接的过程一般采用两步链接法,首先是扫描所有的目标文件,获取他们各个段的长度,属性和位置,并将输入目标文件中的符号表中的所有符号引用收集起来,统一放到一个全局表中。这一步,链接器就能够获得所有输入目标文件的段长度,然后开始合并,计算合并后的位置和大小。第二步,根据每个文件的重定位表,可以得到要对哪个段的多少偏移的位置(外部符号所在的位置)的值进行调整,然后就可以进行指令修正(这一步是和硬件平台强相关的,所以要选择对应arm 或者x86的编译器,否则程序压根运行不起来)
- 关于指令修正,需要结合arm汇编指令集来理解,这里我打算开一个专题来讲述
- 所谓符号解析的过程,ld a.o, 执行的时候会去找a.o中的UNDEFINE的符号,去哪里找呢,去第一步扫描得到的全局符号表中找,找不到就会报错。所以可以推测,第一步得到全局符号表就是扫描所有的目标文件的符号表,拿到其中的symtbl中的ndx不为UND的符号,最终形成一张大表。进一步的理解,什么时候会出现UND呢,如何判断呢,很简单,编译期间,发现一个符号的定义不在当前文件内,那就是UND, 也就是每个段中都找不到这个符号,那可不就是UND吗。
- 静态库一般由编译器厂商提供,使用ar -t libc.a可以看到每个函数都是一个独立的目标文件。这样的好处是避免为了只链接某个函数而导致引入了很多不必要的空间浪费。ld链接器会自动找到必须的符号以及符号的所有其他依赖,然后把这个目标从libc.a中解压出来,最终链接成一个可执行文件【因此我们链接一个动态库时,最终目标文件的大小可能并不是直接加上静态库的size】
- 我们自己手动执行ld a.o b.o -e main -o ab,和执行gcc a.c b.c -o ab得到的结果是大不相同的,我们自己手动链接的版本甚至压根就无法使用,问题就在编译器链接的时候干的额外的必要的事情还有很多,可以用--verbose选项查看每一个具体的步骤
- 编译器会为我们提供c库,它是和linux以及arm有关,因此叫做arm-linux 编译工具链,它的内涵是什么,如何我有一块arm的板子,然后我给它移植了linux, 那么我就可以用c库的printf. 其实C库的printf是对系统调用的封装。进一步的arm-linux和x86-linux区别在哪,同样是libc.a中的printf函数,同样是调用系统调用,C代码的层级上都是一样的,区别是在汇编的代码中,同样是跳转到系统调用中,arm linux的的跳转指令和x86 linux的肯定是不一样的指令。