Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
failpoint — Biblioteca de injeção de falhas para Go que adiciona failpoints controláveis em tempo de execução para disparar panics, sleeps, returns e caminhos de erro condicionais via variáveis de ambiente. | Kitploit
Ferramentas/GitHubGitHub/pingcap/failpoint
Utilitários de Propósito GeralDepuradoresEngenharia do Caos
GitHubpingcap/failpoint

failpoint

Biblioteca de injeção de falhas para Go que adiciona failpoints controláveis em tempo de execução para disparar panics, sleeps, returns e caminhos de erro condicionais via variáveis de ambiente.

Ver Repositório
89467há 12 diasRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

failpoint

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

Uma implementação de failpoints para Golang. Fail points são usados para adicionar pontos de código onde erros podem ser injetados de forma controlada pelo usuário. Um fail point é um trecho de código que só é executado quando o failpoint correspondente está ativo.

Início rápido (usando failpoint-ctl)

  1. Compile failpoint-ctl a partir do código-fonte

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. Injete failpoints no seu programa, por exemplo:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Transforme o seu código com failpoint-ctl enable

  4. Compile com go build

  5. Ative failpoints com a variável de ambiente GO_FAILPOINTS

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

    Nota: GO_FAILPOINTS não funciona com marcadores do tipo InjectCall.

  6. Se você usar go run para executar o teste, não se esqueça de adicionar o gerado ao seu comando, por exemplo:

Início rápido (usando failpoint-toolexec)

  1. Compile failpoint-toolexec a partir do código-fonte

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. Injete failpoints no seu programa, por exemplo:

    root@kitploit:~
    package main
    
    import "github.com/pingcap/failpoint"
    
    func main() {
        failpoint.Inject("testPanic", func() {
            panic("failpoint triggerd")
        })
    }
    
  3. Use um cache de build separado para evitar misturar caches sem o failpoint-toolexec e compile

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

  4. Ative failpoints com a variável de ambiente GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. Você também pode usar go run ou go test, por exemplo:

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

Princípios de design

  • Defina failpoint em código Golang válido, não em comentários ou qualquer outra coisa

  • Failpoint não tem nenhum custo extra

    • Não terá efeito na lógica regular
    • Não causará regressão de desempenho no código normal
    • O código do failpoint não aparecerá no binário final
  • A rotina do failpoint é escrevível/legível e deve ser verificada por um compilador

  • O código gerado pela definição de failpoint é fácil de ler

  • Mantenha os números de linha iguais aos dos códigos de injeção (mais fácil de depurar)

  • Suporte a testes paralelos com context.Context

Conceitos-chave

  • Failpoint

    Um failpoint é um trecho de código que só é executado quando o failpoint correspondente está ativo. A closure nunca será executada se failpoint.Disable("failpoint-name-for-demo") for executado.

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

    • É apenas uma função vazia

      • Para indicar ao reescritor que reescreva com uma instrução de igualdade
      • Para receber alguns parâmetros como regra de reescrita
      • Será embutida (inline) em tempo de compilação e não emitirá nada para o binário (custo zero)
      • As variáveis do escopo externo podem ser acessadas na closure por captura, e o código convertido continua válido porque todas as variáveis capturadas estão localizadas no escopo externo da instrução IF.
    • É fácil de escrever/ler

    • Introduz uma verificação do compilador para failpoints que não podem compilar no modo normal se o código do failpoint for inválido

  • Lista de funções de marcador

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

Como injetar um failpoint no seu programa

  • Você pode chamar failpoint.Inject para injetar um failpoint no local da chamada, onde failpoint-name é usado para acionar o failpoint e failpoint-closure será expandido como o corpo da instrução IF.

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

    O código convertido fica assim:

    root@kitploit:~
    if val, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        return "unit-test", val
    }
    
  • failpoint.Value é o valor passado por failpoint.Enable("failpoint-name", "return(5)"), que pode ser ignorado.

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

    E o código convertido fica assim:

    root@kitploit:~

Alguns exemplos de failpoints complicados

  • Injete um failpoint na instrução IF INITIAL ou na expressão CONDITIONAL

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

    O bloco de código acima gerará algo assim:

    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)
        }
    }
    
  • Injete um failpoint na instrução SELECT para fazer com que ela bloqueie um CASE se o failpoint estiver ativo

Melhores práticas para nomes de failpoint

