dede 手機網(wǎng)站模板wordpress 安裝主題 ftp
鶴壁市浩天電氣有限公司
2026/01/24 09:02:46
dede 手機網(wǎng)站模板,wordpress 安裝主題 ftp,wordpress登錄注冊代碼,免費建立網(wǎng)站這篇文章將帶你寫第二個BootLoader程序#xff0c;對應的是以下那篇博文的第二種啟動方式#xff1a;APP原本設計在Flash中運行#xff0c;但實際執(zhí)行時會先將自身代碼復制到RAM#xff0c;然后在RAM中運行。 帶你搞懂BootLoader#xff08;一#xff09; 引言
那么是誰…這篇文章將帶你寫第二個BootLoader程序?qū)氖且韵履瞧┪牡牡诙N啟動方式APP原本設計在Flash中運行但實際執(zhí)行時會先將自身代碼復制到RAM然后在RAM中運行。帶你搞懂BootLoader一引言那么是誰將APP程序從Flash復制到內(nèi)存呢又復制到哪里呢APP程序從Flash復制到內(nèi)存可以由BootLoader復制也可以由APP自我復制關于復制到哪里先通過分析APP程序的反匯編文件來引入幾個概念int mymain() { char c A; while (1) { putchar(c); delay(1000000); if (c Z) c A; } return 0; }這里APP程序為了它的反匯編文件代碼更精簡我將它的main函數(shù)修改成了mymain函數(shù)去掉系統(tǒng)自帶的其他代碼這里要引入一個概念BL是相對跳轉(zhuǎn)指令相對跳轉(zhuǎn)是什么意思呢相對跳轉(zhuǎn)是指跳轉(zhuǎn)目標地址是相對于當前程序計數(shù)器PC的位置來計算的。具體來說BL指令執(zhí)行時處理器會計算當前PC值與偏移量OFFSET的和得到目標地址。計算公式為當前PC 當前PC OFFSET。舉個例子 假設當前PC值為0x1000偏移量OFFSET為0x200那么執(zhí)行BL指令后程序?qū)⑻D(zhuǎn)到0x1200地址處執(zhí)行。拿上面這個圖來說處理器執(zhí)行putchar函數(shù)怎么去跳轉(zhuǎn)到它的地址呢就是根據(jù)當前PC值加上一個偏移值跳轉(zhuǎn)到putchar函數(shù)的地址那么如果使用的是函數(shù)指針呢如下圖如果使用的是函數(shù)指針來調(diào)用putchar函數(shù)使用的就是絕對跳轉(zhuǎn)再來看看它的反匯編文件首先PC程序計數(shù)器將0x20000034地址處的值0x2000003d加載到寄存器r5中然后跳轉(zhuǎn)到r5的地址來執(zhí)行putchar函數(shù)加載的地址值0x2000003d的最低位是1這是ARM架構中Thumb指令集的標志位去掉最低位1后實際地址是0x2000003c這個0x2000003c就是putchar函數(shù)的真實入口地址。這種跳轉(zhuǎn)方式屬于絕對跳轉(zhuǎn)與相對跳轉(zhuǎn)不同絕對跳轉(zhuǎn)直接指定目標地址在ARM架構中函數(shù)指針調(diào)用通常采用這種絕對跳轉(zhuǎn)方式。這個mymain函數(shù)能夠調(diào)用putchar函數(shù)的前提是在內(nèi)存地址0x2000003c處必須存在有效的機器碼指令。這里涉及到幾個關鍵的技術細節(jié)當APP程序的鏈接地址被指定在RAM區(qū)域0x20000000時這意味著編譯器生成的機器碼是按照這個基地址進行地址計算的。所有函數(shù)調(diào)用和變量訪問的地址都是基于這個基地址的偏移量。如果程序沒有被實際加載到0x20000000開始的RAM區(qū)域那么通過函數(shù)指針進行的絕對地址跳轉(zhuǎn)如跳轉(zhuǎn)到0x2000003c就會失敗。該地址必須包含有效的putchar函數(shù)機器碼否則CPU會嘗試執(zhí)行無效指令導致系統(tǒng)崩潰。如果沒有正確復制程序函數(shù)指針跳轉(zhuǎn)會訪問到隨機數(shù)據(jù)或全0區(qū)域可能觸發(fā)硬件異常如HardFault在Cortex-M架構中會導致進入異常處理程序可以通過調(diào)試器檢查0x2000003c地址內(nèi)容驗證內(nèi)存保護單元(MPU)設置是否允許訪問該區(qū)域檢查程序是否被完整復制到目標區(qū)域確認該地址是否包含預期的機器碼實驗現(xiàn)在來做一個實驗如果APP程序不復制到指定的鏈接地址來運行程序而是燒寫bin文件在Flash上就地運行會發(fā)生什么事呢現(xiàn)在我修改一下APP程序的鏈接參數(shù)將它的只讀地址(ro)和讀寫地址(rw)都定位到RAM區(qū)域APP程序就要將它的ro段復制到0x20000000地址rw段復制到0x20000800地址第二個Bootloader程序就是APP程序從Flash自我復制到RAM區(qū)域但是一般程序都不會允許代碼段(ro)和數(shù)據(jù)段(rw)中間會有這么大的空內(nèi)存空間所以就要用散列文件來指定代碼段和數(shù)據(jù)段的鏈接地址所以這里寫一個散列文件來指定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 名稱可自定義通常表示“加載區(qū)域”。0x20000000該 Load Region 的加載地址。0x10000區(qū)域大小64KB。ER_IROM1Execution Region 名稱執(zhí)行區(qū)域。0x20000000執(zhí)行地址。此處加載地址執(zhí)行地址表示“加載后無需搬移直接在加載地址執(zhí)行”。*.o (RESET, First)將所有目標文件中名為RESET的段通常是向量表放在執(zhí)行區(qū)域最前面.ANY (RO)放置只讀代碼如函數(shù).ANY (XO)可執(zhí)行的只讀數(shù)據(jù)較少用.ANY (RW ZI)讀寫數(shù)據(jù)初始化變量和零初始化數(shù)據(jù)未初始化全局變量。只需要理解散列文件就是將APP程序的運行地址改為我自定義指定的地址然后在APP程序里面添加一個靜態(tài)全局數(shù)組這個數(shù)組會保存在數(shù)據(jù)段并且使用函數(shù)指針來執(zhí)行putchar函數(shù)打印數(shù)組里面的字符串#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; }編譯程序然后先燒寫B(tài)ootloader程序讓它跳轉(zhuǎn)到0x08040000地址再燒寫編譯APP程序生成的.bin文件到0x08040000地址看看會發(fā)生什么結果意料之中串口只打印了bootloader的字符串并沒有打印APP程序里面的字符串再看看APP程序的反匯編文件BLX r5這行代碼就是跳轉(zhuǎn)putchar函數(shù)的絕對跳轉(zhuǎn)指令跳轉(zhuǎn)過去之后發(fā)現(xiàn)指定的地址根本就沒有指令來運行所以這就導致了系統(tǒng)崩潰那么注釋掉函數(shù)指針的代碼程序是不是就可以正常運行了修改程序重新編譯燒寫.bin文件運行結果還是只打印了bootloader程序的字符串這是為什么呢bootloader程序會跳轉(zhuǎn)到0x08040000地址來取APP程序的向量表中的Reset_Handler地址來執(zhí)行Reset_Handler但是Reset_Handler現(xiàn)在的地址是在RAM區(qū)域APP程序并沒有將程序復制過去所以RAM區(qū)域沒有可運行的指令導致系統(tǒng)崩潰所以APP程序的start.s文件需要將Reset_Handler改為0x08040009地址系統(tǒng)會將這個地址賦給Reset_Handler函數(shù)來執(zhí)行Reset_Handler函數(shù)的代碼這樣程序就可以運行了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文件就應該在指定的鏈接地址來運行程序必須被復制到該地址才能正確運行所有函數(shù)調(diào)用和變量訪問都會基于這個基地址如果使用的是函數(shù)指針來進行函數(shù)調(diào)用使用的就是絕對跳轉(zhuǎn)來跳轉(zhuǎn)到函數(shù)的地址來執(zhí)行函數(shù)函數(shù)的地址都是在鏈接地址的范圍里面如果程序沒有被復制到該地址就無法正常運行如果不是用函數(shù)指針來進行函數(shù)調(diào)用C語言編譯器會優(yōu)先使用的就是相對跳轉(zhuǎn)來執(zhí)行函數(shù)相對跳轉(zhuǎn)依賴于當前指令指針的偏移量這種使用相對跳轉(zhuǎn)的程序可以放在任何地址都可以正常運行還有如果是長距離調(diào)用比如main函數(shù)地址是Afun函數(shù)地址是B如果B的地址遠大于A也是使用的絕對跳轉(zhuǎn)但這種情況幾乎不太可能鏈接地址就是程序運行的地址程序不在這個地址就無法運行第二個Bootloader程序就是將跳轉(zhuǎn)到APP程序APP程序?qū)㈡溄拥刂沸薷某蒖AM區(qū)域再自我復制程序到RAM來運行Bootloader程序設置跳轉(zhuǎn)到指定地址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
); /* 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程序現(xiàn)在分析一下整體架構芯片上電→ CPU 從0x08040000Flash讀取向量表跳轉(zhuǎn)到Reset_Handler仍在 Flash 中執(zhí)行調(diào)用copy_myself將整個 App 鏡像從 Flash 復制到 RAM跳轉(zhuǎn)到 RAM 中的mymain函數(shù)執(zhí)行第一部分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 數(shù)據(jù)已初始化會被鏈接器放入.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字節(jié)從fromFlash復制到toRAM。App 主循環(huán)(mymain)打印預定義字符串循環(huán)打印遞增字母A→Z使用函數(shù)指針fp調(diào)用putchar驗證 RAM 中代碼可正常執(zhí)行函數(shù)指針注意此函數(shù)將在RAM 中執(zhí)行而非 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 頂部作為棧頂?shù)?1 項Reset_Handler硬編碼為0x08040009Flash 地址為什么 Reset_Handler 地址寫死因為此時 App 還在 Flash 中必須先執(zhí)行 Flash 中的Reset_Handler來完成自搬移。搬移完成后才會跳到 RAM 中的mymain。EXPORT Reset_Handler [WEAK] IMPORT mymain IMPORT copy_myself IMPORT |Image$$ER_IROM1$$Length|導出Reset_Handler導入 C 函數(shù)mymain、copy_myself關鍵導入鏈接器生成的符號|Image$$ER_IROM1$$Length|表示當前鏡像長度單位字節(jié)。|Image$$...$$Length|是什么這是 ARM 鏈接器自動生成的符號表示某個執(zhí)行區(qū)域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 字節(jié)邊界目的獲取 App 在 Flash 中的起始地址即0x08040000。原理adr r0, Reset_Handler將Reset_Handler的地址加載到r0例如0x08040008bic r0, r0, #0xff清除低 8 位即 ~0xFF得到0x08040000為什么可行因為向量表必須位于 256 字節(jié)對齊地址VTOR 要求所以 App 起始地址低 8 位必為 0。ldr r1, __Vectors ; r1 __Vectors 的鏈接地址應為 0x20000000__Vectors在鏈接時被分配到 RAM 起始地址如0x20000000所以r1 0x20000000。這就是 RAM 中的目標地址。ldr r2, |Image$$ER_IROM1$$Length| ; LENGTH將 App 鏡像總長度字節(jié)加載到r2。BL copy_myself調(diào)用 C 函數(shù)copy_myself(r0, r1, r2)即copy_myself(0x08040000, 0x20000000, image_length);整個 App包括向量表、代碼、RW 數(shù)據(jù)從 Flash 復制到 RAM。注意此時 ZI 段未初始化變量不會被復制因為 Flash 中無內(nèi)容但 RAM 中原本就是 0上電清零或 Bootloader 清過通??山邮?。ldr pc, mymain直接將 PC 設置為mymain的地址跳轉(zhuǎn)到 RAM 中執(zhí)行。為什么不用BL mymainBL會保存返回地址到lr但這里不需要返回更重要的是mymain現(xiàn)在在 RAM 中而BL mymain會跳轉(zhuǎn)到Flash 中的 mymain鏈接地址ldr pc, mymain的妙處mymain是 mymain 的鏈接地址比如0x20000100因為我們剛把整個鏡像復制到 RAMRAM 中0x20000100處就是 mymain 的代碼所以ldr pc, mymain實際跳轉(zhuǎn)到RAM 中的 mymain