完全使用匯編語(yǔ)言來(lái)編寫(xiě)程序會(huì)非常的繁瑣,因此通常情況下,只是使用匯編程序來(lái)完成少量必須由匯編程序才能完成的工作,而其它工作則由C語(yǔ)言程序來(lái)完成。這樣一來(lái),我們實(shí)際上就是在進(jìn)行匯編和C的混合編程,甚至同一個(gè)程序的匯編源文件和C源文件是由不同的程序員編寫(xiě)的。在這種情況下,要想使不同程序員編寫(xiě)的匯編代碼和C代碼能耦合的很好,則必須有一個(gè)雙方都必須遵守的規(guī)則,這就是ATPCS規(guī)則。
第一部分內(nèi)容:ATPCS規(guī)則
ATPCS(ARM-Thumb Produce Call Standard)是ARM程序和Thumb程序中子程序調(diào)用的基本規(guī)則,目的是為了使單獨(dú)編譯的C語(yǔ)言程序和匯編程序之間能夠相互調(diào)用。這些基本規(guī)則包括子程序調(diào)用過(guò)程中寄存器的使用規(guī)則、數(shù)據(jù)棧的使用規(guī)則和參數(shù)的傳遞規(guī)則。
1、寄存器的使用規(guī)則:
a)、寄存器R0 -- R11被分為2組:a1 -- a4,v1 – v8。所以對(duì)于兼容ATPCS的編譯器而言,在編程的時(shí)候可以使用a1替換R0
b)、除了R13 -- R15有別名外,對(duì)于兼容ATPCS的編譯器而言,也可以使用其它寄存器的別名:wr, sb, sl, fp, ip,它們都有自己的一些特殊用法
c)、寄存器R0 -- R3用于傳遞子程序的參數(shù)和返回結(jié)果(詳見(jiàn)本文后部)
d)、寄存器a1 -- a4和ip是scratch寄存器(即:臨時(shí)寄存器),其值在進(jìn)行子程序調(diào)用時(shí)不需要保存和恢復(fù)。(詳見(jiàn)本文后部)
2、數(shù)據(jù)棧的使用規(guī)則
在“其它尋址模式與其它指令”一文中,我講到棧有4種類(lèi)型:
FD (Full Descending) 滿遞減
ED (Empty Descending)空遞減
FA (Full Ascending) 滿遞增
EA (Empty Ascending) 空遞增
ATPCS規(guī)定數(shù)據(jù)棧為FD(滿遞減)類(lèi)型,并且對(duì)數(shù)據(jù)棧的操作是8字節(jié)對(duì)齊的。這意味著我們?cè)诰帉?xiě)匯編子程序時(shí),如果要進(jìn)行出棧和入棧操作,則必須使用ldmfd和stmfd指令(或者ldmia和ldmdb);而兼容ATPCS的編譯器在編譯C代碼時(shí),也必須這樣做。
3、參數(shù)的傳遞規(guī)則
參數(shù)個(gè)數(shù)固定的子程序參數(shù)傳遞規(guī)則:
前4個(gè)整數(shù)參數(shù),通過(guò)寄存器R0~R3來(lái)傳遞。其他參數(shù)通過(guò)數(shù)據(jù)棧傳遞。
子程序結(jié)果返回規(guī)則
結(jié)果為一個(gè)32位的整數(shù)時(shí),必須通過(guò)寄存器R0返回;結(jié)果為一個(gè)64位整數(shù)時(shí),通過(guò)寄存器R0和R1返回,依次類(lèi)推。
下面看一下編譯器對(duì)于這幾個(gè)規(guī)則的遵循(實(shí)現(xiàn))情況。
參數(shù)傳遞(4個(gè)參數(shù))以及結(jié)果返回 :很顯然,主調(diào)程序在調(diào)用子程序(即:bl func1)之前,將要傳遞給子程序的4個(gè)參數(shù)準(zhǔn)備在了R0~R3中,從而使得子程序可以通過(guò)該4個(gè)寄存器獲得轉(zhuǎn)遞給它的參數(shù)(即:4個(gè)參數(shù)是通過(guò)寄存器R0~R3來(lái)傳遞的);子程序在返回之前,將返回值放在了寄存器R0中,從而使得主調(diào)函數(shù)可以通過(guò)R0來(lái)獲得子程序的返回值(即:結(jié)果為一個(gè)32位的整數(shù)時(shí),通過(guò)寄存器R0返回)
多于4個(gè)參數(shù),前4個(gè)參數(shù)通過(guò)寄存器R0~R3來(lái)傳遞,其他參數(shù)通過(guò)數(shù)據(jù)棧傳遞。
7個(gè)參數(shù)的情景。下面是程序
int func(int a, int b, int c, int d, int e, int f, int g)
{
return(a+b+c+d+e+f+g);
}
int main()
{
int a=1,b=2,c=3,d=4,e=5,f=6,g=7;
return func(a,b,c,d,e,f,g);
}
的反匯編結(jié)果。我們通過(guò)它來(lái)分析一下當(dāng)參數(shù)超過(guò)4個(gè)的時(shí)候,所謂“通過(guò)數(shù)據(jù)棧傳遞其它參數(shù)”是什么含義。
1 int func(int a, int b, int c, int d, int e, int f, int g)
2 {
3 func [0xe92d4010] stmfd r13!,{r4,r14}
4 000080ac [0xe59d4010] ldr r4,[r13,#0x10] //r4為第7個(gè)參數(shù)的值
5 000080b0 [0xe28de008] add r14,r13,#8 //r14指向了存放傳入?yún)?shù)在棧中的位置
6 000080b4 [0xe89e5000] ldmia r14,{r12,r14} //r12為第5個(gè)參數(shù)的值,r14為第6個(gè)參數(shù)的值
7 return(a+b+c+d+e+f+g);
8 000080b8 [0xe0800001] add r0,r0,r1
9 000080bc [0xe0800002] add r0,r0,r2
10 000080c0 [0xe0800003] add r0,r0,r3
11 000080c4 [0xe080000c] add r0,r0,r12
12 000080c8 [0xe080000e] add r0,r0,r14
13 000080cc [0xe0800004] add r0,r0,r4
14 }
15 000080d0 [0xe8bd8010] ldmfd r13!,{r4,pc}
16 int main()
17 {
18 main [0xe92d401e] stmfd r13!,{r1-r4,r14}
19 int a=1,b=2,c=3,d=4,e=5,f=6,g=7;
20 000080d8 [0xe3a00001] mov r0,#1
21 000080dc [0xe3a0c002] mov r12,#2
22 000080e0 [0xe3a0e003] mov r14,#3
23 000080e4 [0xe3a04004] mov r4,#4
24 000080e8 [0xe3a01005] mov r1,#5
25 000080ec [0xe3a02006] mov r2,#6
26 000080f0 [0xe3a03007] mov r3,#7
27 return func(a,b,c,d,e,f,g);
28 000080f4 [0xe88d000e] stmia r13,{r1-r3} //main處的入棧操作,r1-r3實(shí)為占位符,是替第5、6、7個(gè)參數(shù)預(yù)先在棧內(nèi)占位置的
29 000080f8 [0xe1a03004] mov r3,r4
30 000080fc [0xe1a0200e] mov r2,r14
31 00008100 [0xe1a0100c] mov r1,r12
32 00008104 [0xebffffe7] bl func
33 }
34 00008108 [0xe8bd801e] ldmfd r13!,{r1-r4,pc}
第3行采用的是stmfd指令實(shí)施入棧,這是因?yàn)橐獫M足ATPCS中的“數(shù)據(jù)棧的使用規(guī)則”。而入棧的寄存器是r4和r14,r4入棧是因?yàn)閞4在子程序中被破壞(使用)了,因此必須在子程序的入口入棧保存,在子程序的出口處出?;謴?fù)(第28行);而r14要入棧則是因?yàn)閞14存放的是子程序的返回地址,而r14又在子程序中被破壞(使用)了,如果不保存的話,在子程序返回(第34行)的時(shí)候,將不會(huì)正確地返回到主調(diào)程序。當(dāng)然,你也許發(fā)現(xiàn)了r0,r1,r2,r3,r12同樣在子程序中被破壞了,為什么它們不需要保存和恢復(fù)呢?這是因?yàn)椤凹拇嫫鱝1 -- a4和ip是scratch寄存器(即:臨時(shí)寄存器),其值在進(jìn)行子程序調(diào)用時(shí)不需要保存和恢復(fù)?!保ㄒ?jiàn)前文)。也就是說(shuō),對(duì)于主調(diào)程序的編寫(xiě)者而言,他應(yīng)該很清楚他必須遵循ATPCS規(guī)則,所以他不會(huì)期望在子程序返回后,寄存器r0, r1, r2, r3, r12的值一定會(huì)維持原樣。因此子程序的編寫(xiě)者也就不必保存和恢復(fù)這幾個(gè)寄存器了,即使子程序破壞了它們的值。隨便說(shuō)一句,這條stmfd指令是由編譯器自動(dòng)加在子函數(shù)的第1條語(yǔ)句之前的,所以類(lèi)推一下就應(yīng)該明白,main函數(shù)運(yùn)行時(shí)的第1條指令并不是程序員書(shū)寫(xiě)main函數(shù)的第1條語(yǔ)句,而是編譯器添加的入棧指令。更進(jìn)一步,為什么編譯器要加這條入棧指令呢?因?yàn)閙ain函數(shù)本質(zhì)上也是個(gè)子函數(shù)而已,它也會(huì)被別人調(diào)用,也就是說(shuō),程序運(yùn)行起來(lái)后,main函數(shù)并不是首先運(yùn)行的。那么,是誰(shuí)首先運(yùn)行呢?當(dāng)然是調(diào)用main函數(shù)的代碼,這段代碼被稱(chēng)之為:例行啟動(dòng)程序(boot routine),或稱(chēng)啟動(dòng)例程。它是由編譯器在編譯程序時(shí)自動(dòng)加入的。
第20、21、22、23、29、30、31行顯然是在準(zhǔn)備(傳遞)前4個(gè)參數(shù);第18、24、25、26、28行的執(zhí)行,顯然將后3個(gè)參數(shù)放到了棧中,而第4、5、6行完成后,子程序則將棧中的3個(gè)參數(shù)取出了。這樣就完成了“多于4個(gè)的參數(shù)通過(guò)數(shù)據(jù)棧來(lái)傳遞”這個(gè)操作。
由此我們可以得到關(guān)于程序優(yōu)化的一個(gè)結(jié)論:開(kāi)始四個(gè)字大小的參數(shù)直接使用寄存器的R0-R3來(lái)傳遞(快速且高效的);如果需要更多的參數(shù),將使用堆棧。(需要額外的指令和慢速的存儲(chǔ)器操作) ;所以通常限制參數(shù)的個(gè)數(shù),使它為4或更少,如果不可避免,把常用的參數(shù)前4個(gè)放在R0-R3中。
第二部分內(nèi)容:C和ARM匯編程序間相互調(diào)用(點(diǎn)擊下載示例代碼)
在C和ARM匯編程序之間相互調(diào)用必須遵守ATPCS規(guī)則。C和匯編之間的相互調(diào)用可以從以下這四方面來(lái)說(shuō)明:
在C語(yǔ)言程序中調(diào)用匯編程序
在匯編程序中調(diào)用C語(yǔ)言程序
匯編程序?qū)全局變量的訪問(wèn)
C程序?qū)R編全局變量的訪問(wèn)
C程序中內(nèi)嵌匯編
1、在C語(yǔ)言程序中調(diào)用匯編子程序
為了保證程序調(diào)用時(shí)參數(shù)的正確傳遞,匯編程序的設(shè)計(jì)要遵守ATPCS。在匯編程序中需要使用EXPORT偽操作來(lái)聲明,使得本程序可以被其它程序調(diào)用。同時(shí),在C程序調(diào)用該匯編程序之前需要在C語(yǔ)言程序中使用extern關(guān)鍵詞來(lái)聲明該匯編程序。
參閱示例代碼中xmain函數(shù)(在ledtest.c中)對(duì)delay函數(shù)(在delay.s中)的調(diào)用
extern int delay(int time);
EXPORT delay
2、在匯編程序中調(diào)用C語(yǔ)言子程序
為了保證程序調(diào)用時(shí)參數(shù)的正確傳遞,匯編程序的設(shè)計(jì)要遵守ATPCS。在C程序中不需要使用任何關(guān)鍵字來(lái)聲明將被匯編語(yǔ)言調(diào)用的C程序(只要該程序的聲明前不要加static關(guān)鍵字),但是在匯編程序調(diào)用該C程序之前需要在匯編語(yǔ)言程序中使用IMPORT偽操作來(lái)聲明該C程序。在匯編程序中通過(guò)BL指令來(lái)調(diào)用子程序。
參閱示例代碼中init.s文件中的代碼對(duì)xmain函數(shù)的調(diào)用
IMPORT xmain
bl xmain
int xmain(int val)
3、匯編程序訪問(wèn)全局C變量
匯編程序可以通過(guò)C全局變量的地址間接訪問(wèn)在C語(yǔ)言程序中聲明的全局變量。在匯編程序中,通過(guò)使用IMPORT關(guān)鍵詞引人C全局變量,該C全局變量的名稱(chēng)在匯編程序中被認(rèn)為是一個(gè)標(biāo)號(hào),從而匯編程序可以利用LDR和STR指令訪問(wèn)該標(biāo)號(hào)所代表的地址處存放的內(nèi)容(即:C全局變量的值)。
參閱示例代碼中init.s文件中的如下幾行:
IMPORT i
ldr r0, i
sub r0, r0, #1
str r0, i
對(duì)于不同類(lèi)型的變量,需要采用不同選項(xiàng)的LDR和STR指令,如下所示:
unsigned char LDRB/STRB
unsigned short LDRH/STRH
unsigned int LDR/STR
char LDRSB/STRB
short LDRSH/STRH
4、C程序?qū)R編全局變量的訪問(wèn)
匯編程序中用DCD為全局變量分配空間并賦初值,并定義一個(gè)標(biāo)號(hào)代表該存儲(chǔ)位置,用EXPORT導(dǎo)出該標(biāo)號(hào)。C程序?qū)?huì)將該標(biāo)號(hào)視為全局變量的名稱(chēng),在C程序中用extern聲明該全局變量,之后就可以按正常的方式訪問(wèn)該全局變量了。
參閱示例代碼中delay.s文件中的代碼和xmain函數(shù)的代碼:
EXPORT DELAYVAL
DELAYVAL
DCD 0xffff
extern int DELAYVAL;
5、C程序中內(nèi)嵌匯編
有些操作C語(yǔ)言程序是做不了的,例如:改變cpsr寄存器的值、初始化堆棧指針寄存器sp,等等,它們只能由匯編程序完成。但出于編程簡(jiǎn)潔以及其它一些因素的考慮,有時(shí)我們需要在C源代碼中實(shí)現(xiàn)上述的操作,此時(shí)我們就必須采用在C源代碼中嵌入少量匯編代碼的方法來(lái)實(shí)現(xiàn),這就是C程序中的內(nèi)嵌匯編。
內(nèi)嵌的匯編指令包括大部分的ARM指令和Thumb指令,但是不能直接引用C的變量定義,數(shù)據(jù)交換必須通過(guò)ATPCS進(jìn)行,不支持諸如直接修改PC實(shí)現(xiàn)跳轉(zhuǎn)等底層功能。嵌入式匯編語(yǔ)句在形式上是獨(dú)立定義的函數(shù)體,其語(yǔ)法格式為:
__asm
{
指令[;指令]
……
[指令]
}
其中“__asm”為內(nèi)嵌匯編語(yǔ)句的關(guān)鍵字,需要特別注意的是前面有兩個(gè)下劃線。同一行如有多條指令,則指令之間用分號(hào)分隔,如果一條指令占據(jù)多行,除最后一行外都要使用連字符“\”
例如,如果我們需要在C程序中禁用中斷,那么內(nèi)嵌的匯編代碼如下:
__asm
{
MRS R0 CPSR
ORR R0, R0,#0x80
MSR CPSR_c,R0
}
出于完整性的考慮,最后將內(nèi)嵌匯編相對(duì)于一般匯編的一些不同的特點(diǎn)羅列如下:
操作數(shù)可以是寄存器、常量或C表達(dá)式。它們可以是char、short或者int類(lèi)型,而且是作為無(wú)符號(hào)數(shù)進(jìn)行操作 。
內(nèi)嵌的匯編指令中使用物理寄存器有一些限制。
常量前的符號(hào)“#”可以省略
只有指令B可以使用C程序中的標(biāo)號(hào),指令BL不能使用C程序中的標(biāo)號(hào)。
不支持匯編語(yǔ)言中用于內(nèi)存分配的偽操作。
指令中如果包含常量操作數(shù),該指令可能會(huì)被匯編器展開(kāi)成幾條指令。
內(nèi)嵌匯編器不支持通過(guò)“·”指示符或PC獲取當(dāng)前指令地址;
不支持LDR Rn,= expression偽指令,而使用MOV Rn, expression指令向寄存器賦值;
不支持標(biāo)號(hào)表達(dá)式;
不支持ADR和ADRL偽指令;
不支持BX和BLX指令;
不可以向PC賦值;
使用0x前綴替代“&”表示十六進(jìn)制數(shù)。
必須小心使用物理寄存器,如R0~R3,LR和PC。
不要使用寄存器尋址變量。
使用內(nèi)嵌匯編時(shí),編譯器自己會(huì)保存和恢復(fù)它可能用到的寄存器,用戶(hù)無(wú)須保存和恢復(fù)寄存器。
LDM和STM指令的寄存器列表只允許物理寄存器。
上一篇:ARM編程進(jìn)階之三 —— 裸機(jī)硬件的控制方法與例程
下一篇:ARM指令狀態(tài)切換到Thumb指令狀態(tài)
推薦閱讀
史海拾趣
設(shè)計(jì)資源 培訓(xùn) 開(kāi)發(fā)板 精華推薦
- Microchip 升級(jí)數(shù)字信號(hào)控制器(DSC)產(chǎn)品線 推出PWM 分辨率和 ADC 速度業(yè)界領(lǐng)先的新器件
- 意法半導(dǎo)體STM32MP23x:突破成本限制的工業(yè)AI應(yīng)用核心
- 意法半導(dǎo)體推出用于匹配遠(yuǎn)距離無(wú)線微控制器STM32WL33的集成的匹配濾波芯片
- ESP32開(kāi)發(fā)板連接TFT顯示屏ST7789跳坑記
- 如何讓ESP32支持analogWrite函數(shù)
- LGVL配合FreeType為可變字體設(shè)置字重-ESP32篇
- 使用樹(shù)莓派進(jìn)行 ESP32 Jtag 調(diào)試
- ESP32怎么在SPIFFS里面存儲(chǔ)html,css,js文件,以及網(wǎng)頁(yè)和arduino的通訊
- ESP32 freeRTOS使用測(cè)試
- 適用于高速應(yīng)用的先進(jìn)全局快門(mén)圖像傳感器
- Nexperia率先推出適用于48V電動(dòng)汽車(chē)通信網(wǎng)絡(luò)的ESD保護(hù)二極管
- 消息稱(chēng)英特爾 CEO 陳立武考慮放棄向新外部客戶(hù)推銷(xiāo) Intel 18A (-P) 工藝
- 曾與諾基亞三星比肩的手機(jī)巨頭沒(méi)落!LG手機(jī)退場(chǎng)
- 特斯拉透露旗下美國(guó)內(nèi)華達(dá)州磷酸鐵鋰電池工廠即將投產(chǎn),使用寧德時(shí)代設(shè)備
- 三星晶圓代工舉行 SAFE 論壇 2025 首爾場(chǎng):聚焦挽回客戶(hù),引入 SF2P+ 工藝
- 汽車(chē)區(qū)域控制器方案指南
- 丹佛斯DCM1000功率模塊的封裝技術(shù)演進(jìn)
- IAA MOBILITY移動(dòng)出行趨勢(shì)指數(shù)發(fā)布
- 寧德時(shí)代CTP 2.0電池包產(chǎn)線投產(chǎn),助力問(wèn)界系列車(chē)型加速交付
- 答題有禮 驚喜盡在恩智浦技術(shù)中心!
- 分分鐘完成電子設(shè)計(jì)的奧秘—2015WEBENCH邀你共同見(jiàn)證!
- 又到一年總結(jié)時(shí)——EE社區(qū)送溫暖
- 【已結(jié)束】 Qorvo、村田、NI直播【UWB最新技術(shù)、方案、市場(chǎng)、應(yīng)用解析】(13:30開(kāi)始入場(chǎng))
- phyBOARD-i.MX 8M Plus 開(kāi)發(fā)板來(lái)襲 免費(fèi)申請(qǐng)進(jìn)行時(shí)!
- 下載《Altera SoC深度體驗(yàn)》,打分評(píng)論贏好禮
- 4月26日上午10:00邀您觀看 基于TI Sitara™AM5708的工業(yè)派開(kāi)源平臺(tái)介紹 有獎(jiǎng)直播
- 分享你的國(guó)賽經(jīng)驗(yàn),還有好禮相送!
- 電源在線設(shè)計(jì)工具評(píng)測(cè),總有一款適合你!
- 基于Windows CE的嵌入式網(wǎng)絡(luò)收音機(jī)
- 關(guān)于單片機(jī)編程里面調(diào)用sprintf死機(jī)的解決方法及原因分析
- STM32定時(shí)器配置,定時(shí)計(jì)數(shù)模式下總結(jié)
- Keil MDK下如何設(shè)置非零初始化變量(復(fù)位后變量值不丟失)
- 運(yùn)營(yíng)商「5G短消息」,能挑戰(zhàn)微信嗎?
- 一加8、一加8 Pro外形、配置曝光:售價(jià)5600元起
- 三星:年內(nèi)全面支持5G消息業(yè)務(wù)
- SiC與GaN徹底刷屏了!芯力量·云路演第八期創(chuàng)參會(huì)人數(shù)新高
- 華為智慧屏X65售價(jià)24999元!