Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
failpoint — Fehlerinjektions-Bibliothek für Go, die zur Laufzeit steuerbare Failpoints hinzufügt, um über Umgebungsvariablen Panics, Sleeps, Returns und bedingte Fehlerpfade auszulösen. | Kitploit
Tools/GitHubGitHub/pingcap/failpoint
Allgemeine DienstprogrammeDebuggerChaos-Engineering
GitHubpingcap/failpoint

failpoint

Fehlerinjektions-Bibliothek für Go, die zur Laufzeit steuerbare Failpoints hinzufügt, um über Umgebungsvariablen Panics, Sleeps, Returns und bedingte Fehlerpfade auszulösen.

Repository anzeigen
89467vor 12 TagenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

failpoint

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

Eine Implementierung von Failpoints für Golang. Failpoints werden verwendet, um Codepunkte einzufügen, an denen Fehler in einer benutzergesteuerten Weise injiziert werden können. Ein Failpoint ist ein Codeausschnitt, der nur ausgeführt wird, wenn der entsprechende Failpoint aktiv ist.

Schnellstart (mit failpoint-ctl)

  1. Baue failpoint-ctl aus dem Quellcode

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. Injiziere Failpoints in dein Programm, z. B.:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Transformiere deinen Code mit failpoint-ctl enable

  4. Baue mit go build

  5. Aktiviere Failpoints mit der Umgebungsvariable GO_FAILPOINTS

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

    Hinweis: GO_FAILPOINTS funktioniert nicht mit Markern vom Typ InjectCall.

  6. Wenn du go run zum Ausführen des Tests verwendest, vergiss nicht, die generierte Datei in deinen Befehl aufzunehmen, z. B.:

Schnellstart (mit failpoint-toolexec)

  1. Baue failpoint-toolexec aus dem Quellcode

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. Injiziere Failpoints in dein Programm, z. B.:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Verwende einen separaten Build-Cache, um eine Vermischung mit Caches ohne failpoint-toolexec zu vermeiden, und baue:

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

  4. Aktiviere Failpoints mit der Umgebungsvariable GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. Du kannst auch go run oder go test verwenden, z. B.:

    root@kitploit:~

Designprinzipien

  • Definiere Failpoints als gültigen Golang-Code, nicht als Kommentare oder etwas anderes

  • Failpoints verursachen keine zusätzlichen Kosten

    • Sie haben keine Auswirkung auf die reguläre Logik
    • Sie verursachen keine Leistungsregression im regulären Code
    • Failpoint-Code erscheint nicht im finalen Binary
  • Failpoint-Routinen sind schreib-/lesbar und sollten von einem Compiler geprüft werden

  • Der durch die Failpoint-Definition erzeugte Code ist leicht lesbar

  • Die Zeilennummern bleiben identisch mit den injizierten Codes (einfacheres Debugging)

  • Unterstützung paralleler Tests mit context.Context

Kernkonzepte

  • Failpoint

    Ein Failpoint ist ein Codeausschnitt, der nur ausgeführt wird, wenn der entsprechende Failpoint aktiv ist. Die Closure wird niemals ausgeführt, wenn failpoint.Disable("failpoint-name-for-demo") ausgeführt wird.

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

    • Sie sind nur leere Funktionen

      • Sie weisen den Rewriter an, mit einer Gleichheitsanweisung umzuschreiben
      • Sie empfangen einige Parameter als Umschreibungsregel
      • Sie werden zur Compile-Zeit inline eingefügt und geben nichts an das Binary aus (Nullkosten)
      • Die Variablen im äußeren Gültigkeitsbereich können in der Closure durch Capturing zugegriffen werden, und der konvertierte Code ist weiterhin gültig, da sich alle erfassten Variablen im äußeren Gültigkeitsbereich der IF-Anweisung befinden.
    • Sie sind leicht zu schreiben/lesen

    • Sie führen eine Compilerprüfung für Failpoints ein, die im regulären Modus nicht kompilieren können, wenn der Failpoint-Code ungültig ist

  • Liste der Markerfunktionen

    • 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) {}

So injizierst du einen Failpoint in dein Programm

  • Du kannst failpoint.Inject aufrufen, um einen Failpoint an der Aufrufstelle zu injizieren, wobei failpoint-name zum Auslösen des Failpoints verwendet wird und failpoint-closure als Rumpf der IF-Anweisung expandiert wird.

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

    Der konvertierte Code sieht wie folgt aus:

    root@kitploit:~
    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        return "unit-test", val
    }
    
  • failpoint.Value ist der Wert, der durch failpoint.Enable("failpoint-name", "return(5)") übergeben wird, und kann ignoriert werden.

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

    ODER

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

Einige komplizierte Failpoint-Demos

  • Injiziere einen Failpoint in die IF-INITIAL-Anweisung oder den CONDITIONAL-Ausdruck

    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)
        })
    }
    

    Der obige Codeblock erzeugt etwas wie folgt:

    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)
        }
    }
    
  • Injiziere einen Failpoint in die SELECT-Anweisung, um einen CASE zu blockieren, wenn der Failpoint aktiv ist

Best Practices für Failpoint-Namen

Wie oben zu sehen, umschließt _curpkg_ den ursprünglichen Failpoint-Namen im failpoint.Eval-Aufruf automatisch. Du kannst dir _curpkg_ wie ein Makro vorstellen, das dem Failpoint-Namen automatisch den aktuellen Paketpfad voranstellt. Zum Beispiel:

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 {...}
}

