当前位置: 首页 > news >正文

STM32两种不同的链接配置方式

对比两种不同的链接配置方式,主要体现在是否使用标准库、启动文件和系统调用支持方式的不同。下面详细逐项对比:


🧩 一、两个配置

配置 1:使用标准启动方式 + nano libc

LIBS = -lc -lm -lnosys
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=... -Wl,--gc-sections

这是比较 标准、温和的嵌入式设置,特点如下:

项目说明
-lc -lm链接标准 libc (newlib) 和 libm (math)
-lnosys提供空的系统调用桩(用于防止链接 _kill, _getpid 等失败)
-specs=nano.specs使用 newlib-nano,是更轻量的 libc 版本
没有 -nostdlib链接器仍然会使用 crt0.o 等默认启动代码
适合需要 printf, malloc 的工程,并希望较小代码体积

配置 2:完全裸机方式

LDFLAGS_GCC += \-nostdlib -nostartfiles \-specs=nosys.specs -specs=nano.specs

这是更为彻底控制的裸机配置

项目说明
-nostdlib不链接任何标准运行时(包括 libc, libgcc, crt0)
-nostartfiles不链接 crt0.o, crti.o, crtn.o 等启动代码
-specs=nosys.specs不启用系统调用(不自动拉入 _kill, _getpid
-specs=nano.specsnewlib-nano(但实际上 -nostdlib 会取消它除非手动链接)
❗ 必须自己提供:startup.s_start_exit_write 等运行时支持
适合极简/极控工程,如 bootloader、freestanding firmware

🆚 二、核心区别总结对比表:

特性配置 1:标准启动 + newlib-nano配置 2:裸机全控方式
启动文件(如 crt0.o✅ 自动链接❌ 不自动链接
标准 C 库 (newlib)✅ 默认链接 (-lc)❌ 不链接(你要手动提供函数或库)
printf, malloc✅ 可用❌ 除非你自己实现或手动链接
链接器脚本(-T linker.ld✅ 必须提供✅ 必须提供
系统调用符号(如 _sbrk✅ 自动拉入 nosys.specs 或你自定义❌ 你必须手动实现
控制程度⚪ 中等🔴 极高,完全裸机

🔧 三、用法建议:

使用标准库,但减小体积(推荐大多数项目)

LDFLAGS = -T linker.ld -specs=nano.specs -lc -lm -lnosys

适用于:USB、FreeRTOS、HAL、需要 printf、malloc 的项目。

你可以手动重定向 _write 来定制 printf 输出(如重定向到 UART、ITM)。


🔴 完全裸机 / 自己控制一切

LDFLAGS = -nostdlib -nostartfiles -T linker.ld -Wl,--gc-sections

适用于:bootloader、自定义运行时、不想用 libc 的极简项目。

你需要提供:

  • startup.s(或 _start
  • _sbrk, _write, _exit, _kill 这些裸函数
  • 不要使用 malloc、printf,或者自己实现

✅ 总结一句话:

  • 你希望轻量而不想完全脱离标准库时 👉 用配置 1(nano.specs, -lnosys, -lc -lm
  • 你希望完全掌控链接行为并自己写所有底层实现时 👉 用配置 2(-nostdlib -nostartfiles

当你自己写了 start.s(或 crt0.s)作为启动入口,并正确配置链接脚本使用你的 Reset_Handler_start就不会再链接系统自带的 crt0.o 或其他启动代码了


🔍 为什么不会链接系统的 crt0.o

默认情况下,GNU 工具链(如 arm-none-eabi-gcc)会自动链接标准 C 运行时启动代码 crt0.o(或 crtbegin.ocrtend.o):

  • 前提是你没有加 -nostartfiles-nostdlib
  • 或者你没有提供自定义的入口符号(比如 Reset_Handler

一旦你这样做了,就不会链接系统的启动文件:

  1. 自己写了 start.s/crt0.s,并提供了 _startReset_Handler
  2. 链接器参数中明确指定了 ENTRY(Reset_Handler) 或使用 -nostartfiles

这样,链接器认为你已经准备好了启动逻辑,不需要默认的启动文件,就不会链接它们了。


🚫 如果没注意,会隐式链接系统的 crt0

例如,如果你:

  • 没有 -nostartfiles
  • 没有自己的 start.s
  • 用的是 main(),但没指定入口函数

那编译器会自动加上类似 /lib/gcc/.../crt0.o 来完成初始化。

这就是为啥你有时看到“程序居然能跑 main 但你没写启动文件”——是因为系统偷偷帮你加了。


✅ 总结一句话

只要你提供了自己的 start.s 并控制链接过程(比如用 -nostartfiles 或指定入口),系统就不会再加 crt0.o,你就是自己控制运行时的“导演”。


完全正确 ✅!

链接器(ld)识别你是否需要默认的启动文件(如 crt0.o,关键就是:


🔍 识别入口的两个方式:

✅ 方式 1:在链接脚本中写 ENTRY(...)

ENTRY(Reset_Handler)
  • 明确告诉链接器程序从哪开始执行
  • 通常你写了自己的 start.s,就会加这个
  • 会跳过默认的 crt0.o

✅ 方式 2:链接器默认查找 _start

如果你没写 ENTRY(...),链接器默认使用 _start 作为入口:

.global _start
_start:...
  • 如果你提供了 _start,链接器会用它,不再链接系统的 _start(如 crt0.o)
  • 否则它会去找系统默认的启动文件来提供 _start

🔧 补充控制选项

GCC 选项意义
-nostartfiles不自动加 crt0.o 等启动文件(但还保留标准库)
-nostdlib更彻底,连 libc(如 newlib/libm) 都不加
-Wl,-e,XXX显式告诉链接器使用 XXX 作为程序入口,和 ENTRY() 是一样的意思
-T script.ld指定你自己的链接脚本,通常其中会有 ENTRY()

✅ 总结一句话:

链接器只要看到你指定了 ENTRY() 或你自己提供了 _start(或 Reset_Handler),它就不会再偷偷地加系统的 crt0.o


如果你想彻底掌控裸机程序启动过程,那就要做到:

  • ✅ 写你自己的 start.s
  • ✅ 提供 ENTRY(...)
  • ✅ 明确使用 -nostartfiles(可选)
  • ✅ 提供自己的链接脚本 .ld

说到 crt0(C runtime zero),它是程序启动时的“第一块敲门砖”,非常关键。


crt0 到底干了啥?

简单来说,crt0 是程序启动时执行的第一段代码,负责为你的程序搭建运行环境,让 main() 能正常运行。


crt0 的主要工作:

  1. 设置堆栈指针(Stack Pointer)
    在裸机环境下,程序一开始,堆栈指针(SP)没有被设置,crt0 会帮你把 SP 指向正确位置。

  2. 初始化数据段(.data)
    把存储在 Flash 中的初始化变量复制到 RAM。

  3. 清零 BSS 段(.bss)
    清理未初始化的全局变量区(把它们清成 0)。

  4. 调用全局构造函数
    对 C++ 来说,调用 __libc_init_array(),执行所有全局和静态对象的构造函数。

  5. 调用 main() 函数
    运行完初始化后,跳转到用户写的 main(),开始程序主体。

  6. 处理程序结束
    在裸机一般不会返回,但在某些环境,crt0 会处理 main 返回后的清理和退出。


形象比喻

  • crt0 就是舞台灯光和幕布的拉开动作,帮演员(main 函数)做好上场准备。

总结

crt0 负责
初始化硬件环境(堆栈指针)
复制初始化数据(.data)
清空未初始化数据区(.bss)
调用全局/静态构造函数(C++)
进入主函数 main()

当然可以!以下是一个适用于 ARM Cortex-M(如 STM32)最小 crt0.s 启动文件示例,内容包括堆栈初始化、.data/.bss 初始化和进入 main() 的全过程。


🔧 示例:最小裸机 crt0.s

    .syntax unified.cpu cortex-m3.thumb.global _start.global Reset_Handler.global main.extern __libc_init_array/* 堆栈顶地址,由链接脚本定义 */.section .isr_vector, "a", %progbits.word _estack          /* 初始堆栈指针 */.word Reset_Handler    /* 复位中断向量 */.text.thumb_func
Reset_Handler:
_start:/* 初始化 .data 段 */ldr r0, =_sidata       /* Flash 中 .data 的起始地址 */ldr r1, =_sdata        /* RAM 中 .data 的目标起始地址 */ldr r2, =_edata        /* RAM 中 .data 的结束地址 */
data_copy:cmp r1, r2ittt ltldrlt r3, [r0], #4strlt r3, [r1], #4blt data_copy/* 清零 .bss 段 */ldr r1, =_sbssldr r2, =_ebssmovs r3, #0
bss_zero:cmp r1, r2strlt r3, [r1], #4blt bss_zero/* 如果是 C++,调用构造函数数组 */bl __libc_init_array/* 跳转到 main */bl main/* 如果 main 返回,就停在这里 */
hang:b hang

🧩 链接脚本对应段(摘录)

你的链接脚本(.ld 文件)要定义这些符号:

_estack = ORIGIN(RAM) + LENGTH(RAM); /* 栈顶 */_sidata = LOADADDR(.data); /* .data 在 Flash 中的起始 */
_sdata = ADDR(.data);      /* .data 在 RAM 中的起始 */
_edata = .;                /* .data 在 RAM 中的结束 */_sbss = .;                 /* .bss 起始 */
_ebss = .;                 /* .bss 结束 */

✅ 支持 C/C++ 都可以

  • 如果你用 C++,__libc_init_array 会自动处理全局/静态对象构造。
  • 如果你只用 C,也可以不调用 __libc_init_array

适用范围

✅ 适用于:

  • STM32F1/F4/F7 等 Cortex-M 系列
  • 裸机工程(无 RTOS,无 HAL)

http://www.dtcms.com/a/268392.html

相关文章:

  • Python 中 ffmpeg-python 库的详细使用
  • CppCon 2018 学习:Undefined Behavior is Not an Error
  • Solidity——pure 不消耗gas的情况、call和sendTransaction区别
  • 【PyTorch】PyTorch中torch.nn模块的池化层
  • 汇编与接口技术:8259中断实验
  • Dify+Ollama+QwQ:3步本地部署,开启AI搜索新篇章
  • 1025 反转链表(附详细注释,逻辑分析)
  • 网络调式常用知识
  • 【机器学习笔记Ⅰ】1 机器学习
  • 【拓扑空间】可分性2
  • Spring Boot 集成 Thymeleaf​​ 的快速实现示例,无法渲染页面问题解决
  • 记录一点开发技巧
  • Spring Boot 3.x 整合 Swagger(springdoc-openapi)实现接口文档
  • class类和style内联样式的绑定 + 事件处理 + uniapp创建自定义页面模板
  • React Ref 指南:原理、实现与实践
  • 深度学习篇---Yolov系列
  • 远程桌面启动工具
  • Flutter 每日翻译之 Widget
  • Day53GAN对抗生成网络思想
  • MySQL主从复制与读写分离概述
  • 一文了解PMI、CSPM、软考、、IPMA、PeopleCert和华为项目管理认证
  • Protein FID:AI蛋白质结构生成模型评估新指标
  • Redis-主从复制-分布式系统
  • 算法学习day15----蓝桥杯--进制转换
  • Web攻防-XMLXXE无回显带外SSRF元数据DTD实体OOB盲注文件拓展
  • 大数据Hadoop之——Flink1.17.0安装与使用(非常详细)
  • 桥梁桥拱巡检机器人cad+【4张】设计说明书+绛重+三维图
  • 了解微服务
  • JVM的内存区域划分,类加载器和GC
  • Modbus 与 BACnet 协议互操作:工业协议转换方案(一)