目录

说明

要了解 libopencm3 的代码结构, 就需要先了解它编译产生的固件结构和启动顺序, 这部分和 CMSIS 不一样

ld, Linker script, 连接器脚本文件

在 2020-11-29 的改动 drop all part specific ld files之前, 在 lib/stm32/f0 - f7 目录下, 可以看到各个型号的ld文件, 在这个提交中删除了所有具体型号的ld文件, 改为编译中使用脚本生成. 在用户的代码目录下可以看到生成的ld文件. 以下说明ld文件的功能和内容.

ld 文件的功能和结构

以ld为扩展名的文件是针对具体MCU的连接器脚本(Linker script), 连接器脚本用于在link阶段, 告诉linker(连接器)关于生成固件时需要的存储布局和根据不同内存区域的启动方式对其进行初始化. 连接器脚最关键的一点是指定了 flash 和 RAM 的起始位置和大小. 在连接阶段通过 -Tscriptname.ld参数传递给连接器.

连接器脚本会包含以下的内容

  • Memory layout 内存布局
  • Entry point definition 程序入口定义
  • Section definitions 各存储区的定义(定义Flash、RAM中代码和数据的存放位置)

ld 文件的示例代码说明

以下以 libopencm3 的 STM32F103x8 ld 文件为例

/* Linker script for STM32F103x8, 64k flash, 20k RAM. */

/* Define memory regions. 定义片上存储在地址空间的起始位置和大小 */
MEMORY
{
rom (rx) : ORIGIN = 0x08000000, LENGTH = 64K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
} /* Enforce emmition of the vector table. 声明中断向量表 */
EXTERN (vector_table) /* Define the entry point of the output file. 定义程序入口, 程序将从 reset_handler 开始执行 */
ENTRY(reset_handler) /* Define sections. */
SECTIONS
{
/** .text对应程序的可执行代码 */
.text : {
*(.vectors) /* Vector table 中断向量表 */
*(.text*) /* Program code 程序代码 */
. = ALIGN(4); /* 4字节对齐 */
*(.rodata*) /* 只读数据, 程序中使用的常量数据 */
. = ALIGN(4); /* 4字节对齐, 对应一个32bit字 */
} >rom /* C++ Static constructors/destructors, also used for __attribute__
* ((constructor)) and the likes
* .preinit_array, .init_array, .fini_array 指向构造函数和解构函数的指针数组
*/
.preinit_array : {
. = ALIGN(4);
__preinit_array_start = .; /* KEEP() 这个函数用在SECTIONS内部, 用于连接阶段的垃圾回收(--gc-sections参数开启), 在创建依赖树时定义这部分为root节点,
* 标记为在使用, 这样连接器就会保留这部分内存, 哪怕里面没有变量被引用.
*
* The KEEP statement within a linker script will instruct the linker to keep the specified section,
* even if no symbols inside it are referenced. This statement is used within the SECTIONS section of the
* linker script. This becomes relevant when garbage collection is performed at link time, enabled by
* passing the --gc-sections switch to the linker. The KEEP statement instructs the linker to use the
* specified section as a root node when creating a dependency graph, looking for unused sections.
* Essentially forcing the section to be marked as used.
* This statement is commonly seen in linker scripts targeting the ARM architecture for placing the
* interrupt vector table at offset 0x00000000. Without this directive the table, which might not be
* referenced explicitly in code, would be pruned out.
*/
KEEP (*(.preinit_array))
__preinit_array_end = .;
} >rom
.init_array : {
. = ALIGN(4);
__init_array_start = .;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
__init_array_end = .;
} >rom
.fini_array : {
. = ALIGN(4);
__fini_array_start = .;
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
__fini_array_end = .;
} >rom /*
* Another section used by C++ stuff, appears when using newlib with
* 64bit (long long) printf support
*
* C++使用的部分, 当使用带64位printf支持的newlib时需要
*/
.ARM.extab : {
*(.ARM.extab*)
} >rom
.ARM.exidx : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} >rom . = ALIGN(4);
_etext = .; /**
* .data对应已初始化的全局变量, 编译后位于可执行文件中, 由启动代码加载到数据区中
* 在单片机中这部分数据会存于flash中, 由启动代码把这部分内容拷贝到RAM
*/
.data : {
_data = .;
*(.data*) /* Read-write initialized data */
. = ALIGN(4);
_edata = .;
} >ram AT >rom
_data_loadaddr = LOADADDR(.data); /* .bss段是没有初始值的全局变量, 由启动代码把这部分内容全初始化为0 */
.bss : {
*(.bss*) /* Read-write zero initialized data */
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >ram /*
* The .eh_frame section appears to be used for C++ exception handling.
* You may need to fix this if you're using C++.
*/
/DISCARD/ : { *(.eh_frame) } . = ALIGN(4);
end = .;
} PROVIDE(_stack = ORIGIN(ram) + LENGTH(ram));

