STM32高级开发——链接器与启动文件

💡 原文中文,约10300字,阅读约需25分钟。
📝

内容提要

本文讨论了编译器和链接器的作用和必要性,以及如何将程序入口引导到main函数上。链接器将.c文件编译为.o文件,并将所有.o文件链接起来,生成可供单片机执行的文件。启动文件在执行main函数前初始化全局变量和静态变量,并将程序跳转到main函数入口。文章提醒读者学习汇编语言以更好地理解单片机工作原理。

🔎

延伸解读

链接器如何把分散的函数拼成可执行文件

文章指出,每个.c文件先编译成带未解析地址的.o文件,链接器再按顺序合并这些.o文件,并解析每个函数的地址,使不同位置的函数能相互跳转。链接后生成的.map文件能直观展示各段在存储器中的排列,例如中断向量表固定在0x08000000,main函数和Reset_Handler的地址也清晰可见。理解这一点,有助于排查函数跳转异常或段布局问题。

为什么推荐用gcc驱动链接而非直接调用ld

文章提到,直接使用arm-none-eabi-ld在.c与.cpp混合工程中会报错,而官方推荐用arm-none-eabi-gcc来链接,它会自动调用ld并避免该问题。样例makefile中,gcc链接时通过-T指定.ld文件,并附带--gc-sections、-Map等参数。对读者而言,这意味着实际工程中应优先采用gcc作为链接入口,而不是手动调用ld。

.ld文件与启动文件如何配合完成初始化

文章说明,.ld文件定义ROM/RAM的地址与大小,并划分.isr_vector、.text、.data、.bss等段;启动文件则利用ld中定义的_sidata、_sdata、_sbss等符号,把.data初值从Flash复制到RAM,并将.bss清零。两者符号一一对应,修改堆栈大小或内存布局时需同步关注。这解释了为何启动文件能完成全局变量和静态变量的初始化。

启动文件中的中断向量表与weak属性

文章指出,Cortex-M3上电后从ROM首地址取出栈顶指针和复位向量,因此启动文件用attribute将g_pfnVectors放入.isr_vector段,而ld文件又把该段固定在Flash起始处。中断处理函数用weak修饰,提供默认地址,用户重新定义后即可覆盖。这既保证了中断向量表的默认可用性,也方便用户按需替换处理函数。

❓

Q&A

链接器在STM32开发中具体起什么作用?

链接器将编译器生成的多个.o文件按照一定顺序链接起来,解析每个函数的地址,使不同位置的函数可以相互跳转,最终生成可供单片机执行的可执行文件(如.elf)。

为什么推荐使用arm-none-eabi-gcc而不是arm-none-eabi-ld来链接工程?

因为在实际编写过程中,遇到.c和.cpp文件混合的工程时,ld会在链接过程中报错。官方推荐使用arm-none-eabi-gcc指令来链接工程,它会自动调用ld程序且不会出现上述情况。

.ld文件在链接过程中扮演什么角色?

.ld文件是链接器使用的规则性文件,它告诉链接器单片机系统的ROM、RAM的地址和大小等信息,并指示链接器将什么代码保存在什么位置(如将中断向量表放在FLASH起始处)。

STM32启动文件在执行main函数之前做了哪些事情?

启动文件首先设置中断向量表(通常从ROM复制到RAM),然后初始化全局变量和静态变量(将.data段从Flash复制到RAM,并清零.bss段),接着调用SystemInit()配置时钟等,最后跳转到main函数。

启动文件中的__attribute__((weak))修饰符有什么用途?

该属性表明其后的变量或函数为弱声明,即在没有其他声明的情况下调用该函数,而如果其他地方声明了同名函数,则会顶替该弱声明函数。在启动文件中,它被用来修饰中断处理函数,为中断向量表提供一个默认地址,当用户定义后,地址转为用户定义的位置。

启动文件中引用的_sidata、_sdata等变量来自哪里?

这些变量在启动文件中被声明为extern,它们的实际定义来自于链接器使用的.ld文件。链接器在链接过程中会将它们转换为实际的地址供程序使用。

🏷️

标签

➡️

继续阅读