自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

使用 gdb 工具調(diào)試 Go

開發(fā) 開發(fā)工具
排除應(yīng)用程序故障是比較復(fù)雜的,特別是處理像 Go 這樣的高并發(fā)語言。它更容易在具體位置使用 print 打印語句來確定程序狀態(tài),但是這個(gè)方法很難根據(jù)條件發(fā)展去動(dòng)態(tài)響應(yīng)你的代碼。

排除應(yīng)用程序故障是比較復(fù)雜的,特別是處理像 Go 這樣的高并發(fā)語言。它更容易在具體位置使用 print 打印語句來確定程序狀態(tài),但是這個(gè)方法很難根據(jù)條件發(fā)展去動(dòng)態(tài)響應(yīng)你的代碼。

調(diào)試器提供了一個(gè)強(qiáng)大得令人難以置信的故障排除機(jī)制。添加排除故障的代碼可以巧妙地影響到應(yīng)用程序該如何運(yùn)行。調(diào)試器可以給正在迷茫的你更精確的看法。

[[145285]]

已經(jīng)有許多 Go 的調(diào)試器存在了,其中一些調(diào)試器的不好之處是通過在編譯時(shí)注入代碼來提供一個(gè)交互終端。gdb 調(diào)試器則允許你調(diào)試已經(jīng)編譯好的二進(jìn)制文件,只要他們已經(jīng)與 debug 信息連接,并不用修改源代碼。這是個(gè)相當(dāng)不錯(cuò)的特性,因此你可以從你的部署環(huán)境中取一個(gè)產(chǎn)品然后靈活地調(diào)試它。你可以從Golang 官方文檔中閱讀更多關(guān)于 gdb 的信息,那么這篇指南將簡單講解使用 gdb 調(diào)試器來調(diào)試 Go 應(yīng)用程序的基本用法。

這兒會(huì)宣布一些 gdb 的***更新,最特別的是替換 -> 操作為 . 符號來訪問對象屬性。記住這兒可能在gdb 和 Go 版本中有細(xì)微改變。本篇指南基于 gdb 7.7.1和go 1.5beta2。

開始 gdb 調(diào)試

