Ребята из компании 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
ссылка на оригинал статьи http://habrahabr.ru/post/256355/