3-链接文件分析

链接文件分析

一、链接文件基础知识

什么是链接文件?有何作用?

​ 当我们编写了多个C文件时,我们需要将它们编译链接成一个可执行的文件xxx.exe,此时就需要用到链接文件(.ld)。链接文件的主要功能就是:将多个目标文件(.o)和库文件(.a)链接成一个可执行文件。

链接脚本文件主要有什么内容?

  1. 链接配置(可不配置)

    如一些符号变量的定义、入口地址、输出格式等

1
2
3
4
STACK_SIZE = 0X200
OUTPUT_FORMAT(elf32-littlearm)
OUTPUT_ARCH(arm)
ENTRY(_start)
  1. 内存布局定义

    链接文件中以MEMORY命令定义了存储空间,其中以ORIGIN定义地址空间的起始地址,LENGTH定义地址空间的长度

1
2
3
4
MEMORY
{
FLASH (rx) : ORIGIN = 0, LENGTH = 64K
}
  1. 段链接定义

    链接文件中以SECTIONS命令定义一些段(text、data、bss段等)链接分布

1
2
3
4
5
6
7
SECTIONS
{
.text:
{
*(.text*)
} > FLASH
}

.text段即代码段, *(.text*)指示将工程中所有目标文件的.text段链接到FLASH中

二、常用关键字、命令

  1. MEMORY命令

    1.1 使用MEMORY来定义内存如下:

1
2
3
4
5
6
MEMORY
{
NAME [(ATTR)] : ORIGIN = ORIGIN1, LENGTH = LEN1
NAME [(ATTR)] : ORIGIN = ORIGIN2, LENGTH = LEN2
...
}

​ 1.2 参数介绍

NAME : 存储区域的名字 。(命名无要求)

ATTR :定义改存储区域的属性(权限)。ATTR属性内可以出现以下7个字符(可出现多个):

· R 只读section

· W 读/写section

· X 可执行section

· A 可分配的section

· I 初始化了的section

· L 与I相同

· ! 除该字符以外的任何一个属性的section

ORIGIN(关键字) : 区域的开始地址,可简写成org或o

LENGTH(关键字) : 区域的大小,可简写成len或l

