做纱窗修水管的一个网站,深圳网站设计要点,友情链接又称,手机网站做适配ARM学习#xff08;25#xff09;链接装载高阶认识 1、例子引出
笔者先引入几个编译链接的例子来介绍一下#xff1a; 声明无效#xff1a;declared implicitly#xff1f;#xff0c;属于编译错误还是链接错误#xff1f; 编译阶段的错误#xff0c;属于编译错误25链接装载高阶认识 1、例子引出
笔者先引入几个编译链接的例子来介绍一下 声明无效declared implicitly属于编译错误还是链接错误 编译阶段的错误属于编译错误因为编译器发现这个函数没有声明声明异常 标识符/符号找不到xxxx is undefined? undefined xxxxx? 无法解析的外部符号属于编译错误还是链接错误? 第一个是结构体找不到属于编译阶段错误相当于类型找不到。 第二个和第三个属于链接错误找不到对应的函数符号。 只编译不实现会出现什么情况 编译可以通过链接可以通过 编译通过链接不过因为链接会关心函数的大小和实现。 补充例子图。 如何骗过编译器/链接器 骗过编译器让编译器认为我们写的代码是OK的 可以编译通过。骗过链接器让链接器认为我们代码正常可以链接成完整的可执行文件比如axf。通常来说我们为了让代码运行可以会编译链接出一个完整的axf此时需要快速解决一些编译问题和链接问题就需要让编译器和链接器认为我们代码是OK的就需要快速适配即“骗过”。比如编译到一个函数test_speed()找不到对应的函数test_speed此时就需要声明一下函数然后就可以编译过**下图1**。然后到链接的时候发现找不到符号Error: L6218E: Undefined symbol test_speed (referred from main.o)。下图2那么只有声明不行需要定义一下所以再加上一个空函数就行此时就可以链接过。下图3 编译链接学习的意义 帮助理解代码执行过程提升代码质量 熟悉处理编译等警告优化代码性能 了解编译优化更好的跨平台开发 各个平台编译差异更深入掌握调试技巧 各自视图 调试不按行 主要有PE和ELF两种可执行文件格式
2、编译链接
整体框图1、预处理 2、编译 3、汇编 4、链接 由下图可以看到
C文件经过预处理可以得到.i文件编译选项-E.i文件经过编译可以得到汇编文件编译选项-S.s文件经过汇编可以得到目标文件.o文件经过打包可以形成静态库.a文件也可以经过与库文件链接形成可执行文件后缀为out或者axf。
2.1 预处理器
预处理的主要内容有如下
#define进行替换处理#if #ifdef等预编译指令展开#include删除 // /* */添加行号和文件名保留Progma指令 …… string.h 文件展开
2.2 编译
编译遵循的语法规则个人总结
函数需要声明不能重复声明变量、结构体不能重复定义变量函数定义需要封号结尾定义变量数组需要指明大小不能为负数 宏与枚举不能重复声明宏需要多行如果多行需要\进行链接包含头文件的路径需要指明需要包含正确的头文件函数的声明和定义需要一致If whilefor等关键字得正确使用注释的正确使用
2.3 链接
链接将目标文件粘贴在一起形成可执行文件。 按.o文件进行地址排序
Main fun - Uart1Init fun Main fun - UartPoll fun每个目标文件为一个section目标文件中首个函数地址均从0开始根据链接顺序依次向后排向后排的大小按照目标文件所有函数的大小后面的符号地址确定后会在前面地址进行修正 按section进行地址排序设置了分割section 属性将每个函数进行section分割
Main fun - Uart1Init funMain fun - UartPoll fun目标文件每个函数为一个section函数地址均从0开始根据链接顺序依次向后排想后排的大小按照函数的大小后面的符号地址确定后会在前面地址进行修正 3、目标文件的认识
3.1 简介
目标文件以.o或者.obj文件结尾是可重定位文件下图1中 RELRelocatable file。
包括了代码和数据 下图2入口地址为0 下图1包括多个section/Segment 下图2Section中包含符号表/重定位表下图3可以被用来链接成可执行文件或者共享库文件遵循ELF文件格式 下图中有365个段包括了bss以及data段以及重定位段等。 Section 链接视图中的段 Segemnt装载视图中的段合并一定相同属性的段
由下图可以看到Section中定义的段到了Segment里面代码都合并成了一段。 比如ER_IROM1 、ER_REGION_HEADER、ER_IROM2 合并了 这样的好处可以减少段零散节省内存同时加载相对简单不需要每个section都去分散加载。
3.2 目标文件分析
目标文件分析分割section 分割section的意思按函数分割为一个段 UART1InitSection10Size 208 Byte重定位后的地址0x08004C5C下图1 UART1PollSection11Size 176Byte重定位后的地址0x08004D2C下图1恰好相差0xD0也就是208Byte下图2。 结论目标文件确定后其大小则确定即链接器按照地址和size依次向后排列确定地址。 从下图4也可以看出最终的可执行文件指令代码和目标文件形成的指令代码是一致的。 图1 图2 图3 图4 目标文件分析文件为section Uart.o 为一个section内部函数按顺序地址递增然后文件之间进行地址排序 Uart.o wifi.o地址0x08007E68 – 0x08009004下图2相差0x119C4508个byte下图3 结论目标文件确定后链接器按照文件地址和size依次向后排列确定地址同时size增大44280 - 62128下图5。 图1 图2 图3 图4 图5
3.3 目标文件重定位
目标文件重定位表记录着哪些位置的值链接器需要进行重定位 表结构两个成员一个offset一个type
typedef struct rel_table_struct
{u32 offset;u32 type;
}rel_table_t;可能是数据重定位也可能是函数重定位
下图1 可以看到是一个重定位表第一个是函数重定位其type类型是 R_THM_CALL符号是DMA_Get_CurrDataCounter下图1中其他是数据Type是R_ARMC_ABS32图2 可以看到UART1Poll函数其数据地址都是0重定位后图2可以看到都有了相应的地址。 图1 图2 图3
函数地址进行重定位
目标文件中的BL指令F7FFFFFE经过重定位后变成F7DFFE94BL的修改规则是通过BL的ARM 指令编码表来计算的如下图2。例如知道知道当前地址和编码后的指令代码就可以知道跳转的地址下图3和图4当然如果知道当前地址以及跳转地址可以推断出修订指令编码值。函数跳转地址实现如下面代码所示根据ARM BL指令编码表然后计算出S、J1和J2imm1和imm2最后再组装在一起形成最后的值。 图1 图2 图3 图4 int it,pc,offset 0;printf(please input Intruction:\r\n);scanf(%x, it);printf(please input pc:\r\n);scanf(%x, pc);int S (it 0x04000000) 26;int J1 (it 0x00002000) 13;int J2 (it 0x00000800) 11;int I1 (~(J1 ^ S))0x1;int I2 (~(J2 ^ S))0x1;int imm10 (it 0x03FF0000) 16;int imm11 (it 0x000007FF);if(S 1){offset 0xFF000000;}offset | (S24);offset | (I123);offset | (I222);offset | (imm1012);offset | (imm111);printf(jump addr0x%x\r\n,(offset pc 4));4、静态链接
4.1 空间地址分配
在链接的时候如果形成图1的这种可执行文件那么加载的时候有一些劣势。 简单地址分配
空间浪费不利于管理不利于加载
4.2 强弱符号和修饰
强符号、弱符号与符号修饰__weak 或者 attribute((weak)) 符号函数和变量链接器接口 符号名函数名和变量名 强符号只允许存在一个 弱符号允许存在多个weak修饰 符号修饰符号名根据特定规则进行修改 extern “C”将函数名按照C语言中生成函数名的方式去生成。
对于弱符号如果只定义不实现可以编译过链接过能执行吗 可以看到能编译过同样可以链接过但是执行报错地址为空可能无法访问。
所以GCC编译器
若符号即使没定义也可以链接有符号名符号地址为空允许出错
对于ARMCC编译器
若符号即使没定义也可以链接没有符号名函数引用指令链接成nop可正常运行
再来说说修饰规则C调用C
GCC不作任何操作VC编译器符号前面加”_”下划线。不声明extern “C”情况按照C的函数命名去修饰 修饰规则CGCCN或者_N开头………VC编译器??或者?开头 …….
4.3 链接与ABI接口
链接与ABI接口Application Binary Interface应用程序二进制接口 API与ABI前者为源码级别的接口如POSIX 后者来二进制级别的接口各大编译器无法兼容的原因就是ABI不同比如GCC和VC编译器C标准都一样但是ABI不同导致无法互相调用。 影响ABI的因素C角度 基本类型大小以及存储方式大小端符号修饰方面 函数调用方式入栈/返回值寄存器使用约定等堆栈分布方式 影响ABI的因素C角度 继承类体系分布指向成员指针内存分布虚函数的调用模板类的实例化外部符号的修饰全局对象的构造和析构异常产生和捕获机制
4.4 链接过程控制与脚本语法
链接过程控制其实就是链接脚本来控制链接的过程比如将数据分配到链接脚本指定的段。
加载视图加载期间代码和数据的分布情况 运行视图运行期间代码和数据的分布情况
存储地址代码数据存放的位置 加载地址代码数据加载到内存中执行代码的地址 执行地址代码数据真正执行的地址
来看一个加载过程具体可参考【Bootloader学习理解学习–加强版】。 链接脚本语法如下面两张图所示。