<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何閱讀 Go 源碼?

          共 10856字,需瀏覽 22分鐘

           ·

          2022-06-07 13:10

          前言

          最近在看Go語言調度器相關的源碼,發(fā)現看源碼真是個技術活,所以本文就簡單總結一下該如何查看Go源碼,希望對你們有幫助。

          Go源碼包括哪些?

          以我個人理解,Go源碼主要分為兩部分,一部分是官方提供的標準庫,一部分是Go語言的底層實現,Go語言的所有源碼/標準庫/編譯器都在src目錄下:https://github.com/golang/go/tree/master/src,想看什么庫的源碼任君選擇;

          觀看Go標準庫 and Go底層實現的源代碼難易度也是不一樣的,我們一般也可以先從標準庫入手,挑選你感興趣的模塊,把它吃透,有了這個基礎后,我們在看Go語言底層實現的源代碼會稍微輕松一些;下面就針對我個人的一點學習心得分享一下如何查看Go源碼;

          查看標準庫源代碼

          標準庫的源代碼看起來稍容易些,因為標準庫也屬于上層應用,我們可以借助IDE的幫忙,其在IDE上就可以跳轉到源代碼包,我們只需要不斷來回跳轉查看各個函數實現做好筆記即可,因為一些源代碼設計的比較復雜,大家在看時最好通過畫圖輔助一下,個人覺得畫UML是最有助于理解的,能更清晰的理清各個實體的關系;

          有些時候只看代碼是很難理解的,這時我們使用在線調試輔助我們理解,使用IDE提供的調試器或者GDB都可以達到目的,寫一個簡單的demo,斷點一打,單步調試走起來,比如你要查看fmt.Println的源代碼,開局一個小紅點,然后就是點點點;

          查看Go語言底層實現

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

          分析匯編代碼

          前面我們已經介紹了Go語言實現了runtime庫,我們想看到一些Go語言關鍵字特性對應runtime里的那個函數,可以查看匯編代碼,Go語言的匯編使用的plan9,與x86匯編差別還是很大,很多朋友都不熟悉plan9的匯編,但是要想看懂Go源碼還是要對plan9匯編有一個基本的了解的,這里推薦曹大的文章:plan9 assembly 完全解析,會一點匯編我們就可以看源代碼了,比如想在我們想看make是怎么初始化slice的,這時我們可以先寫一個簡單的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í)行方式一命令后,我們可以看到對應的匯編代碼如下:

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

          在線調試

          雖然上面的方法可以幫助我們定位到源代碼,但是后續(xù)的操作全靠review還是難于理解的,如果能在線調試跟蹤代碼可以更好助于我們理解,目前Go語言支持GDBLLDBDelve調試器,但只有Delve是專門為Go語言設計開發(fā)的調試工具,所以使用Delve可以輕松調試Go匯編程序,Delve的入門文章有很多,這篇就不在介紹Delve的詳細使用方法,入門大家可以看曹大的文章:https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-09-debug.html,本文就使用一個小例子帶大家來看一看dlv如何調試Go源碼,大家都知道向一個nil的切片追加元素,不會有任何問題,在源碼中是怎么實現的呢?接下老我們使用dlv調試跟蹤一下,先寫一個小demo

          import?"fmt"

          func?main()?{
          ?var?s?[]int
          ?s?=?append(s,?1)
          ?fmt.Println(s)
          }

          進入命令行包目錄,然后輸入dlv debug進入調試

          $?dlv?debug
          Type?'help'?for?list?of?commands.
          (dlv)

          因為這里我們想看到append的內部實現,所以在append那行加上斷點,執(zhí)行如下命令:

          (dlv)?break?main.go:7
          Breakpoint?1?set?at?0x10aba57?for?main.main()?./main.go:7

          執(zhí)行continue命令,運行到斷點處:

          (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函數對應的匯編代碼:

          (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

          從以上內容我們看到調用了runtime.growslice方法,我們在這里加一個斷點:

          (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í)行到該斷點處:

          (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?{

          之后就是不斷的單步調試可以看出來切片的擴容策略;到這里大家也就明白了為啥向nil的切片追加數據不會有問題了,因為在容量不夠時會調用growslice函數進行擴容,具體擴容規(guī)則大家可以繼續(xù)追蹤,打臉網上那些瞎寫的文章。

          上文我們介紹調試匯編的一個基本流程,下面在介紹兩個我在看源代碼時經常使用的命令;

          • goroutines命令:通過goroutines命令(簡寫grs),我們可以查看所goroutine,通過goroutine (alias: gr)命令可以查看當前的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),我們可查看當前函數調用棧信息:
          (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命令,可以查看當前函數所有變量值:
          (dlv)?locals
          newcap?=?1
          doublecap?=?0

          總結

          看源代碼的過程是沒有捷徑可走的,如果說有,那就是可以先看一些大佬輸出的底層原理的文章,然后參照其文章一步步入門源碼閱讀,最終還是要自己去克服這個困難,本文介紹了我自己查看源碼的一些方式,你是否有更簡便的方式呢?歡迎評論區(qū)分享出來~。

          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  四川美女一片毛片 | 91小仙女jK白丝袜呻吟 | 成人福利视频网站 | 国产夫妻操逼视频 | 五月婷婷操|