欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

在ARM GCC环境中链接文件的探索

最编程 2024-02-22 19:03:54
...

所谓的Link File是指在GCC链接阶段用到的一个用于表示MCU内存分布的文件,这个文件是GCC连接器的一个输入文件,不同的链接文件会有不同的结果,比如我们常见的下载到RAM或者Flash中,这两种不同的地址就对应不同的连接文件。本篇文章我们就对链接文件做一个简单的描述。

连接文件

连接文件的基本概念

在GCC中,连接文件扩展名一般是.ld, 是编译器链接阶段的输入文件,他主要用来表示我们编译的程序在目标系统里面的存储方式。在解释这个概念之前我们有必要在回顾一下程序编译的具体过程。

很早之前我们就知道程序编译要经过编译和链接两个阶段,但是现在各种GUI工具的盛行,很多时候我们都是直接点一个按钮就搞定了,工具实际在后台做了什么操作我们并不是特别清楚。实际上编译器的编译主要经过的两个阶段主要做的事情如下:

  • 预处理阶段:这个阶段编译器会对我们的代码进行预处理,主要就是进行宏的替换和注释的删除,这样我们就会得到比较干脆的代码,很多编译器不在单独列出这个预处理阶段,而是直接和编译过程综合在一起。

  • 编译阶段:这个阶段主要将我们的C语言编译成机器语言,生成的目标文件是.o的object文件和.lst的连接文件,这个连接文件和我们今天要讲的连接文件有区别,他类似于机器语言,但是里面的地址信息都是符号和相对地址,而不是真正的地址。编译阶段是针对源代码中的C文件的,每个C文件都会单独编译,所以编译器也没法获得绝对地址。

  • 连接阶段:这个阶段生成最终的可执行程序,这个阶段的输入文件主要有我们今天要讲的ld链接文件和编译阶段生成的所有.o文件,连接过程因为得到了所有的程序信息,所以它会根据ld文件中的地址信息安排代码在处理器中的存放方式,比如代码放到什么位置,变量放到什么位置等等。

连接文件中比较重要的几个部分

连接文件中内存的分布是按照region来分布的:

MEMORY
{
  VECTORS (rwx)      : ORIGIN = 0x0,         LENGTH = 0x0410
  ITCM (rwx)         : ORIGIN = 0x00000410,  LENGTH = 128K- 0x410
  DTCM (rwx)         : ORIGIN = 0x20000000,  LENGTH = 128K
}
ENTRY(_reset_init)
Heap_Size = 4K;
Stack_Size =41K;

SECTIONS
{
    /* The startup code goes first into INTERNAL_FLASH */
    .isr_vector :
    {
        __vector_table = .;
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > VECTORS


    .text :
    {
        *(.text)                 /* .text sections (code) */
        *(.text*)                /* .text* sections (code) */
        *(.rodata)               /* .rodata sections (constants, strings, etc.) */
        *(.rodata*)              /* .rodata* sections (constants, strings, etc.) */
        *(.srodata .srodata.*)
        *(.glue_7)               /* glue arm to thumb code */
        *(.glue_7t)              /* glue thumb to arm code */
        *(.eh_frame)
        *(.init)
        *(.fini)
    } > ITCM


    .data :
    {
        . = ALIGN(4);
        __DATA_RAM = .;
        __data_start__ = .;      /* create a global symbol at data start */
        __etext = .;
        *(.data)                 /* .data sections */
        *(.data*)                /* .data* sections */
        *(.sdata .sdata.*)
        *(.heapsram*)            /* This is only for the pulpino official test code. */
        __noncachedata_start__ = .;   /* create a global symbol at ncache data start */
        *(NonCacheable)
        __noncachedata_end__ = .;     /* define a global symbol at ncache data end */
        KEEP(*(.jcr*))
        . = ALIGN(4);
        __data_end__ = .;        /* define a global symbol at data end */
    } > DTCM

    .bss :
    {
        __bss_start__ = .;
        *(.bss*)
        *(COMMON)
        __bss_end__ = .;
    } > DTCM

    .heap :
    {
        __end__ = .;
        end = __end__;
        __heap_start = .;
        . = + Heap_Size;
        . = ALIGN(4);
        __heap_end = .;
    } > DTCM

    /* Set stack top to end of RAM */
    __StackTop = ORIGIN(DTCM) + LENGTH(DTCM) - 8;
    __StackLimit = __StackTop - Stack_Size;
    PROVIDE(__stack = __StackTop);

    /* Check if data + heap + stack exceeds RAM limit */
    ASSERT(__StackLimit >= __heap_end, "region RAM overflowed with stack")

}

上面是一个简单的RAM中分布的连接文件,可以看到我们主要将内存分成了VECTORS, ITCM, DTCM三个region,这三个部分中分别存放了不同的内容,比如*(.text*)就是代码段, *(.data*)就是内存中的变量,这些变量一般是需要初始化的全局变量,如果程序不是直接下载到RAM中而是Flash中,我们还需要通过AT关键字进行地址重定义。*(.bss*)是为初始化的全局变量,这些我们一般会在程序初始化的时候后将他们初始化为全零。

堆和栈的定义是直接采用定义地址偏移和定义符号来实现的,我们直接在用到的DATA段和BSS段之后指定了Heap_Size大小的空间作为堆空间,这里可以通过ALIGN关键字进行地址对齐。栈则是直接将后面的剩余空间直接利用,最后会有一个判断,用来计算最终地址是否溢出。

地址重定义

连接文件中还有一个比较关键的AT关键字这里没有体现,这个关键字主要是实现一个地址映射的功能,我们知道变量我们一般是放到RAM中去的,对于一些有初始值的变量我们不能简单的放到RAM中,因为对于MCU而言,我们不可能每次都去下载程序之后在运行,当程序从flash直接启动的时候,RAM数据并不会自动初始化,常用的方法是将这些RAM变量的初始值放到flash中去,在程序启动的时候手动将这些值初始化到RAM中,AT关键字就是实现这个功能的,它的作用就是告诉连接器某个段连接的时候放到哪个地址,运行的时候在哪个地址运行。

好了就酱吧,该去吃早饭了。

推荐阅读