(GCC)STM32基础详解之内存分配

1.软硬件说明

硬件使用STM32F103ZET6最小系统板。

软件:

代码编辑器:VSCode

Debug仿真:Ozone

基础工程生成:STM32CubeMX

交叉编译链:arm-none-eabi-gcc

2.工程说明

本章所使用工程为STM32CubeMX生成,去掉了时钟初始化,堆栈设置初始都为0,优化等级为0,初始main函数如下:

int main(void)
{
  while (1)
  {

  }
}

初次编译如下:

(GCC)STM32基础详解之内存分配_第1张图片

 其中:

text:代码段

data:已初始化数据段

bss:未初始化数据段

dec:十进制,结果为text+data+bss总和

hex:16进制,同上

为什么main函数什么都没有使用,却显示有8个字节的已初始化数据段和32个字节的未初始化数据段?

这个问题可以由.map文件看出:

(GCC)STM32基础详解之内存分配_第2张图片

可以看到,在交叉编译链所提供的某些.o文件比如crtbegin.o中含有.data段,这些文件多是与代码初始化,或C库初始化有关,没必要深究。本文不会讨论与交叉编译链启动相关的内容

完整工程在本文末尾。

3.向量表与代码段

根据Cortex-M3权威指南描述,系统复位后,在向量表异常0处保存的是堆栈起始地址,而后紧跟中断向量表,中断向量表说的通俗一点,是一个列表,里面按顺序存放有系统异常和外部中断回调函数地址

(GCC)STM32基础详解之内存分配_第3张图片

(GCC)STM32基础详解之内存分配_第4张图片

以上图片截取自Cortex-M3权威指南,在32位单片机里,每个函数地址占用4个字节,也就是每个编号的异常占用4个字节的空间。从我们的.s启动文件中看,我们只有两个中断函数,一个是复位所对应的中断函数,还有一个是缺省中断函数,即所有没有单独编写中断函数的异常或中断,我们都把它的中断函数设置为缺省中断函数。在.s启动文件中有:

(GCC)STM32基础详解之内存分配_第5张图片

(GCC)STM32基础详解之内存分配_第6张图片

 然后把所有没有单独写中断函数的异常与中断设置为缺省函数:(只截取了一部分)

(GCC)STM32基础详解之内存分配_第7张图片

使用.weak关键字告诉编译器,如果外部有非弱定义的同名函数,用外部的。

由上述内容,我们猜测首先在0x0000 0000处存的是主堆栈(MSP)起始地址,因为向量表是第一个被载入进FLASH的,在下文中会有解释。我们的STM32F103ZET6的RAM起始地址是0x2000 0000,RAM大小是64K即0x10000,那RMA的地址范围就是0x2000 0000~0x2000 FFFF。所以主堆栈起始地址是0x2000 0000。其次,根据下图Ozone中显示的汇编代码:

(GCC)STM32基础详解之内存分配_第8张图片

可以看到Reset_HandlerDefault_Handler函数的首个指令地址分别为0x0800 022C和0x0800 0270,这样我们猜测FLASH地址0x0000 0004处存的是0x0800 022C,而后续被设置为缺省函数的存的都是0x0800 0270,个数由.s文件中的异常和函数个数决定。

随后,在debug模式下,到FLASH中去印证我们的想法:

(GCC)STM32基础详解之内存分配_第9张图片

上图中有些地址为0的,则可以对照.s启动脚本中相关定义,侧面印证我们的猜测:

(GCC)STM32基础详解之内存分配_第10张图片

可以看到,地址0x0000 0000处存的是0x2001 0000,0x0000 0004处存的是0x 0800 022D,后续存的都是0x0800 0271,和我们猜想的很接近但并不相同,原因如下: 

首先RAM起始地址是0x2000 0000 ,大小是64K,所以范围是0x2000 0000~0x2000 FFFF,因为是向下生长,所以起始地址应该是0x2000 FFFF,但是MSP必须为起始地址+1,所以MSP应该为0x2001 0000。 

(GCC)STM32基础详解之内存分配_第11张图片

(GCC)STM32基础详解之内存分配_第12张图片

也就是这个函数地址的最低位必须为1,表示是在Thumb状态下,到此为止向量表和我们猜想完全一致。 

接下来看代码段,抛去交叉编译链所携带代码段和.s启动文件中的代码段,我们只看main函数所生成的机器码,在main.lst中:

(GCC)STM32基础详解之内存分配_第13张图片

因为main.c里面只有循环,所以可以看到非常简单只有三行机器码。使用Ozone打开elf文件,可以看到反汇编:

(GCC)STM32基础详解之内存分配_第14张图片

代码是从0x0800 0224开始的,我们查看bin文件或者直接仿真状态下查看内存:

 可以看到从0x0800 0224开始的数据正是B4 80 AF 00 E7 FE,这与代码所译为的机器码正好相同,而机器码中省去的0000 0002 0004前缀则是代表的相对偏移。

这里有几个疑问:

1.为什么首地址就是栈顶?为什么后续就跟的中断向量表?

其实打开bin文件就可以很直接的看出来:

(GCC)STM32基础详解之内存分配_第15张图片

因为代码烧录就是把bin文件从头开始烧录到单片机FLASH里,而STM32单片机FLASH的起始地址就是0x0800 0000或者说是0x0000 0000,两者相同。

2.那为什么bin文件里,数据是按这个顺序组织的?

答案在链接脚本里,链接脚本规定了.isr_vector这个段第一个被链接进FLASH里,其后紧跟代码段.text:

而这个.isr_vector就是中断向量表,它被定义在.s启动文件里:

(GCC)STM32基础详解之内存分配_第16张图片

