ARM Cortex—M0+机器码文件分析方法

蔡伯峰+蒋建武+王宜怀



摘 要: MCU深层次应用开发需要开发人员深入了解机器码在MCU中的存储和执行机制,但机器码自身可读性差、相互关系不清晰等特点决定了对其阅读、查找、分析难度较大。针对这一情况,以采用ARM Cortex?M0+内核的KL25 MCU为蓝本,根据工程编译链接过程和链接脚本文件(.ld),分析机器码文件的生成机制和组织结构。在此基础上针对机器码文件中的中断向量表、初始化代码、函数、常变量、FLASH配置域等主要内容,结合机器码系列文件,给出了简明快捷实用的分析方法,为嵌入式开发人员优化及动态更新程序和数据、设计机器码下载软件等提供支撑,对其他内核机器码文件的分析有借鉴意义。
关键词: ARM Cortex?M0+; 机器码文件; KL25; 链接脚本
中图分类号: TN918.2?34; TP311 文献标识码: A 文章编号: 1004?373X(2017)14?0044?05
Abstract: MCU deep?level application development requires its developers to understand storage and enforcement mechanisms of machine codes in MCU, but the machine code itself has poor readability and unclear structure, thus it is difficult to be directly read, searched and analyzed. In response to this situation, using the ARM Cortex?M0+ KL25 MCU as a model, and according to engineering compiling linking process and linker script files (.ld) of MCU application project, the generative mechanism and structure of machine code file are analyzed. On this basis, a concise, efficient and practical analysis method is given in allusion to the interrupt vector table, initialization code, functions, constants, variables and Flash configuration domain. This is helpful for the embedded developers to update dynamically and optimize programs and data, design machine code download software, and analyze machine code files of other kernels.
Keywords: ARM Cortex?M0+; machine?code file; KL25; linker script
0 引 言
用C语言开发的MCU应用工程经过编译链接后生成机器码目标文件,正确全面深入地掌握机器码文件的分析方法是快速进行MCU深层次应用开发必备的技能。机器码文件要通过机器码下载软件下载到目标板上芯片的FLASH存储区中运行,下载软件一般需要开发人员自行设计,因为KDS(Kinetis Design Studio)等集成开发环境提供的下载方法在脱离开发环境后无法使用,不便于产品的批量生产;而在程序正常运行后,有时需要动态在线更新或升级部分程序和数据,如滤波算法程序、排序算法程序、系统参数等;嵌入式系统的存储资源与通用计算机系统相比,相对匮乏、速度较低,对实时性、可靠性要求较高[1],需要根据实际情况动态修改或优化程序和数据等。这一切都需要开发人员对机器码文件有充分的了解,能正确深入地分析机器码文件,了解程序数据的存储和执行机制、芯片复位启动过程、程序执行时间等芯片底层内容。但不同厂家不同内核的指令机器码存在差异,应用工程的机器码文件结构和组成也不相同,分析方法也不尽相同,目前有关ARM Cortex?M0+内核机器码文件的系统性分析研究很少。机器码自身可读性差、相互关系不清晰、不符合通常思维习惯等特点决定了由其组成的机器码文件阅读、理解和分析困难,为此,要有正确实用的分析机制和分析方法。
本文在深入剖析机器码文件的生成机制、组织结构的基础上,结合机器码系列文件,给出了简明快捷的分析方法。
1 机器码文件的生成
MCU应用工程在编译链接工程中会使用一些文件,并生成若干文件,只有了解这些文件的生成过程、相互关系、功能作用等内容才能全面准确地分析机器码文件。
1.1 MCU程序编译链接过程
KDS集成开发环境下,对任一用C语言开发的MCU应用工程,运行Cross ARM GCC编译器,会经过如图1所示的编译、汇编和链接等过程。
MCU应用工程往往包含若干源代码文件(.c)(也包含需要快速执行的汇编程序.S),每个源代码文件在编译、汇编后都会生成一个可重定位的二进制目标文件(.o)[2](本文称之为中间文件),再通过ld链接器根据链接脚本文件(.ld)的内部相关规则将这些中间文件链接组合成针对ARM CPU的.elf格式的可执行目标文件[3]。如果程序调用了静态库(.a)中的函数或变量,链接时还会包括相应的库文件。在编译链接过程中,还可同时生成机器码文件(.hex)、映像文件(.map)、列表文件(.lst)。
1.2 链接脚本文件的使用
链接脚本文件(.ld)提供给链接器控制链接过程,用于重定位代码并提供程序、数据在内存中的存储位置[4?5]。它规定如何将各个中间文件中的段(称为输入段)放入可执行目标文件中合适的段(称为输出段)中,并控制目标文件内各部分的地址分配。它主要由MEMORY,SECTIONS命令组成[6]。首先通过MEMORY命令划分FLASH和RAM存储空间可用资源区为多个存储区,并定义各存储区的起始地址及长度、设置读写或执行等属性。再通过SECTIONS命令定义一些可供应用程序使用的符号并赋值、包含在可执行目标文件中的各个输出段及装载到哪个存储区。在各个输出段中定义一些与该段相关的一些符号并赋值,同时确定该段应该由哪些中间文件的哪些输入段如何链接而成。
以 KL25_UART应用工程为例,该工程运行于本课题组自行设计的基于KL25(NXP公司的MKL25Z128VLK4)的开发板上,用于测试串口与PC机间通信情况。KL25[7]包含128 KB FLASH,16 KB SRAM。FLASH分成128扇区,每扇区1 024 KB。MEMORY和SECTIONS命令所定义的各存储区、包含的输出段及存储器空间分配情况如图2所示。共定义4个存储区:中断向量区“m_interrupts”、FLASH配置区“m_flash_config”、用户程序区“m_text”和数据区“m_text”。前3个区在FLASH(本文称为目标FLASH)中,数据区在SRAM中。其中,“m_text”区中的.text段定义如下:
.text:
{ . = ALIGN(4); /*4 B的边界对齐*/
*(.text) /*非函数代码*/
*(.text.*) /*函数代码*/
*(.rodata) /*字符串等*/
*(.rodata.*) /*数组常量等*/

} > m_text
链接脚本文件可由开发人员根据MCU应用工程的程序、数据所需空间大小及存放要求、链接要求等自行编写或由开发环境生成后修改。
1.3 机器码系列文件的生成
可执行目标文件(.elf)的生成依赖于链接脚本文件和各个中间文件。当源文件被编译成中间文件时,其中的各输入段并没有具体地址,中间文件中只是记录了程序中所使用的各个符号和各符号在输入段中的相对位置。只有当链接器根据链接脚本文件链接中间文件的各个输入段时,各段才会根据链接脚本文件中所分配的地址获得加载内存地址LMA和虚拟内存地址VMA[8],各个符号也才有了真实地址。再根据每一符号所对应的真实地址更新相应的指令,从而实现真正的函数调用和符号引用功能。通过这一重定位过程就可生成可执行目标文件(.elf)。
因为可执行目标文件是二进制文件,包含程序开始执行地址、程序头、输出段头、符号表、各个输出段、重定位信息以及调试和说明信息等并需专门工具软件打开,可读性差、分析不便,不是本文分析研究的对象。本文所要分析的机器码文件是指由可执行目标文件在KDS下经格式转换而生成的机器码文件(.hex)。它是由十六进制文本组成的ASCII码文件,已经包含可执行目标文件中的程序入口地址、各个输出段等主要内容,可读性和结构清晰度均较好,分析比较方便。而可执行目标文件中的其他内容也已经包含在KDS下生成的列表文件(.lst)和映像文件(.map)中。
映像文件提供了链接脚本文件中所定义的各个符号的地址值和各个输入输出段的长度及LMA,VMA地址映射信息。列表文件提供了程序开始执行地址、程序头、输出段头、符号表,以及“C代码、汇编代码、机器码及存储地址”的对应关系及LMA、常量数据等内容,用于程序分析。
2 机器码文件组织结构分析
机器码文件(.hex)存放了中断向量表、FLASH配置域信息、程序机器码、常量、初始化了的静态变量和全局变量、程序入口地址等内容,可直接使用串行线调试(Serial Wire Debug,SWD)技术通过下载程序下载到目标MCU中运行,比通过USBDM外部调试接口下载.elf文件方便、快捷。
机器码文件是由若干行符合Intel HEX文件格式的十六进制文本所构成的ASCII文本文件,文件每一行均是一条HEX记录,记录分成6种不同类型,但记录格式均相同,如表1所示。
本文以KL25_UART应用工程的KL25_UART.hex为例进行分析,文件内容如图3所示。记录中的偏移量表示当前记录中“数据/信息区”的有效数据在装载到目标FLASH中时的偏移地址,也只有这些数据才会被装载到目标FLASH中,它们在FLASH中以小端方式(KL25 MCU内部采用的存储方式[4],即字的低字节存储在低地址中)依次存放。
图3中“数据/信息区”的内容简要分析如下:
(1) 中断向量表。存放于第1~12行的记录中。KL25有48个中断向量,每个中断向量占4 B,每条记录可存放4个中断向量,共占用12条记录。装载到目标FLASH中时,中断向量表将会从偏移地址是“0x0000”处开始存放,占用(4×12)×4=192 B。
(2) FLASH配置域。存放于第13行记录中, 固定长度为16 B,在目标FLASH中的偏移地址是“0x0400”。
(3) 程序和常量。存放于第14~209行中,在目标FLASH中的偏移地址是“0x0800”。
(4) 静态变量及全局变量。紧接着上述机器码程序和常量后是已初始化为非0的静态变量及全局變量,数据长度因应用工程而异。它们也会装载到目标FLASH中,断电后数据不会丢失。复位启动时初始化程序会将它们拷贝到RAM中。
(5) 其他内容。机器码文件倒数第2行固定为存放程序开始执行地址的记录,记录类型为“0x03”,最后一行固定为文件结束记录,记录类型为“0x01”。
3 机器码文件内容分析
虽然机器码文件本身提供的信息量有限,但结合.ld,.lst、,map等系列文件提供的编译链接和装载等信息,就可以全面分析该文件,以深入了解中断向量表、芯片复位启动过程、FLASH加密解密机制、代码及数据的链接方法、定位、装载和运行地址、空间占用情况、执行时间、符号的处理等内容。
3.1 中断向量表定位与分析
中断向量表位于机器码文件的开始处,将直接装载到目标FLASH的0x00000000地址处。因为根据芯片内部工作机制,中断向量表应存放于目标FLASH的0x00000000地址处即第0扇区,当MCU上电启动或复位时,将从该处取出4 B的第一表项内容赋给堆栈指针SP,完成堆栈指针初始化工作,取出4 B的第二表项内容赋给程序计数器PC,以便转到相应地址处执行芯片复位处理代码。中断向量表占用的空间依赖于具体MCU的中断源数量,就目前市场上的MCU而言,最多占用1个扇区,在扇区大小是1 024 KB的情况下,1个扇区可以存放=256个中断向量,即使扇区大小是512 KB也可以存放128个中断向量。
中断向量表中各个中断向量对应的是符号值、0(表示未用)或中断处理函数名或弱函数名,在编译链接并重定位时,链接器会将这些符号、函数名、弱函数名更新为相应的符号值、函数、弱函数或对应的强函数的虚拟内存地址VMA(即运行地址)。
中断向量表一般在芯片启动文件(.s)中定义,启动文件的内容结构[9]如图4所示。
在KL25芯片启动文件定义的中断向量表中,序号为0的中断向量是__StackTop(栈顶地址符号),序号为1的中断向量是Reset_Handler(芯片复位处理函数),序号为29的中断向量是UART1_IRQHandler弱函数,并在中断处理源文件中定义了对应的强函数。图3中序号为0的中断向量4字节组“00 30 00 20”实际表示的数据是“20 00 30 00”,正是_StackTop的值。序号为1的中断向量4字节组“01 08 00 00”→“00 00 08 01”,其内容减1后的值正是Reset_Handler函数的VMA,也是程序开始执行地址。内容减1是因为KL25的Cortex?M0+处理器的指令地址为半字对齐,即PC寄存器的最低位必须始终为0,但程序在跳转时,PC的最低位必须被置为1,以表明内核处于执行16位Thumb指令的Thumb状态,而非执行32位指令的ARM状态[10]。序号为29的中断向量4字节组“ED 08 00 00”→“00 00 08 ED”,其内容减1后的值正是UART1_IRQHandler强函数的VMA。从KL25_UART.map或intflash.ld文件中可以查到_StackTop和Reset_Handler,UART1_IRQHandler等函数的VMA正是这些值。
进而根据中断处理函数的VMA就可在机器码文件中相应的偏移地址(=中断处理函数的VMA-0x00000000)处查找到其机器码。
3.2 FLASH配置域定位与分析
FLASH配置域在机器码文件中紧接着中断向量表后存放,记录中的偏移量对应的是目标FLASH第一扇区的开始地址。由于FLASH保护和加解密功能需擦除整个第一扇区,所以该扇区剩余的空间保留着,当不用FLASH保护和加解密功能时可以存放機器码数据。
FLASH配置域保存了默认的FLASH保护设定及加密属性,用于对FLASH模块进行访问控制,以防止某些FLASH存储区域受意外擦除、写入,或通过外部调试接口(如JTAG,SWD和USBDM等)访问FLASH存储器,但加密后通过外部调试接口仍可进行大规模的擦除操作,只是无法执行读取或写入FLASH的指令。在芯片复位时,FLASH模块读取FLASH配置域相应字段信息对FLASH保护寄存器FPROT 和加密寄存器FSEC进行初始化。FLASH配置域信息如表2所示,相关寄存器内容可见文献[7]。
开发者可直接对FLASH配置域字段进行改写(先擦除整个扇区,再复位芯片后写入)以启用/关闭加密功能,或使用封装的加密解密函数来对配置域进行设置,但前者操作简单直接且更为安全。
3.3 MCU初始化代码定位与分析
MCU应用程序机器码区包括芯片初始化代码、各个函数代码、常量、初始化为非0的静态和全局变量等,位于机器码文件中紧接着FLASH配置域后,首条记录中的偏移量对应的是目标FLASH第二扇区的开始地址。应用工程不同,记录条数和机器码长度也不相同。由于中断向量表第二表项存放的是MCU初始化函数Reset_Handler的VMA,所以只要计算出偏移地址(=Reset_Handler函数的VMA-0x00000000),就可方便地在机器码文件中查找到Reset_Handler函数的机器码。图3中Reset_Handler函数的机器码从记录中偏移量是0x0800处(第14行记录)开始存放。通过查看KL25_UART.lst文件可知,该处的机器码“72 B6 00 F0…”→“B6 72 F0 00…”对应的是芯片启动文件的Reset_Handler函数,共占0x44 B,初始化过程见图4。弄清MCU初始化过程后,在实际应用中,就可根据是否要启动看门狗、是否要复制中断向量表至RAM、是否要清零未初始化.bss段、启动时间、是否添加监控程序等要求来编写或修改、优化相关代码,以提高系统的稳定性、可靠性、灵活性。监控程序可添加在进入main函数之前的位置处,以判断是否有要写入机器码到FLASH中的命令,若有则完成写入,这是更新用户程序的一种简便方法。
3.4 函数和功能段的定位与分析
在C函数代码编译成汇编指令时,编译器自动在函数开始处添加push指令以保存函数被调用时的现场,如push{r7, lr},函数结束处自动添加pop指令以恢复现场,如pop{r7, pc}。其机器码高位字节分别为B5,BD,低位字节与指令中使用的寄存器相关,在存储时机器码是半字对齐并采用小端格式。这样就可根据B5,BD来定位函数。
(1) 用户函数及ISR的定位。若某一存储单元的内容为“B5”且其偏移地址的最低位为1,则从该存储单元的上一单元开始表示某个函数开始。其后第一个“BD”所在的存储单元(其偏移地址的最低位必须为1)表示该函数结束。找出函数机器码后就可直接计算其占用的存储空间字节数,更加便捷的方法是查询.lst文件中的符号表。
(2) 某个特定函数的定位。由于机器码文件中没有函数名,所以要定位特定名称的某个函数需要借助于.lst文件进行,即根据.lst文件中提供的“C代码、汇编代码、机器码及存储地址”部分的内容,找到该函数的前几个机器码字节,从而就可在机器码文件中定位到函数的开始部分。
(3) 相邻存储的两函数之间的数据归属。如果函数中使用了字符串常数、数组常量、静态和全局变量等数据,那么它们的VMA地址将会存放到该函数最后。函数中的指令先通过相对寻址方式,从PC值与指令中给出的偏移量相加后形成的操作数地址所指向的存储单元中取出VMA地址,再通过获得的VMA地址访问到所需数据。其余常量直接转换成指令中的立即数存放于机器码中。
(4) 功能段的定位。特定的C语言功能段代码对应的机器码可以直接在.lst文件中查找到,据此就可在机器码文件中定位,并可很方便地计算其占用的存储空间字节数。计算执行时间时可根据对应的汇编指令及内核参考手册上提供的指令执行周期进行精确计算[11]。
定位到函数或功能段后就可修改其代码,或动态在线更新目标FLASH中的相应代码。要实现动态更新功能,可以在编程时在main的永久循环中添加监控代码,以判断是否有要写入机器码到FLASH中的命令,若有则完成写入。
3.5 常量、静态和全局变量的定位与分析
字符串常数、数组常量等在机器码文件中的存放位置取决于.ld文件中对.text段的定义,在本文中,它们集中存放于机器码的后面,而其余常量将其值以立即数形式直接存放于指令中。要确定某个特定常量的存放位置,可从.lst文件的最后内容中先查找到该常量的存放地址,再到机器码文件中精确定位。
根据链接脚本文件,未初始化或初始化为0的静态和全局变量存放于RAM中.data段后的.bss内存区域中,但.bss段不占机器码文件空间和目标FLASH空间,只占运行时的RAM空间。而.data段中包含的初始化为非0的静态和全局变量占用机器码文件空间、目标FLASH空间和运行时的RAM空间,断电后初始化数据能保存。
在机器码文件中,初始化为非0的静态和全局变量紧接着机器码和常量的后面存放。从.lst文件中可以查看到它们的LMA和占用空间,据此可在机器码文件中精确定位到特定的变量,并可计算出FLASH剩余空间的起始地址。定位到常量、静态和全局变量后,可根据需要修改其值或通过监控程序动态在线更新目标FLASH中的相应值。FLASH剩余空间可用于存放其他数据,如系统参数等,同样可实现动态在线更新。
4 结 语
本文在对ARM Cortex?M0+机器码文件的生成机制、组织结构和内容进行深入剖析的基础上,结合机器码系列文件提供的信息,系统地给出了中断向量表、FLASH配置域、初始化代码、函数和常量、全局或静态变量等内容在机器码文件中的定位与分析方法,使得对机器码文件的分析变得简明快捷。开发人员可以将分析结果应用于MCU深层次应用开发工作中,如编写或修改链接脚本文件及芯片复位启动程序、精确计算代码执行时间和占用的存储空间、添加监控代码、优化及动态更新程序和数据、设计机器码下载软件等,以充分利用MCU有限的资源,提高系统的可靠性、稳定性、健壮性。
参考文献
[1] 廉玉龙,史峥,李春强,等.基于C.SKY CPU的地址立即數编译优化方法[J].计算机工程,2016,42(1):46?50.
[2] 胡宗棠,王宜怀,沈忱.面向MC1321X的低开销无线重编程机制的研究与设计[J].计算机应用与软件,2014,31(12):272?277.
[3] 王宜怀,朱仕浪,郭芸.嵌入式技术基础与实践[M].3版.北京:清华大学出版社,2013:86?87.
[4] STANKIC M, CETIC N, KRNJETIN M, et al. Graphical tool for generating linker configuration files in embedded systems [C]// Proceedings of 2011 19th Telecommunications Forum. [S.l.: s.n.], 2011: 1550?1553.
[5] ARM. Armlink user guide [DB/OL]. (2016?03?31) [2016?05?15]. http://infocenter.arm.com/help/.
[6] Free Software Foundation. Documentation for binutils 2.26 [DB/OL]. (2016?01?25) [2016?05?15]. https://sourceware.org/binutils/docs?2.26/ld/.
[7] NXP. KL25 Sub?family reference manual [DB/OL]. (2014?09?01) [2016?05?15]. http://www.nxp.com/.
[8] 胡敏,卢永江,刘兵.基于CK810处理器的汇编链接时优化[J].计算机工程,2014,40(11):250?254.
[9] NXP. Kinetis assembler reference manual [DB/OL]. (2014?02?01) [2016?05?15]. http://www.nxp.com/.
[10] JOESEP Yiu.ARM?Cortex?M0权威指南[M].吴常玉,译.北京:清华大学出版社,2013:52?53.
[11] ARM. Cortex?M0+ Processor technical reference manual [DB/OL]. (2012?12?16) [2016?05?15]. http://infocenter.arm.