Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
Outils/GitHubGitHub/pingcap/failpoint
Utilitaires GénérauxDébogueursIngénierie du Chaos
GitHubpingcap/failpoint

failpoint

Bibliothèque d'injection de fautes pour Go qui ajoute des failpoints contrôlables au runtime pour déclencher des panics, des mises en veille, des retours et des chemins d'erreur conditionnels via des variables d'environnement.

Voir le dépôt
89467il y a 12 joursVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

failpoint

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

Une implémentation des failpoints pour Golang. Les failpoints sont utilisés pour ajouter des points de code où des erreurs peuvent être injectées de manière contrôlée par l'utilisateur. Un failpoint est un extrait de code qui n'est exécuté que lorsque le failpoint correspondant est actif.

Démarrage rapide (utilisation de failpoint-ctl)

  1. Compilez failpoint-ctl à partir des sources

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. Injectez des failpoints dans votre programme, par exemple :

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Transformez votre code avec failpoint-ctl enable

  4. Compilez avec go build

  5. Activez les failpoints avec la variable d'environnement GO_FAILPOINTS

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

    Remarque : GO_FAILPOINTS ne fonctionne pas avec le type de marqueur InjectCall.

  6. Si vous utilisez go run pour exécuter le test, n'oubliez pas d'ajouter le fichier généré à votre commande, par exemple :

Démarrage rapide (utilisation de failpoint-toolexec)

  1. Compilez failpoint-toolexec à partir des sources

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. Injectez des failpoints dans votre programme, par exemple :

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Utilisez un cache de build séparé pour éviter de mélanger les caches sans failpoint-toolexec, et compilez

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

  4. Activez les failpoints avec la variable d'environnement GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. Vous pouvez aussi utiliser go run ou go test, par exemple :

    root@kitploit:~

Principes de conception

  • Définir le failpoint dans du code Golang valide, pas dans des commentaires ou autre chose

  • Un failpoint n'a aucun coût supplémentaire

    • N'affectera pas la logique normale
    • Ne provoquera pas de régression de performance du code normal
    • Le code du failpoint n'apparaîtra pas dans le binaire final
  • Le code du failpoint est lisible/modifiable et doit être vérifié par un compilateur

  • Le code généré par la définition d'un failpoint est facile à lire

  • Conserve les numéros de ligne identiques aux codes d'injection (plus facile à déboguer)

  • Prend en charge les tests parallèles avec context.Context

Concepts clés

  • Failpoint

    Un failpoint est un extrait de code qui n'est exécuté que lorsque le failpoint correspondant est actif. La fermeture ne sera jamais exécutée si failpoint.Disable("failpoint-name-for-demo") est exécuté.

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

    • Ce n'est qu'une fonction vide

      • Pour indiquer au réécrivain de réécrire avec une instruction d'égalité
      • Pour recevoir certains paramètres comme règle de réécriture
      • Elle sera inlinée au moment de la compilation et n'émettra rien dans le binaire (coût nul)
      • Les variables de la portée externe peuvent être accédées dans la fermeture par capture, et le code converti reste valide car toutes les variables capturées se trouvent dans la portée externe de l'instruction IF.
    • C'est facile à écrire/lire

    • Introduit une vérification par le compilateur pour les failpoints qui ne peuvent pas compiler en mode normal si le code du failpoint est invalide

  • Liste des fonctions de marqueur

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

Comment injecter un failpoint dans votre programme

  • Vous pouvez appeler failpoint.Inject pour injecter un failpoint au site d'appel, où failpoint-name est utilisé pour déclencher le failpoint et failpoint-closure sera développé comme corps de l'instruction IF.

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

    Le code converti ressemble à :

    root@kitploit:~
    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        return "unit-test", val
    }
    
  • failpoint.Value est la valeur transmise par failpoint.Enable("failpoint-name", "return(5)"), qui peut être ignorée.

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

    OU

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

    Et le code converti ressemble à :

Quelques exemples de failpoints complexes

  • Injection d'un failpoint dans l'instruction IF INITIALE ou l'expression CONDITIONNELLE

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

    Le bloc de code ci-dessus générera quelque chose comme ceci :

    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)
        }
    }
    
  • Injection d'un failpoint dans l'instruction SELECT pour qu'elle bloque un CASE si le failpoint est actif

Bonnes pratiques pour les noms de failpoints

