如何閱讀Go源碼?
前言
哈嘍,大家好,我是
asong;最近在看Go語言調(diào)度器相關(guān)的源碼,發(fā)現(xiàn)看源碼真是個(gè)技術(shù)活,所以本文就簡單總結(jié)一下該如何查看Go源碼,希望對你們有幫助。
Go源碼包括哪些?
以我個(gè)人理解,Go源碼主要分為兩部分,一部分是官方提供的標(biāo)準(zhǔn)庫,一部分是Go語言的底層實(shí)現(xiàn),Go語言的所有源碼/標(biāo)準(zhǔn)庫/編譯器都在src目錄下:https://github.com/golang/go/tree/master/src,想看什么庫的源碼任君選擇;
觀看Go標(biāo)準(zhǔn)庫 and Go底層實(shí)現(xiàn)的源代碼難易度也是不一樣的,我們一般也可以先從標(biāo)準(zhǔn)庫入手,挑選你感興趣的模塊,把它吃透,有了這個(gè)基礎(chǔ)后,我們在看Go語言底層實(shí)現(xiàn)的源代碼會(huì)稍微輕松一些;下面就針對我個(gè)人的一點(diǎn)學(xué)習(xí)心得分享一下如何查看Go源碼;
查看標(biāo)準(zhǔn)庫源代碼
標(biāo)準(zhǔn)庫的源代碼看起來稍容易些,因?yàn)闃?biāo)準(zhǔn)庫也屬于上層應(yīng)用,我們可以借助IDE的幫忙,其在IDE上就可以跳轉(zhuǎn)到源代碼包,我們只需要不斷來回跳轉(zhuǎn)查看各個(gè)函數(shù)實(shí)現(xiàn)做好筆記即可,因?yàn)橐恍┰创a設(shè)計(jì)的比較復(fù)雜,大家在看時(shí)最好通過畫圖輔助一下,個(gè)人覺得畫UML是最有助于理解的,能更清晰的理清各個(gè)實(shí)體的關(guān)系;
有些時(shí)候只看代碼是很難理解的,這時(shí)我們使用在線調(diào)試輔助我們理解,使用IDE提供的調(diào)試器或者GDB都可以達(dá)到目的,寫一個(gè)簡單的demo,斷點(diǎn)一打,單步調(diào)試走起來,比如你要查看fmt.Println的源代碼,開局一個(gè)小紅點(diǎn),然后就是點(diǎn)點(diǎn)點(diǎn);

查看Go語言底層實(shí)現(xiàn)
人都是會(huì)對未知領(lǐng)域充滿好奇,當(dāng)使用一段時(shí)間Go語言后,就想更深入的搞明白一些事情,例如:Go程序的啟動(dòng)過程是怎樣的,goroutine是怎么調(diào)度的,map是怎么實(shí)現(xiàn)的等等一些Go底層的實(shí)現(xiàn),這種直接依靠IDE跳轉(zhuǎn)追溯代碼是辦不到的,這些都屬于Go語言的內(nèi)部實(shí)現(xiàn),大都在src目錄下的runtime包內(nèi)實(shí)現(xiàn),其實(shí)現(xiàn)了垃圾回收,并發(fā)控制, 棧管理以及其他一些 Go 語言的關(guān)鍵特性,在編譯Go代碼為機(jī)器代碼時(shí)也會(huì)將其也編譯進(jìn)來,runtime就是Go程序執(zhí)行時(shí)候使用的庫,所以一些Go底層原理都在這個(gè)包內(nèi),我們需要借助一些方式才能查看到Go程序執(zhí)行時(shí)的代碼,這里分享兩種方式:分析匯編代碼、dlv調(diào)試;

