肝了一上午golang之plan9入門

【關(guān)注公眾號】「syd3600520」 回復(fù)002 獲取Go相關(guān)學(xué)習(xí)資料

從計算機(jī)誕生到現(xiàn)在掌眠,編程語言的發(fā)展大致分為了三個階段

  • 從打孔程序的機(jī)器語言
  • 一系列指令夏醉、寄存器代碼的匯編語言
  • 再到我們?nèi)粘J褂玫母呒壵Z言

機(jī)器語言一堆的0/1代碼確實反人類,匯編語言指令繁雜 不同機(jī)器設(shè)備還有較大差異涧衙。比如x86架構(gòu)的匯編指令一般有兩種格式:

  • Intel匯編

    • DOS肤视、Windows包括我們之前了解的8086處理器
    • Windows派系:VC編譯器
  • AT&T匯編

    • Linux赂弓、Unix、Mac OS
    • Unix派系:GCC編譯器

而Go使用的匯編叫做plan9匯編這些東西的確我們現(xiàn)在使用的高級語言的編譯器都幫助我們屏蔽掉了孽鸡,但是今天我們要來學(xué)學(xué)Go的plan9匯編蹂午,要是硬扛為什么?沒錯 我是為了炫技!!!

對于一只老鳥來說彬碱,我覺得搞搞Plan9匯編還是有不少益處的:

  • 可以搞懂一段代碼底層到底是如何運行的 性能極致追求的優(yōu)化
  • 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)如何運行 比如hashmap豆胸、channel
  • 反編譯對二進(jìn)制包進(jìn)行分析
  • 繞過go系統(tǒng)限制 訪問私有方法
  • ......

常用指令

匯編其實跟Go Java這些語言類似無非是變量、方法等巷疼。的確匯編存在比較多的指令晚胡、寄存器代碼。我對待匯編語言就像是對待學(xué)習(xí)的日語一樣嚼沿,雖然不少晦澀難記的單詞 但是先掌握好五十音行 再搞懂語法估盘,單詞的問題可以回頭查閱,常用的也就那么多

常數(shù)定義

plan9匯編中使用num表示常數(shù)骡尽,可以為負(fù)遣妥,默認(rèn)情況為十進(jìn)制,可以使用0x123的形式表示十六進(jìn)制

操作方向

plan9匯編操作數(shù)方向 與intel匯編方向相反

//plan9 匯編
MOVQ $123, AX
//intel匯編
mov rax, 123

棧擴(kuò)大攀细、縮小

plan9中棧操作并沒有push pop箫踩,而是采用subadd SP

SUBQ $0x18, SP //對SP做減法 為函數(shù)分配函數(shù)棧幀
ADDQ $0x18, SP //對SP做加法 清楚函數(shù)棧幀

數(shù)據(jù)copy

MOVB $1, DI // 1 byte
MOVW $0x10, BX // 2bytes
MOVD $1, DX // 4 bytes
MOVQ $-10, AX // 8 bytes

計算指令

ADDQ AX, BX // BX += AX
SUBQ AX, BX // BX -= AX
IMULQ AX, BX // BX *= AX

跳轉(zhuǎn)

//無條件跳轉(zhuǎn)
JMP addr // 跳轉(zhuǎn)到地址,地址可為代碼中的地址 不過實際上手寫不會出現(xiàn)這種東西
JMP label // 跳轉(zhuǎn)到標(biāo)簽 可以跳轉(zhuǎn)到同一函數(shù)內(nèi)的標(biāo)簽位置
JMP 2(PC) // 以當(dāng)前置頂為基礎(chǔ)谭贪,向前/后跳轉(zhuǎn)x行
JMP -2(PC) //同上
//有條件跳轉(zhuǎn)
JNZ target // 如果zero flag被set過境钟,則跳轉(zhuǎn)

變量聲明

匯編中的變量一般是存儲在.rodata或者.data段中的只讀值。對應(yīng)到應(yīng)用層就是已經(jīng)初始化的全局的const故河、var變量/常量

DATA symbol+offset(SB)/width,value

上面的語句初始化symbol+offset(SB)的數(shù)據(jù)中width bytes,賦值為value吱韭,相對于棧操作,SB的操作都是增地址鱼的,棧時減地址

