本文將簡單介紹一下go語言的匯編聚假。
主要的內(nèi)容如下:
- plan 9 是什么伴郁?為什么學(xué)習(xí)plan9?
- plan9 的常見指令
- go程序如何轉(zhuǎn)換為plan9 ?
下面就開始簡單介紹一下告唆。
plan 9 是什么?為什么學(xué)習(xí)plan9?
plan9, Go一套自己的匯編帆啃。按照官方文檔的說法,其設(shè)計初衷是解決跨平臺的問題窍帝,但是沒有做好努潘。并且它不同于傳統(tǒng)的匯編,也就是說要想學(xué)習(xí)go匯編坤学,你需要重新學(xué)習(xí)一套語法疯坤。社區(qū)在爭論這個問題,有人說是因為go的幾個大佬深浮,原來是用plan9的贴膘。這個問題咱們這兒不討論。
那作為普通人略号,我們學(xué)匯編干啥呢刑峡?
裝逼!這是第一生產(chǎn)力玄柠!我一直有一個不成熟的想法突梦,希望自己能夠像機器一樣看穿代碼的運行。有時想想羽利,也挺可笑的宫患。
除了裝逼,真的一點用處都沒有了嗎这弧?
當然不是M尴小!匾浪!
說下我能想到的幾個點:
- 確定一段代碼底層執(zhí)行了什么函數(shù)
- 查看基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)的運行機制
- 查看內(nèi)存的分配
- 查看go在函數(shù)頭和函數(shù)尾插入的相關(guān)的調(diào)度
...
難道這幾條還不夠嗎皇帮?
plan9 的常見指令
其實匯編跟java, go等語言 沒啥區(qū)別蛋辈,無非也是變量属拾、方法等。只是我們做應(yīng)用層開發(fā)不常用而已冷溶。如果懂了基礎(chǔ)的語法渐白,其實也就這樣。下面列幾個常用的指令逞频,看不懂也沒事纯衍,多看幾遍就知道了,其實我也不是很熟苗胀,啥時候真用到襟诸,再回來看也行褒颈。
棧擴大、縮小
plan9 中棧操作并沒有使用push励堡,pop,而是采用sub 跟add SP堡掏。
SUBQ $0x18, SP // 對 SP 做減法应结,為函數(shù)分配函數(shù)棧幀
ADDQ $0x18, SP // 對 SP 做加法,清除函數(shù)棧幀
數(shù)據(jù)copy
MOVB $1, DI // 1 byte
MOVW $0x10, BX // 2 bytes
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)到標簽,可以跳轉(zhuǎn)到同一函數(shù)內(nèi)的標簽位置
JMP 2(PC) // 以當前指令為基礎(chǔ)亭畜,向前/后跳轉(zhuǎn) x 行
JMP -2(PC) // 同上
// 有條件跳轉(zhuǎn)
JNZ target // 如果 zero flag 被 set 過扮休,則跳轉(zhuǎn)
變量聲明
在匯編里所謂的變量,一般是存儲在 .rodata 或者 .data 段中的只讀值拴鸵。對應(yīng)到應(yīng)用層的話玷坠,就是已初始化過的全局的 const、var劲藐、static 變量/常量八堡。
DATA symbol+offset(SB)/width, value
使用 DATA 結(jié)合 GLOBL 來定義一個變量
GLOBL 必須跟在 DATA 指令之后:
DATA age+0x00(SB)/4, $18 // forever 18
GLOBL age(SB), RODATA, $4
DATA pi+0(SB)/8, $3.1415926
GLOBL pi(SB), RODATA, $8
DATA birthYear+0(SB)/4, $1988
GLOBL birthYear(SB), RODATA, $4
函數(shù)聲明
先看一個定義:
// func add(a, b int) int
// => 該聲明定義在同一個 package 下的任意 .go 文件中
// => 只有函數(shù)頭,沒有實現(xiàn)
TEXT pkgname·add(SB), NOSPLIT, $0-8
MOVQ a+0(FP), AX
MOVQ a+8(FP), BX
ADDQ AX, BX
MOVQ BX, ret+16(FP)
RET
代碼存儲在TEXT段中聘芜。
pkgname 可以省略兄渺。
比如你的方法是 runtime·main,在編譯之后的程序里的符號則是 runtime.main汰现。
參數(shù)及返回值大小
|
TEXT pkgname·add(SB),NOSPLIT,$32-32
| | |
包名 函數(shù)名 棧幀大小(局部變量+可能需要的額外調(diào)用函數(shù)的參數(shù)空間的總大小挂谍,但不包括調(diào)用其它函數(shù)時的 ret address 的大小)
寄存器
有4個核心的偽寄存器,這4個寄存器是編譯器用來維護上下文瞎饲、特殊標識等作用的:
FP(Frame pointer): arguments and locals
PC(Program counter): jumps and branches
SB(Static base pointer): global symbols
SP(Stack pointer): top of stack
還有很多其他的用法口叙,就先不摘抄了,后面用到再去查吧嗅战。
go程序如何轉(zhuǎn)換為plan9 ?
// 編譯
go build -gcflags="-S"
go tool compile -S hello.go
go tool compile -N -S hello.go // 禁止優(yōu)化
// 反編譯
go tool objdump <binary>
總結(jié)與后記
本文簡單的講述了plan9 是什么庐扫?什么用途? 也羅列了幾個plan9 常見的指令仗哨,增加自信形庭。最后簡單的幾行指令,教你如何看某一段代碼的plan9 指令厌漂。
如果時間允許萨醒,會結(jié)合前人的文章,針對go中常見的類型苇倡、api操作富纸、內(nèi)存分配囤踩、channel 等進行匯編指令級的學(xué)習(xí),給自己加油~
參考文獻
1晓褪、匯編 is so easy
2堵漱、Go 系列文章3 :plan9 匯編入門
3、golang內(nèi)核系列--深入理解plan9匯編&實踐