Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
failpoint — مكتبة حقن أعطال للغة Go تضيف نقاط فشل قابلة للتحكم في وقت التشغيل لإحداث حالات panic، ونوم (sleeps)، وإرجاعات (returns)، ومسارات أخطاء شرطية عبر متغيرات البيئة. | Kitploit
أدوات/GitHubGitHub/pingcap/failpoint
أدوات عامةمصممي الأخطاءهندسة الفوضى
GitHubpingcap/failpoint

failpoint

مكتبة حقن أعطال للغة Go تضيف نقاط فشل قابلة للتحكم في وقت التشغيل لإحداث حالات panic، ونوم (sleeps)، وإرجاعات (returns)، ومسارات أخطاء شرطية عبر متغيرات البيئة.

عرض المستودع
89467منذ 12 أيامتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

failpoint

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

تنفيذ لنقاط الفشل failpoints بلغة Golang. تُستخدم نقاط الفشل لإضافة نقاط في الكود يمكن عندها حقن الأخطاء بطريقة يتحكم بها المستخدم. نقطة الفشل هي مقتطف برمجي لا يُنفَّذ إلا عندما تكون نقطة الفشل المقابلة نشطة.

البدء السريع (استخدام failpoint-ctl)

  1. ابنِ failpoint-ctl من المصدر

    root@kitploit:~
    git clone https://github.com/pingcap/failpoint.git
    cd failpoint
    make
    ls bin/failpoint-ctl
    
  2. احقن نقاط الفشل في برنامجك، مثال:

    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. فعّل نقاط الفشل باستخدام متغير البيئة 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. احقن نقاط الفشل في برنامجك، مثال:

    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. فعّل نقاط الفشل باستخدام متغير البيئة GO_FAILPOINTS

    root@kitploit:~
    GO_FAILPOINTS="main/testPanic=return(true)" ./your-program
    
  5. يمكنك أيضًا استخدام go run أو go test، مثل:

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

مبادئ التصميم

  • عرّف نقطة الفشل في كود Golang صالح، وليس في تعليقات أو أي شيء آخر

  • لا تكلف نقطة الفشل أي عبء إضافي

    • لن تؤثر على المنطق العادي
    • لن تسبب تراجعًا في أداء الكود العادي
    • لن يظهر كود نقطة الفشل في الملف الثنائي النهائي
  • روتين نقطة الفشل قابل للكتابة/القراءة ويجب أن يفحصه المترجم

  • الكود المُولَّد من تعريف نقطة الفشل سهل القراءة

  • الحفاظ على نفس أرقام الأسطر كما في الأكواد المُحقنة (أسهل للتنقيح)

  • دعم الاختبارات المتوازية باستخدام context.Context

المفاهيم الأساسية

  • نقطة الفشل

    نقطة الفشل هي مقتطف برمجي لا يُنفَّذ إلا عندما تكون نقطة الفشل المقابلة نشطة. لن يُنفَّذ الإغلاق أبدًا إذا تم تنفيذ 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.
    • سهلة الكتابة/القراءة

    • توفير فحص مترجم لنقاط الفشل التي قد لا تُترجم في الوضع العادي إذا كان كود نقطة الفشل غير صالح

  • قائمة دوال العلامات

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

كيفية حقن نقطة فشل في برنامجك

  • يمكنك استدعاء failpoint.Inject لحقن نقطة فشل في موقع الاستدعاء، حيث يُستخدم failpoint-name لتفعيل نقطة الفشل وسيتم توسيع 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")
    })
    

    والكود المحوَّل يبدو كالتالي:

    root@kitploit:~
    if _, _err_ := failpoint.Eval(_curpkg_("failpoint-name")); _err_ == nil {
        fmt.Println("unit-test")
    }
    

بعض الأمثلة المعقدة لنقاط الفشل

  • حقن نقطة فشل في جملة INIT التالية لـ 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)
        }
    }
    
  • حقن نقطة فشل في جملة SELECT لحجب إحدى حالات CASE إذا كانت نقطة الفشل نشطة

أفضل الممارسات لاسم نقطة الفشل

