Рассматриваем под лупой отладчик Delve для Go-разработчиков

от автора

Отладка не должна быть частью разработки, потому что она непродуктивна и отнимает много времени. В идеале код нужно сразу делать чистым, понятным и покрывать тестами. Но хотя современные подходы к разработке ПО не подразумевают дальнейшей отладки, мы каждый день продолжаем сталкиваться с унаследованным кодом, который может быть не покрыт тестами, быть сложным и запутанным. И в результате нам всё же приходится заниматься этим неблагодарным делом.

Сегодня есть множество IDE, поддерживающих работу с Go и позволяющих отлаживать приложения. На текущий момент для Go представлены два отладчика: GDB (но он не поддерживает многие фичи языка, например Go-рутины) и Delve. Многие IDE используют последний как дефолтный отладчик. И в этой статье я расскажу о возможностях Delve: о том, что умеет сам отладчик, а не что нам предоставляет IDE.

Основы работы с Delve

Для того чтобы начать работу с отладчиком, нужно скомпилировать программу на Go и выполнить в командной строке команду dlv debug, находясь в директории с исполняемым файлом. После этого мы попадём в Delve. Для начала работы требуется установить первую точку останова и выполнить команду continue.

Рассмотрим пример.

Возьмём простую программу на Go, которая читает данные из текстового файла и обновляет его, если объём данных не превышает 12 байт. А если объём равен 12 байтам, то программа просто выводит строку hello и завершает выполнение.

package main  import (    "fmt"    "io/ioutil"    "log"    "os" )  func main() {    file, err := os.Open("test.txt")    defer file.Close()    data, err := ioutil.ReadAll(file)    if err != nil {        fmt.Errorf(" problem: %v", err)    }     fmt.Println(data)    fmt.Println(len(data))     if len(data) == 12 {        fmt.Println("hello")        return    }     data = append(data, byte(len(data)))     err = ioutil.WriteFile("test.txt", data, 0644)    if err != nil {        log.Fatal(err)    } } 

Так выглядит моя директория перед компиляцией:

Теперь скомпилируем программу, выполнив команду go build main.go в командной строке. В результате должно получиться вот что:

Получив бинарный файл, заходим в директорию с ним и выполняем команду dlv debug:

Далее устанавливаем в файле точку останова на строке номер 14, выполнив команду break main.go:14:

И запускаем отладку с помощью команды continue:

Исполнение программы остановилось на 14-й строке. Теперь можно посмотреть значения переменных:

Чтобы продолжить отладку, нужно в командной строке либо выполнить команду next (и тогда выполнится следующая строка кода), либо набрать continue, (и программа выполнится до следующей точки останова).

Теперь вкратце расскажу про основные команды Delve, с помощью которых вы сможете отлаживать свои приложения:

  • next — следующая строка;

  • step — вход внутрь вызываемой функции:

  • continue — следующая точка останова (breakpoint):

  • break — установка точки останова, например break m67 main.go:67;

  • cond — задаёт условия, при которых произойдёт останова на текущей команде отладки. Например, при выполнении команды cond m67 len(array) == 8 сработает останова на этой строке, если в массиве будет восемь элементов;

  • breakpoints — отображает все заданные точки останова;

  • print — распечатывает значение выражения или переменной;

  • vars— выводит значения всех загруженных переменных приложения:

  • locals — выводит значения локальных переменных функции:

Это основные команды Delve, которых будет достаточно для начала работы с отладчиком. Разумеется, инструментарий решения гораздо серьёзнее, и подробнее обо всех командах вы можете узнать из официальной документации

Но главной фишкой Delve является возможность создавать пользовательские команды, которые позволяют гибче использовать отладчик и открывают широкие возможности для автоматизации. Давайте рассмотрим синтаксис и пример создания пользовательской команды.

Пишем свои команды на Starlark

Delve поддерживает синтаксис Starlark — это диалект Python, который позволяет писать полезные и функциональные плагины. Так как Starlark был придуман для написания небольших программ-конфигураций в отладчиках, а не программ, которые будут долго выполняться, он не содержит таких возможностей Python, как классы, исключения и рефлексия. 

На Starlark, например, можно написать команду для создания дампа текущего приложения и перезапуска его отладки уже с новыми дампом и данными. Такая функциональность может пригодиться, если какая-то ошибка воспроизводится только в очень «экзотических» случаях. 

Структура программ-конфигураций на языке Starlark:

