杜欽生,楚葉峰,唐伎玲
(長春大學 計算機科學技術學院,長春 130022)
基于ARM的匯編語言與C語言混合編程的方法研究
杜欽生,楚葉峰,唐伎玲
(長春大學 計算機科學技術學院,長春 130022)
針對ARM的匯編語言與C語言混合編程的編程問題,具體研究了C語言中內嵌匯編指令、匯編語言和C語言程序變量的相互調用、匯編語言和C語言程序的相互調用和C編譯器的特定關鍵字問題,并給出了實例。
ARM;匯編語言;C語言
隨著網路與通信技術的發展,正在涌現出大量新的嵌入式系統。在眾多嵌入式系統廠家的共同參與、推動下,基于ARM系列處理器的應用技術在多個領域已取得突破性進展。一個基于ARM的嵌入式系統的完整的程序設計,通常情況用C或者C++完成大部分的編程任務,僅有初始化部分用匯編語言完成。本文主要研究匯編語言和C語言混合編程,結合實際情況給出兩種處理策略:若匯編代碼較短,則可在C/C++源文件中直接內嵌匯編語言實現混合編程;若匯編代碼較長,可以單獨寫成匯編文件,最后以匯編文件的形式加入項目中,通過ATPCS規定與C程序相互調用及訪問。
C和C++編譯器中內置了內嵌匯編器,可以用其實現C語言不能或不易完成的操作,提高程序執行效率。Armcc編譯器的內嵌匯編器支持ARM指令集,tcc編譯器的內嵌匯編器支持Thumb指令集。
內嵌的匯編指令支持大部分ARM和Thumb指令,其在使用上有以下特點:
(1)操作數。內嵌指令的操作數可以是C或C++表達式,這些表達式的值均按無符號數處理。當指令中同時使用寄存器和C或C++表達式時,表達式不要過于復雜,以免編譯器在計算表達式時用到過多的寄存器,以至于指令中用的寄存器沖突。
(2)寄存器。一般不推薦直接使用,因為可能影響編譯器的寄存器分配,從而影響程序效率。如果必須使用要注意:不要向PC賦值,只能利用B或BL指令實現跳轉;需要注意編譯器可能會使用R12和R13存儲臨時變量,在計算表達式的值時可能會把R0~R3、R12、R14用于函數調用;如果C變量用到了指令中已經用到的物理寄存器,編譯器一般會在必要時用棧保存或恢復這些寄存器,但排除sp、sl、fp和sb。
(3)常量。定值表達式的“#”可以省略,如果用“#”,則其后面必須是常量。
(4)標號。可以利用B指令(不能用BL)跳轉到C或C++中的標號。
(5)指令展開。除了與協處理器相關的指令,大多數ARM或Thumb指令對常量的操作會被展開成多條指令,各指令的展開對標志位的影響情況:算術指令可以正確的設置NZCV位;邏輯指令可以正確地設置NZ標志位,不影響V位,破壞C位。
(6)內存分配。所有的內存分配在C或C++程序中聲明,通過標號在內嵌匯編中引用,不要在內嵌匯編中用偽操作分配內存。
(7)SWI和BL指令的使用。在內嵌的SWI和BL指令中,除了正常的操作數域外,還必須增加如下三個可選的寄存器列表:第1個寄存器列表中的寄存器用于存放輸入的參數;第2個寄存器列表中的寄存器用于存放返回的結果。第3個寄存器列表中的寄存器的內容可能被調用的子程序破壞。
使用內嵌匯編還應注意以下幾點:
(1)不能通過(.)或{PC}獲得當前指令地址。
(2)不能用LDR Rn,=expr偽指令,可以用MOV Rn,expr替代(可生成從數據緩沖池中加載數據的匯編指令)。
(3)不支持標號表達式。
(4)不支持ADR和ADRL偽指令。
(5)表示十六進制數只能用0x,不能用&。
(6)編譯器可能使用寄存器R0~R3、IP、LR存放中間結果,因此在使用這些寄存器時要注意。
(7)CPSR寄存器中的NZCV條件標志位可能會被編譯器在計算C表達式時改變,因此在指令中使用這些標志位時要注意。
(8)指令中使用的C變量不要與ARM物理寄存器同名。
(9)LDM與STM指令的寄存器列表中只能使用物理寄存器,不能使用C表達式。
(10)不能寫寄存器PC,不支持BX和BLX指令。
(11)用戶不需要維護數據棧,因為編譯器會根據需要自動保存或恢復工作寄存器的值。
(12)用戶可以改變處理器模式,修改ATPCS寄存器sb、sl、fp,改變協處理器的狀態,但這并不為編譯器所知。所以,如果用戶改變了處理器的模式,不要使用原來的C表達式,直至重新恢復到原來的處理器模式后,方可使用這些C表達式。
(13)內嵌匯編指令常量前面的符號“#”可以省略;
(14)內嵌匯編指令不支持內存分配的偽操作。
ARM C++程序中可以使用關鍵詞__asm來表示一段內嵌匯編指令,格式如下:asm(“指令”);
其中,asm后面的括號中必須是一條匯編指令語句,并且不能包含注釋語句。
在C語言中嵌入的ARM匯編需要注意一些問題。
(1)如果一條指令占用了多行,那么應該使用符號“”續行,如果一行中有多個匯編指令,那么應該使用將多個指令隔開;
(2)匯編中不能再使用“;”作為注釋行的開頭,而應該使用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的結果。用一個C語言變量代替R0就可以解決這個問題。例如