.word相当于一个size为一个字的占位符,它的值等于后面的变量。 

4.堆栈

上文说到,工程堆栈都是0,我们把栈设置为0x200,把堆设置为0x100,再次编译:

可以看到.bss段刚好增加了0x300 = 768字节,这与预期相符,因为堆栈就是在RAM中一段未被初始化的连续内存。在链接脚本中可以看到,堆栈所处段被保存在RAM中,且是放在.bss段之后的:

(GCC)STM32基础详解之内存分配_第17张图片

所以,我们猜测堆栈的起始地址紧跟.bss段末尾,使用objdump查看elf文件:

可以看到.bss段的起始地址为0x2000 0000(RAM起始地址) ,大小为0x1c,而堆栈段起始地址为0x2000 001c,大小为0x304,这0x4又是怎么来的?查看map文件,可以看到:

(GCC)STM32基础详解之内存分配_第18张图片

很直观的可以看到,堆占用了0x100字节,栈占用了0x200字节,而剩下的0x4字节来自于

. = ALIGN(0x8) 

即8字节对齐,因为堆栈段紧跟.bss段之后,那首地址应该是0x2000 001C,1C换成10进制为28,但是规定了8字节对齐,所以最小为32,即需要补上4个字节,所以堆栈段起始地址应该是0x2000 0020。

5.变量

代码中各种变量都保存在哪里?

5.1全局变量

5.1.1已初始化全局变量

我们在main.c中定义一个全局变量tmp1,如下图:

(GCC)STM32基础详解之内存分配_第19张图片

编译后,发现所有段都没有增加:

这是因为编译器认为这个变量没有被使用,所以直接优化掉了,导致这个变量根本就没有保存在可执行文件中。为了不让编译器优化掉这个变量,我们写一个fun函数把这个变量用上:

(GCC)STM32基础详解之内存分配_第20张图片

 再次编译后的结果:

发现.data段增加了4个字节,说明全局变量保存在.data段内,但是.bss段减小了4个字节,这又是为什么?查看elf文件:

(GCC)STM32基础详解之内存分配_第21张图片

可以看到.data段增加4个字节,正好是一个unsigned int类型的数据,因为.data段是第一个被加载进RAM中的,导致后续所有加载进RAM中的段地址都后移了4个字节,而上文中提到的堆栈段为了保证8字节对齐而补的4个字节因为后移的这4个字节不再需要,所以堆栈段大小从0x304变成了0x300。链接脚本可以看到加载顺序

(GCC)STM32基础详解之内存分配_第22张图片

这里注意只有.data段后面有 AT > FALSH,这个是为什么又代表什么意思?

首先,RAM和FLASH在链接脚本中表示的是一个地址范围,使用MEMORY关键字声明:

(GCC)STM32基础详解之内存分配_第23张图片

而 > RAM就表示运行地址(VMA)在起始地址为0x2000 0000,大小为64K的范围内,至于这个地址映射的是RAM还是FLASH或者是片外的SRAM,链接脚本并不关心。而AT > FLASH表示.data段载入地址(LMA)在FLASH里。我们再看上图的elf文件:

它表示.data段size为4,运行时地址为0x2000 0000,加载地址为0x0800 02fc 。这里有几个疑问:

1.为什么要把全局变量存在FLASH里?

因为全局变量的初始值是固定的,如果要保证单片机每次上电,那些有初始值的变量都能被正确初始化,则必须要保存它们的初始值,这个值不能掉电就丢失,所以不能保存在RAM里,而必须保存在FLASH里。

2.那为什么运行时不也在FLASH里?

因为FLASH的擦写是有次数限制的,在程序里,一个变量读写是很频繁的,这对于一个寿命可能只有10万次擦写的FLASH来说是无法承受的。而且,FLASH的擦除通常是以扇区为单位,如果只为了读写一个变量而擦除整个扇区,明显是不现实的。

3.怎么由FLASH里搬运到RAM里?

我们知道FLASH里的tmp1是由代码直接烧录进去的,那RAM里的tmp1是哪里来的?答案在启动文件里:

(GCC)STM32基础详解之内存分配_第24张图片

 首先r0 = _sdata,而_sdata在链接脚本中有定义,因为在.data段之前,其他段都没有输出到RAM里的,所以这里 . = 0x2000 0000 即_sdata = 0x2000 0000,所以r0 = 0x2000 0000。

(GCC)STM32基础详解之内存分配_第25张图片

r1 = _edata,这里因为.data段只有一个4字节的变量tmp1,所以_edata = 0x2000 0004即r1 = 0x2000 0004,而r2 = LOADADDR(.data),这里r2等于.data段的加载地址,查看map文件可以看到:

(GCC)STM32基础详解之内存分配_第26张图片

在.data加载前,FLASH地址已经累加到0x0800 02fc了,所以.data保存在FLASH里的首地址是0x0800 02fc,即r2 = 0x0800 02fc。

而这段Reset_Handler函数的汇编代码意思是:

(GCC)STM32基础详解之内存分配_第27张图片

简单的说就是把加载地址循环累加4byte,然后把里面的内容加载到运行地址里。

我们也可以在map文件中查看.data段中被加载的详细内容:

(GCC)STM32基础详解之内存分配_第28张图片

可以看到tmp1这个变量在main.o中表示为.data.tmp1,命名方式为段名+变量名,这样可以在链接过程中使用通配符 * 很好的操作某段合集。

最后我们在debug模式下印证我们的想法:

在FLASH(0x0800 0000地址对应0x0000 0000)中有:

在RAM中有: 

结论:已初始化的变量保存在.data段,而.data段保存在FLASH最后,它随代码一起被烧录进FLASH里,当程序运行时,它被最先加载进RAM里。