def command_название команды     "Комментарий, который будет выведен, если набрать help имя команды"      Далее пишем код. 

Синтаксис языка можно посмотреть здесь

Давайте рассмотрим пример создания команды для Delve: 

def command_flaky(args): "Repeatedly runs program until a breakpoint is hit" while True: if dlv_command("continue") == None: break dlv_command("restart")

Эта команда будет перезапускать отладку до тех пор, пока не будет достигнута точка останова. Чтобы выполнить её в Delve:

  1. Сохраните команду в файл с расширением .star.

  2. Запустите Delve.

  3. Выполните в командной строке команду source flaky.star.

  4. Расставьте точки останова.

  5. Выполните команду flaky.

Для работы с flaky возьмём программу из предыдущего раздела. Пример того, что отобразится в консоли отладчика: 

Как видите, программа была перезапущена семь раз, и при каждом выполнении условия срабатывала точка останова. Отлавливать такие вещи вручную в Visual Studio Code и других средах разработки не так-то просто. 

Если вам интересно, что ещё можно сделать в Delve с помощью Starlark-синтаксиса, за подробностями добро пожаловать сюда. А если вы не любите использовать командную строку или не хотите разбираться в тонкостях «неродного» языка, то давайте рассмотрим, как сделать то же самое на Go. 

Написание плагинов на Go

Рассмотрим этот процесс на примере удалённой отладки приложений. В Delve реализован gRPC-сервер, к которому можно обращаться по API. Для этого сначала необходимо установить Delve рядом с приложением. Если вы используете микросервисную архитектуру, то можно добавить этот инструмент в образ вашего контейнера.

Возьмём код из первого раздела и попробуем отладить его с помощью Go. Для этого нам нужно выполнить в командной строке команду:

dlv exec —continue —headless —accept-multiclient —api-version 2 —listen 0.0.0.0:50080  main

Открываем любимую IDE и пишем на Go:

package main   import (    "encoding/json"    "fmt"    "os"      "github.com/go-delve/delve/service/api"    "github.com/go-delve/delve/service/rpc2" )   func main() {      serverAddr := "localhost:50080"    funcToTrace := "main.main"      // Create a new connection to the Delve debug server.    // rpc2.NewClient will log.Fatal if connection fails so there    // won't be an error to handle here.    client := rpc2.NewClient(serverAddr)      defer client.Disconnect(true)      // Stop the program we are debugging.    // The act of halting the program will return it's current state.    state, err := client.Halt()    if err != nil {        bail(err)    }      bp := &api.Breakpoint{        FunctionName: funcToTrace,        Tracepoint:   true,        Line:         12,    }      client.Restart(false)      tracepoint, err := client.CreateBreakpoint(bp)    if err != nil {        bail(err)    }    defer client.ClearBreakpoint(tracepoint.ID)      for _, i := range []int{1, 2} {        fmt.Printf("i:\t %d\n", i)        client.Restart(false)        // Continue the program.        stateChan := client.Continue()          // Create JSON encoder to write to stdout.        enc := json.NewEncoder(os.Stdout)        fmt.Println("____________________________________________")        fmt.Println("state")        for state = range stateChan {            // Write state to stdout.            enc.Encode(state)        }        fmt.Println("____________________________________________")    } }   func bail(s interface{}) {    fmt.Println(s)    os.Exit(1) }

Что происходит на стороне сервера, когда идёт отладка:

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

Изучим информацию, которую выдаёт Delve:

  • Pid — идентификатор приложения в Linux;

  • Running — запущено ли приложение;

  • Recording — идёт запись информации о процессе;

  • CoreDumping — идёт запись дампа приложения;

  • Threads — информация о потоках исполнения приложения; 

  • breakPoint — информация о сработавшей точке останова.

Подробно про выведенную информацию можно почитать здесь.

Отладка приложения с помощью написания другого приложения позволяет создавать анализаторы поведения программы и автоматизировать проверку своих приложений. Если вам захотелось написать что-то такое, то вам поможет gRPC-клиент.

Заключение

Я только поверхностно ознакомил вас с возможностями Delve. Показал, что мы можем отлаживать код и без IDE. Можно писать анализаторы поведения программ и приложения для отладки своего приложения. Наконец, функциональность Delve можно расширять собственными командами, что делает его очень мощным инструментом.

Дополнительная литература


ссылка на оригинал статьи https://habr.com/ru/company/ozontech/blog/701198/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *