分析ARM啟動(dòng)代碼和中斷處理過(guò)程
1. 定義程序入口點(diǎn)
2. 設置異常向量表
3. 初始化存儲系統
4. 初始化用戶(hù)程序的執行環(huán)境
5. 初始化堆棧指針寄存器,改變處理器的模式
6. 設置FIQ/IRQ中斷處理程序入口
7. 進(jìn)入C程序
1)編譯器選擇
GBLL THUMBCODE
[ {CONFIG} = 16
THUMBCODE SETL {TRUE}
CODE32
|
THUMBCODE SETL {FALSE}
]
因為處理器分為16位 32位兩種工作狀態(tài),程序的編譯器也是分16位和32兩種編譯方式,所以這段程序用于根據處理器工作狀態(tài)確定編譯器編譯方式,程序不難理解,主要解釋一下 符號“[ | ]”的意思,上面的程序是指:
if({CONFIG} = 16 )
{ THUMBCODE SETL {TRUE}
CODE32 }
else
THUMBCODE SETL {FALSE}
還 是沒(méi)有不明白CONFIG怎么區分是16位還是32位?哪里決定它的取值?應該是編譯器指定的這個(gè)值。
2)宏定義
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=$HandleLabel
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
MEND
$HandlerLabel是宏的地址標 號,HANDLER是宏名,$HandleLabel是宏的參數,$標號在宏指令展開(kāi)時(shí),標號會(huì )替換為用戶(hù)定義的符號。在此后,所有遇 到$HandlerLabel HANDLER $HandleLabel這種形式的表達式都會(huì )被展開(kāi)成$HandlerLabel到MEND之間的函數。
例如:ADC_IRQ HANDLER HandleADC即代表如下函數,語(yǔ)句ldr pc,=ADC_IRQ的作用也就是跳轉到這個(gè)函數:
ADC_IRQ
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=HandleADC
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
這段程序用于把ADC中斷服務(wù)程序的首地址裝載到pc中,跳轉到中斷處理函數,稱(chēng)之為 “加載程序”。HandleADC是一個(gè)地址標號,它的內容就是ADC中斷服務(wù)程序的地址標號,即文件最后的那個(gè)表HandleADC # 4所示,將HandleADC # 4中的4換成中斷服務(wù)程序的地址標號即是,程序在這里定義了一個(gè)數據區,存放各種中斷服務(wù)程序的首地址。每個(gè)字空間都有一個(gè)標號,以Handle***命 名。
3)寄存器及堆棧設置
按照上面的順序,可以比較容易讀懂啟動(dòng)代碼的作用,主要就是通過(guò)設置特殊功能寄存器來(lái)達到對系統參數的 設定。依次禁止看門(mén)狗,中斷,設定時(shí)鐘控制寄存器,存儲器控制寄存器等等。
由于各個(gè)工作模式下的堆棧指針是相互獨立的,所以要分別進(jìn)入各個(gè)模式下 設置其堆棧指針,基本上都差不多,比如未定義指令模式下的設置:
mrs r0,cpsr
bic r0,r0,#MODEMASK
orr r1,r0,#UNDEFMODE|NOINT
msr cpsr_cxsf,r1
ldr sp,=UndefStack
UnderStack是在程序后面用UnderStack # 256建立的一個(gè)堆??臻g的首地址,這部分空間建立在RAM中,256字節空間的堆棧大小。
4)初始化用戶(hù)程序的執行環(huán)境
之前在 44B0里的啟動(dòng)代碼里還有包括把ROM里的程序拷貝到RAM中并跳轉到RAM運行程序,也就是把加載狀態(tài)下的程序按照編譯連接時(shí)的設置重新排布成運行時(shí) 的程序狀態(tài),以達到符號能夠正確連接的目的,這里是涉及到了所謂的映像文件,但是2410這里沒(méi)有這一段,即程序的加載態(tài)就是它的運行態(tài),所以要求燒寫(xiě)程 序時(shí)必須要把它燒寫(xiě)在設置的RO地址上,否則程序將不能正確執行。下面這段程序實(shí)現RW數據初始化,只是把數據段復制到高地址,如果沒(méi)有設置RW的話(huà)這段 代碼也不會(huì )執行。
;Copy and paste RW data/zero initialized data
ldr r0, =|Image$$RO$$Limit| ; Get pointer to ROM data
ldr r1, =|Image$$RW$$Base| ; and RAM copy
ldr r3, =|Image$$ZI$$Base|
;Zero init base => top of initialised data
cmp r0, r1 ; Check that they are different
beq %F2
1
cmp r1, r3
ldrcc r2, [r0], #4
strcc r2, [r1], #4
bcc %B1
2
ldr r1, =|Image$$ZI$$Limit|
mov r2, #0
3
cmp r3, r1 ; Zero init
strcc r2, [r3], #4
bcc %B3
b %F1(B1)的意思是在臨近的地址標號跳轉,F是向后尋找,B是向前尋找。
5) 說(shuō)說(shuō)映象文件
用ADS編譯產(chǎn)生的映像文件有.axf、.bin、.hex等等格式,就拿要直接燒進(jìn)Flash里的.bin文件來(lái)說(shuō),在其他書(shū)上看 到有這么句話(huà)“映像文件一般由域組成,域由最多三個(gè)輸出段(RO,RW,ZI)組成,輸出段又由輸入段組成。”
對于這段話(huà),前兩句是比較好理解 的,域就是整個(gè)映像文件,對于大部分程序來(lái)說(shuō)就只有一個(gè)域,也就是燒進(jìn)Flash里的那部分東東,稱(chēng)作加載域;輸出段就是用AREA定義的RO,Rw,一 般就這兩個(gè),拿前面的bootloader來(lái)說(shuō),整體框架是這樣的:
AREA Init,CODE,READONLY ;<--RO段
ENTRY
Entry ;<--CODE部分
… …
SMRDATA DATA ;<--DATA部分
… …
AREA RamData, DATA, READWRITE ;<--RW段
… …
然而對于 輸出段又由輸入段組成卻著(zhù)實(shí)糊涂了好一陣,輸入段是指源程序的代碼(CODE)部分和數據(DATA)部分。上面這個(gè)框架中,在RO輸出段的Entry下 開(kāi)始一系列的匯編指令操作,這個(gè)應該是CODE輸入段,而SMRDATA DATA引領(lǐng)DCD用于開(kāi)辟一片數據存儲空間,這部分應該是DATA輸入段,它與RW里的數據不同之處在于這部分數據不能被修改。
在A(yíng)DS編譯前 在A(yíng)RM-Linker里的Ro_Base和Rw_Base兩個(gè)地址值,就是指兩個(gè)輸出段的起始地址,即程序是按照你設置的這種方式排布在內存中的,各個(gè) 地址標號根據這兩個(gè)值確定。然而,用Ultraedit打開(kāi)bin文件卻發(fā)現其實(shí)Rw是跟在Ro后面的,也就是說(shuō),這兩個(gè)段并沒(méi)有按照你設置的地址起始, 由此引出映像文件的加載域和運行時(shí)域兩個(gè)概念。
評論