Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
failpoint — 環境変数を介して実行時制御可能なフェイルポイントを追加し、パニック、スリープ、リターン、条件付きエラーパスをトリガーできるGo向けフォールトインジェクションライブラリ。 | Kitploit
ツール/GitHubGitHub/pingcap/failpoint
汎用ユーティリティデバッガカオスエンジニアリング
GitHubpingcap/failpoint

failpoint

環境変数を介して実行時制御可能なフェイルポイントを追加し、パニック、スリープ、リターン、条件付きエラーパスをトリガーできるGo向けフォールトインジェクションライブラリ。

リポジトリを見る
8946712日前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

failpoint

LICENSE Language Go Report Card Build Status Coverage Status Mentioned in Awesome Go

Golang向けのfailpointsの実装です。フェイルポイントは、ユーザーが制御した方法でエラーを注入できるコードポイントを追加するために使用されます。フェイルポイントは、対応するフェイルポイントがアクティブな場合にのみ実行されるコードスニペットです。

クイックスタート (failpoint-ctl を使う)

  1. ソースから failpoint-ctl をビルドする

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. フェイルポイントをプログラムに注入します。例:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. failpoint-ctl enable でコードを変換する

  4. go build でビルドする

  5. GO_FAILPOINTS 環境変数でフェイルポイントを有効にする

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    

    注: GO_FAILPOINTS は InjectCall タイプのマーカーでは機能しません。

  6. go run でテストを実行する場合は、生成された binding__failpoint_binding__.go をコマンドに追加することを忘れないでください。例:

クイックスタート (failpoint-toolexec を使う)

  1. ソースから failpoint-toolexec をビルドする

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. フェイルポイントをプログラムに注入します。例:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. failpoint-toolexec なしのキャッシュが混ざらないように、別のビルドキャッシュを使用してビルドする

    GOCACHE=/tmp/failpoint-cache go build -toolexec path/to/failpoint-toolexec

  4. GO_FAILPOINTS 環境変数でフェイルポイントを有効にする

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. go run や go test を使うこともできます。例:

    root@kitploit:~
    GOCACHE=/tmp/failpoint-cache GO_FAILPOINTS="main/testPanic=return(true)" go run -toolexec path/to/failpoint-toolexec your-program.go
    

設計原則

  • フェイルポイントをコメントやその他のものではなく、有効な Golang コードで定義する

  • フェイルポイントに追加コストはない

    • 通常のロジックには影響しない
    • 通常のコードのパフォーマンスを低下させない
    • フェイルポイントコードは最終バイナリには含まれない
  • フェイルポイントのルーチンは読み書き可能で、コンパイラでチェックされるべきである

  • フェイルポイント定義によって生成されるコードは読みやすい

  • 注入コードと同じ行番号を維持する(デバッグしやすい)

  • context.Context による並列テストをサポートする

主要な概念

  • フェイルポイント

    フェイルポイントは、対応するフェイルポイントがアクティブな場合にのみ実行されるコードスニペットです。 failpoint.Disable("failpoint-name-for-demo") が実行された場合、クロージャは決して実行されません。

    root@kitploit:~
    var outerVar = "declare in outer scope"
    failpoint.Inject("failpoint-name-for-demo", func(val failpoint.Value) {
        fmt.Println("unit-test", val, outerVar)
    })
    
  • マーカー関数

    • それは単なる空の関数です

      • リライタに等価ステートメントで書き換えることを示すため
      • 書き換えルールとしていくつかのパラメータを受け取るため
      • コンパイル時にインライン化され、バイナリには何も出力されません(ゼロコスト)
      • 外部スコープの変数はキャプチャによってクロージャからアクセスでき、変換後のコードも有効です。キャプチャされた変数はすべて IF ステートメントの外側スコープに配置されるためです。
    • 書きやすく読みやすい

    • フェイルポイントコードが無効な場合、通常モードではコンパイルできないようにするコンパイラチェックを導入する

  • マーカー関数リスト

    • func Inject(fpname string, fpblock func(val Value)) {}
    • func InjectContext(fpname string, ctx context.Context, fpblock func(val Value)) {}
    • func InjectCall(fpname string, args ...any) {}
    • func Break(label ...string) {}
    • func Goto(label string) {}
    • func Continue(label ...string) {}