(7)匯編器檢測器檢測到隱含的寄存器沖突就會報錯。盡管有時寄存器明顯對應某個變量,但不能直接使用寄存器代替變量。例如

盡管根據編譯器編譯規則似乎可確定R0對應x,但這樣的代碼會使匯編器認為發生了寄存器沖突。用其他寄存器代替R0存放參數x,則使得該函數將x原封不動地返回。正確地寫法如下:

(8)對于內嵌的匯編代碼用到的寄存器,編譯器在編譯時會自動加入保存和恢復這些寄存器的代碼而不用用戶去管理,除了寄存器CPSR和SPSR,其他寄存器都必須先賦值然后再讀取,否則編譯時將出現錯誤。
在一個工程中,一般都會由多個匯編文件和多個C/C++程序文件有機組成。在這些匯編文件和C/C++文件之間就存在變量相互訪問和函數相互調用的問題。
內嵌匯編不用單獨編輯匯編語言文件,比較簡潔,但是有諸多限制,當匯編的代碼較多時一般放在單獨的匯編文件中。這時就需要在匯編和C之間進行一些數據的傳遞,最簡便的辦法就是使用全局變量。
在C/C++程序中聲明的全局變量可以被匯編程序通過地址間接訪問。具體訪問方法/步驟如下:
(1)在C/C++程序中聲明全局變量;
(2)在匯編程序使用IMPORT/EXTERN偽指令聲明引用該全局變量;
(3)使用LDR偽指令讀取該變量的內存地址;
(4)根據該數據的類型使用相應的LDR或STR指令讀取或設置該變量的值。對于無符號變量,使用LDRB/STRB訪問char;使用LDRH/STRH訪問short;使用LDR/STR訪問integer。對于有符號數,使用LDRSB/LDRSH。
在匯編的源程序中調用C語言風格的字符串需要使用IMPORT偽操作。IMPORT相當于C語言中的extern關鍵字,告訴編譯器引用的符號不是在本文件中定義的,而是在其他的源文件中定義的。
偽操作的格式:
IMPORT symbol[,WEAK]
symbol是聲明的符號的名稱;[,WEAK]指示編譯器如果發現symbol在所有的源文件中都沒有找到,那么它也不會產生任何的錯誤信息。
在匯編程序中聲明的數據可以被C/C++程序所訪問,具體訪問方法/步驟是:在匯編程序中用EXPORT/GLOBAL偽指令聲明該符號為全局標號,可以被其他文件應用;C/C++程序中定義相應數據類型的指針變量;對該指針變量賦值為匯編程序中的全局標號,利用該指針訪問匯編程序中的數據。
C/C++程序和ARM匯編程序之間相互調用必須遵守ATPCS(ARM/Thumb Procedure Call Standard)。使用ADS的C語言編譯器編譯的C語言子程序會自動滿足用戶指定的ATPCS類型。而對于匯編語言來說,完全要依賴用戶來保證各個子程序滿足選定的ATPCS類型。具體來說,匯編程序必須滿足以下3個條件才能實現與C語言的相互調用。在子程序編寫時必須遵守相應的ATPCS的規則;堆棧的使用要遵守相應的ATPCS的規則;在匯編編譯器中使用-atpcs選項。
獨立匯編和編譯的子程序間的互訪要遵守ATPCS標準。ATPCS中規定了在子程序調用時的一些基本規則,如子程序調用過程中的寄存器的使用規則、堆棧的使用規則、參數的傳遞規則等,還有一些為特定需要派生的特定ATPCS規則,包括數據棧限制檢查、只讀段位置無關、可讀寫段位置無關、ARM和Thumb程序混合使用以及關于浮點運算的一些規則,等。這里僅對ATPCS基本規則進行分析。
3.1.1 寄存器的使用和命名規則
表1列出了ATPCS中寄存器的使用和命名規則。

