Go Debugging with Delve, or No More Fmt.Printfs

栏目: IT技术 · 发布时间: 4年前

内容简介:I admit that I’d only used a debugger for Go a couple of times; up until now all my debugging involved writing a new test, or multipleI hope by the time you’re done reading this post, you’ll be convinced to do the same!GDB is an awesome utility that every

I admit that I’d only used a debugger for Go a couple of times; up until now all my debugging involved writing a new test, or multiple fmt.Printf statements. This past weekend I decided to finally learn how to use Delve.

I hope by the time you’re done reading this post, you’ll be convinced to do the same!

Why not GDB?

GDB is an awesome utility that every programmer should add to their arsenal. The thing is, there’s a tool for every job, and Delve can understand the Go runtime, data structures and expressions better than GDB. Furthermore, GDB isn’t as easy to work with concurrent code; while Delve makes switching between different execution contexts a breeze.

So, want to get started?

Installation

Installation is as simple as

$ go get github.com/go-delve/delve/cmd/dlv
$ dlv version
Delve Debugger
Version: 1.4.0
Build: $Id: 67422e6f7148fa1efa0eac1423ab5594b223d93b $

and you’re now ready to get your hands dirty!

If you’re working on a MacOS you might need the Xcode toolchain; you can also enable developer mode using the following line, so that you don’t get pestered whenever dlv takes over the execution of another process.

sudo /usr/sbin/DevToolsSecurity -enable

Your first debugging session

Let’s use Delve to debug the simplest Go program one could write.

1     package main
  2       
  3     import "fmt"
  4       
  5     func main() {
  6         a := 1
  7         b := 2
  8         fmt.Println(a + b)
  9     }

Here’s a really simple debugging session. We set a breakpoint using break main.go:7 , use the continue and next commands to execute until that next breakpoint and move one line forward. We then take a peek under the hood, exploring variables and statements by using commands such as whatis , print and set .

$ dlv debug main.go
Type 'help' for list of commands.
(dlv) break main.go:7
Breakpoint 1 set at 0x10bfa5a for main.main() ./main.go:7
(dlv) continue
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x10bfa5a)
     2:
     3:	import "fmt"
     4:
     5:	func main() {
     6:		a := 1
=>   7:		b := 2
     8:		fmt.Println(a + b)
     9:	}
(dlv) next
> main.main() ./main.go:8 (PC: 0x10bfa63)
     3:	import "fmt"
     4:
     5:	func main() {
     6:		a := 1
     7:		b := 2
=>   8:		fmt.Println(a + b)
     9:	}
(dlv) whatis a
int
(dlv) print a
1
(dlv) whatis a+b
int
(dlv) set a=10
(dlv) print a
10
(dlv) continue
12
Process 57754 has exited with status 0
(dlv) quit

Let’s start at the beginning.

The dlv command can either start an interactive debugging session or a headless one where one or more clients can connect to.

You can launch a session using one of

  • dlv debug works like go run ; it will build and run a Go package
  • dlv exec will start a session with a precompiled binary
  • dlv attach will attach to a PID of a running Go binary

In order to properly debug a binary, it should be compiled with optimizations disabled, eg. with -gcflags="all=-N -l"

The --log will start dlv in verbose mode, offering much more information. There are some more advanced options such as dlv core , dlv trace or dlv dap , but these three should should cover most use-cases.

Here’s an example session launch

