Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
Инструменты/GitHubGitHub/pingcap/failpoint
Утилиты общего назначенияОтладчикиИнженерия Хаоса
GitHubpingcap/failpoint

failpoint

Библиотека внедрения сбоев для Go, добавляющая управляемые во время выполнения точки отказа для запуска паник, задержек, возвратов и условных путей ошибок через переменные окружения.

Репозиторий
8946712 дней назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

failpoint

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

Реализация failpoints для Golang. Fail points используются для добавления точек в коде, где ошибки могут быть внедрены под управлением пользователя. Fail point — это фрагмент кода, который выполняется только тогда, когда соответствующий failpoint активен.

Быстрый старт (использование failpoint-ctl)

  1. Соберите failpoint-ctl из исходников

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. Внедрите failpoints в вашу программу, например:

    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. Включите failpoints с помощью переменной окружения GO_FAILPOINTS

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

    Примечание: GO_FAILPOINTS не работает с маркером типа InjectCall.

  6. Если вы используете go run для запуска теста, не забудьте добавить сгенерированный в вашу команду, например:

Быстрый старт (использование failpoint-toolexec)

  1. Соберите failpoint-toolexec из исходников

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-toolexec
    
  2. Внедрите failpoints в вашу программу, например:

    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. Включите failpoints с помощью переменной окружения GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. Вы также можете использовать go run или go test, например:

    root@kitploit:~

Принципы проектирования

  • Определяйте failpoint в валидном коде Golang, а не в комментариях или где-либо ещё

  • Failpoint не имеет дополнительных затрат

    • Не влияет на обычную логику
    • Не вызывает регресса производительности обычного кода
    • Код failpoint не появится в итоговом бинарном файле
  • Код failpoint можно писать и читать, и он должен проверяться компилятором

  • Сгенерированный код по определению failpoint легко читается

  • Сохраняйте номера строк такими же, как у кода с внедрением (легче отлаживать)

  • Поддержка параллельных тестов с context.Context

Ключевые концепции

  • Failpoint

    Failpoint — это фрагмент кода, который выполняется только тогда, когда соответствующий failpoint активен. Замыкание никогда не будет выполнено, если выполняется 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.
    • Её легко писать и читать

    • Добавляет проверку компилятором для failpoints: код не скомпилируется в обычном режиме, если код failpoint невалиден

  • Список функций-маркеров

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

Как внедрить failpoint в вашу программу

  • Вы можете вызвать failpoint.Inject, чтобы внедрить failpoint в место вызова, где failpoint-name используется для запуска failpoint, а 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")
    })
    

    Преобразованный код выглядит так:

Некоторые сложные примеры failpoints

  • Внедрение failpoint в оператор инициализации 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)
        }
    }
    
  • Внедрение failpoint в оператор SELECT, чтобы он блокировал один из CASE, если failpoint активен

Рекомендации по именованию failpoint

Как вы видите выше, _curpkg_ автоматически оборачивает исходное имя failpoint в вызов failpoint.Eval. Можно думать о _curpkg_ как о макросе, который автоматически добавляет путь текущего пакета к имени failpoint. Например,

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.

Поскольку все failpoints в пакете используют одно и то же пространство имён, нужно быть внимательными, чтобы избежать конфликтов имён. Существуют некоторые рекомендуемые правила именования, улучшающие эту ситуацию.

  • Сохраняйте имя уникальным в текущем подпакете

  • Используйте самодокументируемое имя для failpoint

    Вы можете включить failpoints с помощью переменных окружения

    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. Разберите импорты и отбросьте исходные файлы, которые не импортируют failpoint
  3. Обойдите AST, чтобы найти вызовы функций-маркеров
  4. Вызовы функций-маркеров будут переписаны с помощью оператора IF, который вызывает failpoint.Eval, чтобы определить, активен ли failpoint, и выполняет код failpoint, если он включён

rewrite-demo

Благодарности

  • Благодарим gofail за предоставленную первоначальную реализацию.
Скачать инструмент
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) {}
  • Поддерживаемая переменная окружения failpoint

    failpoint можно включить с помощью экспортируемых переменных окружения по следующему шаблону, который очень похож на SYSCTL-переменные failpoint FreeBSD

    Примечание: InjectCall нельзя включить с помощью переменных окружения.

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

    Аргумент определяет, какое действие выполнить; он может быть одним из:

    • off: Не выполнять никаких действий (не запускает код failpoint)
    • return: Запустить failpoint с указанным аргументом
    • sleep: Усыпить на указанное количество миллисекунд
    • panic: Вызвать панику
    • break: Выполнить gdb и войти в отладчик
    • print: Вывести путь failpoint для переменной инъекции
    • pause: Приостановить выполнение до тех пор, пока failpoint не будет отключён
  • root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    
  • Кроме того, замыкание failpoint может быть функцией, принимающей context.Context. Вы можете делать с context.Context различные настраиваемые вещи, например управлять тем, активен ли failpoint в параллельных тестах или в других случаях. Например,

    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. Используя этот маркер, вы можете избежать загрязнения исходного кода кодом failpoint. См. examples.

  • Вы можете управлять failpoint с помощью 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))
    }
    
  • Если вы используете failpoint в контексте цикла, возможно, вам понадобятся другие функции-маркеры.

    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 напрямую, вместо того чтобы использовать функции-маркеры failpoint.

    • В Golang не допускаются неиспользуемые символы, такие как идентификатор или метка. Код будет невалидным, если какая-то метка используется только в замыкании failpoint. Например,

      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, если использовать их в замыкании напрямую.

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

    Приведённый выше блок кода сгенерирует что-то вроде этого:

    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")
        }
    }
    
  • Внедрение failpoint для динамического расширения веток 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")
    }
    
  • Более сложные failpoints

    • Существуют более сложные места для внедрения failpoint:
      • для оператора инициализации цикла, условного выражения и POST-выражения
      • для оператора RANGE
      • оператора инициализации SWITCH
      • …
    • В любом месте, где можно вызвать функцию