Ребята из компании Mailgun презентовали новый кроссплатформенный дебаггер для Go, который использует оригинальную технологию, в корне отличающуюся от стандартных подходов. Забегая наперед — с помощью Gopherjs этот дебаггер работает даже в браузере.

image

Интро


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
  • print

В планах поддержка остановки/инспекции горутин и другие вещи, которые коммьюнити сочтет нужными/полезными.

Ссылки:


Комментарии (8)


  1. WaveCut
    22.04.2015 12:52

    Я давно ждал чего нибудь подобного для Golang. Он все привлекательнее и привлекательнее!


    1. neolink
      22.04.2015 13:08
      +4

      ну это костыль, по сути кроме дебага для play.golang.org сложно придумать реальное ему применение


      1. divan0 Автор
        22.04.2015 13:11

        ну, я хоть и не большой фанат дебаггеров, но godebug уже пару раз удачно попользовал.


      1. taliban
        22.04.2015 16:12

        А в чем проблема его применения для себя в своих проектах? Всеравно на выходе когда нужно получается чистый бинарник.


        1. neolink
          22.04.2015 19:25

          Отлаживать нужно одну программу, если вы ее меняете то отлаживаете уже другую программу.
          Ну и ставить бряки кодом? простите но это как-то странно (хотя глядя на то что получается после генерации вроде легко сделать отдельный список)
          По сути чем это принципиально отличается от вставки в код вывода в этом месте?
          Ну и из кода обычно и так видно что он делает, всякие нетривиальные штуки нуждаются в симуляции, частенько (по сишной истории ещё) нужно запустить систему и подождать часов 10 пока начнет проявляться бага, и пойди разбери откуда у нее ноги выросли.

          ЗЫ Вообще забавно да, даже условные бряки будут работать — если в коде if написать.


          1. taliban
            22.04.2015 19:46
            +1

            developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/debugger — это рас
            А от вставки кода в этом месте это отличается хотя бы тем что у вас есть доступ ко всем инициализированным переменным, вы всегда можете узнать значение, а вот из кода не всегда можно это предположить, на то и нужны дебагеры, вы знаете где примерно у вас узкое место, ставите там брейкпоинт и дальше уже анализируете значения в этом месте или рядом.


            1. neolink
              22.04.2015 21:22

              только в Firefox это не единственный вариант точки останова, а тут чтобы поставить новую нужно программу пересобрать.


              1. taliban
                22.04.2015 21:43

                Ну да, нужно пересобрать, а еще при каждом изменении программы нужно пересобрать. Там есть методы для движения по коду, не обязательно прям везде тыкать точки останова, можно спокойно дойти до нужного места. Нельзя сказать что это прям костыль, это как минимум инструмент, и весьма достойный, учитывая отсутствие вменяемого окружения у го.