1. 程式人生 > >線上除錯Go語言實戰技巧

線上除錯Go語言實戰技巧

Go語言命令列除錯-Dlv

簡介

  • 使用過python的朋友們都知道python中有自帶的pdb命令列除錯功能,同樣,go語言也有官方提供的gdb除錯,但是特別不好用,尤其是對go routinue除錯很雞肋,我們先看下官方的解釋:
GDB does not understand Go programs well. The stack management, threading, and runtime contain aspects that differ enough from the execution model GDB expects that they can confuse the debugger, even when the program is compiled with gccgo.

In short, the instructions below should be taken only as a guide to how to use GDB when it works, not as a guarantee of success.

In time, a more Go-centric debugging architecture may be required.

於是作者調查發現目前市面上用的更多的是dlv除錯,不僅支援gdb所有的功能,還對go routinue有很好的支援,大家知道除錯的時候,單執行緒是最好除錯的,dlv就有這樣的優點,下面我們來看下怎麼玩起來。

安裝

  • go語言安裝和環境配置,這裡
  • dlv安裝:
$ go get github.com/derekparker/delve/cmd/dlv

ps. 預設會安裝到$GOPATH/src/github.com/下面

  • 配置dlv:
$ vim ~/.bash_profile
alias dlv="~/go/bin/dlv"   (一般安裝完了會在$GOPATH/bin下生成二進位制可執行檔案)
$ source ~/.bash_profile
  • 執行命令$ dlv debug --help,出現一些命令說明成功

執行

  • 我們先給一段測試程式碼(dlv.go):
package main

import (
	"fmt"
	"sync"
	"time"
)

func doing(wg *sync.WaitGroup, i int) {
	fmt.Printf("start goroutine id %d\n", i)
	time.Sleep(2 * time.Second)
	fmt.Printf("finish goroutine id %d\n", i)
	wg.Done()
}

func main() {
	var wg sync.
WaitGroup workers := 10 wg.Add(workers) for i := 0; i < workers; i++ { go doing(&wg, i) } wg.Wait() }

基本功能

  • debug模式:
$ dlv debug dlv.go

powerdeMBP:interface power$ dlv debug dlv.go
Type 'help' for list of commands.
(dlv)
  • break(設定斷點):
(dlv) break main.main
Breakpoint 1 set at 0x10b27c3 for main.main() ./dlv.go:16
(dlv)
  • continue(快捷鍵:c), 走到斷點的位置:
(dlv) continue
> main.main() ./dlv.go:16 (hits goroutine(1):1 total:1) (PC: 0x10b27c3)
    11:		time.Sleep(2 * time.Second)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