​ 1.3 KL36——BIOS示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 定义内存区域的起始地址和长度 */
MEMORY
{
/*中断向量区*/
m_interrupts (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000000, LENGTH = 0x00000100
/*配置区*/
m_flash_config (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000400, LENGTH = 0x00000010
/*Flash代码区*/
m_text (RX) : ORIGIN = MCU_SECTORSIZE * GEC_BIOS_SECTOR_START +0x00000410, LENGTH = MCU_SECTORSIZE *(GEC_BIOS_SECTOR_END +0x01) - MCU_SECTORSIZE * GEC_BIOS_SECTOR_START - 0x0410
/*统命令代码放置处。*/
DY_CMD (RX) : ORIGIN = MCU_SECTORSIZE * GEC_DYNAMIC_START, LENGTH =MCU_SECTORSIZE * ( GEC_DYNAMIC_END - GEC_DYNAMIC_START + 1 )
/*函数列表放置处。*/
CPT_LST (RX) : ORIGIN = MCU_SECTORSIZE * GEC_COMPONENT_LST_START, LENGTH =MCU_SECTORSIZE * ( GEC_COMPONENT_LST_END - GEC_COMPONENT_LST_START + 1 )
/*函数代码放置处。*/
CPT_FUN (RX) : ORIGIN = MCU_SECTORSIZE * GEC_COMPONENT_FUN_START, LENGTH =MCU_SECTORSIZE * ( GEC_COMPONENT_FUN_END - GEC_COMPONENT_FUN_START + 1 )
/*RAM区*/
m_data (RW) : ORIGIN = GEC_BIOS_RAM_START, LENGTH = GEC_BIOS_RAM_END - GEC_BIOS_RAM_START
}
  1. SECTIONS命令

    2.1 SECTIONS基本的命令语法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
SECTIONS
{
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{
contents
} >region :phdr =fill
...
}

/* 这么多参数,secname和contents是必须的,即可简写成: */

SECTIONS
{
...
secname :
{
contents
}
...
}

​ 2.2 参数解析

链接脚本本质就是描述输入和输出。

secname表示输出文件的段,即输出文件中有那些段。

contents描述输出文件的这个段从哪些文件里抽取出来,即输入文件,一般就是目标文件。

​ 2.3 示例

如下,将目标文件的数据段链接到输出文件的数据段(段名可以自己定义,段名前后必须要有空格)

1
2
3
4
5
6
7
8
9
10
SECTIONS
{
...
.data : /*输出文件中的data*/
{
main.o(.data) /*输入文件中的data*/
*(.data)
}
...
}

其中 *(.data) 表示将所有的目标的.data段链接到输出文件.data段中, 特别注意的是,之前链接的就不会再链接,这样做的目的是可以将某些特殊的目标文件链接到地址前面。

​ 2.4 其他参数的介绍

· start: 表示将某个段强制链接到的地址

· AT(addr) : 实现存放地址和加载地址不一致的功能,AT表示在文件中存放的位置,而在内存里呢,按照普通方式存储。

· region:这个region就是前面说的MEMORY命令定义的位置信息。

​ 2.5 KL36——BIOS示例

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/* Define output sections */
SECTIONS
{
/* The startup code goes first into internal flash */
.interrupts :
{
__VECTOR_TABLE = .;
. = ALIGN(4);

/*
isr_vector在start.S中定义:.section .isr_vector, "a",按照MEMORY命令说明,
isr_vector由于没有指定输出section,因此会创建与输入section同名的输出section,且会按照
isr_vector的属性放到合适的内存区域,此处KEEP是保证isr_vector的输出section不会被删除
*/
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} > m_interrupts

.flash_config :
{
. = ALIGN(4);
KEEP(*(.FlashConfig)) /* Flash Configuration Field (FCF) */
. = ALIGN(4);
} > m_flash_config

/* The program code and other data goes into internal flash */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
} > m_text

.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > m_text

.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} > m_text

.ctors :
{
__CTOR_LIST__ = .;
/* 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
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))
__CTOR_END__ = .;
} > m_text

.dtors :
{
__DTOR_LIST__ = .;
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
__DTOR_END__ = .;
} > m_text

.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} > m_text

.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} > m_text

.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} > m_text

.dynamic_command :
{
. = ALIGN(1);
KEEP(*(.dynamic_command))
. = ALIGN(1);
} > DY_CMD

.component_list :
{
. = ALIGN(1);
KEEP(*(.component_list))
. = ALIGN(1);
} > CPT_LST

/* Flash中的构件函数区 */
.component_fun :
{
. = ALIGN(1);
KEEP(*(.component_fun))
. = ALIGN(1);
} > CPT_FUN
__etext = .; /* define a global symbol at end of code */
__DATA_ROM = .; /* Symbol is used by startup for data initialization */





/* RAM中的变量区 */
.data : AT(__DATA_ROM) /*存储在FLASH中,上电后拷贝至RAM区*/
{
. = ALIGN(4);
__DATA_RAM = .;
__data_start__ = .; /* create a global symbol at data start */
*(.data) /* .data sections */
*(.data*) /* .data* sections */
KEEP(*(.jcr*))
. = ALIGN(4);
__data_end__ = .; /* define a global symbol at data end */
} > m_data


__DATA_END = __DATA_ROM + (__data_end__ - __data_start__);
.bss :
{
/* This is used by the startup in order to initialize the .bss section */
. = ALIGN(4);
__START_BSS = .;
__bss_start__ = .;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
__END_BSS = .;
} > m_data


.heap :
{
. = ALIGN(8);
__end__ = .;
PROVIDE(end = .);
__HeapBase = .;
__HeapLimit = .;
} > m_data

.stack :
{
. = ALIGN(8);
} > m_data

/* Initializes stack on the end of block */
__StackTop = ORIGIN(m_data) + LENGTH(m_data);

PROVIDE(__stack = __StackTop);

.ARM.attributes 0 : { *(.ARM.attributes) }


}
  1. 各关键字

    3.1 定位符号’.’的使用

    ‘.’表示当前地址,它可以被赋值也可以赋值给某个变量。

​ 如下为将当前地址赋值给某个变量(链接器链接是按照SECTIONS里的段顺序排列的,前面的排列完之后就能计算出当前地址)—我们可以看到上述SECTIONS中频繁出现 . = ALIGN(x)或者 xxx = .;那么这个.的地址是会不断更新的。比如上一段.的地址 为 0x80,上一段大小有20,那么下一段的时候.的地址则为0x100

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*将.赋值给RAM_START*/
RAM_START = .;


SECTIONS
{
. = 0×10000;/*将0x10000赋值给.*/
.text :
{
*(.text)
}

. = 0×8000000;/*将0x8000000赋值给.*/
.data :
{
*(.data)
}
}

​ “. = 0×10000;”该语句表示将当前地址设置为0x10000。如上代码中,意思是将所有目标文件的text段从0x10000地址开始存放。

​ 3.2 PROVIDE关键字

​ 该关键字定义一个(目标文件内被引用但没定义)符号。相当于定义一个全局变量的符号表,其他C文件可以通过该符号来操作对应的存储内存。

1
2
3
4
5
6
7
8
9
SECTIONS
{
.text :
{
*(.text)
_etext = .;
PROVIDE(etext = .);
}
}

​ 如上,在链接脚本中声明了_etext 符号。特别注意的是_etext 只是一个符号,没有存储内存,并不是一个变量,该符对应(映射)的是一个地址,,其地址为.text section之后的第一个字节的地址。C文件中引用用法如下。

1
2
3
4
5
6
7
int main()
{
//引用该变量
extern char _etext;
char *p = &_etext;
//...
}

若在链接脚本中 “ _etext = 0x100; “,即表示符号_etext对应的地址为0X100, 此时 & _etext的值为 0x100, char a= *p;表示为 从0X100地址取值存储的值赋值给变量a。

​ 3.3 KEEP关键字

​ 在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的。如KEEP(* (.text))或KEEP(SORT(*)(.text))。说的通俗易懂就是:防止被优化。

​ 3.4 ALIGN关键字

​ 表示字节对齐, 如 “ . = ALIGN(4);”表示从该地址开始后面的存储进行4字节对齐。

后续解析具体实例