Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
failpoint — Libreria di fault-injection per Go che aggiunge failpoint controllabili a runtime per innescare panic, sleep, return e percorsi di errore condizionali tramite variabili d'ambiente. | Kitploit
Strumenti/GitHubGitHub/pingcap/failpoint
Utilità GenericheDebuggerIngegneria del CaosTop in Ingegneria del Caos n.18
GitHubpingcap/failpoint

failpoint

Libreria di fault-injection per Go che aggiunge failpoint controllabili a runtime per innescare panic, sleep, return e percorsi di errore condizionali tramite variabili d'ambiente.

Vedi Repository
89467301 mese faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

failpoint

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

Un'implementazione dei failpoint per Golang. I failpoint vengono usati per aggiungere punti nel codice in cui gli errori possono essere iniettati in modo controllato dall'utente. Un failpoint è uno snippet di codice che viene eseguito solo quando il corrispondente failpoint è attivo.

Avvio rapido (usa )

failpoint-ctl
  1. Compila failpoint-ctl dal sorgente

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. Inietta i failpoint nel tuo programma, ad esempio:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Trasforma il tuo codice con failpoint-ctl enable

  4. Compila con go build

  5. Abilita i failpoint con la variabile d'ambiente GO_FAILPOINTS

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

    Nota: GO_FAILPOINTS non funziona con il tipo di marcatore InjectCall.

  6. Se usi go run per eseguire il test, non dimenticare di aggiungere il file generato binding__failpoint_binding__.go al tuo comando, ad esempio:

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

Avvio rapido (usa failpoint-toolexec)

  1. Compila failpoint-toolexec dal sorgente

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. Inietta i failpoint nel tuo programma, ad esempio:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Usa una cache di build separata per evitare di mescolare le cache senza failpoint-toolexec, e compila

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

  4. Abilita i failpoint con la variabile d'ambiente GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. Puoi anche usare go run o go test, ad esempio:

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

Principi di progettazione

  • Definisci il failpoint in codice Golang valido, non in commenti o altro

  • Un failpoint non comporta alcun costo aggiuntivo

    • Non avrà effetto sulla logica normale
    • Non causerà regressioni di prestazioni nel codice normale
    • Il codice del failpoint non apparirà nel binario finale
  • La routine del failpoint è scrivibile/leggibile e deve essere controllata da un compilatore

  • Il codice generato dalla definizione del failpoint è facile da leggere

  • Mantieni gli stessi numeri di riga dei codici di iniezione (più facile da debuggare)

  • Supporto per test paralleli con context.Context

Concetti chiave

  • Failpoint

    Un failpoint è uno snippet di codice che viene eseguito solo quando il corrispondente failpoint è attivo. La closure non verrà mai eseguita se viene eseguito 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)
    })
    
  • Funzioni marcatore

    • Sono solo funzioni vuote

      • Per suggerire al rewriter di riscrivere con un'istruzione di uguaglianza
      • Per ricevere alcuni parametri come regola di riscrittura
      • Saranno inline in fase di compilazione e non emetteranno nulla nel binario (costo zero)
      • Le variabili nello scope esterno possono essere accedute nella closure tramite cattura, e il codice convertito è comunque legale perché tutte le variabili catturate si trovano nello scope esterno dell'istruzione IF.
    • Sono facili da scrivere/leggere

    • Introducono un controllo del compilatore per i failpoint che non possono compilare in modalità normale se il codice del failpoint non è valido

  • Elenco delle funzioni marcatore

    • 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) {}
    • func Fallthrough() {}
    • func Return(results ...interface{}) {}
    • func Label(label string) {}
  • Variabili d'ambiente supportate per i failpoint

    Un failpoint può essere abilitato esportando variabili d'ambiente con il seguente pattern, molto simile a freebsd failpoint SYSCTL VARIABLES

    Nota: InjectCall non può essere abilitato tramite variabili d'ambiente.

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

    L'argomento specifica quale azione intraprendere; può essere uno dei seguenti:

    • off: Non eseguire alcuna azione (non attiva il codice del failpoint)
    • return: Attiva il failpoint con l'argomento specificato
    • sleep: Dormi per il numero specificato di millisecondi
    • panic: Panico
    • break: Esegui gdb e interrompi nel debugger
    • print: Stampa il percorso del failpoint per la variabile di iniezione
    • pause: Pausa sospende fino a quando il failpoint non viene disabilitato

