【嵌入式】读代码之startup_stm32f103xb.s

【嵌入式】读代码之startup_stm32f103xb.s startup_stm32f103xb.s里的代码风格怪是正常的。因为现在看到的不是 C / C / C#而是ARM 汇编 IAR 汇编语法它比 C 更贴近 CPU所以它不是在写“类、函数、对象、语句块”而是在写这个符号公开不公开这段代码放哪个段某个标签地址叫什么把哪个地址装进寄存器跳到哪里执行一、先把代码整体翻译成人话EG:THUMB PUBWEAK Reset_Handler SECTION .text:CODE:REORDER:NOROOT(2) Reset_Handler LDR R0, SystemInit BLX R0 LDR R0, __iar_program_start BX R0它的意思这是在定义复位处理函数Reset_Handler。芯片上电后会先执行这里。它先调用SystemInit()再跳到__iar_program_start后面才会逐步进入 C 运行时和main()。二、要先换一种“读代码思维”以前读 C / C#通常是这样读这是一个函数这个函数里有变量、分支、循环、对象调用而读汇编时要换成这种方式这是一个标签还是伪指令这是在操作哪个寄存器这是在调用还是跳转CPU 执行完这一行下一步去哪也就是说汇编更像是在看CPU 的动作脚本三、逐行讲这几行到底怎么读1THUMBTHUMB这一行不是业务逻辑不是“执行语句”。它是在告诉汇编器后面用Thumb 指令集来汇编对 Cortex-M3 这类内核运行的主要就是 Thumb 指令。所以你先把它理解成“后面这些代码按 ARM 的 Thumb 模式来解释”这行你知道用途就够了先别深究。2PUBWEAK Reset_HandlerPUBWEAK Reset_Handler这一行也不是 CPU 运行时干的事它更像是给链接器/工程系统看的声明。你可以先拆成两部分理解PUB公开这个符号WEAK这是一个弱定义合起来大概就是Reset_Handler这个符号我先提供一个默认版本而且它是弱的。“弱”是什么意思意思是如果别的地方有一个更强的同名定义别的地方可以把这里覆盖掉。在启动文件里大量中断函数都是这么定义的作用就是默认先给你一个版本你自己写了同名中断函数就用你自己的比如TIM3_IRQHandler默认是个死循环但你在 C 文件里自己写了TIM3_IRQHandler()最终就会用你的。这个启动文件里有大量这种弱定义中断入口。3SECTION .text:CODE:REORDER:NOROOT(2)SECTION .text:CODE:REORDER:NOROOT(2)这也是“工程组织信息”不是业务逻辑。它是在说接下来这段内容要放到.text这个代码段里可以先粗理解成.text代码段CODE这是代码不是数据REORDER、NOROOT(2)IAR 的段属性控制现在不用深究这些参数细节。先把它理解成一句话就够“下面定义的Reset_Handler放到代码段里去。”4Reset_HandlerReset_Handler这一行非常像 C 里的函数名但它在汇编里更准确地说叫标签label可以把它理解成一个“地址名字”。就是说这一行所在的位置有个名字叫Reset_Handler别的地方可以跳到这里来执行在向量表里第二项放的就是这个Reset_Handler的地址所以芯片上电后会从这里开始执行。启动文件向量表里明确把第二项设成了Reset_Handler。所以它虽然看起来像函数名但底层本质更像“这是一个程序入口地址标签”5LDR R0, SystemInitLDR R0, SystemInit这句很关键。先分开读LDR加载R0寄存器 0SystemInit这里不是取变量内容而是把SystemInit的地址拿出来可以先把这句翻译成把SystemInit这个函数的地址装到寄存器R0里这里R0就像一个很小很快的 CPU 内部临时变量。如果用 C 的感觉类比可以粗略想成pSystemInit;当然这不是严格等价只是为了帮助建立感觉。6BLX R0BLX R0这句的意思是调用R0指向的函数因为上一句已经把SystemInit的地址放到R0里了所以这句实际上就是调用SystemInit()BLX可以怎么记可以先这样记BBranch跳转LLink带返回地址X切换/兼容不同指令状态的分支形式现在不用抠那么细先把它理解成这是一次“函数调用”式跳转所以这两句连起来LDR R0, SystemInit BLX R0就等价于脑子里熟悉的SystemInit();7LDR R0, __iar_program_startLDR R0, __iar_program_start这句和前面一模一样的套路把__iar_program_start这个入口地址装进R0这个__iar_program_start不是写的普通函数它是IAR 运行库的启动入口。启动文件里也把它声明成了外部符号。它后面会继续做数据段初始化BSS 清零运行库准备最后到main()所以它比main()更靠前。8BX R0BX R0这一句意思是跳到R0指向的地址去执行因为上一句已经把__iar_program_start的地址放进R0所以这里就是跳到__iar_program_start和BLX不同这里用的是BX现在可以先粗理解成BLX更像“调用函数”会保存返回关系BX更像“直接转过去不打算回来了”所以这两句连起来就是LDR R0, __iar_program_start BX R0相当于把控制权正式交给 IAR 运行时启动入口后面不回这个Reset_Handler了四、把整段合起来看所以整段THUMB PUBWEAK Reset_Handler SECTION .text:CODE:REORDER:NOROOT(2) Reset_Handler LDR R0, SystemInit BLX R0 LDR R0, __iar_program_start BX R0可以完整翻译成使用 Thumb 指令集。这里定义了一个弱符号Reset_Handler并把它放在代码段里。当复位进入Reset_Handler后先调用SystemInit()做系统级初始化然后跳转到__iar_program_start由 IAR 运行库继续完成 C 环境初始化并最终进入main()。五、为什么它看起来和 C / C# 差别这么大因为层级完全不同。C / C# 是“告诉程序要做什么”比如SystemInit();main();这种写法隐藏了很多细节。汇编是“告诉 CPU 每一步怎么做”比如把函数地址放进寄存器用分支指令跳过去决定是不是保留返回关系所以它不再是“像人写给人看的业务语言”而更像“写给 CPU 的动作清单”六、可以先建立一个最小词典现在先记住下面几个就够了R0CPU 的寄存器临时存东西用LDR装载把某个值/地址放进寄存器B跳转BL带返回地址的跳转像函数调用BX跳到某个寄存器指向的位置BLX通过寄存器进行函数调用式跳转label:一个地址名字比如Reset_HandlerSECTION告诉工具链这段内容放哪一类段PUBWEAK弱定义、可被覆盖的公开符号七、现在最需要抓住的不是语法细节而是执行顺序要先看懂这条主线复位后执行顺序进入Reset_Handler调SystemInit()跳到__iar_program_start运行库初始化进入main()八、 C 风格 类比一次这段汇编粗略类比成 C可以想成voidReset_Handler(void){SystemInit();__iar_program_start();// 后面再到 main()}注意这只是帮助理解的类比不是完全等价。因为真实汇编里做的是通过寄存器取地址再跳转这是启动链路的一部分但从“作用”上看这样理解是对的。把这几句再拆成“寄存器视角”的执行动画版 比如执行前 R0 是什么执行后 R0 里变成什么PC 又是怎么变化的先认识两个主角1R0 是什么R0 是 ARM CPU 里的一个通用寄存器。你可以先把它理解成CPU 手边的一个小抽屉用来临时放数据或地址2PC 是什么PC Program Counter程序计数器。它表示CPU 下一条要执行的指令地址你可以把 PC 理解成“代码执行的指针”。CPU 执行代码时本质上就是看 PC 指向哪里执行那条指令然后修改 PC再去执行下一条整个执行过程像动画一样串起来起点已经进入Reset_Handler此时MSP 已经装好了PC 来到Reset_HandlerCPU准备执行复位处理逻辑动作 1LDR R0, SystemInit结果R0 SystemInit的地址PC 指向下一条动作 2BLX R0结果PC 跳到SystemInit开始执行SystemInit()SystemInit()执行完后返回动作 3返回后继续执行LDR R0, __iar_program_start结果R0 __iar_program_start的地址PC 指向下一条动作 4BX R0结果PC 直接跳到__iar_program_start后续由 IAR 运行库接管再往后才会到main()为什么汇编非得这么绕不直接写函数名调用因为汇编就是更底层它本质上就是在显式地告诉 CPU地址放到哪个寄存器再根据寄存器跳过去它不帮你隐藏细节。而 C/C 编译器会替你把这些底层动作都生成掉所以你平时看不到。最小表格指令R0 变化PC 变化作用LDR R0, SystemInitR0 SystemInit地址PC 到下一条准备调用SystemInitBLX R0R0 不一定变PC 跳到SystemInit调用SystemInit()LDR R0, __iar_program_startR0 __iar_program_start地址PC 到下一条准备跳到运行库入口BX R0R0 不一定变PC 跳到__iar_program_start转交控制权