Even that ease and simplicity of using go
are one of its main advanatages,
there are difficulties in debugging applications written in go
.
The lack of mature tools (like supported vim
plugin) push most of us to use
logging techniques to inspect and track down issues.
In this article, I will demonstrates how you can use vim
and lldb
to debug
a go application. Before that you should make the application capable for debugging.
Prerequisites
You should compile the application by emitting the debug information and disable inlining.
The -N
flag enables debug information emitting and -l
disables compiler inlining:
go build -gcflags '-N -l' <file_or_package>
The compiled binary supports DWARF debugging data format, which is supported by debuggers as GDB and LLDB.
You should install vim-lldb
plugin from here.
The easiest way to install it by using package manager as bundle
. You should
add Bundle "gilligan/vim-lldb"
in your bundle list.
Then you can use the following commands and shortcuts:
Ltarget
specifies the binary that we are going to debugLbreakpoint
sets a breakpoint in file on particular line (<leader>lb
)Lrun
starts the debugger (<leader>lr
)Lstep
does a source level single step in the current thread. (<leader>ls
)Lfinish
steps out of the currently selected frame. (<leader>lo
)Lnext
does a source level single step over in the current thread. (<leader>ln
)Lcontinue
continues execution until next breakpoint. (<leader>lc
)Lprint
evaluates a generalized expression in the current frame. (<leader>lp
)Lframe variable
prints the frame local variables (<leader>lv
)
You can add my extra shortcuts in your .vimrc
file:
nnoremap <silent> <leader>lr :Lrun<CR>
nnoremap <silent> <leader>lb :Lbreakpoint<CR>
nnoremap <silent> <leader>lc :Lcontinue<CR>
nnoremap <silent> <leader>ln :Lnext<CR>
nnoremap <silent> <leader>ls :Lstep<CR>
nnoremap <silent> <leader>li :Lstepin<CR>
nnoremap <silent> <leader>lo :Lfinish<CR>
nnoremap <silent> <leader>lp :Lprint<CR>
nnoremap <silent> <leader>lv :Lframe variable<CR>
Lets have the following source code that we are aiming to debug:
// main.go
package main
import "fmt"
type User struct {
FirstName string
LastName string
}
func (user User) String() string {
return fmt.Sprintf("%s %s", user.FirstName, user.LastName)
}
func main() {
user := User{
FirstName: "John",
LastName: "Smith",
}
message := FormatMessage(user, "Golang Weekly Newsletter #756")
for index := 0; index < 3; index++ {
fmt.Printf("Sending #%d message with %s\n", index, message)
}
}
func FormatMessage(user User, message string) string {
return fmt.Sprintf("body: %s by %s", message, user.String())
}
- Compile the application:
$ go build -gcflags '-N -l' -o app main.go
- Open the source code:
$ vim main.go
- Set the
LLDB
target to be the compiled binary::Ltarget app
- Set a breakpoint on desired line by using
Lbreakpoint
command or<leader>lb
shortcut. - Then you can run the application in debug mode by using
Lrun
command or<leader>lr
shortcut.
You can watch the illustrates steps in the following video:
Conclusion
Even though LLDB
is very powerful and commonly used debugger, it does not work properly in the context of Go
.
It crashes sometimes. It made for C\C++
not for Go
. It cannot follow the execution
flow properly due to the fact that the debugger is not aware about defer
statement.
In addition sometimes go scheduler
changes the context of current executing go routine
.
It changes the stack frame by moving go routine
from one thread to another.