為了實(shí)驗(yàn) gdb 我使用了一個(gè)測試程序,完整的源代碼可以在gdb_sandbox_on_Github上查看。讓我們從一個(gè)非常簡單的程序開始吧:

 

  1. package main 
  2.  
  3. import (  
  4.     "fmt"  
  5.  
  6. func main() {  
  7.     for i := 0; i < 5; i++ { 
  8.         fmt.Println("looping")  
  9.     }  
  10.     fmt.Println("Done")  

我們可以運(yùn)行這段代碼并看到它輸出內(nèi)容的和我們想象的一樣:

  1. $ go run main.go 
  2. looping 
  3. looping 
  4. looping 
  5. looping 
  6. looping 
  7. Done 

我們來調(diào)試這個(gè)程序吧。首先,使用 go build 編譯成二進(jìn)制文件,接著使用這個(gè)二進(jìn)制文件的路徑做為參數(shù)運(yùn)行 gdb。根據(jù)你的設(shè)定,你也可以使用 source 命令來獲取 Go 運(yùn)行時(shí)(Go runtime)的支持?,F(xiàn)在我們已經(jīng)在 gdb 的命令行中了,我們可以在運(yùn)行我們的二進(jìn)制文件前為它設(shè)置斷點(diǎn)。

  1. $ go build -gcflags "-N -l" -o gdb_sandbox main.go  
  2. $ ls 
  3. gdb_sandbox  main.go  README.md 
  4. $ gdb gdb_sandbox 
  5. .... 
  6. (gdb) source /usr/local/src/go/src/runtime/runtime-gdb.py 
  7. Loading Go Runtime support. 

***關(guān),我們在 for 循環(huán)里面設(shè)置一個(gè)斷點(diǎn)(b)來查看執(zhí)行每次循環(huán)時(shí)我們的代碼會(huì)各有什么狀態(tài)。我們可以使用print(p)命令來檢查當(dāng)前內(nèi)容的一個(gè)變量,還有 list(l)和 backtrace(bt)命令查看當(dāng)前步驟周圍的代碼。程序運(yùn)行時(shí)可以使用 next(n)執(zhí)行下一步或者使用 breakpoint(c)執(zhí)行到下一個(gè)斷點(diǎn)。

  1. (gdb) b main.go:9  
  2. Breakpoint 1 at 0x400d35: file /home/bfosberry/workspace/gdb_sandbox/main.go, line 9.  
  3. (gdb) run  
  4. Starting program: /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandbox Breakpoint 1, main.main () at  
  5. /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:9  
  6. 9         fmt.Println("looping")  
  7. (gdb) l  
  8. 4         "fmt"  
  9. 5         )  
  10. 6   
  11. 7 func main() { 
  12. 8         for i := 0; i < 5; i++ {  
  13. 9         fmt.Println("looping")  
  14. 10        }`  
  15. 11        fmt.Println("Done")  
  16. 12 }  
  17. (gdb) p i  
  18. $1 = 0  
  19. (gdb) n  
  20. looping  
  21. Breakpoint 1, main.main () at  
  22. /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:9  
  23. 9        fmt.Println("looping")  
  24. (gdb) p i  
  25. $2 = 1  
  26. (gdb) bt 
  27. 0 main.main () at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:9 

我們的斷點(diǎn)可以設(shè)置在關(guān)聯(lián)文件的行號中、GOPATH里的文件的行號或一個(gè)包里的函數(shù)。如下也是一個(gè)有效的斷點(diǎn):

  1. (gdb) b github.com/bfosberry/gdb_sandbox/main.go:9 
  2. (gdb) b 'main.main' 

Structs

我們可以用稍微復(fù)雜一點(diǎn)的代碼來實(shí)例演示如何調(diào)試。我們將使用f函數(shù)生成一個(gè)簡單的pair,x和y,當(dāng)x相等時(shí)y=f(x),否則=x。

 

  1. type pair struct {  
  2.     x int  
  3.     y int  
  4.  
  5. func handleNumber(i int) *pair {  
  6.     val := i  
  7.     if i%2 == 0 {  
  8.         val = f(i)  
  9.     }  
  10.     return &pair{  
  11.        x: i,  
  12.        y: val,  
  13.     }  
  14.  
  15. func f(int x) int {  
  16.     return x*x + x  

也可以在循環(huán)中改變代碼來訪問這些新函數(shù)。

  1. p := handleNumber(i) 
  2. fmt.Printf("%+v/n", p) 
  3. fmt.Println("looping"

因?yàn)槲覀冃枰{(diào)試的是變量 y。我們可以在y被設(shè)置的地方放置斷點(diǎn)然后單步執(zhí)行。可以使用 info args 查看函數(shù)的參數(shù),在 bt 之前可以返回當(dāng)前回溯。

 

  1. (gdb) b 'main.f'  
  2. (gdb) run  
  3. Starting program: /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandbox 
  4.  
  5. Breakpoint 1, main.f (x=0, ~anon1=833492132160)  
  6.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:33  
  7. 33       return x*x + x  
  8. (gdb) info args  
  9. x = 0  
  10. (gdb) continue  
  11. Breakpoint 1, main.f (x=0, ~anon1=833492132160)  
  12.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:33  
  13. 33       return x*x + x  
  14. (gdb) info args  
  15. x = 2  
  16. (gdb) bt 
  17. #0 main.f (x=2, ~anon1=1)  
  18.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:33 
  19. #1 0x0000000000400f0e in main.handleNumber (i=2, ~anon1=0x1
  20.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:24 
  21. #2 0x0000000000400c47 in main.main () 
  22.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:14 

因?yàn)槲覀冊谧兞?y 是在函數(shù) f 中被設(shè)定的這樣一個(gè)條件下,我們可以跳到這個(gè)函數(shù)的上下文并檢查堆區(qū)的代碼。應(yīng)用運(yùn)行時(shí)我們可以在一個(gè)更高的層次上設(shè)置斷點(diǎn)并檢查其狀態(tài)。

  1. (gdb) b main.go:26  
  2. Breakpoint 2 at 0x400f22: file  
  3. /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go, line 26.  
  4. (gdb) continue  
  5. Continuing. 
  6. Breakpoint 2, main.handleNumber (i=2, ~anon1=0x1)  
  7.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:28  
  8. 28             y: val,  
  9. (gdb) l  
  10. 23         if i%2 == 0 {  
  11. 24             val = f(i)  
  12. 25         }  
  13. 26         return &pair{  
  14. 27             x: i,  
  15. 28             y: val,  
  16. 29         }  
  17. 30     }  
  18. 31   
  19. 32 func f(x intint {  
  20. (gdb) p val  
  21. $1 = 6  
  22. (gdb) p i  
  23. $2 = 2 

如果我們在這個(gè)斷點(diǎn)處繼續(xù)住下走我們將越過在這個(gè)函數(shù)中的斷點(diǎn)1,而且將立即觸發(fā)在 HandleNumer 函數(shù)中的斷點(diǎn),因?yàn)楹瘮?shù) f 只是對變量 i 每隔一次才執(zhí)行。我們可以通過暫時(shí)使斷點(diǎn) 2不工作來避免這種情況的發(fā)生。

  1. (gdb) disable breakpoint 2  
  2. (gdb) continue  
  3. Continuing.  
  4. &{x:2 y:6}  
  5. looping  
  6. &{x:3 y:3}  
  7. looping  
  8. [New LWP 15200]  
  9. [Switching to LWP 15200
  10. Breakpoint 1, main.f (x=4, ~anon1=1)  
  11.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:33  
  12. 33         return x*x + x  
  13. (gdb) 

我們也可以分別使用 clear 和 delete breakpoint NUMBER 來清除和刪除斷點(diǎn)。動(dòng)態(tài)產(chǎn)生和系住斷點(diǎn),我們可以有效地在應(yīng)用流中來回移動(dòng)。

Slices and Pointers

上例程序太簡單了,只用到了整數(shù)型和字符串,所以我們將寫一個(gè)稍微復(fù)雜一點(diǎn)的。首先添加一個(gè)slice(切片類型)的指針到 main 函數(shù),并保存生成的 pair,我們后面將用到它。

  1. var pairs []*pair 
  2. for i := 0; i < 10; i++ { 
  3.     p := handleNumber(i) 
  4.     fmt.Printf("%+v/n", p) 
  5.     pairs = append(pairs, p) 
  6.     fmt.Println("looping"
  7.     } 

現(xiàn)在我們來檢查生成出來的 slice 或 pairs,首先我們用轉(zhuǎn)換成數(shù)組來看一下這個(gè) slice。因?yàn)?handleNumber 返回的是一個(gè) *pair 類型,我們需要引用這個(gè)指針來訪問 struct(結(jié)構(gòu))的屬性。

 

  1. (gdb) b main.go:18  
  2. Breakpoint 1 at 0x400e14: file /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go, line 18.  
  3. (gdb) run  
  4. Starting program: /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandbox &{x:0 y:0
  5.  
  6. Breakpoint 1, main.main () at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:18  
  7. 18         fmt.Println("looping")  
  8. (gdb) p pairs  
  9. $1 = []*main.pair = {0xc82000a3a0}  
  10. (gdb) p pairs[0]  
  11. Structure has no component named operator[].  
  12. (gdb) p pairs.array  
  13. $2 = (struct main.pair **) 0xc820030028  
  14. (gdb) p pairs.array[0]  
  15. $3 = (struct main.pair *) 0xc82000a3a0  
  16. (gdb) p *pairs.array[0]  
  17. $4 = {x = 0, y = 0}  
  18. (gdb) p (*pairs.array[0]).x  
  19. $5 = 0  
  20. (gdb) p (*pairs.array[0]).y  
  21. $6 = 0  
  22. (gdb) continue  
  23. Continuing.  
  24. looping  
  25. &{x:1 y:1
  26.  
  27. Breakpoint 1, main.main () at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:18  
  28. 18         fmt.Println("looping")  
  29. (gdb) p (pairs.array[1][5]).y  
  30. $7 = 1  
  31. (gdb) continue  
  32. Continuing.  
  33. looping  
  34. &{x:2 y:6
  35.  
  36. Breakpoint 1, main.main () at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:18  
  37. 18         fmt.Println("looping")  
  38. (gdb) p (pairs.array[2][6]).y  
  39. $8 = 6  
  40. (gdb) 

你會(huì)發(fā)現(xiàn)這里 gdb 并不確定 pairs 是一個(gè) slice 類型,我們不能直接訪問它的屬性,為了訪問它的成員我們需要使用 pairs.array 來轉(zhuǎn)換成數(shù)組,然后我們就可以檢查 slice 的 length(長度)和 capacity(容量):

  1. (gdb) p $len(pairs) 
  2. $12 = 3 
  3. (gdb) p $cap(pairs) 
  4. $13 = 4 

這時(shí)我們可以讓它循環(huán)幾次,并透過這個(gè) slice 不用的成員方法監(jiān)聽增加的 xy 的值,要注意的是,這里的 struct 屬性可以通過指針訪問,所以 p pairs.array[2].y 一樣可行。

Goroutines

現(xiàn)在我們已經(jīng)可以訪問 struct 和 slice 了,下面再來更加復(fù)雜一點(diǎn)的程序吧。讓我們添加一些goroutines 到 mian 函數(shù),并行處理每一個(gè)數(shù)字,返回的結(jié)果存入信道(chan)中:

 

  1.     pairs := []*pair{} 
  2.     pairChan := make(chan *pair) 
  3.     wg := sync.WaitGroup{} 
  4.         for i := 0; i < 10; i++ { 
  5.           wg.Add(1
  6.           go func(val int) { 
  7.             p := handleNumber(val) 
  8.             fmt.Printf("%+v/n", p) 
  9.             pairChan <- p 
  10.             wg.Done() 
  11.             }(i) 
  12.     } 
  13.     go func() { 
  14.             for p := range pairChan { 
  15.               pairs = append(pairs, p) 
  16.             } 
  17.     }() 
  18.     wg.Wait() 
  19.     close(pairChan) 
  20.  
  21. 如果我等待 WaitGroup 執(zhí)行完畢再檢查 pairs slice 的結(jié)果,我們可以預(yù)期到內(nèi)容是完全相同的,雖然它的排序可能有些出入。gdb 真正的威力來自于它可以在 goroutines 正在運(yùn)行時(shí)進(jìn)行檢查: 
  22.  
  23. (gdb) b main.go:43  
  24. Breakpoint 1 at 0x400f7f: file /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go, line 43.  
  25. (gdb) run  
  26. Starting program: /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/gdb_sandbox 
  27.  
  28. Breakpoint 1, main.handleNumber (i=0, ~r1=0x0)  
  29.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:43  
  30. 43         y: val,  
  31. (gdb) l  
  32. 38     if i%2 == 0 {  
  33. 39         val = f(i)  
  34. 40     }  
  35. 41     return &pair{  
  36. 42         x: i,  
  37. 43         y: val,  
  38. 44     }  
  39. 45 }  
  40. 46   
  41. 47 func f(x intint {  
  42. (gdb) info args  
  43. i = 0  
  44. ~r1 = 0x0  
  45. (gdb) p val  
  46. $1 = 0 

你會(huì)發(fā)現(xiàn)我們在 goroutine 要執(zhí)行的代碼段中放置了一個(gè)斷點(diǎn),從這里我們可以檢查到局部變量,和進(jìn)程中的其它 goroutines:

  1. (gdb) info goroutines  
  2.   1 waiting runtime.gopark  
  3.   2 waiting runtime.gopark  
  4.   3 waiting runtime.gopark  
  5.   4 waiting runtime.gopark  
  6. 5 running main.main.func1  
  7.   6 runnable main.main.func1  
  8.   7 runnable main.main.func1  
  9.   8 runnable main.main.func1  
  10.   9 runnable main.main.func1  
  11. 10 running main.main.func1  
  12.   11 runnable main.main.func1  
  13.   12 runnable main.main.func1  
  14.   13 runnable main.main.func1  
  15.   14 runnable main.main.func1  
  16.   15 waiting runtime.gopark  
  17. (gdb) goroutine 11 bt 
  18. #0 main.main.func1 (val=6, pairChan=0xc82001a180, &wg=0xc82000a3a0
  19.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:19 
  20. #1 0x0000000000454991 in runtime.goexit () at /usr/local/go/src/runtime/asm_amd64.s:1696 
  21. #2 0x0000000000000006 in ?? () 
  22. #3 0x000000c82001a180 in ?? () 
  23. #4 0x000000c82000a3a0 in ?? () 
  24. #5 0x0000000000000000 in ?? () 
  25. (gdb) goroutine 11 l  
  26. 48         return x*x + x  
  27. 49     }  
  28. (gdb) goroutine 11 info args  
  29. val = 6  
  30. pairChan = 0xc82001a180  
  31. &wg = 0xc82000a3a0  
  32. (gdb) goroutine 11 p val  
  33. $2 = 6 

在這里我們做的***件事就是列出所有正在運(yùn)行的 goroutine,并確定我們正在處理的那一個(gè)。然后我們可以看到一些回溯,并發(fā)送任何調(diào)試命令到 goroutine。這個(gè)回溯和列表清單并不太準(zhǔn)確,如何讓回溯更準(zhǔn)確,goroutine 上的 info args 顯示了我們的局部變量,以及主函數(shù)中的可用變量,goroutine 函數(shù)之外的使用前綴&。

結(jié)論

當(dāng)調(diào)試應(yīng)用時(shí),gdb 的強(qiáng)大令人難以置信。但它仍然是一個(gè)相當(dāng)新的事物,并不是所有的地方工作地都很***。使用***的穩(wěn)定版 gdb,go 1.5 beta2,有不少地方有突破:

Interfaces

根據(jù) go 博客上的文章, go 的 interfaces 應(yīng)該已經(jīng)支持了,這允許在 gdb 中動(dòng)態(tài)的投影其基類型。這應(yīng)該算一個(gè)突破。

Interface{} 類型

目前沒有辦法轉(zhuǎn)換 interface{} 為它的類型。

列出 goroutine 的不同點(diǎn)

在其他 goroutine 中列出周邊代碼會(huì)導(dǎo)致一些行數(shù)的漂移,最終導(dǎo)致 gdb 認(rèn)為當(dāng)前的行數(shù)超出文件范圍并拋出一個(gè)錯(cuò)誤:

  1. (gdb) info goroutines  
  2.   1 waiting runtime.gopark  
  3.   2 waiting runtime.gopark  
  4.   3 waiting runtime.gopark  
  5.   4 waiting runtime.gopark  
  6. 5 running main.main.func1  
  7.   6 runnable main.main.func1  
  8.   7 runnable main.main.func1  
  9.   8 runnable main.main.func1  
  10.   9 runnable main.main.func1  
  11. 10 running main.main.func1  
  12.   11 runnable main.main.func1  
  13.   12 runnable main.main.func1  
  14.   13 runnable main.main.func1  
  15.   14 runnable main.main.func1  
  16.   15 waiting runtime.gopark  
  17. (gdb) goroutine 11 bt 
  18. #0 main.main.func1 (val=6, pairChan=0xc82001a180, &wg=0xc82000a3a0
  19.     at /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go:19 
  20. #1 0x0000000000454991 in runtime.goexit () at /usr/local/go/src/runtime/asm_amd64.s:1696 
  21. #2 0x0000000000000006 in ?? () 
  22. #3 0x000000c82001a180 in ?? () 
  23. #4 0x000000c82000a3a0 in ?? () 
  24. #5 0x0000000000000000 in ?? () 
  25. (gdb) goroutine 11 l  
  26. 48         return x*x + x  
  27. 49     }  
  28. (gdb) goroutine 11 l  
  29. Python Exception <class 'gdb.error'> Line number 50 out of range; /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go has 49 lines.:  
  30. Error occurred in Python command: Line number 50 out of range; /home/bfosberry/.go/src/github.com/bfosberry/gdb_sandbox/main.go has 49 lines. 

Goroutine 調(diào)試還不穩(wěn)定

處理 goroutines 往往不穩(wěn)定;我遇到過執(zhí)行簡單命令產(chǎn)生錯(cuò)誤的情況?,F(xiàn)階段你應(yīng)該做好處理類似問題的準(zhǔn)備。

gdb 支持 Go 的配置非常麻煩

運(yùn)行 gdb 支持 Go 調(diào)試的配置非常麻煩,獲取正確的路徑結(jié)合與構(gòu)建 flags,還有 gdb 自動(dòng)加載功能好像都不能正常的工作。首先,通過一個(gè) gdb 初始化文件加載 Go 運(yùn)行時(shí)支持就會(huì)產(chǎn)生初始化錯(cuò)誤。這就需要手動(dòng)通過一個(gè)源命令去加載,調(diào)試 shell 需要像指南里面描述的那樣去進(jìn)行初始化。

我什么時(shí)候該使用一個(gè)調(diào)試器?

所以什么情況下使用 gdb 更有用?使用 print 語言和調(diào)試代碼是更有針對性的方法。

  • 當(dāng)不適合修改代碼的時(shí)候

  • 當(dāng)調(diào)試一個(gè)問題,但是不知道源頭,動(dòng)態(tài)斷點(diǎn)或許更有效

  • 當(dāng)包含許多 goroutines 時(shí),暫停然后審查程序狀態(tài)會(huì)更好

“Debugging #golang with gdb” – via @codeship —— from Tweet

 

責(zé)任編輯:王雪燕 來源: oschina
相關(guān)推薦

2021-07-28 08:53:53

GoGDB調(diào)試

2021-03-15 06:23:40

GDB調(diào)試代碼編程語言

2025-03-31 03:25:00

2010-06-04 17:48:20

Linux編程工具

2022-07-25 07:57:19

工具代碼調(diào)試

2017-02-06 18:42:37

Linuxgdb程序

2023-03-29 08:18:16

Go調(diào)試工具

2021-06-04 05:18:29

ARM程序Gdbserver

2024-02-26 00:02:00

開發(fā)Go

2020-07-10 16:52:43

DelveGo程序開源

2016-03-29 10:32:34

2022-09-15 14:56:12

GDB調(diào)試鴻蒙

2023-11-22 13:13:54

多線程死鎖

2010-03-26 15:41:39

Python腳本

2022-12-19 10:10:07

GDB命令

2021-07-26 10:14:38

Go語言工具

2016-12-02 20:23:51

AndroidADB

2018-11-27 11:35:32

systemtapMySQL調(diào)試工具

2019-12-06 14:30:41

GNU調(diào)試器GDB修復(fù)代碼

2021-07-05 11:00:43

GDB??臻g編程語言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號