$ dlv debug --headless --accept-multiclient main.go &
[1] 1667
API server listening at: 127.0.0.1:51054
debugserver-@(#)PROGRAM:debugserver  PROJECT:debugserver-1001.0.13.3
 for x86_64.
Got a connection, launched process /Users/paschalistsilias/delve-blogpost/__debug_bin (pid = 1691).

$ dlv connect localhost:51054
Type 'help' for list of commands.
(dlv)
(dlv) q
Would you like to kill the headless instance? [Y/n]

Delve commands

So what are some of the commands that can be used in a debugging session?

This isn’t meant to be an exhaustive list, but a cheatsheet or a quick reference

print
whatis
locals
args
vars
funcs
types

With the exception of print and whatis , all other commands can be used with a regex appended, to quickly filter through larger result fields.

...
(dlv) funcs main
main.main
main.spawnGoroutines
main.spawnMoreGoroutines
runtime.main

(dlv) vars -v time
time.atoiError = error(*errors.errorString) *{
	s: "time: invalid number",}
time.daysBefore = [13]int32 [0,31,59,90,120,151,181,212,243,273,304,334,365]
time.errLeadingInt = error(*errors.errorString) *{
	s: "time: bad [0-9]*",}
time.unitMap = map[string]int64 [
	"ns": 1,
	"us": 1000,
	"µs": 1000,
	"μs": 1000,
	"ms": 1000000,
	"s": 1000000000,
	"m": 60000000000,
	"h": 3600000000000,
]
...

The list command will display the code around the current execution step or at a specific linespec . This can be used to easily fly around function definitions; for example

(dlv) funcs
(dlv) list main.spawnGoroutines
Showing /Users/paschalistsilias/delve-blogpost/main.go:11 (PC: 0x105f7bf)
   6:		for i := 0; i < 4; i++ {
   7:			go spawnGoroutines(i)
   8:		}
   9:	}
  10:
  11:	func spawnGoroutines(i int) {
  12:		time.Sleep(1 * time.Second)
  13:		if i%2 == 0 {
  14:			go spawnMoreGoroutines()
  15:		}
  16:	}

Finally, there are two more commands that can be used to manipulate the application state. The set command can be used to alter the value held in a numerical or pointer variable, and call is used to inject a function call and resume the running process.

Stop the world!

One of the main features of any debugger, is, well, to stop execution so you can debug.

The break command can be used to insert a breakpoint according to a linespec

  • At a specific line, such as break main.go:15
  • At a relative point in a file break +5 or break -12
  • Whenever a function is called or defined, as break main.myfunc

The breakpoints command will display all breakpoints along with their IDs. You’ll notice a default delve breakpoint, used to catch any application panics so that you don’t get tossed out of the debugging session. The clear and clearall commands can be used to clear a specific or all breakpoints; finally the on command can be used to run a specific delve command every time a breakpoint is hit.

The condition command can be used to set smarter stop conditions, and not halt execution in a specific line, but whenever a given condition is met.