分析匯編代碼
前面我們已經(jīng)介紹了Go語言實(shí)現(xiàn)了runtime庫,我們想看到一些Go語言關(guān)鍵字特性對應(yīng)runtime里的那個(gè)函數(shù),可以查看匯編代碼,Go語言的匯編使用的plan9,與x86匯編差別還是很大,很多朋友都不熟悉plan9的匯編,但是要想看懂Go源碼還是要對plan9匯編有一個(gè)基本的了解的,這里推薦曹大的文章:plan9 assembly 完全解析,會(huì)一點(diǎn)匯編我們就可以看源代碼了,比如想在我們想看make是怎么初始化slice的,這時(shí)我們可以先寫一個(gè)簡單的demo:
//?main.go
import?"fmt"
func?main()?{
?s?:=?make([]int,?10,?20)
?fmt.Println(s)
}
有兩種方式可以查看匯編代碼:
1.?go?tool?compile?-S?-N?-l?main.go
2.?go?build?main.go?&&?go?tool?objdump?./main
方式一是將源代碼編譯成.o文件,并輸出匯編代碼,方式二是反匯編,這里推薦使用方式一,執(zhí)行方式一命令后,我們可以看到對應(yīng)的匯編代碼如下:

s := make([]int, 10, 20)對應(yīng)的源代碼就是 runtime.makeslice(SB),這時(shí)候我們就去runtime包下找makeslice函數(shù),不斷追蹤下去就可查看源碼實(shí)現(xiàn)了,可在runtime/slice.go中找到:

