Adding debugger notes
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…
Reference in New Issue