1 of 23

Goのデバッグ用ロガーの開発を通して得た

デバッグとgoパッケージに関する知見

Go Conference 2023

2023/06/02

Takashi Mima(@task4233)

2 of 23

Takashi Mima(@task4233)

所属�株式会社メルカリ(Identity Platform)

興味分野�・バックエンド� ・Go(Contribution/Go Conference)�・セキュリティ� ・CTF(SECCON Beginners)� ・セキュリティ・ミニキャンプ in 三重 2023 講師

ほか�・Twitter: @task4233�・Portfolio: task4233.dev

2

3 of 23

Goでデバッグする際に

何を使いますか?👀

3

4 of 23

Delve?GDB?ロギング?

他の何か?🧐

4

5 of 23

本発表で共有する2つのこと

本発表で共有しないこと

1. Goにおける3種類のデバッグ方法

2. 自作したロガーの開発に用いられているgoパッケージ

・デバッガの詳しい使い方�・GODEBUG等の環境変数を用いたデバッグ

5

6 of 23

3種類のデバッグ方法

6

Delve

GDB

ロギング

👍

大半のGoプログラム� のデバッグに利用可能

・Goに特化しており、� GDBよりも正確な結果� が得られる

Cgoやランタイムの� デバッグに利用可能�・機能拡張が豊富�・Pythonスクリプトで� 処理を自動化可能

手軽(学習コスト低)

複数の変数の状態を� まとめて確認可能

・手動のデバッガ操作� よりも高速

🤔

・単一の状態でしか情報� を取得できない

・単一の状態でしか情報� を取得できない�・ビルド時のフラグを� 設定する必要がある

・ログの埋め込み/� 消し忘れが起き得る�・実行速度の低下

7 of 23

Delve: Goに特化したサードパーティー製のデバッガ

目標�・Go用のシンプルでフル機能を備えたデバッグツールの提供

Delveが適する場面�・ビルドされたGoプログラムをデバッグする場合の大半�・Goのランタイムやデータ構造、式を理解しているため

実装�github.com/go-delve/delve

7

8 of 23

Goクイズ: 次のプログラム実行時に表示されるのは?🤔

package main

import "fmt"

type T struct{ N int }

func main() {

ts := []T{{N: 1}, {N: 3}, {N: 5}}

minT := &T{N: 10000}

for _, t := range ts {

if t.N < minT.N {

minT = &t

}

}

fmt.Println(minT.N)

}

8

1️⃣ 1

2️⃣ 5

3️⃣ 10000

4️⃣ 未定義

Playground:

go.dev/play/p/rkeWSeUx4pm

9 of 23

Goクイズ: 次のプログラム実行時に表示されるのは?🤔

package main

import "fmt"

type T struct{ N int }

func main() {

ts := []T{{N: 1}, {N: 3}, {N: 5}}

minT := &T{N: 10000}

for _, t := range ts {

if t.N < minT.N {

minT = &t

}

}

fmt.Println(minT.N)

}

9

1️⃣ 1

2️⃣ 5

3️⃣ 10000

4️⃣ 未定義

Playground:

go.dev/play/p/rkeWSeUx4pm

10 of 23

GDB: Goに限らず普遍的に使えるデバッガ

用途�・プログラム実行中の内部情報の把握�・クラッシュした際のメモリやスタックの状態の把握

GDBが適する場面�Cgoコードやランタイム自体のデバッグをする場合�

参考リンク�https://sourceware.org/git/binutils-gdb.git �・https://go.dev/doc/gdb

10

11 of 23

GDB: 低レイヤのデバッグ

拡張スクリプト�・Go用拡張: go/src/runtime/runtime-gdb.py�・peda: https://github.com/longld/peda �・gef: https://github.com/hugsy/gef

11

12 of 23

ロギング: プログラムの変数等の情報出力

ロギングが適する場面�・複数の変数情報を手軽にまとめて出力したい場合

利用できるもの�・builtin関数のpanic, print, println�・fmt, log, x/exp/slog�・glog, logrus, zap�・logr.Logger interface

12

13 of 23

3種類のデバッグ方法

13

Delve

GDB

ロギング

👍

