dlv

使用 dlv (Delve) 工具调试 Golang 程序。它是理解Go程序时 GDB 调试器的有效替代品。

Install

# go install github.com/go-delve/delve/cmd/dlv@latest
sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories
apk add go
export GOPROXY=https://goproxy.cn,direct
export CGO_ENABLED=1
export GO111MODULE=on
go install github.com/go-delve/delve/cmd/dlv@latest
ls -lah /root/go/bin/dlv
go version
dlv version

Use

# 开始
dlv debug ./cmd/main.go
dlv exec /opt/iam/bin/iam-apiserver
# 如果希望二进制文件被调试,在编译二进制文件时需要关闭内联优化:
# go build -gcflags=all="-N -l"
# 如果不希望二进制文件被调试,则可以使用以下编译选项:
# go build -ldflags "-s -w" # -s: 去掉符号信息;-w: 去掉 DWARF 调试信息。
dlv core <executable-file> <core-file> # 使用core文件启动调试,这种方式可以找出可执行文件core的原因

# 查看源码组
(dlv) list main.main # 定位 main包main 函数
(dlv) list main.init # 定位 main包init
(dlv) funcs fib # 搜索函数
(dlv) vars main # 查看 main包变量

# 添加断点
(dlv) break ./main.go:10 #b ./main.go:10
(dlv) b main.main
(dlv) b main.init
(dlv) list ./main.go:10 #l ./main.go:10
(dlv) breakpoints #bp # 查看设置了哪些断点

# 删除断点
(dlv) clear 1 # 删除标识ID为1的断点
(dlv) clearall # 清除所有断点

# 调试断点(一旦设置断点,能用list命令查源代码)
(dlv) continue #c # 运行到下一个断点处
(dlv) next #n # 运行到源代码下一行
(dlv) step #s # 进入到函数调用的内部
(dlv) stepout #so # 跳出函数调用内部

(dlv) restart #r # 在程序终止或准备重新开始调试时,重启程序,断点不丢失

# 退出调试
(dlv) exit # quit # q

我这里要查找的是使用 https://github.com/eddycjy/fake-useragent 后,一开始很卡的原因(main函数前),通过以下函数定位到了这个包

GODEBUG=inittrace=1

进而找到了

// https://github.com/eddycjy/fake-useragent/blob/master/b.go#L30
var defaultBrowser = NewBrowser(Client{
	MaxPage: setting.BROWSER_MAX_PAGE,
	Delay:   setting.HTTP_DELAY,
	Timeout: setting.HTTP_TIMEOUT,
}, Cache{})
func NewBrowser(client Client, cache Cache) *browser {
	maxPage := setting.GetMaxPage(client.MaxPage)
	delay := setting.GetDelay(client.Delay)
	timeout := setting.GetTimeout(client.Timeout)

	b := browser{
		Client: Client{
			MaxPage: maxPage,
			Delay:   delay,
			Timeout: timeout,
		},
		Cache: Cache{
			UpdateFile: cache.UpdateFile,
		},
	}
	return b.load()
}

func (b *browser) load() *browser {
	fileCache := cache.NewFileCache(cache.GetTempDir(), fmt.Sprintf(setting.TEMP_FILE_NAME, setting.VERSION))
	fileExist, err := fileCache.IsExist()
	if err != nil {
		log.Fatalf("fileCache.IsExist err: %v", err)
	}

	// handle cache.
	if b.UpdateFile == false {
		var (
			isCache      bool
			cacheContent []byte
			m            map[string][]string
		)

		if fileExist == true {
			cacheContent, err = fileCache.Read()
			if err != nil {
				log.Fatalf("fileCache.Read err: %v", err)
			}
			isCache = true
		} else {
			rawCache := cache.NewRawCache(setting.CACHE_URL, fmt.Sprintf(setting.TEMP_FILE_NAME, setting.CACHE_VERSION))
			rawResp, rawExist, err := rawCache.Get()
...

而后发现

// https://github.com/eddycjy/fake-useragent/blob/master/setting/setting.go#L13

const (
	VERSION = "0.2.0"

	BROWSER_URL            = "https://developers.whatismybrowser.com/useragents/explore/%s/%s/%d"
	BROWSER_MAX_PAGE       = 5
	BROWSER_ALLOW_MAX_PAGE = 8

	CACHE_VERSION = "0.2.0"
	CACHE_URL     = "https://raw.githubusercontent.com/EDDYCJY/fake-useragent/v0.2.0/static/"

	HTTP_TIMEOUT         = 5 * time.Second
	HTTP_DELAY           = 100 * time.Millisecond
	HTTP_ALLOW_MIN_DELAY = 100 * time.Millisecond

	TEMP_FILE_NAME      = "fake_useragent_%s.json"
	TEMP_FILE_TEST_NAME = "fake_useragent_test_%s.json"
)

推荐使用这个包:https://github.com/brianvoe/gofakeit