一文搞懂如何在 Go 包中支持 Hash-Based Bisect 調試

bisect 是一個英文動詞,意爲 “二分” 或“分成兩部分”。在數學和計算機科學中,通常指將一個區間或一個集合分成兩個相等的部分。

對於程序員來說,最熟悉的 bisect 應用莫過於下面兩個:

二分查找是一個經典且高效的查找算法,任何一本介紹數據結構或計算機算法的書都會包含對二分查找的系統說明。所謂二分查找就是通過不斷將搜索區間一分爲二來找到目標值。一些排序算法也應用了 bisect 的思想,比如快速排序 (QuickSort) 等。

git bisect 是一個非常實用的 Git 命令,它通過二分查找的方式有效縮小可能導致錯誤的提交範圍,幫助開發人員快速定位引入錯誤的提交。其工作原理是反覆從版本控制系統中檢出不同的提交併運行測試,將結果標記爲 “good” 或“bad”。這個過程持續進行,直到找到引入 bug 的具體提交(bad commit):

git bisect 特別適用於當你懷疑某個 bug 是由於代碼庫歷史中的特定更改引起時,這種情況在日常開發中非常常見。

然而,並非所有的 bug 都能通過 git bisect 查找出來。尤其在編譯器、運行時庫以及大型複雜項目中,問題往往潛藏在難以排查的調用棧、數據流或代碼路徑中。在這些情況下,git bisect 這種傳統的工具可能會顯得力不從心。

注:如果你還不熟悉 git bisect 的使用方法,可以參考本文後面附錄中的入門示例。

在今年 7 月份,Go 團隊前技術主管 Russ Cox 在他的博客上發表了一篇題爲 “Hash-Based Bisect Debugging in Compilers and Runtimes[1]” 的文章,介紹了 Go 編譯器和運行時團隊內部使用的高級調試技術——Hash-Based Bisect。這一技術爲我們提供了一種全新的問題定位方式。

在這篇文章中,我將帶領大家深入瞭解 Hash-Based Bisect 這一高級調試技術,探索如何讓我們自己的 Go 包支持這一調試技術,以及如何在日常開發中幫助我們快速定位一些難以排查的潛在問題。

  1. Hash-Based Bisect 是什麼

前面提到過,git bisect 常用於代碼提交歷史的迴歸問題排查。然而,當問題不是由提交歷史引發,而是涉及程序行爲的動態變化時,git bisect 便顯得無能爲力。例如:

Hash-Based Bisect 正是爲了解決這些問題而設計的。它突破了靜態版本的侷限,將調試範圍擴展到了動態行爲層面。

那麼 Hash-Based Bisect 究竟是什麼技術呢?它是一種基於哈希值和二分搜索的調試技術,旨在快速定位複雜程序中導致問題的最小變化點集合。通過爲代碼中的變化點(如函數、行號或調用棧)生成唯一的哈希值,該技術將程序行爲映射到這些標識符上。接着,通過逐步啓用或禁用特定變化點,結合測試程序的運行結果,遞歸縮小問題範圍,最終定位問題根源 (某幾行代碼甚至是某一行代碼):

與 git bisect 專注於找到引入錯誤的提交不同,基於散列的 bisect 不會去遍歷版本歷史,而是直接對代碼的結構和執行流進行操作,其調試的結果也不會與特定提交相關,而是與代碼與特定執行路徑或功能的交互相關,即精確定位特定的代碼行,函數調用,甚至是觸發失敗的調用堆棧

下面我們再來仔細說明一下該技術的工作原理。

  1. Hash-Based Bisect 的工作原理

Hash-Based Bisect 的核心在於利用哈希值爲程序的變化點(如函數、代碼行、調用棧等)分配唯一標識,並通過二分搜索算法,逐步縮小問題範圍。它通過動態啓用或禁用這些變化點,結合測試結果判斷問題是否被觸發,從而定位導致問題的最小變化集。

