程序编译的几个阶段
一般而言,程序编译经历下图四个阶段,链接是编译的最后一步,无论是在PC上编译代码,还是在PC上使用嵌入式gcc工具交叉编译嵌入式代码,编译过程都是如下几步。深入理解链接过程是嵌入式工程师必要掌握的能力!
ld链接脚本的基础概念
链接过程是将各式各样的.o文件链接为一个文件的过程。链接脚本描述连接器如何将这些输入文件(.o)文件映射为一个输出文件的,并且定义了输出文件的memory layout。几乎所有的链接脚本都是在做这些事情。
下面给出一个简单的链接脚本实例,每行脚本都有相应的注解:
左右滑动查看完整内容
SECTIONS { . = 0x10000; .text : { *(.text) } . = 0x8000000; .bss : { *(.bss) } }
上面提到的定位计数器就是点 ‘.’
这个链接脚本文件(Linker Scripty),用于告诉链接器如何将不同的代码和数据段(sections)组合在一起形成可执行文件。下面我会解释其中的每一部分:
1 . = 0x10000;
这行代码重新设置了定位计数器(location counter)的值为0x10000,即地址0x10000。
它告诉链接器在此处开始分配.text段的地址空间。
2 .text : { *(.text) }
这行代码定义了一个.text段,并告诉链接器将所有名为.text的数据节(section)放入这个段中。
*(.text)表示将所有输入文件中的.text段合并到输出文件的.text段中。
3 . = 0x8000000;
这行代码重新设置了定位计数器的值为 0x8000000,即地址 0x8000000。
它告诉链接器在此处开始分配.data和.bss段的地址空间。
4 .data : { *(.data) }
这行代码定义了一个.data段,并告诉链接器将所有名为.data的数据节放入这个段中。
*(.data)表示将所有输入文件中的.data段合并到输出文件的.data段中。
5 .bss : { *(.bss) }
这行代码定义了一个.bss段,并告诉链接器将所有名为.bss的数据节放入这个段中。
*(.bss)表示将所有输入文件中的.bss段合并到输出文件的.bss段中。
总体来说,这段链接脚本告诉链接器在特定的地址处分配.text、.data和.bss段,并将对应的数据节合并到这些段中。
链接脚本相关的概念
内存(Memory)
左右滑动查看完整内容
MEMORY { name [(attr)] : ORIGIN = origin, LENGTH = len … }
注解:这里的“attr”只能由以下特性组成:
‘R’ Read-only section
‘W’ -- Read/write section
‘X’ -- Executable section
‘A’ -- Allocatable section
‘I’ -- Initialized section
‘L’ -- Same as ‘I’
‘!’ -- Invert the sense of any of the attributes that follow
左右滑动查看完整内容
/* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 36K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K }
注解:
“xrw”表示“RAM”区是可读、可写和可执行的,且RAM 的起始地址为“0x20000300”,长度为36K。
“rx”表示“FLASH”区是可读和可执行的,FLASH的起始地址为“0x08000000”,长度为128K。
段(Section)
Section有loadable(可加载)和allocatable(可分配)两种类型。不可加载也不可分配的内存段,通常包含某些调试信息。
loadable(可加载)是指:程序运行时,该段内容应该被加载到内存中。
allocatable(可分配)是指:该段的内容应该被预留出,但不应该加载任何别的内容(某些情况下,这些内存必须归零)。
“可加载”和“可分配”的section都有两个地址:“VMA”和“LMA”。
VMA(the virtual memory address):这是运行输出文件时,该section的地址。VMA是可选项,可以不设置。
LMA(load memory address):这是加载section时的地址。
在大多数情况下,这两个地址是相同的。当然也可以不相等,比如下面的例子就是LMA和VMA不同的案例:
数据段被加载到ROM中,然后在程序启动时复制到RAM中(通常用于初始化全局变量)。此时ROM地址就是LMA,RAM地址就是VMA。
语法:
左右滑动查看完整内容
SECTIONS { section [address] [(type)] : { [AT(lma)] [ALIGN(section_align) | ALIGN_WITH_INPUT] [SUBALIGN(subsection_align)] [constraint] { output-section-command output-section-command … } [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp] [,] ... }
大多数的段仅使用了上述的一部分属性。
示例:
左右滑动查看完整内容
/* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH /* Initialized data sections into "RAM" Ram type memory */ .data: { . = ALIGN(4); _sdata = .; /* create a global symbol at data start */ *(.data) /* .data sections */ *(.data*) /* .data* sections */ . = ALIGN(4); _edata = .; /* define a global symbol at data end */ } >RAM AT> FLASH }
上述示例中“.isr_vector”的LMA与VMA是相等的。“.data”因为有“>RAM AT> FLASH”的修饰,表示.data段的VMA为RAM,LMA为FLASH。即.data段的内容会放在FLASH中,但是运行时,会加载到RAM中。
常用命令
ASSERT
语法:ASSERT(exp, message)
确保exp是非零值,如果为零,将以错误码的形式退出链接文件,并输出message。在必要的位置添加断言,可以清晰的定位问题。
左右滑动查看完整内容
/* The usage of ASSERT */ .test : { ASSERT ((_estack > (_Min_Stack_Size + _Min_Heap_Size)),"Error: There is an ERR occurred"); }
当示例中的“_estack”大于“_Min_Stack_Size + _Min_Heap_Size”时,就会打印“There is an ERR occurred”。
KEEP
用途:当链接器使用('--gc-sections')进行垃圾回收时,KEEP()可以使得被标记段的内容不被清除。
左右滑动查看完整内容
/* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH
指定“变量”的输出地址:
可以定义如下的memory,然后将“变量”存放于该memory,就能控制“变量”的输出地址。
左右滑动查看完整内容
/* Memories definition */ MEMORY { FW_RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x300 /* 0x20000000 ~ 0x200002FF */ RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 35K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K }
同时在c文件中,在定义“变量”时,添加如下对应的属性:
__attribute__((section(".FW_RAM"))) uint8_t key[8] = {0,1,2,3,4,5,6,7 };
变量将位于“0x20000000 ~ 0x200002FF”区域(如果仅仅只有key数组位于该区域,将从0x20000000开始存放,如果有多个变量存储于该区域,将按照编译的顺序,从0x20000000依次存放)。
指定“函数”的输出地址:
可以定义如下的memory和section,然后将“函数”存放于该section,就能控制“函数”的输出地址。
左右滑动查看完整内容
/* Memories definition */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x300 /* 0x08000000 ~ 0x080002FF */ CG_FLASH (rx) : ORIGIN = 0x08000300, LENGTH = 0x134 /* 0x08000300 ~ 0x08000433 */ RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 0x900 /* 0x20000300 ~ 0x20001FFF */ } SECTIONS { … .SE_Call_Fun: { . = ALIGN(4); . = . + 0x4; *(.SE_Call_Fun) . = ALIGN(4); } > CG_FLASH … }
同时在c文件中, 在“函数”的实现部分,添加如下对应的属性:
__attribute__((section(".SE_Call_Fun"))) uint32_t call_fun(Callgate_Func_Type_t ftype, void *param)
函数“call_fun”将存放于0x08000304处(留意此处的位置计数器将产生0x04的内存间隙)。
指定“文件”输出地址:
可以定义如下的memory和section,然后将指定的文件存放于该section,就能控制“文件”的输出地址。
左右滑动查看完整内容
/* Memories definition */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 0x300 /* 0x08000000 ~ 0x080002FF */ FW_FLASH (rx) : ORIGIN = 0x08000434, LENGTH = 0x2BCC/* 0x08000434 ~ 0x08003000 */ RAM (xrw) : ORIGIN = 0x20000300, LENGTH = 0x900 /* 0x20000300 ~ 0x20001FFF */ } /* Sections */ SECTIONS { … .main_section : { . = ALIGN(4); Core/Src/main.o(.text*) . = ALIGN(4); } >FLASH … }
示例中将main.o指定到FLASH区域中;更改FLASH的地址或者main_section的LMA,就可以实现将特定文件指定到特定内存区域。
案例:RZ/N2L把 .text, .data, .bss段从ATCM改到SYSTEM_RAM
这里描述的RZ/N2L的内存分配:
左右滑动查看完整内容
长按可保存查看大图
把.text段从ATCM改到SYSTEM_RAM:
左右滑动查看完整内容
长按可保存查看大图
把.data段从ATCM改到SYSTEM_RAM:
左右滑动查看完整内容
长按可保存查看大图
.bss段的改动也是类似的:
左右滑动查看完整内容
长按可保存查看大图
全部0条评论
快来发表一下你的评论吧 !