<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 實戰(zhàn)營系列」源碼調(diào)試:Go是如何判斷實現(xiàn)了interface

          共 6149字,需瀏覽 13分鐘

           ·

          2021-08-11 11:09


          本文來自于《Go 高級工程師實戰(zhàn)營》一期學(xué)員:鬼鸮

          原文地址:https://blog.csdn.net/zxxshaycormac/article/details/117606285


          本文中調(diào)試的go源碼為1.14.12版本,本文介紹的調(diào)試方法與go版本沒有關(guān)系


          我們在go的學(xué)習(xí)過程中,有可能會需要對go的源碼進行調(diào)試;但是我們直接跑程序的話,是沒法實現(xiàn)源碼調(diào)試的;所以這里來介紹一下go源碼的調(diào)試方法。


          使用goland進行調(diào)試,能夠有比較清楚的圖形化界面,這有助于我們在調(diào)試過程中對一些相關(guān)參數(shù)的查看,也能讓調(diào)試變簡單,所以我們使用goland進行調(diào)試。


          編寫你的程序


          想要進行源碼調(diào)試首先肯定得有你自己的代碼,你自己的代碼在運行的過程中會調(diào)用到你要調(diào)試的那部分源碼

          我這里用判斷結(jié)構(gòu)體是否實現(xiàn)了某interface做例子

          package main

          import (
           "fmt"
          )

          type Duck interface {
           Quack()
          }

          type Cat struct{}

          func (c Cat) Quacks() {
           fmt.Println("meow")
          }

          func main() {
           //./main.go:19:6: cannot use Cat literal (type Cat) as type Duck in assignment: Cat does not implement Duck (missing Quack method)
           var d Duck = Cat{}
           println(d)
          }

          因為結(jié)構(gòu)體Cat沒有實現(xiàn)Quack方法,所以這段代碼在19行是執(zhí)行不下去的,會報錯【報錯的內(nèi)容就是18行】,也就是會被檢查出來結(jié)構(gòu)體Cat沒有實現(xiàn)接口Duck

          用goland打開go源碼


          然后我們用goland打開go源碼,也就是goroot下的src文件夾,這里需要注意一下就是,goland調(diào)用的是windows版的go,我們想要調(diào)試的話實際就是讓goland去調(diào)用go源碼然后一步步執(zhí)行,所以這里要打開的是windows的go,當然你要是蘋果電腦你就開蘋果版goland調(diào)的go就是了,一個道理,總之我們就是用goland去跑的。

          打斷點并設(shè)置goland參數(shù)


          我們先在go源碼中找到你要調(diào)試的部分,這個找法多種多樣,比如我上面那樣故意寫了一個錯誤的代碼對不對,我們可以直接在src下搜索這段報錯,看看這個報錯是來自哪里的,也可以根據(jù)你豐富的知識,判斷你要調(diào)試的這個行為是發(fā)生在go源碼的哪個包中,然后直接去找那個包的main.go


          因為go判斷結(jié)構(gòu)體是否實現(xiàn)某interface是在抽象語法樹生成之后的編譯階段,且剛才那段報錯經(jīng)過搜索發(fā)現(xiàn)其存在于go源碼的src\cmd\compile\internal\gc\subr.go:610,固我們可以猜測調(diào)試的入口應(yīng)當為src\cmd\compile\main.go中的main方法,如下圖

          通過閱讀這段代碼,我們很明顯可以看出來,真正核心的邏輯肯定是在52行的gc.Main(archInit)方法中,固我們將52行定為調(diào)試的起點。


          我們在這里打一個斷點。


          goland的打斷點大家應(yīng)該都會我就不多解釋了,點擊行數(shù)52右邊的空白部分即可。

          打完斷點效果如下

          此時我們需要設(shè)置一下goland的參數(shù)

          我們右鍵點擊截圖中main函數(shù)左邊的綠色開始箭頭,會出現(xiàn)三個選項,分別是【運行】【調(diào)試】和【修改運行配置】,選擇【修改運行配置】,會出現(xiàn)一個彈窗如下圖所示

          就是配置里的參數(shù)我們需要進行調(diào)整


          第一項運行種類要選【文件】


          第三項輸出目錄寫你剛才自己寫的哪個代碼的路徑,寫到文件夾,也可以點擊輸入框后面的文件圖標直接打開文件選擇框去找,我的路徑是$GOPATH\src\test


          第五項工作目錄同上,我填的也是$GOPATH\src\test


          第八項程序參數(shù),需要填寫你剛才寫的那段代碼的go文件的路徑,我寫的是$GOPATH\src\test\main.go

          另外第八項上面那個【使用所有自定義構(gòu)建標記】要勾起來


          調(diào)整好以后如下圖

          然后點擊【應(yīng)用】或者【確定】進行保存



          開始調(diào)試


          現(xiàn)在我們就已經(jīng)可以進行源碼調(diào)試了

          我們右鍵點擊main.go左邊的綠色箭頭【對,還是剛才那個綠色箭頭】,這次我們選擇調(diào)試

          goland運行一小段時間后,我們就會進入調(diào)試狀態(tài)

          可以看到底部出現(xiàn)了調(diào)試面板,調(diào)試面板最左側(cè)和頂部有一些按鈕,面板左側(cè)是調(diào)用棧,面板右側(cè)是變量

          我們先簡單講一下頂部的五個按鈕吧,他們會比較常用

          第一個是【顯示執(zhí)行點】,就是在調(diào)試的過程中會有光標指向當前在執(zhí)行的邏輯【我覺得這個開不開無所謂】

          第二個叫【步過】,其實就是執(zhí)行當前行的邏輯,不去探究當前行調(diào)用的函數(shù)內(nèi)部的邏輯,對于那些我們不關(guān)注的函數(shù)就要使用步過

          第三個叫【步進】,也就是按步執(zhí)行,如果執(zhí)行的是一個函數(shù)則會跳轉(zhuǎn)到函數(shù)中,注意使用步進的話所有函數(shù)都會進,這使得你可以一路走到匯編代碼的地方

          第四個叫【步出】,也就是本函數(shù)后續(xù)邏輯自動執(zhí)行,我們回到本函數(shù)的調(diào)用處的下個邏輯

          第五個是【執(zhí)行到光標處】,就字面意思,我們在調(diào)試過程中可以直接用鼠標點擊我們關(guān)注的行,然后用這個按鈕直接略過中間的邏輯,執(zhí)行到我們剛才設(shè)置了光標的行


          比如我們在52行打了斷點并且我們執(zhí)行了調(diào)試,此時程序執(zhí)行到52行就會停下,我們可以點擊【步進】進入這個main函數(shù),這樣我們就來到了src\cmd\compile\internal\gc\main.go:144


          進入以后我們可以一直使用【步過】跳轉(zhuǎn)到我們想看的位置,也可以鼠標點擊一下我們想看的行然后使用【執(zhí)行到光標處】,這里我關(guān)注的行是594行,于是我在594行點擊一下,然后使用【執(zhí)行到光標處】直接跳過144行到594行中間的其他邏輯,效果如下圖

          這時使用【步進】進入typecheckslice函數(shù)

          ……

          如果你有跟著操作,就會發(fā)現(xiàn),此時點擊【步進】不是進入typecheckslice函數(shù)而是進入了Slice函數(shù),當然啦,我們調(diào)用函數(shù)前需要先搞清楚傳入的參數(shù)到底是啥,這沒問題。此時我們可以【步過】【步進】快速的走完Slice函數(shù),也可以直接使用【步出】離開Slice函數(shù),都是一樣的,最后都會回到上圖處,依然是594行,這時我們再點擊一次【步進】,就可以進入typecheckslice函數(shù)了

          連續(xù)點擊【步進】,我們就會進入到typecheckslice中的typecheck函數(shù),稍微看一下這個函數(shù)就會發(fā)現(xiàn)他實際的核心邏輯都在300行調(diào)用的typecheck1函數(shù)中,所以我們直接在300行點擊一下,然后使用【執(zhí)行到光標處】直接執(zhí)行到300行

          此時點擊【步進】進入typecheck1函數(shù)

          可以看到我們此時就來到了327行,typecheck1函數(shù),這是一個大幾百行的巨型函數(shù),我們先停一停


          大家注意,在變量框中出現(xiàn)了n、top、res三個變量,他們分別是typecheck1函數(shù)的兩個參數(shù)和一個返回值

          top就是一個值為1的int沒什么好說的,res現(xiàn)在必然是個nil我們也不管他,我們看這個n


          畢竟,既然函數(shù)名都叫typecheck了,這個函數(shù)必然是用來做類型檢查的,那top是一個int,所以這個檢查的對象肯定就是第一個參數(shù)n,n是Node的指針類型的,這個Node結(jié)構(gòu)體我們進去看一下就能知道,這是go的抽象語法樹結(jié)點的結(jié)構(gòu)體,所以這個typecheck函數(shù)就是用來對參數(shù)n做類型檢查的


          在變量框中我們右鍵n選擇檢查

          就會打開變量詳情彈框

          這個彈框中,我們可以很容易的對n這個對象里面各屬性的值進行確認


          我們其實可以通過這個n具體的值來判斷此次對這個函數(shù)的調(diào)用是不是我們想要的那一次,因為在邏輯執(zhí)行的過程中,同一個函數(shù)可能使用不同的參數(shù)調(diào)用許多次,而只有其中一次是我們需要的,我們檢查參數(shù)發(fā)現(xiàn)此次調(diào)用不是我們關(guān)注的之后,可以點擊【步退】直接離開此次調(diào)用


          下述詳細流程類似于開荒,實際調(diào)試代碼,如果你能夠通過參數(shù)中的某個屬性判斷


          在調(diào)試的過程中,任何時候我們都可以這么做


          好的我們繼續(xù)調(diào)試


          觀察typecheck1函數(shù),發(fā)現(xiàn)函數(shù)主要邏輯都在352行開始的switch中,所以我們將光標移到352行并點擊【執(zhí)行到光標處】,直接執(zhí)行到352行,然后我們點擊【步進】會去到549行,再次點擊【步進】會渠道1227行,這時再點擊【步進】就會開始執(zhí)行1228行,也就是1227行的case下的邏輯,所以看起來549行的case,雖然我們【步進】的時候會走到那里,但是似乎并沒有進入其中的邏輯里,這個我也不是很清楚


          1227行是ONCALL,并不是我想看的,說明這個n并不是我關(guān)注的目標,我這里可以使用【步出】直接跳過本函數(shù)剩余的邏輯。

          如果你是自己在調(diào)試,哪里要跳過哪里要一步步跟著看你要自己做判斷,最好是先把相關(guān)源碼看一下


          連續(xù)點擊兩次【步出】之后會回到typecheckslice方法進行下一次循環(huán)

          然而這里沒有下一次循環(huán),接著【步進】就會發(fā)現(xiàn)我們退出了尋穿最終回到了gc\main.go的循環(huán)中

          但是這個循環(huán)是有下一次的,我們【步過】和【步進】并用,可以再次進入typecheckslice方法,并用和之前一樣的流程再次來到typecheck1方法中的switch


          不過這次進的case是OCOPY,也不是我們想要的,【步退】出來,退到typecheckslice方法進行下一次循環(huán)


          通過【步進】我們可以在第二次循環(huán)中再次進入typecheck方法,并通過與上述相同的流程來到typecheck1方法中的switch

          這一次,我們會進入一個叫OAS的case

          我們使用【步進】進入typecheckas方法

          使用【執(zhí)行到光標處】直接執(zhí)行到3181行,使用【步進】進入assignconv函數(shù),再次點擊【步進】進入assignconvfn函數(shù)

          點擊838行并使用【執(zhí)行到光標處】直接執(zhí)行到838行,使用【步進】進入assignop函數(shù)

          這個函數(shù)就是最開始我們搜索到的,生成報錯的函數(shù)

          注意583行的注釋:dst是一種interface,src實現(xiàn)了dst

          這說明我們想看的,判斷結(jié)構(gòu)體是否實現(xiàn)了interface的邏輯就在這里

          哪個IsInterrface進去看一眼就能知道是用來確定src是不是interface的

          所以我們關(guān)心的邏輯就在587行的implements函數(shù)中

          點擊587行并使用【執(zhí)行到光標處】直接執(zhí)行到587行,使用【步進】進入implements函數(shù)

          這個時候其實可以看一眼變量,第一個參數(shù),t就是src,根據(jù)注釋,我們可以猜到,src應(yīng)該是一個結(jié)構(gòu)體;第二個參數(shù),iface就是dst,前面的注釋里也說的很清楚,dst是一個interface


          我們來檢查一下這兩個參數(shù)

          右鍵變量列表中的t,選擇 檢查 ,出現(xiàn)檢查彈框,查看Sym屬性,可以看到Name=Cat

          也就是說這個t參數(shù),其實就是我們在自己程序中定義的Cat結(jié)構(gòu)體


          我們再用同樣的方法看一下iface

          同樣是查看iface下的Sym屬性,可見Name=Duck

          由此可確認,iface就是我們自己程序中定義的Duck接口


          那就,邏輯接著往下走唄


          1660行的邏輯是t是interface時才會進入的,我們不管他,接著向下就來到了這里

          我們先看1692行

          iface是我們定義的Duck接口,F(xiàn)ields方法會返回調(diào)用者的字段/方法,如果調(diào)用者是結(jié)構(gòu)體則返回字段,如果調(diào)用者是interface則返回方法,顯然此時他會返回我們定義的Duck接口的方法,也就是Quack方法;Slice方法會將Fields方法的返回值處理成切片格式

          所以1692行,就是在遍歷這個切片【當然我們知道這個切片的長度只有1


          1693行不知道在檢查什么,無所謂。我們看1696行


          當i小于tms的長度……等下,tms是什么玩意?


          我們回頭看看,1686行到1689行,會看到tms = t.AllMethods().Slice(),這說明tms是t的全部函數(shù)的切片,我們之前說過了,t就是我們定義的Cat結(jié)構(gòu)體,他有一個Quacks方法;所以這里我們可以知道,tms就是一個函數(shù)切片,長度為1,里面的內(nèi)容是Quacks函數(shù)


          好的回到1696行,i在for開始之前定義,初始值為0,固此時i為0,你不信的話也可以直接在編輯器下方的變量列表里面找i,看是不是0【截圖中我編輯器里面邏輯是已經(jīng)走到1699行了,所以顯示i=1】


          此時i < len(tms),我們看第二個條件,tms[i].Sym != im.Sym

          前面已經(jīng)說過了,tms[0]就是Cat結(jié)構(gòu)體的Quacks方法,im就是Duck接口的Quack方法,這里顯然是在對他們進行比較,那他倆一樣嗎?當然不一樣啦Quacks函數(shù)名多了個s怎么會一樣呢,所以我們就會進入這個for,讓i++


          此時我們也可以通過下方的變量框?qū)ms和im進行檢查,看我們的判斷對不對

          tms的第0個,Sym.Name為Quacks

           

          im的Sym.Name為Quack

          印證了我們上面對這兩個變量的推斷


          我們同時也能夠意識到就是在這里,src\cmd\compile\internal\gc\subr.go:1696的這個tms[i].Sym != im.Sym邏輯中,進行了【某結(jié)構(gòu)體是否實現(xiàn)了某interface】的判斷,當然啦這是在一個循環(huán)里面,如果我們的interface和結(jié)構(gòu)體各有好多函數(shù)的話,他會循環(huán)遍歷一個個去判斷,但總之,判斷,是這一行的這個邏輯在做判斷


          i++以后i就是1了,不再小于len(tms),固這個for只會循環(huán)1次,然后就會來到1699行,此時i等于1,len(tms)也等于1,所以我們會進這個if,并最終,在1703行,return false


          于是,我們又回到了subr的587行

          繼續(xù)使用【步進】

          最后我們會來到610行,也就是最開始我們搜索到的報錯的位置

          就是在610行,構(gòu)建了【此結(jié)構(gòu)體未實現(xiàn)此interface】這樣的報錯


          之后就是一些返回的邏輯,你有興趣自己去看,我這里就不再往下講了


          經(jīng)此次探索,我們學(xué)習(xí)了如何使用goland進行g(shù)o源碼調(diào)試,并成功找到了,go是在哪里判斷結(jié)構(gòu)體是否實現(xiàn)了某個interface




          想要和曹大深入交流的,趕緊掃描下方二維碼進群交流吧~



          如果群人數(shù)已滿,可加小助理 Judy,拉你進群哦




          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  欧美香蕉在线观看 | 在线 天堂国产产 | 年轻人在线毛片免费看视频在线 | 一级 a一级 a 免费观看免免黄 | 天天插,天天狠,天天透 |