前面写过一篇文章,STM32 启动代码分析 。这个里面的ld .*.s 都是自动生成的,为了弄清原理自己手动实现一个
先上三个代码。stm32.ld, start.s, startup.c

stm32.ld

start.s

startup.c

startup.c2
先看看stm32.ld 连接脚本。

第1行,设置连接脚本的entry 函数为Reset_hander。也可以删除。
2--100行 如上篇所说。 MEMORY为我的板子memory 布局状况。两个RAM,一个FLASH,然后把estack 定义在第一个RAM的最后的位置。

16到48 行,分别指定了连接的三个段text,data, bss,注意的是37行,把 data段 在程序运行时的VMA地址放在的RAM上,然后用AT指定的加载地址在flash上,意思就是在编译的时候生成的elf/bin/hex.这段的数据是放在了flash的地址空间上, 然后在程序在运行时,这段是放在RAM中。
具体初始化,看startup.c代码的init_runtime,也是和前篇的汇编代码(CopyDataInit、LoopCopyDataInit)功能一样的,把data数据初始化, 这个三个段在地址空间都是4字节对齐。
先用binutils 中size 工具看一下

目前这段代码data,bss段都是size都是0,也就是没有任何的数据,改改代码加一些验证。

定义了几个变量,再来看一下。

现在长度变了,和定义的变量实际大小有点不匹配,4(uint32_t global1)+1(char global_s)+1(char global_t)+1(char global_m) =7 考虑对齐变成了8个。同理un_init_var1,un_init_var2也是一样。
在用nm 工具看一下5个变量的分布情况,global1就被放在RAM的最开始的位置0x20000000,这是整个程序的第一个全局的数据,接下来就是char 数据分别为4,5,6的偏移位置。
BSS段数据按照连接脚本是紧挨着的data段的,也可以定义到偏移的一段的位置 。 这个BSS是从0x20000008开始的,因为对齐所以是0x20000008出开始的。

在用arm-none-eabi-readelf -a hello.elf 看一下 section

第一个section VA 为0x8000000,PA也是这个,FLG是R,E(R只读,E可执行)
第一个section VA 为0x200000000,PA0x80025A,FLG是R,E(运行时fRAM中,段数据放在flash上)
再来看看中断向量表。

__attribute((section(".isr_vector"))) 指定isr_vectors数组放在.isr_vector section中。

连接脚本指定这接放在.text 段中最开始的地方,然后再是各程序的(*.text).
这个数组的第一个元素是SP的位置,第二个就是系统Reset_handler 处理函数的位置。
连接脚本指定了_estack为0x20000000+96k == 0x20018000

在来看一下isr_vectors数组和Reset_hander 放在哪位置。

反汇编一下arm-none-eabi-objdump -S hello.elf

数组的第一个元素00 80 01 20 ,因为是小端模式,所以就是 0x20018000 正好是SP的地址,这个没问题。第二个元素是Reset_hander 中断处理函数.41 02 00 08 0x08000241,这个是reset_hander处理函数的地址,也是没问题。这个函数地址竟然没四字节对齐,只要第二个元素指向的是reset_handler函数就行,先忽略。

Reset_handler 处理 设置SP,跳到 init_runtime. 看看汇编代码有没有可以指定对齐的伪指令。上面的问题就解释了。

init_runtime 比较简单,就是把flash(LVM)处存放的.data段的数据在RAM(VMA)中重新初始化, .bss段用0填充,为了验证特意改成0x22初始化。 startup.c代码中定义的那些变量global1,global_s,un_init_var等在编译连接时就指定变量VMA地址在RAM中(连接脚本指定),所以这个阶段在那些要访问这些变量的指令是不能运行的,需等它初始化完成,为啥这么做,逻辑也简单。.data 因为你的程序给了初始值。.bss段因为定了变量,未初始化,但是可能会用到。

下载调试:openocd+st_link

1,下载,2,加载符号表,3,Reset_handler,init_runtime,SystemInit 三个函数加上断点

c 运行,首先第一个是Reset_handler断点了,i r 看了一下寄存器。SP其实已经初始化了,这个是向量表第一个元素的值,这个之前也有粉丝朋友说 reset_handler 不需要ldr sp,=_estack,看来是不需要的,然后N下条指令,这个就是ldr sp,=_estack。重新又把sp给初始化了一次。
继续执行断点到init_runtime开始处

结合这个图

这个时候在init_runtime未执行前先看一下 RAM中0x20000000的值及flash 中的值。
RAM 中是随机值,flash 0x00001234 , 0x73,0x74,0x6d分别为s,t,m三个字母ascii码值。

程序继续c 到sysetm_init 在来看看RAM的情况。这个时候两个循环是跑完了,在检查一下RAM中的变量值。

红色框框为RAM变量(global1,global_s,un_init_var等)的值,说明RAM中这些变量已成功初始化,可以对照变量地址检查一下。

启动代码的第一部分 这个先写到这里 ,后面去实现system_init,去做一下stm32 板子时钟初始化的动作。 可能很多人觉得有start.s 就不是C代码了,这个不要去纠结,主要是C代码本身没法设置SP。
如果把init_runtime 函数改成如下,再把start.S 去掉就是一个“”纯C“”的代码了

本文深入探讨STM32启动代码细节,包括ld脚本配置、中断向量表设置及初始化过程。通过手动实现启动代码,揭示数据段(.data)与未初始化数据段(.bss)如何在RAM中正确初始化。
2358

被折叠的 条评论
为什么被折叠?



