杜欽生,楚葉峰,唐伎玲
(長春大學(xué) 計算機科學(xué)技術(shù)學(xué)院,長春 130022)
基于ARM的匯編語言與C語言混合編程的方法研究
杜欽生,楚葉峰,唐伎玲
(長春大學(xué) 計算機科學(xué)技術(shù)學(xué)院,長春 130022)
針對ARM的匯編語言與C語言混合編程的編程問題,具體研究了C語言中內(nèi)嵌匯編指令、匯編語言和C語言程序變量的相互調(diào)用、匯編語言和C語言程序的相互調(diào)用和C編譯器的特定關(guān)鍵字問題,并給出了實例。
ARM;匯編語言;C語言
隨著網(wǎng)路與通信技術(shù)的發(fā)展,正在涌現(xiàn)出大量新的嵌入式系統(tǒng)。在眾多嵌入式系統(tǒng)廠家的共同參與、推動下,基于ARM系列處理器的應(yīng)用技術(shù)在多個領(lǐng)域已取得突破性進展。一個基于ARM的嵌入式系統(tǒng)的完整的程序設(shè)計,通常情況用C或者C++完成大部分的編程任務(wù),僅有初始化部分用匯編語言完成。本文主要研究匯編語言和C語言混合編程,結(jié)合實際情況給出兩種處理策略:若匯編代碼較短,則可在C/C++源文件中直接內(nèi)嵌匯編語言實現(xiàn)混合編程;若匯編代碼較長,可以單獨寫成匯編文件,最后以匯編文件的形式加入項目中,通過ATPCS規(guī)定與C程序相互調(diào)用及訪問。
C和C++編譯器中內(nèi)置了內(nèi)嵌匯編器,可以用其實現(xiàn)C語言不能或不易完成的操作,提高程序執(zhí)行效率。Armcc編譯器的內(nèi)嵌匯編器支持ARM指令集,tcc編譯器的內(nèi)嵌匯編器支持Thumb指令集。
內(nèi)嵌的匯編指令支持大部分ARM和Thumb指令,其在使用上有以下特點:
(1)操作數(shù)。內(nèi)嵌指令的操作數(shù)可以是C或C++表達式,這些表達式的值均按無符號數(shù)處理。當指令中同時使用寄存器和C或C++表達式時,表達式不要過于復(fù)雜,以免編譯器在計算表達式時用到過多的寄存器,以至于指令中用的寄存器沖突。
(2)寄存器。一般不推薦直接使用,因為可能影響編譯器的寄存器分配,從而影響程序效率。如果必須使用要注意:不要向PC賦值,只能利用B或BL指令實現(xiàn)跳轉(zhuǎn);需要注意編譯器可能會使用R12和R13存儲臨時變量,在計算表達式的值時可能會把R0~R3、R12、R14用于函數(shù)調(diào)用;如果C變量用到了指令中已經(jīng)用到的物理寄存器,編譯器一般會在必要時用棧保存或恢復(fù)這些寄存器,但排除sp、sl、fp和sb。
(3)常量。定值表達式的“#”可以省略,如果用“#”,則其后面必須是常量。
(4)標號??梢岳肂指令(不能用BL)跳轉(zhuǎn)到C或C++中的標號。
(5)指令展開。除了與協(xié)處理器相關(guān)的指令,大多數(shù)ARM或Thumb指令對常量的操作會被展開成多條指令,各指令的展開對標志位的影響情況:算術(shù)指令可以正確的設(shè)置NZCV位;邏輯指令可以正確地設(shè)置NZ標志位,不影響V位,破壞C位。
(6)內(nèi)存分配。所有的內(nèi)存分配在C或C++程序中聲明,通過標號在內(nèi)嵌匯編中引用,不要在內(nèi)嵌匯編中用偽操作分配內(nèi)存。
(7)SWI和BL指令的使用。在內(nèi)嵌的SWI和BL指令中,除了正常的操作數(shù)域外,還必須增加如下三個可選的寄存器列表:第1個寄存器列表中的寄存器用于存放輸入的參數(shù);第2個寄存器列表中的寄存器用于存放返回的結(jié)果。第3個寄存器列表中的寄存器的內(nèi)容可能被調(diào)用的子程序破壞。
使用內(nèi)嵌匯編還應(yīng)注意以下幾點:
(1)不能通過(.)或{PC}獲得當前指令地址。
(2)不能用LDR Rn,=expr偽指令,可以用MOV Rn,expr替代(可生成從數(shù)據(jù)緩沖池中加載數(shù)據(jù)的匯編指令)。
(3)不支持標號表達式。
(4)不支持ADR和ADRL偽指令。
(5)表示十六進制數(shù)只能用0x,不能用&。
(6)編譯器可能使用寄存器R0~R3、IP、LR存放中間結(jié)果,因此在使用這些寄存器時要注意。
(7)CPSR寄存器中的NZCV條件標志位可能會被編譯器在計算C表達式時改變,因此在指令中使用這些標志位時要注意。
(8)指令中使用的C變量不要與ARM物理寄存器同名。
(9)LDM與STM指令的寄存器列表中只能使用物理寄存器,不能使用C表達式。
(10)不能寫寄存器PC,不支持BX和BLX指令。
(11)用戶不需要維護數(shù)據(jù)棧,因為編譯器會根據(jù)需要自動保存或恢復(fù)工作寄存器的值。
(12)用戶可以改變處理器模式,修改ATPCS寄存器sb、sl、fp,改變協(xié)處理器的狀態(tài),但這并不為編譯器所知。所以,如果用戶改變了處理器的模式,不要使用原來的C表達式,直至重新恢復(fù)到原來的處理器模式后,方可使用這些C表達式。
(13)內(nèi)嵌匯編指令常量前面的符號“#”可以省略;
(14)內(nèi)嵌匯編指令不支持內(nèi)存分配的偽操作。
ARM C++程序中可以使用關(guān)鍵詞__asm來表示一段內(nèi)嵌匯編指令,格式如下:asm(“指令”);
其中,asm后面的括號中必須是一條匯編指令語句,并且不能包含注釋語句。
在C語言中嵌入的ARM匯編需要注意一些問題。
(1)如果一條指令占用了多行,那么應(yīng)該使用符號“”續(xù)行,如果一行中有多個匯編指令,那么應(yīng)該使用將多個指令隔開;
(2)匯編中不能再使用“;”作為注釋行的開頭,而應(yīng)該使用C語言中的“/**/”或者“//”進行注釋;(3)不要企圖使用一個物理寄存器去改變一個C變量;
(4)計算匯編代碼中的C語言表達式時,會使用這些物理寄存器并會修改CPSR中的NZCV標志位;
(5)在匯編指令中,可以使用表達式,使用逗號“,”作為分隔符。例如__asm{ADD x,y,(f(),z)},其中,(f(),z)為CC++語言表達式。
(6)必須小心使用物理寄存器,如R0-R3、LR和PC。例如:
當計算x/y時,R0會被修改,從而影響R0+x/y的結(jié)果。用一個C語言變量代替R0就可以解決這個問題。例如
(7)匯編器檢測器檢測到隱含的寄存器沖突就會報錯。盡管有時寄存器明顯對應(yīng)某個變量,但不能直接使用寄存器代替變量。例如
盡管根據(jù)編譯器編譯規(guī)則似乎可確定R0對應(yīng)x,但這樣的代碼會使匯編器認為發(fā)生了寄存器沖突。用其他寄存器代替R0存放參數(shù)x,則使得該函數(shù)將x原封不動地返回。正確地寫法如下:
(8)對于內(nèi)嵌的匯編代碼用到的寄存器,編譯器在編譯時會自動加入保存和恢復(fù)這些寄存器的代碼而不用用戶去管理,除了寄存器CPSR和SPSR,其他寄存器都必須先賦值然后再讀取,否則編譯時將出現(xiàn)錯誤。
在一個工程中,一般都會由多個匯編文件和多個C/C++程序文件有機組成。在這些匯編文件和C/C++文件之間就存在變量相互訪問和函數(shù)相互調(diào)用的問題。
內(nèi)嵌匯編不用單獨編輯匯編語言文件,比較簡潔,但是有諸多限制,當匯編的代碼較多時一般放在單獨的匯編文件中。這時就需要在匯編和C之間進行一些數(shù)據(jù)的傳遞,最簡便的辦法就是使用全局變量。
在C/C++程序中聲明的全局變量可以被匯編程序通過地址間接訪問。具體訪問方法/步驟如下:
(1)在C/C++程序中聲明全局變量;
(2)在匯編程序使用IMPORT/EXTERN偽指令聲明引用該全局變量;
(3)使用LDR偽指令讀取該變量的內(nèi)存地址;
(4)根據(jù)該數(shù)據(jù)的類型使用相應(yīng)的LDR或STR指令讀取或設(shè)置該變量的值。對于無符號變量,使用LDRB/STRB訪問char;使用LDRH/STRH訪問short;使用LDR/STR訪問integer。對于有符號數(shù),使用LDRSB/LDRSH。
在匯編的源程序中調(diào)用C語言風格的字符串需要使用IMPORT偽操作。IMPORT相當于C語言中的extern關(guān)鍵字,告訴編譯器引用的符號不是在本文件中定義的,而是在其他的源文件中定義的。
偽操作的格式:
IMPORT symbol[,WEAK]
symbol是聲明的符號的名稱;[,WEAK]指示編譯器如果發(fā)現(xiàn)symbol在所有的源文件中都沒有找到,那么它也不會產(chǎn)生任何的錯誤信息。
在匯編程序中聲明的數(shù)據(jù)可以被C/C++程序所訪問,具體訪問方法/步驟是:在匯編程序中用EXPORT/GLOBAL偽指令聲明該符號為全局標號,可以被其他文件應(yīng)用;C/C++程序中定義相應(yīng)數(shù)據(jù)類型的指針變量;對該指針變量賦值為匯編程序中的全局標號,利用該指針訪問匯編程序中的數(shù)據(jù)。
C/C++程序和ARM匯編程序之間相互調(diào)用必須遵守ATPCS(ARM/Thumb Procedure Call Standard)。使用ADS的C語言編譯器編譯的C語言子程序會自動滿足用戶指定的ATPCS類型。而對于匯編語言來說,完全要依賴用戶來保證各個子程序滿足選定的ATPCS類型。具體來說,匯編程序必須滿足以下3個條件才能實現(xiàn)與C語言的相互調(diào)用。在子程序編寫時必須遵守相應(yīng)的ATPCS的規(guī)則;堆棧的使用要遵守相應(yīng)的ATPCS的規(guī)則;在匯編編譯器中使用-atpcs選項。
獨立匯編和編譯的子程序間的互訪要遵守ATPCS標準。ATPCS中規(guī)定了在子程序調(diào)用時的一些基本規(guī)則,如子程序調(diào)用過程中的寄存器的使用規(guī)則、堆棧的使用規(guī)則、參數(shù)的傳遞規(guī)則等,還有一些為特定需要派生的特定ATPCS規(guī)則,包括數(shù)據(jù)棧限制檢查、只讀段位置無關(guān)、可讀寫段位置無關(guān)、ARM和Thumb程序混合使用以及關(guān)于浮點運算的一些規(guī)則,等。這里僅對ATPCS基本規(guī)則進行分析。
3.1.1 寄存器的使用和命名規(guī)則
表1列出了ATPCS中寄存器的使用和命名規(guī)則。
表1 寄存器的使用和命名規(guī)則
3.1.2 堆棧的使用規(guī)則
子程序的調(diào)用經(jīng)常需要用到堆棧,特別當使用到較多參數(shù)時候。ATPCS規(guī)定堆棧為FD類型,即滿遞減堆棧,對堆棧的操作是8字節(jié)對齊。堆棧中為子程序分配的內(nèi)存區(qū)域可用來保存寄存器和局部變量,我們稱這塊內(nèi)存區(qū)域為堆棧中的數(shù)據(jù)幀。
使用ADS中的編譯器產(chǎn)生的目標代碼中包含了DRAFT2格式的數(shù)據(jù)幀。在調(diào)試過程中,調(diào)試器可以使用這些數(shù)據(jù)幀來查看堆棧中的相關(guān)信息。對于匯編語言來說,用戶必須使用FRAME偽指令來描述堆棧的數(shù)據(jù)幀。ARM匯編器根據(jù)這些偽指令在目標文件中產(chǎn)生相應(yīng)的DRAFT2格式的數(shù)據(jù)幀。對于匯編程序來說,如果目標文件中包含了外部函數(shù)調(diào)用,則必須滿足以下條件:外部接口的堆棧必須是8字節(jié)對齊的;在匯編程序中使用PRESERVE8偽指令告訴鏈接器,在本匯編程序數(shù)據(jù)是8字節(jié)對齊的。
3.1.3 參數(shù)傳遞規(guī)則
根據(jù)參數(shù)是否固定可以將子程序分為參數(shù)個數(shù)固定的子程序和參數(shù)個數(shù)可變化的子程序。如果系統(tǒng)包含浮點運算的硬件部件,這兩種子程序傳遞參數(shù)的規(guī)則是不一樣的;否則的話,這兩種子程序傳遞參數(shù)的規(guī)則是一樣的。
(1)子程序參數(shù)傳遞規(guī)則
對于參數(shù)個數(shù)可變的子程序,在子程序調(diào)用中,當參數(shù)個數(shù)不超過4個時,可以使用寄存器R0~R3來傳遞,參數(shù)傳遞時所用參數(shù)被看作是存放在連續(xù)的內(nèi)存字單元的字數(shù)據(jù),然后依次將各字數(shù)據(jù)傳送到寄存器R0、R1、R2、R3中;當參數(shù)個數(shù)超過4個時,多余的參數(shù)可以使用堆棧來傳遞,入棧的順序與參數(shù)順序相反,即最后一個字數(shù)據(jù)先入棧。這樣,對于超過4字節(jié)的參數(shù)(如雙精度浮點數(shù),結(jié)構(gòu)體),可能一部分保存于寄存器,另一部分保存于數(shù)據(jù)棧,或者全部壓棧。
對于包含浮點運算的硬件系統(tǒng),在浮點參數(shù)的傳遞方面與參數(shù)個數(shù)不固定的情況不同,規(guī)則為:浮點參數(shù)按順序處理,為每個浮點參數(shù)分配滿足其需要的且編號最小的一組連續(xù)的FP寄存器。
(2)子程序中結(jié)果返回規(guī)則
結(jié)果為一個字(32位)的整數(shù)時,可以通過寄存器R0返回;結(jié)果為一個2~4字(64位)的整數(shù)時,可以通過寄存器R0~R1、R0~R2、R0~R3來返回;結(jié)果為浮點數(shù),用浮點寄存器f0、d0或s0返回;結(jié)果為復(fù)合型的浮點數(shù),用f0~fn或d0~dn返回;對于位數(shù)更多的結(jié)果,需要間接通過內(nèi)存來返回。
匯編程序的設(shè)置要遵循ATPCS規(guī)則,保證程序調(diào)用時參數(shù)的正確傳遞,在這種情況下,C程序可以調(diào)用匯編子函數(shù)。C程序調(diào)用匯編程序的方法如下:匯編程序中使用EXPORT偽指令聲明本子程序可外部使用,使其他程序可調(diào)用該子程序;在C語言程序中使用extern關(guān)鍵字聲明外部函數(shù)(聲明要調(diào)用的匯編子程序),才可調(diào)用此匯編的子程序。
匯編程序的設(shè)置要遵循ATPCS規(guī)則,保證程序調(diào)用時參數(shù)的正確傳遞。匯編程序調(diào)用C程序的方法如下:在匯編程序中使用IMPORT偽指令聲明將要調(diào)用的C程序函數(shù);在調(diào)用C程序時,要正確設(shè)置入口參數(shù),然后使用BL指令調(diào)用。
ARM C編譯器支持一些對ANSI C進行擴展的關(guān)鍵字,用于聲明特定的函數(shù)或數(shù)據(jù)類型等。下面列舉了一些常用的關(guān)鍵字,_irq、_value_in_regs、_inline、_volatile_pure和_suli等。
在程序設(shè)計時,對于同一功能既可以使用匯編語言來實現(xiàn),又可以用C語言來實現(xiàn)。比如對于一個實例:寄存器R1和R2中有兩個數(shù),若R0為0則求R1與R2的和,若R0為1則求R1與R2的差,結(jié)果存儲在R0中。我們用兩種語言實現(xiàn),而且做了對比。
本文研究了ARM的匯編語言與C語言混合編程的編程問題,重點對于C語言中內(nèi)嵌匯編指令、匯編語言和C語言程序變量的相互調(diào)用、匯編語言和C語言程序的相互調(diào)用和C編譯器的特定關(guān)鍵字問題進行了分析,并給出了實例,希望對于嵌入式程序設(shè)計與開發(fā)人員具有一定的借鑒意義。
[1] 賈智平,張瑞華.嵌入式系統(tǒng)原理與接口技術(shù)[M].2版.北京:清華大學(xué)出版社,2009.
[2] 張石.ARM嵌入式系統(tǒng)教程[M].北京:機械工業(yè)出版社,2008.
[3] 熊茂華,謝建華,熊昕.嵌入式Linux C語言應(yīng)用程序設(shè)計與實踐[M].北京:清華大學(xué)出版社,2010.
[4] 熊茂華,楊震倫.ARM9嵌入式系統(tǒng)設(shè)計與開發(fā)應(yīng)用[M].北京:清華大學(xué)出版社,2010.
[5] 俞輝,李永,劉凱,王曉虹.ARM嵌入式Linux系統(tǒng)設(shè)計與開發(fā)[M].北京:機械工業(yè)出版社,2010.
Research on ARM-based Programming Methods of Assemble Language and C Language
DU Qin-sheng, CHU Ye-feng,TANG Ji-ling
(College of Computer Science and Technology,Changchun University,Changchun 130022,China)
In view of ARM-based programming methods of assemble language and C Language,this paper discusses such problems as the assembly instruction embedded in C Language,the transference between assembly language and program variables,the transference between assembly language and program and the keywords of C Compiler,then it gives some examples.
ARM;assembly language;C Language
TP393.17
A
1009-3907(2011)10-0019-05
2011-08-28
吉林省教育廳“十一五”科學(xué)技術(shù)研究項目(2010449);吉林省科技發(fā)展計劃項目(201105046);吉林省教育科學(xué)“十二五”規(guī)劃2011年度項目(GH11067)。
杜欽生(1978-),男,吉林磐石人,講師,博士研究生,主要研究方向為嵌入式系統(tǒng)設(shè)計與開發(fā)。
責任編輯:吳旭云