大半のGoプログラム� のデバッグに利用可能

・Goに特化しており、� GDBよりも正確な結果� が得られる

Cgoやランタイムの� デバッグに利用可能�・機能拡張が豊富�・Pythonスクリプトで� 処理を自動化可能

手軽(学習コスト低)

複数の変数の状態を� まとめて確認可能

・手動のデバッガ操作� よりも高速

🤔

・単一の状態でしか情報� を取得できない

・単一の状態でしか情報� を取得できない�・ビルド時のフラグを� 設定する必要がある

・ログの埋め込み/� 消し忘れが起き得る�・実行速度の低下

14 of 23

3種類のデバッグ方法

14

Delve

GDB

ロギング

👍

大半のGoプログラム� のデバッグに利用可能

・Goに特化しており、� GDBよりも正確な結果� が得られる

Cgoやランタイムの� デバッグに利用可能�・機能拡張が豊富�・Pythonスクリプトで� 処理を自動化可能

手軽(学習コスト低)

複数の変数の状態を� まとめて確認可能

・手動のデバッガ操作� よりも高速

🤔

・単一の状態でしか情報� を取得できない

・単一の状態でしか情報� を取得できない�・ビルド時のフラグを� 設定する必要がある

・ログの埋め込み/� 消し忘れが起き得る�・実行速度の低下

15 of 23

こんな経験はありませんか?

・テストがうまく動かない...�・プログラムが謎の挙動をする�→ ログを仕込む!

・対象変数の値は何?� log.Printf(“%#v”, targetVariable)�・対象変数の型は何?� log.Printf(“%T”, targetVariable)�・そもそも、その箇所は実行されている?� log.Printf(“passed!”)仕込んだログを消し忘れて commit & push (& release)...

15

16 of 23

人はデバッグ時に余計なことを考えたくない

・デバッグ後、ログを仕込んだ場所を忘れがち

コミット時にロギング部分を全て消すロガーを開発すれば� 良いのでは?🤔

16

17 of 23

デバッグ用ロガーのdlを開発しました

仕組み�・コミット前後のGit Hooksでロギング部分を削除・復元�・ロガーの削除・復元はGoの静的解析により実現

実装�・https://github.com/task4233/dl

紹介記事�・Git にコミットされない魔法のデバッグ用ロガーを開発� してきた - Qiita

17

18 of 23

バグらせずにロガー部分を削除する技術

静的解析をする�・プログラムを実行せずにGoのコードを解析すること�・詳しくは tenntennさんの14. 静的解析とコード生成

字句解析→構文解析→型チェックの順に行われる�・今回、型情報は必要ないので構文解析のフェーズまで(コード)�・構文解析までするとAST(抽象構文木)が得られる�・型チェックまですると型情報が得られる

18

19 of 23

AST(抽象構文木)とは

言語の意味に関係のある情報のみを取り出した木�return 1, nilReturn Statements

19

ReturnStmt

Expr�(1)

Expr

(nil)

Results

20 of 23

dlで利用されているgoパッケージと用途

20

パッケージ名

用途

該当部分

go/token

字句解析(Go Code→tokens)

go/parser

構文解析(tokens→AST)

(x/tools/)�go/ast

ASTの関連操作

go/format

AST操作後の整形

21 of 23

ASTからロガーの呼び出しを削除するために

言語仕様に沿って呼び出され得る箇所を考えるロガーの定義は func Info[T any](v T) (int, error)� Expression

Expressionが入る場所は?�・Expression Statements(例: dl.Info(1) )�・Assignment Statements(例: n, err := dl.Info(1) )�・Return Statements(例: return dl.Info(1) )�など�

21

22 of 23

ASTからロガーの呼び出しを削除するために

該当部分をASTから発見する�・Expression Statementsを見つけたい時は以下のように�・見つけた後に、該当部分を削除する�

22

23 of 23

まとめ

3種類のデバッグ方法(Delve, GDB, ロギング)�・得手不得手がある�・目的にあった方法を選択できると良い

goパッケージ(go/ast, go/tokenなど)�・静的解析のためのパッケージ�・ASTや型情報等を得ることができる�・興味のある方はツール自作も!

23

ありがとうございました!