GLOBL runtime·tlsoffset(SB), NOPTR, $4
// 聲明一個全局變量tlsoffset理盆,4byte,沒有DATA部分凑阶,因其值為0猿规。
// NOPTR 表示這個變量數(shù)據(jù)中不存在指針,GC不需要掃描宙橱。

(使用DATA結(jié)合GLOBL來定義一個變量姨俩,GLOBL必須跟在DATA指令之后)當(dāng)時我嘗試了下發(fā)現(xiàn)GLOBL不放在DATA之后 也沒啥問題蘸拔,如果知道的小伙伴可以分享一下。

舉個栗子:

pkg.go

package pkg

var Id int

var Name string

pkg_amd64.s

GLOBL ·Id(SB),$8

DATA ·Id+0(SB)/1,$0x37
DATA ·Id+1(SB)/1,$0x25
DATA ·Id+2(SB)/1,$0x00
DATA ·Id+3(SB)/1,$0x00
DATA ·Id+4(SB)/1,$0x00
DATA ·Id+5(SB)/1,$0x00
DATA ·Id+6(SB)/1,$0x00
DATA ·Id+7(SB)/1,$0x00

GLOBL ·Name(SB),$24
DATA ·Name+0(SB)/8,$·Name+16(SB)
DATA ·Name+8(SB)/8,$6
DATA ·Name+16(SB)/8,$"gopher"

函數(shù)聲明

舉個栗子:

fun.go

package fun

//go:noinline
func Swap(a, b int) (int, int)

fun_amd64.s

#include "textflag.h"
// func Swap(a,b int) (int,int)
告訴匯編器該數(shù)據(jù)放到TEXT區(qū)
 |          告訴匯編器這是基于靜態(tài)地址的數(shù)據(jù)(static base)
 |             |
TEXT fun·Swap(SB),NOSPLIT,$0-32
    MOVQ a+0(FP), AX  // FP(Frame pointer)棧幀指針 這里指向棧幀最低位
    MOVQ b+8(FP), BX
    MOVQ BX ,ret0+16(FP)
    MOVQ AX ,ret1+24(FP)
    RET

上述代碼存儲在TEXT段中环葵。pkgname可以省略调窍,比如你的方法是fun·Swap(這里的· 是個unicode的中點 mac下的輸入方式為 option+shift+9),在編譯后的程序里的符號則是fun.Swap,總結(jié)起來如下:

method

stack frame size 棧幀大小(局部變量+可能需要的額外調(diào)用函數(shù)的參數(shù)空間的總大小,但不不包含調(diào)用其他函數(shù)時的ret address的大小)

arguments size 參數(shù)及返回值大小

若不指定NOSPLIT张遭,arguments size必須指定邓萨。

測試代碼

func main() {
    println(pkg.Id)
    println(pkg.Name)

    a, b := 1, 2
    a, b = fun.Swap(a, b)
    fmt.Println(a, b)
}

寄存器

Go匯編引入了4個偽寄存器,這4個寄存器時編譯器用來維護(hù)上下文菊卷、特殊標(biāo)識等作用的:

FP(Frame pointer):arguments and locals
PC(Program counter): jumps and branches
SB(Static base pointer):global symbols
SP(Stack pointer):top of stack

所有用戶空間的數(shù)據(jù)都可以通過FP/SP(局部數(shù)據(jù)缔恳、輸入?yún)?shù)、返回值)和SB(全局?jǐn)?shù)據(jù))訪問洁闰。通常情況下歉甚,不會對SB/FP寄存器進(jìn)行運算操作,通常情況會以SB/FP/SP作為基準(zhǔn)地址扑眉,進(jìn)行偏移纸泄、解引用等操作

其中

  1. SP是棧指針,用來指向局部變量和函數(shù)調(diào)用的參數(shù)腰素,通過symbol+offset(SP)的方式使用刃滓。SP指向local stack frame的棧頂,所以使用時需要使用負(fù)偏移量耸弄,取之范圍為[-framesize,0)咧虎。foo-8(SP)表示foo的棧第8byte。SP有偽SP和硬件SP的區(qū)分计呈,如果硬件支持SP寄存器砰诵,那么不加name的時候就是訪問硬件寄存器,因此x-8(SP)-8(SP)訪問的會是不同的內(nèi)存空間捌显。對SP和PC的訪問都應(yīng)該帶上name茁彭,若要訪問對應(yīng)的硬件寄存器可以使用RSP。
  • 偽SP:本地變量最高起始地址
  • 硬件SP:函數(shù)棧真實棧頂?shù)刂?/li>

