看过《STM32入门学习第一或二阶段视频教程》,对STM32有一定的基础;或没有基础,先看此文再去看《STM32入门学习第一或二阶段视频教程》
1,C程序到可执行程序的四个步骤:
预处理(Preprocessing),编译(Compilation),汇编(Assemble),链接(Linking)
2,回顾GCC编译过程(以doubixiaohanhan.c举例)
(参考 逗比小憨憨的《gcc零基础入门学习教程》)
//doubixiaohanhan.c
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int i = 0;
if(argc < 2)
{
printf("parameters is not enough\n");
exit(1);
}
while(argv[i])
{
printf("argv[%d]=%s\n",i,argv[i]);
i ;
}
return 0;
}
(1)预处理
以“#”号开头的预处理指令,比如包含#include,宏定义指定#define等。在源程序中这些指令都放在函数之外,而且一般放在源文件的前面。使用预处理器把源文件doubixiaohanhan.c经过预处理生成doubixiaohanhan.i文件。(简单来说就是展开程序中的宏等)
预处理的命令为:
gcc -E doubixiaohanhan.c -o doubixiaohanhan.i
(2)编译
指将经过预处理文件(doubixiaohanhan.i)之后的程序转换成特定汇编(doubixiaohanhan.s)代码的过程。
命令:gcc -S doubixiaohanhan.i -o doubixiaohanhan.s
备注:Linux下,命令stat可以查看汇编代码和代码大小;命令file可以查看文件类型
(3)汇编
汇编过程将上一步生成的汇编代码转换成机器码,这一步产生的文件叫做目标文件,是二进制格式。
命令:gcc -c doubixiaohanhan.s -o doubixiaohanhan.o
(4)链接
链接过程使用链接器将上述目标文件与其它目标文件、库文件、启动文件等链接起来生成可执行文件。附加的目标文件包括静态连接库和动态连接库。
命令: gcc doubixiaohanhan.o -o doubixiaohanhan
备注:指定库文件路径 gcc -L xxx(参考 逗比小憨憨的《gcc零基础入门学习教程》)
二,keil MDK编译程序过程详解
(1) 编译(汇编):MDK 软件使用的编译器是 armcc 和 armasm,它们根据每个 c/c 和汇编源文件编译成对应的以“.o”为后缀名的对象文件 (Object Code,也称目标文件),其内容主要是从源文件编译得到的机器码,包含了代码、数据以及调试使用的信息。
(2) 链接:链接器 armlink 把各个.o文件和相应的库文件链接成一个映像文件“.axf”或“.elf”;
(3) 格式转换:一般来说 Windows 或 Linux 系统使用链接器直接生成可执行映像文件elf 然后,内核根据该文件的信息加载后,就可以运行程序了,但在单片机平台上,需要把该文件的内容加载到芯片上,所以还需要对链接器生成的 elf 映像文件利用格式转换器 fromelf 转换成“.bin”或“.hex”文件,交给下载器下载到芯片的 FLASH 或 ROM 中。
扩展知识点:hex文件与bin文件的主要区别?
hex文件包含了地址信息,bin文件格式只包括了数据本身,没有包含地址。
回顾《单片机实践视频教程》课程:
在用ISP方式(通过串口下载)烧写51程序时的步骤:
- 选择单片机型号
- 选择串口号
- 设置波特率(或者默认)
- 选择下载的文件
- 点击下载按钮下载
从上述步骤可知:我们并没有选择要把程序下载到单片机的哪块内存中,即不需要设置地址。因为HEX文件内部的信息已经包括了地址。而烧写下载bin文件时需要选择内存的起始地址和终止地址,即要把bin文件下载到指定的内存空间。通常需要指定程序内存地址的芯片为ARM芯片和DSP芯片。
打开Keil MDK软件安装目录,可以找到上述提高的armasm,armlink等文件。
具体方法:
(1)打开任意一个《STM32入门视频教程第一或二阶段》工程文件,然后编译keil工程,出现一下提示信息:
上图找到: *** Using Compiler 'V5.06 update 3 (build 300)', folder: 'E:\keilmdk5Setup\ARM\ARMCC\Bin'
(2)打开这个文件夹:E:\keilmdk5Setup\ARM\ARMCC\Bin
解释:
- armar:转换文件为其它文件(包括库文件)
- armasm:编译汇编文件;编译后每个汇编文件都对应有一个独立的.o 文件
- armcc:编译c/c 文件;编译后每个 c/c 每个文件都对应有一个独立的.o 文件
- armlink:链接对象文件;根据程序的调用把各个.o 文件的内容连接起来,最后生成程序的axf 映像文件,并附带程序各个域大小的说明,包括 Code、RO-data、RW-data 及 ZI-data 的大小。
- fromelf:使用 fromelf 生成下载格式文件,它根据 axf 映像文件转化成 hex 文件,并列出编译过程出现的错误 (Error) 和警告 (Warning) 数量。
备注:每个 C 文件都对应生成了.o、.d 及.crf 后缀的文件,还有一些额外的.dep、.hex、.axf、.htm、.lnp、.sct、.lst 及.map 文件。
三,程序的组成、存储与运行扩展知识点:Linux段管理(这一部分可以自行百度看看,这里只是简单讲解)
在Linux下内存分配是以页为单位的,而页是通过段管理,各个段之间是独立的,方便管理。一个简单的程序被编译成目标文件后的大致结构如下:
各个段位的简单说明
- init段
程序初始化入口代码,在main()之前运行
- bss段
通常是指用来存放程序中未初始化的全局变量和未初始化的局部静态变量。未初始化的全局变量和未初始化的局部静态变量默认值是0。bss段属于静态内存分配。
- data段
数据段(data segment)通常是指用来存放程序中已初始化的全局变量和已初始化的静态变量的一块内存区域。数据段属于静态内存分配。
- text段
代码段(code segment / text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
- rodata段
存放的是只读数据,比如字符串常量,全局const变量和#define定义的常量。本段又称为常量区。并不是所有的常量都放在rodata段的,其特殊情况如下:
1) 有些立即数和指令编译在一起直接放在代码段;
2) 对于字符串常量,编译器会去掉重复的常量,让程序的每个字符串常量只有一份
3) 有些系统中rodata段是多个进程共享的,目的是为了提高空间的利用率
- strtab段
存储的是变量名、函数名等
- shstrtab段
存储bss、text、data等段名。
- rel.text段
针对text段的重定位表,还有rel.data(针对data段的重定位表)
- heap堆
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc()等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free()等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
- stack栈
是用户存放程序临时创建的局部变量和在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把栈看成一个寄存、交换临时数据的内存区。
重点了解:
bss段,data段,text段,rodata段,heap堆,stack栈
在Keil MDK工程中的编译提示输出信息中有一个语句“Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx”,它说明了程序各个域的大小,编译后,应用程序中所有具有同一性质的数据 (包括代码) 被归到一个域(或称为段),程序在存储或运行的时候,不同的段会呈现不同的状态,这些段的意义如下:- Code,即代码段:指的是编译器生成的机器指令,这些内容被存储到 ROM 区。
- RO-data(Read Only data),即只读数据段:指程序中用到的只读数据,这些数据被存储在ROM 区,因而程序不能修改其内容。比如 C 语言中 const 关键字定义的变量就是典型的RO-data。
- RW-data(Read Write data),即可读写数据段:指初始化为“非 0 值”的可读写数据,程序刚运行时,这些数据具有非 0 的初始值,且运行的时候它们会常驻在 RAM 区,因而应用程序可以修改其内容。比如 C 语言中使用定义的全局变量,且定义时赋予“非 0 值”给该变量进行初始化。
- ZI-data(Zero Initialie data),即 0 初始化数据:指初始化为“0 值”的可读写数据域,它与RW-data 的区别是程序刚运行时这些数据初始值全都为 0,而后续运行过程与 RW-data 的性质一样,它们也常驻在 RAM 区,因而应用程序可以更改其内容。例如 C 语言中使用定义的全局变量,且定义时赋予“0 值”给该变量进行初始化 (若定义该变量时没有赋予初始值,编译器会把它当 ZI-data 来对待,初始化为 0)。
备注:ZI-data 的栈空间 (Stack) 及堆空间 (Heap):在 C 语言中,函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间。而使用 malloc(在C 程序中的new) 动态分配的变量属于堆空间。在程序中的栈空间和堆空间都是属于ZI-data 区域的,这些空间都会被初始值化为 0 值。编译器给出的 ZI-data 占用的空间值中包含了堆栈的大小 (经实际测试,若程序中完全没有使用 malloc 动态申请堆空间,编译器会优化,不把堆空间计算在内)。
使用malloc动态分配内存,则使用free释放其内存;使用new动态分配内存,则使用delete释放其内存。
总结:
- VMA(virtual memory address): 某程序区段在程序执行时的地址
- LMA(load memory address): 某程序区段加载时的地址。
一般情况下,LMA和VMA都是相等的,不等的情况主要发生在一些嵌入式系统上
参考资料:MDK 的帮助手册《ARM Development Tools》,点击 MDK 界面的“help->uVision Help”菜单可打开该文件
欢迎关注逗比小憨憨此文还没写完,待更新。。。。。。
,