ARM Cortex-M底层技术(3)—编译内核的原理及其应用
概述:
当前开发中,我使用的Keil开发工具较多(keil526),故以keil为例进行介绍,其他开发环境大同小异。
1. 编译链接的定义
不管我们编写的代码有多么简单,都必须经过「编译 --> 链接」的过程才能生成可执行文件:
- 编译就是将我们编写的源代码“翻译”成计算机可以识别的二进制格式,它们以目标文件的形式存在;
- 链接就是一个“打包”的过程,它将所有的目标文件以及系统组件组合成一个可执行文件。
抛开嵌入式而言,C语言的编译器有很多种,不同的平台下有不同的编译器,例如:
- Windows 下常用的是微软开发的 Visual C++,它被集成在 Visual Studio 中,一般不单独使用;
- Linux 下常用的是 GUN 组织开发的GCC,很多 Linux 发行版都自带 GCC;
- Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,后来由于 GCC 的不配合才改为 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加强大)。
意思就是说,在windows平台下进行开发时,我们选用的开发工具大部分是帮我们集成了一些编译器,只需要进行界面配置就可以了。这里我要最好是知道Keil开发环境中,这些编译链接工具是怎么使用的。
keil中编译链接如下,下面会对编译连接器进行解释:
2. Keil的编译链接
在第一讲中提过,当我们利用Keil进行编译一个工程时,下面的输出框中的内容是这样的:
Compiler编译器,可以看到该编译器在我们电脑中的D盘D:\Keil_v526\ARM\ARMCC\bin,如下图所示:
armar 是用于把.o 文件打包成 lib 文件,armasm 编译汇编文件 ,armcc 编译 c/c++文件 ,armlink 链接对象文件 ,fromelf 生成下载格式文件,它根据 axf 映像文件转化成 hex 文件,并列出编译过程出现的错误(Error)和警告(Warning)数量 。调用这些编译工具,需要用到Windows的“命令行提示符工具”,为了让命令行方便地找到这些工具,我们先把工具链的目录添加到系统的环境变量中。
2.1 添加环境变量
Win7 系统为例添加工具链的路径到 PATH 环境变量 。
(1)右键电脑系统的“计算机图标”,在弹出的菜单中选择“属性” ,如下图
(2)在弹出的属性页面依次点击“高级系统设置” ->“环境变量”,在用户变量一栏中找到名为“PATH”的变量,若没有该变量,则新建一个。编辑“PATH”变量,在它的变量值中输入工具链的路径,如本机的是“;D:\Keil_v526\ARM\ARMCC\bin”,注意要使用“分号;”让它与其它路径分隔开(英文分号),输入完毕后依次点确定,如下图
(3) 打开 Windows 的命令行,点击系统的“开始菜单”,在搜索框输入“cmd”,在搜索结果中点击“cmd.exe”即可打开命令行,见图
(4)在弹出的命令行窗口中输入“fromelf”回车,若窗口打印出 formelf 的帮助说明,那么路径正常,就可以开始后面的工作了;若提示“不是内部名外部命令,也不是可运行的程序…”信息, 说明路径不对,请重新配置环境变量,并确认该工作目录下有编译工具链。
这个过程本质就是让命令行通过“PATH”路径找到“fromelf.exe”程序运行,默认运行“fromelf.exe”时它会输出自己的帮助信息,这就是工具链的调用过程, Keil本质上也是如此调用工具链的,只是它集成为 GUI(界面),相对于命令行对用户更友好,毕竟上述配置环境变量的过程已经让新手烦躁了。解释一下,这个cmd框中的内容怎么和Keil对应起来。fromelf 可根据 axf 文件生成 hex、 bin 文件, hex和 bin 文件是大多数下载器支持的下载文件格式 。例如如果我们想利用 fromelf 生成 bin 文件,可以在 MDK的“Option for Target->User”页中添加调用 fromelf 的指令,如下图
还有链接器的配置界面如下:
那么这些东西有什么用呢?来看一下这段代码:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main LDR r0, =errorfunc
BLX r0
LDR r0, =__main
BX r0
ENDP
启动代码中的Reset_Handler代码,大家可以一眼看出来这段代码是错误的,其中红色字体的errorfunc是我故意填在这里的一个不存在的函数,编译必然报错,那么我们看看编译器是怎么报错的?
重点在途中蓝色底纹的部分,注意第一个单词:assembling……,然后后面跟着出错信息,没错,这是一个汇编器错误;再看下面,我再代码里面给出一个错误(我在main.c里面删掉了一个头文件):
看蓝色底纹部分,注意第一个单词:compiling……,然后后面跟着出错信息,没错,这是一个编译器错误,跟上面的不同,这是编译器报错,我们再看下面一个错误,代码如下
int main(void)
{
char ch; /* Init board hardware. */
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH); BOARD_InitPins();
BOARD_BootClockFROHF48M();
BOARD_InitDebugConsole(); PRINTF("hello world.\r\n"); while ()
{
ch = errorfunc();
PUTCHAR(ch);
}
}
看蓝色底纹部分,注意第一个单词:linking……,然后后面跟着出错信息,这次是一个链接器错误!
大家经常说的编译器报错,其实是几个不同的东西再报错,编译器、汇编器、链接器都会报错,那你可能会问,知道这个有啥用呢?当然是有用的,比如汇编器报错,基本跟C语言没关系,基本可以断定是汇编语言语法错误或者是嵌入C语言的汇编语言出错(在C中嵌入汇编是一种非常有效的编程手段);如果是链接器报错,那就基本跟C语言语法无关,不是你的C语法上出错,很可能是你调用了不存在的函数或者链接器脚本写错了,或者使用了不存在的标号Symbol,或者没有包含头文件.h;而只有编译器报错,才总是你的C写的有问题.
3. 工作报表.map文件
真正有用的链接器描述文件“*.map”非常有用(编译链接后,双击工程列表下的第二个文件夹可以打开,变成灰色的那个)
“*.map”绝对是你的“核心员工”的工作报表,也是最复杂的一个。它主要包含交叉链接信息,查看该文件可以了解工程中各种符号之间的引用以及整个工程的 Code、 RO-data、 RW-data 以及 ZI-data 的详细及汇总信息。它的内容中主要包含了“节区的跨文件引用”、“删除无用节区”、“符号映像表”、“存储器映像索引”以及“映像组件大小”。
3.1 节区的跨文件引用
Section Cross References startup_stm32f10x_hd.o(RESET) refers to startup_stm32f10x_hd.o(STACK) for __initial_sp
startup_stm32f10x_hd.o(RESET) refers to startup_stm32f10x_hd.o(.text) for Reset_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.NMI_Handler) for NMI_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.HardFault_Handler) for HardFault_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.MemManage_Handler) for MemManage_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.BusFault_Handler) for BusFault_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.UsageFault_Handler) for UsageFault_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.SVC_Handler) for SVC_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.DebugMon_Handler) for DebugMon_Handler
startup_stm32f10x_hd.o(RESET) refers to stm32f10x_it.o(i.PendSV_Handler) for PendSV_Handler ......(省略)
在这部分中,详细列出了各个*.o 文件之间的符号引用。由于*.o 文件是由 asm 或 c/c++源文件编译后生成的,各个文件及文件内的节区间互相独立,链接器根据它们之间的互相引用链接起来,链接的详细信息在这个“Section Cross References”一一列出。
解释一下第3行,其他行的解释也差不多,第3行说明的是 startup_stm32f10x.o 文件中的“RESET”节区中的“__initial_sp” 符号(/函数)引用了同文件“STACK”节区(/函数)。这些跨文件引用的符号其实就是源文件中的函数名、变量名 。
3.2 删除无用节区
Removing Unused input sections from the image. Removing startup_stm32f10x_hd.o(HEAP), ( bytes).
Removing core_cm3.o(.emb_text), ( bytes).
Removing system_stm32f10x.o(i.SystemCoreClockUpdate), ( bytes).
Removing system_stm32f10x.o(.data), ( bytes).
Removing misc.o(i.NVIC_Init), ( bytes).
Removing misc.o(i.NVIC_PriorityGroupConfig), ( bytes).
Removing misc.o(i.NVIC_SetVectorTable), ( bytes).
Removing misc.o(i.NVIC_SystemLPConfig), ( bytes).
Removing misc.o(i.SysTick_CLKSourceConfig), ( bytes).
Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogCmd), ( bytes).
Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogSingleChannelConfig), ( bytes).
Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogThresholdsConfig), ( bytes).
Removing stm32f10x_adc.o(i.ADC_AutoInjectedConvCmd), ( bytes). ......(省略)
这部分列出了在链接过程它发现工程中未被引用的节区,这些未被引用的节区将会被删除(指不加入到*.axf 文件,不是指在*.o 文件删除),这样可以防止这些无用数据占用程序空间。这部分是编译器自动做的,不需要人工参与。
3.3 符号映像表
这个表列出了被引用的各个符号在存储器中的具体地址、占据的空间大小等信息。如我们可以查到 LED_GPIO_Config 符号(0x080002c5)存储在 0x080002c4 地址,它属于 Thumb Code 类型,大小为 90 字节,它所在的节区为 bsp_led.o 文件的 i.LED_GPIO_Config 节区。
3.4 存储器映像索引
Memory Map of the image Image Entry point : 0x08000131 Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000005d4, Max: 0x00080000, ABSOLUTE) Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x000005d4, Max: 0x00080000, ABSOLUTE) Exec Addr Load Addr Size Type Attr Idx E Section Name Object 0x08000000 0x08000000 0x00000130 Data RO RESET startup_stm32f10x_hd.o
0x08000130 0x08000130 0x00000000 Code RO * .ARM.Collect$$$$ mc_w.l(entry.o)
0x08000130 0x08000130 0x00000004 Code RO .ARM.Collect$$$$ mc_w.l(entry2.o)
0x08000134 0x08000134 0x00000004 Code RO .ARM.Collect$$$$ mc_w.l(entry5.o) ...... 0x080002c4 0x080002c4 0x00000060 Code RO i.LED_GPIO_Config bsp_led.o
0x08000324 0x08000324 0x00000004 Code RO i.MemManage_Handler stm32f10x_it.o
0x08000328 0x08000328 0x00000002 Code RO i.NMI_Handler stm32f10x_it.o
0x0800032a 0x0800032a 0x00000002 Code RO i.PendSV_Handler stm32f10x_it.o ...... Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x080005d4, Size: 0x00000400, Max: 0x00010000, ABSOLUTE) Exec Addr Load Addr Size Type Attr Idx E Section Name Object 0x20000000 - 0x00000400 Zero RW STACK startup_stm32f10x_hd.o
本工程的存储器映像索引分为 ER_IROM1 及 RW_IRAM1 部分,它们分别对应 STM32内部 FLASH 及 SRAM 的空间。相对于符号映像表,这个索引表描述的单位是节区,而且它描述的主要信息中包含了节区的类型及属性,由此可以区分 Code、 RO-data、 RW-data 及ZI-data。
从上面的表中我们可以看到 i.LED_GPIO_Config 节区存储在内部 FLASH 的0x080002c4 地址,大小为 0x00000060,类型为 Code,属性为 RO。而程序的 STACK 节区(栈空间)存储在 SRAM 的 0x20000000 地址,大小为 0x00000400,类型为 Zero,属性为 RW(即 RW-data)。
3.5 映像组件大小
map 文件的最后一部分是包含映像组件大小的信息(Image component sizes),这也是最常查询的内容。
最后一部分列出了只读数据(RO)、可读写数据(RW)及占据的 ROM 大小。其中只读数据大小为 1492 字节,它包含 Code 段及 RO-data 段; 可读写数据大小为 1024 字节,它包含RW-data 及 ZI-data 段;占据的 ROM 大小为 1492 字节,它除了 Code 段和 RO-data 段,还包含了运行时需要从 ROM加载到 RAM的 RW-data数据(本工程中 RW-data数据为 0字节)。其实,在map文件中,连每一个函数的存储空间占用情况都会列出来(符号映像表 )。
总结:综合整个 map 文件的信息,可以分析出,当程序下载到 STM32 的内部 FLASH 时,需要使用的内部 FLASH 是从 0x0800 0000 地址开始的大小为 1492 字节的空间;当程序运行时,需要使用的内部 SRAM 是从 0x20000000 地址开始的大小为 1024 字节的空间。
如果你真的把这个文件看明白了并能熟练应用,那么恭喜你,你就解锁了一个重要技能包。
ARM Cortex-M底层技术(3)—编译内核的原理及其应用的更多相关文章
- [原创] 详解云计算网络底层技术——虚拟网络设备 tap/tun 原理解析
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. 在云计算时代, ...
- ARM Cortex-M底层技术(2)—启动代码详解
杂谈 工作了一天,脑袋比较乱.一直想把底层的知识写成一个系列,希望可以坚持下去.为什么要写底层的东西呢?首先,工作用到了这部分内容,最近和内部Flash打交道比较多,自然而然会接触到一些底层的东西:第 ...
- 编译内核是出现:arch/arm/mm/tlb-v4wbi.S:64:error: too many positional arguments
内核:Linux-3.4.2 编译内核出现arch/arm/mm/tlb-v4wbi.S:64:error: too many positional arguments 交叉工具链太老了,换新一点的. ...
- Linux下编译内核配置选项简介
Code maturity level options代码成熟度选项 Prompt for development and/or incomplete code/drivers 显示尚在开发中或尚未完 ...
- 深入php内核,从底层c语言剖析php实现原理
深入php内核,从底层c语言剖析php实现原理 非常好的电子书:http://www.cunmou.com/phpbook/preface.md 这是它的目录: PHP的生命周期 让我们从SAPI ...
- 嵌入式Linux编译内核步骤 / 重点解决机器码问题 / 三星2451
嵌入式系统更新内核 1. 前言 手里有一块Friendly ARM的MINI2451的板子,这周试着编译内核,然后更新一下这个板子的Linux内核,想要更新Linux Kernel 4.1版本,但是种 ...
- 灵动微电子ARM Cortex M0 MM32F0010 GPIO 的配置驱动LED灯
灵动微电子ARM Cortex M0 MM32F0010 GPIO的配置 目录: 1.前言 2.学习方法简要说明 3.要点提示 4.注意事项 5.MM32F0010系统时钟的配置 6.MM32F001 ...
- linux内核调试技术之修改内核定时器来定位系统僵死问题
1.简介 在内核调试中,会经常出现内核僵死的问题,也就是发生死循环,内核不能产生调度.导致内核失去响应.这种情况下我们可以采用修改系统内核中的系统时钟的中断来定位发生僵死的进程和函数名称.因为内核系统 ...
- 支撑Java NIO 与 NodeJS的底层技术
支撑Java NIO 与 NodeJS的底层技术 众所周知在近几个版本的Java中增加了一些对Java NIO.NIO2的支持,与此同时NodeJS技术栈中最为人称道的优势之一就是其高性能IO,那么我 ...
随机推荐
- php开发常用技巧总结
1.[本地开启xdebug导致执行时间超max_execution_time产生的问题处理方法]xdebug开启,会导php执行速度慢,超max_execution_time,这种情况下有必要合理设置 ...
- Docker在CentOS7中的安装与启动
Docker是当下很流行的应用容器,在系统快速部署方面有着独特的优势.由于最近在做的一个项目需要用到Docker,所以找了些资料学了学.Docker不仅仅在应用快速部署方面有着独特的优势,而且在资源共 ...
- windows及linux下 golang开发环境配置
windows环境: 1.系统以及软件包版本: OS: windows 8.1 64位 x64处理器 GO:安装包:go1.7.3.windows-amd64.mis IDE:压缩包:liteid ...
- activeMQ安全机制
- 通过java反射机制,修改年龄字段的值
需求:将生日转为年龄 /** * 获取年龄值 */ public List getAgeInfo(List list) throws Exception { if (null == list || l ...
- 记一次SQL Server delete语句的优化过程
今天测试反应问题,性能测试环境一个脚本执行了3个小时没有出结果,期间其他dba已经建立了一些索引但是没有效果. 语句: DELETE T from License T WHERE exists ( ...
- TensorFlow学习笔记6-数值计算基础
TensorFlow学习笔记6-数值计算 本笔记内容为"数值计算的基础知识".内容主要参考<Deep Learning>中文版. \(X\)表示训练集的矩阵,其大小为m ...
- Node.js实战7:你了解buffer吗?
Buffer是NodeJS的重要数据类型,很有广泛的应用. Buffer是代表原始堆的分配额的数据类型.在NodeJS中以类数组的方式使用. 比如,用法示例: var buf = new Buffer ...
- [Python3] 015 冰冻集合的内置方法
目录 0. 前言 英文名 元素要求 使用限制 返回 方法数量 1. 如何查看 frozenset() 的内置方法 2. 少废话,上例子 2.1 copy() 2.2 difference() 2.3 ...
- SpringBoot(六) -- SpringBoot错误处理机制
一.SpringBoot中的默认的错误处理机制 1.在SpringBootWeb开发中,当我们访问请求出现错误时,会返回一个默认的错误页面: 2.在使用其他客户端访问的时候,则返回一个json数据: ...