在線調(diào)試
雖然上面的方法可以幫助我們定位到源代碼,但是后續(xù)的操作全靠review還是難于理解的,如果能在線調(diào)試跟蹤代碼可以更好助于我們理解,目前Go語言支持GDB、LLDB、Delve調(diào)試器,但只有Delve是專門為Go語言設(shè)計(jì)開發(fā)的調(diào)試工具,所以使用Delve可以輕松調(diào)試Go匯編程序,Delve的入門文章有很多,這篇就不在介紹Delve的詳細(xì)使用方法,入門大家可以看曹大的文章:https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-09-debug.html,本文就使用一個(gè)小例子帶大家來看一看dlv如何調(diào)試Go源碼,大家都知道向一個(gè)nil的切片追加元素,不會(huì)有任何問題,在源碼中是怎么實(shí)現(xiàn)的呢?接下老我們使用dlv調(diào)試跟蹤一下,先寫一個(gè)小demo:
import?"fmt"
func?main()?{
?var?s?[]int
?s?=?append(s,?1)
?fmt.Println(s)
}
進(jìn)入命令行包目錄,然后輸入dlv debug進(jìn)入調(diào)試
$?dlv?debug
Type?'help'?for?list?of?commands.
(dlv)
因?yàn)檫@里我們想看到append的內(nèi)部實(shí)現(xiàn),所以在append那行加上斷點(diǎn),執(zhí)行如下命令:
(dlv)?break?main.go:7
Breakpoint?1?set?at?0x10aba57?for?main.main()?./main.go:7
執(zhí)行continue命令,運(yùn)行到斷點(diǎn)處:
(dlv)?continue
>?main.main()?./main.go:7?(hits?goroutine(1):1?total:1)?(PC:?0x10aba57)
?????2:?
?????3:?import?"fmt"
?????4:?
?????5:?func?main()?{
?????6:?????????var?s?[]int
=>???7:?????????s?=?append(s,?1)
?????8:?????????fmt.Println(s)
?????9:?}
接下來我們執(zhí)行disassemble反匯編命令查看main函數(shù)對應(yīng)的匯編代碼:
(dlv)?disassemble
TEXT?main.main(SB)?/Users/go/src/asong.cloud/Golang_Dream/code_demo/src_code/main.go
????????main.go:5???????0x10aba20???????4c8d6424e8??????????????????????lea?r12,?ptr?[rsp-0x18]
????????main.go:5???????0x10aba25???????4d3b6610????????????????????????cmp?r12,?qword?ptr?[r14+0x10]
????????main.go:5???????0x10aba29???????0f86f6000000????????????????????jbe?0x10abb25
????????main.go:5???????0x10aba2f???????4881ec98000000??????????????????sub?rsp,?0x98
????????main.go:5???????0x10aba36???????4889ac2490000000????????????????mov?qword?ptr?[rsp+0x90],?rbp
????????main.go:5???????0x10aba3e???????488dac2490000000????????????????lea?rbp,?ptr?[rsp+0x90]
????????main.go:6???????0x10aba46???????48c744246000000000??????????????mov?qword?ptr?[rsp+0x60],?0x0
????????main.go:6???????0x10aba4f???????440f117c2468????????????????????movups?xmmword?ptr?[rsp+0x68],?xmm15
????????main.go:7???????0x10aba55???????eb00????????????????????????????jmp?0x10aba57
=>??????main.go:7???????0x10aba57*??????488d05a2740000??????????????????lea?rax,?ptr?[rip+0x74a2]
????????main.go:7???????0x10aba5e???????31db????????????????????????????xor?ebx,?ebx
????????main.go:7???????0x10aba60???????31c9????????????????????????????xor?ecx,?ecx
????????main.go:7???????0x10aba62???????4889cf??????????????????????????mov?rdi,?rcx
????????main.go:7???????0x10aba65???????be01000000??????????????????????mov?esi,?0x1
????????main.go:7???????0x10aba6a???????e871c3f9ff??????????????????????call?$runtime.growslice
????????main.go:7???????0x10aba6f???????488d5301????????????????????????lea?rdx,?ptr?[rbx+0x1]
????????main.go:7???????0x10aba73???????eb00????????????????????????????jmp?0x10aba75
????????main.go:7???????0x10aba75???????48c70001000000??????????????????mov?qword?ptr?[rax],?0x1
????????main.go:7???????0x10aba7c???????4889442460??????????????????????mov?qword?ptr?[rsp+0x60],?rax
????????main.go:7???????0x10aba81???????4889542468??????????????????????mov?qword?ptr?[rsp+0x68],?rdx
????????main.go:7???????0x10aba86???????48894c2470??????????????????????mov?qword?ptr?[rsp+0x70],?rcx
????????main.go:8???????0x10aba8b???????440f117c2450????????????????????movups?xmmword?ptr?[rsp+0x50],?xmm15
????????main.go:8???????0x10aba91???????488d542450??????????????????????lea?rdx,?ptr?[rsp+0x50]
????????main.go:8???????0x10aba96???????4889542448??????????????????????mov?qword?ptr?[rsp+0x48],?rdx
????????main.go:8???????0x10aba9b???????488b442460??????????????????????mov?rax,?qword?ptr?[rsp+0x60]
????????main.go:8???????0x10abaa0???????488b5c2468??????????????????????mov?rbx,?qword?ptr?[rsp+0x68]
????????main.go:8???????0x10abaa5???????488b4c2470??????????????????????mov?rcx,?qword?ptr?[rsp+0x70]
????????main.go:8???????0x10abaaa???????e8f1dff5ff??????????????????????call?$runtime.convTslice
????????main.go:8???????0x10abaaf???????4889442440??????????????????????mov?qword?ptr?[rsp+0x40],?rax
????????main.go:8???????0x10abab4???????488b542448??????????????????????mov?rdx,?qword?ptr?[rsp+0x48]
????????main.go:8???????0x10abab9???????8402????????????????????????????test?byte?ptr?[rdx],?al
????????main.go:8???????0x10ababb???????488d35be640000??????????????????lea?rsi,?ptr?[rip+0x64be]
????????main.go:8???????0x10abac2???????488932??????????????????????????mov?qword?ptr?[rdx],?rsi
????????main.go:8???????0x10abac5???????488d7a08????????????????????????lea?rdi,?ptr?[rdx+0x8]
????????main.go:8???????0x10abac9???????833d30540d0000??????????????????cmp?dword?ptr?[runtime.writeBarrier],?0x0
????????main.go:8???????0x10abad0???????7402????????????????????????????jz?0x10abad4
????????main.go:8???????0x10abad2???????eb06????????????????????????????jmp?0x10abada
????????main.go:8???????0x10abad4???????48894208????????????????????????mov?qword?ptr?[rdx+0x8],?rax
????????main.go:8???????0x10abad8???????eb08????????????????????????????jmp?0x10abae2
????????main.go:8???????0x10abada???????e8213ffbff??????????????????????call?$runtime.gcWriteBarrier
????????main.go:8???????0x10abadf???????90??????????????????????????????nop
????????main.go:8???????0x10abae0???????eb00????????????????????????????jmp?0x10abae2
????????main.go:8???????0x10abae2???????488b442448??????????????????????mov?rax,?qword?ptr?[rsp+0x48]
????????main.go:8???????0x10abae7???????8400????????????????????????????test?byte?ptr?[rax],?al
????????main.go:8???????0x10abae9???????eb00????????????????????????????jmp?0x10abaeb
????????main.go:8???????0x10abaeb???????4889442478??????????????????????mov?qword?ptr?[rsp+0x78],?rax
????????main.go:8???????0x10abaf0???????48c784248000000001000000????????mov?qword?ptr?[rsp+0x80],?0x1
????????main.go:8???????0x10abafc???????48c784248800000001000000????????mov?qword?ptr?[rsp+0x88],?0x1
????????main.go:8???????0x10abb08???????bb01000000??????????????????????mov?ebx,?0x1
????????main.go:8???????0x10abb0d???????4889d9??????????????????????????mov?rcx,?rbx
????????main.go:8???????0x10abb10???????e8aba8ffff??????????????????????call?$fmt.Println
????????main.go:9???????0x10abb15???????488bac2490000000????????????????mov?rbp,?qword?ptr?[rsp+0x90]
????????main.go:9???????0x10abb1d???????4881c498000000??????????????????add?rsp,?0x98
????????main.go:9???????0x10abb24???????c3??????????????????????????????ret
????????main.go:5???????0x10abb25???????e8f61efbff??????????????????????call?$runtime.morestack_noctxt
????????.:0?????????????0x10abb2a???????e9f1feffff??????????????????????jmp?$main.main
從以上內(nèi)容我們看到調(diào)用了runtime.growslice方法,我們在這里加一個(gè)斷點(diǎn):
(dlv)?break?runtime.growslice
Breakpoint?2?set?at?0x1047dea?for?runtime.growslice()?/usr/local/opt/go/libexec/src/runtime/slice.go:162
之后我們再次執(zhí)行continue執(zhí)行到該斷點(diǎn)處:
(dlv)?continue
>?runtime.growslice()?/usr/local/opt/go/libexec/src/runtime/slice.go:162?(hits?goroutine(1):1?total:1)?(PC:?0x1047dea)
Warning:?debugging?optimized?function
???157:?//?NOT?to?the?new?requested?capacity.
???158:?//?This?is?for?codegen?convenience.?The?old?slice's?length?is?used?immediately
???159:?//?to?calculate?where?to?write?new?values?during?an?append.
???160:?//?TODO:?When?the?old?backend?is?gone,?reconsider?this?decision.
???161:?//?The?SSA?backend?might?prefer?the?new?length?or?to?return?only?ptr/cap?and?save?stack?space.
=>?162:?func?growslice(et?*_type,?old?slice,?cap?int)?slice?{
???163:?????????if?raceenabled?{
???164:?????????????????callerpc?:=?getcallerpc()
???165:?????????????????racereadrangepc(old.array,?uintptr(old.len*int(et.size)),?callerpc,?funcPC(growslice))
???166:?????????}
???167:?????????if?msanenabled?{
之后就是不斷的單步調(diào)試可以看出來切片的擴(kuò)容策略;到這里大家也就明白了為啥向nil的切片追加數(shù)據(jù)不會(huì)有問題了,因?yàn)樵谌萘坎粔驎r(shí)會(huì)調(diào)用growslice函數(shù)進(jìn)行擴(kuò)容,具體擴(kuò)容規(guī)則大家可以繼續(xù)追蹤,打臉網(wǎng)上那些瞎寫的文章。
上文我們介紹調(diào)試匯編的一個(gè)基本流程,下面在介紹兩個(gè)我在看源代碼時(shí)經(jīng)常使用的命令;
goroutines命令:通過 goroutines命令(簡寫grs),我們可以查看所goroutine,通過goroutine (alias: gr)命令可以查看當(dāng)前的gourtine:
(dlv)?grs
*?Goroutine?1?-?User:?./main.go:7?main.main?(0x10aba6f)?(thread?218565)
??Goroutine?2?-?User:?/usr/local/opt/go/libexec/src/runtime/proc.go:367?runtime.gopark?(0x1035232)?[force?gc?(idle)]
??Goroutine?3?-?User:?/usr/local/opt/go/libexec/src/runtime/proc.go:367?runtime.gopark?(0x1035232)?[GC?sweep?wait]
??Goroutine?4?-?User:?/usr/local/opt/go/libexec/src/runtime/proc.go:367?runtime.gopark?(0x1035232)?[GC?scavenge?wait]
??Goroutine?5?-?User:?/usr/local/opt/go/libexec/src/runtime/proc.go:367?runtime.gopark?(0x1035232)?[finalizer?wait]
stack命令:通過stack命令(簡寫bt),我們可查看當(dāng)前函數(shù)調(diào)用棧信息:
(dlv)?bt
0??0x0000000001047e15?in?runtime.growslice
???at?/usr/local/opt/go/libexec/src/runtime/slice.go:183
1??0x00000000010aba6f?in?main.main
???at?./main.go:7
2??0x0000000001034e13?in?runtime.main
???at?/usr/local/opt/go/libexec/src/runtime/proc.go:255
3??0x000000000105f9c1?in?runtime.goexit
???at?/usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1581
regs命令:通過regs命令可以查看全部的寄存器狀態(tài),可以通過單步執(zhí)行來觀察寄存器的變化:
(dlv)?regs
???Rip?=?0x0000000001047e15
???Rsp?=?0x000000c00010de68
???Rax?=?0x00000000010b2f00
???Rbx?=?0x0000000000000000
???Rcx?=?0x0000000000000000
???Rdx?=?0x0000000000000008
???Rsi?=?0x0000000000000001
???Rdi?=?0x0000000000000000
???Rbp?=?0x000000c00010ded0
????R8?=?0x0000000000000000
????R9?=?0x0000000000000008
???R10?=?0x0000000001088c40
???R11?=?0x0000000000000246
???R12?=?0x000000c00010df60
???R13?=?0x0000000000000000
???R14?=?0x000000c0000001a0
???R15?=?0x00000000000000c8
Rflags?=?0x0000000000000202?????[IF?IOPL=0]
????Cs?=?0x000000000000002b
????Fs?=?0x0000000000000000
????Gs?=?0x0000000000000000
locals命令:通過locals命令,可以查看當(dāng)前函數(shù)所有變量值:
(dlv)?locals
newcap?=?1
doublecap?=?0
總結(jié)
看源代碼的過程是沒有捷徑可走的,如果說有,那就是可以先看一些大佬輸出的底層原理的文章,然后參照其文章一步步入門源碼閱讀,最終還是要自己去克服這個(gè)困難,本文介紹了我自己查看源碼的一些方式,你是否有更簡便的方式呢?歡迎評論區(qū)分享出來~。
好啦,本文到這里就結(jié)束了,我是asong,我們下期見。