5.1.2未初始化全局变量

我们新添加一个未初始化的全局变量tmp2,然后在main里对它进行赋值:

(GCC)STM32基础详解之内存分配_第29张图片

  再次编译,查看结果:

可以看到代码段有所增加,.bss段增加4个字节。debug时查看RAM:

按照链接脚本来看,.data段后紧跟.bss段,为什么中间有间隔?打开map文件查看:

(GCC)STM32基础详解之内存分配_第30张图片

发现中间有0x1c长度被crtbegin.o中的.bss占用,所以结果是正常的。 .bss段在FLASH中不占用位置,那又是怎么在RAM中被初始化的?打开.s启动文件可以看到:

(GCC)STM32基础详解之内存分配_第31张图片

可以看到关键就在于_sbss与_ebss这两个地址,启动文件里的代码就是通过这两个地址把.bss段做清0初始化的。这两个地址引用于链接脚本:

(GCC)STM32基础详解之内存分配_第32张图片

在链接脚本中使用定位符.获取脚本当前的VMA或LMA,在MAP文件里可以看到定位符的具体数值。

结论:未初始化的全局变量被分配在.bss段,因为它们没有初始值,所以它们在FLASH里不占空间,但是他们在程序运行后,在.data被加载进RAM后同样也被加载进RAM里,加载方式就是把.bss段大小的RAM空间清0。

5.1.3const修饰过的全局变量

我们把tmp1这个全局变量使用const修饰:

编译后结果如下:

 这个结果让人迷惑:按照我们学过的知识,const修饰后变量应该是只读变量,所以应该被放在.rodata段,这样.data段减小4字节是没问题的,.bss因为字节对齐所需的4字节补齐也不再需要所以减小4字节也是没问题的,但是为什么.text段也减小了?那tmp1到底被放在哪里了?查看map文件:

(GCC)STM32基础详解之内存分配_第33张图片

可以清楚的看到,tmp1在被const修饰后并没有被放进.rodata段(read only),而是消失了。查看反汇编揭晓答案:

可以看到,编译器直接优化掉了tmp1这个变量,它不再占用任何空间,而是在调用fun函数时,直接给寄存器赋值0x1111 1111然后调用函数,它既不占用RAM也不占用FLASH。造成这个现象的主要原因是int类型的变量值可以直接用立即数代替,假如我们使用一个字符串,就无法用立即数代替,我们申请一个字符串常量tmp3:

(GCC)STM32基础详解之内存分配_第34张图片

  编译后结果如下:

.data段因为tmp3是全局变量的字符串指针所以增加4,.bss因为.data增加了4所以为了补齐也增加了4,查看map文件:

(GCC)STM32基础详解之内存分配_第35张图片

这里可以看到,字符串"1234"因为带有结束符0所以是5个字节,然后因为4字节对齐,所以又补了3个字节,所以.rodata总共8个字节。但是注意,上文的tmp3并没有用const修饰!再看.data段:

(GCC)STM32基础详解之内存分配_第36张图片

可以看到增加tmp3给.data段增加了4个字节,因为tmp3是一个指针,STM32里指针是32位即4个字节。在debug模式下,查看FLASH:

可以看到0x0800 0324处存的正是字符串31 32 33 34 00,而.data段载入RAM后首地址是0x2000 0000,此处的值正是0x0800 0324:

这一切看起来都是正常的,但是恰恰隐藏着一个问题,假如给tmp3赋值呢?

(GCC)STM32基础详解之内存分配_第37张图片

结果是编译没有ERROR,甚至没有WARNING,但是运行后会直接跳入Default_Handler:

(GCC)STM32基础详解之内存分配_第38张图片

而添加const并不会改变可执行文件结构,只是在你更改tmp3时给你一个错误警告:

所以const是很有必要的,它能让某些问题暴露在编译期。

结论:const修饰过的全局变量或被编译器认为是常量的符号,可能直接被优化为寄存器保存,也可能被放在.rodata段,在FLASH中位于代码段之后,当使用时和全局变量一样,但是如果试图修改它的值,则会让CPU尝试去修改.rodata段所在FLASH地址处的内容,这会导致CPU触发一个错误,所以为了让修改了某些不该被修改的值能在编译期报错,请在需要的地方使用const。

5.2局部变量

5.2.1已初始化局部变量

我们在代码中定义一个有初始值的局部变量tmp3:

(GCC)STM32基础详解之内存分配_第39张图片

编译结果:

可以看到.data和.bss都没有增加,只有代码段有所增加,查看debug下RMA:

可以看到局部变量使用的是栈空间。从汇编文件分析:

(GCC)STM32基础详解之内存分配_第40张图片

把0x3333 3333保存在R7偏移4字节地址上,R7值为:

这里有一个问题,局部变量不应该保存在栈里吗?而栈的地址范围在map文件中有很清楚的描述:

(GCC)STM32基础详解之内存分配_第41张图片

为什么局部变量tmp3没有被分配到0x2000 0128~0x2000 0328这个我们划分好的栈区内呢?

由前文中向量表那部分我们知道,系统在复位后使用的MSP(主堆栈指针)是从向量表起始位置取的,从.s启动文件我们可以找到此处保存的值:

(GCC)STM32基础详解之内存分配_第42张图片

这个值在链接脚本中被定义:

_estack被初始化为一个确定的值,即STM32F103ZET6的最大栈顶+1,而不是我们分配的堆栈栈顶,这导致了我们分配的堆栈其实根本没有使用。难道官方生成的代码是有bug的?

查看链接脚本关于堆栈段的注释:

