一、概念
将.o文件经过 ld链接脚本 生成可执行文件
加载地址和虚拟地址
lma = load memory address (内存存储加载的地址)
vma = vitual memory address (最终实际运行的地址)
二、格式 / 指令语法
1、格式
(1)MEMORY格式
MEMORY
{
/*标准格式如下*/
name [(attr)] : ORIGIN = origin, LENGTH = len
/* Flash */
interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x000000C0
flash_config (RX) : ORIGIN = 0x00000400, LENGTH = 0x00000010
text (RX) : ORIGIN = 0x00000410, LENGTH = 0x10000
/* SRAM */
data (RW) : ORIGIN = 0x1FFFFC00, LENGTH = 0x1000
}
ORIGIN:最终实际运行的地址(vma)
LENGTH:属于该name的存储长度
(2)SECTIONS格式
用来指定目标文件生成输出文件时的规则(输出段格式)
SECTIONS {
...
sectionname [address_vma] [align] : AT (lma)
{
output-section-command
...
} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
...
}
sectionname:输出段名
【通常包含以下输出段名:.text 代码段 .rodata 只读常量 .data 初始化的全局变量 .bss 未初始化的全局变量】
[address_vma]:生成的执行文件中,该段所运行的位置(vma)
[align]:指定段起始的对齐大小字节数
AT(lma):指定该段实际内存存储加载的地址(lma)
output-section-command:指定输入段文件内容
[>region]:指定输出段所指向的运行地址(vma)
[AT>lma_region]:指定输出段所指向的加载地址(lma)
[:phdr :phdr ...]:将该段分配给一个或多个程序段
[=fillexp]:指定该段的初始填充值
【output-section-command】
可以是输入段文件,也可以是输入段文件中的某一个段信息
.interrupts_ram :
{
. = ALIGN(4);
__VECTOR_RAM__ = .;
__RAM_START = .;
__interrupts_ram_start__ = .; /* Create a global symbol at data start. */
*(.m_interrupts_ram) /* This is a user defined section. */
. += M_VECTOR_RAM_SIZE; /* 0xC0 */
. = ALIGN(4);
__interrupts_ram_end__ = .; /* Define a global symbol at data end. */
} > data
__VECTOR_RAM = DEFINED(__flash_vector_table__) ? ORIGIN(m_interrupts) : __VECTOR_RAM__ ;
__RAM_VECTOR_TABLE_SIZE = DEFINED(__flash_vector_table__) ? 0x0 : (__interrupts_ram_end__ - __interrupts_ram_start__) ;
.data : AT(_DATA_ROM)
{
. = ALIGN(4);
_DATA_RAM = .;
__data_start__ = .; /* Create a global symbol at data start. */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
data3.o(.abcd)
KEEP(*(.jcr))
. = ALIGN(4);
__data_end__ = .; /* Define a global symbol at data end. */
} > data AT>_DATA_ROM
......
.xxx:
{
. = ALIGN(4);
__data_start__ = .; /* Create a global symbol at data start. */
xxx
. = ALIGN(4);
__data_end__ = .; /* Define a global symbol at data end. */
} > data
符号 . 位置计数器
*(.data) 指定输入段为所有输入文件中的 .data 段
*(.data*) 指定输入段为所有输入文件中以 .data* 段( * 作为通配符)
data3.o(.abcd) 指定输入段为文件data3.o中的 .abcd 段
KEEP(*(.jcr)) 防止链接器优化,强制让链接器保留指定的输入段
【上述代码含义:将data输出段加载在_DATA_ROM处,实际运行地址在SRAM中(MEMORY中的data段在SRAM) 】
【出现多个输出段都 > data 时,从data( : ORIGIN = 0x1FFFFC00)的vma地址0x1FFFFC00依次开始分配虚拟运行地址】
2、指令
(1)ENTRY指令
ENTRY(Reset_Handler)
ENTRY指定程序入口地址,当前Reset_Handler()函数为重启处理接口
(2)KEEP指令
KEEP(*(.jcr))
防止链接器优化,强制让链接器保留指定的输入段
强制保留所有输入文件中的 .jcr段
二、链接后生成.map文件
.interrupts_ram
0x1ffffc00 0xc0
0x1ffffc00 . = ALIGN (0x4)
0x1ffffc00 __VECTOR_RAM__ = .
0x1ffffc00 __RAM_START = .
0x1ffffc00 __interrupts_ram_start__ = .
*(.m_interrupts_ram)
0x1ffffcc0 . = (. + M_VECTOR_RAM_SIZE)
*fill* 0x1ffffc00 0xc0
0x1ffffcc0 . = ALIGN (0x4)
0x1ffffcc0 __interrupts_ram_end__ = .
0x1ffffc00 __VECTOR_RAM = DEFINED(__flash_vector_table__)?ORIGIN (m_interrupts):__VECTOR_RAM__
0x000000c0 __RAM_VECTOR_TABLE_SIZE = DEFINED (__flash_vector_table__)?0x0:(__interrupts_ram_end__ - __interrupts_ram_start__)
.data 0x1ffffcc0 0x19c load address 0x00004fb4
0x1ffffcc0 . = ALIGN (0x4)
0x1ffffcc0 _DATA_RAM = .
0x1ffffcc0 __data_start__ = .
*(.data)
*(.data*)
......
【上述代码含义:data输出段中,vma存放的为 .interrupts_ram + .data + ... ... (从0x1FFFFC00开始分配)
.interrupts_ram大小为0xC0,加载地址默认等同于运行地址,为0x1ffffc00;
.data大小计算为0x19C,加载load address 为0x00004fb4,运行地址为0x1ffffcc0】
三、源码
1、copy 加载lma到运行vma
以S32K系列芯片为例:
startup_S32Kxxx.S文件
......
/* Init .data and .bss sections */
ldr r0,=init_data_bss
blx r0
; cpsie i /* Unmask interrupts */
......
在startup.c文件中调用
void init_data_bss(void)
{
......
/* Data */
data_ram = (uint8_t *)__DATA_RAM;
data_rom = (uint8_t *)__DATA_ROM;
data_rom_end = (uint8_t *)__DATA_END;
/* CODE RAM */
code_ram = (uint8_t *)__CODE_RAM;
code_rom = (uint8_t *)__CODE_ROM;
code_rom_end = (uint8_t *)__CODE_END;
......
/* Copy initialized data from ROM to RAM */
while (data_rom_end != data_rom)
{
*data_ram = *data_rom;
data_ram++;
data_rom++;
}
/* Copy functions from ROM to RAM */
while (code_rom_end != code_rom)
{
*code_ram = *code_rom;
code_ram++;
code_rom++;
}
......
}
其中__DATA_RAM / __DATA_ROM等宏定义,在ld链接脚本中被赋值
【上述代码含义:ROM地址存储信息依次赋值到RAM地址,实现特定输入段的信息转移至RAM中运行】
四、代码编写
1、增加指定的段信息
__attribute__((section(".xxx"))) uint8_t example = 0;
__attribute__((section(".xxx"))) static int demo(void);
2、中断开启/关闭
__asm("cpsid i"); /* 中断禁用 */
__asm("cpsie i"); /* 中断启用 */