连接脚本介绍 Linker Script

目录

连接器的基本介绍

连接脚本介绍

完整的linker script:

额外知识点补充

启动代码

中断系统


连接器的基本介绍

为了得到一个可以工作的二进制文件,在代码开发过程中需要使用一系列工具。

编译简单的应用程序需要几个工具: 它们是编译器、汇编器、链接器和二进制生成器。它们中的每一个都在链式过程中完成自己的任务。

连接器将不同的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数字异常(供参考-异常表地址的最顶部用于存储堆栈指针的起始点):

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱思考的发菜_汽车网络信息安全

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值