這個方法有兩個關鍵要素:

在 Russ Cox 的文章中,他提及了一些傳統的二分方法,比如 List-Based Bisect-Reduce、Counter-Based Bisect-Reduce 等,但這些方法存在編號順序不穩定、多變化點調試困難、擴展性有限以及不適合併發或動態場景等問題。

而通過哈希函數生成變化點的標識,確保無論代碼執行順序、環境或併發情況如何,變化點的標識始終唯一且穩定的。同時輸入更爲簡潔,通過簡短的哈希模式(如 001+110),避免長列表或複雜編號,並且可適配多種問題類型(優化規則、運行時行爲、動態調用棧等)。

利用二分搜索算法在運行時動態啓用和禁用變化點,高效縮小問題範圍,減少需要手動排查的複雜度。

下面我們再通過 Hash-Based Bisect 的典型工作流程來進一步理解它的原理。

首先是定義變化點

將程序中可能導致問題的變化點抽象出來,比如:

接下來,生成變化點的唯一哈希值

以 Go 當前的 hash-based bisect 工具 [2] 以及支持該工具調試的 Go 包爲例,對於每個變化點,Go 包需要通過 bisect.Hash 方法生成哈希值,用於唯一標識。例如:

id := bisect.Hash("foo.go", 10) // 生成foo.go文件第10行的唯一標識。

第三步,利用二分搜索進行自動的遞歸測試。具體來說,就是通過二分搜索逐步啓用或禁用變化點:

最後,報告變化點,即最終輸出導致問題的最小變化集,幫助開發者快速定位問題。

Russ Cox 文章中給了一個 “某個函數的編譯優化規則導致測試失敗” 的例子,例子中包含一組數學函數:

add, cos, div, exp, mod, mul, sin, sqr, sub, tan

要針對這個問題場景使用 hash-based bisect 進行調試,第一步就是要定義函數變化點,併爲每個變化點生成唯一哈希值標識:

add: 00110010
cos: 00010000
sin: 11000111
...

然後啓用二分搜索,利用 Hash-Based Bisect 工具依次禁用某些函數的優化,逐步縮小範圍。例如:

第一步:禁用add, cos, div, exp, mod,測試通過。
第二步:禁用mul, sin, sqr, sub, tan,測試失敗。
第三步:進一步細分,最終定位sin爲導致問題的函數。開發者只需檢查該函數的優化規則即可解決問題。

原文章中,Russ Cox 利用函數變化點哈希值的位後綴構建了一顆二叉樹 (如下圖),並利用後綴模式的不同進行問題定位:

圖來自 Russ Cox 博客

瞭解了大致的工作原理後,我們再來看看 Hash-Based Bisect 在 Go 項目中的使用現狀。

  1. Hash-Based Bisect 在 Go 項目中的使用現狀

目前 Hash-Based Bisect 已經成爲 Go 項目編譯器和運行時的重要調試工具之一,其工具鏈 (golang.org/x/tools/cmd/bisect) 和庫 (golang.org/x/tools/internal/bisect) 提供了強大的功能支持,幫助 Go 團隊在編譯器開發、運行時庫升級和語言特性修改等場景下快速定位問題。

Go 實現的 hash-based bisect 調試技術包含兩部分:

bisect 命令行工具可用於驅動測試運行(如 go test)並自動化調試過程,支持靈活的模式定義(如 - godebug、-compile 選項),結合用戶輸入定位問題點。

該包爲庫和工具開發者提供一個接口,輕鬆實現與 bisect 工具的集成。並且提供了哈希生成、啓用判斷和變化點報告等功能,適配複雜調試需求。

上述工具目前在 Go 編譯器的 SSA(靜態單賦值)後端開發、Go 運行時庫升級(比如 Go 1.23 的 Timer Stop/Reset 的新實現 [4])以及語言特性的修改(比如 loopvar 語義變更 [5])等方面都有重要的應用,大大提高了 Go 團隊在定位複雜問題時的調試效率。