(dlv) break main.go:15
Breakpoint 1 set at 0x10c0701 for main.spawnGoroutines() ./main.go:15
(dlv) condition 1 cc==3
(dlv) continue
0
1
2
3
> main.spawnGoroutines() ./main.go:15 (hits goroutine(1):1 total:1) (PC: 0x10c0701)
    10:			spawnGoroutines(i)
    11:		}
    12:	}
    13:
    14:	func spawnGoroutines(i int) {
=>  15:		cc := i
    16:		delay := 1 * time.Second
    17:		time.Sleep(delay)
    18:		if i%2 == 0 {
    19:			go spawnMoreGoroutines()
    20:		}
(dlv)

Finally, a delve feature that I especially liked is trace , a breakpoint that doesn’t halt execution, but prints a message whenever the execution passes through that point.

Move the world, one step at a time!

So, after setting your breakpoints, conditions and tracepoints, how do you move around?

continue
next N
step
stepout
restart

Which is pretty much what you’d expect from a debugger. Let’s move on to the more interesting stuff!

Goroutines and Threads

This is one of the flagship features of Delve, setting it apart from GDB. Delve allows interactive debugging of complex, concurrent code and its emergent properties.

The goroutine(s) and thread(s) commands can be used to see all available goroutines and threads, note where they launched from, display their stack, or the deferred function calls for each frame. The stack command will print a detailed stack trace, with all the steps that were taken for the application to reach the current state.

The debugger allows interactive switching between execution contexts, as you can choose to step forward in a specific goroutine or thread. Most of the commands mentioned above can be prefixed with a goroutine N statement, so that they get executed in that context only.

(dlv) help goroutines
List program goroutines.

	goroutines [-u (default: user location)|-r (runtime location)|-g (go statement location)|-s (start location)] [-t (stack trace)] [-l (labels)]

Print out info for every goroutine. The flag controls what information is shown along with each goroutine:

	-u	displays location of topmost stackframe in user code
	-r	displays location of topmost stackframe (including frames inside private runtime functions)
	-g	displays location of go instruction that created the goroutine
	-s	displays location of the start function
	-t	displays goroutine's stacktrace
	-l	displays goroutine's labels

(dlv) threads
* Thread 136829 at 0x105f762 ./main.go:7 main.main
  ...
  Thread 136877 at :0
  Thread 136876 at 0x1032500 /usr/local/go/src/runtime/proc.go:3439 runtime.gfget

(dlv) goroutines -s
* Goroutine 1 - Start: /usr/local/go/src/runtime/proc.go:113 runtime.main (0x102aa30) (thread 138380)
  ...
  ...
  Goroutine 17 - Start: ./main.go:11 main.spawnGoroutines (0x105f7b0)
  Goroutine 18 - Start: ./main.go:11 main.spawnGoroutines (0x105f7b0)
  Goroutine 33 - Start: /usr/local/go/src/runtime/time.go:247 runtime.timerproc (0x1044f50)
  Goroutine 34 - Start: ./main.go:18 main.spawnMoreGoroutines (0x105f820)
[10 goroutines]

(dlv) goroutine
Thread 139469 at /usr/local/go/src/runtime/malloc.go:878
Goroutine 1:
	Runtime: /usr/local/go/src/runtime/malloc.go:878 runtime.mallocgc (0x100b22f)
	User: ./main.go:7 main.main (0x105f784)
	Go: /usr/local/go/src/runtime/asm_amd64.s:220 runtime.rt0_go (0x1051626)
	Start: /usr/local/go/src/runtime/proc.go:113 runtime.main (0x102aa30)

(dlv) goroutine 17
Switched from 1 to 17 (thread 139504)

(dlv) goroutine
Thread 139504 at /usr/local/go/src/runtime/malloc.go:878
Goroutine 17:
	Runtime: /usr/local/go/src/runtime/malloc.go:878 runtime.mallocgc (0x100b22f)
	User: ./main.go:14 main.spawnGoroutines (0x105f7ff)
	Go: ./main.go:7 main.main (0x105f784)
	Start: ./main.go:11 main.spawnGoroutines (0x105f7b0)

(dlv) next

(dlv) goroutine 17 stack
0  0x000000000100b31c in runtime.mallocgc
   at /usr/local/go/src/runtime/malloc.go:931
1  0x0000000001051720 in runtime.systemstack_switch
   at /usr/local/go/src/runtime/asm_amd64.s:330
2  0x0000000001031b65 in runtime.newproc
   at /usr/local/go/src/runtime/proc.go:3255
3  0x000000000105f7ff in main.spawnGoroutines
   at ./main.go:14
4  0x0000000001053651 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1357

Sourcing commands

Many times, you want to repeatedly run a set of delve commands, so you end up in a specific debugging situation. You can do this by either sourcing a file line-by-line using the source command, or provide such a file when starting the session with the --init flag.

Going hardcore

In desperate times, you may want to dive into CPU-level debugging.

Useful commands include

step-instruction
disassemble
regs
threads
examinemem

Parting words

What do y’all think? For me, one advantage is that I’m now using fmt.Printf a lot less, and don’t have to clean up my code after debugging. Moreover, I gained a better understanding of how goroutines work by peeking under the hood of concurrent bits and pieces.

I enjoy reading comments, so don’t hesitate to reach out for any war stories or new perspectives!

Until next time!

Resources

Some more great resources


以上所述就是小编给大家介绍的《Go Debugging with Delve, or No More Fmt.Printfs》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据结构与算法分析

数据结构与算法分析

韦斯 (Mark Allen Weiss) / 机械工业出版社 / 2009-1-1 / 55.00元

本书是国外数据结构与算法分析方面的经典教材,使用卓越的Java编程语言作为实现工具讨论了数据结构(组织大量数据的方法)和算法分析(对算法运行时间的估计)。 随着计算机速度的不断增加和功能的日益强大,人们对有效编程和算法分析的要求也不断增长。本书把算法分析与最有效率的Java程序的开发有机地结合起来,深入分析每种算法,内容全面、缜密严格,并细致讲解精心构造程序的方法。一起来看看 《数据结构与算法分析》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试