原来堆栈段的划分并不是真正用作系统堆栈,而是用来在链接阶段确定VMA是否有足够的空间放置堆栈的。也就是链接阶段从.bss段结尾开始,分配用户设置的堆栈大小的空间,如果分配成功,证明余下的RAM有足够的空间,至于一共余下多少空间,比用户设置的堆栈还多出来多少,我们并不需要去关心,反正到程序运行的时候,我们使用栈是从最高地址开始使用的(通俗点就是倒序使用的),所以即使我们使用的栈空间比我们设置的更大,但程序还是跑的好好的。

假如我就想让系统用链接阶段划分的堆栈怎么做?很简单(强烈不建议这样做),修改向量表起始地址即可,但是栈顶是根据我们划分大小以及代码变量多少随时改变的,所以我们删除掉原来对_estack的初始化,而在链接堆栈段时赋值:

(GCC)STM32基础详解之内存分配_第43张图片

结论:已初始化的局部变量使用栈空间,在它定义时被初始化。

5.2.2未初始化的局部变量

我们把tmp3的初始值去掉会发现,定义变量这一句根本已经没有对应的汇编代码,而在fun(tmp3)函数调用中,直接传入的参数是r7+4处的值和上面传入的参数地址相同,但是却没有初始化:

(GCC)STM32基础详解之内存分配_第44张图片

(GCC)STM32基础详解之内存分配_第45张图片

查看r7+4处的值发现是一个脏数据,所以如果调用这个值将发生不可预料的后果。

结论:未初始化的局部变量使用栈空间,在使用它时记得为它初始化,否则它可能有一个脏数据。

5.2.3用static修饰的局部变量

把tmp3改成用static修饰并去掉初始值:

(GCC)STM32基础详解之内存分配_第46张图片

编译后发现又没有变化:

 容易的我们想到和之前的情况对比,发现还是8字节对齐的问题:

 也就是static修饰的局部变量是在.bss段,载入地址和运行地址情况:

 因为在.s启动文件中把.bss段都赋值为0,所以有:

(GCC)STM32基础详解之内存分配_第47张图片

查看汇编代码:

(GCC)STM32基础详解之内存分配_第48张图片

发现函数调用的还是r7+4,所以被static修饰过的局部变量也一定要初始化,否则会发生不可预料的后果。

为tmp3添加初始值:

(GCC)STM32基础详解之内存分配_第49张图片

编译后查看:

 查看更加详细的elf文件可以发现是.data段增加了4字节,.bss段减小了4字节:

(GCC)STM32基础详解之内存分配_第50张图片

 debug查看FLASH和RAM:

(GCC)STM32基础详解之内存分配_第51张图片

结论:使用static修饰的局部变量相当于全局变量,如果它有初始值,它在.data段,如果没有它在.bss段。

5.2.4使用register修饰局部变量

回到第一个局部变量例子,我们在tmp3的定义前使用register修饰:

(GCC)STM32基础详解之内存分配_第52张图片

 查看反汇编代码:

(GCC)STM32基础详解之内存分配_第53张图片

对比没有使用register修饰的汇编代码:

(GCC)STM32基础详解之内存分配_第54张图片

很明显,在没有使用register修饰时有关tmp3的操作是:

给r3赋值0x3333 3333,然后保存r3的值到栈空间(也就是r7里面的地址偏移4),运行fun函数时,用来传递参数的寄存器r0传递的tmp3是从栈空间内读取出来的。

而在使用register修饰后,有关tmp3的操作是:

给r4赋值0x3333 3333,在运行fun函数时,用来传递参数的寄存器r0直接被赋值为r4的值,中间没有使用任何栈空间。

当然register并不是一个指令,而是一个建议(实际会不会被采用还是要看编译器的判断),它会建议编译器把修饰过的变量不再保存在栈中,而是直接使用寄存器暂存它们,因为某些变量需要频繁使用,如果一直入栈出栈会徒增损耗。但是这样会使修饰过的变量无法取址,但不影响它被重新赋值,因为它一直保存在寄存器中,本就没有地址。

5.3malloc申请内存

5.3.1申请内存所占空间

我们在代码中添加一个tmp4全局的 unsigned int 指针,然后在main中用malloc申请:

(GCC)STM32基础详解之内存分配_第55张图片

注意包含头文件stdlib.h,且链接时要链接标准库:

首先tmp4是一个未初始化的全局指针,所以在.bss段,从map文件也可以看出:

(GCC)STM32基础详解之内存分配_第56张图片

可以看到因为添加了头文件和链接了标准库,.bss段又添加了新的malloc相关的内容。我们在debug模式下查看0x2000 0094保存的内容:

保存的内容为0x2000 000B,这个地址又是什么呢?查看反汇编文件:

(GCC)STM32基础详解之内存分配_第57张图片

可以看到malloc函数调用后,其返回值保存在r0里,而r0里的值是:

 也就是0x2000 0094保存的0x2000 00B0就是malloc 申请到的内存地址。这个地址刚好是在堆所在的地址空间:

对这个地址赋值0x4444 4444,可以看到RAM中有:

5.3.2内存泄漏

我们不释放tmp4,再申请一个tmp5:

(GCC)STM32基础详解之内存分配_第58张图片

然后debug查看tmp5的地址:

(GCC)STM32基础详解之内存分配_第59张图片

可以看到申请到的内存0x2000 00C0在tmp4的0x2000 00B8之后,而且如果没有初始化,则是脏数据。

我们先释放tmp4,再申请一个tmp5:

(GCC)STM32基础详解之内存分配_第60张图片

 然后在debug模式下查看RAM:

(GCC)STM32基础详解之内存分配_第61张图片