Comme vous le voyez ci-dessus, _curpkg_ enveloppe automatiquement le nom original du failpoint dans un appel failpoint.Eval. Vous pouvez considérer _curpkg_ comme une macro qui préfixe automatiquement le chemin du package courant au nom du failpoint. Par exemple,

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

Vous n'avez pas besoin de vous soucier de _curpkg_ dans votre application. Il est généré automatiquement après avoir exécuté failpoint-ctl enable et supprimé avec failpoint-ctl disable.

Comme tous les failpoints d'un package partagent le même espace de noms, nous devons faire attention à éviter les conflits de noms. Voici quelques règles de nommage recommandées pour améliorer cette situation.

  • Gardez un nom unique dans le sous-package actuel

  • Utilisez un nom explicite pour le failpoint

    Vous pouvez activer les failpoints grâce aux variables d'environnement

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

Détails de l'implémentation

  1. Définir un groupe de fonctions de marqueur
  2. Analyser les imports et supprimer les fichiers sources qui n'importent pas de failpoint
  3. Parcourir l'AST pour trouver les appels aux fonctions de marqueur
  4. Les appels aux fonctions de marqueur seront réécrits avec une instruction IF, qui appelle failpoint.Eval pour déterminer si un failpoint est actif et exécute le code du failpoint si le failpoint est activé

rewrite-demo

Remerciements

  • Merci à gofail pour l'implémentation initiale.
Télécharger l’outil
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) {}
  • Variable d'environnement de failpoint prise en charge

    Un failpoint peut être activé en exportant des variables d'environnement selon le modèle suivant, ce qui ressemble beaucoup aux variables SYSCTL du failpoint FreeBSD

    Remarque : InjectCall ne peut pas être activé par des variables d'environnement.

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

    L'argument spécifie l'action à effectuer ; il peut être l'un des suivants :

    • off : n'effectue aucune action (ne déclenche pas le code du failpoint)
    • return : déclenche le failpoint avec l'argument spécifié
    • sleep : dort pendant le nombre de millisecondes spécifié
    • panic : provoque une panique
    • break : exécute gdb et s'arrête dans le débogueur
    • print : affiche le chemin du failpoint pour la variable d'injection
    • pause : suspend l'exécution jusqu'à ce que le failpoint soit désactivé
  • root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    
  • De plus, la fermeture du failpoint peut être une fonction qui prend un context.Context. Vous pouvez personnaliser certaines choses avec context.Context, comme contrôler si un failpoint est actif dans les tests parallèles ou dans d'autres cas. Par exemple,

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

    Le code converti ressemble à :

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Vous pouvez ignorer context.Context, et cela générera le même code que la version sans contexte ci-dessus. Par exemple,

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

    Devient

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(nil, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Vous pouvez utiliser failpoint.InjectCall pour injecter un appel de fonction. Ce type de marqueur ne peut être activé qu'avec failpoint.EnableCall et doit être appelé dans le même processus que le site d'appel InjectCall. Avec ce marqueur, vous pouvez éviter que le code du failpoint ne pollue votre code source. Voir examples.

  • Vous pouvez contrôler un failpoint grâce à 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))
    }
    
  • Si vous utilisez un failpoint dans le contexte d'une boucle, vous utiliserez peut-être d'autres fonctions de marqueur.

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

    Le bloc de code ci-dessus générera le code suivant :

    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
                        }
                    }
                }
            }
        }
    
  • Vous vous demandez peut-être pourquoi nous n'utilisons pas directement label, break, continue et fallthrough au lieu des fonctions de marqueur de failpoint.

    • Tout symbole inutilisé, comme un identifiant ou une étiquette, n'est pas autorisé en Golang. Ce serait invalide si une étiquette n'était utilisée que dans la fermeture du failpoint. Par exemple,

      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 et continue ne peuvent être utilisés que dans le contexte d'une boucle, ce qui n'est pas valide dans le code Golang si on les utilise directement dans une fermeture.

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

    Le bloc de code ci-dessus générera quelque chose comme ceci :

    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")
        }
    }
    
  • Injection d'un failpoint pour étendre dynamiquement les branches 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")
    }
    

    Le bloc de code ci-dessus générera quelque chose comme ceci :

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

    • Il existe des sites de failpoint plus complexes dans lesquels injecter un failpoint
      • pour l'instruction INITIALE de la boucle, l'expression CONDITIONNELLE et l'instruction POST
      • pour l'instruction RANGE
      • l'instruction INITIALE de SWITCH
      • …
    • Partout où vous pouvez appeler une fonction