嵌入式进阶——嵌入式MCU编译工具链总结

嵌入式MCU编译工具链全面总结

本文介绍MCU在编译过程中的一些知识及技能,使用GLM5.0完成编写,作者只是提供了大纲,总结了从业以来所接触到的编译相关技能。相关附件存放到gitee。需要的自取------https://gitee.com/GWLZ/embedded-advanced.git

compile

目录

  1. 编译工具链可执行文件介绍
  2. 常用嵌入式编译选项
  3. 编译详细流程
  4. 链接流程与链接脚本
  5. 变量与函数的存储位置和生命周期
  6. ELF文件后处理与产物生成
  7. LST文件与MAP文件的应用
  8. Ninja与GN项目管理框架
  9. 资深工程师经验与技巧

1. 编译工具链介绍

在官方获取到编译工具包后,会发现在bin目录下有很多的可执行程序,我们编写的代码正式进过这些可执行文件的层层加工,最终变成了MCU能执行的机器码。下面就逐一介绍下常用的工具及其入参。

下载工具链:访问Arm Developer官网的下载页面,该工具链支持32位Arm Cortex-A、Cortex-M和Cortex-R系列处理器,包含GNU编译器(GCC),完全免费。

下载链接:https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads

备用页面:如果上述链接失效,可访问 https://developer.arm.com/downloads/-/gnu-rm。请注意该页面已不再更新,但可用于查找历史版本。

具体文件:下载文件名类似 arm-gnu-toolchain-<版本号>-x86_64-arm-none-eabi.exe 的可执行文件。例如,当前可用的一个版本是 gcc-arm-none-eabi-10.3-2021.10-win32.exe。

安装并配置:运行下载的 .exe 文件,按照安装向导完成安装。安装完成后,需要将工具链的 bin 目录路径添加到系统的 PATH 环境变量中,才能在命令行中直接使用编译命令。

1.1 GCC (GNU Compiler Collection)

功能: 编译器前端,负责预处理、编译、汇编等阶段。

常用参数:

bash 复制代码
# 基本编译选项
-E                  # 仅预处理,输出.i文件
-S                  # 编译到汇编,输出.s文件
-c                  # 编译、汇编到目标文件,输出.o文件
-o <file>           # 指定输出文件名

# 预处理选项
-D<macro>[=value]   # 定义宏
-U<macro>           # 取消宏定义
-I<dir>             # 添加头文件搜索路径
-include <file>     # 强制包含头文件

# 优化选项
-O0                 # 不优化(默认)
-O1                 # 基本优化
-O2                 # 推荐的优化级别
-O3                 # 激进优化
-Os                 # 优化代码大小
-Og                 # 调试友好优化

# 警告选项
-Wall               # 启用常见警告
-Wextra             # 额外警告
-Werror             # 警告视为错误
-Wpedantic          # 严格遵循标准
-Wshadow            # 变量遮蔽警告
-Wconversion        # 隐式类型转换警告

# 调试选项
-g                  # 生成调试信息
-ggdb               # 生成GDB专用调试信息
-g3                 # 包含宏定义的调试信息

# 语言标准
-std=c99            # C99标准
-std=c11            # C11标准
-std=c++11          # C++11标准
-std=c++17          # C++17标准

# 架构相关
-mcpu=cortex-m4     # 指定CPU类型
-mthumb             # 生成Thumb指令
-mfloat-abi=hard    # 硬件浮点ABI
-mfpu=fpv4-sp-d16   # 指定FPU类型

# 代码生成选项
-fPIC               # 生成位置无关代码
-ffreestanding      # 独立环境编译
-fno-builtin        # 禁用内置函数
-fno-common         # 禁止common段
-fdata-sections     # 每个数据项独立段
-ffunction-sections # 每个函数独立段

使用示例:

bash 复制代码
# 预处理
arm-none-eabi-gcc -E main.c -o main.i -I./include

# 编译到汇编
arm-none-eabi-gcc -S main.c -o main.s -O2

# 编译到目标文件
arm-none-eabi-gcc -c main.c -o main.o -mcpu=cortex-m4 -mthumb -O2 -g

# 一步编译链接
arm-none-eabi-gcc main.c -o firmware.elf -T linker.ld -mcpu=cortex-m4 -mthumb

1.2 LD (Linker)

功能: 链接器,将多个目标文件和库文件链接成可执行文件。

常用参数:

bash 复制代码
# 基本选项
-o <file>           # 指定输出文件名
-T <script>         # 指定链接脚本
-e <symbol>         # 指定入口点

# 库和路径选项
-L<dir>             # 添加库搜索路径
-l<library>         # 链接库文件
--start-group       # 库分组开始
--end-group         # 库分组结束

# 符号选项
--defsym=<sym>=<value>  # 定义符号
-Map=<file>         # 生成map文件

# 段选项
--gc-sections       # 垃圾回收未使用段
--print-gc-sections # 打印被回收的段

# 输出格式
--oformat=<format>  # 输出格式(binary, ihex, srec等)

# 其他选项
-nostdlib           # 不链接标准库
-static             # 静态链接
-rdynamic           # 导出符号给动态链接器
--whole-archive     # 包含整个归档文件
--no-whole-archive  # 结束whole-archive

使用示例:

bash 复制代码
# 基本链接
arm-none-eabi-ld main.o -o firmware.elf -T stm32.ld -Map=firmware.map

# 带库链接
arm-none-eabi-ld main.o -L./lib -lmylib -o firmware.elf -T linker.ld

# 使用gc-sections
arm-none-eabi-ld main.o -o firmware.elf -T linker.ld --gc-sections

1.3 OBJDUMP

功能: 显示目标文件信息,包括反汇编、段信息、符号表等。

常用参数:

bash 复制代码
# 反汇编选项
-d                  # 反汇编可执行段
-D                  # 反汇编所有段
-S                  # 混合源码和汇编
-C                  # C++符号名解码

# 信息显示
-h                  # 显示段头部信息
-x                  # 显示所有头部信息
-t                  # 显示符号表
-r                  # 显示重定位信息
-p                  # 显示程序头

# 输出控制
--start-address=<addr>  # 起始地址
--stop-address=<addr>   # 结束地址
-j <section>            # 指定段

# 架构指定
-m <machine>            # 指定架构

使用示例:

bash 复制代码
# 反汇编并显示源码
arm-none-eabi-objdump -d -S firmware.elf > firmware.lst

# 显示段信息
arm-none-eabi-objdump -h firmware.elf

# 显示符号表
arm-none-eabi-objdump -t firmware.elf

# 反汇编指定段
arm-none-eabi-objdump -d -j .text firmware.elf

# 显示重定位信息
arm-none-eabi-objdump -r main.o

1.4 NM

功能: 列出目标文件中的符号。

常用参数:

bash 复制代码
# 输出格式
-a                  # 显示所有符号
-g                  # 仅显示外部符号
-u                  # 仅显示未定义符号
-S                  # 显示符号大小

# 排序
-n                  # 按地址排序
-p                  # 不排序
-r                  # 逆序
--size-sort         # 按大小排序

# 输出控制
-C                  # C++符号解码
--defined-only      # 仅显示已定义符号
--undefined-only    # 仅显示未定义符号

符号类型说明:

复制代码
T/t - 代码段(.text)中的符号
D/d - 数据段(.data)中的符号
B/b - BSS段中的符号
R/r - 只读数据段(.rodata)中的符号
U   - 未定义符号(外部引用)
W   - 弱符号
C   - common符号
S/s - 小数据段符号

使用示例:

bash 复制代码
# 列出所有符号
arm-none-eabi-nm firmware.elf

# 按地址排序
arm-none-eabi-nm -n firmware.elf

# 显示符号大小
arm-none-eabi-nm -S firmware.elf

# 仅显示外部符号
arm-none-eabi-nm -g firmware.elf

# 查找特定符号
arm-none-eabi-nm firmware.elf | grep main

1.5 OBJCOPY

功能: 复制和转换目标文件格式。

常用参数:

bash 复制代码
# 格式转换
-O <format>         # 输出格式(elf32-littlearm, binary, ihex, srec)
-I <format>         # 输入格式
-B <arch>           # 二进制架构

# 段操作
-j <section>        # 仅复制指定段
-R <section>        # 删除指定段
--add-section <name>=<file>  # 添加段
--set-section-flags <section>=<flags>  # 设置段属性

# 符号操作
--strip-all         # 删除所有符号
--strip-debug       # 删除调试符号
--strip-unneeded    # 删除不需要的符号
--keep-symbol <sym> # 保留指定符号
--localize-hidden   # 隐藏符号本地化

使用示例:

bash 复制代码
# 生成bin文件
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

# 生成hex文件
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex

# 提取特定段
arm-none-eabi-objcopy -O binary -j .data firmware.elf data.bin

# 删除调试信息
arm-none-eabi-objcopy --strip-debug firmware.elf firmware_stripped.elf

# 删除指定段
arm-none-eabi-objcopy -R .comment -R .note firmware.elf firmware.elf

1.6 ADDR2LINE

功能: 将地址转换为文件名和行号。

常用参数:

bash 复制代码
-e <file>           # 指定可执行文件
-f                  # 显示函数名
-C                  # C++符号解码
-s                  # 隐藏文件路径
-p                   # 显示函数上下文
-i                  # 内联函数处理
-j <section>        # 指定段

使用示例:

bash 复制代码
# 地址转换
arm-none-eabi-addr2line -e firmware.elf 0x08001234

# 显示函数名
arm-none-eabi-addr2line -e firmware.elf -f 0x08001234

# 批量转换
arm-none-eabi-addr2line -e firmware.elf -f -C 0x08001234 0x08005678

# 内联函数处理
arm-none-eabi-addr2line -e firmware.elf -f -i 0x08001234

1.7 STRIP

功能: 从目标文件中删除符号信息。

常用参数:

bash 复制代码
--strip-all         # 删除所有符号
--strip-debug       # 删除调试符号
--strip-unneeded    # 删除不需要的符号
--keep-symbol <sym> # 保留指定符号
--remove-section <section>  # 删除段
-o <file>           # 输出文件

使用示例:

bash 复制代码
# 删除所有符号
arm-none-eabi-strip --strip-all firmware.elf -o firmware_stripped.elf

# 仅删除调试符号
arm-none-eabi-strip --strip-debug firmware.elf

# 保留特定符号
arm-none-eabi-strip --keep-symbol=main --strip-all firmware.elf

1.8 SIZE

功能: 显示目标文件段大小。

常用参数:

bash 复制代码
-A                  # System V格式
-B                  # Berkeley格式(默认)
--format=<format>   # 输出格式
--common            # 包含common符号
--totals            # 显示总计

使用示例:

bash 复制代码
# 显示段大小
arm-none-eabi-size firmware.elf

# System V格式
arm-none-eabi-size -A firmware.elf

# 显示详细信息
arm-none-eabi-size --format=sysv firmware.elf

输出示例:

复制代码
   text    data     bss     dec     hex filename
  12345    1234    5678   19257    4b39 firmware.elf

1.9 READLF

功能: 显示ELF文件信息。

常用参数:

bash 复制代码
-h                  # ELF头信息
-l                  # 程序头
-S                  # 段头
-s                  # 符号表
-r                  # 重定位信息
-d                  # 动态段
-V                  # 版本信息
-n                  # notes段
-W                  # 宽格式输出

使用示例:

bash 复制代码
# 查看ELF头
arm-none-eabi-readelf -h firmware.elf

# 查看程序头
arm-none-eabi-readelf -l firmware.elf

# 查看段头
arm-none-eabi-readelf -S firmware.elf

# 查看符号表
arm-none-eabi-readelf -s firmware.elf

2. 常用嵌入式编译选项

2.1 资源优化选项