表1 寄存器的使用和命名規則
3.1.2 堆棧的使用規則
子程序的調用經常需要用到堆棧,特別當使用到較多參數時候。ATPCS規定堆棧為FD類型,即滿遞減堆棧,對堆棧的操作是8字節對齊。堆棧中為子程序分配的內存區域可用來保存寄存器和局部變量,我們稱這塊內存區域為堆棧中的數據幀。
使用ADS中的編譯器產生的目標代碼中包含了DRAFT2格式的數據幀。在調試過程中,調試器可以使用這些數據幀來查看堆棧中的相關信息。對于匯編語言來說,用戶必須使用FRAME偽指令來描述堆棧的數據幀。ARM匯編器根據這些偽指令在目標文件中產生相應的DRAFT2格式的數據幀。對于匯編程序來說,如果目標文件中包含了外部函數調用,則必須滿足以下條件:外部接口的堆棧必須是8字節對齊的;在匯編程序中使用PRESERVE8偽指令告訴鏈接器,在本匯編程序數據是8字節對齊的。
3.1.3 參數傳遞規則
根據參數是否固定可以將子程序分為參數個數固定的子程序和參數個數可變化的子程序。如果系統包含浮點運算的硬件部件,這兩種子程序傳遞參數的規則是不一樣的;否則的話,這兩種子程序傳遞參數的規則是一樣的。
(1)子程序參數傳遞規則
對于參數個數可變的子程序,在子程序調用中,當參數個數不超過4個時,可以使用寄存器R0~R3來傳遞,參數傳遞時所用參數被看作是存放在連續的內存字單元的字數據,然后依次將各字數據傳送到寄存器R0、R1、R2、R3中;當參數個數超過4個時,多余的參數可以使用堆棧來傳遞,入棧的順序與參數順序相反,即最后一個字數據先入棧。這樣,對于超過4字節的參數(如雙精度浮點數,結構體),可能一部分保存于寄存器,另一部分保存于數據棧,或者全部壓棧。
對于包含浮點運算的硬件系統,在浮點參數的傳遞方面與參數個數不固定的情況不同,規則為:浮點參數按順序處理,為每個浮點參數分配滿足其需要的且編號最小的一組連續的FP寄存器。
(2)子程序中結果返回規則
結果為一個字(32位)的整數時,可以通過寄存器R0返回;結果為一個2~4字(64位)的整數時,可以通過寄存器R0~R1、R0~R2、R0~R3來返回;結果為浮點數,用浮點寄存器f0、d0或s0返回;結果為復合型的浮點數,用f0~fn或d0~dn返回;對于位數更多的結果,需要間接通過內存來返回。
匯編程序的設置要遵循ATPCS規則,保證程序調用時參數的正確傳遞,在這種情況下,C程序可以調用匯編子函數。C程序調用匯編程序的方法如下:匯編程序中使用EXPORT偽指令聲明本子程序可外部使用,使其他程序可調用該子程序;在C語言程序中使用extern關鍵字聲明外部函數(聲明要調用的匯編子程序),才可調用此匯編的子程序。
匯編程序的設置要遵循ATPCS規則,保證程序調用時參數的正確傳遞。匯編程序調用C程序的方法如下:在匯編程序中使用IMPORT偽指令聲明將要調用的C程序函數;在調用C程序時,要正確設置入口參數,然后使用BL指令調用。
ARM C編譯器支持一些對ANSI C進行擴展的關鍵字,用于聲明特定的函數或數據類型等。下面列舉了一些常用的關鍵字,_irq、_value_in_regs、_inline、_volatile_pure和_suli等。
在程序設計時,對于同一功能既可以使用匯編語言來實現,又可以用C語言來實現。比如對于一個實例:寄存器R1和R2中有兩個數,若R0為0則求R1與R2的和,若R0為1則求R1與R2的差,結果存儲在R0中。我們用兩種語言實現,而且做了對比。
本文研究了ARM的匯編語言與C語言混合編程的編程問題,重點對于C語言中內嵌匯編指令、匯編語言和C語言程序變量的相互調用、匯編語言和C語言程序的相互調用和C編譯器的特定關鍵字問題進行了分析,并給出了實例,希望對于嵌入式程序設計與開發人員具有一定的借鑒意義。
[1] 賈智平,張瑞華.嵌入式系統原理與接口技術[M].2版.北京:清華大學出版社,2009.
[2] 張石.ARM嵌入式系統教程[M].北京:機械工業出版社,2008.
[3] 熊茂華,謝建華,熊昕.嵌入式Linux C語言應用程序設計與實踐[M].北京:清華大學出版社,2010.
[4] 熊茂華,楊震倫.ARM9嵌入式系統設計與開發應用[M].北京:清華大學出版社,2010.
[5] 俞輝,李永,劉凱,王曉虹.ARM嵌入式Linux系統設計與開發[M].北京:機械工業出版社,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
吉林省教育廳“十一五”科學技術研究項目(2010449);吉林省科技發展計劃項目(201105046);吉林省教育科學“十二五”規劃2011年度項目(GH11067)。
杜欽生(1978-),男,吉林磐石人,講師,博士研究生,主要研究方向為嵌入式系統設計與開發。
責任編輯:吳旭云