Keil Listing选项卡汇编/链接列表生成与代码分析实战


Keil MDK-ARM的Listing选项卡是嵌入式开发中代码分析、性能优化、问题定位的核心工具,通过生成汇编列表、链接列表等文件,可直观查看代码编译后的汇编指令、内存分布、符号表等关键信息。本文从Listing选项卡配置、列表文件解读、实战分析三个维度,手把手讲解如何生成并利用列表文件优化代码、定位问题,零基础也能快速掌握。

一、Listing选项卡核心作用

在嵌入式开发中,直接阅读C代码无法精准掌握程序运行的底层逻辑(如指令执行周期、内存占用、函数调用栈),Listing选项卡生成的列表文件可解决这些问题:

  1. 汇编列表:将C代码与编译后的汇编指令一一对应,分析指令执行效率、定位代码优化点;
  2. 链接列表:展示程序各段(代码段.text、数据段.data、bss段)的内存分布、符号地址、全局变量占用空间;
  3. 交叉引用列表:查看函数、变量的调用/引用关系,定位未使用的代码/变量;
  4. 模块映射:展示各源文件编译后的大小、内存占用,辅助内存资源分配。

二、前期准备

  • 软件环境:Keil MDK-ARM V5.36(需安装对应芯片器件库,如STM32F103);
  • 工程准备:已有可编译的STM32工程(本文以STM32F103C8T6工程为例);
  • 基础知识:熟悉ARM汇编指令基础、Keil工程编译流程、内存分段(.text/.data/.bss)概念。

三、Listing选项卡配置(生成列表文件)

1. 打开Listing配置界面

  • 打开Keil工程,点击魔法棒图标(Options for Target);
  • 切换到Listing选项卡,进入配置界面(核心配置项均在此处)。

2. 核心配置项详解

(1)输出路径与文件名

  • Output Path:设置列表文件保存路径(建议与工程输出目录一致,如./Output/Listing);
  • Name of Executable:列表文件前缀(默认与工程名一致,无需修改)。

(2)汇编列表生成(最常用)

勾选以下选项,生成C代码与汇编指令对应的列表文件:

  • Assembly Listing:启用汇编列表生成;
  • C Compiler Listing
    • Source:输出C源代码(必选,便于对比C代码与汇编);
    • Assembly:输出编译后的汇编指令(必选);
    • Machine Code:输出机器码(可选,用于分析指令二进制值);
  • Assembler Listing:若工程包含纯汇编文件(.s),勾选后生成其汇编列表。

(3)链接列表生成

勾选Linker Listing,并启用以下子项:

  • Memory Map:输出内存映射(核心,展示各段内存分布);
  • Symbol Table:输出符号表(展示函数、全局变量的地址与大小);
  • Cross Reference:输出交叉引用表(展示函数/变量的调用关系);
  • Module Map:输出模块映射(展示各源文件编译后的大小)。

(4)其他实用配置

  • Create Batch File:生成批处理文件(可选,便于批量生成列表);
  • Use ELF/DWARF for Debugging:调试时关联列表文件(默认勾选)。

3. 生成列表文件

配置完成后,点击OK保存,重新编译工程(Build/Rebuild),Keil会在指定Output Path下生成两类核心文件:

  • 汇编列表文件:工程名.lst(每个C文件对应一个.lst,如main.lst);
  • 链接列表文件:工程名.m51(全局链接列表,包含内存映射、符号表等)。

四、实战1:汇编列表文件解读(分析代码执行效率)

汇编列表文件是C代码编译后的“底层翻译稿”,以main.lst为例,解读核心内容:

1. 列表文件结构

; *** main.c ***
1  #include "stm32f10x.h"
2  
3  void delay_ms(u32 ms)
4  {
5      u32 i,j;
6      for(i=0; i<ms; i++)
7          for(j=0; j<7200; j++);
8  }
9  
10 int main(void)
11 {
12     GPIO_InitTypeDef GPIO_InitStructure;
13     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
14     
15     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
16     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
17     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
18     GPIO_Init(GPIOA, &GPIO_InitStructure);
19     
20     while(1)
21     {
22         GPIO_SetBits(GPIOA, GPIO_Pin_0);
23         delay_ms(1000);
24         GPIO_ResetBits(GPIOA, GPIO_Pin_0);
25         delay_ms(1000);
26     }
27 }