ld 相关参考资料

启动代码

libopencm3 和 CMSIS 不一样, 没有使用汇编代码的startup文件, 而是用 vector.c 生成startup文件, 这个文件位于 lib/cm3/ 目录下.

启动文件 vector.c

#include <libopencm3/cm3/scb.h>
// 在头文件中声明中断项列表的类型 vector_table_t
#include <libopencm3/cm3/vector.h> // 根据不同的芯片, 引入 pre_main() 方法, STM32F1 没有对应的方法
/* load optional platform dependent initialization routines */
#include "../dispatch/vector_chipset.c" // 弱定义, 会被实际值覆盖
/* load the weak symbols for IRQ_HANDLERS */
#include "../dispatch/vector_nvic.c" /* Less common symbols exported by the linker script(s): */
typedef void (*funcp_t) (void);
extern funcp_t __preinit_array_start, __preinit_array_end;
extern funcp_t __init_array_start, __init_array_end;
extern funcp_t __fini_array_start, __fini_array_end; // 主函数声明
int main(void);
// while循环函数, 空阻塞函数
void blocking_handler(void);
// 空函数
void null_handler(void); // 定义中断向量表, 定义各个中断对应的处理函数
__attribute__ ((section(".vectors")))
vector_table_t vector_table = {
.initial_sp_value = &_stack,
.reset = reset_handler,
.nmi = nmi_handler, // Non maskable interrupt 不可屏蔽中断
.hard_fault = hard_fault_handler, // All classes of fault. /* Those are defined only on CM3 or CM4 */
#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
.memory_manage_fault = mem_manage_handler, // Memory management.
.bus_fault = bus_fault_handler, // Pre-fetch fault, memory access fault.
.usage_fault = usage_fault_handler, // Undefined instruction or illegal state.
.debug_monitor = debug_monitor_handler,
#endif .sv_call = sv_call_handler, // System service call via SWI instruction, (Software Interrupt, SWI)软件中断指令用于产生软中断, 实现从用户模式变换到管理模式
.pend_sv = pend_sv_handler, // Pendable request for system service. 可挂起的中断
.systick = sys_tick_handler, // System tick timer
.irq = {
IRQ_HANDLERS // 中断向量的定义在芯片的irq.json中, 定义在 irq2nvic_h 脚本生成lib目录下对应型号下的 vector_nvic.c
}
}; // reset_handler 是连接器脚本中定义的程序执行入口, 下面的代码是具体的实现
void __attribute__ ((weak)) reset_handler(void)
{
volatile unsigned *src, *dest;
funcp_t *fp; for (src = &_data_loadaddr, dest = &_data;
dest < &_edata;
src++, dest++) {
*dest = *src;
} while (dest < &_ebss) {
*dest++ = 0;
} /* Ensure 8-byte alignment of stack pointer on interrupts */
/* Enabled by default on most Cortex-M parts, but not M3 r1 */
SCB_CCR |= SCB_CCR_STKALIGN; /* might be provided by platform specific vector.c */
pre_main(); /* Constructors. */
for (fp = &__preinit_array_start; fp < &__preinit_array_end; fp++) {
(*fp)();
}
for (fp = &__init_array_start; fp < &__init_array_end; fp++) {
(*fp)();
} /* Call the application's entry point. */
(void)main(); /* Destructors. */
for (fp = &__fini_array_start; fp < &__fini_array_end; fp++) {
(*fp)();
} } void blocking_handler(void)
{
while (1);
} void null_handler(void)
{
/* Do nothing. */
} #pragma weak nmi_handler = null_handler
#pragma weak hard_fault_handler = blocking_handler
#pragma weak sv_call_handler = null_handler
#pragma weak pend_sv_handler = null_handler
#pragma weak sys_tick_handler = null_handler /* Those are defined only on CM3 or CM4 */
#if defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__)
#pragma weak mem_manage_handler = blocking_handler
#pragma weak bus_fault_handler = blocking_handler
#pragma weak usage_fault_handler = blocking_handler
#pragma weak debug_monitor_handler = null_handler
#endif