代码大小优化
bash 复制代码
# 基本大小优化
-Os                 # 优化代码大小,启用-O2的基本优化但不增加代码大小

# 函数和数据段分离(配合链接器gc-sections)
-ffunction-sections # 每个函数独立段
-fdata-sections     # 每个数据项独立段

# 链接时优化
-flto               # 链接时优化,跨模块优化

# 内联控制
-fno-inline-functions                    # 禁止内联
-fno-inline-small-functions             # 禁止小函数内联
--param max-inline-insns-single=50      # 内联指令数限制

# 循环优化
-funroll-loops                          # 展开循环(可能增大代码)
-fno-unroll-loops                       # 不展开循环

# 示例
arm-none-eabi-gcc -Os -ffunction-sections -fdata-sections -flto \
    -mcpu=cortex-m4 -mthumb main.c -o firmware.elf
内存使用优化
bash 复制代码
# 栈使用优化
-fstack-usage                   # 生成栈使用报告
-Wframe-larger-than=<bytes>     # 警告大栈帧
-fno-stack-protector            # 禁用栈保护(减小开销)

# 寄存器使用
-fcall-saved-<reg>              # 指定寄存器跨调用保存
-fcall-used-<reg>               # 指定寄存器跨调用可用

# 数据优化
-fmerge-all-constants           # 合并常量
-fmerge-constants               # 合并相同常量
-fno-common                     # 避免common段

# 示例
arm-none-eabi-gcc -Os -fstack-usage -Wframe-larger-than=256 main.c
优化效果对比
c 复制代码
// 原始代码
int calculate(int a, int b) {
    int result = 0;
    for (int i = 0; i < 100; i++) {
        result += a * b + i;
    }
    return result;
}
bash 复制代码
# -O0: 60+ 条指令
# -O2: 20 条指令
# -Os: 15 条指令
# -O3: 可能展开循环,代码变大但速度更快

2.2 编程规范选项

编码标准检查
bash 复制代码
# MISRA C检查(需要专门工具,但GCC可辅助)
-Wpedantic                       # 严格遵循ISO C标准
-std=c99 -std=c11               # 指定C标准
-Wtraditional                   # 传统C警告

# 类型和符号检查
-Wsign-conversion               # 符号转换警告
-Wconversion                    # 隐式转换警告
-Wcast-qual                     # 类型转换丢失const
-Wcast-align                    # 类型转换对齐问题
-Wstrict-prototypes             # 函数原型检查
-Wold-style-definition          # 旧式函数定义警告

# 未使用代码检查
-Wunused                        # 未使用实体警告
-Wunused-function               # 未使用函数
-Wunused-variable               # 未使用变量
-Wunused-parameter              # 未使用参数
-Wunused-but-set-variable       # 仅设置未使用的变量
代码质量检查
bash 复制代码
# 常见错误检测
-Wuninitialized                 # 未初始化变量
-Wmaybe-uninitialized           # 可能未初始化
-Wnull-dereference              # 空指针解引用
-Wdangling-else                 # 悬挂else
-Wdouble-promotion              # float到double隐式转换
-Wshadow                        # 变量遮蔽
-Wformat=2                      # printf/scanf格式检查

完整示例:

bash 复制代码
arm-none-eabi-gcc -c main.c -o main.o \
    -std=c11 \
    -Wpedantic \
    -Wall -Wextra \
    -Wconversion -Wsign-conversion \
    -Wshadow -Wformat=2 \
    -Wstrict-prototypes \
    -Wunused -Wuninitialized \
    -Werror

2.3 防御编程选项

运行时检查
bash 复制代码
# 栈保护
-fstack-protector                # 启用栈保护
-fstack-protector-strong         # 强栈保护(推荐)
-fstack-protector-all            # 所有函数栈保护
-fstack-protector-explicit       # 显式指定

# 边界检查
-fstack-check                    # 栈边界检查
-fsanitize=address               # 地址消毒器
-fsanitize=undefined             # 未定义行为消毒器
-fsanitize=thread                # 线程消毒器

# 溢出检查
-ftrapv                          # 整数溢出陷阱
-fwrapv                          # 整数溢出环绕
错误预防
bash 复制代码
# 严格别名规则
-fstrict-aliasing               # 启用严格别名优化
-Wstrict-aliasing=3             # 严格别名警告级别

# 空指针检查
-fdelete-null-pointer-checks    # 删除空指针检查(优化)
-fno-delete-null-pointer-checks # 保留空指针检查(安全)

# 浮点异常
-ftrapping-math                 # 浮点异常陷阱
-fsignaling-nans                # 信令NaN处理

# 示例
arm-none-eabi-gcc -c main.c -o main.o \
    -fstack-protector-strong \
    -Wstrict-aliasing=2 \
    -Wall -Wextra -Werror \
    -fno-delete-null-pointer-checks
防御编程代码示例
c 复制代码
// 启用防御编译选项后的代码保护效果

#include <stdint.h>
#include <string.h>

// 栈保护示例
void vulnerable_function(char *input) {
    char buffer[64];  // -fstack-protector-strong 会添加canary
    strcpy(buffer, input);  // 编译器会警告不安全
}

// 更安全的版本
void safe_function(const char *input, size_t len) {
    char buffer[64];
    size_t copy_len = (len < sizeof(buffer) - 1) ? len : sizeof(buffer) - 1;
    memcpy(buffer, input, copy_len);
    buffer[copy_len] = '\0';
}

// 使用属性增强安全性
void critical_function(void) __attribute__((noinline));
void critical_function(void) {
    // 关键函数,禁止内联以确保栈帧完整
}

// 使用静态断言
_Static_assert(sizeof(int) == 4, "int must be 4 bytes");

2.4 架构适配选项

ARM架构选项
bash 复制代码
# 处理器指定
-mcpu=cortex-m0                 # Cortex-M0
-mcpu=cortex-m0plus             # Cortex-M0+
-mcpu=cortex-m3                 # Cortex-M3
-mcpu=cortex-m4                 # Cortex-M4
-mcpu=cortex-m7                 # Cortex-M7
-mcpu=cortex-m23                # Cortex-M23
-mcpu=cortex-m33                # Cortex-M33

# 指令集选择
-mthumb                         # Thumb指令集
-marm                           # ARM指令集(部分MCU不支持)

# 浮点选项
-mfloat-abi=soft                # 软件浮点
-mfloat-abi=softfp              # 软件浮点ABI,使用硬件FPU
-mfloat-abi=hard                # 硬件浮点ABI

# FPU类型
-mfpu=fpv4-sp-d16               # Cortex-M4 FPU
-mfpu=fpv5-sp-d16               # Cortex-M7 单精度FPU
-mfpu=fpv5-d16                  # Cortex-M7 双精度FPU
-mfpu=fp-armv8                  # ARMv8 FPU

# 字节序
-mlittle-endian                 # 小端(默认)
-mbig-endian                    # 大端
RISC-V架构选项
bash 复制代码
# 架构指定
-march=rv32imc                  # RV32 I/M/C扩展
-march=rv32imac                 # RV32 I/M/A/C扩展
-march=rv32imafc                # RV32 带FPU

# ABI
-mabi=ilp32                     # 32位软浮点ABI
-mabi=ilp32f                    # 32位单精度浮点ABI
-mabi=lp64                      # 64位软浮点ABI

# 代码模型
-mcmodel=medlow                 # 小代码模型
-mcmodel=medany                 # 中等代码模型
架构适配示例
bash 复制代码
# Cortex-M4带FPU的配置
arm-none-eabi-gcc -c main.c -o main.o \
    -mcpu=cortex-m4 \
    -mthumb \
    -mfloat-abi=hard \
    -mfpu=fpv4-sp-d16 \
    -O2 -g

# Cortex-M7高性能配置
arm-none-eabi-gcc -c main.c -o main.o \
    -mcpu=cortex-m7 \
    -mthumb \
    -mfloat-abi=hard \
    -mfpu=fpv5-d16 \
    -O3 -g

# RISC-V配置
riscv32-unknown-elf-gcc -c main.c -o main.o \
    -march=rv32imc \
    -mabi=ilp32 \
    -O2 -g

2.5 性能提升选项

速度优化
bash 复制代码
# 基本速度优化
-O2                             # 推荐的速度优化级别
-O3                             # 激进优化
-Ofast                          # 极致优化(可能违反标准)

# 循环优化
-funroll-loops                  # 循环展开
-funroll-all-loops              # 展开所有循环
-fpeel-loops                    # 循环剥离
-ftree-loop-vectorize           # 循环向量化
-ftree-slp-vectorize            # 超长指令字向量化

# 函数优化
-finline-functions              # 函数内联
-finline-functions-called-once  # 仅调用一次的函数内联
-finline-limit=<n>              # 内联大小限制
-fipa-sra                       # 过程间标量替换
-fpartial-inlining              # 部分内联

# 分支优化
-fbranch-probabilities          # 分支概率优化
-fprofile-use                   # 使用profiling数据
-freorder-blocks                # 重排基本块
-freorder-functions             # 重排函数

# 数据优化
-fprefetch-loop-arrays          # 预取循环数组
-ftree-loop-distribution        # 循环分布
-ftree-loop-distribute-patterns # 循环模式分布
性能优化示例
c 复制代码
// 原始代码
void matrix_mult(int n, float a[n][n], float b[n][n], float c[n][n]) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            c[i][j] = 0;
            for (int k = 0; k < n; k++) {
                c[i][j] += a[i][k] * b[k][j];
            }
        }
    }
}
bash 复制代码
# 性能优化编译
arm-none-eabi-gcc -c matrix.c -o matrix.o \
    -O3 \
    -mcpu=cortex-m7 \
    -mthumb \
    -mfloat-abi=hard \
    -mfpu=fpv5-d16 \
    -ftree-vectorize \
    -ffast-math
Profile Guided Optimization (PGO)
bash 复制代码
# 第一步:编译带profiling的版本
arm-none-eabi-gcc -fprofile-generate main.c -o firmware.elf

# 第二步:运行程序收集数据(需要目标板支持)
# 生成 *.gcda 文件

# 第三步:使用profiling数据重新编译
arm-none-eabi-gcc -fprofile-use main.c -o firmware_optimized.elf

3. 编译详细流程

3.1 编译流程概览

复制代码
┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   源文件    │     │  预处理文件  │     │   汇编文件   │     │   目标文件   │
│  main.c    │────▶│   main.i    │────▶│   main.s    │────▶│   main.o    │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       │                   │                   │                   │
       │    预处理         │      编译         │      汇编         │
       │    cpp/gcc -E     │    cc1/gcc -S     │     as/gcc -c     │
       ▼                   ▼                   ▼                   ▼
   头文件展开          语法分析          汇编代码生成         机器码生成
   宏替换             中间代码          目标文件格式
   条件编译            优化

3.2 预处理阶段 (Preprocessing)

主要工作:

  1. 头文件包含展开
  2. 宏定义展开和替换
  3. 条件编译处理
  4. 注释删除
  5. 行号和文件名标记

示例:

c 复制代码
// main.c
#include <stdio.h>
#include "config.h"

#define MAX_SIZE 100
#define SQUARE(x) ((x) * (x))

#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg)
#endif

int main(void) {
    int arr[MAX_SIZE];
    int result = SQUARE(5);
    LOG("Program started");
    return result;
}
bash 复制代码
# 执行预处理
arm-none-eabi-gcc -E main.c -o main.i -I./include -DDEBUG

# 查看预处理结果(部分)
c 复制代码
// main.i (预处理输出,简化版)
# 1 "main.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.c"
# 1 "stdio.h" 1 3 4
// ... stdio.h 内容 ...
# 1 "config.h" 1
// ... config.h 内容 ...

int main(void) {
    int arr[100];
    int result = ((5) * (5));
    printf("[DEBUG] %s\n", "Program started");
    return result;
}

