几个重要的概念
摘取自知乎内容:
链接器与链接脚本 - 知乎
linker 链接器
链接器(linker) 是一个程序,这个程序主要的作用就是将目标文件(包括用到的标准库函数目标文件)的代码段、数据段以及符号表等内容搜集起来并按照 ELF或者EXE 等格式组合成一个可执行的二进制文件的过程。
链接脚本
链接器在链接过程中需要使用链接脚本。如果没有通过 “-T” 参数指定链接脚本时,链接器会使用内置的链接脚本。链接脚本的作用: 将输入文件的段按照指定的地址空间布局合并到输出文件的段中。输出的文件具有可执行性。
可执行程序
任何一个可执行程序,不论是exe还是elf,都是由代码段,数据段,未初始化的数据段等组成。
section
一个输出段有两个地址 - 虚拟地址(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)需要把加载地址放到对应运行的地址中。加载地址和运行地址,是程序员自定义的,受到存储器的特点进行调整。