LibOpenCM3(三) .ld文件(连接器脚本)和startup代码说明的更多相关文章

  1. 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版

    上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文 ...

  2. RCP: MANIFEST.MF, plugin.xml, build.properties三种文件的区别

    在Eclipse插件开发中, MANIFEST.MF, plugin.xml, build.properties是三种最常见的文件,由于它们共享同一个编辑器(Plug-in Manifest Edit ...

  3. Tomcat剖析(三):连接器(2)

    Tomcat剖析(三):连接器(2) 1. Tomcat剖析(一):一个简单的Web服务器 2. Tomcat剖析(二):一个简单的Servlet服务器 3. Tomcat剖析(三):连接器(1) 4 ...

  4. Linux中/etc/fstab /etc/mtab /proc/mounts这三个文件的分析与比较 分区表位置

    本文主要讲解Linux中/etc/fstab /etc/mtab /proc/mounts这三个文件的作用以及不同之处. 转自http://haohaozhang.blog.51cto.com/917 ...

  5. u-boot(三)启动文件

    目录 u-boot(三)启动文件 汇编 C:_start_armboot 代码摘要 C:main_loop 内核启动 菜单处理(自定义实现) 命令处理 title: u-boot(三)启动文件 tag ...

  6. Oracle启动中,spfile.ora、init<SID>.ora、spfile<SID>.ora 这三个文件正确的先后顺序是什么?

    Oracle启动中,spfile.ora.init<SID>.ora.spfile<SID>.ora 这三个文件正确的先后顺序是什么? 解答:启动数据库,使用startup命令 ...

  7. Unity3D开发入门教程(三)——添加启动脚本

    五邑隐侠,本名关健昌,12年游戏生涯. 本教程以 Unity 3D + VS Code + C# + tolua 为例. 一.启动脚本 第一篇 "搭建开发环境",在 "配 ...

  8. Java基础知识笔记(三:文件与数据流)

    一.输入流与输出流 输入流将数据从文件.标准输入或其他外部输入设备中加载到内存.输出流的作用则刚好相反,即将在内存中的数据保存到文件中,或传输给输出设备.输入流在Java语言中对应于抽象类java.i ...

  9. 用于svn添加当前目录下所有未追踪的文件,和删除所有手动删除的文件的脚本

    由于要经常用到类似与 git 中的 git add --all 这种操作,但是发现svn中并不支持类似的操作. 虽然可以使用 wildcard 进行匹配,但是 wildcard是在shell中进行匹配 ...

随机推荐

  1. redis的bind误区

    对于Redis中bind的正确的理解是:bind:是绑定本机的IP地址,(准确的是:本机的网卡对应的IP地址,每一个网卡都有一个IP地址),而不是redis允许来自其他计算机的IP地址.如果指定了bi ...

  2. Mysql存储过程二

    1.MySQL中创建存储过程时通过DEFINER和SQL SECURITY设置访问权限 procedure与function.trigger等创建时紧接着CREATE都有个definer可选项,该de ...

  3. Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  4. RocketMQ 介绍与安装

    目录 RocketMQ 介绍 MQ 介绍 MQ 作用 MQ 缺点 MQ 常见产品 RocketMQ 简介 RocketMQ 架构 RocketMQ 安装 RocketMQ 介绍 MQ 介绍 定义: M ...

  5. javascript随机变色--案例

    1.打开网页,网页效果如图所示 代码如下: 1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset=" ...

  6. C#获取http图片

    public Image GetHttpImage(string url) { var client = new HttpClient(); var uri = new Uri(Uri.EscapeU ...

  7. 【机器学习】GMM和EM算法

    机器学习算法-GMM和EM算法 目录 机器学习算法-GMM和EM算法 1. GMM模型 2. GMM模型参数求解 2.1 参数的求解 2.2 参数和的求解 3. GMM算法的实现 3.1 gmm类的定 ...

  8. golang gin框架中实现大文件的流式上传

    一般来说,通过c.Request.FormFile()获取文件的时候,所有内容都全部读到了内存.如果是个巨大的文件,则可能内存会爆掉:且,有的时候我们需要一边上传一边处理. 以下的代码实现了大文件流式 ...

  9. Superset SSO改造和自定义宏命令

    目录 背景 关于Superset 需要解决的问题 定制化改造 准备环境 改造OAuth SSO 安装依赖 配置SSO 添加自定义的SecurityManager 运行一下吧 自定义宏命令 开启配置 添 ...

  10. 论文解读第三代GCN《 Deep Embedding for CUnsupervisedlustering Analysis》

    Paper Information Titlel:<Semi-Supervised Classification with Graph Convolutional Networks>Aut ...