3.3 编译阶段 (Compilation)

主要工作:

  1. 词法分析 - 将代码分解为token
  2. 语法分析 - 构建语法树(AST)
  3. 语义分析 - 类型检查、符号解析
  4. 中间代码生成 - 生成RTL/GIMPLE
  5. 优化 - 各种优化pass
  6. 汇编代码生成

示例:

bash 复制代码
# 生成汇编代码
arm-none-eabi-gcc -S main.i -o main.s -O2 -mcpu=cortex-m4 -mthumb
asm 复制代码
// main.s (汇编输出,简化版)
    .arch armv7e-m
    .eabi_attribute 28, 1
    .eabi_attribute 20, 1
    .eabi_attribute 21, 1
    .eabi_attribute 23, 3
    .eabi_attribute 24, 1
    .eabi_attribute 25, 1
    .eabi_attribute 26, 1
    .eabi_attribute 30, 2
    .eabi_attribute 34, 1
    .eabi_attribute 18, 4
    .file "main.c"
    .text
    .global main
    .type main, %function
main:
    @ args = 0, pretend = 0, frame = 8
    @ frame_needed = 0, uses_anonymous_args = 0
    push    {r7, lr}
    sub     sp, sp, #400
    add     r7, sp, #0
    movs    r3, #25
    str     r3, [r7, #4]
    ldr     r0, .L3
    bl      printf
    ldr     r3, [r7, #4]
    mov     r0, r3
    add     sp, r7, #0
    pop     {r7, pc}
.L3:
    .word   .LC0
    .size   main, .-main
    .section    .rodata
    .align  2
.LC0:
    .ascii  "[DEBUG] Program started\000"

3.4 汇编阶段 (Assembly)

主要工作:

  1. 解析汇编指令和伪操作
  2. 符号解析和符号表创建
  3. 指令编码为机器码
  4. 生成重定位信息
  5. 生成ELF目标文件

示例:

bash 复制代码
# 生成目标文件
arm-none-eabi-gcc -c main.s -o main.o
# 或使用汇编器
arm-none-eabi-as main.s -o main.o
bash 复制代码
# 查看目标文件内容
arm-none-eabi-objdump -h main.o
复制代码
main.o:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000028  00000000  00000000  00000034  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  00000000  00000000  0000005c  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  00000000  00000000  0000005c  2**0
                  ALLOC
  3 .rodata       00000018  00000000  00000000  0000005c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .comment      00000058  00000000  00000000  00000074  2**0
                  CONTENTS, READONLY

3.5 链接阶段 (Linking)

主要工作:

  1. 地址空间分配
  2. 符号解析
  3. 重定位
  4. 段合并
  5. 生成可执行文件

示例:

bash 复制代码
# 链接多个目标文件
arm-none-eabi-gcc main.o utils.o startup.o \
    -o firmware.elf \
    -T stm32f407.ld \
    -L./lib -lc -lm \
    -mcpu=cortex-m4 -mthumb \
    -Wl,-Map=firmware.map

3.6 完整编译示例

项目结构:

复制代码
project/
├── src/
│   ├── main.c
│   ├── uart.c
│   ├── timer.c
│   └── startup.s
├── include/
│   ├── uart.h
│   └── timer.h
├── lib/
│   └── libdriver.a
├── ld/
│   └── stm32f407.ld
└── Makefile

Makefile示例:

makefile 复制代码
# 工具链
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy
OBJDUMP = arm-none-eabi-objdump
SIZE = arm-none-eabi-size

# 编译选项
CPU = -mcpu=cortex-m4 -mthumb
CFLAGS = $(CPU) -Wall -Wextra -Werror -std=c11 -O2 -g
CFLAGS += -ffunction-sections -fdata-sections
CFLAGS += -I./include

# 链接选项
LDFLAGS = $(CPU) -T./ld/stm32f407.ld
LDFLAGS += -Wl,--gc-sections -Wl,-Map=build/firmware.map
LDFLAGS += -L./lib -ldriver -lc -lm -lnosys

# 源文件
C_SRCS = src/main.c src/uart.c src/timer.c
ASM_SRCS = src/startup.s

# 目标文件
OBJS = $(C_SRCS:.c=.o) $(ASM_SRCS:.s=.o)

# 默认目标
all: build/firmware.elf build/firmware.bin build/firmware.hex build/firmware.lst

# 编译C文件
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 编译汇编文件
%.o: %.s
	$(AS) $(CPU) $< -o $@

# 链接
build/firmware.elf: $(OBJS)
	$(LD) $(LDFLAGS) $(OBJS) -o $@
	$(SIZE) $@

# 生成bin
build/firmware.bin: build/firmware.elf
	$(OBJCOPY) -O binary $< $@

# 生成hex
build/firmware.hex: build/firmware.elf
	$(OBJCOPY) -O ihex $< $@

# 生成反汇编
build/firmware.lst: build/firmware.elf
	$(OBJDUMP) -d -S $< > $@

clean:
	rm -f $(OBJS) build/*

.PHONY: all clean

编译流程输出:

bash 复制代码
$ make all
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Wall -Wextra -Werror -std=c11 -O2 -g -ffunction-sections -fdata-sections -I./include -c src/main.c -o src/main.o
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Wall -Wextra -Werror -std=c11 -O2 -g -ffunction-sections -fdata-sections -I./include -c src/uart.c -o src/uart.o
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -Wall -Wextra -Werror -std=c11 -O2 -g -ffunction-sections -fdata-sections -I./include -c src/timer.c -o src/timer.o
arm-none-eabi-as -mcpu=cortex-m4 -mthumb src/startup.s -o src/startup.o
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -T./ld/stm32f407.ld -Wl,--gc-sections -Wl,-Map=build/firmware.map -L./lib -ldriver -lc -lm -lnosys src/main.o src/uart.o src/timer.o src/startup.o -o build/firmware.elf
arm-none-eabi-size build/firmware.elf
   text    data     bss     dec     hex filename
  15234    1234    4568   21036    522c build/firmware.elf
arm-none-eabi-objcopy -O binary build/firmware.elf build/firmware.bin
arm-none-eabi-objcopy -O ihex build/firmware.elf build/firmware.hex
arm-none-eabi-objdump -d -S build/firmware.elf > build/firmware.lst

4. 链接流程与链接脚本

4.1 链接流程详解

复制代码
┌────────────────────────────────────────────────────────────────────┐
│                           链接流程                                  │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  输入文件                                                          │
│  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐              │
│  │  main.o  │ │ utils.o  │ │startup.o │ │ libmy.a  │              │
│  └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘              │
│       │            │            │            │                    │
│       └────────────┴─────┬──────┴────────────┘                    │
│                          ▼                                        │
│                  ┌───────────────┐                                │
│                  │   符号解析     │                                │
│                  │ 符号表合并     │                                │
│                  │ 未定义符号查找 │                                │
│                  └───────┬───────┘                                │
│                          ▼                                        │
│                  ┌───────────────┐                                │
│                  │   段合并       │                                │
│                  │ .text 合并     │                                │
│                  │ .data 合并     │                                │
│                  │ .bss 合并      │                                │
│                  └───────┬───────┘                                │
│                          ▼                                        │
│                  ┌───────────────┐                                │
│                  │   地址分配     │                                │
│                  │ 遵循链接脚本   │                                │
│                  └───────┬───────┘                                │
│                          ▼                                        │
│                  ┌───────────────┐                                │
│                  │    重定位      │                                │
│                  │ 修正地址引用   │                                │
│                  └───────┬───────┘                                │
│                          ▼                                        │
│                  ┌───────────────┐                                │
│                  │  生成ELF文件   │                                │
│                  └───────────────┘                                │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

4.2 常用链接选项详解

bash 复制代码
# 基本选项
-T <script>                     # 指定链接脚本
-o <file>                       # 输出文件名
-e <symbol>                     # 入口点符号

# 库路径和库文件
-L<dir>                         # 添加库搜索路径
-l<name>                        # 链接库libname.a或libname.so
--start-group <libs> --end-group # 库分组,解决循环依赖

# 段处理
--gc-sections                   # 回收未使用段
--print-gc-sections             # 打印回收的段
--icf=safe                      # 相同代码折叠
--keep=<section>                # 保留指定段

# 符号处理
--defsym=<symbol>=<value>       # 定义符号
--undefined=<symbol>            # 强制符号未定义
--require-defined=<symbol>      # 要求符号定义

# 输出控制
-Map=<file>                     # 生成map文件
--cref                          # 交叉引用表
--orphan-handling=<mode>        # 孤儿段处理

# 内存和地址
-Ttext=<addr>                   # 代码段起始地址
-Tdata=<addr>                   # 数据段起始地址
-Tbss=<addr>                    # BSS段起始地址

# 输出格式
--oformat=binary                # 原始二进制输出
--oformat=ihex                  # Intel HEX格式
--oformat=srec                  # Motorola S-record

# 嵌入式常用
-nostartfiles                   # 不使用标准启动文件
-nostdlib                       # 不使用标准库
-static                         # 静态链接
-Wl,--wrap=<symbol>             # 包装函数

# 调试和诊断
--verbose                       # 详细输出
--trace                         # 跟踪输入文件
--warn-common                   # common段警告
--warn-section-align            # 段对齐警告

4.3 链接脚本语法详解

基本结构
ld 复制代码
/* 链接脚本基本结构 */

/* 入口点 */
ENTRY(Reset_Handler)

/* 内存区域定义 */
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 128K
    CCRAM (rw)  : ORIGIN = 0x10000000, LENGTH = 64K
}

/* 段定义 */
SECTIONS
{
    /* 代码段 */
    .text :
    {
        *(.vectors)           /* 中断向量表 */
        *(.text)              /* 代码 */
        *(.text*)             /* 代码(通配符) */
        *(.rodata)            /* 只读数据 */
        *(.rodata*)           /* 只读数据(通配符) */
        . = ALIGN(4);
    } > FLASH

    /* 数据段 */
    .data :
    {
        _sdata = .;           /* 数据段起始地址 */
        *(.data)
        *(.data*)
        . = ALIGN(4);
        _edata = .;           /* 数据段结束地址 */
    } > RAM AT > FLASH        /* 运行在RAM,存储在FLASH */

    /* BSS段 */
    .bss :
    {
        _sbss = .;
        *(.bss)
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;
    } > RAM

    /* 堆栈 */
    .stack (NOLOAD) :
    {
        . = ALIGN(8);
        _sstack = .;
        . = . + 0x2000;       /* 8KB栈 */
        _estack = .;
    } > RAM
}
内存区域命令
ld 复制代码
MEMORY
{
    /* name (attributes) : ORIGIN = origin, LENGTH = length */
    
    /* 属性:
       R - 可读
       W - 可写  
       X - 可执行
       A - 可分配
       I - 可初始化
       L - 同I
       ! - 反选
    */
    
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 128K
    
    /* 使用表达式 */
    EXTMEM (rw) : ORIGIN = 0x60000000, LENGTH = 1M
    
    /* 别名 */
    REGION_ALIAS("CODE_REGION", FLASH)
    REGION_ALIAS("DATA_REGION", RAM)
}
段定义详解
ld 复制代码
SECTIONS
{
    /* 输出段语法:
       output_section_name : 
       {
           input_section_description
       } > memory_region AT > load_memory_region
    */

    /* 1. 基本段定义 */
    .text :
    {
        /* 输入段描述 */
        
        /* 匹配所有目标文件的.text段 */
        *(.text)
        
        /* 匹配所有目标文件的.text*段 */
        *(.text*)
        
        /* 匹配特定目标文件 */
        main.o(.text)
        
        /* 匹配特定库 */
        libmylib.a:*(.text)
        
        /* 排除特定文件 */
        EXCLUDE_FILE(*debug.o) *(.text)
        
    } > FLASH

    /* 2. 带LMA的段 */
    .data :
    {
        _data_start = .;
        *(.data)
        _data_end = .;
    } > RAM AT > FLASH    /* VMA在RAM, LMA在FLASH */
    
    /* 显式指定LMA */
    .fastcode :
    {
        *(.fastcode)
    } > RAM AT (0x08010000)

    /* 3. 带对齐的段 */
    .aligned_section :
    {
        . = ALIGN(4096);    /* 4KB对齐 */
        _page_start = .;
        *(.page_data)
    } > RAM

    /* 4. 填充 */
    .fill_section :
    {
        *(.fill_data)
        . = ALIGN(4);
        LONG(0xFFFFFFFF)     /* 填充特定值 */
        FILL(0x00)          /* 后续填充0x00 */
    } > FLASH

    /* 5. NOLOAD段 */
    .noinit (NOLOAD) :
    {
        *(.noinit)
    } > RAM

    /* 6. 特殊段 */
    .init_array :
    {
        _init_array_start = .;
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        _init_array_end = .;
    } > FLASH
    
    .fini_array :
    {
        _fini_array_start = .;
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        _fini_array_end = .;
    } > FLASH

    /* 7. 丢弃段 */
    /DISCARD/ :
    {
        *(.comment)
        *(.note*)
        *(.ARM.attributes*)
    }
}
符号和表达式
ld 复制代码
/* 符号定义 */
_symbol = 0x1000;
_array_size = (_array_end - _array_start) / 4;

SECTIONS
{
    .text :
    {
        /* 位置计数器 */
        _text_start = .;        /* 当前位置 */
        
        *(.text)
        
        /* 表达式 */
        _text_size = SIZEOF(.text);
        _text_end = ADDR(.text) + SIZEOF(.text);
        
        /* 对齐 */
        . = ALIGN(4);
        
        /* 条件判断 */
        _has_debug = DEFINED(_DEBUG) ? 1 : 0;
        
    } > FLASH

    /* 使用LOADADDR, ADDR, SIZEOF等内置函数 */
    .data :
    {
        _data_load = LOADADDR(.data);   /* LMA */
        _data_start = ADDR(.data);       /* VMA */
        _data_size = SIZEOF(.data);      /* 大小 */
    } > RAM AT > FLASH
}
内置函数
ld 复制代码
/* 链接脚本内置函数 */

SECTIONS
{
    /* ABSOLUTE(exp) - 返回绝对值 */
    _abs_val = ABSOLUTE(0x1000);

    /* ADDR(section) - 返回段的VMA */
    _text_addr = ADDR(.text);

    /* ALIGN(exp) - 对齐 */
    . = ALIGN(4096);

    /* DEFINED(symbol) - 判断符号是否定义 */
    _use_ext = DEFINED(_EXTERNAL) ? 1 : 0;

    /* LOADADDR(section) - 返回段的LMA */
    _data_load = LOADADDR(.data);

    /* LOG2CEIL(exp) - log2向上取整 */
    _shift = LOG2CEIL(SIZEOF(.buffer));

    /* MAX / MIN */
    _max_size = MAX(SIZEOF(.text), SIZEOF(.data));

    /* ORIGIN(memory) - 返回内存区域起始地址 */
    _flash_start = ORIGIN(FLASH);

    /* LENGTH(memory) - 返回内存区域长度 */
    _ram_size = LENGTH(RAM);

    /* SIZEOF(section) - 返回段大小 */
    _text_size = SIZEOF(.text);

    /* SIZEOF_HEADERS - 输出文件头大小 */
    _headers_size = SIZEOF_HEADERS;
}

4.4 实际链接脚本示例

ld 复制代码
/* STM32F407 完整链接脚本 */

/* 入口点 */
ENTRY(Reset_Handler)

/* 栈和堆大小 */
_stack_size = 0x4000;    /* 16KB栈 */
_heap_size = 0x2000;     /* 8KB堆 */

/* 内存布局 */
MEMORY
{
    RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 128K
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    CCRAM (rw)  : ORIGIN = 0x10000000, LENGTH = 64K
}

/* 段定义 */
SECTIONS
{
    /* 中断向量表 - 必须在Flash起始位置 */
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > FLASH

    /* 代码段 */
    .text :
    {
        . = ALIGN(4);
        _stext = .;
        
        *(.text)
        *(.text*)
        *(.glue_7)          /* ARM/Thumb转换代码 */
        *(.glue_7t)
        
        /* 只读数据 */
        *(.rodata)
        *(.rodata*)
        
        /* C++构造/析构函数表 */
        . = ALIGN(4);
        __preinit_array_start = .;
        KEEP(*(.preinit_array))
        __preinit_array_end = .;
        
        __init_array_start = .;
        KEEP(*(SORT(.init_array.*)))
        KEEP(*(.init_array))
        __init_array_end = .;
        
        __fini_array_start = .;
        KEEP(*(SORT(.fini_array.*)))
        KEEP(*(.fini_array))
        __fini_array_end = .;
        
        /* 异常处理 */
        __eh_frame_start = .;
        KEEP(*(.eh_frame))
        __eh_frame_end = .;
        
        . = ALIGN(4);
        _etext = .;
        
    } > FLASH

    /* ARM异常处理段 */
    .ARM.extab :
    {
        *(.ARM.extab* .gnu.linkonce.armextab.*)
    } > FLASH

    .ARM :
    {
        __exidx_start = .;
        *(.ARM.exidx*)
        __exidx_end = .;
    } > FLASH

    /* 用于辅助调试的信息 */
    .ARM.attributes :
    {
        *(.ARM.attributes)
    } > FLASH

    /* 需要初始化的数据 - 存储在Flash,运行时复制到RAM */
    _sidata = LOADADDR(.data);

    .data :
    {
        . = ALIGN(4);
        _sdata = .;
        
        *(.data)
        *(.data*)
        *(.RamFunc)         /* 在RAM中运行的函数 */
        
        . = ALIGN(4);
        _edata = .;
        
    } > RAM AT > FLASH

    /* 未初始化数据段 */
    .bss :
    {
        . = ALIGN(4);
        _sbss = .;
        __bss_start__ = _sbss;
        
        *(.bss)
        *(.bss*)
        *(COMMON)
        
        . = ALIGN(4);
        _ebss = .;
        __bss_end__ = _ebss;
        
    } > RAM

    /* 不需要初始化的数据 */
    .noinit (NOLOAD) :
    {
        . = ALIGN(4);
        *(.noinit)
        . = ALIGN(4);
    } > RAM

    /* 用户堆 */
    ._user_heap_stack :
    {
        . = ALIGN(8);
        PROVIDE(end = .);
        PROVIDE(_end = .);
        . = . + _heap_size;
        . = . + _stack_size;
        . = ALIGN(8);
    } > RAM

    /* 栈顶地址 */
    _estack = ORIGIN(RAM) + LENGTH(RAM);

    /* 内存使用统计 */
    _Min_Heap_Size = _heap_size;
    _Min_Stack_Size = _stack_size;
    
    /* 内存区域使用情况 */
    _flash_used = _etext - ORIGIN(FLASH) + (_edata - _sdata);
    _ram_used = _ebss - _sdata + _stack_size + _heap_size;

    /* 丢弃不需要的段 */
    /DISCARD/ :
    {
        libc.a(*)
        libm.a(*)
        libgcc.a(*)
        *(.note.GNU-stack)
        *(.comment)
    }
}

4.5 链接脚本使用技巧

1. 内存区域别名
ld 复制代码
/* 使用REGION_ALIAS简化多配置链接脚本 */
REGION_ALIAS("TEXT_REGION", FLASH);
REGION_ALIAS("DATA_REGION", RAM);

SECTIONS
{
    .text : { *(.text) } > TEXT_REGION
    .data : { *(.data) } > DATA_REGION
}
2. 条件包含
ld 复制代码
/* 根据符号定义条件包含段 */
SECTIONS
{
    .text :
    {
        *(.text)
        
        /* 仅在定义DEBUG符号时包含调试段 */
        _debug_section_start = DEFINED(DEBUG) ? . : 0;
        *(.debug_info)
        _debug_section_end = DEFINED(DEBUG) ? . : 0;
        
    } > FLASH
}
3. 段排序
ld 复制代码
SECTIONS
{
    .text :
    {
        /* 按名称排序 */
        *(SORT(.text.*))
        
        /* 按名称逆序 */
        *(SORT_BY_NAME(.init_array.*))
        
        /* 按对齐值排序 */
        *(SORT_BY_ALIGNMENT(.data.*))
        
        /* 按初始化优先级排序 */
        KEEP(*(SORT(.init_array.*)))
        
    } > FLASH
}
4. 垃圾回收保护
ld 复制代码
SECTIONS
{
    .text :
    {
        /* KEEP防止段被gc-sections删除 */
        KEEP(*(.isr_vector))
        KEEP(*(.init))
        KEEP(*(.fini))
        
        *(.text)
    } > FLASH
}
5. 多内存区域分配
ld 复制代码
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (rwx)   : ORIGIN = 0x20000000, LENGTH = 128K
    DTCM (rw)   : ORIGIN = 0x20000000, LENGTH = 64K
    ITCM (rx)   : ORIGIN = 0x00000000, LENGTH = 16K
}

