最近休息了一下,中间断断续续在虚拟机上靠着记忆恢复了原来崩溃的虚拟机上80%的工作成果,还算过得去吧,完全丢失的也就是些不大重要的资料。今天新买的机械键盘也到货了,不得不说顺丰的工作人员好评,给过年假期里仍在工作的商家和快递员们点个赞。现在我的感觉炒鸡棒,所以我们继续下面的教程吧~
在上一篇中我们介绍了,样例工程中的makefile的工作原理和功能。我想对大多数童鞋来说理解编译器将.c文件编译为.o文件并不大困难,但是却难以明白最后链接的过程是什么作用和为什么要这样做。还有就是我们在样例工程中启动的文件为什么是自己编写的,它又怎样做到将程序入口引导到main函数上,那么在这篇中我们就来深入的讨论下这两个话题。
链接器
链接的过程
首先,想要明白链接器的工作原理我们还是要来深入的看看整个编译过程中具体的方式和原理。
我想大家都知道高级语言出现之前我们所用的汇编语言是除机器码外最接近硬件的语言。使用汇编的代码甚至可以很容易的手动转换为机器代码。那么接下来的介绍就需要童鞋们多少了解一点汇编程序了(如8051的汇编)。在单片机执行的过程中命令被执行的顺序只有两种:顺序执行和根据指令跳转执行位置。在汇编的代码中,良好的写法是把各个函数分块放在储存的不同位置上,并在前面写上程序的标号 (如:“START:”),最后由编译器将START程序处的地址装入写有 START标号跳转指令的地方。
由此,我们就可以理解C语言被编译为二进制执行文件的过程了,首先每个C文件都被编译为了.o的,带有未解析地址的中间文件,而后工具链的链接器将所有C文件的.o 文件链接将他们有序的排列到储存中,并将他们个个函数处的地址解析使得其他不同地方的函数能够跳转到该函数的入口地址。由此一个有序排列的可被单片机执行的文件便生成了。至于其中各个.c文件产生的功能在单片机储存中的排列顺序和地址位置,在最后我们链接器工作产生的.map文件中是有显示的,如下面从样例工程中.map文件中复制的片段:
.isr_vector 0x08000000 0x134
0x08000000 . = ALIGN (0x4)
*(.isr_vector)
.isr_vector 0x08000000 0x134 ./USER/CoIDE_startup.o
0x08000000 g_pfnVectors
0x08000134 . = ALIGN (0x4)
.text 0x08000134 0x1464
0x08000134 . = ALIGN (0x4)
*(.text)
.text 0x08000134 0x5c /home/yangliu/Library/gcc-arm-none-eabi-5_4-2016q3/bin/../lib/gcc/arm-none-eabi/5.4.1/armv7-m/crtbegin.o
.text 0x08000190 0x80 ./USER/main.o
0x08000190 main
.text 0x08000210 0x68 ./USER/CoIDE_startup.o
0x08000210 Reset_Handler
0x08000210 Default_Reset_Handler
0x08000268 EXTI2_IRQHandler
0x08000268 TIM8_TRG_COM_IRQHandler
0x08000268 TIM8_CC_IRQHandler
0x08000268 TIM1_CC_IRQHandler
0x08000268 TIM6_IRQHandler
0x08000268 PVD_IRQHandler
0x08000268 SDIO_IRQHandler
0x08000268 EXTI3_IRQHandler
0x08000268 EXTI0_IRQHandler
0x08000268 I2C2_EV_IRQHandler
0x08000268 ADC1_2_IRQHandler
所以我们的gcc链接器就是用来做这个工作的,当然不只是gcc的链接器,世上所有c程序的编译工具链应该都是以这种理念设计的。。当然不排除我见识少,没见过特殊的。
工具链中链接器的用法
在实际中,链接器的执行程序实际上是arm-none-eabi-ld这个文件,但是我再实际的编写过程中在遇到.c和.cpp文件混合的工程中,ld会在链接过程中报错。而对此官方的说明是推荐使用arm-none-eabi-gcc指令来链接工程,它会自动的调用ld程序且不会出现上面这种情况,所以接下来我们都是以arm-none-eabi-gcc指令来介绍链接器工作的。
$(CC) $(C_OBJ) -T stm32_f103ze_gcc.ld -o $(TARGET).elf -mthumb -mcpu=cortex-m3 -Wl,--start-group -lc -lm -Wl,--end-group -specs=nano.specs -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=Project.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80
在上面这段截取自样例工程makefile的代码片中,我们可以看到在最后生成.elf文件时的指令。变量CC为arm-none-eabi-gcc,变量OBJ为所有.o文件。* -o xx.elf*为链接.o文件生成.elf文件。
ld文件
在链接的过过程中与编译过程相比其中显著的与编译指令不同的便是 -T xx.ld。
在这里 -T xx.ld实际上是调用了一个.ld的文件,那么.ld文件是做什么的呢?这里就比较高深了,在51单片机中我们知道最后在生成代码后51单片机内存中会有如 code、xdata、data的区段,来讲代码中执行部分、变量部分等分区块放置,而.ld就是一种链接器使用的规则性文件,他告诉链接器单片机系统的ROM、RAM的地址和他们的大小等信息,并指示链接器将什么代码保存在什么位置。
对于.ld文件它是有一套自己的语法及设置参数的规则的,大家可以不具体作了解,但求看懂其中一部分的信息。
/* Entry Point */
ENTRY(Reset_Handler)
/* Highest address of the user mode stack */
_estack = 0x20010000; /* end of 64K RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0; /* required amount of heap */
_Min_Stack_Size = 0x200; /* required amount of stack */
/* Specify the memory areas */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K
}
SECTIONS
{
/* The startup code goes first into FLASH */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >FLASH
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* define a global symbols at end of code */
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』