First Foray into Delve
24 Jul 2016
I started playing with the Go debuger Delve, and decided to capture some rough notes here.
Our sample file:
package main import "fmt" func main() { fmt.Println("Hello") a := 2 b := 3 c := a + b fmt.Printf("a + b is %d\n", c) for i := 0; i < 10; i++ { fmt.Printf("i == %d\n", i) } fmt.Println("We are near the end now.") fmt.Println("This is the end.") }
A run of our program:
$ go build $ ./hello Hello a + b is 5 i == 0 i == 1 i == 2 i == 3 i == 4 i == 5 i == 6 i == 7 i == 8 i == 9 We are near the end now. This is the end.
Let's install Delve.
$ go get github.com/derekparker/delve/cmd/dlv
That was easy.
If we launch on our compiled binary, everything is in assemlber! Very cool, but I was hoping to see golang:
$ dlv exec ./hello Type 'help' for list of commands. (dlv) list > _rt0_amd64_linux() /home/mwood/golang/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x456700) 3: // license that can be found in the LICENSE file. 4: 5: #include "textflag.h" 6: 7: TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 => 8: LEAQ 8(SP), SI // argv 9: MOVQ 0(SP), DI // argc 10: MOVQ $main(SB), AX 11: JMP AX 12: 13: // When building with -buildmode=c-shared, this symbol is called when the shared (dlv)
I wonder if I can get golang if I run dlv against the source?
$ rm hello $ dlv debug Type 'help' for list of commands. (dlv) list > _rt0_amd64_linux() /home/mwood/golang/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x456870) 3: // license that can be found in the LICENSE file. 4: 5: #include "textflag.h" 6: 7: TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8 => 8: LEAQ 8(SP), SI // argv 9: MOVQ 0(SP), DI // argc 10: MOVQ $main(SB), AX 11: JMP AX 12: 13: // When building with -buildmode=c-shared, this symbol is called when the shared
No, same thing! I wonder how to list go source?
Oh, this blog entry says that I would want to set a breakpoint at main.main and then continue until I get there:
$ dlv debug Type 'help' for list of commands. (dlv) break main.main Breakpoint 1 set at 0x40101b for main.main() ./main.go:5 (dlv) continue > main.main() ./main.go:5 (hits goroutine(1):1 total:1) (PC: 0x40101b) 1: package main 2: 3: import "fmt" 4: => 5: func main() { 6: fmt.Println("Hello") 7: a := 2 8: b := 3 9: c := a + b 10: fmt.Printf("a + b is %d\n", c)
Oh, and now we have the source of my program! Nice! So my earlier attempts must have started at the real beginning of the program, in the assembler routines before main.main is called. Makes sense.
Ah, and then I can type next
to go to the next source line.
When I type next
inside the loop, I correctly loop around.
I can also print the value of i
when I am inside the loop:
This is after doing next
a few times:
(dlv) next > main.main() ./main.go:12 (PC: 0x401276) 7: a := 2 8: b := 3 9: c := a + b 10: fmt.Printf("a + b is %d\n", c) 11: for i := 0; i < 10; i++ { => 12: fmt.Printf("i == %d\n", i) 13: } 14: fmt.Println("We are near the end now.") 15: fmt.Println("This is the end.") 16: } (dlv) print i 2
Now, when I decide I want to break out of the loop, I set a
breakpoint at line 14, outside the loop, and continue
until
I get to it:
(dlv) break 14 Breakpoint 2 set at 0x4013a4 for main.main() ./main.go:14 (dlv) continue i == 3 i == 4 i == 5 i == 6 i == 7 i == 8 i == 9 > main.main() ./main.go:14 (hits goroutine(1):1 total:1) (PC: 0x4013a4) 9: c := a + b 10: fmt.Printf("a + b is %d\n", c) 11: for i := 0; i < 10; i++ { 12: fmt.Printf("i == %d\n", i) 13: } => 14: fmt.Println("We are near the end now.") 15: fmt.Println("This is the end.") 16: } (dlv) print i 10
Nice.
And, as expected, if, at the start of main
, I feel like stepping
into fmt.Printf, instead of next
ing past it, I can do that too:
(dlv) next > main.main() ./main.go:6 (PC: 0x40102d) 1: package main 2: 3: import "fmt" 4: 5: func main() { => 6: fmt.Println("Hello") 7: a := 2 8: b := 3 9: c := a + b 10: fmt.Printf("a + b is %d\n", c) 11: for i := 0; i < 10; i++ { (dlv) step > runtime.convT2E() /home/mwood/golang/go/src/runtime/iface.go:128 (PC: 0x40bfd3) 123: tab := getitab(inter, t, false) 124: atomicstorep(unsafe.Pointer(cache), unsafe.Pointer(tab)) 125: return tab 126: } 127: => 128: func convT2E(t *_type, elem unsafe.Pointer, x unsafe.Pointer) (e eface) { 129: if raceenabled { 130: raceReadObjectPC(t, elem, getcallerpc(unsafe.Pointer(&t)), funcPC(convT2E)) 131: } 132: if msanenabled { 133: msanread(elem, t.size)
So, can we get inside a goroutine easily?
Here is a new program:
package main import "fmt" func main() { fmt.Println("Hello") done := make(chan struct{}) go func() { a := 4 b := 5 c := a + b fmt.Printf("in anonymous go func, a + b is %d\n", c) done <- struct{}{} }() <-done // wait for goroutine to finish a := 2 b := 3 c := a + b fmt.Printf("a + b is %d\n", c) for i := 0; i < 10; i++ { fmt.Printf("i == %d\n", i) } fmt.Println("We are near the end now.") fmt.Println("This is the end.") }
I ran dlv debug
and set a breakpoint at main, and then...
(dlv) next > main.main() ./main.go:6 (PC: 0x40102d) 1: package main 2: 3: import "fmt" 4: 5: func main() { => 6: fmt.Println("Hello") 7: done := make(chan struct{}) 8: go func() { 9: a := 4 10: b := 5 11: c := a + b (dlv) break 11 Breakpoint 2 set at 0x4016f1 for main.main.func1() ./main.go:11 (dlv) continue Hello > main.main.func1() ./main.go:11 (hits goroutine(5):1 total:1) (PC: 0x4016f1) 6: fmt.Println("Hello") 7: done := make(chan struct{}) 8: go func() { 9: a := 4 10: b := 5 => 11: c := a + b 12: fmt.Printf("in anonymous go func, a + b is %d\n", c) 13: done <- struct{}{} 14: }() 15: <-done // wait for goroutine to finish 16: a := 2
So it looks like there is no problem getting inside a go routine, which is nice.
If I change my program to have a named function, it might be easier to give that function name as a break point the first time it is called.
package main import "fmt" func main() { fmt.Println("Hello") done := make(chan struct{}) go DoStuff(done) <-done // wait for goroutine to finish a := 2 b := 3 c := a + b fmt.Printf("a + b is %d\n", c) for i := 0; i < 10; i++ { fmt.Printf("i == %d\n", i) } fmt.Println("We are near the end now.") fmt.Println("This is the end.") } func DoStuff(done chan<- struct{}) { a := 4 b := 5 c := a + b fmt.Printf("in anonymous go func, a + b is %d\n", c) done <- struct{}{} }
$ dlv debug Type 'help' for list of commands. (dlv) break main.DoStuff Breakpoint 1 set at 0x4016d8 for main.DoStuff() ./main.go:21 (dlv) continue Hello > main.DoStuff() ./main.go:21 (hits goroutine(5):1 total:1) (PC: 0x4016d8) 16: } 17: fmt.Println("We are near the end now.") 18: fmt.Println("This is the end.") 19: } 20: => 21: func DoStuff(done chan<- struct{}) { 22: a := 4 23: b := 5 24: c := a + b 25: fmt.Printf("in anonymous go func, a + b is %d\n", c) 26: done <- struct{}{}
That worked quite nicely!