概述
编译代码的一些理解
ARM Cortex-M底层技术—编译内核的原理及其应用
参考网址:
https://www.cnblogs.com/39950436-myqq/p/11545140.html
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_v526ARMARMCCbin,如下图所示:
armar 是用于把.o 文件打包成 lib 文件
,armasm 编译汇编文件
,armcc 编译 c/c++文件
,armlink 链接对象文件
,fromelf 生成下载格式文件
,它根据 axf 映像文件转化成 hex 文件,并列出编译过程出现的错误(Error)和警告(Warning)数量 。调用这些编译工具,需要用到Windows的“命令行提示符工具”,为了让命令行方便地找到这些工具,我们先把工具链的目录添加到系统的环境变量中。
3.打开 Windows 的命令行,点击系统的“开始菜单”,在搜索框输入“cmd”,在搜索结果中点击“cmd.exe”即可打开命令行,见图
在弹出的命令行窗口中输入“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 的指令,如下图
上面的命令简写了。正常的写法是 C:programformelf.exe --bin -o ./obj/xx.bin ./obj/xx.elf
还有链接器的配置界面如下:
那么这些东西有什么用呢?来看一下这段代码:
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.rn");
while (1)
{
ch = errorfunc();
PUTCHAR(ch);
}
}
看蓝色底纹部分,注意第一个单词:linking……
,然后后面跟着出错信息,这次是一个链接器错误!
大家经常说的编译器报错,其实是几个不同的东西再报错,编译器、汇编器、链接器都会报错,那你可能会问,知道这个有啥用呢?当然是有用的,比如汇编器报错,基本跟C语言没关系,基本可以断定是汇编语言语法错误或者是嵌入C语言的汇编语言出错(在C中嵌入汇编是一种非常有效的编程手段);如果是链接器报错,那就基本跟C语言语法无关,不是你的C语法上出错,很可能是你调用了不存在的函数或者链接器脚本写错了,或者使用了不存在的标号Symbol,或者没有包含头文件.h;而只有编译器报错,才总是你的C写的有问题.
4. 工作报表.map文件
真正有用的链接器描述文件“.map”非常有用(编译链接后,双击工程列表下的第二个文件夹可以打开,变成灰色的那个)
“.map”绝对是你的“核心员工”的工作报表,也是最复杂的一个。它主要包含交叉链接信息,查看该文件可以了解工程中各种符号之间的引用以及整个工程的 Code
、 RO-data
、 RW-data
以及 ZI-data
的详细及汇总信息。它的内容中主要包含了“节区的跨文件引用
”、“删除无用节区
”、“符号映像表
”、“存储器映像索引
”以及“映像组件大小
”。
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”节区(/函数)。这些跨文件引用的符号其实就是源文件中的函数名、变量名 。
Removing Unused input sections from the image.
Removing startup_stm32f10x_hd.o(HEAP), (512 bytes).
Removing core_cm3.o(.emb_text), (32 bytes).
Removing system_stm32f10x.o(i.SystemCoreClockUpdate), (164 bytes).
Removing system_stm32f10x.o(.data), (20 bytes).
Removing misc.o(i.NVIC_Init), (112 bytes).
Removing misc.o(i.NVIC_PriorityGroupConfig), (20 bytes).
Removing misc.o(i.NVIC_SetVectorTable), (20 bytes).
Removing misc.o(i.NVIC_SystemLPConfig), (32 bytes).
Removing misc.o(i.SysTick_CLKSourceConfig), (40 bytes).
Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogCmd), (20 bytes).
Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogSingleChannelConfig), (16 bytes).
Removing stm32f10x_adc.o(i.ADC_AnalogWatchdogThresholdsConfig), (6 bytes).
Removing stm32f10x_adc.o(i.ADC_AutoInjectedConvCmd), (22 bytes)
.......(省略)
这部分列出了在链接过程它发现工程中未被引用的节区,这些未被引用的节区将会被删除(指不加入到*.axf 文件,不是指在*.o 文件删除),这样可以防止这些无用数据占用程序空间。这部分是编译器自动做的,不需要人工参与。
这个表列出了被引用的各个符号在存储器中的具体地址、占据的空间大小等信息。如我们可以查到 LED_GPIO_Config 符号(0x080002c5)存储在 0x080002c4 地址,它属于 Thumb Code 类型,大小为 90 字节,它所在的节区为 bsp_led.o 文件的 i.LED_GPIO_Config 节区。
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 3 RESET startup_stm32f10x_hd.o
0x08000130 0x08000130 0x00000000 Code RO 3207 * .ARM.Collect$$$$00000000 mc_w.l(entry.o)
0x08000130 0x08000130 0x00000004 Code RO 3210 .ARM.Collect$$$$00000001 mc_w.l(entry2.o)
0x08000134 0x08000134 0x00000004 Code RO 3213 .ARM.Collect$$$$00000004 mc_w.l(entry5.o)
......
0x080002c4 0x080002c4 0x00000060 Code RO 3192 i.LED_GPIO_Config bsp_led.o
0x08000324 0x08000324 0x00000004 Code RO 3132 i.MemManage_Handler stm32f10x_it.o
0x08000328 0x08000328 0x00000002 Code RO 3133 i.NMI_Handler stm32f10x_it.o
0x0800032a 0x0800032a 0x00000002 Code RO 3134 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 1 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)。
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 字节的空间。
最后
以上就是甜甜魔镜为你收集整理的编译代码的一些理解编译代码的一些理解的全部内容,希望文章能够帮你解决编译代码的一些理解编译代码的一些理解所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复