這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Plan9組合語言備查
2013-02-21
本文源於對A Manual for the Plan 9 assembler的部分翻譯
機器
這個組合語言可以用於MIPS,SPARC,Intel 386,Intel 960,AMD 29000,Motorola 68020和68000,Motorola Power PC,AMD64,DEC Alpha,ARM.
寄存器
組合語言中所有預定義的符號都是大號的.資料寄存器是R0到R7;地址寄存器是A0到A7;浮點寄存器是F0到F7。
A6寄存器是供C編譯器使用的,用於指向資料。A6寄存器是常量,必須在C程式初始化時設定成外部定義的符號地址。
接著是硬體寄存器,比如在68020中的:CAAR,CACR,CCR,DFC,ISP,MSP,SFC,SR,USP,和VBR
組合語言定義了一些偽寄存器,FP,SP和TOS用於棧操作。FP是楨寄存器,0(FP)是第一個參數,4(FP)是第二個,依此類推。0(SP)是第一個自動變數。TOS是top-of-stack寄存器,用於向procedure中推入參數,儲存臨時變數等等。(注:這裡拿68020的硬體體係為例子的,看上去這個硬體體系有點像lisp語言的)
A7是硬體的棧地址寄存器,注意,混用A7和偽寄存器SP會出問題。組合語言接受像p+0(FP)這種類似標籤的指令,p是第一個參數。名稱來自於符號表中,對最終的程式結果沒有影響。
資料引用
所有的外部參考必須是相對某偽寄存器的,PC(program counter)或者SB(static base)。
BRA 2(PC)
允許使用標籤,比如
BRA return NOPreturn: RTS
使用標籤時沒有PC標註
偽寄存器SB指程式的起始地址空間。引用全域資料寫成對SB的位移,比如
MOVL $array(SB), TOS
將一個全域數組的址址push到棧上,或者
MOVL array+4(SB), TOS
將數組的第二個元素進棧,注意位移的使用。類似地,子常式調用必須使用SB:
BSR exit(SB)
檔案靜態變數使用符號
local <>+4(SB)
<>將會在載入時用一個獨一無二的整數填充
當一個程式開始時,它必須在訪問任何全域資料之前執行
MOVL $a6base(SB), A6
運算式
源檔案會被C編譯器預先處理,因此#define和#include可以正常工作
定址模式
o表示offset,d表示替換,是一個-128到127的常量.
放置資料
放到指令流:
LONG $12345
放到資料區段用偽指令DATA,使用兩個參數:放置的地址,包括大小,和放置的位置。例如,定義一個字串"abc":
DATA array+0(SB)/1, $' a'DATA array+1(SB)/1, $' b'DATA array+2(SB)/1, $' c'GLOBL array(SB), $4
或者
DATA array+0(SB)/4, $"abc\z"GLOBL array(SB), $4
/1定義位元組數,GLOBL產生全域符號,$4說明符號佔用多少位元組。未初始化資料是自動清0的。字元\z等價於C語言的\0.DATA中最多隻能是8個位元組
定義函數
進入點使用偽操作TEXT定義,接受函數名作為參數,以及自動預分配在棧上的位元組數,在寫組譯工具時位元組數一般是0。下面是一個返回兩數之和的函數:
TEXT sum(SB), $0 MOVL arg1+0(FP), R0 ADDL arg2+4(FP), R0 RTS
還可以帶個參數是控制最佳化的,1表示阻止最佳化,例如:
TEXT sum(SB), 1, $0 MOVL arg1+0(FP), R0 ADDL arg2+4(FP), R0 RTS
不會進行最佳化,而上面一個例子中會。帶特殊狀態的子常式,比如系統調用,不應該最佳化。
傳回值放在R0中。浮點傳回值放在F0中。返回結構體到C程式的函數,接受的第一個參數是儲存結果的地址,這種函數中調用協議不使用R0。調用函數要負責儲存自己的參數(caller saves)。
指令集
NOP在loader中直接被消除,而不是一條什麼都不做的指令。如果想產生什麼都不做的指令,使用WORD偽指令
i386
彙編器假定是32位保護模式。寄存器名是SP,AX,BX,CX,DX,BP,DI,和SI.棧指標是SP(不是偽寄存器)。傳回值寄存器是AX。沒有楨指標但是FP可以用為楨指標偽寄存器
二進位碼名大多和Intel手冊一樣,L,W,B分別表示32位,16位,8位操作。除了loads,stores,conditionals例外。所有load和store來自通用寄存器,特殊寄存器(比如CR0,CR3,GDTR,IDTR,SS,CS,DS,ES,FS和GS)或者記憶體的操作寫作:
MOVx src, dst
條件指令按68020而不是Intel彙編的習語,使用JOS,JOC,JCS,JCC,JEQ,JNE,JLS,JHI,JMI,JPL,JPS,JPC,JLT,JGE,JLE,JGT而不是JO,JNO,JB,JNB,JZ,JNZ,JBE,JNBE,JS,JNS,JP,JNP,JL,JNL,JLE,JNLE.
地址模式使用類似AX,(AX),(AX)(BX*4),10(AX),10(AX)(BX*4)的符號。相對AX的位移可以換成FP或者SB來訪問名稱,例如extern+5(SB)(AX*2).
注意:非相對跳轉JMP和CALL要加一個*符號。只有LOOP,LOOPEQ和LOOPNE是合法的迴圈指令。只有REP和REPN被當作重複。
AMD64
彙編器假定是64位元模式。如果想改到32位元模式,模式偽操作:
MODE $32
這個作用主要是檢測給定的模式中指令是否合法,但是loader仍然假設是32位運算元和地址,調用和返回都是32位的PC。大多類似上面的386。體繫結構中有額外的R8到R15。所有寄存器都是64位,但是指令會訪問低8位,16位和32位。例如對AX進行MOVL會將低32位賦值,高32位清0。64位使用MOVQ。Plan 9的C語言使用額外寄存器是從R15往下。有一些MMX和XMM等指令。MMX寄存器是M0到M7,XMM寄存器是X0到X15。都統一使用L表示'long word'(32位),Q表示'quad word'(64位)。有些指令使用O('octword')表示128位。C語言的long long類型是64位的,但它是傳遞和返回的是值而不是引用。更要注意的是,C指標是64位的。AX仍然是傳回值,但跟386不同的是,浮點傳回值是X0。所有少於8位元組的參數在棧中都是按8位元組對齊的。
本來看這個的目的是源於對go語言彙編的學習,結果看了一圈發現意義不大,還不如直接看看各種情況下go產生的彙編碼,在實踐中學習。
func f(x,y int32) int32 { return x}
彙編出來之後是
--- prog list "f" ---0000 (test.go:3) TEXT f+0(SB),$0-120001 (test.go:4) MOVL x+0(FP),BX0002 (test.go:4) MOVL BX,.noname+8(FP)0003 (test.go:4) RET ,
x+0(FP)中x是變數名x,這個好像沒什麼用,0(FP)這個是指第一個參數。.noname也是沒什麼用的。注意的是這裡把最後的傳回值放到了8(FP)。0(FP)是參數x,4(FP)是參數y,因此可以看出go語言的函數調用協議:傳回值是挨著參數放在棧中的。這樣就很容易解釋多值返回了。
這個代碼有點短,調用的時候被內聯了。如果寫長一點,再看看函數調用產生的彙編
f(3,4)
彙編之後
0034 (test.go:14) MOVL $3,(SP)0035 (test.go:14) MOVL $4,4(SP)0036 (test.go:14) CALL ,f+0(SB)0037 (test.go:15) RET ,
這裡可以看出參數的進棧順序,SP之上依次是第一個參數,第二個參數…