他們的關(guān)系為:

  • 若沒有本地變量: 偽SP=硬件SP+8
  • 若有本地變量:偽SP=硬件SP+16+本地變量空間大小
  1. FP偽寄存器

FP偽寄存器:用來標(biāo)識函數(shù)參數(shù)扶歪、返回值理肺,編譯器維護(hù)了基于FP偏移的棧上參數(shù)指針,0(FP)表示function的第一個參數(shù)善镰,8(FP)表示第二個參數(shù)(64位系統(tǒng)上)后臺加上偏移量就可以訪問更多的參數(shù)妹萨。要訪問具體function的參數(shù),編譯器強(qiáng)制要求必須使用name來訪問FP炫欺,比如 foo+0(FP)獲取foo的第一個參數(shù)乎完,foo+8(FP)獲取第二個參數(shù)。

與偽SP寄存器的關(guān)系是:

  • 若本地變量或者棧調(diào)用存嚴(yán)格split關(guān)系(無NOSPLIT)品洛,偽FP=偽SP+16
  • 否則 偽FP=偽SP+8
  • FP是訪問入?yún)⑹饕獭⒊鰠⒌幕纺ν埃话阌谜蚱苼韺ぶ罚琒P是訪問本地變量的起始基址帽揪,一般用負(fù)向偏移來尋址
  • 修改硬件SP硝清,會引起偽SP、FP同步變化
SUBQ $16, SP // 這里golang解引用時转晰,偽SP/FP都會-16
  1. SB偽寄存器可以理解為原始內(nèi)存耍缴,foo(SB)的意思是用foo來代表內(nèi)存中的一個地址。foo(SB)可以用來定義全局的function和數(shù)據(jù)挽霉,foo<>(SB)表示foo只在當(dāng)前文件可見,跟C中的static效果類似变汪。此外可以在引用上加偏移量侠坎,如foo+4(SB)表示foo+4bytes的地址
  2. 參數(shù)/本地變量訪問

通過symbol+/-offset(FP/SP)的方式進(jìn)行使用,例如arg0+0(FP)表示函數(shù)第一個參數(shù)的位置裙盾,arg1+8(FP)表示函數(shù)參數(shù)偏移8byte的另一個參數(shù)实胸。arg0/arg1用于助記,但是必須存在番官,否則無法通過編譯(golang會識別并做處理)庐完。

其中對于SP來說,還有一種訪問方式: +/-offset(FP) 這里SP前面沒有symbol修飾徘熔,代表這是硬件SP门躯??酷师?

  1. PC寄存器

實際上就是在體系結(jié)構(gòu)的知識中常見的PC寄存器讶凉,在x86平臺下對應(yīng)ip寄存器,amd64上則是rip山孔。除了個別跳轉(zhuǎn)之外懂讯,手寫代碼與PC寄存器打交道的情況較少。

  1. BP寄存器

還有BP寄存器台颠,表示已給調(diào)用棧的起始棧底(棧的方向從大到小褐望,SP表示棧頂);一般用的不多串前,若需要做手動維護(hù)調(diào)用棧關(guān)系瘫里,需要用到BP寄存器,手動split調(diào)用棧荡碾。

  1. 通用寄存器

在plan9匯編里還可以直接使用amd64的通用寄存器减宣,應(yīng)用代碼層面會用到的通用寄存器主要是:

rax,rbx,rcx,rdx,rdi,rsi,r8~r15這14個寄存器。plan9中使用寄存器不需要帶r或e的前綴玩荠,例如rax漆腌,只要寫AX即可:

MOVQ $101, AX

示例:

func Add(a ,b int) (c int){
  sum := 0
  return a + b + sum
}

各變量通用寄存器解引用如下:(偽FP=偽SP+16=硬件SP+24)

  • a: a+0(SP)或者a+16(SP)
  • b: b+8(SP)或者a+24(SP)
  • c: c+16(SP)或者a+32(SP)
  • sum:sum-8(SP)或者a-24(FP)
  1. TLS偽寄存器

該寄存器存儲當(dāng)前goroutine g結(jié)構(gòu)地址

Go程序如何轉(zhuǎn)換為plan9贼邓?

