摘取自知乎内容:
链接器(linker) 是一个程序,这个程序主要的作用就是将目标文件(包括用到的标准库函数目标文件)的代码段、数据段以及符号表等内容搜集起来并按照 ELF或者EXE 等格式组合成一个可执行的二进制文件的过程。
链接器在链接过程中需要使用链接脚本。如果没有通过 “-T” 参数指定链接脚本时,链接器会使用内置的链接脚本。链接脚本的作用: 将输入文件的段按照指定的地址空间布局合并到输出文件的段中。输出的文件具有可执行性。
任何一个可执行程序,不论是exe还是elf,都是由代码段,数据段,未初始化的数据段等组成。
一个输出段有两个地址 - 虚拟地址(Virtual Memory Address,VMA):运行时段所在的地址,即运行地址 - 加载地址(Load Memory Address,LMA): 加载时段所在的地址
有个地方需要注意的是,如果没有通过 “AT” 来指定LMA,那么 LMA=VMA,即加载地址等于运行地址。
一般嵌入式系统中,经常遇到加载地址和运行地址不一致的情况。比如image 存放在Flash中,运行时复制到RAM中。
分析s32k144的工程
分析S32K1xx_flash_debug.ld
1,memory map ,非常重要的一个概念,一个处理器的存储布局,存储器地址空间,cpu指令集直接可以寻址的外存空间。对应于地址空间的实体可以是flash,nandflash,sram,ddr等,如norflash,emmc等使用spi或者mdio控制器才能访问的存储器,则不在这个地址空间内。
- /* Specify the memory areas */
-
- MEMORY
-
- {
-
- /* Flash */
-
- m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
-
- m_flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
-
- m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x0007FBF0
-
- /* SRAM_L */
-
- m_data (RW) : ORIGIN = 0x1FFF8000, LENGTH = 0x00008000
-
- /* SRAM_U */
-
- m_data_2 (RW) : ORIGIN = 0x20000000, LENGTH = 0x00007000
-
- }
2,目标可执行文件是由输入文件的各个段填充过来的,具体填充规则,以及虚拟地址和加载地址的定义均在链接文件中定义。
中断向量,os的异常向量表表存储在m_interrupts中,m_interrupts段定义在地址空间的RIGIN = 0x00000000, LENGTH = 0x00000400。
- .interrupts :
-
- {
-
- __VECTOR_TABLE = .;
-
- . = ALIGN(4);
-
- "*(.isr_vector)"
-
- "*(Os_ExceptionVectors)" /* Startup code */
-
- . = ALIGN(4);
-
- } > m_interrupts
代码段(.code)虚拟地址(运行)在m_text (RX) : ORIGIN = 0x00000410, LENGTH = 0x0007FBF0,数据段虚拟地址(运行)在m_data,bss段虚拟地址(运行)在m_data_2中。注意段的虚拟地址和存储地址是两回事。下面会解释。
3,需要注意链接脚本的地址信息是会被程序引用的,也就是说连接脚本也属于程序的一部分,可以理解为既是程序的一个头文件,又是链接器的脚本程序。
- __etext = .; /* Define a global symbol at end of code. */
-
- __DATA_ROM = .; /* Symbol is used by startup for data initialization. */
-
- .data : AT(__DATA_ROM)
-
- {
-
- . = ALIGN(4);
-
- __DATA_RAM = .;
-
- __data_start__ = .; /* Create a global symbol at data start. */
-
- "*(.data)" /* .data sections */
-
- "*(.data*)" /* .data* sections */
-
- "*(.os_data)"
-
- "*(.mcal_data)"
-
- "*(.jcr*)"
-
- . = ALIGN(4);
-
- __data_end__ = .; /* Define a global symbol at data end. */
-
- } > m_data
虚拟地址是程序运行的地址,加载地址是程序存储的地址,或者是程序从哪里加载的地方。
数据段一开始定义了两个宏地址,这两个宏就是会被用到c代码中的。
__DATA_ROM就是近接着代码段之后的一个地址,AT(__DATA_ROM)表示将.data段加载地址设置为__DATA_ROM。
在startup_init_bss.c引用了这个地址:
- void init_data_bss(void)
-
- data_rom = (uint8_t *)__DATA_ROM;
-
- /* Copy initialized data from ROM to RAM */
-
- while (data_rom_end != data_rom)
-
- {
-
- *data_ram = *data_rom;
-
- data_ram++;
-
- data_rom++;
-
- }
startup.s中使用init_data_bss,.data从加载地址拷贝到运行地址(实际上是从flash拷贝到sram中,不拷贝其实也能执行,只要cpu可以寻址就行,flash和sram都在地址空间内)
- ; Init .data and .bss sections
-
- LDR R0,=init_data_bss
-
- BLX R0
-
- ;cpsie i ; Unmask interrupts
-
- BL main
从规格书上找一下memorymap
总结:
1,可执行程序都是由数据段,代码段,bss段等组成。
2,每个段对应有两个地址:虚拟地址(运行地址),加载地址(存储地址),这个地址都是cpu可以寻址的地址空间。
3,链接脚本中的地址信息会被C代码引用,链接脚本可以是程序一部分(类似头文件)。
4,CPU的memory map 对应的存储器可以是flash 也可以是sarm,ddr等,如果加载地址和运行地址不一样,那么程序在运行的时候(startup.s)需要把加载地址放到对应运行的地址中。加载地址和运行地址,是程序员自定义的,受到存储器的特点进行调整。