<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>

          曹大帶我學(xué) Go(3)—— 如何用匯編打同事的臉

          共 3058字,需瀏覽 7分鐘

           ·

          2021-06-01 17:31

          你好,我是小X。

          曹大最近開 Go 課程了,小X 正在和曹大學(xué) Go。

          這個系列會講一些從課程中學(xué)到的讓人醍醐灌頂?shù)臇|西,撥云見日,帶你重新認(rèn)識 Go。

          今天介紹幾個常用的查看 Go 匯編代碼、調(diào)試 Go 程序的命令和工具,既可以在平時和同事、網(wǎng)友抬杠時使用,還能在關(guān)鍵時刻打他們的臉。

          比如,有同事說這段代碼:

          package main

          type Student struct {
           Class int
          }

          func main() {
           var a = &Student{1}
           println(a)
          }

          的執(zhí)行效率要高于下面這段代碼:

          package main

          type Student struct {
           Class int
          }

          func main() {
           var a = Student{1}
           var b = &a
           println(b)
          }

          并且給你講了一通道理,你好像沒法辯贏他。怎么辦?

          直接用一行命令生成匯編代碼,馬上可以戳穿他,打他的臉。

          go tool 生成匯編

          其實(shí)很簡單,有兩個命令可以做到:

          go tool compile -S main.go

          和:

          go build main.go && go tool objdump ./main

          前者是編譯,即將源代碼編譯成 .o 目標(biāo)文件,并輸出匯編代碼。

          后者是反匯編,即從可執(zhí)行文件反編譯成匯編,所以要先用 go build 命令編譯出可執(zhí)行文件。

          二者不盡相同,但都能看到前面兩個示例代碼對應(yīng)的匯編代碼是一致的。同事的“謠言”不攻自破,臉都被你打疼了。

          找到 runtime 源碼

          Go 是一門有 runtime 的語言,什么是 runtime?其實(shí)就是一段輔助程序,用戶沒有寫的代碼,runtime 替我們寫了,比如 Go 調(diào)度器的代碼。

          我們只需要知道用 go 關(guān)鍵字創(chuàng)建 goroutine,就可以瘋狂堆業(yè)務(wù)了。至于 goroutine 是怎么被調(diào)度的,根本不需要關(guān)心,這些是 runtime 調(diào)度器的工作。

          那我們自己寫的代碼如何和 runtime 里的代碼對應(yīng)起來呢?

          前面介紹的方法就可以做到,只需要加一個 grep 就可以。

          例如,我想知道 go 關(guān)鍵字對應(yīng) runtime 里的哪個函數(shù),于是寫了一段測試代碼:

          package main

          func main() {
           go func() {
            println(1+2)
           }()
          }

          因?yàn)?go func(){}() 那一行代碼在第 4 行,所以,grep 的時候加一個條件:

          go tool compile -S main.go | grep "main.go:4"

          // 或

          go build main.go && go tool objdump ./main | grep "main.go:4"
          go func

          馬上就能看到 go func(){}() 對應(yīng) newproc() 函數(shù),這時再深入研究下 newproc() 函數(shù)就大概知道 goroutine 是如何被創(chuàng)建的。

          用 dlv 調(diào)試

          那有同學(xué)問了,有沒有其他可以調(diào)試 Go、以及和 Go 程序互動的方法呢?其實(shí)是有的!這就是我們要介紹的 dlv 調(diào)試工具,目前它對調(diào)試 Go 程序的支持是最好的。

          之前沒我怎么研究它,只會一些非常簡單的命令,這次學(xué)會了幾個進(jìn)階的指令,威力挺大,也進(jìn)一步加深了對 Go 的理解。

          下面我們帶著一個任務(wù)來講解 dlv 如何使用。

          我們知道,向一個 nil 的 slice append 元素,不會有任何問題。但是向一個 nil 的 map 插入新元素,馬上就會報(bào) panic。這是為什么呢?又是在哪 panic 呢?

          首先寫出讓 map 產(chǎn)生 panic 的示例程序:

          package main

          func main() {
           var m map[int]int
           m[1] = 1
          }

          接著用 go build 命令編譯生成可執(zhí)行文件:

          go build a.go

          然后,使用 dlv 進(jìn)入調(diào)試狀態(tài):

          dlv exec ./a

          使用 b 這個命令打斷點(diǎn),有三種方法:

          1. b + 地址
          2. b + 代碼行數(shù)
          3. b + 函數(shù)名

          我們要在對 map 賦值的地方加個斷點(diǎn)。先找到代碼位置:

          cat -n a.go

          看到:

          hello.go

          賦值的地方在第 5 行,加斷點(diǎn):

          (dlv) b a.go:5
          Breakpoint 1 set at 0x45e55d for main.main() ./a.go:5

          執(zhí)行 c 命令,直接運(yùn)行到斷點(diǎn)處:

          運(yùn)行到斷點(diǎn)處

          執(zhí)行 disass 命令,可以看到匯編指令:

          disass

          這時使用 si 命令,執(zhí)行單條指令,多次執(zhí)行 si,就會執(zhí)行到 map 賦值函數(shù) mapassign_fast64

          mapassign_fast64

          這時再用單步命令 s,就會進(jìn)入判斷 h 的值為 nil 的分支,然后執(zhí)行 panic 函數(shù):

          panic

          至此,向 nil 的 map 賦值時,產(chǎn)生 panic 的代碼就被我們找到了。接著,按圖索驥找到對應(yīng) runtime 源碼的位置,就可以進(jìn)一步探索了。

          除此之外,我們還可以使用 bt 命令看到調(diào)用棧:

          調(diào)用棧

          使用 frame 1 命令可以跳轉(zhuǎn)到相應(yīng)位置。這里 1 對應(yīng)圖中的 a.go:5,也就是我們前面打斷點(diǎn)的地方,是不是非??犰?。

          上面這張圖里我們也能清楚地看到,用戶 goroutine 其實(shí)是被 goexit 函數(shù)一路調(diào)用過來的。當(dāng)用戶 goroutine 執(zhí)行完畢后,就會回到 goexit 函數(shù)做一些收尾工作。當(dāng)然,這是題外話了。

          另外,用 dlv 也能干第二部分“找到 runtime 源碼”活。

          總結(jié)

          今天系統(tǒng)地講了幾招通過命令和工具查看用戶代碼對應(yīng)的 runtime 源碼或者匯編代碼的方法,非常實(shí)用。最后再匯總一下:

          1. go tool compile

          2. go tool objdump

          3. dlv

          使用這些命令和工具,可以讓你在看 Go 源碼的過程中事半功倍。

          好了,這就是今天全部的內(nèi)容了~ 我是小X,我們下期再見~


          歡迎關(guān)注曹大的 TechPaper 以及碼農(nóng)桃花源~

          瀏覽 66
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  6080伦理 | 91天天爱天天射天天干天天 | 国产精品久久久久久久久久久久久久久久久久 | 日韩理论视频 | 国产精品资源在线观看 |