编译 链接的总过程
编译器(比如gcc)先逐个编译.C文件,生成一个个 .o文件,各个.o文件中,都含有 .text .data .bss 段,然后由Linker Script 再将各个.o文件的不同段们,聚在一起,生成elf文件,elf文件的信息量是大于bin文件的,elf点开还能看到文字信息,bin文件的话就是纯二进制数据了(当然也固定的格式,解析起来费劲些)
参考文档
http://www.bravegnu.org/gnu-eprog/linker.html
http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_toc.html
LMA VMA的概念
LMA(Load Memory Address)
存储地址
VMA(Virtual Memory Address)
运行地址
一般情况下,两个地址相同,若不同时,比如 LMA 是0x10000000,VMA是0x80000000,代表这一段存储在ROM中,运行时在RAM中。
.bss段 Block Started by Symbol
用来存放未初始化的全局变量和静态变量, 不占用编译出来的可执行文件的空间,但是还是会使用加载到RAM后,RAM的空间
.data段
用来存放已初始化的全局变量,占用可执行文件的空间
.rodata段(常量数据段)
字符串会被编译器自动放在rodata中,加 const 关键字的常量数据会被放在 rodata 中
在有的嵌入式系统中, rodata放在 ROM(或 NOR Flash)里,运行时直接读取,不须加载到RAM内存中。
所以,在嵌入式开发中,常将已知的常量系数,表格数据等造表加以 const 关键字。存在ROM中,避免占用RAM空间。
占用可执行文件的空间。
.text段
存放可执行的代码,占用可执行文件的空间。
calloc/realloc
1)calloc()函数
void calloc(size_t nmemb, size_t size);
参数 nmemb 表示要分配元素的个数,size表示每个元素的大小,分频的内存空间大小是 nmembsize; 返回值是 void* 类型的指针,指向分配好的内存首地址。
用法一:分配1024*sizeof(int)字节大小的内存,并清空为0
int *p = (int *)calloc(1024,sizeof(int));
用法二:与 alloc等价的 malloc 用法
int p = (int )malloc(1024sizeof(int));
memset(p,0,1024sizeof(int));
差异:用法一calloc,会根据分配的的类型来初始化为0,如:分配int型,则初始化为(int)0; 若为指针类型,则初始化为空指针;若为浮点,则初始化为浮点型。
用法二memset,不能保证初试化为空指针值和浮点型。(与NULL常量和浮点型的定义有关)
2)realloc()函数
realloc()用来重新分配正在使用的一块内存大小。
定义:
void *realloc(void *ptr, size_t size);
用法示例:
int *p = (int *)malloc(1024); //
p = (int *)realloc(512); // 重新分配为 512字节大小内存,缩小数据丢失
p = (int *)realloc(2048); // 重新分配为2048字节大小内存
注意:经过realloc()调整后的内存空间起始地址有可能与原来的不同。
许多脚本是相当的简单的.
可能的最简单的脚本只含有一个命令: ‘SECTIONS’. 你可以使用’SECTIONS’来描述输出文件的内存布局.
‘SECTIONS’是一个功能很强大的命令. 这里这们会描述一个很简单的使用. 让我们假设你的程序只有代码节(text section), 初始化过的数据节(data section),
和未初始化过的数据节(bss section). 这些会存在于’.text’,’.data’和’.bss’节, 另外, 让我们进一步假设在你的输入文件中只有这些节.对于这个例子, 我们说代码(text段)应当被载入到地址’0x10000’处, 而数据(data和bss段)应当从0x8000000处开始. 下面是一个实现这个功能的脚本:
SECTIONS
{
. = 0x10000;
.text : { *(.text) }
. = 0x8000000;
.data : { *(.data) }
.bss : { *(.bss) }
}
你使用关键字’SECTIONS’写了这个SECTIONS命令, 后面跟有一串放在花括号中的符号赋值和输出节描述的内容.
上例中, 在’SECTIONS’命令中的第一行是对一个特殊的符号’.‘赋值, 这是一个定位计数器.
如果你没有以其它的方式指定输出节的地址(其他方式在后面会描述), 那地址值就会被设为定位计数器的现有值. 定位计数器然后被加上输出节的尺寸.
在’SECTIONS’命令的开始处, 定位计数器拥有值’0’.第二行定义一个输出节,’.text’. 冒号是语法需要,现在可以被忽略.
节名后面的花括号中,你列出所有应当被放入到这个输出节中的输入节的名字. '‘是一个通配符,匹配任何文件名.
表达式’(.text)‘意思是所有的输入文件中的’.text’输入节.因为当输出节’.text’定义的时候,
定位计数器的值是’0x10000’,连接器会把输出文件中的’.text’节的地址设为’0x10000’.余下的内容定义了输出文件中的’.data’节和’.bss’节. 连接器会把’.data’输出节放到地址’0x8000000’处.
连接器放好’.data’输出节之后, 定位计数器的值是’0x8000000’加上’.data’输出节的长度.
得到的结果是连接器会把’.bss’输出节放到紧接’.data’节后面的位置.连接器会通过在必要时增加定位计数器的值来保证每一个输出节具有它所需的对齐. 在这个例子中,
为’.text’和’.data’节指定的地址会满足对齐约束, 但是连接器可能会需要在’.data’和’.bss’节之间创建一个小的缺口.就这样,这是一个简单但完整的连接脚本.
每个连接都被一个’连接脚本’所控制. 这个脚本是用连接命令语言书写的.
关键字 MEMORY
作用:MEMORY关键字用于描述一个MCU ROM和RAM的内存地址分布
语法:
其中 < name >是所要定义的内存区域的名字,< origin>是其起始地址,< len>为内存区域的大小。另外,< attr>是可选的
MEMORY
{
<name> [(<attr>)] : ORIGIN = <origin>, LENGTH = <len>
...
}
例子1:
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 256K // MEMORY语法中可以使用如K、M和G这样的内存单位
ram (!rx) : org = 0x40000000, l = 4M // ORIGIN可以写为org,而LENGTH可以写为l 或者 len
}
例子2:
MEMORY
{
PROG_VEC_TCMA_RAM : org = 0x00000000 len = 0x00000100 /* PROGRAM MEMORY (ROM) (256 Bytes)- 8 BYTE ALIGNED */
MSS_TCMA_RAM : org = 0x00000100 len = 0x0004FF00 /* PROGRAM MEMORY (RAM : TCMA) (320k Bytes) */
MSS_TCMA_L3SH : org = 0x00050000 len = 0x00010000 /* PROGRAM MEMORY (RAM : TCMA) (64k Bytes) */
MSS_TCMB : org = 0x08000000 len = 0x00024000 /* DATA MEMORY (RAM : TCMB) (144k Bytes) */
MSS_TCMB_RE : org = 0x08024000 len = 0xC000 /* DATA MEMORY (RAM : DSS_L3) (48k Bytes) */
}
参考地址:
https://www.cnblogs.com/uestcliming666/p/11456217.html
http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_16.html#SEC16
以上的链接讲的很清楚了
SECTIONS 指令
参考链接: http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_18.html#SEC18
作用: 将输入段们分配到输出段里,也规定了各个输出段的顺序,位置,内容,填充方式,并指出输出段的对齐方式,地址,内容是数据段还是代码段。
可以实现:
- 决定程序入口
- 把值分给符号(但为了linker 的简洁,不在section里进行1和2的操作)
- 描述一个输出段的位置
语法:
以下是SECTIONS指令的完整语法
SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{ contents } >region :phdr =fill
...
}
secname 和 contents是必需的,剩下的是可选的
a. 关键字 start
用来强制把输出段加载到指定地址,比如下例,该段output将会放到0x40000000(LMA存储地址)
SECTIONS {
...
output 0x40000000: {
...
}
...
}
b. 关键字 BLOCK(align)
设定了对齐后,该输出段会按照指定的对齐方式开始
c. 关键字 (NOLOAD)
添加这段后,代码加载时,将不会把这一段加载在RAM中,
但是链接时还是会正常的链接
SECTIONS {
ROM 0 (NOLOAD) : { ... }
...
}
d. 关键字 AT( ldadr)
可以指定该输出段加载( LMA)的地址
e. 关键字 region
把该输出段分配到之前用 MEMORY 定义的区域
规则: Linker Script中最多只有一个SECTION指令,但里面的声明可以有很多
格式:
SECTIONS
{
…
secname : {内容contents}
…
}
注意事项:
- 在secname旁边的空格是必须的,这样才不会引起secname的歧义,其他的空格可以随意
- secname不能使用linker script的标准符号
- 特殊的secname ‘/DISCARD/’ 用来放置无用的section,任何放置在此段的内容,都不会被放置在最后链接的输出中
- 假如没有相应的文件的话,linker script 不会创建其段,比如
.foo { *(.foo) }
当.foo section 中有文件时才会创建这个section
section内容中的声明:
最简形式:
例如, .text { a.o b.o c.o}
上面的例子
可以是一些input files,或者是一些input files的section(比如.text )
以下内容都可以用空格隔开
文件名( section )
文件名( section , section, … )
文件名( section section … )
文件中的每个section都可以用以上方式隔开
例如,
DIO(Gpt)
DIO(ISR)
DIO(TEXT)
可以写成
DIO(Gpt ISR TEXT)
例子一,下例将all.o输出到outputa(地址是0x10000),紧接着的是foo.o的所有.input1段。然后,所有的foo.o中的 .input2段和foo1.o的input1段,输出到outputb。最后所有剩余的.input1段和input2段都输出到outputc中。
SECTIONS {
outputa 0x10000 :
{
all.o
foo.o (.input1)
}
outputb :
{
foo.o (.input2)
foo1.o (.input1)
}
outputc :
{
*(.input1)
*(.input2)
}
}
例子二,先把所有的.text段放到.text中,所有由大写字母开头的文件中,.data段将被放在.DATA段中,然后将所有.data段放到.data中,最后把所有的.bss段放到.bss中
SECTIONS {
.text : { *(.text) }
.DATA : { [A-Z]*(.data) }
.data : { *(.data) }
.bss : { *(.bss) }
*号的使用
星号可以代表所有文件,也就是任意名的意思,假如在使用星号前,已经包含了个别文件,那么星号将会指向剩余的文件
a. 星号对于文件
*.o
上例是代表所有的.o文件
b. 星号对于数据段
*(DIO_TEXT_SECTION)
上例指的是所有文件中DIO_TEXT_SECTION这段
其他符号的使用
A. ?
问号可以匹配任何单字符
B. [chars]
括号可以匹配一串字符的一段
C. \
引用接下来的字符
D. -
可以匹配一系列的字符,比如[a-z]
.DATA : { [A-Z]*(.data) }
以上代表所有由大写字母开头的文件中,.data段将被放在.DATA段中
或者环境变量,比如:
--entry_point=_c_int00 /* ENTRY POINT */
-stack 0x2800 /* SOFTWARE STACK SIZE */
-heap 0x2800 /* HEAP AREA SIZE */
--retain="*(.intvecs)"
关键字 SECTION
/*
- STACK SIZE MACRO DEFINITIONS
/
#define M_USER_STACK_SIZE 0x2800 / Multiple of 8 bytes - 6KB /
#define M_PREV_STACK_SIZE 0x50 / Multiple of 8 bytes - 16B */
/*
- LINKER OPTIONS
/
–entry_point=_c_int00 / ENTRY POINT /
-stack 0x2800 / SOFTWARE STACK SIZE /
-heap 0x2800 / HEAP AREA SIZE /
–retain="(.intvecs)"
/* SPECIFY THE SYSTEM MEMORY MAP */
MEMORY
{
PROG_VEC_TCMA_RAM : org = 0x00000000 len = 0x00000100 /* PROGRAM MEMORY (ROM) (256 Bytes)- 8 BYTE ALIGNED /
MSS_TCMA_RAM : org = 0x00000100 len = 0x0004FF00 / PROGRAM MEMORY (RAM : TCMA) (320k Bytes) /
MSS_TCMA_L3SH : org = 0x00050000 len = 0x00010000 / PROGRAM MEMORY (RAM : TCMA) (64k Bytes) /
MSS_TCMB : org = 0x08000000 len = 0x00024000 / DATA MEMORY (RAM : TCMB) (144k Bytes) /
MSS_TCMB_RE : org = 0x08024000 len = 0xC000 / DATA MEMORY (RAM : DSS_L3) (48k Bytes) */
}
--define=MCAL_CODE1=MSS_TCMA_RAM
--define=MCAL_CODE2=MSS_TCMA_RAM
--define=MCAL_DATA=MSS_TCMB
--define=MCAL_BSS=MSS_TCMB
--define=MCAL_NOINIT=MSS_TCMA_RAM
--define=MCAL_CONST=MSS_TCMA_RAM
--define FILL_PATTERN=0xFEAA55EF
--define FILL_LENGTH=0x100
/* SPECIFY THE SECTIONS ALLOCATION INTO MEMORY */
SECTIONS
{
.intvecs : { (.intvecs) } > PROG_VEC_TCMA_RAM ALIGN(8) / INTERRUPT VECTORS */
.startup : { (.startup) } > MSS_TCMA_RAM ALIGN(8) / STARTUP CODE - 8 BYTE ALIGNED */
.systcmsysvimRam : > MSS_TCMA_RAM
/* TEXT SECTION - Executable Code */
.text : > MSS_TCMA_RAM, fill=FILL_PATTERN
{
.=align(4);
__linker_text_start = .;
. += FILL_LENGTH;
*(.text)
.=align(4);
. += FILL_LENGTH;
__linker_text_end = .;
}
/* DATA SECTION - Initialized Data */
.data : > MSS_TCMB_RE
{
.=align(4);
__linker_data_start = .;
. += FILL_LENGTH;
*(.data)
.=align(4);
. += FILL_LENGTH;
__linker_data_end = .;
}
/* CONST SECTION - Initialized Global Variables */
.const : > MSS_TCMA_L3SH, fill=FILL_PATTERN
{
.=align(4);
__linker_const_start = .;
. += FILL_LENGTH;
*(.const)
.=align(4);
. += FILL_LENGTH;
__linker_const_end = .;
}
/* BSS SECTION - Contains Uninitialized Global variables */
.bss : load > MSS_TCMB
RUN_START(bss_start)
RUN_END(bss_end)
{
.=align(4);
__linker_bss_start = .;
. += FILL_LENGTH;
*(.bss)
.=align(4);
. += FILL_LENGTH;
__linker_bss_end = .;
}
/* CINIT SECTION - Tables which initializes global variables */
.cinit : load > MSS_TCMA_RAM
/* STACK - System Stack */
.stack : load > MSS_TCMA_RAM, fill=FILL_PATTERN
/* SYSMEM - Heap Memory */
.sysmem : load > MSS_TCMA_RAM
}