gdb 調試 Go 源碼
Go 的源碼是用 Go 寫的。可以很方便的閱讀。
一、源碼編譯安裝
- Go 的源碼編譯需要一個 >= 1.4 版本的編譯好的二進制文件來編譯,去 golang.org 下載編譯包和源碼包。
go1.14.6.src.tar.gz
go1.16.4.linux-amd64.tar.gz
當然也可以下載不同版本的源碼包和編譯包。
- 解壓編譯包和源碼包
tar -zxvf go1.16.4.linux-amd64.tar.gz
mv go go_bin
tar -zxvf go1.14.6.src.tar.gz
這樣得到了兩個目錄,一個是編譯好的 Go,一個源碼 Go。查看一下編譯好的 Go:
./go_bin/bin/go version
#go version go1.16.4 linux/amd64
- 設置編譯用的目錄和參數
export GOROOT_BOOTSTRAP=~/go_bin // 改成自己 go_bin 所在目錄
export GO_GCFLAGS="-N -l"
GOROOT_BOOTSTRAP
是放編譯好的 Go 二進制文件的目錄。 GO_GCFLAGS
是編譯用的參數,"-N":表示不需要優化,"-l":表示不需要內聯,禁止優化和內聯可以讓運行時(runtime)的函數更易於調試。
- 編譯 Go 源碼
#進入源碼
cd ./go/src
#執行編譯(調用 make.bash)
./all.bash
編譯完成,go/bin 下生成兩個可執行文件
go
gofmt
- 設置環境變量
vim ~/.bashrc
# go 環境
export GOROOT=/home/shijie/go
export GOPATH=/home/shijie/work
export PATH=$PATH:$GOROOT/bin
export PATH=$PATH:$GOPATH/bin
export GO111MODULE=on
export GOPROXY=https://goproxy.io
#應用環境
source ~/.bashrc
Go
go version
#go version go1.16.4 linux/amd64
編譯完成
二、gdb 調試源碼
ok,編譯完源碼,寫個小 demo。
package main
import "fmt"
func main(){
strSlice := []string{"bye world","say hi","i hit it"}
newStrSlice := append(strSlice,"fuck u")
fmt.Println(newStrSlice)
}
ok,編譯一下 demo,記得不要優化內聯
go build -gcflags "-N -l"
編譯完,目錄下回生成一個 main 的可執行文件。用 gdb 執行它,並開始調試
gdb ./main
# 進入 gdb 調試模式,加斷點
# 在main.go 第 6 行斷點
(gdb)b main.go:6
# 運行
(gdb)r
# Thread 1 "main" hit Breakpoint 1, main.main () at /home/shijie/work/test/main.go:6
# 24 newStrSlice := append(strSlice,"fuck u")
# 進入 append 源碼
(gdb)s
# runtime.growslice (et=0x4a31a0, old=..., cap=7, ~r3=...) at /home/shijie/file/software/go/src/runtime/slice.go:125
# 125 func growslice(et *_type, old slice, cap int) slice {
# 查看代碼
(gdb)l
#120 // NOT to the new requested capacity.
#121 // This is for codegen convenience. The old slice's length is used immediately
#122 // to calculate where to write new values during an append.
#123 // TODO: When the old backend is gone, reconsider this decision.
#124 // The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
#125 func growslice(et *_type, old slice, cap int) slice {
#126 if raceenabled {
#127 callerpc := getcallerpc()
#128 racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
#129}
# 這裏可以看到進入了源碼,在 src/runtime/slice.go 裏,append 的動態擴展。
# 查看反彙編
(gdb)disas
Dump of assembler code for function runtime.growslice:
=> 0x0000000000448500 <+0>: mov %fs:0xfffffffffffffff8,%rcx
0x0000000000448509 <+9>: cmp 0x10(%rcx),%rsp
0x000000000044850d <+13>: jbe 0x448c4f <runtime.growslice+1871>
0x0000000000448513 <+19>: sub $0x60,%rsp
0x0000000000448517 <+23>: mov %rbp,0x58(%rsp)
0x000000000044851c <+28>: lea 0x58(%rsp),%rbp
0x0000000000448521 <+33>: mov 0x88(%rsp),%rdx
0x0000000000448529 <+41>: mov 0x80(%rsp),%rbx
0x0000000000448531 <+49>: cmp %rbx,%rdx
0x0000000000448534 <+52>: jl 0x448c32 <runtime.growslice+1842>
0x000000000044853a <+58>: mov 0x68(%rsp),%rsi
0x000000000044853f <+63>: mov (%rsi),%rdi
0x0000000000448542 <+66>: test %rdi,%rdi
0x0000000000448545 <+69>: je 0x448b1b <runtime.growslice+1563>
0x000000000044854b <+75>: lea (%rbx,%rbx,1),%r8
0x000000000044854f <+79>: cmp %r8,%rdx
0x0000000000448552 <+82>: jg 0x448b13 <runtime.growslice+1555>
0x0000000000448558 <+88>: nopl 0x0(%rax,%rax,1)
0x0000000000448560 <+96>: cmp $0x400,%rbx
0x0000000000448567 <+103>: jge 0x448578 <runtime.growslice+120>
還可以 p var 來查看變量的值,gdb 的用法可以網上搜索。在 append 加斷點,一直 s 可以看到從 main 裏調用 append,整個全過程,從 go 源碼到彙編所有的函數調用。
當然你可以修改源碼文件,只要不報錯,重新編譯就可以了。比如:
vim src/runtime/slice.go
// 找到 function growslice,添加 print("test")
然後,進入 go/src 重新編譯
./all.bash
在執行 go run main.go,除了輸出 clice,還會輸出 test。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/FLnKHsgMKNISknRnjBXfhA