链接器脚本(Linker Script,.lds
文件)用于控制程序的内存布局,指定代码、数据、堆栈等段在内存中的位置。以下是关键段的详细解析及写法说明:
1. MEMORY
命令:定义物理内存区域
-
作用:声明目标硬件的内存区域及其属性(可读、可写、可执行)。
-
语法:
MEMORY { 区域名称 (属性) : ORIGIN = 起始地址, LENGTH = 长度 }
-
示例:
MEMORY { ROM (rx) : ORIGIN = 0x00000000, LENGTH = 256K /* 只读,存放代码 */ RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K /* 可读写,存放数据、堆栈 */ }
-
属性:
-
r
:可读,w
:可写,x
:可执行。 -
组合如
rx
(代码段)、rw
(数据段)。
-
2. SECTIONS
命令:定义输出段布局
-
作用 :将输入文件(
.o
)的段映射到输出文件(如.elf
)的段,并指定存放位置。 -
核心段:
-
.text
:代码段。 -
.data
:已初始化的全局变量。 -
.bss
:未初始化的全局变量(自动清零)。 -
.rodata
:只读数据(如常量字符串)。 -
.stack
/.heap
:自定义堆栈或堆区域。
-
基本写法:
SECTIONS {
. = 起始地址; /* 定位计数器(Location Counter)设置当前地址 */
/* 输出段名 : { 输入段描述 } > 内存区域 AT> 加载地址 */
.text : {
*(.text*) /* 所有输入文件的 .text 段 */
KEEP(*(.vectors)) /* 强制保留特定段(如中断向量表) */
} > ROM
.data : {
_data_start = .; /* 定义符号,记录.data段起始地址 */
*(.data*)
_data_end = .; /* 定义符号,记录.data段结束地址 */
} > RAM AT> ROM /* VMA(运行时地址)在RAM,LMA(加载地址)在ROM */
.bss : {
_bss_start = .;
*(.bss*)
*(COMMON) /* 未初始化的全局变量 */
_bss_end = .;
} > RAM
. = ALIGN(4); /* 按4字节对齐 */
_end = .; /* 定义程序结束地址 */
}
3. 关键符号定义
-
作用:在链接脚本中定义符号,供程序访问内存布局信息。
-
示例:
_stack_top = ORIGIN(RAM) + LENGTH(RAM); /* 栈顶地址(向下增长) */ _data_load_start = LOADADDR(.data); /* .data段的加载地址(ROM中) */
4. 高级用法
段对齐(ALIGN)
.data : ALIGN(4) { ... } /* 强制按4字节对齐 */
覆盖特定文件的段
.special_section : {
some_file.o(.text) /* 将某个文件的段单独放置 */
} > ROM
动态内存分配(堆)
.heap : {
. = ALIGN(4);
_heap_start = .;
. += 0x1000; /* 预留4KB堆空间 */
_heap_end = .;
} > RAM
5. 典型链接脚本示例
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS {
.isr_vector : {
KEEP(*(.isr_vector)) /* 中断向量表必须位于FLASH起始 */
} > FLASH
.text : {
*(.text*)
*(.rodata*)
. = ALIGN(4);
_etext = .; /* 代码段结束地址 */
} > FLASH
.data : {
_sdata = .;
*(.data*)
. = ALIGN(4);
_edata = .;
} > SRAM AT> FLASH /* 运行时在SRAM,加载时在FLASH */
.bss : {
_sbss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > SRAM
.stack (NOLOAD) : {
. = ALIGN(8);
. += 0x800;
_stack_top = .;
} > SRAM
}
6. 调试与验证
-
查看内存布局 :使用
objdump -h output.elf
查看段地址和大小。 -
符号表检查 :
nm output.elf
检查_data_start
、_stack_top
等符号的地址。 -
加载地址与运行地址 :确保
.data
段从FLASH复制到SRAM(需在启动代码中实现)。
项目中遇到malloc分配大数据失败的问题,调整heap后可正常访问。
/* Linker script to place sections and symbol values.
* It references following symbols, which must be defined in code:
* start64 : Entry point
*
* It defines following symbols, which code can use without definition:
* __cs3_peripherals
* __code_start
* __exidx_start
* __exidx_end
* __data_start
* __preinit_array_start
* __preinit_array_end
* __init_array_start
* __init_array_end
* __fini_array_start
* __fini_array_end
* __bss_start__
* __bss_end__
* __end__
* __stack
* __el3_stack
* __ttb0_l1
* __ttb0_l2_ram
* __ttb0_l2_private
* __ttb0_l2_periph
* __top_of_ram
*/
ENTRY(start64)
SECTIONS
{
/*
* CS3 Peripherals is a 64MB region from 0x1c000000
* that includes the following:
* System Registers at 0x1C010000
* UART0 (PL011) at 0x1C090000
* Color LCD Controller (PL111) at 0x1C1F0000
* plus a number of others.
* CS3_PERIPHERALS is used by the startup code for page-table generation
* This region is not truly empty, but we have no
* predefined objects that live within it
*/
__cs3_peripherals = 0x1c000000;
/*
* GICv2 distributor
*/
.gicd 0xF0611000 (NOLOAD):
{
__gicd_start = .;
*(.gicd)
}
/*
* GICv2 cpu interface
*/
.gicc 0xF0612000 (NOLOAD):
{
__gicc_start = .;
*(.gicc)
}
/*
* 0xe8440000: this is tx511 sram addr
* 0x80000000: this is fvp ram addr
* 0~3.5G DDR, reserve 16M space
*/
.vectors 0x1000000:
{
__code_start = .;
KEEP(*(StartUp))
KEEP(*(EL1VECTORS EL2VECTORS EL3VECTORS))
}
.init :
{
KEEP (*(SORT_NONE(.init)))
}
.text :
{
*(.text*)
}
.fini :
{
KEEP (*(SORT_NONE(.fini)))
}
.rodata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
}
.eh_frame :
{
KEEP (*(.eh_frame))
}
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
}
.ARM.exidx :
{
__exidx_start = .;
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
__exidx_end = .;
}
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
}
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array ))
PROVIDE_HIDDEN (__init_array_end = .);
}
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array ))
PROVIDE_HIDDEN (__fini_array_end = .);
}
.ctors :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}
.jcr :
{
KEEP (*(.jcr))
}
.data :
{
__data_start = . ;
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
}
.heap (NOLOAD):
{
. = ALIGN(64);
__end__ = .;
PROVIDE(end = .);
. = . + 0x200000;
}
.stack (NOLOAD):
{
. = ALIGN(64);
. = . + 2 * 0x4000;
__stack = .;
}
.el3_stack (NOLOAD):
{
. = ALIGN(64);
. = . + 2 * 0x1000;
__el3_stack = .;
}
.ttb0_l1 (NOLOAD):
{
. = ALIGN(4096);
__ttb0_l1 = .;
. = . + 0x1000;
}
.ttb0_l2_ram (NOLOAD):
{
. = ALIGN(4096);
__ttb0_l2_ram = .;
. = . + 0x1000;
}
.ttb0_l3_ram_1 (NOLOAD):
{
. = ALIGN(4096);
__ttb0_l3_ram_1 = .;
. = . + 0x1000;
}
.ttb0_l3_ram_2 (NOLOAD):
{
. = ALIGN(4096);
__ttb0_l3_ram_2 = .;
. = . + 0x1000;
}
.ttb0_l3_ram_3 (NOLOAD):
{
. = ALIGN(4096);
__ttb0_l3_ram_3 = .;
. = . + 0x1000;
}
/*
* The startup code uses the end of this region to calculate
* the top of memory - don't place any RAM regions after it
*/
__top_of_ram = .;
}
通过合理编写链接脚本,可以精确控制程序的内存布局,适用于嵌入式系统、Bootloader、操作系统内核等需要严格内存管理的场景。