新聞中心
Delve 是能讓調(diào)試變成輕而易舉的事的萬能工具包。
十年的界首網(wǎng)站建設(shè)經(jīng)驗(yàn),針對設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。營銷型網(wǎng)站建設(shè)的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整界首建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。成都創(chuàng)新互聯(lián)從事“界首網(wǎng)站設(shè)計(jì)”,“界首網(wǎng)站推廣”以來,每個客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
你上次嘗試去學(xué)習(xí)一種新的編程語言時什么時候?你有沒有持之以恒,你是那些在新事物發(fā)布的第一時間就勇敢地去嘗試的一員嗎?不管怎樣,學(xué)習(xí)一種新的語言也許非常有用,也會有很多樂趣。
你嘗試著寫簡單的 “Hello, world!”,然后寫一些示例代碼并執(zhí)行,繼續(xù)做一些小的修改,之后繼續(xù)前進(jìn)。我敢保證我們都有過這個經(jīng)歷,不論我們使用哪種技術(shù)。假如你嘗試用一段時間一種語言,并且你希望能夠精通它,那么有一些事物能在你的進(jìn)取之路上幫助你。
其中之一就是調(diào)試器。有些人喜歡在代碼中用簡單的 “print” 語句進(jìn)行調(diào)試,這種方式很適合代碼量少的簡單程序;然而,如果你處理的是有多個開發(fā)者和幾千行代碼的大型項(xiàng)目,你應(yīng)該使用調(diào)試器。
最近我開始學(xué)習(xí) Go 編程語言了,在本文中,我們將探討一種名為 Delve 的調(diào)試器。Delve 是專門用來調(diào)試 Go 程序的工具,我們會借助一些 Go 示例代碼來了解下它的一些功能。不要擔(dān)心這里展示的 Go 示例代碼;即使你之前沒有寫過 Go 代碼也能看懂。Go 的目標(biāo)之一是簡單,因此代碼是始終如一的,理解和解釋起來都很容易。
Delve 介紹
Delve 是托管在 GitHub 上的一個開源項(xiàng)目。
它自己的文檔中寫道:
Delve 是 Go 編程語言的調(diào)試器。該項(xiàng)目的目標(biāo)是為 Go 提供一個簡單、全功能的調(diào)試工具。Delve 應(yīng)該是易于調(diào)用和易于使用的。當(dāng)你使用調(diào)試器時,事情可能不會按你的思路運(yùn)行。如果你這樣想,那么你不適合用 Delve。
讓我們來近距離看一下。
我的測試系統(tǒng)是運(yùn)行著 Fedora Linux 的筆記本電腦,Go 編譯器版本如下:
$ cat /etc/fedora-releaseFedora release 30 (Thirty)$$ go versiongo version go1.12.17 linux/amd64$
Golang 安裝
如果你沒有安裝 Go,你可以運(yùn)行下面的命令,很輕松地就可以從配置的倉庫中獲取。
$ dnf install golang.x86_64
或者,你可以在安裝頁面找到適合你的操作系統(tǒng)的其他安裝版本。
在開始之前,請先確認(rèn)已經(jīng)設(shè)置好了 Go 工具依賴的下列各個路徑。如果這些路徑?jīng)]有設(shè)置,有些示例可能不能正常運(yùn)行。你可以在 SHELL 的 RC 文件中輕松設(shè)置這些環(huán)境變量,我的機(jī)器上是在 $HOME/bashrc 文件中設(shè)置的。
$ go env | grep GOPATHGOPATH="/home/user/go"$$ go env | grep GOBINGOBIN="/home/user/go/gobin"$
Delve 安裝
你可以像下面那樣,通過運(yùn)行一個簡單的 go get 命令來安裝 Delve。go get 是 Golang 從外部源下載和安裝需要的包的方式。如果你安裝過程中遇到了問題,可以查看 Delve 安裝教程。
$ go get -u github.com/go-delve/delve/cmd/dlv$
運(yùn)行上面的命令,就會把 Delve 下載到你的 $GOPATH 的位置,如果你沒有把 $GOPATH 設(shè)置成其他值,那么默認(rèn)情況下 $GOPATH 和 $HOME/go 是同一個路徑。
你可以進(jìn)入 go/ 目錄,你可以在 bin/ 目錄下看到 dlv。
$ ls -l $HOME/gototal 8drwxrwxr-x. 2 user user 4096 May 25 19:11 bindrwxrwxr-x. 4 user user 4096 May 25 19:21 src$$ ls -l ~/go/bin/total 19596-rwxrwxr-x. 1 user user 20062654 May 25 19:17 dlv$
因?yàn)槟惆?Delve 安裝到了 $GOPATH,所以你可以像運(yùn)行普通的 shell 命令一樣運(yùn)行它,即每次運(yùn)行時你不必先進(jìn)入它所在的目錄。你可以通過 version 選項(xiàng)來驗(yàn)證 dlv 是否正確安裝。示例中安裝的版本是 1.4.1。
$ which dlv~/go/bin/dlv$$ dlv versionDelve DebuggerVersion: 1.4.1Build: $Id: bda606147ff48b58bde39e20b9e11378eaa4db46 $$
現(xiàn)在,我們一起在 Go 程序中使用 Delve 來理解下它的功能以及如何使用它們。我們先來寫一個 hello.go,簡單地打印一條 Hello, world! 信息。
記著,我把這些示例程序放到了 $GOBIN 目錄下。
$ pwd/home/user/go/gobin$$ cat hello.gopackage mainimport "fmt"func main() {fmt.Println("Hello, world!")}$
運(yùn)行 build 命令來編譯一個 Go 程序,它的輸入是 .go 后綴的文件。如果程序沒有語法錯誤,Go 編譯器把它編譯成一個二進(jìn)制可執(zhí)行文件。這個文件可以被直接運(yùn)行,運(yùn)行后我們會在屏幕上看到 Hello, world! 信息。
$ go build hello.go$$ ls -l hello-rwxrwxr-x. 1 user user 1997284 May 26 12:13 hello$$ ./helloHello, world!$
在 Delve 中加載程序
把一個程序加載進(jìn) Delve 調(diào)試器有兩種方式。
在源碼編譯成二進(jìn)制文件之前使用 debug 參數(shù)
第一種方式是在需要時對源碼使用 debug 命令。Delve 會為你編譯出一個名為 __debug_bin 的二進(jìn)制文件,并把它加載進(jìn)調(diào)試器。
在這個例子中,你可以進(jìn)入 hello.go 所在的目錄,然后運(yùn)行 dlv debug 命令。如果目錄中有多個源文件且每個文件都有自己的主函數(shù),Delve 則可能拋出錯誤,它期望的是單個程序或從單個項(xiàng)目構(gòu)建成單個二進(jìn)制文件。如果出現(xiàn)了這種錯誤,那么你就應(yīng)該用下面展示的第二種方式。
$ ls -ltotal 4-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go$$ dlv debugType 'help' for list of commands.(dlv)
現(xiàn)在打開另一個終端,列出目錄下的文件。你可以看到一個多出來的 __debug_bin 二進(jìn)制文件,這個文件是由源碼編譯生成的,并會加載進(jìn)調(diào)試器。你現(xiàn)在可以回到 dlv 提示框繼續(xù)使用 Delve。
$ ls -ltotal 2036-rwxrwxr-x. 1 user user 2077085 Jun 4 11:48 __debug_bin-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go$
使用 exec 參數(shù)
如果你已經(jīng)有提前編譯好的 Go 程序或者已經(jīng)用 go build 命令編譯完成了,不想再用 Delve 編譯出 __debug_bin 二進(jìn)制文件,那么第二種把程序加載進(jìn) Delve 的方法在這些情況下會很有用。在上述情況下,你可以使用 exec 命令來把整個目錄加載進(jìn) Delve 調(diào)試器。
$ ls -ltotal 4-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go$$ go build hello.go$$ ls -ltotal 1956-rwxrwxr-x. 1 user user 1997284 Jun 4 11:54 hello-rw-rw-r--. 1 user user 74 Jun 4 11:48 hello.go$$ dlv exec ./helloType 'help' for list of commands.(dlv)
查看 delve 幫助信息
在 dlv 提示符中,你可以運(yùn)行 help 來查看 Delve 提供的多種幫助選項(xiàng)。命令列表相當(dāng)長,這里我們只列舉一些重要的功能。下面是 Delve 的功能概覽。
(dlv) helpThe following commands are available:Running the program:Manipulating breakpoints:Viewing program variables and memory:Listing and switching between threads and goroutines:Viewing the call stack and selecting frames:Other commands:Type help followed by a command for full documentation.(dlv)
設(shè)置斷點(diǎn)
現(xiàn)在我們已經(jīng)把 hello.go 程序加載進(jìn)了 Delve 調(diào)試器,我們在主函數(shù)處設(shè)置斷點(diǎn),稍后來確認(rèn)它。在 Go 中,主程序從 main.main 處開始執(zhí)行,因此你需要給這個名字提供個 break 命令。之后,我們可以用 breakpoints 命令來檢查斷點(diǎn)是否正確設(shè)置了。
不要忘了你還可以用命令簡寫,因此你可以用 b main.main 來代替 break main.main,兩者效果相同,bp 和 breakpoints 同理。你可以通過運(yùn)行 help 命令查看幫助信息來找到你想要的命令簡寫。
(dlv) break main.mainBreakpoint 1 set at 0x4a228f for main.main() ./hello.go:5(dlv) breakpointsBreakpoint runtime-fatal-throw at 0x42c410 for runtime.fatalthrow() /usr/lib/golang/src/runtime/panic.go:663 (0)Breakpoint unrecovered-panic at 0x42c480 for runtime.fatalpanic() /usr/lib/golang/src/runtime/panic.go:690 (0)print runtime.curg._panic.argBreakpoint 1 at 0x4a228f for main.main() ./hello.go:5 (0)(dlv)
程序繼續(xù)執(zhí)行
現(xiàn)在,我們用 continue 來繼續(xù)運(yùn)行程序。它會運(yùn)行到斷點(diǎn)處中止,在我們的例子中,會運(yùn)行到主函數(shù)的 main.main 處中止。從這里開始,我們可以用 next 命令來逐行執(zhí)行程序。請注意,當(dāng)我們運(yùn)行到 fmt.Println("Hello, world!") 處時,即使我們還在調(diào)試器里,我們也能看到打印到屏幕的 Hello, world!。
(dlv) continue> main.main() ./hello.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a228f)1: package main2:3: import "fmt"4:=> 5: func main() {6: fmt.Println("Hello, world!")7: }(dlv) next> main.main() ./hello.go:6 (PC: 0x4a229d)1: package main2:3: import "fmt"4:5: func main() {=> 6: fmt.Println("Hello, world!")7: }(dlv) nextHello, world!> main.main() ./hello.go:7 (PC: 0x4a22ff)2:3: import "fmt"4:5: func main() {6: fmt.Println("Hello, world!")=> 7: }(dlv)
退出 Delve
你隨時可以運(yùn)行 quit 命令來退出調(diào)試器,退出之后你會回到 shell 提示符。相當(dāng)簡單,對嗎?
(dlv) quit$
Delve 的其他功能
我們用其他的 Go 程序來探索下 Delve 的其他功能。這次,我們從 golang 教程 中找了一個程序。如果你要學(xué)習(xí) Go 語言,那么 Golang 教程應(yīng)該是你的第一站。
下面的程序,functions.go 中簡單展示了 Go 程序中是怎樣定義和調(diào)用函數(shù)的。這里,我們有一個簡單的把兩數(shù)相加并返回和值的 add() 函數(shù)。你可以像下面那樣構(gòu)建程序并運(yùn)行它。
$ cat functions.gopackage mainimport "fmt"func add(x int, y int) int {return x + y}func main() {fmt.Println(add(42, 13))}$
你可以像下面那樣構(gòu)建和運(yùn)行程序。
$ go build functions.go && ./functions55$
進(jìn)入函數(shù)
跟前面展示的一樣,我們用前面提到的一個選項(xiàng)來把二進(jìn)制文件加載進(jìn) Delve 調(diào)試器,再一次在 main.main 處設(shè)置斷點(diǎn),繼續(xù)運(yùn)行程序直到斷點(diǎn)處。然后執(zhí)行 next 直到 fmt.Println(add(42, 13)) 處;這里我們調(diào)用了 add() 函數(shù)。我們可以像下面展示的那樣,用 Delve 的 step 命令從 main 函數(shù)進(jìn)入 add() 函數(shù)。
$ dlv debugType 'help' for list of commands.(dlv) break main.mainBreakpoint 1 set at 0x4a22b3 for main.main() ./functions.go:9(dlv) c> main.main() ./functions.go:9 (hits goroutine(1):1 total:1) (PC: 0x4a22b3)4:5: func add(x int, y int) int {6: return x + y7: }8:=> 9: func main() {10: fmt.Println(add(42, 13))11: }(dlv) next> main.main() ./functions.go:10 (PC: 0x4a22c1)5: func add(x int, y int) int {6: return x + y7: }8:9: func main() {=> 10: fmt.Println(add(42, 13))11: }(dlv) step> main.add() ./functions.go:5 (PC: 0x4a2280)1: package main2:3: import "fmt"4:=> 5: func add(x int, y int) int {6: return x + y7: }8:9: func main() {10: fmt.Println(add(42, 13))(dlv)
使用文件名:行號來設(shè)置斷點(diǎn)
上面的例子中,我們經(jīng)過 main 函數(shù)進(jìn)入了 add() 函數(shù),但是你也可以在你想加斷點(diǎn)的地方直接使用“文件名:行號”的組合。下面是在 add() 函數(shù)開始處加斷點(diǎn)的另一種方式。
(dlv) break functions.go:5Breakpoint 1 set at 0x4a2280 for main.add() ./functions.go:5(dlv) continue> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)1: package main2:3: import "fmt"4:=> 5: func add(x int, y int) int {6: return x + y7: }8:9: func main() {10: fmt.Println(add(42, 13))(dlv)
查看當(dāng)前的棧信息
現(xiàn)在我們運(yùn)行到了 add() 函數(shù),我們可以在 Delve 中用 stack 命令查看當(dāng)前棧的內(nèi)容。這里在 0 位置展示了棧頂?shù)暮瘮?shù) add() ,緊接著在 1 位置展示了調(diào)用 add() 函數(shù)的 main.main。在 main.main 下面的函數(shù)屬于 Go 運(yùn)行時,是用來處理加載和執(zhí)行該程序的。
(dlv) stack0 0x00000000004a2280 in main.addat ./functions.go:51 0x00000000004a22d7 in main.mainat ./functions.go:102 0x000000000042dd1f in runtime.mainat /usr/lib/golang/src/runtime/proc.go:2003 0x0000000000458171 in runtime.goexitat /usr/lib/golang/src/runtime/asm_amd64.s:1337(dlv)
在幀之間跳轉(zhuǎn)
在 Delve 中我們可以用 frame 命令實(shí)現(xiàn)幀之間的跳轉(zhuǎn)。在下面的例子中,我們用 frame 實(shí)現(xiàn)了從 add() 幀跳到 main.main 幀,以此類推。
(dlv) frame 0> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)Frame 0: ./functions.go:5 (PC: 4a2280)1: package main2:3: import "fmt"4:=> 5: func add(x int, y int) int {6: return x + y7: }8:9: func main() {10: fmt.Println(add(42, 13))(dlv) frame 1> main.add() ./functions.go:5 (hits goroutine(1):1 total:1) (PC: 0x4a2280)Frame 1: ./functions.go:10 (PC: 4a22d7)5: func add(x int, y int) int {6: return x + y7: }8:9: func main() {=> 10: fmt.Println(add(42, 13))11: }(dlv)
打印函數(shù)參數(shù)
一個函數(shù)通常會接收多個參數(shù)。在 add() 函數(shù)中,它的入?yún)⑹莾蓚€整型。Delve 有個便捷的 args 命令,它能打印出命令行傳給函數(shù)的參數(shù)。
(dlv) argsx = 42y = 13~r2 = 824633786832(dlv)
查看反匯編碼
由于我們是調(diào)試編譯出的二進(jìn)制文件,因此如果我們能查看編譯器生成的匯編語言指令將會非常有用。Delve 提供了一個 disassemble 命令來查看這些指令。在下面的例子中,我們用它來查看 add() 函數(shù)的匯編指令。
(dlv) step> main.add() ./functions.go:5 (PC: 0x4a2280)1: package main2:3: import "fmt"4:=> 5: func add(x int, y int) int {6: return x + y7: }8:9: func main() {10: fmt.Println(add(42, 13))(dlv) disassembleTEXT main.add(SB) /home/user/go/gobin/functions.go=> functions.go:5 0x4a2280 48c744241800000000 mov qword ptr [rsp+0x18], 0x0functions.go:6 0x4a2289 488b442408 mov rax, qword ptr [rsp+0x8]functions.go:6 0x4a228e 4803442410 add rax, qword ptr [rsp+0x10]functions.go:6 0x4a2293 4889442418 mov qword ptr [rsp+0x18], raxfunctions.go:6 0x4a2298 c3 ret(dlv)
單步退出函數(shù)
另一個功能是 stepout,這個功能可以讓我們跳回到函數(shù)被調(diào)用的地方。在我們的例子中,如果我們想回到 main.main 函數(shù),我們只需要簡單地運(yùn)行 stepout 命令,它就會把我們帶回去。在我們調(diào)試大型代碼庫時,這個功能會是一個非常便捷的工具。
(dlv) stepout> main.main() ./functions.go:10 (PC: 0x4a22d7)Values returned:~r2: 555: func add(x int, y int) int {6: return x + y7: }8:9: func main() {=> 10: fmt.Println(add(42, 13))11: }(dlv)
打印變量信息
我們一起通過 Go 教程 的另一個示例程序來看下 Delve 是怎么處理 Go 中的變量的。下面的示例程序定義和初始化了一些不同類型的變量。你可以構(gòu)建和運(yùn)行程序。
$ cat variables.gopackage mainimport "fmt"var i, j int = 1, 2func main() {var c, python, java = true, false, "no!"fmt.Println(i, j, c, python, java)}$$ go build variables.go &&; ./variables1 2 true false no!$
像前面說過的那樣,用 delve debug 在調(diào)試器中加載程序。你可以在 Delve 中用 print 命令通過變量名來展示他們當(dāng)前的值。
(dlv) print ctrue(dlv) print java"no!"(dlv)
或者,你還可以用 locals 命令來打印函數(shù)內(nèi)所有的局部變量。
(dlv) localspython = falsec = truejava = "no!"(dlv)
如果你不知道變量的類型,你可以用 whatis 命令來通過變量名來打印它的類型。
(dlv) whatis pythonbool(dlv) whatis cbool(dlv) whatis javastring(dlv)
總結(jié)
現(xiàn)在我們只是了解了 Delve 所有功能的皮毛。你可以自己去查看幫助內(nèi)容,嘗試下其它的命令。你還可以把 Delve 綁定到運(yùn)行中的 Go 程序上(守護(hù)進(jìn)程?。?,如果你安裝了 Go 源碼庫,你甚至可以用 Delve 導(dǎo)出 Golang 庫內(nèi)部的信息。勇敢去探索吧!
本文名稱:使用Delve代替Println來調(diào)試Go程序
標(biāo)題URL:http://fisionsoft.com.cn/article/dhhjhhp.html


咨詢
建站咨詢

