Goのデバッグ用ロガーの開発を通して得た
デバッグとgoパッケージに関する知見
Go Conference 2023
2023/06/02
Takashi Mima(@task4233)
Takashi Mima(@task4233)
所属�・株式会社メルカリ(Identity Platform)
興味分野�・バックエンド� ・Go(Contribution/Go Conference)�・セキュリティ� ・CTF(SECCON Beginners)� ・セキュリティ・ミニキャンプ in 三重 2023 講師
ほか�・Twitter: @task4233�・Portfolio: task4233.dev
2
Goでデバッグする際に
何を使いますか?👀
3
Delve?GDB?ロギング?
他の何か?🧐
4
本発表で共有する2つのこと
本発表で共有しないこと
1. Goにおける3種類のデバッグ方法
2. 自作したロガーの開発に用いられているgoパッケージ
・デバッガの詳しい使い方�・GODEBUG等の環境変数を用いたデバッグ
5
3種類のデバッグ方法
6
| Delve | GDB | ロギング |
👍 | ・大半のGoプログラム� のデバッグに利用可能 ・Goに特化しており、� GDBよりも正確な結果� が得られる | ・Cgoやランタイムの� デバッグに利用可能�・機能拡張が豊富�・Pythonスクリプトで� 処理を自動化可能 | ・手軽(学習コスト低) ・複数の変数の状態を� まとめて確認可能 ・手動のデバッガ操作� よりも高速 |
🤔 | ・単一の状態でしか情報� を取得できない | ・単一の状態でしか情報� を取得できない�・ビルド時のフラグを� 設定する必要がある | ・ログの埋め込み/� 消し忘れが起き得る�・実行速度の低下 |
Delve: Goに特化したサードパーティー製のデバッガ
目標�・Go用のシンプルでフル機能を備えたデバッグツールの提供
Delveが適する場面�・ビルドされたGoプログラムをデバッグする場合の大半�・Goのランタイムやデータ構造、式を理解しているため
7
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
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
GDB: Goに限らず普遍的に使えるデバッガ
用途�・プログラム実行中の内部情報の把握�・クラッシュした際のメモリやスタックの状態の把握
GDBが適する場面�・Cgoコードやランタイム自体のデバッグをする場合�
参考リンク�・https://sourceware.org/git/binutils-gdb.git �・https://go.dev/doc/gdb
10
GDB: 低レイヤのデバッグ
拡張スクリプト�・Go用拡張: go/src/runtime/runtime-gdb.py�・peda: https://github.com/longld/peda �・gef: https://github.com/hugsy/gef
11
ロギング: プログラムの変数等の情報出力
ロギングが適する場面�・複数の変数情報を手軽にまとめて出力したい場合
利用できるもの�・builtin関数のpanic, print, println�・fmt, log, x/exp/slog�・glog, logrus, zap�・logr.Logger interface
12
3種類のデバッグ方法
13
| Delve | GDB | ロギング |
👍 | ・大半のGoプログラム� のデバッグに利用可能 ・Goに特化しており、� GDBよりも正確な結果� が得られる | ・Cgoやランタイムの� デバッグに利用可能�・機能拡張が豊富�・Pythonスクリプトで� 処理を自動化可能 | ・手軽(学習コスト低) ・複数の変数の状態を� まとめて確認可能 ・手動のデバッガ操作� よりも高速 |
🤔 | ・単一の状態でしか情報� を取得できない | ・単一の状態でしか情報� を取得できない�・ビルド時のフラグを� 設定する必要がある | ・ログの埋め込み/� 消し忘れが起き得る�・実行速度の低下 |
3種類のデバッグ方法
14
| Delve | GDB | ロギング |
👍 | ・大半のGoプログラム� のデバッグに利用可能 ・Goに特化しており、� GDBよりも正確な結果� が得られる | ・Cgoやランタイムの� デバッグに利用可能�・機能拡張が豊富�・Pythonスクリプトで� 処理を自動化可能 | ・手軽(学習コスト低) ・複数の変数の状態を� まとめて確認可能 ・手動のデバッガ操作� よりも高速 |
🤔 | ・単一の状態でしか情報� を取得できない | ・単一の状態でしか情報� を取得できない�・ビルド時のフラグを� 設定する必要がある | ・ログの埋め込み/� 消し忘れが起き得る�・実行速度の低下 |
こんな経験はありませんか?
・テストがうまく動かない...�・プログラムが謎の挙動をする�→ ログを仕込む!
・対象変数の値は何?� log.Printf(“%#v”, targetVariable)�・対象変数の型は何?� log.Printf(“%T”, targetVariable)�・そもそも、その箇所は実行されている?� log.Printf(“passed!”)�→ 仕込んだログを消し忘れて commit & push (& release)...
15
人はデバッグ時に余計なことを考えたくない
・デバッグ後、ログを仕込んだ場所を忘れがち
→コミット時にロギング部分を全て消すロガーを開発すれば� 良いのでは?🤔
16
デバッグ用ロガーのdlを開発しました
仕組み�・コミット前後のGit Hooksでロギング部分を削除・復元�・ロガーの削除・復元はGoの静的解析により実現
17
バグらせずにロガー部分を削除する技術
静的解析をする�・プログラムを実行せずにGoのコードを解析すること�・詳しくは tenntennさんの14. 静的解析とコード生成 に
字句解析→構文解析→型チェックの順に行われる�・今回、型情報は必要ないので構文解析のフェーズまで(コード)�・構文解析までするとAST(抽象構文木)が得られる�・型チェックまですると型情報が得られる
18
AST(抽象構文木)とは
言語の意味に関係のある情報のみを取り出した木�・return 1, nil は Return Statements
19
ReturnStmt
Expr�(1)
Expr
(nil)
Results
=
dlで利用されているgoパッケージと用途
20
パッケージ名 | 用途 | 該当部分 |
go/token | 字句解析(Go Code→tokens) | |
go/parser | 構文解析(tokens→AST) | |
(x/tools/)�go/ast | ASTの関連操作 | |
go/format | AST操作後の整形 |
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
ASTからロガーの呼び出しを削除するために
該当部分をASTから発見する�・Expression Statementsを見つけたい時は以下のように�・見つけた後に、該当部分を削除する��
22
まとめ
3種類のデバッグ方法(Delve, GDB, ロギング)�・得手不得手がある�・目的にあった方法を選択できると良い
goパッケージ(go/ast, go/tokenなど)�・静的解析のためのパッケージ�・ASTや型情報等を得ることができる�・興味のある方はツール自作も!
23
ありがとうございました!