Keil Listing选项卡汇编/链接列表生成与代码分析实战
目录
Keil MDK-ARM的Listing选项卡是嵌入式开发中代码分析、性能优化、问题定位的核心工具,通过生成汇编列表、链接列表等文件,可直观查看代码编译后的汇编指令、内存分布、符号表等关键信息。本文从Listing选项卡配置、列表文件解读、实战分析三个维度,手把手讲解如何生成并利用列表文件优化代码、定位问题,零基础也能快速掌握。
一、Listing选项卡核心作用
在嵌入式开发中,直接阅读C代码无法精准掌握程序运行的底层逻辑(如指令执行周期、内存占用、函数调用栈),Listing选项卡生成的列表文件可解决这些问题:
- 汇编列表:将C代码与编译后的汇编指令一一对应,分析指令执行效率、定位代码优化点;
- 链接列表:展示程序各段(代码段.text、数据段.data、bss段)的内存分布、符号地址、全局变量占用空间;
- 交叉引用列表:查看函数、变量的调用/引用关系,定位未使用的代码/变量;
- 模块映射:展示各源文件编译后的大小、内存占用,辅助内存资源分配。
二、前期准备
- 软件环境: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_ms、main);Value:符号地址(如main函数起始地址0x08000084);Type:Function(函数)、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_ms被main.o的0x080000AC地址调用); - 实战应用:定位未使用的函数(无引用记录),删除冗余代码减少Flash占用。
六、实战3:Listing文件的典型应用场景
1. 定位代码执行效率问题
- 场景:某函数执行耗时过长,需优化;
- 方法:查看该函数的汇编列表,统计指令执行周期(ARM指令多数为1周期),替换低效指令(如多次内存访问改为寄存器操作)。
2. 排查内存溢出
- 场景:程序运行时死机,怀疑栈溢出;
- 方法:查看链接列表的
__initial_sp(栈起始地址)和栈大小配置,结合全局变量/局部变量的内存占用,判断栈空间是否不足。
3. 验证代码烧录地址
- 场景:程序下载后无法运行,怀疑地址配置错误;
- 方法:查看链接列表的
Image Entry point(入口地址)和.isr_vector(中断向量表)地址,确认与芯片Flash起始地址匹配。
4. 优化Flash/SRAM占用
- 场景:工程接近Flash/SRAM容量上限;
- 方法:
- 从模块映射中找到占用最大的模块,优化其代码/变量;
- 从符号表中删除未使用的全局变量/函数;
- 将常量移至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分析:
- 统计各模块代码大小;
- 筛选大尺寸函数;
- 检查全局变量占用。

浙公网安备 33010602011771号