Du musst dich in deiner Anwendung nicht um _curpkg_ kümmern. Es wird nach dem Ausführen von failpoint-ctl enable automatisch erzeugt und mit failpoint-ctl disable wieder entfernt.

Da alle Failpoints in einem Paket denselben Namespace teilen, müssen wir vorsichtig sein, um Namenskonflikte zu vermeiden. Es gibt einige empfohlene Namensregeln, um diese Situation zu verbessern.

  • Halte den Namen im aktuellen Unterpaket eindeutig

  • Verwende einen selbsterklärenden Namen für den Failpoint

    Du kannst Failpoints über Umgebungsvariablen aktivieren

    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)"
    

Implementierungsdetails

  1. Definiere eine Gruppe von Markerfunktionen
  2. Analysiere Imports und entferne Quelldateien, die keinen Failpoint importieren
  3. Durchlaufe den AST, um Markerfunktionsaufrufe zu finden
  4. Markerfunktionsaufrufe werden mit einer IF-Anweisung umgeschrieben, die failpoint.Eval aufruft, um zu bestimmen, ob ein Failpoint aktiv ist, und führt den Failpoint-Code aus, wenn der Failpoint aktiviert ist

rewrite-demo

Danksagungen

  • Dank an gofail für die Bereitstellung der ursprünglichen Implementierung.
Tool herunterladen
binding__failpoint_binding__.go
root@kitploit:~
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
GOCACHE=/tmp/failpoint-cache GO_FAILPOINTS="main/testPanic=return(true)" go run -toolexec path/to/failpoint-toolexec your-program.go
  • func Goto(label string) {}
  • func Continue(label ...string) {}
  • func Fallthrough() {}
  • func Return(results ...interface{}) {}
  • func Label(label string) {}
  • Unterstützte Failpoint-Umgebungsvariablen

    Failpoints können durch Exportieren von Umgebungsvariablen mit dem folgenden Muster aktiviert werden, das den FreeBSD-Failpoint-SYSCTL-VARIABLEN recht ähnlich ist.

    Hinweis: InjectCall kann nicht über Umgebungsvariablen aktiviert werden.

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

    Das Argument gibt an, welche Aktion ausgeführt werden soll; es kann eines der folgenden sein:

    • off: Keine Aktion ausführen (Failpoint-Code nicht auslösen)
    • return: Failpoint mit dem angegebenen Argument auslösen
    • sleep: Die angegebene Anzahl von Millisekunden schlafen
    • panic: Panik auslösen
    • break: gdb ausführen und in den Debugger einbrechen
    • print: Failpoint-Pfad für die Injektionsvariable ausgeben
    • pause: Pause wird pausieren, bis der Failpoint deaktiviert wird
  • Und der konvertierte Code sieht wie folgt aus:

    root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    
  • Außerdem kann die Failpoint-Closure eine Funktion sein, die context.Context entgegennimmt. Du kannst mit context.Context eigene Dinge tun, z. B. steuern, ob ein Failpoint in parallelen Tests oder anderen Fällen aktiv ist. Zum Beispiel:

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

    Der konvertierte Code sieht wie folgt aus:

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Du kannst context.Context ignorieren, und dies erzeugt denselben Code wie die obige Nicht-Kontext-Version. Zum Beispiel:

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

    wird zu

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(nil, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Du kannst failpoint.InjectCall verwenden, um einen Funktionsaufruf zu injizieren. Dieser Markertyp kann nur mit failpoint.EnableCall aktiviert werden und muss im selben Prozess wie die InjectCall-Aufrufstelle aufgerufen werden. Mit diesem Marker kannst du vermeiden, dass Failpoint-Code deinen Quellcode verschmutzt. Siehe Beispiele.

  • Du kannst einen Failpoint mit failpoint.WithHook steuern

    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))
    }
    
  • Wenn du einen Failpoint im Schleifenkontext verwendest, benötigst du möglicherweise andere Markerfunktionen.

    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")
                        }
                    })
            }
        }
    }
    

    Der obige Codeblock erzeugt den folgenden Code:

    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
                        }
                    }
                }
            }
        }
    
  • Du fragst dich vielleicht, warum wir nicht direkt label, break, continue und fallthrough verwenden, anstatt Failpoint-Markerfunktionen zu nutzen.

    • Jedes ungenutzte Symbol wie eine Kennung oder ein Label ist in Golang nicht erlaubt. Es ist ungültig, wenn ein Label nur in der Failpoint-Closure verwendet wird. Zum Beispiel:

      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 und continue können nur im Schleifenkontext verwendet werden, was im Golang-Code nicht zulässig ist, wenn wir sie direkt in einer Closure verwenden.

  • root@kitploit:~
    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")
        }
    }
    

    Der obige Codeblock erzeugt etwas wie folgt:

    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")
        }
    }
    
  • Injiziere einen Failpoint, um SWITCH-CASE-Zweige dynamisch zu erweitern

    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")
    }
    

    Der obige Codeblock erzeugt etwas wie folgt:

    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")
    }
    
  • Kompliziertere Failpoints

    • Es gibt kompliziertere Failpoint-Stellen, in die injiziert werden kann
      • für die Schleifen-INITIAL-Anweisung, den CONDITIONAL-Ausdruck und die POST-Anweisung
      • für die RANGE-Anweisung
      • SWITCH-INITIAL-Anweisung
      • …
    • Überall dort, wo du eine Funktion aufrufen kannst