=>  16:	func main() {
    17:		var wg sync.WaitGroup
    18:		workers := 10
    19:
    20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
(dlv)
  • next(快捷鍵:n), 往下走一步,讀者可以多試兩次:
(dlv) next
> main.main() ./dlv.go:17 (PC: 0x10b27d1)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
    16:	func main() {
=>  17:		var wg sync.WaitGroup
    18:		workers := 10
    19:
    20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
    22:			go doing(&wg, i)
(dlv) next
> main.main() ./dlv.go:18 (PC: 0x10b27fa)
    13:		wg.Done()
    14:	}
    15:
    16:	func main() {
    17:		var wg sync.WaitGroup
=>  18:		workers := 10
    19:
    20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
    22:			go doing(&wg, i)
    23:		}
  • print(快捷鍵:p),列印變數
(dlv) n
> main.main() ./dlv.go:20 (PC: 0x10b2803)
    15:
    16:	func main() {
    17:		var wg sync.WaitGroup
    18:		workers := 10
    19:
=>  20:		wg.Add(workers)
    21:		for i := 0; i < workers; i++ {
    22:			go doing(&wg, i)
    23:		}
    24:		wg.Wait()
    25:
(dlv) p workers
10
  • 設定斷點到函式doing, 我們來看看開啟十個go routinue後什麼情況。
(dlv) break doing
Breakpoint 2 set at 0x10b2598 for main.doing() ./dlv.go:9
(dlv) c
> main.doing() ./dlv.go:9 (hits goroutine(10):1 total:4) (PC: 0x10b2598)
> main.doing() ./dlv.go:9 (hits goroutine(6):1 total:4) (PC: 0x10b2598)
> main.doing() ./dlv.go:9 (hits goroutine(5):1 total:4) (PC: 0x10b2598)
> main.doing() ./dlv.go:9 (hits goroutine(14):1 total:4) (PC: 0x10b2598)
     4:		"fmt"
     5:		"sync"
     6:		"time"
     7:	)
     8:
=>   9:	func doing(wg *sync.WaitGroup, i int) {
    10:		fmt.Printf("start goroutine id %d\n", i)
    11:		time.Sleep(2 * time.Second)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
(dlv)
  • 列印任務id多少,看看我們進入了第幾個go routinue:
(dlv) n
> main.doing() ./dlv.go:10 (PC: 0x10b25af)
     5:		"sync"
     6:		"time"
     7:	)
     8:
     9:	func doing(wg *sync.WaitGroup, i int) {
=>  10:		fmt.Printf("start goroutine id %d\n", i)
    11:		time.Sleep(2 * time.Second)
    12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
(dlv) p i
9
(dlv)

ps. 我們發現可以單獨對其中一個go routinue進行除錯,也就是可以在多執行緒中對某個執行緒(在go語言中應該叫協程),更棒的還在後面,dlv還能對執行之中的程式進行除錯。

高階功能

  • 我們還是使用上面那一套程式(dlv.go),將time.sleep設定大一些,這樣有時間操作
package main

import (
	"fmt"
	"sync"
	"time"
)

func doing(wg *sync.WaitGroup, i int) {
	fmt.Printf("start goroutine id %d\n", i)
	// 這裡設定大一些
	time.Sleep(100 * time.Second)
	fmt.Printf("finish goroutine id %d\n", i)
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	workers := 10

	wg.Add(workers)
	for i := 0; i < workers; i++ {
		go doing(&wg, i)
	}
	wg.Wait()

}
  • 我們開啟兩個終端,一個用來執行,一個用來除錯執行中的程式

  • 第一個終端編譯並且執行:

$ go build dlv.go && ./dlv
  • 第二個終端檢視執行中的pid (下面可以看到pid是15426)
power:PKUChain power$ ps
  PID TTY           TIME CMD
  15426 ttys000    0:00.00 ./dlv
  • 在程式執行結束前,我們可以使用attach命令進入程式中進行除錯:
powerdeMBP:PKUChain power$ dlv attach 15426
Type 'help' for list of commands.
(dlv)
  • 設定斷點到doing函式的第三行,因為go routinue基本都在第二行time.Sleep(100 * time.Second)阻塞了,我們在這句話執行完之前在下面一行打好斷點等待。
(dlv) break doing:3
Breakpoint 1 set at 0x10931ff for main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12
(dlv) continue
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(12):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(6):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(5):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
> main.doing() /Users/power/Desktop/my/go_learn/interface/dlv.go:12 (hits goroutine(7):1 total:4) (PC: 0x10931ff)
Warning: debugging optimized function
     7:	)
     8:
     9:	func doing(wg *sync.WaitGroup, i int) {
    10:		fmt.Printf("start goroutine id %d\n", i)
    11:		time.Sleep(100 * time.Second)
=>  12:		fmt.Printf("finish goroutine id %d\n", i)
    13:		wg.Done()
    14:	}
    15:
    16:	func main() {
    17:		var wg sync.WaitGroup
(dlv)

ps. break (函式名): (函式第幾行)

其他

  • 以上都是日常用到的操作,還有一些更高階的操作請看官方文件
  • 命令列除錯還是非常重要的,有時候程式碼上線是沒有ide給你使用的,這個時候優勢就體現出來了,使用習慣了還是非常好用的。

參考資料