Ребята из компании Mailgun презентовали новый кроссплатформенный дебаггер для Go, который использует оригинальную технологию, в корне отличающуюся от стандартных подходов. Забегая наперед — с помощью Gopherjs этот дебаггер работает даже в браузере.
Интро
All that stands in the way [of a good Go debugger] is the writing of a lot of non-portable low-level code talking to buggy undocumented interfaces.
— Rob Pike
Поддержка Go была в дебаггере gdb давно, но оставалось много мелких, но неприятных проблем, которые не давали возможности полноценно им пользоваться. Также есть проект delve, но о его популярности пока сложно сделать выводы. В любом случае, «классические» подходы к написанию дебаггера для Go неизменно сталкивались со сложностью реализации, особенно когда речь заходит о спецефичных для Go вещах вроде дебага горутин.
Абсолютно другой подход выбрал Jeremy Schlatter из компании Mailgun, которая использует Go достаточно активно — он взял за основу ту же идею, которая лежит в основе go coverage tool, встроенной системы тестов и некоторых других: изменение кода на лету перед компиляцией. Такой подход вообще стал возможен благодаря двум основным моментам — огромной скорости компиляции и простой грамматике языка. Встроенные в stdlib инструменты для разбора Go-грамматики (go/ast и go/parser) позволяют достаточно просто на лету модифицировать код компилируемой программы, а это открывает целый новый пласт возможностей.
Пример
К примеру, возьмем простую программу на Go:
package main
import "fmt"
func hello(text string) {
fmt.Println(text)
}
func main() {
fmt.Printf("Hello,")
hello("world")
}
И запустим godebug с командой output, чтобы посмотреть сгенерированный код:
package main
import (
"fmt"
"github.com/mailgun/godebug/lib"
)
var main_go_scope = godebug.EnteringNewScope(main_go_contents)
func hello(text string) {
ctx, ok := godebug.EnterFunc(func() {
hello(text)
})
if !ok {
return
}
defer godebug.ExitFunc(ctx)
scope := main_go_scope.EnteringNewChildScope()
scope.Declare("text", &text)
godebug.Line(ctx, scope, 6)
fmt.Println(text)
}
func main() {
ctx, ok := godebug.EnterFunc(main)
if !ok {
return
}
godebug.Line(ctx, main_go_scope, 10)
fmt.Printf("Hello,")
godebug.Line(ctx, main_go_scope, 11)
hello("world")
}
var main_go_contents = `package main
import "fmt"
func hello(text string) {
fmt.Println(text)
}
func main() {
fmt.Printf("Hello,")
hello("world")
}
`
Не вдаваясь пока в подробности каждой функции, видно, что на каждой строчке расставлены функции-идентификаторы строк, входа и выхода из функций, декларации переменных и так далее. Рантайм дебаггера уже работает с этими, добавленными функциями, имея полную информацию о программе.
Демо
Этот подход, конечно же, не универсален и имеет свои ограничения и недостатки, но его относительная простота и преимущества очевидны. Одной из демонстраций сильных сторон такого подхода является возможность делать отладку прямо в браузере, с помощью gopherjs: focused-sprite-91100.appspot.com/playground/blog-post — попробуйте сами.
Есть некоторые нюансы с тем, что нарушается нативная нумерация строк — это может повлиять на, скажем, логгеры/трейсеры, которые репортят номер строки. Но в целом для большинства случаев этот подход работает на ура.
Установка
Установка дебаггера не отличается от установки любой другой Go-программы:
go install github.com/mailgun/godebug
Использование
Пользоваться дебаггером очень просто:
$ godebug
godebug is a tool for debugging Go programs.
Usage:
godebug command [arguments]
The commands are:
run compile, run, and debug a Go program
test compile, run, and debug Go package tests
output generate debug source code, but do not build or run it
Use "godebug help [command]" for more information about a command.
godebug run *.go — компилирует с поддержкой дебага все исходники в текущей директории. Все дополнительные пакеты, используемые в коде, как свои, так и внешние — компилируются нативно, без дебага — это сделано специально, чтобы не замедлять код там, где это не нужно. Если нужно отлаживать и код в каких-то включенных пакетах, то они должны быть указаны с помощью флага -instrument:
godebug run -instrument github.com/go-sql-driver/mysql main.go
Брекпоинты в программе ставятся следующей конструкцией:
_ = "breakpoint"
При обычной сборке этот код ничего не делает, а godebug его распознает перед компиляцией и вставит инструкцию для остановки.
Статус
Проект уже готов для практического использования, хотя поддерживаются пока только самые базовые функции:
- list
- step
- next
- continue
В планах поддержка остановки/инспекции горутин и другие вещи, которые коммьюнити сочтет нужными/полезными.
Ссылки:
- Подробная статья — анонс дебаггера: blog.mailgun.com/introducing-a-new-cross-platform-debugger-for-go
- Слайды с доклада Jeremy на GoSF'15: go-talks.appspot.com/github.com/jeremyschlatter/godebug-talk/godebug.slide
- Github: github.com/mailgun/godebug
Автор: divan0