SECTIONS
{
    /* 关键代码放在ITCM */
    .fastcode :
    {
        *(.fastcode)
        *(.critical*)
    } > ITCM AT > FLASH
    
    /* 快速数据放在DTCM */
    .fastdata :
    {
        *(.fastdata)
    } > DTCM AT > FLASH
}

5. 变量与函数的存储位置和生命周期

5.1 存储区域分类

在嵌入式MCU中,主要的存储区域包括:

存储区域 类型 特点 用途
Flash/XIP 非易失 掉电保持,读取速度快,写入慢 代码、常量、初始化数据
RAM 易失 读写速度快,掉电丢失 变量、栈、堆

5.2 变量存储详解

全局变量
c 复制代码
/* 全局变量 - 文件作用域,外部链接 */

/* 初始化的全局变量 */
int g_initialized_var = 100;           // .data段 (RAM),初始值在Flash
int g_initialized_arr[10] = {1,2,3};   // .data段 (RAM)

/* 未初始化的全局变量 */
int g_uninitialized_var;               // .bss段 (RAM),初始化为0
int g_uninitialized_arr[100];          // .bss段 (RAM)

/* 生命周期:程序启动到结束 */
/* 存储位置:RAM (.data 或 .bss) */
/* 初始化时机:程序启动时 */
静态全局变量
c 复制代码
/* 静态全局变量 - 文件作用域,内部链接 */

static int s_initialized_var = 200;    // .data段 (RAM)
static int s_uninitialized_var;        // .bss段 (RAM)

/* 生命周期:程序启动到结束 */
/* 存储位置:RAM (.data 或 .bss) */
/* 作用域:仅本文件可见 */
局部变量
c 复制代码
void function(void)
{
    /* 自动局部变量 - 栈上分配 */
    int local_var = 10;                // 栈 (RAM)
    int local_arr[50];                 // 栈 (RAM)
    
    /* 生命周期:函数调用期间 */
    /* 存储位置:栈 (RAM) */
    /* 初始化时机:每次函数调用 */
    
    /* 寄存器变量 - 建议使用寄存器 */
    register int reg_var = 20;         // 寄存器或栈 (RAM)
}
静态局部变量
c 复制代码
void function(void)
{
    /* 静态局部变量 */
    static int s_local_initialized = 30;   // .data段 (RAM)
    static int s_local_uninitialized;      // .bss段 (RAM)
    
    /* 生命周期:程序启动到结束 */
    /* 存储位置:RAM (.data 或 .bss) */
    /* 作用域:仅本函数可见 */
    /* 初始化时机:首次执行时(实际是启动时) */
}
const修饰的变量
c 复制代码
/* const全局变量 */

