☰
Cortex-M3-STM32F1 开发:(八)编译生成的 .MAP 中间文件详解
2026/10/5 6:36:25 网站建设 项目流程
上一篇下一篇
新建 HAL 工程模板详细的 STM32 启动过程

这里写目录标题

  • .MAP 文件
    • 1)是什么
    • 2)组成与如何解读
    • 3)日常排查时怎么快速用

.MAP 文件

1)是什么

MDK 在编译的过程中,会产生一些中间文件:

文件类型简介
.o可重定向对象文件,每个 .c/.s 文件都对应一个 .o 文件
.axf可执行对象文件,由 .o 文件链接生成,仿真的时候需要用到此文件
.hexINTELHex 格式文件,用于下载到 MCU 运行,由 .axf 转换而来
.map连接器生成的列表文件,对分析程序存储占用情况非常有用
其他.crf、.d、.dep、.lnp、.lst、.htm、.build_log.htm等一般用不到

.o 文件→ \rightarrow→.axf 文件→ \rightarrow→.hex 文件。

其中 .MAP 文件和 .hex 文件是最重要的。

配置输出方式:单片机开发工具篇:(十四)配置输出 .map 文件及其本地路径-CSDN博客。

双击工程文件名,即可在 MDK 中打开 .map 文件:

2)组成与如何解读

下面以我做过的一个冷链运输项目的 .map 文件为例,不过都是 M3 内核,MCU 是 STM32F103RCT6 。

【免费】冷链运输项目里冷库端的.map文件资源-CSDN下载

.MAP 文件组成:

组成部分简介
程序段交叉引用关系
Section Cross References
描述各文件之间函数调用关系
删除映像未使用的程序段
Removing Unused input sections from the image.
描述工程中未用到而被删除的冗余程序段(函数/数据)
映像符号表
Image Symbol Table(Local Symbols、Global Symbols)
描述各符号(程序段/数据)在存储器中的地址、类型、大小等
映像内存分布图
Memory Map of the image
描述各个程序段(函数)在存储器中的地址及占用大小
映像组件大小
Image component sizes
给出整个映像代码(.o)占用空间汇总信息

某个 .map 文件内容如下:

Component: ARM Compiler 5.06 update 7 (build 960) Tool: armlink [4d3601] ============================================================================== Section Cross References ...... usart.o(i.USART1_IRQHandler) refers to usart.o(.bss) for USART_RX_BUF usart.o(i.uart_init) refers to stm32f10x_usart.o(i.USART_Init) for USART_Init usart.o(i.fputc) refers (Special) to use_no_semi_2.o(.text) for __use_no_semihosting ...... pow.o(i.pow) refers to pow.o(.constdata) for .constdata ...... _printf_d.o(.ARM.Collect$$…) refers (Weak) to _printf_dec.o(.text) for _printf_int_dec ...... ...... ============================================================================== Removing Unused input sections from the image. ...... Removing lcd.o(i.LCD_Set_Window), (908 bytes). Removing core_cm3.o(.emb_text), (32 bytes). Removing key.o(.data), (1 bytes). ...... ...... 563 unused section(s) (total 34109 bytes) removed from the image. ============================================================================== Image Symbol Table Local Symbols Symbol Name Value Ov Type Size Object(Section) ...... ucHeap 0x200007e8 Data 20480 heap_4.o(.bss) .text 0x080002f4 Section 64 startup_stm32f10x_hd.o(.text) ...... Global Symbols ...... Adc1_Init 0x08002841 Thumb Code 132 adc1.o(i.Adc1_Init) __ARM_use_no_argv 0x00000000 Number 0 main.o ABSOLUTE ...... ============================================================================== Memory Map of the image Image Entry point : 0x08000131 Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000110e0, Max: 0x00040000, ABSOLUTE, COMPRESSED[0x00010fd0]) ← 第 1 层:整个烧录映像 Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00010f94, Max: 0x00040000, ABSOLUTE) ← 第 2 层:运行区 1 Exec Addr Load Addr Size Type Attr Idx E Section Name Object ← 第 3 层:明细 0x08000000 0x08000000 0x00000130 Data RO 3 RESET startup_stm32f10x_hd.o 0x08000130 0x08000130 0x00000008 Code RO 5827 * !!!main c_w.l(__main.o) ...... ...... Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08010f94, Size: 0x00006248, Max: 0x0000c000, ABSOLUTE, COMPRESSED[0x0000003c]) 0x20000000 COMPRESSED 0x00000014 Data RW 28 .data system_stm32f10x.o 0x200007e8 - 0x00005000 Zero RW 5588 .bss heap_4.o ...... ...... ============================================================================== Image component sizes Code (inc. data) RO Data RW Data ZI Data Debug Object Name 452 42 0 0 0 12316 adc1.o 586 86 0 20 512 5266 atk_esp8266.o ...... ---------------------------------------------------------------------- Code (inc. data) RO Data RW Data ZI Data Debug Library Member Name 60 8 0 0 0 84 __0sscanf.o 24 4 0 0 0 84 __2printf.o ...... ---------------------------------------------------------------------- Code (inc. data) RO Data RW Data ZI Data Debug Library Name 9358 306 488 0 96 5164 c_w.l 5184 356 16 0 0 3268 fz_ws.l ...... ============================================================================== Code (inc. data) RO Data RW Data ZI Data Debug 61532 6204 7992 332 24828 555690 Grand Totals 61532 6204 7992 60 24828 555690 ELF Image Totals (compressed) 61532 6204 7992 60 0 0 ROM Totals ============================================================================== Total RO Size (Code + RO Data) 69524 ( 67.89kB) Total RW Size (RW Data + ZI Data) 25160 ( 24.57kB) Total ROM Size (Code + RO Data + RW Data) 69584 ( 67.95kB) ==============================================================================

