You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Drew Bednar a68ba74b5a Add debugger nodes and select chapter 4 months ago
..
README.md Add debugger nodes and select chapter 4 months ago
go.mod Adding debugger notes 4 months ago
main.go Adding debugger notes 4 months ago

README.md

Using the Debugger

Go doesn't ship with a debugger but you can use the delve package instead.

https://www.youtube.com/watch?v=UA0SirX6Siw

go get github.com/go-delve/delve/cmd/dlv
dlv version
Delve Debugger
Version: 1.23.1
Build: $Id: 2eba762d75437d380e48fc42213853f13aa2904d $

Using dlv

From within a go module run:

dlv debug

Type 'help' for list of commands.
(dlv)

Now set a breakpoint on the main function

break main.main

This breaks not at your main function but in the go runtime.

Now you can do something like set a break point at a line number in a file

break main.go:20

Now we can continue until we hit a breakpoint

c or continue


> [Breakpoint 1] main.main() ./main.go:19 (hits goroutine(1):1 total:1) (PC: 0x4ae66e)
    14:
    15: func Three() {
    16:         fmt.Println("Three!")
    17: }
    18:
=>  19: func main() {
    20:         msg := "Hello debugger"
    21:         One()
    22:         fmt.Println(msg)
    23:
    24: }

Now we can check for locals

locals

(no locals)

No surprising there are no local variables at this point.

n 

Will advance us to the next line. But again no locals because that hasn't been executed yet. So n again.

locals

msg = "Hello debugger"
ls

> main.main() ./main.go:21 (PC: 0x4ae687)
    16:         fmt.Println("Three!")
    17: }
    18:
    19: func main() {
    20:         msg := "Hello debugger"
=>  21:         One()
    22:         fmt.Println(msg)
    23:
    24: }

Now we can step into our function with s

s

> main.One() ./main.go:5 (PC: 0x4ae48a)
     1: package main
     2:
     3: import "fmt"
     4:
=>   5: func One() {
     6:         Two()
     7:         fmt.Println("One!")
     8: }
     9:
    10: func Two() {

You can se now we have stepped into the function.

We can n and s into Two().

> main.Two() ./main.go:10 (PC: 0x4ae52a)
     5: func One() {
     6:         Two()
     7:         fmt.Println("One!")
     8: }
     9:
=>  10: func Two() {
    11:         Three()
    12:         fmt.Println("Two!")
    13: }
    14:
    15: func Three() {

How about we look at the stack trace at this point

stack
0  0x00000000004ae52a in main.Two
   at ./main.go:10
1  0x00000000004ae493 in main.One
   at ./main.go:6
2  0x00000000004ae68c in main.main
   at ./main.go:21
3  0x000000000043b647 in runtime.main
   at /usr/local/go/src/runtime/proc.go:272
4  0x0000000000474081 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1700

Let's n and s one more time and check the stack trace.

0  0x00000000004ae5ca in main.Three
   at ./main.go:15
1  0x00000000004ae533 in main.Two
   at ./main.go:11
2  0x00000000004ae493 in main.One
   at ./main.go:6
3  0x00000000004ae68c in main.main
   at ./main.go:21
4  0x000000000043b647 in runtime.main
   at /usr/local/go/src/runtime/proc.go:272
5  0x0000000000474081 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1700

If we needed to we could us frame to go back into the stack and and we can inspect the state of code at that point.

frame 3

locals

But then we need to move back to frame 0 and can continue debugging.

(dlv) ls
> main.Two() ./main.go:11 (PC: 0x4ae52e)
     6:         Two()
     7:         fmt.Println("One!")
     8: }
     9:
    10: func Two() {
=>  11:         Three()
    12:         fmt.Println("Two!")
    13: }
    14:
    15: func Three() {
    16:         fmt.Println("Three!")

Instead of stepping to Three() let's step out of Two.

(dlv) so
Three!
Two!
> main.One() ./main.go:7 (PC: 0x4ae493)
Values returned:

     2:
     3: import "fmt"
     4:
     5: func One() {
     6:         Two()
=>   7:         fmt.Println("One!")
     8: }
     9:
    10: func Two() {
    11:         Three()
    12:         fmt.Println("Two!")

We see out stdout messages now there is one to go.

(dlv) stack
0  0x00000000004ae493 in main.One
   at ./main.go:7
1  0x00000000004ae68c in main.main
   at ./main.go:21
2  0x000000000043b647 in runtime.main
   at /usr/local/go/src/runtime/proc.go:272
3  0x0000000000474081 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1700

We can continue this until our process returns. But when we are back in our main function. Let's look at locals one more time.

    17: }
    18:
    19: func main() {
    20:         msg := "Hello debugger"
    21:         One()
=>  22:         fmt.Println(msg)
    23:
    24: }

locals

msg = "Hello debugger"

We can't change this variable because Delve wants to avoid operations itself that involves memory allocations, but if we had a

func setMsg(value string) {
    msg = value
}

We could use call to set that variable

call setMsg("dirp")

To close it could just continue until the process exits.

(dlv) c

Process 29036 has exited with status 0

Wait there is more

Help has tons of stuff

help

Like

restart

Restarts the program of course. Our current breakpoints are still set.

(dlv) print msg
"Hello debugger"

And hey this time we used print to check a value.

Technically you can edit code with edit.

Debugging goroutines