在我看來廷支,學(xué)習(xí)一門高級編程語言比學(xué)習(xí)一門特定體系結(jié)構(gòu)的匯編更有用嵌灰,但是我很想學(xué)習(xí)ARM匯編程序只是為了好玩问词,因為我知道一些386匯編語言森爽。這個想法不是想成為大師恨豁,而是想了解下面發(fā)生了什么。
ARM簡介
下面的解釋不會力求面面俱到的講述arm的體系結(jié)構(gòu)爬迟,我會盡量精簡講解其中實用的部分橘蜜。
ARM是一種32位體系結(jié)構(gòu),具有一個簡單的目標(biāo):靈活性付呕。盡管這對集成商非常有用(因為他們在設(shè)計硬件時有很大的自由度)计福,但對于必須應(yīng)對ARM硬件差異的系統(tǒng)開發(fā)人員來說卻不是那么好。因此徽职,在本文中象颖,我將假設(shè)一切都在運(yùn)行Raspbian的Raspberry Pi Model B上完成。
有些部分將是ARM通用的姆钉,而有些將是Raspberry Pi專用的说订。 我不會區(qū)分抄瓦。 ARM網(wǎng)站上有很多文檔可查!
開始寫匯編
匯編語言只是二進(jìn)制代碼之上的一個薄語法層陶冷。
二進(jìn)制代碼是計算機(jī)可以運(yùn)行的钙姊,它由以二進(jìn)制表示形式編碼的指令組成(此類編碼已在ARM手冊中記錄)。您可以編寫二進(jìn)制代碼編碼指令埂伦,但這會很麻煩(除了與Linux本身相關(guān)的其他一些技術(shù)煞额,我們現(xiàn)在可以很高興地忽略它們)。
由于計算機(jī)無法運(yùn)行匯編程序沾谜,因此我們必須從中獲取二進(jìn)制代碼膊毁。 我們使用一種稱為匯編器的工具將匯編器代碼匯編成可以運(yùn)行的二進(jìn)制代碼。
在本課程中我們使用gnu assembler基跑,這個工具是gnu project中的其中一個媚媒,有時又被稱為gas.
只需打開vim,nano或emacs之類的編輯器即可涩僻。 我們的匯編語言文件(稱為源文件)將帶有后綴.s缭召。 我不知道為什么是.s,但這是通常的慣例逆日。
第一個程序
從一個簡單的程序開始嵌巷,這個程序只有返回值,在其中不做任何事室抽。
/* -- first.s */
/* This is a comment */
.global main /* 'main' is our entry point and must be global */
main: /* This is main */
mov r0, #2 /* Put a 2 inside the register r0 */
bx lr /* Return from main */
創(chuàng)建一個文件搪哪,將上述內(nèi)容保存其中,然后開始編譯坪圾,命令如下:
$ as -o first.o first.s
上述命令執(zhí)行完后將會創(chuàng)建一個first.o的文件晓折,下面我們將這個文件鏈接成為可執(zhí)行文件
$ gcc -o first first.o
如果上述步驟執(zhí)行順利,你將得到一個first的文件兽泄,這個就是你的程序漓概,下面來執(zhí)行吧。
$ ./first
它應(yīng)該不會有任何反應(yīng)病梢,的確胃珍,有點(diǎn)小失望,但是這個程序是執(zhí)行了的蜓陌,讓我們看一下它的返回值
$ ./first ; echo $?
2
太棒了觅彰,返回值2并非偶然,這是由于匯編代碼中的#2引起的钮热。
由于運(yùn)行匯編器和鏈接器很快會變得很無聊填抬,因此建議您使用以下Makefile文件或類似的文件。
# Makefile
all: first
first: first.o
gcc -o $@ $+
first.o : first.s
as -o $@ $<
clean:
rm -vf first *.o
OK,讓我們來解釋一下吧
我們作弊只是為了使事情變得容易一些隧期。 我們在匯編器中編寫了一個C main函數(shù)飒责,它僅返回2;蛀骇。 這樣,我們的程序就更容易了读拆,因為C運(yùn)行時為我們處理了程序的初始化和終止。 我將一直使用這種方法鸵闪。
讓我們回顧一下最小匯編文件的每一行檐晕。
/* -- first.s */
/* This is a comment */
這些是評論。 注釋包含在/ 和 /中蚌讼。 使用它們來記錄您的匯編器辟灰,因為它們會被忽略。 通常篡石,不要將/ 和 /嵌套在/ *內(nèi)芥喇,因為它不起作用。
.global main /* 'main' is our entry point and must be global */
這是GNU匯編程序的指令凰萨。 指令告訴GNU匯編器做一些特別的事情继控。 它們以點(diǎn)號(。)開頭胖眷,后跟指令名稱和一些參數(shù)武通。 在這種情況下,我們說main是一個全局名稱珊搀。 這是必需的冶忱,因為C運(yùn)行時將調(diào)用main。 如果不是全局的境析,則C運(yùn)行時將無法調(diào)用它囚枪,并且鏈接階段將失敗。
main: /* This is main */
GNU匯編程序中不是指令的每一行都將始終像label:指令劳淆。 我們可以省略label:和指令(忽略空行和空行)链沼。 僅帶有標(biāo)簽:的行將該標(biāo)簽應(yīng)用于下一行(您可以通過這種方式將多個標(biāo)簽引用相同的內(nèi)容)。 指令部分是ARM匯編語言本身沛鸵。 在這種情況下忆植,由于沒有指令,我們只是在定義main谒臼。
mov r0, #2 /* Put a 2 inside the register r0 */
在行的開頭朝刊,空格被忽略,但是縮進(jìn)在視覺上暗示該指令屬于主要功能蜈缤。
這是mov指令拾氓,表示移動。 我們將值2移至寄存器r0底哥。 在下一章中咙鞍,我們將了解有關(guān)寄存器的更多信息房官,現(xiàn)在不用擔(dān)心。 是的续滋,語法很尷尬翰守,因為目的地實際上在左邊。 在ARM語法中疲酌,它始終在左側(cè)蜡峰,因此我們說的是將r0寄存到立即數(shù)2之類的操作。在下一章中朗恳,我們將了解ARM中立即數(shù)的含義湿颅,不用擔(dān)心。
總而言之粥诫,該指令將2放入寄存器r0中(這實際上會覆蓋此時的r0寄存器)油航。
bx lr /* Return from main */
該指令bx表示分支和交換。 在這一點(diǎn)上怀浆,我們實際上并不關(guān)心交換部分谊囚。 分支意味著我們將改變指令執(zhí)行的流程。 ARM處理器一個接一個地依次運(yùn)行指令执赡,因此在上述動作之后秒啦,該bx將被運(yùn)行(此順序執(zhí)行并非特定于ARM,而是在幾乎所有架構(gòu)中都會發(fā)生)搀玖。 分支指令用于更改此隱式順序執(zhí)行余境。 在這種情況下,我們跳轉(zhuǎn)到lr寄存器說的內(nèi)容灌诅。 我們現(xiàn)在不在乎lr包含什么芳来。 足以理解該指令只是離開了主要功能,從而有效地結(jié)束了我們的程序猜拾。
main的結(jié)果是程序的錯誤代碼即舌,并且在離開函數(shù)時必須將結(jié)果存儲在寄存器r0中,因此我們main執(zhí)行的mov指令實際上將錯誤代碼設(shè)置為2挎袜。
以上就是本節(jié)的內(nèi)容顽聂。