可以看到先释放再申请会导致tmp5又申请到了tmp4之前申请过但是已经释放的内存,而这个内存里的数据还是之前的数据,也就是释放并不会把之前的数据清除。所以要注意两点:

1.用完的内存记得释放,否则该内存再也不能被使用,这就是内存泄漏。

2.记得在内存释放前把内存所存内容清除,或在申请后先初始化所申请内存,否则里面可能存有脏数据。

5.3.3可使用内存范围

现在思考一个问题,我们知道在一开始我们分配给堆的内存的大小是0x100字节,如果我们此时申请0x108字节会怎么样?如下:

(GCC)STM32基础详解之内存分配_第62张图片

首先编译通过了:

其次,代码运行也通过了:

(GCC)STM32基础详解之内存分配_第63张图片

所以在STM32中堆和栈没有明显的界限,他们共用._user_heap_stack段起始地址到RAM最大地址这个范围内的所有空间,但是堆是从最小地址开始使用的,每次申请地址累加,栈是从最大地址开始使用的,每次地址累减,但是只要他们的地址没有重叠,程序永远也跑不飞,可是一旦两者所占用地址重叠,会导致不可预料的后果。栈占用堆,会导致堆内所存数据被破坏,堆占用栈会导致代码异常。

5.3.4内存碎片化

假如申请3块内存,tmp4占用4个字节,tmp5占用4个字节,tmp6占用8个字节,我们先申请tmp4和tmp5,然后释放tmp4,再申请tmp6,tmp6会使用tmp4释放的那4个字节吗?

(GCC)STM32基础详解之内存分配_第64张图片

答案是不会:

可以看到再次申请的tmp6没有使用tmp4已经释放的内存,而是在tmp5所使用内存0x2000 00C0后选择了0x2000 00D0,这是因为malloc所选用的策略就是从堆起始地址开始查找,第一个可以容纳下所申请内存的空间,返回它的首地址。想象一下,每次申请的内存空间大小不是固定的,假如我申请了2个内存分别是3字节,4字节,我先把3字节释放,再申请2字节,这个时候2字节和4字节中间会有1个字节的空隙,这个空隙除非遇到申请了1个字节,否则再也不会用到。如果整个代码这样的申请越来越多,会造成可用内存越来越少,这就是内存碎片化,要解决这个问题,通常需要内存管理。

结论:malloc所申请的内存在堆上,可用大小视栈所使用大小而定。每次申请内存需要初始化,否则可能是脏数据。内存使用完毕后需要释放,否则会导致该内存再也不能被申请,这就是内存泄漏。但是频繁的内存申请与释放会导致内存碎片化,需要有合适的内存管理。

6.总览

FLASH和RAM内容:

(GCC)STM32基础详解之内存分配_第65张图片

7.工程内容

7.1main 

unsigned int tmp1 = 0x11111111;
unsigned int tmp2;
void fun(unsigned int tmp)
{
  if(tmp)
  {

  }
}

int main(void)
{
  register unsigned int tmp3 = 0x33333333;
  tmp2 = 0x22222222;
  fun(tmp1);
  fun(tmp2);
  fun(tmp3);
  tmp3 = 0x4444;
  fun(tmp3);
  while (1)
  {

  }
}

7.2启动文件startup_stm32f103xe.s

  .syntax unified
  .cpu cortex-m3
  .fpu softvfp
  .thumb

.global g_pfnVectors
.global Default_Handler

/* start address for the initialization values of the .data section.
defined in linker script */
.word _sidata
/* start address for the .data section. defined in linker script */
.word _sdata
/* end address for the .data section. defined in linker script */
.word _edata
/* start address for the .bss section. defined in linker script */
.word _sbss
/* end address for the .bss section. defined in linker script */
.word _ebss

.equ  BootRAM,        0xF1E0F85F
/**
 * @brief  This is the code that gets called when the processor first
 *          starts execution following a reset event. Only the absolutely
 *          necessary set is performed, after which the application
 *          supplied main() routine is called.
 * @param  None
 * @retval : None
*/

  .section .text.Reset_Handler
  .weak Reset_Handler
  .type Reset_Handler, %function
Reset_Handler:

/* Copy the data segment initializers from flash to SRAM */
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit

CopyDataInit:
  ldr r4, [r2, r3]
  str r4, [r0, r3]
  adds r3, r3, #4

LoopCopyDataInit:
  adds r4, r0, r3
  cmp r4, r1
  bcc CopyDataInit
  
/* Zero fill the bss segment. */
  ldr r2, =_sbss
  ldr r4, =_ebss
  movs r3, #0
  b LoopFillZerobss

FillZerobss:
  str  r3, [r2]
  adds r2, r2, #4

LoopFillZerobss:
  cmp r2, r4
  bcc FillZerobss

/* Call the clock system intitialization function.*/
    @ bl  SystemInit
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
  bl main
  bx lr
.size Reset_Handler, .-Reset_Handler

/**
 * @brief  This is the code that gets called when the processor receives an
 *         unexpected interrupt.  This simply enters an infinite loop, preserving
 *         the system state for examination by a debugger.
 *
 * @param  None
 * @retval : None
*/
    .section .text.Default_Handler,"ax",%progbits
Default_Handler:
Infinite_Loop:
  b Infinite_Loop
  .size Default_Handler, .-Default_Handler