//方法一
go build -gcflags="-S" hello.go
//方法二
go tool compile -N -l -S hello.go //禁止優(yōu)化
//方法三
go build -gcflags="-N -l -m" -o xx xx.go
go tool objdump <binary> 
go tool objdump -s <method name> <binary> //反匯編指定函數(shù)
//方法一、二生成的過程中的匯編
//方法三 生成的事最終機(jī)器碼的匯編

棧結(jié)構(gòu)

image

函數(shù)調(diào)用棧關(guān)系

call stack

X86平臺上BP寄存器闷尿,通常用來指示函數(shù)棧的起始位置塑径,僅僅起一個指示作用,現(xiàn)代編譯器生成的代碼通常不會用到BP寄存器填具,但是可能某些debug工具會用到該寄存器來尋找函數(shù)參數(shù)统舀、局部變量等。因此我們寫匯編代碼時劳景,也最好將棧起始位置存儲在BP寄存器中誉简。因此amd64平臺上,會在函數(shù)返回值之后插入8byte來放置CALLER BP寄存器盟广。

此外需要注意的是闷串,CALLER BP是在編譯期由編譯器插入的,用戶手寫匯編代碼時筋量,計算framesize時是不包括這個CALLER BP部分的烹吵,但是要計算函數(shù)返回值的8byte。是否插入CALLER BP的主要判斷依據(jù)是:

  • 函數(shù)的棧幀大小大于0
  • 下述函數(shù)返回true
func Framepointer_enabled(goos, goarch string) bool {
  return framepointer_enabled != 0 && goarch == "amd64" && goos != "nacl"
}

此外需要注意桨武,go編譯器會將函數(shù)椑甙危空間自動加8,用于存儲BP寄存器呀酸,跳過這8字節(jié)后才是函數(shù)棧上局部變量的內(nèi)存凉蜂。邏輯上的FP/SP位置就是我們在寫匯編代碼時,計算便宜量時性誉,F(xiàn)P/SP的基準(zhǔn)位置跃惫,因此局部變量的內(nèi)存在邏輯SP的低地址側(cè),因此我們訪問時艾栋,需要向負(fù)方向偏移爆存。

實際上,在該函數(shù)被調(diào)用后蝗砾,編譯器會添加SUBQ/LEAQ代碼修改物理SP指向的位置先较。我們在反匯編的代碼中能看到這部分操作,因此我們需要注意物理SP與偽SP指向位置的差別悼粮。

舉個栗子:

func zzz(a, b, c int) [3]int{
    var d [3]int
    d[0], d[1], d[2] = a, b, c
    return d
}
stack SP FP

總結(jié)

助記符 名字 用途
AX 累加寄存器(AccumulatorRegister) 用于存放數(shù)據(jù)闲勺,包括算術(shù)、操作數(shù)扣猫、結(jié)果和臨時存放地址
BX 基址寄存器(BaseRegister) 用于存放訪問存儲器時的地址
CX 計數(shù)寄存器(CountRegister) 用于保存計算值菜循,用作計數(shù)器
DX 數(shù)據(jù)寄存器(DataRegister) 用于數(shù)據(jù)傳遞,在寄存器間接尋址中的I/O指令中存放I/O端口的地址
SP 堆棧頂指針(StackPointer) 如果是symbol+offset(SP)的形式表示go匯編的偽寄存器申尤;如果是offset(SP)的形式表示硬件寄存器
BP 堆棸┠唬基指針(BasePointer) 保存在進(jìn)入函數(shù)前的棧頂基址
SB 靜態(tài)基指針(StaticBasePointer) go匯編的偽寄存器衙耕。foo(SB)用于表示變量在內(nèi)存中的地址,foo+4(SB)表示foo起始地址往后偏移四字節(jié)勺远。一般用來聲明函數(shù)或全局變量
FP 棧幀指針(FramePointer) go匯編的偽寄存器橙喘。引用函數(shù)的輸入?yún)?shù),形式是symbol+offset(FP)胶逢,例如arg0+0(FP)
SI 源變址寄存器(SourceIndex) 用于存放源操作數(shù)的偏移地址
DI 目的寄存器(DestinationIndex) 用于存放目的操作數(shù)的偏移地址

操作指令

用于指導(dǎo)匯編如何進(jìn)行厅瞎。以下指令后綴<mark>Q</mark>說明是64位上的匯編指令。

