Delve
Delve1 是使用Go語言實現的,專門用來調試Go程序的工具。它跟 GDB 工具類似,相比 GDB,它簡單易用,能夠更好的理解和處理Go語言的數據結構和語言特性,比如它支持打印 goroutine 以及 defer 函數等Go特有的語法特性。Delve 簡稱 dlv,後文將以 dlv 代稱 Delve.
安裝
# 安裝最新版本
go get -u github.com/go-delve/delve/cmd/dlv
# 查看版本
dlv version
使用
開始調試
dlv 使用 debug 命令進入調試界面:
dlv debug main.go
如果當前目錄是 main 包所在目錄時候,可以不用指定 main.go 文件這個參數的。假定項目結構如下:
.
├── github.com/me/foo
├── cmd
│ └── foo
│ └── main.go
├── pkg
│ └── baz
│ ├── bar.go
│ └── bar_test.go
如果當前已在 cmd/foo 目錄下,我們可以直接執行 dlv debug 命令開始調試。在任何目錄下我們可以使用 dlv debug github.com/me/foo/cmd/foo 開始調試。
如果已構建成二進制可執行文件,我們可以使用 dlv exec 命令開始調試:
dlv exec /youpath/go_binary_file
對於需要命令行參數才能啓動的程序,我們可以通過--來傳遞命令行參數,比如如下:
dlv debug github.com/me/foo/cmd/foo -- -arg1 value
dlv exec /mypath/binary -- --config=config.toml
對於已經運行的程序,可以使用 attach 命令,進行跟蹤調試指定 pid 的Go應用:
dlv attach pid
除了上面調試 main 包外,dlv 通過 test 子命令還支持調試 test 文件:
dlv test github.com/me/foo/pkg/baz
接下來我們可以使用 help 命令查看 dlv 支持的命令有哪些:
(dlv) help
The following commands are available:
Running the program:
call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
continue (alias: c) --------- Run until breakpoint or program termination.
next (alias: n) ------------- Step over to next source line.
rebuild --------------------- Rebuild the target executable and restarts it. It does not work if the executable was not built by delve.
restart (alias: r) ---------- Restart process.
step (alias: s) ------------- Single step through program.
step-instruction (alias: si) Single step a single cpu instruction.
stepout (alias: so) --------- Step out of the current function.
Manipulating breakpoints:
break (alias: b) ------- Sets a breakpoint.
breakpoints (alias: bp) Print out info for active breakpoints.
clear ------------------ Deletes breakpoint.
clearall --------------- Deletes multiple breakpoints.
condition (alias: cond) Set breakpoint condition.
on --------------------- Executes a command when a breakpoint is hit.
trace (alias: t) ------- Set tracepoint.
Viewing program variables and memory:
args ----------------- Print function arguments.
display -------------- Print value of an expression every time the program stops.
examinemem (alias: x) Examine memory:
locals --------------- Print local variables.
print (alias: p) ----- Evaluate an expression.
regs ----------------- Print contents of CPU registers.
set ------------------ Changes the value of a variable.
vars ----------------- Print package variables.
whatis --------------- Prints type of an expression.
Listing and switching between threads and goroutines:
goroutine (alias: gr) -- Shows or changes current goroutine
goroutines (alias: grs) List program goroutines.
thread (alias: tr) ----- Switch to the specified thread.
threads ---------------- Print out info for every traced thread.
Viewing the call stack and selecting frames:
deferred --------- Executes command in the context of a deferred call.
down ------------- Move the current frame down.
frame ------------ Set the current frame, or execute command on a different frame.
stack (alias: bt) Print stack trace.
up --------------- Move the current frame up.
Other commands:
config --------------------- Changes configuration parameters.
disassemble (alias: disass) Disassembler.
edit (alias: ed) ----------- Open where you are in $DELVE_EDITOR or $EDITOR
exit (alias: quit | q) ----- Exit the debugger.
funcs ---------------------- Print list of functions.
help (alias: h) ------------ Prints the help message.
libraries ------------------ List loaded dynamic libraries
list (alias: ls | l) ------- Show source code.
source --------------------- Executes a file containing a list of delve commands
sources -------------------- Print list of source files.
types ---------------------- Print list of types
Type help followed by a command for full documentation.
接下來我們將以下面代碼作爲示例演示如何dlv進行調試。
package main
import "fmt"
func main() {
fmt.Println("go")
}
設置斷點
當我們使用 dlv debug main.go 命令進行 dlv 調試之後,我們可以設置斷點。
(dlv) b main.main # 在main函數處設置斷點
Breakpoint 1 set at 0x4adf8f for main.main() ./main.go:5
繼續執行
設置斷點之後,我們可以通過 continue 命令,可以簡寫成 c ,繼續執行到我們設置的斷點處。
(dlv) c
> main.main() ./main.go:5 (hits goroutine(1):1 total:1) (PC: 0x4adf8f)
1: package main
2:
3: import "fmt"
4:
=> 5: func main() {
6: fmt.Println("go")
7: }
注意不同於 GDB 需要執行 run 命令啓動應用之後,才能執行 continue 命令。而 dlv 在進入調試界面之後,已經指向程序的入口地址處,可以直接執行 continue 命令
執行下一條指令
我們可以通過next命令,可以簡寫成n,來執行下一行源碼。同 GDB 一樣,next 命令是 Step over 操作,遇到函數時不會進入函數內部一行行代碼執行,而是直接執行函數,然後跳過到函數下面的一行代碼。
(dlv) n
go
> main.main() ./main.go:7 (PC: 0x4adfff)
2:
3: import "fmt"
4:
5: func main() {
6: fmt.Println("go")
=> 7: }
打印棧信息
通過 stack 命令,我們可以查看函數棧信息:
(dlv) stack
0 0x00000000004adfff in main.main
at ./main.go:7
1 0x0000000000436be8 in runtime.main
at /usr/lib/go/src/runtime/proc.go:203
2 0x0000000000464621 in runtime.goexit
at /usr/lib/go/src/runtime/asm_amd64.s:1373
打印gorountine信息
通過goroutines命令,可以簡寫成grs,我們可以查看所有 goroutine:
(dlv) goroutines
* Goroutine 1 - User: ./main.go:7 main.main (0x4adfff) (thread 14358)
Goroutine 2 - User: /usr/lib/go/src/runtime/proc.go:305 runtime.gopark (0x436f9b)
Goroutine 3 - User: /usr/lib/go/src/runtime/proc.go:305 runtime.gopark (0x436f9b)
Goroutine 4 - User: /usr/lib/go/src/runtime/proc.go:305 runtime.gopark (0x436f9b)
Goroutine 5 - User: /usr/lib/go/src/runtime/mfinal.go:161 runtime.runfinq (0x418f80)
[5 goroutines]
goroutine 命令,可以簡寫成 gr,用來顯示當前 goroutine 信息:
(dlv) goroutine
Thread 14358 at ./main.go:7
Goroutine 1:
Runtime: ./main.go:7 main.main (0x4adfff)
User: ./main.go:7 main.main (0x4adfff)
Go: /usr/lib/go/src/runtime/asm_amd64.s:220 runtime.rt0_go (0x462594)
Start: /usr/lib/go/src/runtime/proc.go:113 runtime.main (0x436a20)
查看彙編代碼
通過 disassemble 命令,可以簡寫成 disass ,我們可以查看彙編代碼:
(dlv) disass
TEXT main.main(SB) /tmp/dlv/main.go
main.go:5 0x4adf80 64488b0c25f8ffffff mov rcx, qword ptr fs:[0xfffffff8]
main.go:5 0x4adf89 483b6110 cmp rsp, qword ptr [rcx+0x10]
main.go:5 0x4adf8d 767a jbe 0x4ae009
main.go:5 0x4adf8f* 4883ec68 sub rsp, 0x68
main.go:5 0x4adf93 48896c2460 mov qword ptr [rsp+0x60], rbp
main.go:5 0x4adf98 488d6c2460 lea rbp, ptr [rsp+0x60]
main.go:6 0x4adf9d 0f57c0 xorps xmm0, xmm0
main.go:6 0x4adfa0 0f11442438 movups xmmword ptr [rsp+0x38], xmm0
main.go:6 0x4adfa5 488d442438 lea rax, ptr [rsp+0x38]
main.go:6 0x4adfaa 4889442430 mov qword ptr [rsp+0x30], rax
main.go:6 0x4adfaf 8400 test byte ptr [rax], al
main.go:6 0x4adfb1 488d0d28ed0000 lea rcx, ptr [rip+0xed28]
main.go:6 0x4adfb8 48894c2438 mov qword ptr [rsp+0x38], rcx
main.go:6 0x4adfbd 488d0dcce10300 lea rcx, ptr [rip+0x3e1cc]
main.go:6 0x4adfc4 48894c2440 mov qword ptr [rsp+0x40], rcx
main.go:6 0x4adfc9 8400 test byte ptr [rax], al
main.go:6 0x4adfcb eb00 jmp 0x4adfcd
main.go:6 0x4adfcd 4889442448 mov qword ptr [rsp+0x48], rax
main.go:6 0x4adfd2 48c744245001000000 mov qword ptr [rsp+0x50], 0x1
main.go:6 0x4adfdb 48c744245801000000 mov qword ptr [rsp+0x58], 0x1
main.go:6 0x4adfe4 48890424 mov qword ptr [rsp], rax
main.go:6 0x4adfe8 48c744240801000000 mov qword ptr [rsp+0x8], 0x1
main.go:6 0x4adff1 48c744241001000000 mov qword ptr [rsp+0x10], 0x1
main.go:6 0x4adffa e811a1ffff call $fmt.Println
=> main.go:7 0x4adfff 488b6c2460 mov rbp, qword ptr [rsp+0x60]
main.go:7 0x4ae004 4883c468 add rsp, 0x68
main.go:7 0x4ae008 c3 ret
main.go:5 0x4ae009 e8e247fbff call $runtime.morestack_noctxt
<autogenerated>:1 0x4ae00e e96dffffff jmp $main.main
dlv 默認顯示的是 intel 風格彙編代碼,我們可以通過 config 命令設置 gnu 或者 go 風格代碼:
(dlv) config disassemble-flavor go
這種方式更改的配置只會對此次調試有效,若保證下次調試一樣有效,我們需要將其配置到配置文件中。dlv 默認配置文件是 HOME/.config/dlv/config.yml。我們只需要在配置文件加入以下內容:
disassemble-flavor: go
-
https://github.com/go-delve/delve ↩