const int g_const_var = 100;           // .rodata段 (Flash)
const char g_const_str[] = "Hello";    // .rodata段 (Flash)

/* const局部变量 */
void function(void)
{
    const int local_const = 50;        // 栈或寄存器 (RAM)
                                    // 或被优化为立即数
}

/* 注意:const变量仍可能被修改(通过指针),但应该避免 */
volatile修饰的变量
c 复制代码
/* volatile变量 - 每次都从内存读取 */

volatile int g_volatile_var;           // RAM,禁止优化
volatile uint32_t *REG = (uint32_t *)0x40000000;  // 外设寄存器

/* 常见的volatile使用场景:
   1. 硬件寄存器
   2. 中断服务程序中修改的变量
   3. 多线程共享的变量
*/

5.3 函数存储详解

普通函数
c 复制代码
/* 普通函数 - 默认外部链接 */

void normal_function(void)
{
    // 函数体
}

/* 存储位置:.text段 (Flash) */
/* 生命周期:程序运行期间 */
/* 可见性:整个程序可见 */
静态函数
c 复制代码
/* 静态函数 - 内部链接 */

static void static_function(void)
{
    // 函数体
}

/* 存储位置:.text段 (Flash) */
/* 生命周期:程序运行期间 */
/* 可见性:仅本文件可见 */
内联函数
c 复制代码
/* 内联函数 */

static inline void inline_function(void)
{
    // 函数体
}

/* 存储位置:可能内联到调用处,不生成独立函数 */
/* 如果没有内联,仍存储在 .text 段 */

/* 强制内联 */
__attribute__((always_inline)) 
static inline void force_inline_function(void)
{
    // 函数体
}
在RAM中运行的函数
c 复制代码
/* 需要在RAM中运行的函数 */
/* 场景:Flash编程、低功耗模式等 */

__attribute__((section(".ramcode")))
void flash_write_function(void)
{
    // Flash编程时不能从Flash执行代码
}

/* 需要链接脚本支持:
   .ramcode :
   {
       *(.ramcode)
   } > RAM AT > FLASH
*/

5.4 存储位置总结表

变量/函数类型 存储段 存储介质 生命周期 初始化时机
全局变量(初始化) .data RAM 程序运行期 启动时复制
全局变量(未初始化) .bss RAM 程序运行期 启动时清零
static全局变量 .data/.bss RAM 程序运行期 启动时
局部变量 RAM 函数调用期 每次调用
static局部变量 .data/.bss RAM 程序运行期 启动时
const全局变量 .rodata Flash 程序运行期 编译时
const局部变量 栈/立即数 RAM 函数调用期 每次调用
普通函数 .text Flash 程序运行期 -
static函数 .text Flash 程序运行期 -
RAM函数 .ramcode RAM 程序运行期 启动时复制

5.5 内存布局示例

复制代码
┌─────────────────────────────────────┐ 0x20020000
│             栈区 (Stack)            │ ← _estack (栈顶)
│              ↓ 向下增长             │
├─────────────────────────────────────┤
│                                     │
│             堆区 (Heap)             │
│              ↑ 向上增长             │
│                                     │
├─────────────────────────────────────┤ ← _ebss
│              .bss段                 │
│     (未初始化的全局/静态变量)        │
├─────────────────────────────────────┤ ← _sbss
│              .data段                │
│     (已初始化的全局/静态变量)        │
└─────────────────────────────────────┘ ← _sdata, 0x20000000

┌─────────────────────────────────────┐ 0x08080000
│                                     │
│            (未使用区域)              │
│                                     │
├─────────────────────────────────────┤ ← _etext
│             .rodata段               │
│          (const常量数据)             │
├─────────────────────────────────────┤
│             .text段                 │
│             (代码段)                 │
├─────────────────────────────────────┤
│           .isr_vector               │
│          (中断向量表)                │
└─────────────────────────────────────┘ ← 0x08000000

5.6 代码示例与验证

c 复制代码
/* storage_demo.c - 存储位置演示 */

#include <stdint.h>

/* Flash存储 (.rodata) */
const char version_string[] = "Firmware v1.0.0";
const uint32_t crc_table[256] = { /* ... */ };

/* RAM存储 (.data) - 初始化 */
int system_status = 1;
static int error_count = 0;

/* RAM存储 (.bss) - 未初始化 */
uint8_t rx_buffer[1024];
static uint32_t tick_counter;

/* 栈存储 */
void process_data(int param)  // param在栈上
{
    int local_temp;           // 栈上
    static int call_count;    // .bss
    
    call_count++;
    local_temp = param * 2;
}

/* Flash存储 (.text) */
void normal_function(void)
{
    // 函数代码存储在Flash
}

/* RAM存储 (.ramcode) - 用于Flash编程 */
__attribute__((section(".ramcode")))
int flash_program(uint32_t addr, const void *data, uint32_t len)
{
    volatile uint32_t *flash_reg = (uint32_t *)0x40022000;
    // ... Flash编程操作
    return 0;
}

/* 验证存储位置的函数 */
void print_memory_locations(void)
{
    extern uint32_t _stext, _etext;
    extern uint32_t _sdata, _edata;
    extern uint32_t _sbss, _ebss;
    
    printf("Code: 0x%08X - 0x%08X\n", &_stext, &_etext);
    printf("Data: 0x%08X - 0x%08X\n", &_sdata, &_edata);
    printf("BSS:  0x%08X - 0x%08X\n", &_sbss, &_ebss);
}

6. ELF文件后处理与产物生成

6.1 ELF文件格式简介

复制代码
┌────────────────────────────────────┐
│           ELF Header               │  文件类型、架构、入口点
├────────────────────────────────────┤
│        Program Headers             │  段信息(用于加载)
├────────────────────────────────────┤
│                                    │
│           .text                    │  代码段
│           .rodata                  │  只读数据
│           .data                    │  已初始化数据
│           .bss                     │  未初始化数据
│           ...                      │
│                                    │
├────────────────────────────────────┤
│        Section Headers             │  段详细信息
├────────────────────────────────────┤
│        Symbol Table                │  符号表
├────────────────────────────────────┤
│        String Table                │  字符串表
├────────────────────────────────────┤
│        Debug Info                  │  调试信息
└────────────────────────────────────┘

6.2 生成BIN文件

bash 复制代码
# 基本转换
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin

# 仅提取代码段
arm-none-eabi-objcopy -O binary -j .text firmware.elf code.bin

# 多段合并
arm-none-eabi-objcopy -O binary -j .text -j .rodata firmware.elf firmware.bin

# 带段间隙填充
arm-none-eabi-objcopy -O binary --gap-fill=0xFF firmware.elf firmware.bin

# 指定输出字节序
arm-none-eabi-objcopy -O binary --reverse-bytes=4 firmware.elf firmware.bin

生成过程:

复制代码
ELF文件                    BIN文件
┌─────────────┐           ┌─────────────┐
│ ELF Header  │           │             │
├─────────────┤           │             │
│ .text       │ ──────▶   │ .text内容   │
├─────────────┤           │ .rodata内容 │
│ .rodata     │ ──────▶   │ .data内容   │
├─────────────┤           │             │
│ .data       │ ──────▶   │  (无段信息)  │
├─────────────┤           │  (无符号表)  │
│ .bss        │           │             │
├─────────────┤           └─────────────┘
│ 符号表等    │
└─────────────┘           (只保留实际的存储内容)

6.3 生成HEX文件

bash 复制代码
# Intel HEX格式
arm-none-eabi-objcopy -O ihex firmware.elf firmware.hex

# 带特定参数
arm-none-eabi-objcopy -O ihex \
    --set-start=0x08000000 \
    --change-addresses=0x08000000 \
    firmware.elf firmware.hex

# Motorola S-record格式
arm-none-eabi-objcopy -O srec firmware.elf firmware.s19

Intel HEX格式说明:

复制代码
:LLAAAATT[DD...]CC

LL - 数据字节数
AAAA - 16位地址
TT - 记录类型
     00 - 数据记录
     01 - 文件结束
     02 - 扩展地址
     03 - 起始段地址
     04 - 扩展线性地址
     05 - 起始线性地址
DD - 数据字节
CC - 校验和

示例:
:020000040800F2        ; 扩展地址 0x0800
:1000000020010020DD050008DD050008DD050008BF
:00000001FF            ; 文件结束

6.4 生成反汇编文件(LST)

bash 复制代码
# 基本反汇编
arm-none-eabi-objdump -d firmware.elf > firmware.lst

# 带源码混合
arm-none-eabi-objdump -d -S firmware.elf > firmware.lst

# 带C++符号解码
arm-none-eabi-objdump -d -S -C firmware.elf > firmware.lst

# 显示所有段
arm-none-eabi-objdump -D -S firmware.elf > firmware.lst

# 显示段头信息
arm-none-eabi-objdump -h -d firmware.elf > firmware.lst

# 仅反汇编特定函数
arm-none-eabi-objdump -d --disassemble=main firmware.elf

反汇编文件示例:

asm 复制代码
firmware.elf:     file format elf32-littlearm

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00001a34  08000100  08000100  00000100  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       00000124  08001b38  08001b38  00001b38  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .data         00000020  20000000  08001c60  00001c60  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .bss          00000400  20000020  20000020  00001c80  2**2
                  ALLOC

Disassembly of section .text:

