Adding debugger notes

pull/1/head
Drew Bednar 4 months ago
parent c4e7f0d5ea
commit 49410fd49e

@ -0,0 +1,3 @@
module datastructures
go 1.23.1

@ -0,0 +1,297 @@
# Using the Debugger
Go doesn't ship with a debugger but you can use the delve package instead.
```
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

@ -0,0 +1,3 @@
module debugger
go 1.23.1

@ -0,0 +1,51 @@
package main
import (
"fmt"
"sync"
)
func One() {
Two()
fmt.Println("One!")
}
func Two() {
Three()
fmt.Println("Two!")
}
func Three() {
fmt.Println("Three!")
}
// Used in the simple example
// func main() {
// msg := "Hello debugger"
// One()
// fmt.Println(msg)
// }
// Used for Goroutine debugging
func printMe(i int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("I'm a go routine %d\n", i)
}
func main() {
i := 0
var wg sync.WaitGroup
wg.Add(9)
for i < 10 {
go func(rI int) {
printMe(rI, &wg)
}(i)
i++
}
wg.Wait()
}
Loading…
Cancel
Save