كما ترى أعلاه، يقوم _curpkg_ تلقائيًا بلف اسم نقطة الفشل الأصلي في استدعاء failpoint.Eval. يمكنك اعتبار _curpkg_ ككليشيه يقوم تلقائيًا بإضافة مسار الحزمة الحالية إلى اسم نقطة الفشل. على سبيل المثال،

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.

نظرًا لأن جميع نقاط الفشل في حزمة واحدة تشترك في نفس مساحة الأسماء، يجب أن نكون حريصين على تجنب تعارض الأسماء. هناك بعض قواعد التسمية الموصى بها لتحسين هذا الوضع.

  • حافظ على الاسم فريدًا في الحزمة الفرعية الحالية

  • استخدم اسمًا واضحًا بذاته لنقطة الفشل

    يمكنك تفعيل نقاط الفشل عبر متغيرات البيئة

    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. افحص الاستيرادات واستبعد ملفات المصدر التي لا تستورد نقطة فشل
  3. اجتاز شجرة AST للعثور على استدعاءات دوال العلامات
  4. ستُعاد كتابة استدعاءات دوال العلامات بجملة IF، تستدعي failpoint.Eval لتحديد ما إذا كانت نقطة الفشل نشطة وتنفذ كود نقطة الفشل إذا كانت مفعّلة

rewrite-demo

شكر وتقدير

  • شكرًا gofail لتوفير التنفيذ الأولي.
تنزيل الأداة
binding__failpoint_binding__.go
root@kitploit:~
GO_FAILPOINTS="main/testPanic=return(true)" go run your-program.go binding__failpoint_binding__.go
  • func Continue(label ...string) {}
  • func Fallthrough() {}
  • func Return(results ...interface{}) {}
  • func Label(label string) {}
  • متغير بيئة نقطة الفشل المدعوم

    يمكن تفعيل نقطة الفشل بتصدير متغيرات بيئة بالنمط التالي، وهو مشابه جدًا لمتغيرات freebsd failpoint SYSCTL VARIABLES

    ملاحظة: لا يمكن تفعيل InjectCall عبر متغيرات البيئة.

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

    وسيطة تحدد الإجراء الذي سيتم اتخاذه؛ يمكن أن تكون واحدة من:

    • off: لا يتخذ أي إجراء (لا يفعّل كود نقطة الفشل)
    • return: يفعّل نقطة الفشل بالوسيطة المحددة
    • sleep: يوقف التنفيذ لعدد المللي ثانية المحدد
    • panic: يسبب انهيارًا (Panic)
    • break: ينفذ gdb ويدخل إلى المصحح
    • print: يطبع مسار نقطة الفشل لمتغير الحقن
    • pause: يوقف التنفيذ حتى يتم تعطيل نقطة الفشل
  • أيضًا، يمكن أن يكون إغلاق نقطة الفشل دالة تأخذ context.Context. يمكنك القيام بأشياء مخصصة باستخدام context.Context مثل التحكم فيما إذا كانت نقطة الفشل نشطة في الاختبارات المتوازية أو في حالات أخرى. على سبيل المثال،

    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. باستخدام هذه العلامة، يمكنك تجنب تلويث كود المصدر بكود نقطة الفشل. راجع examples.

  • يمكنك التحكم في نقطة فشل باستخدام 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))
    }
    
  • إذا استخدمت نقطة فشل في سياق حلقة، فقد تحتاج إلى دوال علامات أخرى.

    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 مباشرة بدلاً من استخدام دوال علامات نقطة الفشل.

    • أي رمز غير مستخدم مثل معرف أو تسمية غير مسموح به في Golang. سيكون غير صالح إذا كانت بعض التسميات مستخدمة فقط في إغلاق نقطة الفشل. على سبيل المثال،

      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")
        }
    }
    
  • حقن نقطة فشل لتوسيع أذرع 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")
    }
    
  • نقاط فشل أكثر تعقيدًا

    • هناك مواقع نقاط فشل أكثر تعقيدًا يمكن الحقن فيها
      • لجملة INIT الخاصة بالحلقة، والتعبير الشرطي وجملة POST
      • لجملة RANGE
      • جملة INIT الخاصة بـ SWITCH
      • …
    • في أي مكان يمكنك فيه استدعاء دالة