; 编译后的汇编指令(关键片段)
main:
0x08000100  B580      PUSH     {r7,lr}
0x08000102  B082      SUB      sp,sp,#0x08
0x08000104  AF00      ADD      r7,sp,#0x00
...
; delay_ms函数汇编
delay_ms:
0x08000080  B580      PUSH     {r7,lr}
0x08000082  B081      SUB      sp,sp,#0x04
0x08000084  AF00      ADD      r7,sp,#0x00
0x08000086  6078      STR      r0,[r7,#0x00]
0x08000088  2000      MOVS     r0,#0x00
0x0800008A  6038      STR      r0,[r7,#0x04]
L0001:
0x0800008C  687B      LDR      r3,[r7,#0x00]
0x0800008E  683A      LDR      r2,[r7,#0x04]
0x08000090  429A      CMP      r2,r3
0x08000092  D20A      BCC      L0002
0x08000094  3704      SUBS     r7,#0x04
0x08000096  46BD      MOV      sp,r7
0x08000098  BD80      POP      {r7,pc}
L0002:
0x0800009A  2000      MOVS     r0,#0x00
0x0800009C  60F8      STR      r0,[r7,#0x08]
L0003:
0x0800009E  68FA      LDR      r2,[r7,#0x08]
0x080000A0  F44F7200  MOV.W    r2,#0x1C20  ; 7200
0x080000A4  429A      CMP      r2,r3
0x080000A6  D206      BCC      L0004
0x080000A8  683B      LDR      r3,[r7,#0x04]
0x080000AA  3301      ADDS     r3,#0x01
0x080000AC  603B      STR      r3,[r7,#0x04]
0x080000AE  E7F6      B        L0001
L0004:
0x080000B0  68FB      LDR      r3,[r7,#0x08]
0x080000B2  3301      ADDS     r3,#0x01
0x080000B4  60FB      STR      r3,[r7,#0x08]
0x080000B6  E7F8      B        L0003

2. 关键信息解读

  • 地址列(0x08000100):指令在Flash中的存储地址(STM32F103默认Flash起始地址0x08000000);
  • 机器码列(B580):指令对应的二进制值,可用于烧录/反汇编验证;
  • 汇编指令列(PUSH {r7,lr}):C代码对应的ARM指令,如delay_ms函数的双层循环对应L0001/L0002/L0003/L0004标签的循环指令;
  • 行号对应:C代码行号(如第6行for(i=0; i<ms; i++))与汇编指令一一对应,可精准定位每行C代码的汇编实现。

3. 实战分析:优化延时函数

从汇编列表可发现:

  • 原始delay_ms函数采用双层空循环,指令执行周期固定,但占用大量Flash(循环指令多);
  • 优化方向:改用定时器延时(减少指令数),或调整循环次数(精准匹配ms延时)。
    通过对比优化前后的汇编列表,可直观看到指令数减少、执行效率提升。

五、实战2:链接列表文件(.m51)解读(内存与符号分析)

链接列表文件(.m51)是工程链接后的全局分析文件,核心关注内存映射、符号表、模块大小三部分。

1. 内存映射(Memory Map)

===============================================================================
Memory Map of the image
===============================================================================
Image Entry point : 0x08000000

/* Code Segment */
Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00001000, Max: 0x00020000, ABSOLUTE)
Base Addr    Size        Type        Attr      Idx    E Section Name        Object

0x08000000   0x00000004   Code        RO            5    .isr_vector        startup_stm32f10x_md.o
0x08000004   0x00000080   Code        RO            6    .text               delay.o
0x08000084   0x0000007C   Code        RO            7    .text               main.o
0x08000100   0x00000000   Code        RO            8    .text               stm32f10x_gpio.o
...

/* Data Segment */
Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000020, Max: 0x00005000, ABSOLUTE)
Base Addr    Size        Type        Attr      Idx    E Section Name        Object

0x20000000   0x00000004   Data        RW            1    .data               main.o
0x20000004   0x0000001C   Zero        RW            2    .bss                main.o
...

解读:

  • ER_IROM1:Flash存储区域(RO段,只读),存放代码段(.text)、常量(.rodata);
  • RW_IRAM1:SRAM存储区域(RW段,可读写),存放数据段(.data,初始化全局变量)、bss段(未初始化全局变量);
  • Base Addr:段起始地址,Size:段大小(如main.o的.text段占用0x7C字节);
  • 关键检查点:Flash/SRAM占用是否超过芯片容量(如STM32F103C8T6 Flash=64KB,SRAM=20KB)。

2. 符号表(Symbol Table)

===============================================================================
Symbol Table
===============================================================================
Symbol Name                            Value      Type    Size  Object

__initial_sp                           0x20000200  Number    0  startup_stm32f10x_md.o
Reset_Handler                          0x08000004  Function  0  startup_stm32f10x_md.o
delay_ms                               0x08000080  Function  0x80  delay.o
main                                   0x08000084  Function  0x7C  main.o
GPIO_Init                              0x08000100  Function  0x0  stm32f10x_gpio.o
i                                      0x20000000  Local     0x4  main.o
j                                      0x20000004  Local     0x4  main.o

解读:

  • Symbol Name:函数/变量名(如delay_msmain);
  • Value:符号地址(如main函数起始地址0x08000084);
  • TypeFunction(函数)、Local(局部变量)、Global(全局变量);
  • Size:符号占用大小(如delay_ms函数占用0x80字节);
  • 实战应用:定位函数/变量的内存地址,调试时快速查找指令位置。

3. 模块映射(Module Map)

===============================================================================
Module Map
===============================================================================
Module Name                            Code Size   RO Data  RW Data  ZI Data

startup_stm32f10x_md.o                 0x00000004  0x00000000  0x00000000  0x00000000
delay.o                                0x00000080  0x00000000  0x00000000  0x00000000
main.o                                 0x0000007C  0x00000000  0x00000004  0x0000001C
stm32f10x_gpio.o                       0x00000000  0x00000000  0x00000000  0x00000000
...
Total                                  0x00000100  0x00000000  0x00000004  0x0000001C

解读:

  • 展示每个源文件(.o)编译后的代码大小、RO/RW/ZI数据大小;
  • 实战应用:定位“代码膨胀”源头(如某模块Code Size过大),优化冗余代码。

4. 交叉引用表(Cross Reference)

===============================================================================
Cross Reference Table
===============================================================================
Symbol Name                            Referenced by
-------------------------------------------------------------------------------
delay_ms                               main.o (main:0x080000AC)
GPIO_Init                              main.o (main:0x08000098)
GPIO_SetBits                           main.o (main:0x080000A0)
GPIO_ResetBits                         main.o (main:0x080000A4)

解读:

  • 展示函数/变量被哪些文件/位置引用(如delay_msmain.o的0x080000AC地址调用);
  • 实战应用:定位未使用的函数(无引用记录),删除冗余代码减少Flash占用。

六、实战3:Listing文件的典型应用场景

1. 定位代码执行效率问题

  • 场景:某函数执行耗时过长,需优化;
  • 方法:查看该函数的汇编列表,统计指令执行周期(ARM指令多数为1周期),替换低效指令(如多次内存访问改为寄存器操作)。

2. 排查内存溢出

  • 场景:程序运行时死机,怀疑栈溢出;
  • 方法:查看链接列表的__initial_sp(栈起始地址)和栈大小配置,结合全局变量/局部变量的内存占用,判断栈空间是否不足。

3. 验证代码烧录地址

  • 场景:程序下载后无法运行,怀疑地址配置错误;
  • 方法:查看链接列表的Image Entry point(入口地址)和.isr_vector(中断向量表)地址,确认与芯片Flash起始地址匹配。

4. 优化Flash/SRAM占用

  • 场景:工程接近Flash/SRAM容量上限;
  • 方法:
    1. 从模块映射中找到占用最大的模块,优化其代码/变量;
    2. 从符号表中删除未使用的全局变量/函数;
    3. 将常量移至Flash(.rodata段),减少SRAM占用。

七、常见问题与解决

1. 生成的列表文件为空

  • 原因:未勾选Assembly Listing/Linker Listing,或工程编译失败;
  • 解决:确认配置项勾选,重新编译工程(Rebuild),查看编译日志是否有错误。

2. 汇编列表无C代码对应

  • 原因:未勾选C Compiler Listing下的Source选项;
  • 解决:重新配置Listing选项卡,勾选Source后重新编译。

3. 链接列表无内存映射

  • 原因:未勾选Linker Listing下的Memory Map
  • 解决:勾选该选项,确保链接器生成内存映射信息。

4. 列表文件路径找不到

  • 原因:Output Path配置错误(如路径不存在);
  • 解决:确认路径存在(手动创建),或使用相对路径(如./Listing)。

八、进阶技巧

1. 自定义列表文件内容

  • Listing选项卡的Misc Controls中添加编译选项:
    • -al:显示行号;
    • -ah:显示源文件路径;
    • -ap:显示宏展开;
      例如:输入-al -ah,列表文件会增加行号和源文件路径信息。

2. 结合调试器关联列表文件

  • 在Keil调试模式下,点击View->Disassembly Window,可将汇编列表与调试反汇编对比,精准定位指令执行位置。

3. 批量分析列表文件

  • 将列表文件导出为文本格式,用Python/Excel分析:
    • 统计各模块代码大小;
    • 筛选大尺寸函数;
    • 检查全局变量占用。
posted @ 2025-12-15 20:43  哈希技术  阅读(2)  评论(0)    收藏  举报