大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是IAR啟動(dòng)函數(shù)流程及其__low_level_init設(shè)計(jì)對(duì)函數(shù)重定向的影響。
上一篇文章 《IAR下RT-Thread工程自定義函數(shù)段重定向失效分析》 里我們找出了影響 IAR 鏈接器處理自定義程序段重定向的原因,主要跟 __low_level_init() 函數(shù)有關(guān),這個(gè)函數(shù)屬于 IAR 底層設(shè)計(jì),它在 IAR 啟動(dòng)函數(shù) __iar_program_start() 中會(huì)被自動(dòng)調(diào)用。
__iar_program_start() 是 IAR 標(biāo)準(zhǔn)啟動(dòng)函數(shù),也屬于 IDE 底層設(shè)計(jì)。在任何一個(gè) Cortex-M 廠商芯片的啟動(dòng)文件里(startup_xxDevice.s)都能看到它的身影,它是復(fù)位函數(shù) Reset_Handler() 和 主函數(shù) main() 之間的橋梁,今天我們就仔細(xì)說(shuō)說(shuō)這個(gè)啟動(dòng)函數(shù)以及其中 __low_level_init 設(shè)計(jì):
Note 1:閱讀本文前需要對(duì) 《IAR鏈接文件(.icf)》、《IAR映射文件(.map)》 這兩種文件有所了解。Note 2:本文使用的 IAR EWARM 軟件版本是 v9.10.2。
一、通用芯片上電啟動(dòng)流程
在深入挖掘 IAR 啟動(dòng)函數(shù)源代碼之前,有必要先整體了解一下通用的芯片上電啟動(dòng)流程,即進(jìn)入用戶 main 函數(shù)之前內(nèi)核必須要做的事情,注意這里并不包含芯片底層外設(shè)的初始化(這是因芯片而異的)。
通用啟動(dòng)流程簡(jiǎn)單來(lái)說(shuō)分為如下四步:第一步是從 ROM 區(qū)域中斷向量表里獲取入口函數(shù)開(kāi)始執(zhí)行,設(shè)置好初始棧指針,有了正確的棧,內(nèi)核就具備函數(shù)跳轉(zhuǎn)執(zhí)行的能力了。第二步和第三步是全局變量的初始化(將全局變量初值從 ROM 區(qū)域拷貝到變量所鏈接的 RAM 區(qū)域),全局變量初始化完成,應(yīng)用程序就有了正確的初始態(tài),最后一步就是跳轉(zhuǎn)到 main 函數(shù)。
二、從源代碼角度看啟動(dòng)流程
在上一節(jié)通用啟動(dòng)流程的指導(dǎo)下,我們還需要增加一些 MCU 外設(shè)相關(guān)的初始化便形成了完整的芯片啟動(dòng)流程,現(xiàn)在我們從源代碼角度再來(lái)看一下具體實(shí)現(xiàn)。
2.1 典型的 Cortex-M 復(fù)位函數(shù)
我們知道復(fù)位函數(shù) Reset_Handler() 是芯片上電啟動(dòng)執(zhí)行的第一個(gè)函數(shù)(有時(shí)又叫入口函數(shù)),它完成了進(jìn)入用戶 main() 函數(shù)之前的全部動(dòng)作。隨便下載一家 Cortex-M 廠商芯片 SDK 包,找到 IAR 版啟動(dòng)文件,其復(fù)位函數(shù)流程都差不多,這是 Cortex-M 內(nèi)核架構(gòu)決定的。
如下是典型的復(fù)位函數(shù)代碼。復(fù)位函數(shù)里的操作包括關(guān)全局中斷、設(shè)置中斷向量表首地址、設(shè)置棧頂、系統(tǒng)初始化、開(kāi)全局中斷、進(jìn)啟動(dòng)函數(shù)。其中系統(tǒng)初始化 SystemInit() 函數(shù)是因芯片而異的,各廠商 SDK 里會(huì)有具體源代碼實(shí)現(xiàn)(一般在 system_xxDevice.c 文件里),這里面主要做芯片硬件層面的初始化,比如關(guān)看門(mén)狗、Cache 初步設(shè)置等,保證內(nèi)核不受硬件模塊狀態(tài)影響,能正常執(zhí)行指令。
????????THUMB
????????PUBWEAK?Reset_Handler
????????SECTION?.text:CODE:REORDER:NOROOT(2)
Reset_Handler
????????CPSID???I???????????????;?Mask?interrupts
????????LDR?????R0,?=0xE000ED08
????????LDR?????R1,?=__vector_table
????????STR?????R1,?[R0]
????????LDR?????R2,?[R1]
????????MSR?????MSP,?R2
????????LDR?????R0,?=SystemInit
????????BLX?????R0
????????CPSIE???I???????????????;?Unmask?interrupts
????????LDR?????R0,?=__iar_program_start
????????BX??????R0
2.2 __iar_program_start() 到底干了啥?
上一小節(jié)里我們知道復(fù)位函數(shù)里的最后一個(gè)動(dòng)作就是跳轉(zhuǎn)到啟動(dòng)函數(shù),將內(nèi)核執(zhí)行權(quán)交給 __iar_program_start(),這個(gè)啟動(dòng)函數(shù)源代碼并不在廠商 SDK 包里,而在 IAR 安裝目錄下,因?yàn)樗?IAR 的通用底層設(shè)計(jì)。
為了找到 __iar_program_start() 的源代碼,我們可以隨便編譯一個(gè) SDK 例程(痞子衡選擇的是 SDK_2.11.0_MIMXRT1170-EVKboardsevkmimxrt1170demo_appshello_worldcm7iar),查看其對(duì)應(yīng)映射文件(.map),發(fā)現(xiàn)啟動(dòng)函數(shù)來(lái)自于 cstartup_M.o,然后我們?cè)?IAR 安裝目錄下搜索 cstartup_M.c/.s 文件,最終我們?cè)谌缦侣窂秸业搅藛?dòng)函數(shù)相關(guān)的全部源文件。
IAR SystemsEmbedded Workbench 9.10.2armsrclibthumbcstartup_M.s
IAR SystemsEmbedded Workbench 9.10.2armsrclibthumbcmain.s
IAR SystemsEmbedded Workbench 9.10.2armsrclibruntimelow_level_init.c
IAR SystemsEmbedded Workbench 9.10.2armsrclibinitdata_init.c
結(jié)合啟動(dòng)函數(shù)相關(guān)源文件里的代碼,我們終于搞清了啟動(dòng)函數(shù)全部流程,也找到了我們最關(guān)心的 __low_level_init() 函數(shù)調(diào)用位置,它在 .data/.bss/.textrw 段初始化之前被執(zhí)行,所以它的功能應(yīng)該跟 SystemInit() 差不多。默認(rèn) __low_level_init() 函數(shù)是空的,返回值是 1(返回值 0/1 決定后面的 __iar_data_init3() 要不要執(zhí)行,1 是要執(zhí)行),如果你想激活這個(gè)函數(shù),需要在自己的源文件里重新定義實(shí)現(xiàn),IAR 編譯時(shí)會(huì)優(yōu)先引用重新定義的版本。
__iar_program_start() ->
__cmain() ->
__low_level_init() -> // 底層初始化,默認(rèn)是個(gè)空函數(shù)
__iar_data_init3() -> // .data, .bss, .textrw 段初始化
main()
2.3 __low_level_init() 設(shè)計(jì)注意事項(xiàng)
在 EWARM_DevelopmentGuide.ENU 手冊(cè)里搜索 __low_level_init,我們可以找到這個(gè)函數(shù)的設(shè)計(jì)初衷,官方說(shuō)法是為了給應(yīng)用程序一個(gè)早期初始化的機(jī)會(huì),本質(zhì)上就是跟 SystemInit() 一樣的作用,但是因?yàn)檫@個(gè) __low_level_init 函數(shù)只在 IAR 環(huán)境下適用,如果用了它,應(yīng)該程序代碼就不具備跨 IDE 的通用性,因此在各廠商 SDK 包里選擇了統(tǒng)一定義的 SystemInit() 來(lái)完成早期初始化工作。
IAR 開(kāi)發(fā)手冊(cè):IAR SystemsEmbedded Workbench 9.10.2armdocEWARM_DevelopmentGuide.ENU
EWARM_DevelopmentGuide.ENU 手冊(cè)里還特別提了幾點(diǎn)跟 __low_level_init 相關(guān)的注意事項(xiàng),均跟 IAR 鏈接器所識(shí)別的 initialize by copy 鏈接語(yǔ)法有關(guān),概括來(lái)說(shuō)就是因?yàn)?__low_level_init 是在 .data/.bss/.textrw 段初始化之前被執(zhí)行的,所以其代碼本身及其調(diào)用的全部代碼都不受 initialize by copy 作用,也就是這些代碼都不應(yīng)是 RAMFUNC 型。
Note: 更準(zhǔn)確地說(shuō) initialize by copy 作用范圍其實(shí)是 __iar_data_init3() 之后的代碼
三、一個(gè) __low_level_init() 相關(guān)的重定向?qū)嶒?yàn)
最后我們?cè)僮鰝€(gè) __low_level_init() 相關(guān)的小實(shí)驗(yàn),在 SDK_2.11.0_MIMXRT1170-EVKboardsevkmimxrt1170demo_appshello_worldcm7iar 例程基礎(chǔ)上(flexspi_nor_debug build),創(chuàng)建一個(gè)包含如下內(nèi)容的 ramfunc_test.c 源文件,并將其添加進(jìn)工程編譯。
ramfunc_test1/3() 函數(shù)放入自定義程序段 CodeQuickAccess,ramfunc_test2/4() 函數(shù)放到默認(rèn) .textrw 段,然后重寫(xiě) __low_level_init() 函數(shù),在 __low_level_init() 函數(shù)里分別調(diào)用 ramfunc_test1/2/3/4(),其中 ramfunc_test1/2() 函數(shù)的調(diào)用在 __iar_data_init3() 前面,ramfunc_test1/2() 函數(shù)的調(diào)用在 __iar_data_init3() 后面。
void?ramfunc_test1(void)?@"CodeQuickAccess"
{
????__NOP();
}
__ramfunc?void?ramfunc_test2(void)
{
????__NOP();
}
void?ramfunc_test3(void)?@"CodeQuickAccess"
{
????__NOP();
}
__ramfunc?void?ramfunc_test4(void)
{
????__NOP();
}
//?重定義此函數(shù),讓?IAR?編譯器使用這個(gè)版本,而不是默認(rèn)版本
int?__low_level_init(void)
{
????extern?void?__iar_data_init3(void);
????ramfunc_test1();
????ramfunc_test2();
????
????//?這里增加?.data/.bss/.textrw?的初始化調(diào)用,
????//??便于區(qū)分?ramfunc_test1/2?和?ramfunc_test3/4?位置
????__iar_data_init3();
????
????ramfunc_test3();
????ramfunc_test4();
????
????return?0;
}
編譯鏈接修改后的測(cè)試工程,查看其映射文件,以及在板子上實(shí)測(cè),得到如下結(jié)果:
結(jié)論1:放入自定義程序段的函數(shù),無(wú)論其調(diào)用位置在 __iar_data_init3() 之前還是之后,一律被 initialize by copy 忽略,函數(shù)直接鏈接在目標(biāo) RAM 區(qū),函數(shù)重定向無(wú)效;結(jié)論2:放入默認(rèn) .textrw 段的函數(shù),如果其調(diào)用位置在 __iar_data_init3() 之后,能夠被 initialize by copy 作用,函數(shù)重定向生效;結(jié)論3:放入默認(rèn) .textrw 段的函數(shù),如果其調(diào)用位置在 __iar_data_init3() 之前,從映射文件里看其能夠被 initialize by copy 作用,但在板子上實(shí)測(cè),發(fā)現(xiàn)執(zhí)行到該函數(shù)時(shí)返回會(huì)產(chǎn)生總線錯(cuò)誤,因此函數(shù)重定向也是無(wú)效的;
*******************************************************************************
*** PLACEMENT SUMMARY
***
"P1": place in [from 0x3000'2000 to 0x30fb'ffff] { ro };
"P2": place in [from 0x2000'0000 to 0x2003'fbff] { rw };
"P8": place in [from 0x0 to 0x3'ffff] { section CodeQuickAccess };
initialize by copy { rw, section .textrw, section CodeQuickAccess };
Section Kind Address Size Object
------- ---- ------- ---- ------
"P8": 0x8
CodeQuickAccess ro code 0x0 0x8 ramfunc_test.o [6]
"P2-P3|P5|P9", part 1 of 2: 0xc
RW 0x2000'0000 0xc <Block>
.textrw inited 0x2000'0004 0x8 ramfunc_test.o [6]
"P1": 0x443a
.text ro code 0x3000'63d0 0x1a ramfunc_test.o [6]
*******************************************************************************
*** MODULE SUMMARY
***
Module ro code rw code ro data rw data
------ ------- ------- ------- -------
ramfunc_test.o 34 8 8
*******************************************************************************
*** ENTRY LIST
***
Entry Address Size Type Object
---- ------- ---- ---- ------
ramfunc_test1 0x1 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test2 0x2000'0005 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test3 0x5 0x4 Code Gb ramfunc_test.o [6]
ramfunc_test4 0x2000'0009 0x4 Code Gb ramfunc_test.o [6]
至此,IAR啟動(dòng)函數(shù)流程及其__low_level_init設(shè)計(jì)對(duì)函數(shù)重定向的影響痞子衡便介紹完畢了,掌聲在哪里~~~