Slide 1

Slide 1 text

Goのデバッグ用ロガーの開発を通して得た デバッグとgoパッケージに関する知見 Go Conference 2023 2023/06/02 Takashi Mima(@task4233)

Slide 2

Slide 2 text

Takashi Mima(@task4233) 所属 ・株式会社メルカリ(Identity Platform) 興味分野 ・バックエンド  ・Go(Contribution/Go Conference) ・セキュリティ  ・CTF(SECCON Beginners)  ・セキュリティ・ミニキャンプ in 三重 2023 講師 ほか ・Twitter: @task4233 ・Portfolio: task4233.dev 2

Slide 3

Slide 3 text

Goでデバッグする際に 何を使いますか?👀 3

Slide 4

Slide 4 text

Delve?GDB?ロギング? 他の何か?🧐 4

Slide 5

Slide 5 text

本発表で共有する2つのこと 1. Goにおける3種類のデバッグ方法 2. 自作したロガーの開発に用いられているgoパッケージ ・デバッガの詳しい使い方 ・GODEBUG等の環境変数を用いたデバッグ 5 本発表で共有しないこと

Slide 6

Slide 6 text

3種類のデバッグ方法 Delve GDB ロギング 👍 ・大半のGoプログラム  のデバッグに利用可能 ・Goに特化しており、  GDBよりも正確な結果  が得られる ・Cgoやランタイムの  デバッグに利用可能 ・機能拡張が豊富 ・Pythonスクリプトで  処理を自動化可能 ・手軽(学習コスト低) ・複数の変数の状態を  まとめて確認可能 ・手動のデバッガ操作  よりも高速 🤔 ・単一の状態でしか情報  を取得できない ・単一の状態でしか情報  を取得できない ・ビルド時のフラグを  設定する必要がある ・ログの埋め込み/  消し忘れが起き得る ・実行速度の低下 6

Slide 7

Slide 7 text

Delve: Goに特化したサードパーティー製のデバッガ 目標 ・Go用のシンプルでフル機能を備えたデバッグツールの提供 Delveが適する場面 ・ビルドされたGoプログラムをデバッグする場合の大半 ・Goのランタイムやデータ構造、式を理解しているため 実装 ・github.com/go-delve/delve 7

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

GDB: Goに限らず普遍的に使えるデバッガ 用途 ・プログラム実行中の内部情報の把握 ・クラッシュした際のメモリやスタックの状態の把握 GDBが適する場面 ・Cgoコードやランタイム自体のデバッグをする場合 参考リンク ・https://sourceware.org/git/binutils-gdb.git ・https://go.dev/doc/gdb 10

Slide 11

Slide 11 text

GDB: 低レイヤのデバッグ 拡張スクリプト ・Go用拡張: go/src/runtime/runtime-gdb.py ・peda: https://github.com/longld/peda ・gef: https://github.com/hugsy/gef 11

Slide 12

Slide 12 text

ロギング: プログラムの変数等の情報出力 ロギングが適する場面 ・複数の変数情報を手軽にまとめて出力したい場合 利用できるもの ・builtin関数のpanic, print, println ・fmt, log, x/exp/slog ・glog, logrus, zap ・logr.Logger interface 12

Slide 13

Slide 13 text

3種類のデバッグ方法 Delve GDB ロギング 👍 ・大半のGoプログラム  のデバッグに利用可能 ・Goに特化しており、  GDBよりも正確な結果  が得られる ・Cgoやランタイムの  デバッグに利用可能 ・機能拡張が豊富 ・Pythonスクリプトで  処理を自動化可能 ・手軽(学習コスト低) ・複数の変数の状態を  まとめて確認可能 ・手動のデバッガ操作  よりも高速 🤔 ・単一の状態でしか情報  を取得できない ・単一の状態でしか情報  を取得できない ・ビルド時のフラグを  設定する必要がある ・ログの埋め込み/  消し忘れが起き得る ・実行速度の低下 13

Slide 14

Slide 14 text

3種類のデバッグ方法 Delve GDB ロギング 👍 ・大半のGoプログラム  のデバッグに利用可能 ・Goに特化しており、  GDBよりも正確な結果  が得られる ・Cgoやランタイムの  デバッグに利用可能 ・機能拡張が豊富 ・Pythonスクリプトで  処理を自動化可能 ・手軽(学習コスト低) ・複数の変数の状態を  まとめて確認可能 ・手動のデバッガ操作  よりも高速 🤔 ・単一の状態でしか情報  を取得できない ・単一の状態でしか情報  を取得できない ・ビルド時のフラグを  設定する必要がある ・ログの埋め込み/  消し忘れが起き得る ・実行速度の低下 14

Slide 15

Slide 15 text

こんな経験はありませんか? ・テストがうまく動かない... ・プログラムが謎の挙動をする → ログを仕込む! ・対象変数の値は何?  log.Printf(“%#v”, targetVariable) ・対象変数の型は何?  log.Printf(“%T”, targetVariable) ・そもそも、その箇所は実行されている?  log.Printf(“passed!”) → 仕込んだログを消し忘れて commit & push (& release)... 15

Slide 16

Slide 16 text

人はデバッグ時に余計なことを考えたくない ・デバッグ後、ログを仕込んだ場所を忘れがち →コミット時にロギング部分を全て消すロガーを開発すれば  良いのでは?🤔 16

Slide 17

Slide 17 text

デバッグ用ロガーのdlを開発しました 仕組み ・コミット前後のGit Hooksでロギング部分を削除・復元 ・ロガーの削除・復元はGoの静的解析により実現 実装 ・https://github.com/task4233/dl 紹介記事 ・Git にコミットされない魔法のデバッグ用ロガーを開発  してきた - Qiita 17

Slide 18

Slide 18 text

バグらせずにロガー部分を削除する技術 静的解析をする ・プログラムを実行せずにGoのコードを解析すること ・詳しくは tenntennさんの14. 静的解析とコード生成 に 字句解析→構文解析→型チェックの順に行われる ・今回、型情報は必要ないので構文解析のフェーズまで(コード) ・構文解析までするとAST(抽象構文木)が得られる ・型チェックまですると型情報が得られる 18

Slide 19

Slide 19 text

AST(抽象構文木)とは 言語の意味に関係のある情報のみを取り出した木 ・return 1, nil は Return Statements 19 ReturnStmt Expr (1) Expr (nil) Results =

Slide 20

Slide 20 text

dlで利用されているgoパッケージと用途 20 パッケージ名 用途 該当部分 go/token 字句解析(Go Code→tokens) コード go/parser 構文解析(tokens→AST) コード (x/tools/) go/ast ASTの関連操作 コード go/format AST操作後の整形 コード

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

ASTからロガーの呼び出しを削除するために 該当部分をASTから発見する ・Expression Statementsを見つけたい時は以下のように ・見つけた後に、該当部分を削除する 22

Slide 23

Slide 23 text

まとめ 3種類のデバッグ方法(Delve, GDB, ロギング) ・得手不得手がある ・目的にあった方法を選択できると良い goパッケージ(go/ast, go/tokenなど) ・静的解析のためのパッケージ ・ASTや型情報等を得ることができる ・興味のある方はツール自作も! 23 ありがとうございました!