概述
链接脚本(Linker Script,后缀 .ld 或 .lds)是控制链接器行为的脚本文件。它定义了程序在内存中的布局,包括代码段、数据段的位置、大小和属性。理解链接脚本对于嵌入式开发和底层系统编程至关重要。
一、链接器的基本概念
1.1 链接器的作用
编译过程:预处理 → 编译 → 汇编 → 链接
链接器的主要任务:
- 符号解析:将符号引用绑定到符号定义
- 重定位:将代码和数据放置到内存中的具体位置
- 段合并:将多个目标文件的相同段合并
1.2 目标文件的段
编译器生成的目标文件包含多个段(Section):
| 段名 |
内容 |
属性 |
.text |
代码 |
只读、可执行 |
.rodata |
只读数据 |
只读 |
.data |
已初始化全局/静态变量 |
读写 |
.bss |
未初始化全局/静态变量 |
读写 |
.symtab |
符号表 |
- |
.strtab |
字符串表 |
- |
1.3 VMA 与 LMA
这是理解链接脚本的核心概念:
- VMA (Virtual Memory Address):虚拟内存地址,程序运行时的地址
- LMA (Load Memory Address):加载内存地址,程序被加载到的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
┌─────────────────────────────────────────────────────┐
│ VMA vs LMA │
├─────────────────────────────────────────────────────┤
│ │
│ Flash (LMA) RAM (VMA) │
│ ┌──────────┐ ┌──────────┐ │
│ │ .text │ │ │ │
│ │ .rodata │ │ .data │ ← 运行时 │
│ │ .data镜像 │ ──拷贝──► │ │ │
│ │ (初始值) │ └──────────┘ │
│ └──────────┘ │
│ │
│ .text: VMA = LMA (运行和加载在同一位置) │
│ .data: VMA ≠ LMA (启动时需要拷贝) │
│ │
└─────────────────────────────────────────────────────┘
|
二、链接脚本基本结构
2.1 最简单的链接脚本
1
2
3
4
5
6
7
8
9
10
11
12
|
/* 最简单的链接脚本 */
SECTIONS
{
. = 0x10000; /* 设置当前地址计数器 */
.text : { *(.text) } /* 代码段 */
. = 0x8000000; /* 移动地址计数器 */
.data : { *(.data) } /* 数据段 */
.bss : { *(.bss) } /* BSS段 */
}
|
2.2 完整结构示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/* 入口点定义 */
ENTRY(_start)
/* 内存区域定义 */
MEMORY
{
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
/* 栈大小 */
_stack_size = 0x400;
/* 段定义 */
SECTIONS
{
/* 各段的详细定义 */
.text : { ... } > ROM
.data : { ... } > RAM AT > ROM
.bss : { ... } > RAM
}
/* 其他命令 */
|
三、ENTRY 命令
ENTRY 命令指定程序的入口点。
1
2
3
4
5
6
7
8
|
/* 方式1:直接指定符号名 */
ENTRY(main)
/* 方式2:指定函数名 */
ENTRY(Reset_Handler)
/* 方式3:使用下划线前缀(常见于嵌入式) */
ENTRY(_start)
|
链接器确定入口点的优先级:
ENTRY 命令指定的符号
- 目标文件中的
start 符号
- 目标文件中的
main 符号
.text 段的第一个字节
四、MEMORY 命令
MEMORY 命令定义目标板的内存区域。
4.1 基本语法
1
2
3
4
5
|
MEMORY
{
名称 (属性) : ORIGIN = 起始地址, LENGTH = 长度
...
}
|
4.2 属性说明
| 属性字符 |
含义 |
R |
只读 |
W |
读写 |
X |
可执行 |
A |
可分配 |
I |
可初始化 |
! |
取反(否定后续属性) |
4.3 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
MEMORY
{
/* Flash: 只读、可执行 */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
/* RAM: 读写、可执行 */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
/* 外部SDRAM */
SDRAM (rwx) : ORIGIN = 0xC0000000, LENGTH = 8M
/* 外设寄存器区域 */
PERIPH (rw) : ORIGIN = 0x40000000, LENGTH = 512M
}
|
4.4 计算剩余空间
1
2
3
4
5
6
7
8
|
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
}
/* 计算剩余Flash空间 */
_flash_used = SIZEOF(.text) + SIZEOF(.rodata) + SIZEOF(.data);
_flash_remaining = LENGTH(FLASH) - _flash_used;
|
五、SECTIONS 命令
SECTIONS 是链接脚本的核心,定义段的布局。
5.1 基本语法
1
2
3
4
5
6
|
SECTIONS
{
段名 : {
内容
} [> 内存区域] [AT> 加载区域]
}
|
5.2 地址计数器 (Location Counter)
. (点号)是地址计数器,表示当前位置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
SECTIONS
{
. = 0x10000; /* 设置地址为 0x10000 */
.text : {
*(.text)
}
/* 此时 . 等于 .text 段结束后的地址 */
. = ALIGN(4); /* 4字节对齐 */
.data : {
*(.data)
}
}
|
5.3 输入段语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
SECTIONS
{
.text : {
/* 匹配所有输入文件的 .text 段 */
*(.text)
/* 匹配所有以 .text 开头的段 */
*(.text*)
/* 匹配特定文件的段 */
main.o(.text)
/* 匹配多个段 */
*(.text .text.*)
/* 排除某些段 */
EXCLUDE_FILE(*lib*.o) *(.text)
}
}
|
5.4 完整的段定义示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
SECTIONS
{
/* ========== 中断向量表 ========== */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* KEEP 防止被优化掉 */
. = ALIGN(4);
} >FLASH
/* ========== 代码段 ========== */
.text :
{
. = ALIGN(4);
*(.text) /* .text 段 */
*(.text*) /* .text* 段 */
*(.glue_7) /* ARM/Thumb 互调用 */
*(.glue_7t)
*(.eh_frame) /* 异常处理帧 */
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
_etext = .; /* 代码段结束符号 */
} >FLASH
/* ========== 只读数据段 ========== */
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >FLASH
/* ========== ARM 异常表 ========== */
.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_HIDDEN(__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array*))
PROVIDE_HIDDEN(__fini_array_end = .);
} >FLASH
/* ========== 已初始化数据段 ========== */
_sidata = LOADADDR(.data); /* 获取加载地址 */
.data :
{
. = ALIGN(4);
_sdata = .; /* RAM中起始地址 (VMA) */
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .; /* RAM中结束地址 */
} >RAM AT> FLASH /* 运行在RAM,加载在FLASH */
/* ========== 未初始化数据段 ========== */
.bss :
{
. = ALIGN(4);
_sbss = .; /* BSS起始 */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON) /* 公共符号 */
. = ALIGN(4);
_ebss = .; /* BSS结束 */
__bss_end__ = _ebss;
} >RAM
/* ========== 用户堆 ========== */
._user_heap :
{
. = ALIGN(8);
PROVIDE(end = .);
PROVIDE(_end = .);
. = . + _min_heap_size;
. = ALIGN(8);
} >RAM
/* ========== 用户栈 ========== */
._user_stack :
{
. = ALIGN(8);
. = . + _min_stack_size;
. = ALIGN(8);
} >RAM
/* ========== 栈顶 ========== */
_estack = ORIGIN(RAM) + LENGTH(RAM);
/* ========== 丢弃不需要的段 ========== */
/DISCARD/ :
{
libc.a(*)
libm.a(*)
libgcc.a(*)
*(.note*)
*(.comment*)
}
}
|
六、常用内置函数
6.1 地址相关函数
1
2
3
4
5
6
7
8
9
10
11
|
/* 获取段的加载地址 */
_sidata = LOADADDR(.data);
/* 获取段的起始地址 (VMA) */
_start = ADDR(.text);
/* 获取段的大小 */
_size = SIZEOF(.data);
/* 获取段的结束地址 */
_end = ADDR(.data) + SIZEOF(.data);
|
6.2 对齐函数
1
2
3
4
5
6
7
|
/* 字节对齐 */
. = ALIGN(4); /* 4字节对齐 */
. = ALIGN(8); /* 8字节对齐 */
. = ALIGN(4096); /* 页对齐 */
/* 对齐到下一个边界 */
. = ALIGN(4);
|
6.3 内存区域相关
1
2
3
4
5
6
7
8
|
/* 获取内存区域起始地址 */
_start = ORIGIN(RAM);
/* 获取内存区域大小 */
_size = LENGTH(RAM);
/* 获取内存区域结束地址 */
_end = ORIGIN(RAM) + LENGTH(RAM);
|
6.4 其他函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/* 定义符号,如果没有被定义则提供默认值 */
PROVIDE(_stack_top = 0x20005000);
/* 隐藏符号(不导出) */
PROVIDE_HIDDEN(__exidx_start = .);
/* 计算最大值 */
_max = MAX(_a, _b);
/* 计算最小值 */
_min = MIN(_a, _b);
/* 断言检查 */
ASSERT(_stack_top < ORIGIN(RAM) + LENGTH(RAM), "Stack overflow!");
|
七、符号定义
7.1 普通符号定义
1
2
3
4
5
6
7
8
9
|
/* 简单赋值 */
stack_size = 0x1000;
/* 表达式赋值 */
ram_end = ORIGIN(RAM) + LENGTH(RAM);
/* 段地址赋值 */
_text_start = ADDR(.text);
_text_end = ADDR(.text) + SIZEOF(.text);
|
7.2 PROVIDE 命令
PROVIDE 用于定义符号,但如果该符号已被定义则不覆盖:
1
2
3
4
|
/* 如果 _start 未被定义,则定义为 .text 的起始地址 */
PROVIDE(_start = ADDR(.text));
/* 如果 C 代码中定义了 _start,则使用 C 代码的定义 */
|
7.3 在C代码中使用链接脚本符号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/* 声明外部符号(注意:不是变量,是地址) */
extern uint32_t _stack_top; // 链接脚本中定义
extern uint32_t _text_start;
extern uint32_t _text_end;
void print_memory_info(void) {
/* 使用取地址获取值 */
printf("Stack top: 0x%08X\n", (uint32_t)&_stack_top);
printf("Text: 0x%08X - 0x%08X\n",
(uint32_t)&_text_start,
(uint32_t)&_text_end);
/* 计算代码段大小 */
uint32_t text_size = (uint32_t)&_text_end - (uint32_t)&_text_start;
printf("Text size: %u bytes\n", text_size);
}
|
八、KEEP 命令
KEEP 命令防止链接器优化掉看似未使用的段。
8.1 为什么需要 KEEP?
链接器的垃圾回收(--gc-sections)会删除未引用的段。但某些段(如中断向量表、构造函数数组)必须保留。
8.2 使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
SECTIONS
{
.isr_vector :
{
KEEP(*(.isr_vector)) /* 中断向量表必须保留 */
} >FLASH
.init_array :
{
KEEP(*(.init_array*)) /* 构造函数数组必须保留 */
} >FLASH
/* 普通 .text 不需要 KEEP */
.text :
{
*(.text)
} >FLASH
}
|
九、表达式与运算符
9.1 算术运算符
1
2
3
4
5
6
7
8
9
|
/* 加减乘除取模 */
a = 10 + 20;
b = 0x1000 - 0x100;
c = 4 * 1024;
d = 64 / 4;
e = 17 % 5;
/* 负数 */
f = -100;
|
9.2 位运算符
1
2
3
4
5
6
7
8
9
|
/* 与、或、异或、取反 */
a = 0xFF & 0x0F;
b = 0xF0 | 0x0F;
c = 0xFF ^ 0xF0;
d = ~0xFF;
/* 移位 */
e = 1 << 10; /* 左移 */
f = 1024 >> 2; /* 右移 */
|
9.3 比较运算符
1
2
3
4
5
6
7
|
/* 返回 1(真)或 0(假) */
a = (0x1000 == 4096); /* 1 */
b = (0x1000 != 4096); /* 0 */
c = (10 < 20); /* 1 */
d = (10 > 20); /* 0 */
e = (10 <= 10); /* 1 */
f = (10 >= 20); /* 0 */
|
9.4 逻辑运算符
1
2
3
4
|
/* 与、或、非 */
a = (1 && 1); /* 1 */
b = (1 || 0); /* 1 */
c = !0; /* 1 */
|
9.5 三元运算符
1
2
|
/* 条件 ? 真值 : 假值 */
stack_top = use_external_ram ? 0xC0000000 : 0x20005000;
|
十、高级特性
10.1 断言 (ASSERT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/* 检查条件,失败时报错 */
ASSERT(DEFINED(_stack_top), "Stack top not defined!");
ASSERT(SIZEOF(.bss) < 0x10000, "BSS section too large!");
/* 在段定义中使用 */
SECTIONS
{
.stack :
{
. = ALIGN(8);
. = . + 0x1000;
ASSERT(. <= ORIGIN(RAM) + LENGTH(RAM), "Stack overflow!");
} >RAM
}
|
10.2 条件语句
1
2
3
4
5
6
7
8
9
10
11
|
/* 使用三元运算符模拟条件 */
debug_mode = 1;
SECTIONS
{
.text :
{
*(.text)
. = debug_mode ? (. + 0x1000) : .; /* 调试模式下留出空间 */
}
}
|
10.3 包含其他链接脚本
1
2
3
4
5
6
7
8
9
10
11
|
/* 包含其他链接脚本 */
INCLUDE board.ld
INCLUDE memory.ld
/* 在内存定义之后包含 */
MEMORY
{
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}
INCLUDE sections.ld
|
10.4 版本脚本
1
2
3
4
5
6
7
8
9
10
11
|
/* 定义符号版本 */
VERSION
{
V1.0 {
global: func1; func2;
local: *;
};
V2.0 {
global: func3;
} V1.0;
}
|
十一、常见段类型详解
11.1 代码段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
.text :
{
/* 主代码段 */
*(.text)
*(.text*)
/* ARM 特定 */
*(.glue_7) /* ARM 到 Thumb 互调用 */
*(.glue_7t) /* Thumb 到 ARM 互调用 */
*(.vfp11_veneer)
*(.v4_bx)
/* 异常处理 */
*(.eh_frame)
*(.eh_frame_hdr)
/* 初始化/终止代码 */
KEEP(*(.init))
KEEP(*(.fini))
/* GNU 构造/析构 */
. = ALIGN(4);
__CTOR_LIST__ = .;
KEEP(*(SORT(.ctors.*)))
KEEP(*(.ctors))
__CTOR_END__ = .;
__DTOR_LIST__ = .;
KEEP(*(SORT(.dtors.*)))
KEEP(*(.dtors))
__DTOR_END__ = .;
} >FLASH
|
11.2 数据段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
.data :
{
. = ALIGN(4);
_data_start = .;
/* 普通数据 */
*(.data)
*(.data*)
/* 小数据段(某些架构) */
*(.sdata)
*(.sdata*)
/* 线程本地存储 */
*(.tdata)
*(.tdata*)
*(.tbss)
*(.tbss*)
. = ALIGN(4);
_data_end = .;
} >RAM AT> FLASH
/* 加载地址 */
_data_load = LOADADDR(.data);
|
11.3 BSS 段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
.bss :
{
. = ALIGN(4);
_bss_start = .;
/* 未初始化数据 */
*(.bss)
*(.bss*)
/* 小 BSS */
*(.sbss)
*(.sbss*)
/* 公共符号 */
*(COMMON)
. = ALIGN(4);
_bss_end = .;
} >RAM
/* BSS 大小 */
_bss_size = SIZEOF(.bss);
|
11.4 堆栈段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/* 栈定义 */
_stack_size = 0x1000; /* 4KB 栈 */
.stack (NOLOAD) :
{
. = ALIGN(8);
_stack_bottom = .;
. = . + _stack_size;
. = ALIGN(8);
_stack_top = .;
} >RAM
/* 堆定义 */
_min_heap_size = 0x1000; /* 最小堆大小 */
.heap (NOLOAD) :
{
. = ALIGN(8);
_heap_start = .;
. = . + _min_heap_size;
. = ALIGN(8);
_heap_end = .;
} >RAM
|
11.5 NOLOAD 属性
NOLOAD 表示该段不被加载到内存(不占用镜像空间):
1
2
3
4
5
6
7
8
9
10
|
.bss (NOLOAD) :
{
*(.bss)
} >RAM
/* 等价于 */
.bss :
{
*(.bss)
} >RAM (NOLOAD)
|
十二、实战案例
12.1 STM32 链接脚本模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
/*
* STM32F103C8T6 链接脚本
* Flash: 64KB, RAM: 20KB
*/
/* 入口点 */
ENTRY(Reset_Handler)
/* 栈和堆大小 */
_stack_size = 0x400; /* 1KB 栈 */
_min_heap_size = 0x200; /* 512B 最小堆 */
/* 内存区域 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
/* 段定义 */
SECTIONS
{
/* 中断向量表 */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} >FLASH
/* 代码 */
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.rodata)
*(.rodata*)
. = ALIGN(4);
_etext = .;
} >FLASH
/* 初始化数组 */
.init_array :
{
. = ALIGN(4);
__init_array_start = .;
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array*))
__init_array_end = .;
} >FLASH
/* 数据段加载地址 */
_sidata = LOADADDR(.data);
/* 数据段 */
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .;
} >RAM AT> FLASH
/* BSS 段 */
.bss :
{
. = ALIGN(4);
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} >RAM
/* 堆 */
._user_heap :
{
. = ALIGN(8);
PROVIDE(end = .);
PROVIDE(_end = .);
. = . + _min_heap_size;
. = ALIGN(8);
} >RAM
/* 栈顶 */
_estack = ORIGIN(RAM) + LENGTH(RAM);
/* 检查 */
ASSERT(_estack > _ebss, "RAM overflow!")
ASSERT(SIZEOF(.text) + SIZEOF(.rodata) + SIZEOF(.data) < LENGTH(FLASH), "Flash overflow!")
}
|
12.2 自定义段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/* 在链接脚本中定义自定义段 */
SECTIONS
{
/* 日志缓冲区 */
.log_buffer (NOLOAD) :
{
. = ALIGN(4);
_log_buffer_start = .;
. = . + 0x1000; /* 4KB */
_log_buffer_end = .;
} >RAM
/* 配置数据(存储在Flash最后) */
.config :
{
. = ALIGN(4);
KEEP(*(.config_data))
. = ALIGN(4);
} >FLASH
}
|
在C代码中使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/* 使用自定义段 */
__attribute__((section(".config_data")))
const config_t config = {
.version = 1,
.magic = 0xDEADBEEF,
};
/* 访问自定义段 */
extern uint8_t _log_buffer_start;
extern uint8_t _log_buffer_end;
void write_log(const char* msg) {
static uint8_t* ptr = &_log_buffer_start;
// ...
}
|
12.3 多核处理器的链接脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/* 双核处理器链接脚本示例 */
MEMORY
{
/* 共享 Flash */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
/* Core0 专用 RAM */
RAM0 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
/* Core1 专用 RAM */
RAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 64K
/* 共享 RAM */
SHRAM (rwx) : ORIGIN = 0x20020000, LENGTH = 32K
}
/* Core0 的段定义 */
SECTIONS
{
.text0 :
{
*(.text.core0)
*(.text.core0.*)
} >FLASH
.data0 :
{
*(.data.core0)
} >RAM0 AT> FLASH
/* 共享数据 */
.shared :
{
*(.shared)
} >SHRAM
}
|
十三、调试技巧
13.1 查看链接结果
1
2
3
4
5
6
7
8
9
10
11
|
# 查看段布局
arm-none-eabi-objdump -h firmware.elf
# 查看符号地址
arm-none-eabi-nm -n firmware.elf
# 查看完整段信息
arm-none-eabi-readelf -S firmware.elf
# 生成内存映射文件
arm-none-eabi-ld -Map=firmware.map ...
|
13.2 链接器 Map 文件
Map 文件包含详细的链接信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# 在编译时生成
gcc -Wl,-Map=output.map ...
# Map 文件内容示例
Archive member included because of file (symbol)
...
Allocating common symbols
...
Memory Configuration
Name Origin Length Attributes
FLASH 0x08000000 0x00010000 xr
RAM 0x20000000 0x00005000 xrw
...
Linker script and memory map
LOAD main.o
.text 0x08000000 0x100
0x08000000 main
...
|
13.3 链接器诊断选项
1
2
3
4
5
6
7
8
|
# 显示详细的链接过程
arm-none-eabi-ld --verbose
# 显示被忽略的段
arm-none-eabi-ld --print-gc-sections
# 检查未定义的符号
arm-none-eabi-nm -u firmware.elf
|
十四、常见问题与解决
14.1 段重叠错误
1
|
error: section .data overlaps section .bss
|
原因:内存区域太小或段大小超出限制
解决:
1
2
|
/* 添加断言检查 */
ASSERT(ADDR(.bss) >= ADDR(.data) + SIZEOF(.data), "Data/BSS overlap!")
|
14.2 未定义符号错误
1
|
undefined reference to `_start'
|
原因:入口点符号未定义
解决:
1
2
|
/* 使用 PROVIDE 提供默认值 */
PROVIDE(_start = ADDR(.text));
|
14.3 位置无关代码问题
编译时使用 -fPIC 时需要特殊处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
SECTIONS
{
.got :
{
*(.got)
*(.got.plt)
} >RAM
.plt :
{
*(.plt)
} >FLASH
}
|
十五、总结
15.1 关键概念速查
| 概念 |
说明 |
ENTRY |
定义程序入口点 |
MEMORY |
定义内存区域 |
SECTIONS |
定义段布局 |
. |
地址计数器 |
ALIGN(n) |
n 字节对齐 |
KEEP() |
防止段被优化 |
> REGION |
指定运行区域 |
AT> REGION |
指定加载区域 |
PROVIDE |
提供默认符号值 |
ASSERT |
编译时断言 |
15.2 常用符号获取
1
2
3
4
5
6
7
8
9
10
|
/* 段信息 */
_text_start = ADDR(.text);
_text_end = ADDR(.text) + SIZEOF(.text);
_text_size = SIZEOF(.text);
_text_load = LOADADDR(.text);
/* 内存信息 */
_ram_start = ORIGIN(RAM);
_ram_size = LENGTH(RAM);
_ram_end = ORIGIN(RAM) + LENGTH(RAM);
|
参考资料