Como você viu acima, _curpkg_ envolverá automaticamente o nome original do failpoint na chamada failpoint.Eval. Você pode pensar em _curpkg_ como um macro que automaticamente prefixa o caminho do pacote atual ao nome do failpoint. Por exemplo,

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

Você não precisa se preocupar com _curpkg_ na sua aplicação. Ele é gerado automaticamente após executar failpoint-ctl enable e é removido com failpoint-ctl disable.

Como todos os failpoints em um pacote compartilham o mesmo namespace, precisamos ter cuidado para evitar conflitos de nomes. Existem algumas regras de nomenclatura recomendadas para melhorar essa situação.

  • Mantenha o nome único no subpacote atual

  • Use um nome autoexplicativo para o failpoint

    Você pode ativar failpoints por variáveis de 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)"
    

Detalhes de implementação

  1. Defina um grupo de funções de marcador
  2. Analise as importações e elimine um arquivo-fonte que não importe um failpoint
  3. Percorra a AST para encontrar chamadas de funções de marcador
  4. As chamadas de funções de marcador serão reescritas com uma instrução IF, que chama failpoint.Eval para determinar se um failpoint está ativo e executa o código do failpoint se ele estiver ativado

rewrite-demo

Agradecimentos

  • Agradecemos ao gofail por fornecer a implementação inicial.
Baixar ferramenta
binding__failpoint_binding__.go
root@kitploit:~
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
  • func Goto(label string) {}
  • func Continue(label ...string) {}
  • func Fallthrough() {}
  • func Return(results ...interface{}) {}
  • func Label(label string) {}
  • Variável de ambiente suportada para failpoint

    failpoint pode ser ativado exportando variáveis de ambiente com o seguinte padrão, o que é bem semelhante a variáveis SYSCTL do failpoint do FreeBSD

    Nota: InjectCall não pode ser ativado por variáveis de ambiente.

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

    O argumento especifica qual ação tomar; pode ser um dos seguintes:

    • off: Não tomar nenhuma ação (não aciona o código do failpoint)
    • return: Aciona o failpoint com o argumento especificado
    • sleep: Dorme pelo número especificado de milissegundos
    • panic: Entrar em pânico
    • break: Executa o gdb e entra no depurador
    • print: Imprime o caminho do failpoint para a variável de injeção
    • pause: Pausa; pausa até que o failpoint seja desativado
  • if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil { fmt.Println("unit-test") }
  • Além disso, a closure do failpoint pode ser uma função que recebe context.Context. Você pode fazer algumas coisas personalizadas com context.Context, como controlar se um failpoint está ativo em testes paralelos ou em outros casos. Por exemplo,

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

    O código convertido fica assim:

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(ctx, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Você pode ignorar context.Context, e isso gerará o mesmo código da versão sem contexto acima. Por exemplo,

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

    Torna-se

    root@kitploit:~
    if val, _err_ := failpoint.EvalContext(nil, _curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test", val)
    }
    
  • Você pode usar failpoint.InjectCall para injetar uma chamada de função. Esse tipo de marcador só pode ser ativado usando failpoint.EnableCall e deve ser chamado no mesmo processo do local da chamada InjectCall. Usando esse marcador, você pode evitar que o código do failpoint polua o seu código-fonte. Veja exemplos.

  • Você pode controlar um failpoint com 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 você usar um failpoint no contexto de um loop, talvez use outras funções de marcador.

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

    O bloco de código acima gerará o seguinte código:

    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
                        }
                    }
                }
            }
        }
    
  • Você pode se perguntar por que não usamos label, break, continue e fallthrough diretamente em vez de usar as funções de marcador de failpoint.

    • Qualquer símbolo não utilizado, como um identificador ou um rótulo, não é permitido em Golang. Será inválido se algum rótulo for usado apenas na closure do failpoint. Por exemplo,

      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 só podem ser usados no contexto de loop, o que não é legal no código Golang se os usarmos diretamente na closure.

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

    O bloco de código acima gerará algo assim:

    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")
        }
    }
    
  • Injete um failpoint para estender dinamicamente os braços do 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")
    }
    

    O bloco de código acima gerará algo assim:

    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 mais complicados

    • Existem locais de failpoint mais complicados que podem ser injetados em
      • para a instrução INITIAL do loop, expressão CONDITIONAL e instrução POST
      • para a instrução RANGE
      • instrução INITIAL do SWITCH
      • …
    • Em qualquer lugar onde você possa chamar uma função