ARM彙編-從內嵌彙編開始
這是一篇Hello World的入門文章
開始
對於基於ARM的RISC處理器,GNU C編譯器提供了在C程式碼中內嵌彙編的功能。這種特性提供了C程式碼沒有的功能,比如手動優化軟體關鍵部分的程式碼、使用相關的處理器指令。
__asm__ __volatile__("hlt"); "__asm__"表示後面的程式碼為內嵌彙編,"asm"是"__asm__"的別名。"__volatile__"表示編譯器不要優化程式碼,後面的指令保留原樣,"volatile"是它的別名。括號裡面是彙編指令。
彙編指令模板
使用內嵌彙編,要先編寫彙編指令模板,然後將C語言表示式與指令的運算元相關聯,並告訴GCC對這些操作有哪些限制條件。
讓我們以一個簡單的例子開始:
asm(
"mov r0, r0\n\t"
"mov r0, r0"
); /* NOP 例子 */
該語句的作用是將r0移動到r0中。換句話講他並不幹任何事。典型的就是NOP指令,作用就是短時的延時。可以在一個asm宣告中寫多個彙編指令。但是為了增加程式的可讀性,最好將每一個彙編指令單獨放一行。換行符和製表符的使用可以使得指令列表看起來變得美觀(不加\n編譯會報錯)。
1.”NOP"指令即空指令,
2. 執行該指令時微控制器什麼都不做,但是會佔用一個指令的時間。
3. 當指令間需要有延時(給外部裝置足夠的響應時間;或是軟體的延時等),可以插入“NOP”指令。
通用的內嵌彙編模版是這樣的:
asm(code : output operand list : input operand list : clobber list);
彙編和C語句之間的聯絡通過上面asm宣告中可選的output operand list和input operand list。Clobber list後面再說。
下面是將C語言的一個整型變數傳遞給彙編,傳遞給C語言的另外一個整型變數。
int x = 5, y = 0;
__asm__("mov %[output], %[input]\n" : [output] "=r"(y) : [input] "r" (x));
彙編指令
每一個asm語句被冒號(:)分成了四個部分。彙編指令放在第一部分中的“”中間。
"mov %[output], %[input]\n"
運算元列表
接下來是冒號後的可選擇的output operand list:input operand list,每一個條目是由一對[](方括號)和被他包括的符號名組成,它後面跟著限制性字串,再後面是圓括號和它括著的C變數。 "output"前面的限制字串是"=r",其中"="表示"output"是輸出運算元,"r" 表示需要將"result"與某個通用暫存器相關聯,先將運算元的值讀入暫存器,然後在指令中使用相應暫存器,而不是"output"本身,當然指令執行完後需要將暫存器中的值存入變數"output",從表面上看好像是指令直接對"result"進行操作,實際上GCC做了隱式處理,這樣我們可以少寫一 些指令。"input"前面的"r"表示該表示式需要先放入某個暫存器,然後在指令中使用該暫存器參加運算。
C表示式或者變數與暫存器的關係由GCC自動處理,我們只需使用限制字串指導GCC如何處理即可。限制字元必須與指令對運算元的要求相匹配,否則產生的 彙編程式碼將會有錯,讀者可以將上例中的兩個"r",都改為"m"(m表示運算元放在記憶體,而不是暫存器中),編譯後得到的結果是:
movl input, result
很明顯這是一條非法指令,因此限制字串必須與指令對運算元的要求匹配。例如指令movl允許暫存器到暫存器,立即數到暫存器等,但是不允許記憶體到記憶體的操作,因此兩個運算元不能同時使用"m"作為限定字元。
上面NOP例子的輸出如下:
破壞符列表
有時候,當你想通知GCC當前內聯彙編語句可能對某些暫存器和記憶體進行修改,希望GCC將這一點考慮進去,此時就可以在Clobber/Modify域中進行宣告這些暫存器和記憶體。
這種情況一般發生在一個暫存器出現在Instructionlist,但不是有Output/Input操作表示式所指 定的,也不是在一些Output/Input操作表示式使用“r”約束時有GCC為其選擇的,同時此暫存器被Instructionlist修改,而這個暫存器只是供當前內聯彙編使用的情況。
例如:__asm__ (“mov R0, #0x34” ::: “R0”)
暫存器R0出現在Instructionlist中,且被mov指令修改,但卻未被任何Output/Input操作表示式指定,所以需要在Clobber/Modify域中指定“R0”,讓GCC知道這一點。
因為你在Output/Input操作表示式所指定的暫存器,或當你為一些Output/Input表示式使用“r”約束,GCC為你選擇一個暫存器,編譯器對這些暫存器是非常清楚的, 它知道這些暫存器是被修改的,因此不需要在Clobber/Modify域中在宣告它們。除此之外,GCC對剩下的暫存器中哪些會被當前內聯彙編修改一無所知。所以,如果當前內聯彙編修改了這些暫存器,就最好在Clobber/Modify域中宣告,讓GCC針對這些暫存器做相應的處理,否則可能會造成暫存器 的不一致,造成程式執行錯誤。
注意事項
就像上面的NOP例子,asm宣告的4個部分中,只要最尾部沒有使用的部分都可以省略。但是有有一點要注意的是,上面的4個部分中只要後面的還要使用,前面的部分沒有使用也不能省略,必須空但是保留冒號。為了增加程式碼的可讀性,你可以使用換行,空格,還有C風格的註釋。
在早期的C程式碼中還有如下寫法:
asm("mov %0,%1" : "=r" (output) : "m" (input));
在彙編程式碼中運算元的引用使用的是%後面跟一個數字,%1代表第一個運算元,%2程式碼第二個運算元,稱為佔位符,往後的類推。運算元至多有10 個,用"%0","%1"...."%9"表示。這個方法目前最新的編譯器還是支援的。但是它不便於維護程式碼。試想一下,你寫了大量的彙編指令的程式碼,要是你想插入一個運算元,那麼你就不得不從新修改運算元編號。
參考來源