此文件内容的具体解析如下:

  1. Section Cross References —— 谁引用了谁

    • Keil 配置:由 Options for Target → Listing 里的Linker Listing → Cross Reference勾选项生成。

    • 作用:它是 armlink 从 ELF 重定位项还原出来的"引用报告"。

    • 每行的基本格式:

      <源段>refers[(引用类型)]to<目标段>for<符号名>/* 例如↓ usart.o(i.uart_init) refers to stm32f10x_usart.o(i.USART_Init) for USART_Init └──── 源段 ────────┘ └──── 目标段 ───────────────────┘ └─ 符号名 ─┘ */
      • 源段:格式是.o目标文件名(段名),即"谁发起了这次引用"。例如:

        • 段名i.uart_init表示这是"一个函数一个段"的代码段(前缀i.通常代表 Initializer 或 Image,表示该段存储的是仅占用 Flash 空间而无需分配 RAM 的只读初始化数据或代码常量),
        • 段名.text/.emb_text是汇编或多函数合并的代码段,
        • 段名.data/.bss是数据段。
      • 引用类型:默认空(普通引用),另有(Special)、(Weak)两种修饰,插在refers和to中间。

      • 目标段:被引用符号所在的位置,格式同源段。库成员会写成c_w.l(__main.o)这种"库名(成员名)"的形式。

      • 符号名:本次引用最终解析到的符号。这是全行信息量最大的字段,但它不一定是函数名—— 任何"用到某个符号地址"的地方都会产生一条引用记录,调用函数是重定位,读写全局变量也是。常见符号类型和例子如下:

        情形for后面的符号是判断依据(refers to后目标段的段名)
        ① 函数C 函数名i.*、.text、.emb_text、locale$$code、…
        ② 变量或常量全局或静态变量名、常量数组名.data、.bss、.constdata、.conststring
        ③ 段名符号以.开头的段名本身与符号同名的段
        ④ 库标记或配置符号__use_*等内部符号库的特制段,几乎都带(Special)
        ⑤ 弱引用可能未被解析的符号目标可为任意库成员,带(Weak)
    • 用途:

      • 查某个库函数是被谁拖进来的:想给 Flash 瘦身时,先看是谁引入了大块头库代码,搜for 函数名即可。
      • 查"谁访问了这个全局变量"。
      • 可以查真实函数调用链:搜for 函数名得到完整调用者清单,逐级往上就能画出调用图。
      • 验证代码是否真的被链接进去:如果某个函数在源码里明明写了,这里却搜不到,同时它又出现在Removing Unused input sections里,就能确定它没被调用。
  2. Removing Unused input sections —— 被丢掉的代码

    • Keil 配置:由 Listing →Unused Sections Info勾选项打印。

      • 要注意的是:裁剪本身是 armlink 的默认行为,勾选项只决定"要不要把它报告出来"。
    • 每行的基本格式:

      Removing<目标文件>(<段名>),(<字节数>bytes)./* 例如↓ Removing lcd.o(i.LCD_Set_Window), (908 bytes). Removing core_cm3.o(.emb_text), (32 bytes). Removing key.o(.data), (1 bytes). */
      • 这里每一行 = 一个被整体丢弃的输入段,末尾字节数是它原本要占用的空间。
      • 段的粒度是"全留或全丢",没有"半个函数留下"这种情况。列表按目标文件顺序排列,不按大小排。
      • 上述三个例子的段名含义如下:
        • 段名i.<函数名>:未被引用的函数(在 keil 中开启 C/C++ 选项卡里的 “One ELF Section per Function”,就会让每个函数独占一个段)
        • 段名.emb_text:未被引用的汇编代码段
        • 段名.data:未被引用的静态/全局变量
    • 用途:

      • 确认某个函数到底有没有被链接进去。这是最高频的用法(和 Section Cross References 部分联合查看)。
      • 判断哪些.c其实完全没用,估算"砍掉某个功能能省多少 Flash"。
      • 验证裁剪配置是否按预期生效。
  3. Image Symbol Table —— 符号地址表

    这一节是全文件里最像"字典"的部分

    • Keil 配置:由 Options for Target → Listing 里的Linker Listing → Symbols勾选项生成。

    • 它把链接完成后所有符号的最终地址列了出来,包括占多大。

    • 它分两张子表:Local Symbols(局部符号)和Global Symbols(全局符号)。

    • 每行的基本格式:(两张子表共用同一个表头)

      Symbol Name Value Ov Type SizeObject(Section)─────┬───── ──┬── ─┬ ──┬─── ─┬── ───────┬─────── │ │ │ │ │ └─ 符号属于哪个目标文件的哪个段 │ │ │ │ └─ 符号大小(字节),函数是可执行代码长度 │ │ │ └─ 符号类型,值见下表 │ │ └─ 覆盖(overlay)编号,本工程全为空 │ └─ 符号的最终绝对地址 └─ 符号名/* 例如↓ ucHeap 0x200007e8 Data 20480 heap_4.o(.bss) i.LCD_Init 0x08002841 Thumb Code 10592 lcd.o(i.LCD_Init) .text 0x080002f4 Section 64 startup_stm32f10x_hd.o(.text) __ARM_use_no_argv 0x00000000 Number 0 main.o ABSOLUTE ucHeap 0x200007e8 Data 20480 heap_4.o(.bss) ← FreeRTOS 堆 Stack_Mem 0x20005a48 Data 2048 startup_stm32f10x_hd.o(STACK) ← MSP 主栈 Heap_Mem 0x20005848 Data 512 startup_stm32f10x_hd.o(HEAP) ← C 库堆 */
      • Value是链接后的最终地址(不是编译时的偏移)。

      • 注意函数地址的末位是 1(如0x08002841),那是 ARM Thumb 标志位,真实指令地址要减 1。

      • 上述四个例子的四种符号类型含义如下:

        Type含义
        Thumb Code函数,有地址和机器码长度
        Data变量、常量数组、结构体,有地址和字节数
        Section段符号,只标记某个段的起始地址,不表示具名对象
        Number编译期常量(ABSOLUTE),值为 0,用来声明编译配置或依赖
      • Local 和 Global 的区别:

        • Global Symbols 是对外可见的符号 —— 非static的函数和全局变量,以及库导出的符号。链接器就是靠它们把不同.o拼到一起的。
        • Local Symbols 是只在本文件内可见的符号 ——static函数、static变量,外加编译器和汇编器为每个目标文件生成的段符号(.text、.data、.bss)。
        • 由此有一个实用推论:同一个符号名在 Local 里可能出现多次。不同.c里各写一个static uint8_t buf[16],符号表里就是多条buf,各占一块 RAM。短名(i、j、buf、tmp)特别容易撞,用 Golbal/Local 表能查出"到底有几份同名变量、各占多少 RAM"。
    • 用途:

      • 地址反查函数名 —— HardFault 调试的基石(在 debug 里也可以直接查)。
      • 核实 RAM/Flash 占用到底花在哪。
      • 查出隐藏的同名副本和常量表。
  4. Memory Map of the image —— 最重要的一节(⭐️)

    • Keil 配置:由 Options for Target → Listing 里的Linker Listing → Memory Map勾选项生成。

    • 前一节是按"名字"组织信息,这里反过来是按地址顺序把整幅映像铺开,告诉你每个段最终落在哪个地址、多大、是只读还是读写、要去哪里取初值。它是理解启动流程和判断 Flash/RAM 余量的核心。

    • 结构(典型的三层/三行标题结构):

      • 各行格式:

        行类型格式
        Load Region 头Base/Size/Max/ 属性 / 压缩后大小
        Execution Region 头Exec base(运行时地址) /Load base(初值存放地址) /Size/Max/ 属性
        明细行Exec Addr/Load Addr/Size/Type/Attr/Idx/E Section Name/Object
        • 这里的Max不是芯片手册里的容量,而是在 keil 中 Options for Target → Target 选项卡里给 IROM1/IARM1 声明的容量(Flash 0x40000 = 256 KB、RAM 0xC000 = 48 KB)。

      • 各行中各字段的含义:

        1. Load Region 与 Execution Region 的关系:

          • Load Region 是"烧进 Flash 的那一整块",Execution Region 是"运行时实际使用的区域"。
          • 一个 Load Region 可以包含多个 Execution Region,上图就是两个 ER:代码区ER_IROM1和数据区RW_IRAM1。
        2. Execution Region 中 Exec base、Load base 的含义:

          • Exec base 是运行时地址,表示该段代码或数据在运行时内存(如 RAM)中的实际使用起始地址。

          • Load base 是初值存放地址,表示该段代码或数据在非易失性存储器(如 Flash)中的物理存储起始地址。

          • 两者是否相等,直接决定了该段数据在启动时的行为、Flash 占用以及CPU 访问方式。具体区别如下:

            1. 当 Exec base == Load base 时:这意味着代码或数据直接在存储介质(通常是 Flash)中原地运行或读取,无需搬运。一般不占用运行时 RAM,典型段类型:.text(代码段)、.rodata(只读常量)、向量表等。
            2. 当 Exec base ≠ Load base 时:这意味着数据存储在 Flash 中,但必须在 RAM 中使用,需要启动代码进行搬运或初始化。除自身内容外,还需额外存储初始值镜像,必须预留对应大小的 RAM 空间用于运行时读写。典型段类型:.data(已初始化全局/静态变量)等。
            维度Exec == LoadExec ≠ Load
            物理位置Flash 原地使用Flash 存储 + RAM 运行
            启动耗时无额外开销需拷贝/初始化,增加启动时间
            Flash 消耗仅代码/常量本身代码/常量 + 初始值镜像
            RAM 消耗0(纯只读段)等于段大小
            可写性❌ 不可写(RO)✅ 可读写(RW)
            调试关注点确认 Flash 读取时序确认启动拷贝逻辑及 RAM 容量
          • 上图实例:

            • ER_IROM1 两者都是0x08000000→ 代码在 Flash 里原地执行,不需要搬运。
            • RW_IRAM1 的 Exec base 是0x20000000(RAM),Load base 是0x08010f94(Flash)→ 这个区域的初值先存在 Flash 里,启动时由__main的分散加载代码搬到 RAM。注意0x08010f94正好等于0x08000000 + 0x10f94,也就是代码区的结尾——Flash 里代码和数据初值是首尾相接的。
        3. 明细行的各列含义:

          列含义
          Exec Addr运行时的绝对地址,调试器里看到的就是它
          Load Addr初值的来源地址;与 Exec 相同表示原地执行,COMPRESSED表示初值在 Flash 里被压缩存放,-表示没有初值(纯零初始化)
          Size该段字节数
          TypeCode指令 /Data数据 /Zero零初始化数据 /PAD对齐填充
          AttrRO只读(占 Flash)/RW可读写(占 RAM)
          Idx段索引号,供符号表引用
          E Section Name段名,i.函数名/.text/.constdata/.data/.bss/RESET/STACK/HEAP
          Object所属目标文件,库成员形如c_w.l(__main.o)
          • 四种 Type:
            • Code:机器码
            • Data:有初值的数据。在ER_IROM1里是只读常量(.constdata/.conststring),在RW_IRAM1里是可读写变量(.data)
            • Zero:零初始化的.bss,只在 RAM 里占位,Flash 里不占空间
            • PAD:链接器为了满足对齐要求插入的填充
    • 用途:

      • 一眼判断 Flash/RAM 余量:拿Size和Max一比就知道还能加多少功能、够不够加 OTA 双备份。
      • 理解并验证启动流程:以后自己写分散加载文件(比如要把某个数组放到特定 RAM 段、或者加 bootloader 挪动 Flash 基址),改完就看这里 Exec/Load 是否符合预期。
      • 地址区间反查:手上有个裸地址,想知道它是什么,这一节是最快的入口。
  5. Image component sizes —— 按模块汇总 + 总量

    • Keil 配置:由 Options for Target → Listing 里的Linker Listing → Size Info勾选项生成。

    • 这一节主要用于按模块(源文件/库文件)维度统计固件的体积构成。

    • 结构(三段表 + 末尾总账):

      • 这一节全部是十进制字节数;

      • 五列的含义:

        列含义
        Code (inc. data)代码段大小。
        括号/右侧那个数是它的一部分,表示其中有多少字节是内联数据,不是额外开销。
        RO Data只读数据:.constdata、.conststring、字库、字符串常量
        RW Data有初值的可读写变量(.data),同时占 Flash(初值)和 RAM
        ZI Data零初始化数据(.bss),只占 RAM,完全不占 Flash
        DebugDWARF 调试信息体积,存在.axf里,不烧进芯片
      • 第三段库汇总表解析:

        库CodeRO DataZI是什么
        c_w.l9,35848896C 库:printf/scanf/ 字符串 / 内存拷贝
        fz_ws.l5,184160浮点运行库:ddiv、dmul、dsqrt、f2d…
        m_ws.l3,2981440数学库:主要就是pow(2,520)
      • 末尾总账解析:

    • 用途:

      • 判断"我的代码"和"库代码"的比例。
      • 一键定位体积热点:按Code列排个序,即可看出谁体积最大。
      • 查 RAM 的静态占用构成。
      • 做"改前 / 改后"对比。
      • 和Image Symbol Table分工配合:这一节按.o文件汇总,符号表按符号明细。

3)日常排查时怎么快速用

几个高频动作:

  1. 崩溃定位:
    • HardFault 时拿到 PC/LR,直接在Image Symbol Table里找"地址 ≤ PC 且最接近"的符号就是出错函数。
    • 也可以直接在 debug 页面查看。
  2. 查变量地址:
    • 搜变量名看地址区间。如果它是0x2000xxxx且落在ucHeap(0x200007e8–0x200057e8)范围内,说明它是动态分配的。
  3. 查库函数来源:
    • 搜refers to ... for 函数名,能追到调用链的第一环。
  4. 对比两次编译:
    • 只比Image component sizes里的 Object Totals 和最后三行总账,就能知道这次改动是省了还是涨了 Flash/RAM。
  5. 确认函数是否真的没用:
    • 出现在Removing Unused里 = 没被调用,可放心删源码。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询