dede 手机网站模板wordpress 安装主题 ftp
dede 手机网站模板,wordpress 安装主题 ftp,wordpress登录注册代码,免费建立网站这篇文章将带你写第二个BootLoader程序#xff0c;对应的是以下那篇博文的第二种启动方式#xff1a;APP原本设计在Flash中运行#xff0c;但实际执行时会先将自身代码复制到RAM#xff0c;然后在RAM中运行。 带你搞懂BootLoader#xff08;一#xff09; 引言
那么是谁…这篇文章将带你写第二个BootLoader程序对应的是以下那篇博文的第二种启动方式APP原本设计在Flash中运行但实际执行时会先将自身代码复制到RAM然后在RAM中运行。带你搞懂BootLoader一引言那么是谁将APP程序从Flash复制到内存呢又复制到哪里呢APP程序从Flash复制到内存可以由BootLoader复制也可以由APP自我复制关于复制到哪里先通过分析APP程序的反汇编文件来引入几个概念int mymain() { char c A; while (1) { putchar(c); delay(1000000); if (c Z) c A; } return 0; }这里APP程序为了它的反汇编文件代码更精简我将它的main函数修改成了mymain函数去掉系统自带的其他代码这里要引入一个概念BL是相对跳转指令相对跳转是什么意思呢相对跳转是指跳转目标地址是相对于当前程序计数器PC的位置来计算的。具体来说BL指令执行时处理器会计算当前PC值与偏移量OFFSET的和得到目标地址。计算公式为当前PC 当前PC OFFSET。举个例子 假设当前PC值为0x1000偏移量OFFSET为0x200那么执行BL指令后程序将跳转到0x1200地址处执行。拿上面这个图来说处理器执行putchar函数怎么去跳转到它的地址呢就是根据当前PC值加上一个偏移值跳转到putchar函数的地址那么如果使用的是函数指针呢如下图如果使用的是函数指针来调用putchar函数使用的就是绝对跳转再来看看它的反汇编文件首先PC程序计数器将0x20000034地址处的值0x2000003d加载到寄存器r5中然后跳转到r5的地址来执行putchar函数加载的地址值0x2000003d的最低位是1这是ARM架构中Thumb指令集的标志位去掉最低位1后实际地址是0x2000003c这个0x2000003c就是putchar函数的真实入口地址。这种跳转方式属于绝对跳转与相对跳转不同绝对跳转直接指定目标地址在ARM架构中函数指针调用通常采用这种绝对跳转方式。这个mymain函数能够调用putchar函数的前提是在内存地址0x2000003c处必须存在有效的机器码指令。这里涉及到几个关键的技术细节当APP程序的链接地址被指定在RAM区域0x20000000时这意味着编译器生成的机器码是按照这个基地址进行地址计算的。所有函数调用和变量访问的地址都是基于这个基地址的偏移量。如果程序没有被实际加载到0x20000000开始的RAM区域那么通过函数指针进行的绝对地址跳转如跳转到0x2000003c就会失败。该地址必须包含有效的putchar函数机器码否则CPU会尝试执行无效指令导致系统崩溃。如果没有正确复制程序函数指针跳转会访问到随机数据或全0区域可能触发硬件异常如HardFault在Cortex-M架构中会导致进入异常处理程序可以通过调试器检查0x2000003c地址内容验证内存保护单元(MPU)设置是否允许访问该区域检查程序是否被完整复制到目标区域确认该地址是否包含预期的机器码实验现在来做一个实验如果APP程序不复制到指定的链接地址来运行程序而是烧写bin文件在Flash上就地运行会发生什么事呢现在我修改一下APP程序的链接参数将它的只读地址(ro)和读写地址(rw)都定位到RAM区域APP程序就要将它的ro段复制到0x20000000地址rw段复制到0x20000800地址第二个Bootloader程序就是APP程序从Flash自我复制到RAM区域但是一般程序都不会允许代码段(ro)和数据段(rw)中间会有这么大的空内存空间所以就要用散列文件来指定代码段和数据段的链接地址所以这里写一个散列文件来指定APP程序的链接地址; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x20000000 0x10000 { ; load region size_region ER_IROM1 0x20000000 0x10000 { ; load address execution address *.o (RESET, First) .ANY (RO) .ANY (XO) .ANY (RW ZI) } }LR_IROM1Load Region 名称可自定义通常表示“加载区域”。0x20000000该 Load Region 的加载地址。0x10000区域大小64KB。ER_IROM1Execution Region 名称执行区域。0x20000000执行地址。此处加载地址执行地址表示“加载后无需搬移直接在加载地址执行”。*.o (RESET, First)将所有目标文件中名为RESET的段通常是向量表放在执行区域最前面.ANY (RO)放置只读代码如函数.ANY (XO)可执行的只读数据较少用.ANY (RW ZI)读写数据初始化变量和零初始化数据未初始化全局变量。只需要理解散列文件就是将APP程序的运行地址改为我自定义指定的地址然后在APP程序里面添加一个静态全局数组这个数组会保存在数据段并且使用函数指针来执行putchar函数打印数组里面的字符串#include uart.h static char buf[100] this is a test; void delay(int d) { while(d--); } int mymain() { char c A; int (*fp)(char c); fp putchar; //putstr(buf); while (1) { fp(c); //putchar(c); delay(1000000); if (c Z) c A; } return 0; }编译程序然后先烧写Bootloader程序让它跳转到0x08040000地址再烧写编译APP程序生成的.bin文件到0x08040000地址看看会发生什么结果意料之中串口只打印了bootloader的字符串并没有打印APP程序里面的字符串再看看APP程序的反汇编文件BLX r5这行代码就是跳转putchar函数的绝对跳转指令跳转过去之后发现指定的地址根本就没有指令来运行所以这就导致了系统崩溃那么注释掉函数指针的代码程序是不是就可以正常运行了修改程序重新编译烧写.bin文件运行结果还是只打印了bootloader程序的字符串这是为什么呢bootloader程序会跳转到0x08040000地址来取APP程序的向量表中的Reset_Handler地址来执行Reset_Handler但是Reset_Handler现在的地址是在RAM区域APP程序并没有将程序复制过去所以RAM区域没有可运行的指令导致系统崩溃所以APP程序的start.s文件需要将Reset_Handler改为0x08040009地址系统会将这个地址赋给Reset_Handler函数来执行Reset_Handler函数的代码这样程序就可以运行了PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0x200000000x10000 DCD 0x08040009 ;Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT mymain ;LDR SP, (0x200000000x10000) BL mymain ENDP END这样程序就可以正常运行了总结我使用散列文件修改了APP程序的链接地址如下图那么app.bin文件就应该在指定的链接地址来运行程序必须被复制到该地址才能正确运行所有函数调用和变量访问都会基于这个基地址如果使用的是函数指针来进行函数调用使用的就是绝对跳转来跳转到函数的地址来执行函数函数的地址都是在链接地址的范围里面如果程序没有被复制到该地址就无法正常运行如果不是用函数指针来进行函数调用C语言编译器会优先使用的就是相对跳转来执行函数相对跳转依赖于当前指令指针的偏移量这种使用相对跳转的程序可以放在任何地址都可以正常运行还有如果是长距离调用比如main函数地址是Afun函数地址是B如果B的地址远大于A也是使用的绝对跳转但这种情况几乎不太可能链接地址就是程序运行的地址程序不在这个地址就无法运行第二个Bootloader程序就是将跳转到APP程序APP程序将链接地址修改成RAM区域再自我复制程序到RAM来运行Bootloader程序设置跳转到指定地址0x08040000运行APP程序----------------------main.c----------------------- #include uart.h extern void start_app(unsigned int new_vector); void delay(int d) { while(d--); } int mymain() { unsigned int new_vector 0x08040000; uart_init(); putstr(bootloader\r\n); /* start app */ start_app(new_vector); return 0; } ------------------start.s--------------------------- PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0 DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT mymain LDR SP, (0x200000000x10000) BL mymain ENDP start_app PROC EXPORT start_app ; set vector base address as 0x08040000 ldr r3, 0xE000ED08 str r0, [r3] ldr sp, [r0] ; read val from addr 0x08040000 ldr r1, [r0, #4] ; read val from addr 0x08040004 BX r1 ENDP END这个程序在带你搞懂BootLoader(二)-第一个BootLoader里面有讲解这里就不多说了APP程序现在分析一下整体架构芯片上电→ CPU 从0x08040000Flash读取向量表跳转到Reset_Handler仍在 Flash 中执行调用copy_myself将整个 App 镜像从 Flash 复制到 RAM跳转到 RAM 中的mymain函数执行第一部分App 主逻辑#include uart.h static char buf[100] this is app; void copy_myself(int *from, int *to, int len) { // 从哪里到哪里, 多长 ? int i; for (i 0; i len/41; i) { to[i] from[i]; } } void delay(int d) { while(d--); } int mymain() { char c A; int (*fp)(char c); fp putchar; putstr(buf); while (1) { fp(c); putchar(c); delay(1000000); if (c Z) c A; } return 0; }static char buf[100] this is app;定义一个初始化的全局字符串。关键点这是一个RW 数据已初始化会被链接器放入.data段在 Flash 中有初始值在 RAM 中有运行副本。当 App 被复制到 RAM 后这个变量也会在 RAM 中存在且值正确。void copy_myself(int *from, int *to, int len) { int i; for (i 0; i len/41; i) { to[i] from[i]; } }功能将len字节从fromFlash复制到toRAM。App 主循环(mymain)打印预定义字符串循环打印递增字母A→Z使用函数指针fp调用putchar验证 RAM 中代码可正常执行函数指针注意此函数将在RAM 中执行而非 Flash第二部分汇编代码自搬移启动PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0x200000000x10000 DCD 0x08040009 ;Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT mymain IMPORT copy_myself IMPORT |Image$$ER_IROM1$$Length| adr r0, Reset_Handler ; r00x08040000 bic r0, r0, #0xff ldr r1, __Vectors ; r10x20000000 ldr r2, |Image$$ER_IROM1$$Length| ; LENGTH BL copy_myself ;LDR SP, (0x200000000x10000) ;BL mymain ldr pc, mymain ENDP END_Vectors DCD 0x200000000x10000 DCD 0x08040009 ;Reset_Handler第 0 项MSP0x20010000→ RAM 顶部作为栈顶第 1 项Reset_Handler硬编码为0x08040009Flash 地址为什么 Reset_Handler 地址写死因为此时 App 还在 Flash 中必须先执行 Flash 中的Reset_Handler来完成自搬移。搬移完成后才会跳到 RAM 中的mymain。EXPORT Reset_Handler [WEAK] IMPORT mymain IMPORT copy_myself IMPORT |Image$$ER_IROM1$$Length|导出Reset_Handler导入 C 函数mymain、copy_myself关键导入链接器生成的符号|Image$$ER_IROM1$$Length|表示当前镜像长度单位字节。|Image$$...$$Length|是什么这是 ARM 链接器自动生成的符号表示某个执行区域ER的大小。在 scatter 文件中若定义了ER_IROM1链接器会生成Image$$ER_IROM1$$BaseImage$$ER_IROM1$$LimitImage$$ER_IROM1$$Length Limit - Baseadr r0, Reset_Handler ; r0 当前 PC 相对地址即 Reset_Handler 的地址 bic r0, r0, #0xff ; 清除低 8 位对齐到 256 字节边界目的获取 App 在 Flash 中的起始地址即0x08040000。原理adr r0, Reset_Handler将Reset_Handler的地址加载到r0例如0x08040008bic r0, r0, #0xff清除低 8 位即 ~0xFF得到0x08040000为什么可行因为向量表必须位于 256 字节对齐地址VTOR 要求所以 App 起始地址低 8 位必为 0。ldr r1, __Vectors ; r1 __Vectors 的链接地址应为 0x20000000__Vectors在链接时被分配到 RAM 起始地址如0x20000000所以r1 0x20000000。这就是 RAM 中的目标地址。ldr r2, |Image$$ER_IROM1$$Length| ; LENGTH将 App 镜像总长度字节加载到r2。BL copy_myself调用 C 函数copy_myself(r0, r1, r2)即copy_myself(0x08040000, 0x20000000, image_length);整个 App包括向量表、代码、RW 数据从 Flash 复制到 RAM。注意此时 ZI 段未初始化变量不会被复制因为 Flash 中无内容但 RAM 中原本就是 0上电清零或 Bootloader 清过通常可接受。ldr pc, mymain直接将 PC 设置为mymain的地址跳转到 RAM 中执行。为什么不用BL mymainBL会保存返回地址到lr但这里不需要返回更重要的是mymain现在在 RAM 中而BL mymain会跳转到Flash 中的 mymain链接地址ldr pc, mymain的妙处mymain是 mymain 的链接地址比如0x20000100因为我们刚把整个镜像复制到 RAMRAM 中0x20000100处就是 mymain 的代码所以ldr pc, mymain实际跳转到RAM 中的 mymain