プログラムにフェイルポイントを注入する方法

  • failpoint.Inject を呼び出すことで、呼び出しサイトにフェイルポイントを注入できます。failpoint-name はフェイルポイントをトリガーするために使用され、failpoint-closure は IF ステートメントの本体として展開されます。

    root@kitploit:~
    failpoint.Inject("failpoint-name", func(val failpoint.Value) {
        failpoint.Return("unit-test", val)
    })
    

    変換後のコードは次のようになります:

    root@kitploit:~
    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        return "unit-test", val
    }
    
  • failpoint.Value は、failpoint.Enable("failpoint-name", "return(5)") によって渡される値で、無視することができます。

    root@kitploit:~
    failpoint.Inject("failpoint-name", func(_ failpoint.Value) {
        fmt.Println("unit-test")
    })
    

    または

    root@kitploit:~
    failpoint.Inject("failpoint-name", func() {
        fmt.Println("unit-test")
    })
    

    変換後のコードは次のようになります:

    root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    

複雑なフェイルポイントのデモ

  • IF 初期化ステートメントまたは条件式にフェイルポイントを注入する

    root@kitploit:~
    if a, b := func() {
        failpoint.Inject("failpoint-name", func(val failpoint.Value) {
            fmt.Println("unit-test", val)
        })
    }, func() int { return rand.Intn(200) }(); b > func() int {
        failpoint.Inject("failpoint-name", func(val failpoint.Value) int {
            return val.(int)
        })
        return rand.Intn(3000)
    }() && b < func() int {
        failpoint.Inject("failpoint-name-2", func(val failpoint.Value) {
            return rand.Intn(val.(int))
        })
        return rand.Intn(6000)
    }() {
        a()
        failpoint.Inject("failpoint-name-3", func(val failpoint.Value) {
            fmt.Println("unit-test", val)
        })
    }
    

    上記のコードブロックは次のようなコードを生成します:

    root@kitploit:~
    if a, b := func() {
        if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
            fmt.Println("unit-test", val)
        }
    }, func() int { return rand.Intn(200) }(); b > func() int {
        if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
            return val.(int)
        }
        return rand.Intn(3000)
    }() && b < func() int {
        if val, ok := failpoint.Eval(_curpkg_("failpoint-name-2")); ok {
            return rand.Intn(val.(int))
        }
        return rand.Intn(6000)
    }() {
        a()
        if val, ok := failpoint.Eval(_curpkg_("failpoint-name-3")); ok {
            fmt.Println("unit-test", val)
        }
    }
    
  • SELECT ステートメントにフェイルポイントを注入して、フェイルポイントがアクティブな場合に1つの CASE をブロックする

    root@kitploit:~

フェイルポイント名のベストプラクティス

上記のように、_curpkg_ は failpoint.Eval 呼び出し内の元のフェイルポイント名を自動的にラップします。_curpkg_ は、現在のパッケージパスをフェイルポイント名の前に自動的に付加するマクロと考えることができます。例:

root@kitploit:~
package ddl // which parent package is `github.com/pingcap/tidb`

func demo() {
	// _curpkg_("the-original-failpoint-name") will be expanded as `github.com/pingcap/tidb/ddl/the-original-failpoint-name`
	if val, ok := failpoint.Eval(_curpkg_("the-original-failpoint-name")); ok {...}
}

アプリケーション内で _curpkg_ を気にする必要はありません。failpoint-ctl enable を実行すると自動的に生成され、failpoint-ctl disable で削除されます。

パッケージ内のすべてのフェイルポイントは同じ名前空間を共有するため、名前の衝突を避けるように注意する必要があります。この状況を改善するための推奨命名規則がいくつかあります。

  • 現在のサブパッケージ内で名前を一意にする

  • フェイルポイントに自己説明的な名前を使用する

    環境変数でフェイルポイントを有効にできます

    root@kitploit:~
    GO_FAILPOINTS="github.com/pingcap/tidb/ddl/renameTableErr=return(100);github.com/pingcap/tidb/planner/core/illegalPushDown=return(true);github.com/pingcap/pd/server/schedulers/balanceLeaderFailed=return(true)"
    