以上工具和包在 Go 項目中已經演化多年,頗爲成熟。Russ Cox 已經發起提案 #67140[6],旨在將 golang.org/x/tools/internal/bisect 包發佈爲標準庫 debug/bisect 包,這樣編譯器、運行時、標準庫甚至標準庫之外的包都可以基於它提供的功能實現與 bisect 工具的兼容,並利用 bisect 工具實現基於變更點 hash 值的高級調試。

講到這裏,屏幕前的你是否已經感到 “迫不及待” 了呢?這樣優秀的工具!我們現在能否使用它?是否可以將其應用於我們自己的 Go 包的調試過程中呢?接下來,我就來用一個示例演示一下如何讓我們自己的包支持 Go bisect 工具,以幫助我們提升調試效率。

  1. 讓你的庫支持 Hash-Based Bisect 調試

要利用 bisect 調試技術,我們首先要解決的是 bisect 包位於 internal 中的問題,好在 Russ Cox 在實現 bisect 包時考慮了這個問題,bisect 包沒有任何外部依賴,連 Go 標準庫都不依賴,這樣避免了後續變爲 debug/bisect 後導致標準庫循環依賴的問題。現在,我們可以將它直接 copy 出來,放到我們自己的工程中使用。

下面是我準備的示例的目錄結構:

$tree -F hash-based-bisect/bisect-demo
hash-based-bisect/bisect-demo
├── bisect/
│   └── bisect.go
├── foo/
│   ├── foo.go
│   └── foo_test.go
└── go.mod

其中 bisect 目錄下的 bisect.go 來自 github.com/golang/tools/blob/master/internal/bisect/bisect.go,foo 包是我們這次要調試的目標包,我們先來看看 foo.go 的代碼:

// bisect-demo/foo/foo.go

package foo

import (
 "bisect-demo/bisect"
 "flag"
)

