目录
连接器的基本介绍
为了得到一个可以工作的二进制文件,在代码开发过程中需要使用一系列工具。
编译简单的应用程序需要几个工具: 它们是编译器、汇编器、链接器和二进制生成器。它们中的每一个都在链式过程中完成自己的任务。
连接器将不同的object文件连接成为一个统一的object文件(一般情况下是elf格式, 也可称为可执行文件)
连接脚本介绍
链接器脚本是一个文件,它定义了微控制器特定的特性,如内存映射、内存段、堆栈位置和大小。如果需要,它还可能包含特定于应用程序的信息。
MEMORY {
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 8K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
}
“rwx” means rewritable and executable
“rx” – read-only and executable memory.
Executable is meant that code can be run from this memory.
每种内存类型也被划分为存储不同类型数据的section。
所以我们需要向linker展示如何将内存划分到section中。对于这个命令,使用了SECTIONS。
首先,我们需要定义存储程序代码的部分。这个部分被称为.text:
SECTIONS
{
.text :
{
. = ALIGN(4);
KEEP(*(.interrupt_vector))
*(.text)
*(.text*)
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} > FLASH
在这里. = ALIGN(4);指示该节与4字节边界对齐;
KEEP(*(.interrupt_vector))表示在链接过程中必须跳过此部分的优化;
.text表示程序代码段;
.rodata -存储常量的空间;
>FLASH表示节位于FLASH存储器中。
如果需要,您可以添加更多的部分。
“.” 表示当前地址。所以接下来我们需要记住最后使用的地址,通过将它赋值给变量
data_flash = .;
现在我们可以继续下一节—— .data。这个部分通常位于RAM中,当变量具有初始定义值时包含静态数据
.data : AT ( _data_flash )
{
. = ALIGN(4);
_data_begin = .;
*(.data)
*(.data*)
. = ALIGN(4);
_data_end = .;
} > RAM
这里我们使用保存的闪存地址AT (_data_flash),在这里我们可以找到存储在闪存中的初始常数,必须加载到RAM。启动代码将执行此操作。现在,我们创建了一个.data位置,用于加载常量。
下一个部分是.bss。这是未定义/未初始化变量和全局变量存储的地方
.bss :
{
_bss_begin = .;
__bss_start__ = _bss_begin;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_bss_end = .;
__bss_end__ = _bss_end;
} > RAM
.bss section 通常紧接在.data section之后。
最后,我们需要定义堆栈
_stack_size = 1024;
_stack_end = ORIGIN(RAM)+LENGTH(RAM);
_stack_begin = _stack_end - _stack_size;
. = _stack_begin;
._stack :
{
. = . + _stack_size;
} > RAM
堆栈大小可以更改,甚至可以扩展到RAM的末尾。但是linker将没有机会警告堆栈内存不足。
告诉链接器的最后一件事是复位后程序必须从哪里开始。所以我们用简单的一行:
ENTRY(handler_reset);
这意味着在main()程序之前,我们需要初始化变量——将常量从Flash复制到RAM,初始化堆栈,并在需要时做其他事情。初始化在启动代码中完成。
启动代码在微控制器复位后,主程序之前执行。作为链接器脚本,启动代码通常作为所有相同微控制器类型的通用代码实现。因此,您通常不需要从头开始编写。
链接器脚本必须与启动代码一起运行。这意味着它将根据linker中定义的数据自动初始化MCU。启动代码初始化变量,将定义的变量从Flash复制到RAM,初始化堆栈,然后将资源提供给主程序
完整的linker script:
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)
MEMORY
{
FLASH(RX) : ORIGIN = 0x10001000, LENGTH = 0x10000
SRAM(!RX) : ORIGIN = 0x20000000, LENGTH = 0x4000
}
stack_size = DEFINED(stack_size) ? stack_size : 1024;
no_init_size = 4;
SECTIONS
{
/* TEXT section */
.text :
{
sText = .;
KEEP(*(.reset));
*(.text .text.* .gnu.linkonce.t.*);
/* C++ Support */
KEEP(*(.init))
KEEP(*(.fini))
/* .ctors */
*crtbegin.o(.ctors)
*crtbegin?.o(.ctors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors)
*(SORT(.ctors.*))
*(.ctors)
/* .dtors */
*crtbegin.o(.dtors)
*crtbegin?.o(.dtors)
*(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors)
*(SORT(.dtors.*))
*(.dtors)
*(.rodata .rodata.*)
*(.gnu.linkonce.r*)
*(vtable)
. = ALIGN(4);
} > FLASH
.eh_frame_hdr : ALIGN (4)
{
KEEP (*(.eh_frame_hdr))
} > FLASH
.eh_frame : ALIGN (4)
{
KEEP (*(.eh_frame))
} > FLASH
/* Exception handling, exidx needs a dedicated section */
.ARM.extab : ALIGN(4)
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} > FLASH
. = ALIGN(4);
__exidx_start = .;
.ARM.exidx : ALIGN(4)
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
} > FLASH
__exidx_end = .;
. = ALIGN(4);
/* DSRAM layout (Lowest to highest)*/
/* Veneer <-> Stack <-> DATA <-> BSS <-> HEAP */
.VENEER_Code ABSOLUTE(0x2000000C):
{
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
VeneerStart = .;
KEEP(*(.XmcVeneerCode));
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
VeneerEnd = .;
} > SRAM AT > FLASH
eROData = LOADADDR (.VENEER_Code);
VeneerSize = ABSOLUTE(VeneerEnd) - ABSOLUTE(VeneerStart);
/* Dummy section for stack */
Stack (NOLOAD) : AT(0)
{
. = ALIGN(8);
. = . + stack_size;
__initial_sp = .;
} > SRAM
/* Standard DATA and user defined DATA/BSS/CONST sections */
.data :
{
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
__data_start = .;
* (.data);
* (.data*);
*(*.data);
*(.gnu.linkonce.d*)
. = ALIGN(4);
/* preinit data */
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP(*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
. = ALIGN(4);
/* init data */
PROVIDE_HIDDEN (__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
. = ALIGN(4);
/* finit data */
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
__data_end = .;
} > SRAM AT > FLASH
DataLoadAddr = LOADADDR (.data);
__data_size = __data_end - __data_start;
.ram_code :
{
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
__ram_code_start = .;
/* functions with __attribute__ ((section (".ram_code")))*/
*(.ram_code)
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
__ram_code_end = .;
} > SRAM AT > FLASH
__ram_code_load = LOADADDR (.ram_code);
__ram_code_size = __ram_code_end - __ram_code_start;
__text_size = (__exidx_end - sText) + VeneerSize + __data_size + __ram_code_size;
eText = sText + __text_size;
/* BSS section */
.bss (NOLOAD) :
{
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
__bss_start = .;
* (.bss);
* (.bss*);
* (COMMON);
*(.gnu.linkonce.b*)
. = ALIGN(4); /* section size must be multiply of 4. See startup.S file */
__bss_end = .;
. = ALIGN(8);
Heap_Bank1_Start = .;
} > SRAM
__bss_size = __bss_end - __bss_start;
/* .no_init section contains SystemCoreClock. See system.c file */
.no_init ORIGIN(SRAM) + LENGTH(SRAM) - no_init_size (NOLOAD) :
{
Heap_Bank1_End = .;
* (.no_init);
} > SRAM
/* Heap - Bank1*/
Heap_Bank1_Size = Heap_Bank1_End - Heap_Bank1_Start;
ASSERT(Heap_Bank1_Start <= Heap_Bank1_End, "region SRAM overflowed no_init section")
/DISCARD/ :
{
*(.comment)
}
.stab 0 (NOLOAD) : { *(.stab) }
.stabstr 0 (NOLOAD) : { *(.stabstr) }
/* DWARF 1 */
.debug 0 : { *(.debug) }
.line 0 : { *(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : { *(.debug_srcinfo) }
.debug_sfnames 0 : { *(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : { *(.debug_aranges) }
.debug_pubnames 0 : { *(.debug_pubnames) }
.debug_pubtypes 0 : { *(.debug_pubtypes) }
/* DWARF 2 */
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
/* DWARF 2.1 */
.debug_ranges 0 : { *(.debug_ranges) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : { *(.debug_weaknames) }
.debug_funcnames 0 : { *(.debug_funcnames) }
.debug_typenames 0 : { *(.debug_typenames) }
.debug_varnames 0 : { *(.debug_varnames) }
/* Build attributes */
.build_attributes 0 : { *(.ARM.attributes) }
}
此脚本输出目标为ARM
格式是Little Endian的ELF
程序的入口是: Reset_Handler
内存,Flash, 堆栈大小分别被定义
比较重要的section:
.bss (不初始化的RAM区)
.text (代码区)
.data (初始化的RAM区)
.ram_code(RAM函数区,如无ram函数,则此区不占空间)
还有调试区等等
具体在最终的flash与ram中的空间分配情况:
额外知识点补充
每个object文件由一连串的section组成, 某些section为输入section,某些为输出section
每个section都有自己的名字与大小, 大多数section都有自己的内容,
如果某section的类别为loadable,则此section在运行时需要装载到内存中去. 没有内容的section表明需要在内存中为此section留出一片内存,一般都需要初始化(为0),也有不需要初始化的. 还有某些section并无内容,也不需要额外分配内存,这些section为调试所用的section.
在那些需要装载的secton中, 地址又分为两种: VMA与LMA, 大多数情况下此两地址相同. VMA表明运行时的地址, LMA为装载地址.
但是如果某些section需要从flash拷贝到ram执行,则此时VMA为RAM地址,LMA为Flash地址.
启动代码
启动代码通常是用汇编语言编写的,但这也可以用C语言完成,C语言更容易阅读和在必要时修改。首先,在链接器脚本中,我们使用以下命令指定了要启动的入口点
ENTRY(handler_reset);
在启动代码中,这将是第一个被调用的函数
void handler_reset(void)
{
unsigned long *source;
unsigned long *destination;
// Copying data from Flash to RAM
source = &_data_flash;
for (destination = &_data_begin; destination < &_data_end;)
{
*(destination++) = *(source++);
}
// default zero to undefined variables
for (destination = &_bss_begin; destination < &_bss_end;)
{
*(destination++) = 0;
}
// starting main program
main();
}
启动代码将链接器脚本中的源地址和目标地址作为外部变量
extern unsigned long _data_flash;
extern unsigned long _data_begin;
extern unsigned long _data_end;
extern unsigned long _bss_begin;
extern unsigned long _bss_end;
extern unsigned long _stack_end;
启动代码的唯一事情是从闪存中的源地址获取值,并将它们复制到RAM的目标地址。此外,存储在.bss节中的变量默认值为零,因此在使用时不需要在源代码中定义空值
下一步是启动程序分配异常处理程序向量表。由于ARM Cortex架构,向量表中的第一个地址用于存储堆栈结束地址。这是一种方便有效的定义方法。
__attribute__ ((section(".interrupt_vector")))
void (* const table_interrupt_vector[])(void) =
{
(void *) &_stack_end, // 0 - stack
handler_reset, // 1
handler_default, // 2
handler_default, // 3
handler_default, // 4
handler_default, // 5
handler_default, // 6
0, // 7
0, // 8
0, // 9
0, // 10
handler_default, // 11
handler_default, // 12
0, // 13
handler_default, // 14
handler_default, // 15
// peripherals
handler_default, // 0
handler_default, // 1
handler_default, // 2
handler_default, // 3
handler_default, // 4
-//-
handler_default, // 59
handler_default // 60
};
在链接器脚本中,我们定义了“.interrupt_vector”部分从0x00000000地址开始,因此堆栈指针位于Flash的0x00000000地址。然后它会重置处理程序,然后将变量数据复制到RAM并将未定义的变量清空。然后,启动代码就结束了,所有资源都分配给main()例程
中断系统
中断系统可能是任何微控制器的关键特征之一。ARM Cortex-M3微控制器可能有多达256个中断源。前15个中断源称为系统异常。这些异常出现在Cortex内核中,如重置、NMI、硬故障和错误、调试和SystTick计时器中断。在异常表中,它们从地址0x00000004开始,编号从1到15。没有0数字异常(供参考-异常表地址的最顶部用于存储堆栈指针的起始点):