/******************************************************************************
*
* The minimal vector table for a Cortex M3.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
  .section .isr_vector,"a",%progbits
  .type g_pfnVectors, %object
  .size g_pfnVectors, .-g_pfnVectors


g_pfnVectors:

  .word _estack
  .word Reset_Handler
  .word NMI_Handler
  .word HardFault_Handler
  .word MemManage_Handler
  .word BusFault_Handler
  .word UsageFault_Handler
  .word 0
  .word 0
  .word 0
  .word 0
  .word SVC_Handler
  .word DebugMon_Handler
  .word 0
  .word PendSV_Handler
  .word SysTick_Handler
  .word WWDG_IRQHandler
  .word PVD_IRQHandler
  .word TAMPER_IRQHandler
  .word RTC_IRQHandler
  .word FLASH_IRQHandler
  .word RCC_IRQHandler
  .word EXTI0_IRQHandler
  .word EXTI1_IRQHandler
  .word EXTI2_IRQHandler
  .word EXTI3_IRQHandler
  .word EXTI4_IRQHandler
  .word DMA1_Channel1_IRQHandler
  .word DMA1_Channel2_IRQHandler
  .word DMA1_Channel3_IRQHandler
  .word DMA1_Channel4_IRQHandler
  .word DMA1_Channel5_IRQHandler
  .word DMA1_Channel6_IRQHandler
  .word DMA1_Channel7_IRQHandler
  .word ADC1_2_IRQHandler
  .word USB_HP_CAN1_TX_IRQHandler
  .word USB_LP_CAN1_RX0_IRQHandler
  .word CAN1_RX1_IRQHandler
  .word CAN1_SCE_IRQHandler
  .word EXTI9_5_IRQHandler
  .word TIM1_BRK_IRQHandler
  .word TIM1_UP_IRQHandler
  .word TIM1_TRG_COM_IRQHandler
  .word TIM1_CC_IRQHandler
  .word TIM2_IRQHandler
  .word TIM3_IRQHandler
  .word TIM4_IRQHandler
  .word I2C1_EV_IRQHandler
  .word I2C1_ER_IRQHandler
  .word I2C2_EV_IRQHandler
  .word I2C2_ER_IRQHandler
  .word SPI1_IRQHandler
  .word SPI2_IRQHandler
  .word USART1_IRQHandler
  .word USART2_IRQHandler
  .word USART3_IRQHandler
  .word EXTI15_10_IRQHandler
  .word RTC_Alarm_IRQHandler
  .word USBWakeUp_IRQHandler
  .word TIM8_BRK_IRQHandler
  .word TIM8_UP_IRQHandler
  .word TIM8_TRG_COM_IRQHandler
  .word TIM8_CC_IRQHandler
  .word ADC3_IRQHandler
  .word FSMC_IRQHandler
  .word SDIO_IRQHandler
  .word TIM5_IRQHandler
  .word SPI3_IRQHandler
  .word UART4_IRQHandler
  .word UART5_IRQHandler
  .word TIM6_IRQHandler
  .word TIM7_IRQHandler
  .word DMA2_Channel1_IRQHandler
  .word DMA2_Channel2_IRQHandler
  .word DMA2_Channel3_IRQHandler
  .word DMA2_Channel4_5_IRQHandler
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word 0
  .word BootRAM       /* @0x1E0. This is for boot in RAM mode for
                         STM32F10x High Density devices. */