var (
 bisectFlag = flag.String("bisect""""bisect pattern")
 matcher    *bisect.Matcher
)

// Features represents different features that might cause issues
const (
 FeatureRangeIteration  = "range-iteration"  // Using range vs classic for loop
 FeatureConcurrentLogic = "concurrent-logic" // Adding concurrent modifications
)

func Init() {
 flag.Parse()
 if *bisectFlag != "" {
  matcher, _ = bisect.New(*bisectFlag)
 }
}

func ProcessItems(items []int) []int {
 result := make([]int, 0, len(items))

 // First potential problematic change: different iteration approach
 id1 := bisect.Hash(FeatureRangeIteration)
 if matcher == nil || matcher.ShouldEnable(id1) {
  if matcher != nil && matcher.ShouldReport(id1) {
   println(bisect.Marker(id1)"enabled feature:", FeatureRangeIteration)
  }
  // Potentially problematic implementation using range
  for i := range items {
   result = append(result, items[i]*2)
  }
 } else {
  // Correct implementation using value iteration
  for _, v := range items {
   result = append(result, v*2)
  }
 }

 // Second potential problematic change: concurrent modifications
 id2 := bisect.Hash(FeatureConcurrentLogic)
 if matcher == nil || matcher.ShouldEnable(id2) {
  if matcher != nil && matcher.ShouldReport(id2) {
   println(bisect.Marker(id2)"enabled feature:", FeatureConcurrentLogic)
  }
  // Potentially problematic implementation with concurrency
  for i := 0; i < len(result); i++ {
   go func(idx int) {
    result[idx] += 1 // Race condition
   }(i)
  }
 }

 return result
}

大家可以結合前面提及的 Hash-Based Bisect 的典型工作流程來理解上面的代碼。

首先,我們模擬可能導致問題的兩個功能特性並定義了變化點,變化點由特性標識符的 hash 值標識,這裏我們定義的特性標識符爲:

const (
    // 使用有意義的特性名稱作爲 hash 的輸入
    FeatureRangeIteration  = "range-iteration"  // 使用 range vs 經典 for 循環
    FeatureConcurrentLogic = "concurrent-logic" // 添加併發修改邏輯
)

接下來,對於每個可能有問題的變化點,都遵循相同的模式:

// 1. 計算特性的唯一Hash值
id1 := bisect.Hash(FeatureRangeIteration)

// 2. 檢查是否應該啓用該特性
if matcher == nil || matcher.ShouldEnable(id1) {
    // 3. 如果需要,報告該特性被啓用
    if matcher != nil && matcher.ShouldReport(id1) {
        println(bisect.Marker(id1)"enabled feature:", FeatureRangeIteration)
    }
    
    // 4. 執行可能有問題的實現
    for i := range items {
        result = append(result, items[i]*2)
    }
} else {
    // 5. 執行正確的實現
    for _, v := range items {
        result = append(result, v*2)
    }
}

這裏對 matcher == nil 的檢查算是一個小優化:當不在 bisect 調試模式時,matcher 爲 nil。此時我們直接啓用所有特性,不需要計算 hash 和調用其他方法。

代碼中的 ShouldEnable() 決定是否啓用該特性的代碼,ShouldReport() 決定是否需要報告該特性被啓用。這兩個可能返回不同的值,尤其是在 bisect 搜索最小失敗集合時。

Marker() 用於生成標準格式的匹配標記,這些標記會被 bisect 工具用來識別和追蹤啓用了哪些特性,標記會在最終輸出中被移除,只顯示實際的描述文本。

這裏還有一個接收 bisect pattern 的設置,我們是通過命令行參數來接收 bisect 每次傳給 foo 包的 Pattern 的,這裏我們在 Init 函數,而不是 init 函數中調用 Parse,是因爲如果在 init 函數中調用 Parse,會干擾 go test 測試框架,導致出現類似 “flag provided but not defined: -test.paniconexit0” 的測試執行錯誤。

下面是 foo_test.go 的代碼:

// bisect-demo/foo/foo_test.go

package foo

import (
 "flag"
 "testing"
 "time"
)

func TestMain(m *testing.M) {
 flag.Parse()
 Init()
 m.Run()
}

func TestProcessItems(t *testing.T) {
 input := []int{1, 2, 3, 4, 5}
 result := ProcessItems(input)

 // Wait for all goroutines to complete
 time.Sleep(1000 * time.Millisecond)

 // Verify results
 if len(result) != len(input) {
  t.Fatalf("got len=%d, want len=%d", len(result), len(input))
 }

 // Check if results are correct
 for i, v := range input {
  expected := v * 2
  if result[i] != expected {
   t.Errorf("result[%d] = %d, want %d", i, result[i], expected)
  }
 }
}

顯然爲了 foo 包能成功獲取命令行參數,我們重寫了 TestMain,在其中調用了 foo.Init 函數。

接下來,我們就來執行一下 bisect 工具,對 foo 包進行一下調試,你可以通過 go install golang.org/x/tools/cmd/bisect@latest 安裝 bisect。此外下面 bisect 命令行中的 PATTERN 是一個 “佔位符”,bisect 命令會識別該 “佔位符”,並將其替換爲相應的字符串,這個在 bisect 的執行過程中你也會看到:

// 在hash-based-bisect/bisect-demo/foo目錄下執行

$bisect -v go test -v -args -bisect=PATTERN
bisect: checking target with all changes disabled
bisect: run: go test -v -args -bisect=n... ok (0 matches)
bisect: matches:
bisect: run: go test -v -args -bisect=n... ok (0 matches)
bisect: matches:
bisect: checking target with all changes enabled
bisect: run: go test -v -args -bisect=y... FAIL (2 matches)
bisect: matches:
[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic
bisect: run: go test -v -args -bisect=y... FAIL (2 matches)
bisect: matches:
[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic
bisect: target succeeds with no changes, fails with all changes
bisect: searching for minimal set of enabled changes causing failure
bisect: run: go test -v -args -bisect=+0... ok (1 matches)
bisect: matches:
[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
bisect: run: go test -v -args -bisect=+0... ok (1 matches)
bisect: matches:
[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
bisect: run: go test -v -args -bisect=+1... FAIL (1 matches)
bisect: matches:
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic
bisect: run: go test -v -args -bisect=+1... FAIL (1 matches)
bisect: matches:
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic
bisect: confirming failing change set
bisect: run: go test -v -args -bisect=v+x3f... FAIL (1 matches)
bisect: matches:
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic
bisect: run: go test -v -args -bisect=v+x3f... FAIL (1 matches)
bisect: matches:
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic
bisect: FOUND failing change set
--- change set #1 (enabling changes causes failure)
enabled feature: concurrent-logic
---
bisect: checking for more failures
bisect: run: go test -v -args -bisect=-x3f... ok (1 matches)
bisect: matches:
[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
bisect: run: go test -v -args -bisect=-x3f... ok (1 matches)
bisect: matches:
[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
bisect: target succeeds with all remaining changes enabled

簡單解讀一下這個 bisect 調試過程的輸出。

bisect 執行分爲幾個階段:

首先用 - bisect=n 禁用所有變更進行測試 → 測試通過(ok) 然後用 - bisect=y 啓用所有變更進行測試 → 測試失敗(FAIL)

這表明程序在沒有任何變更時是正常的,但啓用所有變更後會失敗。

啓用所有變更時觀察到兩個特性:

[bisect-match 0xcf0b8943315a7804] enabled feature: range-iteration
[bisect-match 0x4d642a7960e4693f] enabled feature: concurrent-logic

測試 + 0(啓用第一個變更:range-iteration)→ 測試通過(ok) 測試 + 1(啓用第二個變更:concurrent-logic)→ 測試失敗(FAIL)

這個過程幫助定位到具體是哪個變更導致了失敗。

使用 v+x3f 模式再次確認 → 測試失敗(FAIL) 明確找到了導致失敗的變更集合:

--- change set #1 (enabling changes causes failure)
enabled feature: concurrent-logic
---

使用 - x3f 模式(禁用確認的問題變更)進行測試 → 測試通過(ok) 確認啓用其他所有變更(除了 concurrent-logic)時程序都能正常運行。

從中得出調試結論:bisect 工具成功定位到問題出在 concurrent-logic 特性上,range-iteration 特性是安全的,不會導致測試失敗。問題明確是在併發邏輯中的 “故意” 邏輯導致的,這符合我們的代碼實現中的預期問題(在 concurrent-logic 特性中,我們確實故意修改了數據)。

  1. 小結

在本文中,我們深入探討了 Hash-Based Bisect 這一先進的調試技術,特別是在 Go 語言項目中的應用。Hash-Based Bisect 通過爲代碼的變化點生成唯一的哈希值,結合二分搜索算法,幫助開發者快速定位複雜程序中的問題,超越傳統的 git bisect 方法。我們還詳細介紹了其工作原理、在 Go 項目中的現狀,以及如何將這一技術集成到自己的 Go 庫中,以提升調試效率。也許這裏的示例也許並不恰當,但已經達成了我向你展示如何使用 bisect 工具和 bisect 包的目的。

儘管 Hash-Based Bisect 在定位複雜問題上表現出色,但感覺其當前設計仍存在一些不足,這些不足可能會影響開發者的使用體驗,尤其是在將其集成到 Go 包或項目時,這個不足主要體現在對代碼的侵入性上。爲了支持 Hash-Based Bisect,Go 包需要顯式實現與 bisect 工具交互的協議,包括支持從命令行或環境變量接收 bisect 傳入的模式 (pattern);需要在代碼中創建 bisect.Matcher 對象,並調用 ShouldEnable 和 ShouldReport 接口來管理變化點;代碼中必須爲潛在變化點顯式生成唯一的哈希值,並根據需要啓用或禁用。

這種顯式集成導致代碼邏輯被調試相關代碼 “污染”,增加了代碼複雜度和維護成本。對於一些簡單的庫或項目,開發者可能不願爲調試需求增加這種負擔。

在 $GOROOT/src/cmd/compile/internal/base 中,編譯器相關代碼就將 bisect 封裝到了一個 HashDebug 結構中,一定程度上減少了代碼的侵入深度以及手動集成的工作量。

此外,golang.org/x/tools/internal/bisect 包尚未正式變爲 debug/bisect,後續其 API 是否會發生變化,尚不得而知,本文中的示例代碼不保證在後續的 Go 版本調整後依然能夠正確運行。

本文涉及的源碼可以在這裏 [7] 下載。

  1. 參考資料

  1. 附錄:git bisect 使用示例

假設你有一個 Go 語言項目,並且發現最近的某次提交引入了一個問題(例如,某個測試用例失敗了)。你希望使用 git bisect 找到引入該問題的具體提交。

你的項目目錄設計如下:

my-go-project/
├── main.go
└── main_test.go

我們來建立這個示例項目:

// 在hash-based-bisect/git-bisect下面執行
$mkdir my-go-project
$cd my-go-project
$git init

創建 main.go:

// main.go
package main

func main() {
    println("Hello, world!")
}

func Add(a, b int) int {
    return a + b
}

提交變更:

$git add main.go
git commit -m "Initial commit with Add function"
[master (root-commit) 16f8736] Initial commit with Add function
 1 file changed, 9 insertions(+)
 create mode 100644 main.go

創建 main_test.go:

// main_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    if Add(2, 3) != 5 {
        t.Error("Expected 5, got something else")
    }
}

提交變更:

$git add main_test.go
git commit -m "Add test for Add function"
[master b7b3c44] Add test for Add function
 1 file changed, 9 insertions(+)
 create mode 100644 main_test.go

故意引入一個 bug 並提交變更:

$sed -i 's/return a + b/return a - b/' main.go
$git commit -am "Introduce a bug in Add function"
[master 977e647] Introduce a bug in Add function
 1 file changed, 1 insertion(+), 1 deletion(-)

添加一些其他提交(無關的變更):

$echo "// Just a comment" >> main.go
$git commit -am "Add a comment"
[master 25f88b0] Add a comment
 1 file changed, 2 insertions(+)

這裏列出上面所有 commit 的 list,便於後續對照:

$git log --oneline
25f88b0 (HEAD -> master) Add a comment
977e647 Introduce a bug in Add function
b7b3c44 Add test for Add function
16f8736 Initial commit with Add function

接下來,我們就可以演示 git bisect 了,先來演示一下手工 bisect。

啓動 git bisect 模式:

$git bisect start

標記當前最新提交爲 bad:

$git bisect bad

標記首次提交爲 good:

$git bisect good 16f8736
Bisecting: 0 revisions left to test after this (roughly 1 step)
[977e647e7461c4c03ee25e53728dd743af925f17] Introduce a bug in Add function

我們看到 git bisect 自動切換到一箇中間的提交,我們需要驗證這次中間提交是否能通過測試:

$go test  
--- FAIL: TestAdd (0.00s)
    main_test.go:7: Expected 5, got something else
FAIL
exit status 1
FAIL github.com/bigwhite/experiments/hash-based-bisect/git-bisect/my-go-project 0.006s

測試失敗,我們將該提交標記爲 bad:

$git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[b7b3c444f0fd55086e6ce36fb543a136a1611b61] Add test for Add function

git bisect 又切換到了另外一箇中間提交,我們用 go test 驗證是否能通過:

$go test 
PASS
ok   github.com/bigwhite/experiments/hash-based-bisect/git-bisect/my-go-project 0.005s

測試通過,我們將這個中間提交標記爲 good:

$git bisect good
977e647e7461c4c03ee25e53728dd743af925f17 is the first bad commit
commit 977e647e7461c4c03ee25e53728dd743af925f17
Author: Tony Bai <bigwhite.cn@aliyun.com>
Date:   Fri Nov 24 13:27:08 2024 +0800

    Introduce a bug in Add function

:100644 100644 e357c05d933724eb8b7c1aafee34b8f95913355e e65baa0414a2a1f983379c23ac549b7d8b056db3 M main.go

我們看到:git bisect 找到了一個 bad commit,並顯示 “977e647e7461c4c03ee25e53728dd743af925f17 is the first bad commit”。

結束 git bisect 模式:

$git bisect reset

上面的過程可以使用 git bisect run 進行自動化,而無需中間手動多次的執行 go test 和標記,下面是一個等價的 git bisect 過程:

$git bisect start

$git bisect bad

$git bisect good 16f8736
Bisecting: 0 revisions left to test after this (roughly 1 step)
[977e647e7461c4c03ee25e53728dd743af925f17] Introduce a bug in Add function

$git bisect run go test
running go test
--- FAIL: TestAdd (0.00s)
    main_test.go:7: Expected 5, got something else
FAIL
exit status 1
FAIL github.com/bigwhite/experiments/hash-based-bisect/git-bisect/my-go-project 0.006s
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[b7b3c444f0fd55086e6ce36fb543a136a1611b61] Add test for Add function
running go test
PASS
ok   github.com/bigwhite/experiments/hash-based-bisect/git-bisect/my-go-project 0.006s
977e647e7461c4c03ee25e53728dd743af925f17 is the first bad commit
commit 977e647e7461c4c03ee25e53728dd743af925f17
Author: Tony Bai <bigwhite.cn@aliyun.com>
Date:   Fri Nov 24 13:27:08 2024 +0800

    Introduce a bug in Add function

:100644 100644 e357c05d933724eb8b7c1aafee34b8f95913355e e65baa0414a2a1f983379c23ac549b7d8b056db3 M main.go
bisect run success

$git bisect reset
Previous HEAD position was b7b3c44 Add test for Add function
Switched to branch 'master'

我們看到通過 git bisect run 可以更快速地定位問題,而無需中間的手工操作,這是我們日常開發中主要使用的 bisect 手段!


參考資料

[1] 

Hash-Based Bisect Debugging in Compilers and Runtimes: https://research.swtch.com/bisect

[2] 

hash-based bisect 工具: https://github.com/golang/tools/tree/master/cmd/bisect

[3] 

bisect 命令行工具: https://github.com/golang/tools/tree/master/cmd/bisect

[4] 

Go 1.23 的 Timer Stop/Reset 的新實現: https://github.com/golang/go/issues/37196

[5] 

loopvar 語義變更: https://tonybai.com/2023/08/20/some-changes-in-go-1-21

[6] 

Russ Cox 已經發起提案 #67140: https://github.com/golang/go/issues/67140

[7] 

這裏: https://github.com/bigwhite/experiments/tree/master/hash-based-bisect

[8] 

Hash-Based Bisect Debugging in Compilers and Runtimes: https://research.swtch.com/bisect

[9] 

proposal: debug/bisect: publish x/tools/internal/bisect: https://github.com/golang/go/issues/67140

[10] 

golang.org/x/tools/internal/bisect package: https://pkg.go.dev/golang.org/x/tools/internal/bisect

[11] 

Hacker News- Hash-based bisect debugging in compilers and runtimes: https://news.ycombinator.com/item?id=40995982


Gopher Daily(Gopher 每日新聞) - https://gopherdaily.tonybai.com

我的聯繫方式:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uGM0xBngd2qTX9nyrcrVOQ