了解 ARM Cortex-M 微控制器的链接描述文件
Understanding the linkerscript for an ARM Cortex-M microcontroller
我使用的是 STMicroelectronics 的 STM32F746NG 微控制器。该设备基于 ARM Cortex-M7 架构。我花了很多时间来理解示例项目中的 linkerscript。我弄清楚了基础知识,但我仍然无法掌握其中的大部分内容。请帮助我理解这些部分。
link脚本开始
link脚本开始如下:
/* Entry Point */
ENTRY(Reset_Handler) /* The function named 'Reset_Handler' is defined */
/* in the 'startup.s' assembly file. */
/* Highest address of the user mode stack */
/* Remember: the stack points downwards */
_estack = 0x20050000; /* End of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* Required amount of heap */
_Min_Stack_Size = 0x400; /* Required amount of stack */
/* --------------------------------------------------------------------*/
/* MEMORY AREAS */
/* --------------------------------------------------------------------*/
MEMORY
{
/* FLASH MEMORY */
/* ------------ */
/* Remember: the flash memory on this device can */
/* get accessed through either the AXIM bus or the */
/* ITCM bus. Accesses on the ITCM bus start at */
/* address 0x0020 0000. Accesses on the AXIM bus */
/* at address 0x0800 0000. */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
/* FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 1024K */
/* RAM MEMORY */
/* ---------- */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
}
矢量图table和程序代码
定义内存区域后,linkerscript 继续定义部分。 linkerscript 中定义的第一部分是向量 table。它必须在闪存的第一个字节结束。
/* --------------------------------------------------------------------*/
/* OUTPUT SECTIONS */
/* --------------------------------------------------------------------*/
SECTIONS
{
/****************************/
/* VECTOR TABLE */
/****************************/
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Vector Table */
. = ALIGN(4);
} >FLASH
向量table插入后,是程序代码的时候了:
/****************************/
/* PROGRAM CODE */
/****************************/
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* Glue ARM to Thumb code */
*(.glue_7t) /* Glue Thumb to ARM code */
*(.eh_frame)
/* Note: The function ‘.text.Reset_Handler’ is one of the *(.text*) sections, */
/* such that it gets linked into the output .text section somewhere here. */
/* We can verify the exact spot where the Reset_Handler section is positioned, by */
/* examining the second entry of the vector table. */
/* A test has given the following results:
/* FLASH (rx) : ORIGIN = 0x0800 0000 ==> Reset_Handler = 0x0800 1C91 */
/* FLASH (rx) : ORIGIN = 0x0020 0000 ==> Reset_Handler = 0x0020 1CB9 */
/*
/* In both cases, the Reset_Handler section ends up a few hundred bytes after the */
/* vector table in Flash. But in the first case, the “Reset_Handler” symbol points */
/* to the Reset-code through AXIM-interface, whereas in the latter case it points */
/* to the Reset-code through the ITCM-interface. */
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* Define a global symbol at end of code */
} >FLASH
linkerscript 定义了 e_text
全局符号,表示闪存中程序代码结束的地址。
常量数据
只读数据最终也存储在闪存中(将其放入易失的 RAM 中毫无意义)。 linkerscript 定义 .rodata
部分应该在 flash:
/****************************/
/* CONSTANT DATA */
/****************************/
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
flash 中的神秘部分
在定义了常量只读数据的位置之后,linkerscript 定义了几个 'mysterious' 部分也应该在闪存中结束:
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} >FLASH
.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
我不知道那些部分是什么。所以让这成为第一个问题。这些部分是什么,它们出现在哪些目标文件中?如您所知,linkerscript 需要link 一些目标文件。我不知道这些神秘部分存在于哪些目标文件中:
.ARM.extab
.ARM
.preinit_array
.init_array
.fini_array
闪存分配到此结束。 linkerscript 继续定义最终出现在 RAM 中的部分。
RAM 中的部分
.data
和 .bss
部分对我来说很清楚。没有问题。
/****************************/
/* INITIALIZED DATA */
/****************************/
_sidata = LOADADDR(.data);
.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
/****************************/
/* UNINITIALIZED DATA */
/****************************/
. = ALIGN(4);
.bss :
{
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
linkerscript 还定义了一个 ._user_heap_stack
部分:
/****************************/
/* USER_HEAP_STACK SECTION */
/****************************/
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
显然此部分不会立即使用。它仅被定义为检查 RAM 是否还有足够的 space 用于堆栈和堆。如果不是这种情况(.
超出了顶部 RAM 地址),则会抛出 linker 错误。
link脚本结束
这就是 link脚本的结尾。老实说,我不知道它的作用。所以这是第二个问题:下面是什么意思?
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
/* END OF LINKERSCRIPT */
.ARM.extab和.ARM.exidx与平仓有关。您可以在此处找到更多信息 http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/index.html。如果您不关心展开(展开对 C++ 异常和调试很有用),则不需要它们。
这些符号与在 main() 之前/之后调用的 C/C++ 构造函数和析构函数启动和拆卸代码有关。名为 .init、.ctors、.preinit_array 和 .init_array 的部分与 C/C++ 对象的初始化有关,而名为 .fini、.fini_array 和.dtors 用于拆除。开始和结束符号定义与此类操作相关的代码部分的开始和结束,并且可以从运行时支持代码的其他部分引用。
.preinit_array 和 .init_array 部分包含指向将在初始化时调用的函数的指针数组。 .fini_array 是将在销毁时调用的函数数组。大概开始和结束标签用于遍历这些列表。
堆:不是真的,那部分允许为堆保留一些space,为堆栈保留一些space。显然,如果保留区域的总和超出 RAM 边界,则会出现错误。这是一个例子:
_Min_Heap_Size = 0; /* 所需的堆数量 /
_Min_Stack_Size = 0x400; / 所需的堆栈数量 */
._user_heap_stack :
{
. =对齐(4);
提供(结束=。);
提供 (_end = .);
. =。 + _Min_Heap_Size;
. =。 + _Min_Stack_Size;
. =对齐(4);
} >RAM
到 link 库我更喜欢不同的表示法,这只是裸机无 RTOS C++ 项目的示例:
GROUP(libgcc.a libc_nano.a libstdc++_nano.a libm.a libcr_newlib_nohost.a crti.o crtn.o crtbegin.o crtend.o)
首先,你对这个概念的理解是错误的;这就是我所相信的。我在理解这个概念的过程中也遇到过类似的问题。
用简单的语言,我可以向您解释链接描述文件为我们提供了三个主要功能:
- 入口点
- 运行-主存中的时间地址。
- 复制下来的程序
让我们考虑一个例子,
假设我们的项目中有N个.c文件。现在编译后每个文件都包含自己的翻译单元,称为目标文件。
每个目标文件包含 .text
section/segment 其中包含实际代码。 .data section/segment 用于数据。
为了合并每个翻译单元的所有 .text
部分,链接描述文件为此提供了一些特定的命令。 .data
部分也是如此。
合并所有目标文件后,最终的可执行文件就可以使用了。
现在谈到一些悖论...
Cortex-M系列的入口点就是ResetISR。
执行完ResetISR函数并初始化SoC中的其他可屏蔽中断后,接下来就是copy-down的过程。
复制下来的过程只不过是将 .data
部分复制到 RAM 中(甚至包括有趣的 .bss
部分,但我现在不考虑那部分)。
从 ROM 复制到 RAM 是必不可少的,因为实际的 ELF 文件总是存储在 ROM 中。
执行完所有这些与启动相关的事情后,我们现在可以调用我们的 main()
函数。
我使用的是 STMicroelectronics 的 STM32F746NG 微控制器。该设备基于 ARM Cortex-M7 架构。我花了很多时间来理解示例项目中的 linkerscript。我弄清楚了基础知识,但我仍然无法掌握其中的大部分内容。请帮助我理解这些部分。
link脚本开始
link脚本开始如下:
/* Entry Point */
ENTRY(Reset_Handler) /* The function named 'Reset_Handler' is defined */
/* in the 'startup.s' assembly file. */
/* Highest address of the user mode stack */
/* Remember: the stack points downwards */
_estack = 0x20050000; /* End of RAM */
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x200; /* Required amount of heap */
_Min_Stack_Size = 0x400; /* Required amount of stack */
/* --------------------------------------------------------------------*/
/* MEMORY AREAS */
/* --------------------------------------------------------------------*/
MEMORY
{
/* FLASH MEMORY */
/* ------------ */
/* Remember: the flash memory on this device can */
/* get accessed through either the AXIM bus or the */
/* ITCM bus. Accesses on the ITCM bus start at */
/* address 0x0020 0000. Accesses on the AXIM bus */
/* at address 0x0800 0000. */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
/* FLASH (rx) : ORIGIN = 0x00200000, LENGTH = 1024K */
/* RAM MEMORY */
/* ---------- */
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 320K
}
矢量图table和程序代码
定义内存区域后,linkerscript 继续定义部分。 linkerscript 中定义的第一部分是向量 table。它必须在闪存的第一个字节结束。
/* --------------------------------------------------------------------*/
/* OUTPUT SECTIONS */
/* --------------------------------------------------------------------*/
SECTIONS
{
/****************************/
/* VECTOR TABLE */
/****************************/
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Vector Table */
. = ALIGN(4);
} >FLASH
向量table插入后,是程序代码的时候了:
/****************************/
/* PROGRAM CODE */
/****************************/
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.glue_7) /* Glue ARM to Thumb code */
*(.glue_7t) /* Glue Thumb to ARM code */
*(.eh_frame)
/* Note: The function ‘.text.Reset_Handler’ is one of the *(.text*) sections, */
/* such that it gets linked into the output .text section somewhere here. */
/* We can verify the exact spot where the Reset_Handler section is positioned, by */
/* examining the second entry of the vector table. */
/* A test has given the following results:
/* FLASH (rx) : ORIGIN = 0x0800 0000 ==> Reset_Handler = 0x0800 1C91 */
/* FLASH (rx) : ORIGIN = 0x0020 0000 ==> Reset_Handler = 0x0020 1CB9 */
/*
/* In both cases, the Reset_Handler section ends up a few hundred bytes after the */
/* vector table in Flash. But in the first case, the “Reset_Handler” symbol points */
/* to the Reset-code through AXIM-interface, whereas in the latter case it points */
/* to the Reset-code through the ITCM-interface. */
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = .; /* Define a global symbol at end of code */
} >FLASH
linkerscript 定义了 e_text
全局符号,表示闪存中程序代码结束的地址。
常量数据
只读数据最终也存储在闪存中(将其放入易失的 RAM 中毫无意义)。 linkerscript 定义 .rodata
部分应该在 flash:
/****************************/
/* CONSTANT DATA */
/****************************/
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
. = ALIGN(4);
} >FLASH
flash 中的神秘部分
在定义了常量只读数据的位置之后,linkerscript 定义了几个 'mysterious' 部分也应该在闪存中结束:
.ARM.extab :
{
*(.ARM.extab* .gnu.linkonce.armextab.*)
} >FLASH
.ARM :
{
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >FLASH
.preinit_array :
{
PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .);
} >FLASH
.init_array :
{
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .);
} >FLASH
.fini_array :
{
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .);
} >FLASH
我不知道那些部分是什么。所以让这成为第一个问题。这些部分是什么,它们出现在哪些目标文件中?如您所知,linkerscript 需要link 一些目标文件。我不知道这些神秘部分存在于哪些目标文件中:
.ARM.extab
.ARM
.preinit_array
.init_array
.fini_array
闪存分配到此结束。 linkerscript 继续定义最终出现在 RAM 中的部分。
RAM 中的部分
.data
和 .bss
部分对我来说很清楚。没有问题。
/****************************/
/* INITIALIZED DATA */
/****************************/
_sidata = LOADADDR(.data);
.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
/****************************/
/* UNINITIALIZED DATA */
/****************************/
. = ALIGN(4);
.bss :
{
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
linkerscript 还定义了一个 ._user_heap_stack
部分:
/****************************/
/* USER_HEAP_STACK SECTION */
/****************************/
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
显然此部分不会立即使用。它仅被定义为检查 RAM 是否还有足够的 space 用于堆栈和堆。如果不是这种情况(.
超出了顶部 RAM 地址),则会抛出 linker 错误。
link脚本结束
这就是 link脚本的结尾。老实说,我不知道它的作用。所以这是第二个问题:下面是什么意思?
/* Remove information from the standard libraries */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
.ARM.attributes 0 : { *(.ARM.attributes) }
}
/* END OF LINKERSCRIPT */
.ARM.extab和.ARM.exidx与平仓有关。您可以在此处找到更多信息 http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/index.html。如果您不关心展开(展开对 C++ 异常和调试很有用),则不需要它们。
这些符号与在 main() 之前/之后调用的 C/C++ 构造函数和析构函数启动和拆卸代码有关。名为 .init、.ctors、.preinit_array 和 .init_array 的部分与 C/C++ 对象的初始化有关,而名为 .fini、.fini_array 和.dtors 用于拆除。开始和结束符号定义与此类操作相关的代码部分的开始和结束,并且可以从运行时支持代码的其他部分引用。
.preinit_array 和 .init_array 部分包含指向将在初始化时调用的函数的指针数组。 .fini_array 是将在销毁时调用的函数数组。大概开始和结束标签用于遍历这些列表。
堆:不是真的,那部分允许为堆保留一些space,为堆栈保留一些space。显然,如果保留区域的总和超出 RAM 边界,则会出现错误。这是一个例子:
_Min_Heap_Size = 0; /* 所需的堆数量 / _Min_Stack_Size = 0x400; / 所需的堆栈数量 */
._user_heap_stack : { . =对齐(4); 提供(结束=。); 提供 (_end = .); . =。 + _Min_Heap_Size; . =。 + _Min_Stack_Size; . =对齐(4); } >RAM
到 link 库我更喜欢不同的表示法,这只是裸机无 RTOS C++ 项目的示例: GROUP(libgcc.a libc_nano.a libstdc++_nano.a libm.a libcr_newlib_nohost.a crti.o crtn.o crtbegin.o crtend.o)
首先,你对这个概念的理解是错误的;这就是我所相信的。我在理解这个概念的过程中也遇到过类似的问题。
用简单的语言,我可以向您解释链接描述文件为我们提供了三个主要功能:
- 入口点
- 运行-主存中的时间地址。
- 复制下来的程序
让我们考虑一个例子,
假设我们的项目中有N个.c文件。现在编译后每个文件都包含自己的翻译单元,称为目标文件。
每个目标文件包含 .text
section/segment 其中包含实际代码。 .data section/segment 用于数据。
为了合并每个翻译单元的所有 .text
部分,链接描述文件为此提供了一些特定的命令。 .data
部分也是如此。
合并所有目标文件后,最终的可执行文件就可以使用了。
现在谈到一些悖论...
Cortex-M系列的入口点就是ResetISR。 执行完ResetISR函数并初始化SoC中的其他可屏蔽中断后,接下来就是copy-down的过程。
复制下来的过程只不过是将 .data
部分复制到 RAM 中(甚至包括有趣的 .bss
部分,但我现在不考虑那部分)。
从 ROM 复制到 RAM 是必不可少的,因为实际的 ELF 文件总是存储在 ROM 中。
执行完所有这些与启动相关的事情后,我们现在可以调用我们的 main()
函数。