/*******************************************************************************
*
* Provide weak aliases for each Exception handler to the Default_Handler.
* As they are weak aliases, any function with the same name will override
* this definition.
*
*******************************************************************************/

  .weak NMI_Handler
  .thumb_set NMI_Handler,Default_Handler

  .weak HardFault_Handler
  .thumb_set HardFault_Handler,Default_Handler

  .weak MemManage_Handler
  .thumb_set MemManage_Handler,Default_Handler

  .weak BusFault_Handler
  .thumb_set BusFault_Handler,Default_Handler

  .weak UsageFault_Handler
  .thumb_set UsageFault_Handler,Default_Handler

  .weak SVC_Handler
  .thumb_set SVC_Handler,Default_Handler

  .weak DebugMon_Handler
  .thumb_set DebugMon_Handler,Default_Handler

  .weak PendSV_Handler
  .thumb_set PendSV_Handler,Default_Handler

  .weak SysTick_Handler
  .thumb_set SysTick_Handler,Default_Handler

  .weak WWDG_IRQHandler
  .thumb_set WWDG_IRQHandler,Default_Handler

  .weak PVD_IRQHandler
  .thumb_set PVD_IRQHandler,Default_Handler

  .weak TAMPER_IRQHandler
  .thumb_set TAMPER_IRQHandler,Default_Handler

  .weak RTC_IRQHandler
  .thumb_set RTC_IRQHandler,Default_Handler

  .weak FLASH_IRQHandler
  .thumb_set FLASH_IRQHandler,Default_Handler

  .weak RCC_IRQHandler
  .thumb_set RCC_IRQHandler,Default_Handler

  .weak EXTI0_IRQHandler
  .thumb_set EXTI0_IRQHandler,Default_Handler

  .weak EXTI1_IRQHandler
  .thumb_set EXTI1_IRQHandler,Default_Handler

  .weak EXTI2_IRQHandler
  .thumb_set EXTI2_IRQHandler,Default_Handler

  .weak EXTI3_IRQHandler
  .thumb_set EXTI3_IRQHandler,Default_Handler

  .weak EXTI4_IRQHandler
  .thumb_set EXTI4_IRQHandler,Default_Handler

  .weak DMA1_Channel1_IRQHandler
  .thumb_set DMA1_Channel1_IRQHandler,Default_Handler

  .weak DMA1_Channel2_IRQHandler
  .thumb_set DMA1_Channel2_IRQHandler,Default_Handler

  .weak DMA1_Channel3_IRQHandler
  .thumb_set DMA1_Channel3_IRQHandler,Default_Handler

  .weak DMA1_Channel4_IRQHandler
  .thumb_set DMA1_Channel4_IRQHandler,Default_Handler

  .weak DMA1_Channel5_IRQHandler
  .thumb_set DMA1_Channel5_IRQHandler,Default_Handler

  .weak DMA1_Channel6_IRQHandler
  .thumb_set DMA1_Channel6_IRQHandler,Default_Handler

  .weak DMA1_Channel7_IRQHandler
  .thumb_set DMA1_Channel7_IRQHandler,Default_Handler

  .weak ADC1_2_IRQHandler
  .thumb_set ADC1_2_IRQHandler,Default_Handler

  .weak USB_HP_CAN1_TX_IRQHandler
  .thumb_set USB_HP_CAN1_TX_IRQHandler,Default_Handler

  .weak USB_LP_CAN1_RX0_IRQHandler
  .thumb_set USB_LP_CAN1_RX0_IRQHandler,Default_Handler

  .weak CAN1_RX1_IRQHandler
  .thumb_set CAN1_RX1_IRQHandler,Default_Handler

  .weak CAN1_SCE_IRQHandler
  .thumb_set CAN1_SCE_IRQHandler,Default_Handler

  .weak EXTI9_5_IRQHandler
  .thumb_set EXTI9_5_IRQHandler,Default_Handler

  .weak TIM1_BRK_IRQHandler
  .thumb_set TIM1_BRK_IRQHandler,Default_Handler

  .weak TIM1_UP_IRQHandler
  .thumb_set TIM1_UP_IRQHandler,Default_Handler

  .weak TIM1_TRG_COM_IRQHandler
  .thumb_set TIM1_TRG_COM_IRQHandler,Default_Handler

  .weak TIM1_CC_IRQHandler
  .thumb_set TIM1_CC_IRQHandler,Default_Handler

  .weak TIM2_IRQHandler
  .thumb_set TIM2_IRQHandler,Default_Handler

  .weak TIM3_IRQHandler
  .thumb_set TIM3_IRQHandler,Default_Handler

  .weak TIM4_IRQHandler
  .thumb_set TIM4_IRQHandler,Default_Handler

  .weak I2C1_EV_IRQHandler
  .thumb_set I2C1_EV_IRQHandler,Default_Handler

  .weak I2C1_ER_IRQHandler
  .thumb_set I2C1_ER_IRQHandler,Default_Handler

  .weak I2C2_EV_IRQHandler
  .thumb_set I2C2_EV_IRQHandler,Default_Handler

  .weak I2C2_ER_IRQHandler
  .thumb_set I2C2_ER_IRQHandler,Default_Handler

  .weak SPI1_IRQHandler
  .thumb_set SPI1_IRQHandler,Default_Handler

  .weak SPI2_IRQHandler
  .thumb_set SPI2_IRQHandler,Default_Handler

  .weak USART1_IRQHandler
  .thumb_set USART1_IRQHandler,Default_Handler

  .weak USART2_IRQHandler
  .thumb_set USART2_IRQHandler,Default_Handler

  .weak USART3_IRQHandler
  .thumb_set USART3_IRQHandler,Default_Handler

  .weak EXTI15_10_IRQHandler
  .thumb_set EXTI15_10_IRQHandler,Default_Handler

  .weak RTC_Alarm_IRQHandler
  .thumb_set RTC_Alarm_IRQHandler,Default_Handler

  .weak USBWakeUp_IRQHandler
  .thumb_set USBWakeUp_IRQHandler,Default_Handler

  .weak TIM8_BRK_IRQHandler
  .thumb_set TIM8_BRK_IRQHandler,Default_Handler

  .weak TIM8_UP_IRQHandler
  .thumb_set TIM8_UP_IRQHandler,Default_Handler

  .weak TIM8_TRG_COM_IRQHandler
  .thumb_set TIM8_TRG_COM_IRQHandler,Default_Handler

  .weak TIM8_CC_IRQHandler
  .thumb_set TIM8_CC_IRQHandler,Default_Handler

  .weak ADC3_IRQHandler
  .thumb_set ADC3_IRQHandler,Default_Handler

  .weak FSMC_IRQHandler
  .thumb_set FSMC_IRQHandler,Default_Handler

  .weak SDIO_IRQHandler
  .thumb_set SDIO_IRQHandler,Default_Handler

  .weak TIM5_IRQHandler
  .thumb_set TIM5_IRQHandler,Default_Handler

  .weak SPI3_IRQHandler
  .thumb_set SPI3_IRQHandler,Default_Handler

  .weak UART4_IRQHandler
  .thumb_set UART4_IRQHandler,Default_Handler

  .weak UART5_IRQHandler
  .thumb_set UART5_IRQHandler,Default_Handler

  .weak TIM6_IRQHandler
  .thumb_set TIM6_IRQHandler,Default_Handler

  .weak TIM7_IRQHandler
  .thumb_set TIM7_IRQHandler,Default_Handler

  .weak DMA2_Channel1_IRQHandler
  .thumb_set DMA2_Channel1_IRQHandler,Default_Handler

  .weak DMA2_Channel2_IRQHandler
  .thumb_set DMA2_Channel2_IRQHandler,Default_Handler

  .weak DMA2_Channel3_IRQHandler
  .thumb_set DMA2_Channel3_IRQHandler,Default_Handler

  .weak DMA2_Channel4_5_IRQHandler
  .thumb_set DMA2_Channel4_5_IRQHandler,Default_Handler

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

 7.3Makefile

######################################
# target
######################################
TARGET = GPIO


######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization

OPT = -O0
# OPT = -Og

#######################################
# paths
#######################################
# Build path
BUILD_DIR = build