Come iniettare un failpoint nel tuo programma

  • Puoi chiamare failpoint.Inject per iniettare un failpoint nel punto di chiamata, dove failpoint-name viene usato per attivare il failpoint e failpoint-closure verrà espansa come corpo dell'istruzione IF.

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

    Il codice convertito è simile a:

    root@kitploit:~
    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        return "unit-test", val
    }
    
  • failpoint.Value è il valore passato da failpoint.Enable("failpoint-name", "return(5)") che può essere ignorato.

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

    OPPURE

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

    E il codice convertito è simile a:

    root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    
  • Inoltre, la closure del failpoint può essere una funzione che accetta context.Context. Puoi fare cose personalizzate con context.Context, come controllare se un failpoint è attivo nei test paralleli o in altri casi. Per esempio,

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

    Il codice convertito è simile a:

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Puoi ignorare context.Context, e questo genererà lo stesso codice della versione senza contesto sopra. Per esempio,

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

    Diventa

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(nil, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Puoi usare failpoint.InjectCall per iniettare una chiamata di funzione; questo tipo di marcatore può essere abilitato solo usando failpoint.EnableCall e deve essere chiamato nello stesso processo del punto di chiamata InjectCall. Usando questo marcatore, puoi evitare che il codice del failpoint inquini il tuo codice sorgente. Vedi esempi.

  • Puoi controllare un failpoint tramite 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))
    }
    
  • Se usi un failpoint nel contesto di un loop, forse userai altre funzioni marcatore.

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

    Il blocco di codice sopra genererà il seguente codice:

    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
                        }
                    }
                }
            }
        }
    
  • Potresti chiederti perché non usiamo direttamente label, break, continue e fallthrough invece delle funzioni marcatore dei failpoint.

    • Qualsiasi simbolo inutilizzato come un identificatore o un'etichetta non è consentito in Golang. Sarà invalido se un'etichetta viene usata solo nella closure del failpoint. Per esempio,

      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 e continue possono essere usati solo nel contesto di un loop, il che non è legale nel codice Golang se li usiamo direttamente in una closure.

Alcuni esempi di failpoint complicati

  • Inietta un failpoint nell'istruzione IF INITIAL o nell'espressione CONDIZIONALE

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

    Il blocco di codice sopra genererà qualcosa come questo:

    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)
        }
    }
    
  • Inietta un failpoint nell'istruzione SELECT per bloccare un CASE se il failpoint è attivo

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

    Il blocco di codice sopra genererà qualcosa come questo:

    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")
        }
    }
    
  • Inietta un failpoint per estendere dinamicamente i rami 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")
    }
    

    Il blocco di codice sopra genererà qualcosa come questo:

    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")
    }
    
  • Failpoint più complicati

    • Ci sono punti di iniezione failpoint più complicati a cui è possibile iniettare:
      • per l'istruzione INITIAL del loop, l'espressione CONDIZIONALE e l'istruzione POST
      • per l'istruzione RANGE
      • istruzione INITIAL di SWITCH
      • …
    • Ovunque si possa chiamare una funzione

Buone pratiche per i nomi dei failpoint

Come visto sopra, _curpkg_ avvolgerà automaticamente il nome originale del failpoint nella chiamata failpoint.Eval. Puoi pensare a _curpkg_ come a una macro che antepone automaticamente il percorso del pacchetto corrente al nome del failpoint. Per esempio,

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

Non devi preoccuparti di _curpkg_ nella tua applicazione. Viene generato automaticamente dopo aver eseguito failpoint-ctl enable e viene eliminato con failpoint-ctl disable.

Poiché tutti i failpoint in un pacchetto condividono lo stesso namespace, dobbiamo fare attenzione a evitare conflitti di nomi. Ci sono alcune regole di denominazione consigliate per migliorare questa situazione.

  • Mantieni il nome univoco nel sottopacchetto corrente

  • Usa un nome autoesplicativo per il failpoint

    Puoi abilitare i failpoint tramite variabili d'ambiente

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

Dettagli di implementazione

  1. Definisci un gruppo di funzioni marcatore
  2. Analizza le importazioni e rimuovi un file sorgente che non importa un failpoint
  3. Attraversa l'AST per trovare le chiamate alle funzioni marcatore
  4. Le chiamate alle funzioni marcatore verranno riscritte con un'istruzione IF, che chiama failpoint.Eval per determinare se un failpoint è attivo ed esegue il codice del failpoint se il failpoint è abilitato

rewrite-demo

Ringraziamenti

  • Grazie a gofail per aver fornito l'implementazione iniziale.
Scarica lo strumento