08000100 <main>:
int main(void)
{
 8000100:	b508      	push	{r3, lr}
    uart_init();
 8000102:	f7ff ffe5 	bl	80000d0 <uart_init>
    while(1) {
        uart_send_string("Hello\r\n");
 8000106:	4805      	ldr	r0, [pc, #20]	; (800011c <main+0x1c>)
 8000108:	f7ff fffa 	bl	8000100 <uart_send_string>
        for(int i = 0; i < 1000000; i++);
 800010c:	2400      	movs	r4, #0
 800010e:	4c04      	ldr	r4, [pc, #16]	; (8000120 <main+0x20>)
 8000110:	3c01      	subs	r4, #1
 8000112:	d1fd      	bne.n	8000110 <main+0x10>

6.5 生成MAP文件

bash 复制代码
# 编译时生成map文件
arm-none-eabi-gcc ... -Wl,-Map=firmware.map

# 链接时生成
arm-none-eabi-ld ... -Map=firmware.map

# 使用链接器选项
arm-none-eabi-gcc ... -Wl,--print-map -Wl,--cref

MAP文件内容示例:

复制代码
Archive member included because of file (symbol)

./lib/libc.a(lib_a-memcpy.o)
                              main.o (memcpy)

Allocating common symbols
Common symbol       size              file

g_buffer            0x400             main.o

Memory Configuration

Name             Origin             Length             Attributes
FLASH            0x08000000         0x00080000         xr
RAM              0x20000000         0x00020000         xrw

Linker script and memory map

LOAD main.o
LOAD utils.o
LOAD ./lib/libc.a

.isr_vector    0x08000000      0x1f8
 *(.isr_vector)
 .isr_vector   0x08000000      0x1f8 startup.o

.text           0x08000200    0x12a34
                0x08000200                _stext = .
 *(.text)
 .text         0x08000200       0x64 main.o
                0x08000200                main
                0x08000264                uart_init
 .text         0x08000264       0x3c utils.o
                0x08000264                delay_ms

.data           0x20000000       0x120 load address 0x08012c34
                0x20000000                _sdata = .
 *(.data)
 .data         0x20000000       0x120 main.o
                0x20000000                g_initialized_var

.bss            0x20000120       0x400
                0x20000120                _sbss = .
 *(.bss)
 .bss          0x20000120       0x400 main.o
                0x20000120                g_buffer

6.6 生成其他格式文件

S-Record文件
bash 复制代码
arm-none-eabi-objcopy -O srec firmware.elf firmware.s19
复制代码
S00F00006669726D776172652E73726563F1
S3150800010020010020DD050008DD050008DD050008BF
S31508000110DD050008DD050008DD050008DD050008B0
S70508000000FA
符号表文件
bash 复制代码
# 生成符号地址表
arm-none-eabi-nm -n firmware.elf > symbols.txt

# 生成带大小的符号表
arm-none-eabi-nm -S firmware.elf > symbols.txt

# 生成用于调试的符号文件
arm-none-eabi-objcopy --only-keep-debug firmware.elf firmware.sym

6.7 批量生成脚本示例

bash 复制代码
#!/bin/bash
# build_outputs.sh - 批量生成输出文件

ELF_FILE="build/firmware.elf"
OUTPUT_DIR="build/outputs"

# 创建输出目录
mkdir -p $OUTPUT_DIR

# 设置工具链
OBJCOPY=arm-none-eabi-objcopy
OBJDUMP=arm-none-eabi-objdump
NM=arm-none-eabi-nm
SIZE=arm-none-eabi-size
READELF=arm-none-eabi-readelf

# 1. BIN文件
echo "Generating BIN file..."
$OBJCOPY -O binary $ELF_FILE $OUTPUT_DIR/firmware.bin

# 2. HEX文件
echo "Generating HEX file..."
$OBJCOPY -O ihex $ELF_FILE $OUTPUT_DIR/firmware.hex

# 3. S19文件
echo "Generating S19 file..."
$OBJCOPY -O srec $ELF_FILE $OUTPUT_DIR/firmware.s19

# 4. 反汇编文件
echo "Generating LST file..."
$OBJDUMP -d -S -C $ELF_FILE > $OUTPUT_DIR/firmware.lst

# 5. 段大小报告
echo "Generating size report..."
$SIZE -A $ELF_FILE > $OUTPUT_DIR/size_report.txt

# 6. 符号表
echo "Generating symbol table..."
$NM -n -S $ELF_FILE > $OUTPUT_DIR/symbols.txt

# 7. ELF信息
echo "Generating ELF info..."
$READELF -a $ELF_FILE > $OUTPUT_DIR/elf_info.txt

# 8. 分离调试信息
echo "Generating debug symbols..."
$OBJCOPY --only-keep-debug $ELF_FILE $OUTPUT_DIR/firmware.debug
$OBJCOPY --strip-debug $ELF_FILE $OUTPUT_DIR/firmware_stripped.elf
$OBJCOPY --add-gnu-debuglink=$OUTPUT_DIR/firmware.debug $OUTPUT_DIR/firmware_stripped.elf

# 9. 计算校验和
echo "Calculating checksums..."
crc32 $OUTPUT_DIR/firmware.bin > $OUTPUT_DIR/checksum.txt
md5sum $OUTPUT_DIR/firmware.bin >> $OUTPUT_DIR/checksum.txt

echo "All outputs generated in $OUTPUT_DIR"

7. LST文件与MAP文件的应用

7.1 LST文件分析

反汇编文件的作用
  1. 代码审查 - 验证编译器生成的代码是否正确
  2. 性能优化 - 分析热点代码的汇编实现
  3. 问题定位 - 定位死机、异常的根本原因
  4. 学习理解 - 学习编译器如何将C代码转换为机器码
代码大小分析
bash 复制代码
# 分析各函数的代码大小
arm-none-eabi-nm -S --size-sort firmware.elf | grep -E " T | t "

# 输出示例
08001234 00000010 T small_function
08001400 00000050 T medium_function
08002000 00000200 T large_function
性能优化分析
c 复制代码
// 原始C代码
int sum_array(int *arr, int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += arr[i];
    }
    return sum;
}
asm 复制代码
# -O0 优化级别的反汇编
08000100 <sum_array>:
 8000100:	push	{r4, r5, lr}
 8000102:	mov	r5, r0        ; arr
 8000104:	mov	r4, r1        ; n
 8000106:	movs	r0, #0        ; sum = 0
 8000108:	cmp	r4, #0
 800010a:	beq.n	800011a <sum_array+0x1a>
 800010c:	movs	r3, #0        ; i = 0
 800010e:	ldr	r2, [r5, r3, lsl #2]
 8000110:	adds	r0, r0, r2
 8000112:	adds	r3, #1
 8000114:	cmp	r3, r4
 8000116:	blt.n	800010e <sum_array+0xe>
 8000118:	pop	{r4, r5, pc}
asm 复制代码
# -O3 优化级别的反汇编
08000100 <sum_array>:
 8000100:	cmp	r1, #0
 8000102:	beq.n	8000114 <sum_array+0x14>
 8000104:	push	{r4, r5}
 8000106:	movs	r2, #0
 8000108:	movs	r3, #0
 800010a:	ldmia	r0!, {r4, r5}
 800010c:	adds	r3, r4
 800010e:	adds	r3, r5
 8000110:	cmp	r2, r1
 8000112:	blt.n	800010a <sum_array+0xa>
 8000114:	mov	r0, r3
 8000116:	pop	{r4, r5}
 8000118:	bx	lr

7.2 MAP文件分析

段布局分析
复制代码
# 从MAP文件查看内存布局

Memory Configuration

Name             Origin             Length             Attributes
FLASH            0x08000000         0x00080000         xr
RAM              0x20000000         0x00020000         xrw

# 分析各段大小
.text    0x08000100    0x1234    (4660 bytes)
.rodata  0x08001334    0x0200    (512 bytes)
.data    0x20000000    0x0100    (256 bytes)
.bss     0x20000100    0x0400    (1024 bytes)
函数地址定位
复制代码
# MAP文件中的函数地址信息

.text.main
 0x08000100    0x64 main.o
              0x08000100                main
              
.text.uart_init
 0x08000164    0x3c uart.o
              0x08000164                uart_init
              
.text.uart_send
 0x080001a0    0x28 uart.o
              0x080001a0                uart_send
全局变量定位
复制代码
# MAP文件中的全局变量信息

.data.g_buffer
 0x20000000    0x400 main.o
              0x20000000                g_buffer
              
.bss.g_rx_buffer
 0x20000500    0x200 main.o
              0x20000500                g_rx_buffer

7.3 死机重启定位应用

步骤1:获取死机地址
c 复制代码
// 在HardFault_Handler中保存死机信息
void HardFault_Handler(void)
{
    uint32_t stack_ptr;
    uint32_t pc_value;
    
    __asm volatile (
        "mov %0, sp\n"
        "ldr %1, [sp, #24]\n"  // 获取PC值
        : "=r" (stack_ptr), "=r" (pc_value)
    );
    
    // 保存到备份寄存器或Flash
    RTC->BKP0R = pc_value;
    
    while(1);
}
步骤2:使用addr2line定位
bash 复制代码
# 使用死机时的PC值定位代码位置
arm-none-eabi-addr2line -e firmware.elf -f -C 0x08001234

# 输出示例
process_data
src/data_processing.c:42
步骤3:使用LST文件详细分析
bash 复制代码
# 在反汇编文件中查找地址
grep -n "8001234:" firmware.lst

# 或查看地址范围
arm-none-eabi-objdump -d --start-address=0x08001230 --stop-address=0x08001250 firmware.elf
asm 复制代码
# 详细反汇编
 8001230:	6813      	ldr	r3, [r2, #0]
 8001232:	2b00      	cmp	r3, #0
 8001234:	d002      	beq.n	800123c <process_data+0x28>  ← 死机地址
 8001236:	681b      	ldr	r3, [r3, #0]    ; 可能是空指针解引用
 8001238:	601b      	str	r3, [r3, #0]
 800123a:	e002      	b.n	8001242
步骤4:使用MAP文件查找函数
bash 复制代码
# 在MAP文件中查找地址对应的函数
grep -B5 "0x08001234" firmware.map

7.4 资源优化应用

段大小分析脚本
bash 复制代码
#!/bin/bash
# analyze_size.sh - 分析各段和函数大小

echo "=== 段大小分析 ==="
arm-none-eabi-size -A firmware.elf

echo ""
echo "=== 最大函数TOP10 ==="
arm-none-eabi-nm -S --size-sort firmware.elf | grep -E " T " | tail -10

echo ""
echo "=== 最大全局变量TOP10 ==="
arm-none-eabi-nm -S --size-sort firmware.elf | grep -E " D | B " | tail -10

echo ""
echo "=== 段详细分析 ==="
arm-none-eabi-objdump -h firmware.elf
优化建议
bash 复制代码
# 查找未使用的函数(如果使用了gc-sections)
grep "removing" firmware.map

# 输出示例:
Removing unused input section .text.unused_function from main.o
Removing unused input section .rodata.unused_string from main.o

# 查找被折叠的相同函数
grep "folded" firmware.map

7.5 实用分析工具

函数调用图生成
bash 复制代码
# 使用nm和cscope生成调用关系
arm-none-eabi-nm -C firmware.elf > symbols.txt

# 使用objdump提取调用关系
arm-none-eabi-objdump -d firmware.elf | grep "bl\|b\." > calls.txt
栈使用分析
c 复制代码
// 编译时使用 -fstack-usage 选项
// arm-none-eabi-gcc -fstack-usage -c main.c

// 生成的 main.su 文件内容示例:
// main.c:10:5:void process_data(void)	16	static
// main.c:25:5:void complex_calculation(void)	256	dynamic
内存映射可视化
bash 复制代码
# 使用readelf提取段信息并生成报告
arm-none-eabi-readelf -S firmware.elf > sections.txt

# 解析MAP文件生成内存布局图
python3 generate_memory_map.py firmware.map

8. Ninja与GN项目管理框架

8.1 GN (Generate Ninja) 简介

GN是一个元构建系统,用于生成Ninja构建文件。GN的主要特点:

  1. 快速 - 比CMake快很多
  2. 简洁 - 语法简单直观
  3. 可扩展 - 支持自定义模板
  4. 跨平台 - 支持多种平台和工具链

8.2 GN基本语法

BUILD.gn基本结构
gn 复制代码
# BUILD.gn - 基本示例

# 声明可执行目标
executable("firmware") {
    sources = [
        "src/main.c",
        "src/uart.c",
        "src/timer.c",
    ]
    
    include_dirs = [
        "include",
        "//third_party/cmsis/include",
    ]
    
    deps = [
        ":startup",
        "//third_party/cmsis",
        "//drivers/uart",
    ]
}

# 声明源集(一组源文件)
source_set("startup") {
    sources = [
        "src/startup.c",
        "src/system_init.c",
    ]
}

# 声明静态库
static_library("mylib") {
    sources = [
        "lib/mylib.c",
    ]
    
    public = [
        "include/mylib.h",
    ]
}
配置声明
gn 复制代码
# config.gn - 配置声明

config("compiler_config") {
    # 编译器标志
    cflags = [
        "-Wall",
        "-Wextra",
        "-Werror",
        "-O2",
        "-g",
        "-mcpu=cortex-m4",
        "-mthumb",
        "-mfloat-abi=hard",
        "-mfpu=fpv4-sp-d16",
        "-ffunction-sections",
        "-fdata-sections",
    ]
    
    # 预处理器定义
    defines = [
        "STM32F407xx",
        "USE_HAL_DRIVER",
        "DEBUG=1",
    ]
    
    # 包含目录
    include_dirs = [
        "include",
        "//third_party/cmsis/include",
    ]
}

config("linker_config") {
    # 链接器标志
    ldflags = [
        "-T//ld/stm32f407.ld",
        "-Wl,--gc-sections",
        "-Wl,-Map=fake",
        "-nostdlib",
        "-static",
    ]
    
    # 库
    libs = [
        "c",
        "m",
        "nosys",
    ]
}
目标类型
gn 复制代码
# 可执行文件
executable("firmware") {
    sources = [ "main.c" ]
    configs += [ ":compiler_config" ]
}

# 静态库
static_library("mylib") {
    sources = [ "lib.c" ]
}

# 共享库
shared_library("myso") {
    sources = [ "lib.c" ]
}

# 源文件集
source_set("common") {
    sources = [ "common.c" ]
}

# 动作(自定义命令)
action("generate_version") {
    script = "scripts/generate_version.py"
    outputs = [ "$target_gen_dir/version.h" ]
    args = [ "--output", rebase_path(outputs[0], root_build_dir) ]
}

# 动作副本
copy("copy_headers") {
    sources = [ "include/header.h" ]
    outputs = [ "$root_out_dir/include/header.h" ]
}

# 组(聚合目标)
group("all") {
    deps = [
        ":firmware",
        ":generate_docs",
    ]
}

8.3 完整嵌入式项目示例

项目结构
复制代码
project/
├── BUILD.gn                 # 主构建文件
├── BUILD.gn                 # 配置文件
├── .gn                      # GN配置文件
├── args.gn                  # 构建参数
├── src/
│   ├── BUILD.gn
│   ├── main.c
│   ├── startup.c
│   └── system.c
├── drivers/
│   ├── BUILD.gn
│   ├── uart/
│   │   ├── BUILD.gn
│   │   ├── uart.c
│   │   └── uart.h
│   └── gpio/
│       ├── BUILD.gn
│       ├── gpio.c
│       └── gpio.h
├── hal/
│   ├── BUILD.gn
│   └── stm32f4xx_hal.c
├── third_party/
│   └── cmsis/
│       ├── BUILD.gn
│       └── include/
├── ld/
│   └── stm32f407.ld
└── toolchain/
    └── arm_gcc.gni
顶层配置文件
gn 复制代码
# .gn - GN配置文件

buildconfig = "//toolchain/BUILDCONFIG.gn"

# 默认工具链
set_default_toolchain("//toolchain:arm_gcc")
gn 复制代码
# toolchain/BUILDCONFIG.gn

# 默认配置
_default_compiler_config = "//:compiler_config"
_default_linker_config = "//:linker_config"

set_default_toolchains() {
    default_toolchain = "//toolchain:arm_gcc"
}
工具链定义
gn 复制代码
# toolchain/arm_gcc.gni

# 工具链前缀
arm_toolchain_prefix = "arm-none-eabi-"

# 工具链路径
arm_toolchain_dir = "//toolchain/gcc-arm"
gn 复制代码
# toolchain/BUILD.gn

import("//toolchain/arm_gcc.gni")

# 定义ARM GCC工具链
toolchain("arm_gcc") {
    # 工具定义
    tool("cc") {
        depfile = "{{output}}.d"
        command = "${arm_toolchain_prefix}gcc {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
        depsformat = "gcc"
        description = "CC {{output}}"
        outputs = [
            "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
        ]
    }
    
    tool("cxx") {
        depfile = "{{output}}.d"
        command = "${arm_toolchain_prefix}g++ {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
        depsformat = "gcc"
        description = "CXX {{output}}"
        outputs = [
            "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
        ]
    }
    
    tool("asm") {
        command = "${arm_toolchain_prefix}gcc {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
        description = "ASM {{output}}"
        outputs = [
            "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
        ]
    }
    
    tool("alink") {
        rspfile = "{{output}}.rsp"
        rspfile_content = "{{inputs}}"
        command = "${arm_toolchain_prefix}ar rcs {{output}} @{{rspfile}}"
        description = "AR {{target_output_name}}{{output_extension}}"
        outputs = [
            "{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
        ]
        default_output_extension = ".a"
        output_prefix = "lib"
    }
    
    tool("solink") {
        command = "${arm_toolchain_prefix}ld -shared {{ldflags}} -o {{output}} {{inputs}} {{libs}}"
        description = "SOLINK {{target_output_name}}"
    }
    
    tool("link") {
        outfile = "{{target_output_name}}{{output_extension}}"
        rspfile = "{{output}}.rsp"
        rspfile_content = "{{inputs}}"
        command = "${arm_toolchain_prefix}g++ {{ldflags}} -o {{output}} @{{rspfile}} {{libs}}"
        description = "LINK {{outfile}}"
        default_output_extension = ".elf"
        outputs = [
            "{{target_out_dir}}/$outfile",
        ]
    }
    
    tool("stamp") {
        command = "touch {{output}}"
        description = "STAMP {{output}}"
    }
    
    tool("copy") {
        command = "cp -f {{source}} {{output}}"
        description = "COPY {{source}} {{output}}"
    }
}
主BUILD.gn
gn 复制代码
# BUILD.gn - 主构建文件

import("//toolchain/arm_gcc.gni")

# 编译器配置
config("compiler_config") {
    cflags = [
        "-Wall",
        "-Wextra",
        "-Werror",
        "-std=c11",
        "-O2",
        "-g",
        "-mcpu=cortex-m4",
        "-mthumb",
        "-mfloat-abi=hard",
        "-mfpu=fpv4-sp-d16",
        "-ffunction-sections",
        "-fdata-sections",
        "-ffreestanding",
        "-fno-builtin",
    ]
    
    defines = [
        "STM32F407xx",
        "USE_HAL_DRIVER",
        "HSE_VALUE=8000000",
    ]
    
    include_dirs = [
        "//include",
        "//third_party/cmsis/include",
        "//hal/include",
    ]
}

# 链接器配置
config("linker_config") {
    ldflags = [
        "-mcpu=cortex-m4",
        "-mthumb",
        "-mfloat-abi=hard",
        "-mfpu=fpv4-sp-d16",
        "-T//ld/stm32f407.ld",
        "-Wl,--gc-sections",
        "-Wl,-Map=fake",
        "-nostartfiles",
        "-nostdlib",
        "-static",
        "-Wl,--start-group",
        "-lgcc",
        "-lc",
        "-lm",
        "-lnosys",
        "-Wl,--end-group",
    ]
}

# 主固件目标
executable("firmware") {
    configs += [
        ":compiler_config",
        ":linker_config",
    ]
    
    deps = [
        "//src",
        "//drivers",
        "//hal",
        "//third_party/cmsis",
    ]
}

# 后处理动作
action("generate_bin") {
    deps = [ ":firmware" ]
    
    script = "//scripts/generate_bin.py"
    
    sources = [ "$root_out_dir/firmware.elf" ]
    outputs = [ "$root_out_dir/firmware.bin" ]
    
    args = [
        "--input", rebase_path(sources[0], root_build_dir),
        "--output", rebase_path(outputs[0], root_build_dir),
    ]
}

action("generate_hex") {
    deps = [ ":firmware" ]
    
    script = "//scripts/generate_hex.py"
    
    sources = [ "$root_out_dir/firmware.elf" ]
    outputs = [ "$root_out_dir/firmware.hex" ]
    
    args = [
        "--input", rebase_path(sources[0], root_build_dir),
        "--output", rebase_path(outputs[0], root_build_dir),
    ]
}

# 构建所有产物
group("all") {
    deps = [
        ":firmware",
        ":generate_bin",
        ":generate_hex",
    ]
}

# 默认目标
default("all")
子目录BUILD.gn
gn 复制代码
# src/BUILD.gn

source_set("src") {
    sources = [
        "main.c",
        "startup.c",
        "system.c",
    ]
    
    deps = [
        "//drivers",
        "//hal",
    ]
}
gn 复制代码
# drivers/BUILD.gn

group("drivers") {
    deps = [
        "uart",
        "gpio",
        "spi",
        "i2c",
    ]
}
gn 复制代码
# drivers/uart/BUILD.gn

source_set("uart") {
    sources = [
        "uart.c",
    ]
    
    public = [
        "uart.h",
    ]
    
    deps = [
        "//hal",
    ]
}

8.4 Ninja构建系统

Ninja基本概念

Ninja是一个小型的构建系统,专注于构建速度。它使用简单的构建文件描述依赖关系。

Ninja文件语法
ninja 复制代码
# 基本规则定义
rule cc
    command = arm-none-eabi-gcc $cflags -c $in -o $out
    description = CC $out

rule link
    command = arm-none-eabi-gcc $ldflags $in -o $out
    description = LINK $out

rule objcopy
    command = arm-none-eabi-objcopy -O $format $in $out
    description = OBJCOPY $out

# 变量定义
cflags = -Wall -O2 -mcpu=cortex-m4 -mthumb
ldflags = -T stm32f407.ld -Wl,--gc-sections

# 构建语句
build main.o: cc main.c
build uart.o: cc uart.c
build firmware.elf: link main.o uart.o
build firmware.bin: objcopy firmware.elf
    format = binary

# 默认目标
default firmware.bin
GN生成的Ninja文件

当运行GN时,它会分析BUILD.gn文件并生成build.ninja文件:

bash 复制代码
# 生成Ninja文件
gn gen out/debug

# 生成的目录结构
out/debug/
├── build.ninja              # 主Ninja文件
├── build.ninja.d            # 依赖信息
├── obj/                     # 目标文件
│   ├── src/
│   ├── drivers/
│   └── ...
├── toolchain.ninja          # 工具链规则
└── args.gn                  # 构建参数

8.5 构建流程

完整构建流程
复制代码
1. 配置阶段
   ┌─────────────┐
   │  BUILD.gn   │
   │  *.gn       │
   └──────┬──────┘
          │ gn gen
          ▼
   ┌─────────────┐
   │ build.ninja │
   │ *.ninja     │
   └──────┬──────┘

2. 编译阶段
   ┌─────────────┐
   │  *.c/*.cpp  │
   │  *.s/*.S    │
   └──────┬──────┘
          │ ninja
          ▼
   ┌─────────────┐
   │    *.o      │
   └──────┬──────┘
          │
          ▼
   ┌─────────────┐
   │  *.elf      │
   └──────┬──────┘
          │
          ▼
   ┌─────────────┐
   │ *.bin/*.hex │
   └─────────────┘
构建命令
bash 复制代码
# 生成Ninja文件
gn gen out/debug

# 带参数生成
gn gen out/debug --args='
    target_cpu="arm"
    is_debug=true
    optimize_for_size=true
'

# 编译
ninja -C out/debug

# 编译特定目标
ninja -C out/debug firmware.bin

# 清理
ninja -C out/debug -t clean

# 查看依赖
ninja -C out/debug -t deps firmware.elf

# 查看构建图
ninja -C out/debug -t query firmware.elf

8.6 构建配置管理

多配置支持
gn 复制代码
# args.gn - 构建参数

# 目标CPU
target_cpu = "arm"

# 调试/发布
is_debug = true

# 优化选项
optimize_for_size = true

# 工具链选择
use_gcc_toolchain = true
gn 复制代码
# BUILD.gn - 根据配置选择不同选项

import("//build/args.gni")

config("compiler_config") {
    cflags = []
    
    if (optimize_for_size) {
        cflags += [ "-Os" ]
    } else if (is_debug) {
        cflags += [ "-O0", "-g3" ]
    } else {
        cflags += [ "-O3" ]
    }
    
    if (target_cpu == "arm") {
        cflags += [
            "-mcpu=cortex-m4",
            "-mthumb",
        ]
    }
}
条件编译
gn 复制代码
# BUILD.gn - 条件编译示例

source_set("hal") {
    sources = [ "hal_common.c" ]
    
    if (defined(target_soc) && target_soc == "stm32f407") {
        sources += [
            "stm32f4xx_hal.c",
            "stm32f4xx_hal_uart.c",
        ]
        defines = [ "STM32F407xx" ]
    } else if (target_soc == "stm32f103") {
        sources += [
            "stm32f1xx_hal.c",
            "stm32f1xx_hal_uart.c",
        ]
        defines = [ "STM32F103xx" ]
    }
}

9. 资深工程师经验与技巧

9.1 编译优化经验

选择正确的优化级别
复制代码
┌────────────┬─────────────────────────────────────────────────────┐
│ 优化级别   │ 适用场景                                            │
├────────────┼─────────────────────────────────────────────────────┤
│ -O0        │ 调试阶段,完全无优化,便于单步调试                   │
│ -Og        │ 调试优化,保留调试信息同时做基本优化                 │
│ -O1        │ 基本优化,编译时间短,适度优化                       │
│ -O2        │ 推荐级别,平衡代码大小和性能                         │
│ -Os        │ 嵌入式首选,优化代码大小,可能牺牲部分性能           │
│ -O3        │ 性能优先,可能显著增加代码大小                       │
│ -Ofast     │ 极致性能,可能违反标准,慎用                         │
└────────────┴─────────────────────────────────────────────────────┘
优化建议
bash 复制代码
# 嵌入式项目推荐配置
arm-none-eabi-gcc \
    -Os \                      # 优化大小
    -ffunction-sections \      # 函数独立段
    -fdata-sections \          # 数据独立段
    -fno-common \              # 避免common段
    -finline-functions-called-once \  # 单次调用函数内联
    -fmerge-all-constants \    # 合并常量
    -flto \                    # 链接时优化
    -mcpu=cortex-m4 \
    -mthumb

# 链接器配合
arm-none-eabi-ld \
    --gc-sections \            # 回收未使用段
    --icf=safe \               # 相同代码折叠
    --print-gc-sections        # 打印回收信息

9.2 调试信息管理

调试信息分离
bash 复制代码
# 分离调试信息
arm-none-eabi-objcopy --only-keep-debug firmware.elf firmware.debug
arm-none-eabi-objcopy --strip-debug firmware.elf firmware_stripped.elf
arm-none-eabi-objcopy --add-gnu-debuglink=firmware.debug firmware_stripped.elf

# 使用分离的调试信息调试
gdb firmware_stripped.elf
(gdb) symbol-file firmware.debug
调试信息优化
bash 复制代码
# 减少调试信息大小
-gsplit-dwarf                # 分离DWARF信息
-gdwarf-4                    # 使用DWARF 4格式
-g1                          # 最小调试信息
-ggdb                        # GDB优化格式

9.3 链接脚本技巧

自动计算校验和
ld 复制代码
/* 链接脚本中定义校验和计算区域 */

SECTIONS
{
    .text :
    {
        *(.text)
    } > FLASH
    
    /* 定义校验和区域 */
    .checksum :
    {
        LONG(CHECKSUM(.text))    /* 计算校验和 */
    } > FLASH
    
    /* 或者手动指定 */
    _text_checksum = 0;
    _text_start = ORIGIN(FLASH);
    _text_end = .;
}
版本信息嵌入
ld 复制代码
/* 在链接脚本中嵌入版本信息 */

_version_major = 1;
_version_minor = 0;
_version_patch = 0;

SECTIONS
{
    .version :
    {
        BYTE(_version_major)
        BYTE(_version_minor)
        BYTE(_version_patch)
        BYTE(0)    /* Reserved */
    } > FLASH
}
多镜像支持
ld 复制代码
/* Bootloader和Application镜像 */

MEMORY
{
    BOOTLOADER (rx) : ORIGIN = 0x08000000, LENGTH = 32K
    APPLICATION (rx) : ORIGIN = 0x08008000, LENGTH = 480K
    CONFIG (rx)      : ORIGIN = 0x0807C000, LENGTH = 16K
    RAM (rwx)        : ORIGIN = 0x20000000, LENGTH = 128K
}

SECTIONS
{
    .bootloader :
    {
        *(.bootloader)
        *(.bootloader.*)
    } > BOOTLOADER
    
    .application :
    {
        *(.text)
        *(.text*)
        *(.rodata*)
    } > APPLICATION
    
    .config :
    {
        *(.config)
    } > CONFIG
}

9.4 内存优化技巧

栈使用优化
c 复制代码
// 避免大数组在栈上
void bad_function(void) {
    uint8_t buffer[4096];  // 栈上分配4KB,危险!
    // ...
}

// 使用静态或全局缓冲区
static uint8_t shared_buffer[4096];

void good_function(void) {
    uint8_t *buffer = shared_buffer;
    // ...
}

// 或使用动态分配(如果有堆)
void better_function(void) {
    uint8_t *buffer = malloc(4096);
    if (buffer) {
        // ...
        free(buffer);
    }
}
内存池管理
c 复制代码
// 定义内存池
#define POOL_SIZE   4096
static uint8_t memory_pool[POOL_SIZE];
static size_t pool_index = 0;

// 简单的内存分配
void *pool_alloc(size_t size) {
    if (pool_index + size > POOL_SIZE) {
        return NULL;
    }
    void *ptr = &memory_pool[pool_index];
    pool_index += size;
    return ptr;
}

// 重置内存池
void pool_reset(void) {
    pool_index = 0;
}

9.5 常见问题解决

问题1:未定义符号
bash 复制代码
# 错误信息
undefined reference to `xxx'

# 解决方法
1. 检查是否链接了相关库:-L/path/to/lib -lmylib
2. 检查库顺序:被依赖的库放在后面
3. 使用库分组:-Wl,--start-group -la -lb -Wl,--end-group
4. 检查符号是否声明但未定义
问题2:段溢出
bash 复制代码
# 错误信息
region `RAM' overflowed by 1024 bytes

# 解决方法
1. 分析段大小:arm-none-eabi-size -A firmware.elf
2. 查找大符号:arm-none-eabi-nm -S --size-sort firmware.elf
3. 启用gc-sections
4. 使用-Os优化
5. 减少静态缓冲区大小
问题3:对齐错误
c 复制代码
// 问题代码
uint32_t *ptr = (uint32_t *)0x20000001;  // 未对齐地址
*ptr = 0x12345678;  // 可能触发HardFault

// 解决方法
// 1. 确保数据对齐
uint32_t data __attribute__((aligned(4)));

// 2. 使用memcpy处理未对齐数据
uint32_t value;
memcpy(&value, ptr, sizeof(value));

// 3. 使用packed属性
struct __attribute__((packed)) packed_struct {
    uint8_t a;
    uint32_t b;
};

9.6 性能优化技巧

关键函数优化
c 复制代码
// 使用RAM运行关键函数
__attribute__((section(".ramcode")))
void flash_program(uint32_t addr, uint32_t data)
{
    // Flash编程时需要从RAM运行
}

// 强制内联热点函数
__attribute__((always_inline))
static inline int hot_function(int x)
{
    return x * 2;
}

// 使用restrict提示优化
void copy_data(int *restrict dst, const int *restrict src, int n)
{
    for (int i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}
缓存优化
c 复制代码
// Cortex-M7 D-Cache相关
// 对于DMA缓冲区,需要考虑缓存一致性

// 定义非缓存区域
#define NONCACHEABLE __attribute__((section(".noncacheable")))

uint32_t dma_buffer[256] NONCACHEABLE;

// 或使用SCB_InvalidateDCache和SCB_CleanDCache
void dma_transfer_prepare(void *buffer, size_t size)
{
    SCB_CleanDCache_by_Addr((uint32_t *)buffer, size);
}

void dma_transfer_complete(void *buffer, size_t size)
{
    SCB_InvalidateDCache_by_Addr((uint32_t *)buffer, size);
}

9.7 工程实践建议

版本控制集成
bash 复制代码
# 在构建时嵌入Git信息
#!/bin/bash
# generate_version.sh

GIT_HASH=$(git rev-parse --short HEAD)
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
BUILD_TIME=$(date +"%Y-%m-%d %H:%M:%S")

echo "#define GIT_HASH \"$GIT_HASH\"" > version.h
echo "#define GIT_BRANCH \"$GIT_BRANCH\"" >> version.h
echo "#define BUILD_TIME \"$BUILD_TIME\"" >> version.h
静态分析集成
bash 复制代码
# 使用cppcheck进行静态分析
cppcheck --enable=all \
    --std=c11 \
    --target=arm-none-eabi \
    --include=config.h \
    src/

# 使用clang-tidy
clang-tidy src/*.c -- \
    -std=c11 \
    -target arm-none-eabi \
    -I./include
构建日志管理
bash 复制代码
# 保存详细构建日志
ninja -C out/debug 2>&1 | tee build.log

# 分析编译警告
grep -E "warning:" build.log > warnings.txt

附录

A. 常用工具链命令速查

功能 命令
预处理 gcc -E main.c -o main.i
编译到汇编 gcc -S main.c -o main.s
编译到目标文件 gcc -c main.c -o main.o
链接 ld main.o -o a.out -T script.ld
查看段信息 objdump -h a.out
反汇编 objdump -d a.out
查看符号 nm a.out
生成bin objcopy -O binary a.out a.bin
生成hex objcopy -O ihex a.out a.hex
地址转行号 addr2line -e a.out 0x1234
查看ELF信息 readelf -a a.out
删除符号 strip --strip-all a.out
查看大小 size a.out

B. 编译选项速查

类别 选项 说明
优化 -O0/-O1/-O2/-O3/-Os/-Og 优化级别
调试 -g/-g3/-ggdb 调试信息
警告 -Wall/-Wextra/-Werror 警告控制
标准 -std=c99/c11/c++11/c++17 语言标准
定义 -Dmacro[=value] 宏定义
包含 -I 头文件路径
架构 -mcpu/-mthumb/-mfloat-abi 目标架构
-ffunction-sections/-fdata-sections 独立段
链接 -T/-L /-l 链接控制

C. 常见错误速查

错误 原因 解决方法
undefined reference 符号未定义 添加库或定义符号
multiple definition 符号重复定义 使用static或weak
region overflowed 内存不足 优化或减少内存使用
relocation truncated 地址超出范围 修改链接脚本
cannot find -lxxx 库文件未找到 检查-L路径
alignment error 地址未对齐 添加对齐属性

文档版本 : v1.0
最后更新 : 2026年4月
适用工具链 : ARM GNU Toolchain, RISC-V GCC
作者:GLM5.0

在AI大爆发的时代,作为从业者我们可能更多的是得有知识的广度,未来只会某一个专项的知识可能不太能符合市场需求,AI将很多东西平权带给所有人,我们可能需要借助AI高效的更多积累,做到厚积簿发。

相关推荐
FreakStudio27 分钟前
亲测可用!可本地部署的 MicroPython 开源仿真器
python·单片机·嵌入式·面向对象·并行计算·电子diy·电子计算机
rit84324992 小时前
STM32 + DS3231 + TM1640 实时时钟数码管显示系统
stm32·单片机·嵌入式硬件
zjxtxdy3 小时前
STM32开发
stm32·单片机·fpga开发
LCG元4 小时前
STM32实战:基于STM32F103的I2C通信(AT24Cxx EEPROM读写)
stm32·单片机·嵌入式硬件
徐某人..4 小时前
基于i.MX6ULL平台的智能网关系统开发
arm开发·c++·单片机·qt·物联网·学习·arm
星恒讯工业路由器6 小时前
MCU+WiFi与CPU+WiFi模块区别
单片机·嵌入式硬件
LCMICRO-133108477466 小时前
长芯微LD7940完全P2P替代AD7940,是一款14位、逐次逼近型模数转换器(ADC)
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发·模数转换器adc
进击的小头7 小时前
20_第20篇:嵌入式外设驱动开发基础:寄存器级开发与库函数开发对比实战
arm开发·驱动开发·单片机
guygg889 小时前
基于STM32的智能小区管理系统设计
stm32·单片机·嵌入式硬件
Deitymoon9 小时前
STM32——震动传感器控制led
stm32·单片机·嵌入式硬件