| # Debugging what you deploy in Go 1.12 | |
| 21 Mar 2019 | |
| Tags: debug, technical | |
| Summary: Go 1.12 improves support for debugging optimized binaries. | |
| OldURL: /debugging-what-you-deploy | |
| David Chase | |
| ## Introduction | |
| Go 1.11 and Go 1.12 make significant progress toward allowing developers | |
| to debug the same optimized binaries that they deploy to production. | |
| As the Go compiler has become increasingly aggressive in producing faster binaries, | |
| we've lost ground in debuggability. | |
| In Go 1.10, users needed to disable optimizations entirely in order to have | |
| a good debugging experience from interactive tools like Delve. | |
| But users shouldn’t have to trade performance for debuggability, | |
| especially when running production services. | |
| If your problem is occurring in production, | |
| you need to debug it in production, and that shouldn’t require deploying | |
| unoptimized binaries. | |
| For Go 1.11 and 1.12, we focused on improving the debugging experience on | |
| optimized binaries (the default setting of the Go compiler). | |
| Improvements include | |
| - More accurate value inspection, in particular for arguments at function entry; | |
| - More precisely identifying statement boundaries so that stepping is less | |
| jumpy and breakpoints more often land where the programmer expects; | |
| - And preliminary support for Delve to call Go functions (goroutines and | |
| garbage collection make this trickier than it is in C and C++). | |
| ## Debugging optimized code with Delve | |
| [Delve](https://github.com/go-delve/delve) is a debugger for Go on x86 | |
| supporting both Linux and macOS. | |
| Delve is aware of goroutines and other Go features and provides one of the | |
| best Go debugging experiences. | |
| Delve is also the debugging engine behind [GoLand](https://www.jetbrains.com/go/), | |
| [VS Code](https://code.visualstudio.com/), | |
| and [Vim](https://github.com/fatih/vim-go). | |
| Delve normally rebuilds the code it is debugging with `-gcflags "all=-N -l"`, | |
| which disables inlining and most optimizations. | |
| To debug optimized code with delve, first build the optimized binary, | |
| then use `dlv exec your_program` to debug it. | |
| Or, if you have a core file from a crash, | |
| you can examine it with `dlv core your_program your_core`. | |
| With 1.12 and the latest Delve releases, you should be able to examine many variables, | |
| even in optimized binaries. | |
| ## Improved value inspection | |
| When debugging optimized binaries produced by Go 1.10, | |
| variable values were usually completely unavailable. | |
| In contrast, starting with Go 1.11, variables can usually be examined even | |
| in optimized binaries, | |
| unless they’ve been optimized away completely. | |
| In Go 1.11 the compiler began emitting DWARF location lists so debuggers | |
| can track variables as they move in and out of registers and reconstruct | |
| complex objects that are split across different registers and stack slots. | |
| ## Improved stepping | |
| This shows an example of stepping through a simple function in a debugger in 1.10, | |
| with flaws (skipped and repeated lines) highlighted by red arrows. | |
| .image debug-opt/stepping.svg _ 450 | |
| Flaws like this make it easy to lose track of where you are when stepping | |
| through a program and interfere with hitting breakpoints. | |
| Go 1.11 and 1.12 record statement boundary information and do a better job | |
| of tracking source line numbers through optimizations and inlining. | |
| As a result, in Go 1.12, stepping through this code stops on every line | |
| and does so in the order you would expect. | |
| ## Function calls | |
| Function call support in Delve is still under development, but simple cases work. For example: | |
| (dlv) call fib(6) | |
| > main.main() ./hello.go:15 (PC: 0x49d648) | |
| Values returned: | |
| ~r1: 8 | |
| ## The path forward | |
| Go 1.12 is a step toward a better debugging experience for optimized binaries | |
| and we have plans to improve it even further. | |
| There are fundamental tradeoffs between debuggability and performance, | |
| so we’re focusing on the highest-priority debugging defects, | |
| and working to collect automated metrics to monitor our progress and catch regressions. | |
| We’re focusing on generating correct information for debuggers about variable locations, | |
| so if a variable can be printed, it is printed correctly. | |
| We’re also looking at making variable values available more of the time, | |
| particularly at key points like call sites, | |
| though in many cases improving this would require slowing down program execution. | |
| Finally, we’re working on improving stepping: | |
| we’re focusing on the order of stepping with panics, | |
| the order of stepping around loops, and generally trying to follow source | |
| order where possible. | |
| ## A note on macOS support | |
| Go 1.11 started compressing debug information to reduce binary sizes. | |
| This is natively supported by Delve, but neither LLDB nor GDB support compressed | |
| debug info on macOS. | |
| If you are using LLDB or GDB, there are two workarounds: | |
| build binaries with `-ldflags=-compressdwarf=false`, | |
| or use [splitdwarf](https://godoc.org/golang.org/x/tools/cmd/splitdwarf) | |
| (`go get golang.org/x/tools/cmd/splitdwarf`) to decompress the debug information | |
| in an existing binary. |