######################################
# source
######################################
# C sources
C_SOURCES =  \
Core/Src/main.c \

# ASM sources
ASM_SOURCES =  \
startup_stm32f103xe.s


#######################################
# binaries
#######################################
PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S
 
#######################################
# CFLAGS
#######################################
# cpu
CPU = -mcpu=cortex-m3

# fpu
# NONE for Cortex-M0/M0+/M3

# float-abi


# mcu
MCU = $(CPU) -mthumb $(FPU) $(FLOAT-ABI)

# macros for gcc
# AS defines
AS_DEFS = 

# C defines
C_DEFS =  

# AS includes
AS_INCLUDES = 

# C includes
C_INCLUDES =  

# compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

CFLAGS = $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sections

ifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif


# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"


#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103ZETx_FLASH.ld

# libraries
# LIBS = -lc -lm -lnosys 
LIBS =
LIBDIR = 
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections

# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin


#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))

$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) 
	$(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@

$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)
	$(AS) -c $(CFLAGS) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile
	$(CC) $(OBJECTS) $(LDFLAGS) -o $@
	$(SZ) $@

$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(HEX) $< $@
	
$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)
	$(BIN) $< $@	
	
$(BUILD_DIR):
	mkdir $@		

#######################################
# clean up
#######################################
clean:
	rmdir $(BUILD_DIR) /s
  
#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

# *** EOF ***

7.4STM32F103ZETx_FLASH.ld

/* Entry Point */
ENTRY(Reset_Handler)

/* Highest address of the user mode stack */
_estack = 0x20010000;    /* end of RAM*/
/* Generate a link error if heap and stack don't fit into RAM */
_Min_Heap_Size = 0x100;      /* required amount of heap  */
_Min_Stack_Size = 0x200; /* required amount of stack */

/* Specify the memory areas */
MEMORY
{
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 64K
FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 512K
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini)) 

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  /* Constant data goes into FLASH */
  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
    . = ALIGN(4);
  } >FLASH

  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH

  .preinit_array     :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH
  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH
  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(SORT(.fini_array.*)))
    KEEP (*(.fini_array*))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH 

  /* used by the startup to initialize data */
  _sidata = LOADADDR(.data);

  /* Initialized data sections goes into RAM, load LMA copy after code */
  .data : 
  {
    . = ALIGN(4);
    _sdata = .;        /* create a global symbol at data start */
    *(.data)           /* .data sections */
    *(.data*)          /* .data* sections */

    . = ALIGN(4);
    _edata = .;        /* define a global symbol at data end */
  } >RAM AT> FLASH

  
  /* Uninitialized data section */
  . = ALIGN(4);
  .bss :
  {
    /* This is used by the startup in order to initialize the .bss secion */
    _sbss = .;         /* define a global symbol at bss start */
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)

    . = ALIGN(4);
    _ebss = .;         /* define a global symbol at bss end */
    __bss_end__ = _ebss;
  } >RAM

  /* User_heap_stack section, used to check that there is enough RAM left */
  ._user_heap_stack :
  {
    . = ALIGN(8);
    PROVIDE ( end = . );
    PROVIDE ( _end = . );
    . = . + _Min_Heap_Size;
    . = . + _Min_Stack_Size;
    . = ALIGN(8);
  } >RAM

  

  /* Remove information from the standard libraries */
  /DISCARD/ :
  {
    libc.a ( * )
    libm.a ( * )
    libgcc.a ( * )
  }

  .ARM.attributes 0 : { *(.ARM.attributes) }
}


 更新:2022-01-26

章节3.向量表与代码段,对代码段代码内容直接在bin文件中查找描述有偏差。

比如在demo后续添加fun函数后,我们跳转函数处的机器码可以查看main.lst:

可以看到跳转fun显示的机器码为FFF7 FEFF。我们查看反汇编文件找到加载地址:

加载地址由上图可知是0x0800 024E,找到bin文件此处数据:

很明显和main.lst中是对不上号的,这又是为什么?

答案是:符号的重定位。由编译脚本我们知道,我们使用了通配符*,把所有.o文件的相同段合并,比如main.c编译汇编为的main.o中有.text段,而启动文件startup_stm32f103xe.s中也可能有.text段 ,链接阶段将相同段合并,这导致了一个问题,有些指令的机器码的目标地址与源地址使用的是相对偏移,合并可能会导致使用相对偏移的地址出现偏差。所以为了解决这个问题,main.o里关于变量地址、函数地址等都使用的临时地址,而不是真正的地址,比如这里的函数地址使用的相对偏移为-4,也就是main里所有函数调用偏移都是-4:

可以看不同偏移地址调用fun都是FFF7FEFF,甚至调用malloc也是FFF7FEFF,而翻译为机器指令正是跳转到相对偏移为-4的指令处。而这个相对地址会在所有文件链接为一个最终的可执行文件时被重新分配。使用objdump 查看elf文件可以看到(arm-none-eabi-objdump -d):

(GCC)STM32基础详解之内存分配_第66张图片

所有相对偏移地址都被重新定位,这与.bin文件刚好可以对应上。

再次推荐《程序员的自我修养--链接、装载与库》。可以查看第四章4.2节讲解。

更新:2022-03-22

在static修饰的局部变量那一章讲未初始化的时候,明显结论和截图不符,没一个人给指出来。

使用static修饰的局部变量无初始值则为.bss段,有初始值则为.data段。所以没有初始值的时候必定被初始化为0而不是脏数据。

无初始值:

 有初始值:

(GCC)STM32基础详解之内存分配_第67张图片

你可能感兴趣的:(GCC下单片机开发,stm32,单片机,arm,嵌入式硬件,c语言)