助記符 指令種類 用途 示例
MOVQ 傳送 數(shù)據(jù)傳送 MOVQ 48, AX表示把48傳送AX中
LEAQ 傳送 地址傳送 LEAQ AX, BX表示把AX有效地址傳送到BX中
PUSHQ 傳送 棧壓入 PUSHQ AX表示先修改棧頂指針初坠,將AX內(nèi)容送入新的棧頂位置在go匯編中使用SUBQ代替
POPQ 傳送 棧彈出 POPQ AX表示先彈出棧頂?shù)臄?shù)據(jù)和簸,然后修改棧頂指針在go匯編中使用ADDQ代替
ADDQ 運算 相加并賦值 ADDQ BX, AX表示BX和AX的值相加并賦值給AX
SUBQ 運算 相減并賦值 略,同上
IMULQ 運算 無符號乘法 略碟刺,同上
IDIVQ 運算 無符號除法 IDIVQ CX除數(shù)是CX锁保,被除數(shù)是AX,結(jié)果存儲到AX中
CMPQ 運算 對兩數(shù)相減南誊,比較大小 CMPQ SI CX表示比較SI和CX的大小。與SUBQ類似蜜托,只是不返回相減的結(jié)果
CALL 轉(zhuǎn)移 調(diào)用函數(shù) CALL runtime.printnl(SB)表示通過<mark>println</mark>函數(shù)的內(nèi)存地址發(fā)起調(diào)用
JMP 轉(zhuǎn)移 無條件轉(zhuǎn)移指令 JMP 389無條件轉(zhuǎn)至0x0185地址處(十進(jìn)制389轉(zhuǎn)換成十六進(jìn)制0x0185)
JLS 轉(zhuǎn)移 條件轉(zhuǎn)移指令 JLS 389上一行的比較結(jié)果抄囚,左邊小于右邊則執(zhí)行跳到0x0185地址處(十進(jìn)制389轉(zhuǎn)換成十六進(jìn)制0x0185)

可以看到,表中的PUSHQPOPQ被去掉了橄务,這是因為在go匯編中幔托,對棧的操作并不是出棧入棧,而是通過對SP進(jìn)行運算來實現(xiàn)的蜂挪。

標(biāo)志位

助記符 名字 用途
OF 溢出 0為無溢出 1為溢出
CF 進(jìn)位 0為最高位無進(jìn)位或錯位 1為有
PF 奇偶 0表示數(shù)據(jù)最低8位中1的個數(shù)為奇數(shù)重挑,1則表示1的個數(shù)為偶數(shù)
AF 輔助進(jìn)位
ZF 0表示結(jié)果不為0 1表示結(jié)果為0
SF 符號 0表示最高位為0 1表示最高位為1

如有錯誤懇請指正。

參考文檔:

go asm

golang 匯編

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棠涮,一起剝皮案震驚了整個濱河市谬哀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌严肪,老刑警劉巖史煎,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驳糯,居然都是意外死亡篇梭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門酝枢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恬偷,“玉大人,你說我怎么就攤上這事帘睦∨刍迹” “怎么了坦康?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長协怒。 經(jīng)常有香客問我涝焙,道長,這世上最難降的妖魔是什么孕暇? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任仑撞,我火速辦了婚禮,結(jié)果婚禮上妖滔,老公的妹妹穿的比我還像新娘隧哮。我一直安慰自己,他們只是感情好座舍,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布沮翔。 她就那樣靜靜地躺著,像睡著了一般曲秉。 火紅的嫁衣襯著肌膚如雪采蚀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天承二,我揣著相機(jī)與錄音榆鼠,去河邊找鬼。 笑死亥鸠,一個胖子當(dāng)著我的面吹牛妆够,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播负蚊,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼神妹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了家妆?” 一聲冷哼從身側(cè)響起鸵荠,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伤极,沒想到半個月后腰鬼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡塑荒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年熄赡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片齿税。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡彼硫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拧篮,我是刑警寧澤词渤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站串绩,受9級特大地震影響缺虐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜礁凡,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一高氮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顷牌,春花似錦剪芍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至运挫,卻和暖如春状共,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谁帕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工峡继, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雇卷。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓鬓椭,卻偏偏與公主長得像颠猴,于是被迫代替她去往敵國和親关划。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容