実装の詳細

  1. マーカー関数のグループを定義する
  2. インポートを解析し、フェイルポイントをインポートしないソースファイルを削除する
  3. AST を走査してマーカー関数呼び出しを見つける
  4. マーカー関数呼び出しは、フェイルポイントがアクティブかどうかを判断するために failpoint.Eval を呼び出す IF ステートメントで書き換えられ、フェイルポイントが有効な場合にフェイルポイントコードを実行します

rewrite-demo

謝辞

  • 初期実装を提供してくれた gofail に感謝します。
ツールをダウンロード
root@kitploit:~
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
  • func Fallthrough() {}
  • func Return(results ...interface{}) {}
  • func Label(label string) {}
  • サポートされているフェイルポイント環境変数

    フェイルポイントは、以下のパターンの環境変数をエクスポートすることで有効にできます。これは freebsd failpoint SYSCTL VARIABLES と非常によく似ています。

    注: InjectCall は環境変数では有効にできません。

    root@kitploit:~
    [<percent>%][<count>*]<type>[(args...)][-><more terms>]
    

    引数は実行するアクションを指定します。以下から1つを選択できます:

    • off: 何もしません(フェイルポイントコードをトリガーしません)
    • return: 指定された引数でフェイルポイントをトリガーします
    • sleep: 指定されたミリ秒数だけスリープします
    • panic: パニックを発生させます
    • break: gdb を実行してデバッガにブレークします
    • print: 注入変数のフェイルポイントパスを出力します
    • pause: フェイルポイントが無効になるまで一時停止します
  • また、フェイルポイントのクロージャは context.Context を受け取る関数にすることができます。並列テストやその他のケースでフェイルポイントがアクティブかどうかを制御するなど、context.Context を使ってカスタマイズした処理を行うことができます。例:

    root@kitploit:~
    failpoint.InjectContext(ctx, "failpoint-name", func(val failpoint.Value) {
        fmt.Println("unit-test", val)
    })
    

    変換後のコードは次のようになります:

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • context.Context を無視することもでき、その場合は上記の非コンテキスト版と同じコードが生成されます。例:

    root@kitploit:~
    failpoint.InjectContext(nil, "failpoint-name", func(val failpoint.Value) {
        fmt.Println("unit-test", val)
    })
    

    は、以下のようになります:

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(nil, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • failpoint.InjectCall を使用して関数呼び出しを注入できます。このタイプのマーカーは failpoint.EnableCall を使用した場合にのみ有効にでき、InjectCall の呼び出しサイトと同じプロセス内で呼び出す必要があります。このマーカーを使用すると、フェイルポイントコードがソースコードを汚染するのを防げます。examples を参照してください。

  • フェイルポイントは failpoint.WithHook で制御できます。

    root@kitploit:~
    func (s *dmlSuite) TestCRUDParallel() {
        sctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
            return ctx.Value(fpname) != nil // Determine by ctx key
        })
        insertFailpoints = map[string]struct{} {
            "insert-record-fp": {},
            "insert-index-fp": {},
            "on-duplicate-fp": {},
        }
        ictx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
            _, found := insertFailpoints[fpname] // Only enables some failpoints.
            return found
        })
        deleteFailpoints = map[string]struct{} {
            "tikv-is-busy-fp": {},
            "fetch-tso-timeout": {},
        }
        dctx := failpoint.WithHook(context.Backgroud(), func(ctx context.Context, fpname string) bool {
            _, found := deleteFailpoints[fpname] // Only disables failpoints. 
            return !found
        })
        // other DML parallel test cases.
        s.RunParallel(buildSelectTests(sctx))
        s.RunParallel(buildInsertTests(ictx))
        s.RunParallel(buildDeleteTests(dctx))
    }
    
  • ループコンテキストでフェイルポイントを使用する場合、他のマーカー関数を使用することになるかもしれません。

    root@kitploit:~
    failpoint.Label("outer")
    for i := 0; i < 100; i++ {
        inner:
            for j := 0; j < 1000; j++ {
                switch rand.Intn(j) + i {
                case j / 5:
                    failpoint.Break()
                case j / 7:
                    failpoint.Continue("outer")
                case j / 9:
                    failpoint.Fallthrough()
                case j / 10:
                    failpoint.Goto("outer")
                default:
                    failpoint.Inject("failpoint-name", func(val failpoint.Value) {
                        fmt.Println("unit-test", val.(int))
                        if val == j/11 {
                            failpoint.Break("inner")
                        } else {
                            failpoint.Goto("outer")
                        }
                    })
            }
        }
    }
    

    上記のコードブロックは次のコードを生成します:

    root@kitploit:~
    outer:
        for i := 0; i < 100; i++ {
        inner:
            for j := 0; j < 1000; j++ {
                switch rand.Intn(j) + i {
                case j / 5:
                    break
                case j / 7:
                    continue outer
                case j / 9:
                    fallthrough
                case j / 10:
                    goto outer
                default:
                    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
                        fmt.Println("unit-test", val.(int))
                        if val == j/11 {
                            break inner
                        } else {
                            goto outer
                        }
                    }
                }
            }
        }
    
  • フェイルポイントのマーカー関数を使わずに、label、break、continue、fallthrough を直接使わないのはなぜか疑問に思うかもしれません。

    • Golang では、識別子やラベルのような未使用のシンボルは許可されません。あるラベルがフェイルポイントのクロージャ内でのみ使用されている場合、無効になります。例えば、

      root@kitploit:~
      label1: // compiler error: unused label1
          failpoint.Inject("failpoint-name", func(val failpoint.Value) {
              if val.(int) == 1000 {
                  goto label1 // illegal to use goto here
              }
              fmt.Println("unit-test", val)
          })
      
    • break と continue はループコンテキストでのみ使用できます。クロージャ内で直接使用すると、Golangコードとして不正です。

  • func (s *StoreService) ExecuteStoreTask() { select { case <-func() chan *StoreTask { failpoint.Inject("priority-fp", func(_ failpoint.Value) { return make(chan *StoreTask) }) return s.priorityHighCh }(): fmt.Println("execute high priority task") case <- s.priorityNormalCh: fmt.Println("execute normal priority task") case <- s.priorityLowCh: fmt.Println("execute normal low task") } }

    上記のコードブロックは次のようなコードを生成します:

    root@kitploit:~
    func (s *StoreService) ExecuteStoreTask() {
        select {
        case <-func() chan *StoreTask {
            if _, ok := failpoint.Eval(_curpkg_("priority-fp")); ok {
                return make(chan *StoreTask)
            })
            return s.priorityHighCh
        }():
            fmt.Println("execute high priority task")
    
        case <- s.priorityNormalCh:
            fmt.Println("execute normal priority task")
    
        case <- s.priorityLowCh:
            fmt.Println("execute normal low task")
        }
    }
    
  • SWITCH CASE の分岐を動的に拡張するためにフェイルポイントを注入する

    root@kitploit:~
    switch opType := operator.Type(); {
    case opType == "balance-leader":
        fmt.Println("create balance leader steps")
    
    case opType == "balance-region":
        fmt.Println("create balance region steps")
    
    case opType == "scatter-region":
        fmt.Println("create scatter region steps")
    
    case func() bool {
        failpoint.Inject("dynamic-op-type", func(val failpoint.Value) bool {
            return strings.Contains(val.(string), opType)
        })
        return false
    }():
        fmt.Println("do something")
    
    default:
        panic("unsupported operator type")
    }
    

    上記のコードブロックは次のようなコードを生成します:

    root@kitploit:~
    switch opType := operator.Type(); {
    case opType == "balance-leader":
        fmt.Println("create balance leader steps")
    
    case opType == "balance-region":
        fmt.Println("create balance region steps")
    
    case opType == "scatter-region":
        fmt.Println("create scatter region steps")
    
    case func() bool {
        if val, ok := failpoint.Eval(_curpkg_("dynamic-op-type")); ok {
            return strings.Contains(val.(string), opType)
        }
        return false
    }():
        fmt.Println("do something")
    
    default:
        panic("unsupported operator type")
    }
    
  • より複雑なフェイルポイント

    • より複雑なフェイルポイントサイトを注入できる場所があります
      • ループの初期化ステートメント、条件式、POST ステートメント用
      • RANGE ステートメント用
      • SWITCH 初期化ステートメント
      • …
    • 関数を呼び出せる場所ならどこでも