Delve是一个通用的工具包,使调试变得轻而易举。你最后一次尝试学习一门新的编程语言是什么时候?你坚持不懈,你是那些第一次发布就尝试新事物的勇敢者吗?无论哪种方式,学习一门新语言都是非常有用且充满乐趣的。您尝试编写一个简单的“Hello,world!”,然后编写一些示例代码并执行它,进行一些小的更改,然后继续。我敢肯定,无论我们使用何种技术,我们都去过那里。如果您已经尝试了一段时间的语言,并且希望精通它,那么有一些事情可以帮助您。其中之一是调试器。有些人喜欢在代码中使用简单的“打印”语句来调试,适合代码量小的简单程序;但是,如果您正在处理一个有许多开发人员和数千行代码的大型项目,那么您应该使用调试器。我最近开始学习Go编程语言,在本文中,我们将探索一个名为Delve的调试器。Delve是专门用来调试Go程序的工具。我们将使用一些Go示例代码来了解它的一些功能。不要担心这里显示的Go示例代码;即使你以前没有写过Go代码,你也能理解它。Go的目标之一是简单性,因此代码是一致的并且易于理解和解释。Delve简介Delve是托管在GitHub上的开源项目。它自己的文档说:Delve是Go编程语言的调试器。这个项目的目标是为Go提供一个简单、功能齐全的调试工具。Delve应该易于调用和使用。当您使用调试器时,事情可能不会像您想象的那样工作。如果您这么认为,那么Delve不适合您。让我们仔细看看。我的测试系统是运行FedoraLinux的笔记本电脑,Go编译器版本如下:$cat/etc/fedora-releaseFedorarelease30(三十)$$goversiongoversiongo1.12.17linux/amd64$Golanginstallifyoudon有了它Go,您可以运行以下命令并轻松地从配置的存储库中获取它。$dnfinstallgolang.x86_64或者,您可以在安装页面上找到适用于您的操作系统的其他安装。在开始之前,请确保Go工具依赖的以下路径已经设置好。如果未设置这些路径,某些示例可能无法正常运行。您可以轻松地在shell的RC文件中设置这些环境变量,在我的机器上它是在$HOME/bashrc文件中设置的。$去环境|grepGOPATHGOPATH="/home/user/go"$$去环境|grepGOBINGOBIN="/home/user/go/gobin"$DelveInstall你可以通过运行一个简单的goget命令来安装Delve,如下所示。goget是Golang从外部源下载和安装所需包的方式。如果在安装过程中遇到问题,可以查看Delve安装教程。$goget-ugithub.com/go-delve/delve/cmd/dlv$运行上面的命令,它会把Delve下载到你的$GOPATH位置,如果你没有设置$GOPATH为其他值,那么默认情况下$GOPATH和$HOME/go是同一条路径。可以进入go/目录,在bin/目录下可以看到dlv。$ls-l$HOME/gototal8drwxrwxr-x。2用户用户40965月25日19:11bindrwxrwxr-x。4用户用户40965月25日19:21src$$ls-l~/go/bin/total19596-rwxrwxr-x。1useruser20062654May2519:17dlv$由于你在$GOPATH中安装了Delve,你可以像普通的shell命令一样运行它,即你不必每次运行它时都转到它所在的目录。您可以使用版本选项验证是否正确安装了dlv。示例中安装的版本为1.4.1。$whichdlv~/go/bin/dlv$$dlvversionDelveDebuggerVersion:1.4.1Build:$Id:bda606147ff48b58bde39e20b9e11378eaa4db46$$现在,让我们在Go程序中使用Delve来了解它的功能以及如何使用它们。让我们先写一个hello.go,它简单地打印一个Hello,world!信息。请记住,我将这些示例程序放在$GOBIN目录中。$pwd/home/user/go/gobin$$cathello.gopackagemainimport"fmt"funcmain(){fmt.Println("Hello,world!")}$运行build命令编译一个Go程序,其输入它是一个带有.go后缀的文件。如果程序没有语法错误,Go编译器会将其编译成二进制可执行文件。这个文件可以直接运行,我们会看到Hello,world!运行后屏幕上的消息。$gobuildhello.go$$ls-lhello-rwxrwxr-x。1useruser1997284May2612:13hello$$./helloHello,world!$在Delve中加载程序通过两种方式将程序加载到Delve调试器中。在源代码编译成二进制文件之前使用调试参数的第一种方法是在需要时对源代码使用调试命令。Delve将为您编译一个名为__debug_bin的二进制文件并将其加载到调试器中。本例中可以进入hello.go所在目录,然后执行dlvdebug命令。如果一个目录中有多个源文件,并且每个文件都有自己的主要功能,Delve可能会抛出一个错误,要求从单个项目构建单个程序或单个二进制文件。如果发生此错误,则应使用如下所示的第二种方法。$ls-ltotal4-rw-rw-r--。1用户用户74Jun411:48hello.go$$dlvdebugType'help'命令列表。(dlv)现在打开另一个终端并列出文件下的目录。您可以看到一个额外的__debug_bin二进制文件,它是从源代码编译并加载到调试器中的。您现在可以返回到dlv提示并继续使用Delve。$ls-ltotal2036-rwxrwxr-x。1个用户用户2077085Jun411:48__debug_bin-rw-rw-r--。1useruser74Jun411:48hello.go$如果已经有则使用exec参数编译的Go程序已经用gobuild命令编译好了,不想用Delve编译__debug_bin二进制文件,那么将程序加载到Delve的第二种方法在这些情况下会很有用。在上述情况下,您可以使用exec命令将整个目录加载到Delve调试器中。$ls-ltotal4-rw-rw-r--。1用户用户74Jun411:48hello.go$$gobuildhello.go$$ls-ltotal1956-rwxrwxr-x。1个用户用户1997284Jun411:54hello-rw-rw-r--。1useruser74Jun411:48hello.go$$dlvexec./helloType'help'forlistofcommands.(dlv)在dlv提示符号中查看delve帮助信息,可以运行help查看提供的各种帮助选项通过Delve。命令列表很长,这里我们只列出一些重要的功能。以下是Delve功能的概述。(dlv)help可以使用以下命令:运行程序:操作断点:查看程序变量和内存:列出并在线程和goroutine之间切换:查看调用堆栈和选择框架:其他命令:键入help后跟命令以获取完整文档.(dlv)设置断点现在我们已经将hello.go程序加载到Delve调试器中,让我们在main函数处设置一个断点,以便稍后确认。在Go中,主程序从main.main开始执行,因此您需要提供一个具有此名称的break命令。之后,我们可以使用breakpoints命令来检查断点是否设置正确。别忘了还可以使用命令缩写,所以可以用bmain.main代替breakmain.main,两者的效果是一样的,bp和breakpoints也是一样的。您可以通过运行help命令查看帮助信息,找到您需要的命令缩写。(dlv)breakmain.mainBreakpoint1setat0x4a228fformain.main()./hello.go:5(dlv)breakpointsBreakpointruntime-fatal-throwat0x42c410forruntime.fatalthrow()/usr/lib/golang/src/runtime/panic.go:663(0)Breakpointunrecovered-panicat0x42c480forruntime.fatalpanic()/usr/lib/golang/src/runtime/panic.go:690(0)printruntime.curg._panic.argBreakpoint1at0x4a228fformain.main()./hello.go:5(0)(dlv)程序继续执行现在,我们使用continue继续运行程序。它将在断点处停止,在我们的例子中,在main函数的main.main处。从这里开始,我们可以使用下一个命令逐行执行程序。请注意,当我们运行fmt.Println("Hello,world!")时,我们会看到Hello,world!即使我们仍在调试器中,也会打印到屏幕上。(dlv)continue>main.main()./hello.go:5(hitsgoroutine(1):1total:1)(PC:0x4a228f)1:packagemain2:3:import"fmt"4:=>5:funcmain(){6:fmt.Println("Hello,world!")7:}(dlv)next>main.main()./hello.go:6(PC:0x4a229d)1:packagemain2:3:import"fmt"4:5:funcmain(){=>6:fmt.Println("Hello,world!")7:}(dlv)nextHello,world!>main.main()./hello.go:7(PC:0x4a22ff)2:3:import"fmt"4:5:funcmain(){6:fmt.Println("Hello,world!")=>7:}(dlv)ExitDelveyou您可以随时运行quit命令退出调试器,之后您将返回到shell提示符。很简单,对吧?(dlv)quit$Delve的其他功能让我们使用其他Go程序来探索Delve的其他功能。这一次,我们从golang教程中找到了一个程序。如果你打算学习Go语言,那么Golang教程应该是你的第一站。下面的程序functions.go简单地展示了如何在Go程序中定义和调用函数。在这里,我们有一个简单的add()函数,它将两个数字相加并返回总和。您可以构建程序并像下面那样运行它。$catfunctions.gopackagemainimport"fmt"funcadd(xint,yint)int{returnx+y}funcmain(){fmt.Println(add(42,13))}$你可以构建并运行程序.$gobuildfunctions.go&&./functions55$进入如上所示的函数,我们使用上面提到的其中一个选项将二进制文件加载到Delve调试器中,再次在main.main处设置断点,继续运行程序直到断点。然后执行next直到fmt.Println(add(42,13));这里我们调用add()函数。我们可以使用Delve的step命令从main函数中进入add()函数,如下图。$dlvdebugType'help'命令列表。(dlv)breakmain.mainBreakpoint1setat0x4a22b3formain.main()./functions.go:9(dlv)c>main.main()./functions.go:9(hitsgoroutine(1):1total:1)(PC:0x4a22b3)4:5:funcadd(xint,yint)int{6:returnx+y7:}8:=>9:funcmain(){10:fmt.Println(add(42,13))11:}(dlv)next>main.main()./functions.go:10(PC:0x4a22c1)5:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){=>10:fmt.Println(add(42,13))11:}(dlv)step>main.add()./functions.go:5(PC:0x4a2280)1:packagemain2:3:import"fmt"4:=>5:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){10:fmt.Println(add(42,13))(dlv)使用文件名:行号设置断点在上面的例子中,我们通过main函数function进入add(),但也可以直接在要加断点的地方使用“文件名:行号”的组合。这是在add()函数开头添加断点的另一种方法。(dlv)breakfunctions.go:5Breakpoint1setat0x4a2280formain.add()./functions.go:5(dlv)continue>main.add()./functions.go:5(hitsgoroutine(1):1总计:1)(PC:0x4a2280)1:packagemain2:3:import"fmt"4:=>5:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){10:fmt.Println(add(42,13))(dlv)查看当前栈信息现在我们已经运行到add()函数,我们可以使用Delve中的stack命令查看当前堆栈的内容。这里堆栈顶部的函数add()显示在位置0,后面是main.main在位置1调用add()函数。main.main下面的函数属于Go运行时,用于处理加载并执行程序。(dlv)stack00x00000000004a2280inmain.addat./functions.go:510x00000000004a22d7inmain.mainat./functions.go:1020x0000000000042dd1finruntime.mainat/usr/lib/gruntimeolang/proc.rc.:20030x0000000000458171inruntime.goexitat/usr/lib/golang/src/runtime/asm_amd64.s:1337(dlv)帧间跳转在Delve中,我们可以使用frame命令进行帧间跳转。在下面的例子中,我们使用frame从add()框架跳转到main.main框架,等等。(dlv)frame0>main.add()./functions.go:5(hitsgoroutine(1):1total:1)(PC:0x4a2280)Frame0:./functions.go:5(PC:4a2280)1:packagemain2:3:import"fmt"4:=>5:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){10:fmt.Println(add(42,13))(dlv)frame1>main.add()./functions.go:5(hitsgoroutine(1):1total:1)(PC:0x4a2280)Frame1:./functions.go:10(PC:4a22d7)5:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){=>10:fmt.Println(add(42,13))11:}(dlv)打印函数参数一个函数通常接收多个参数。在add()函数中,它的输入参数是两个整数。Delve有一个方便的args命令,可以在命令行上打印传递给函数的参数。(dlv)argsx=42y=13~r2=824633786832(dlv)查看反汇编代码由于我们调试的是编译后的二进制文件,如果能查看编译器生成的汇编语言指令,那将是非常有用的。Delve提供了一个反汇编命令来查看这些指令。在下面的例子中,我们用它来查看add()函数的汇编指令。(dlv)step>main.add()./functions.go:5(PC:0x4a2280)1:packagemain2:3:import"fmt"4:=>5:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){10:fmt.Println(add(42,13))(dlv)disassembleTEXTmain.add(SB)/home/user/go/gobin/functions.go=>functions.go:50x4a228048c744241800000000movqwordptr[rsp+0x18],0x0functions.go:60x4a2289488b442408movrax,qwordptr[rsp+0x8]functions.go:60x4a228e480add3442qwordptr[rsp+0x10]functions.go:60x4a22934889442418movqwordptr[rsp+0x18],raxfunctions.go:60x4a2298c3ret(dlv)单步退出函数另一个函数是stepout,这个函数可以让我们跳回到函数被调用的地方。在我们的例子中,如果我们想回到main.main函数,我们只需运行stepout命令,它就会带我们回去。当我们调试大型代码库时,此功能可能是一个非常方便的工具。(dlv)stepout>main.main()./functions.go:10(PC:0x4a22d7)Valuesreturned:~r2:555:funcadd(xint,yint)int{6:returnx+y7:}8:9:funcmain(){=>10:fmt.Println(add(42,13))11:}(dlv)打印变量信息让我们看看Delve是如何通过Go教程中的另一个示例程序处理的Go中的变量。下面的示例程序定义并初始化了一些不同类型的变量。您可以构建和运行程序。$catvariables.gopackagemainimport"fmt"vari,jint=1,2funcmain(){varc,python,java=true,false,"no!"fmt.Println(i,j,c,python,java)}$$去构建变量.go&&;./variables12truefalseno!$如前所述,使用delvedebug在调试器中加载程序。可以使用Delve中的print命令,通过变量名来显示它们当前的值。(dlv)打印ctrue(dlv)打印java“不!”(dlv)或者,您也可以使用locals命令打印函数内的所有局部变量。(dlv)localspython=falsesec=truejava=“不!”(dlv)如果你不知道变量的类型,你可以使用whatis命令通过变量名打印它的类型。(dlv)whatispythonbool(dlv)whatiscbool(dlv)whatisjavastring(dlv)总结现在我们只触及了Delve功能的皮毛。可以自己查看帮助内容,尝试其他命令。您还可以将Delve绑定到正在运行的Go程序(守护进程!),如果您安装了Go源存储库,您甚至可以使用Delve导出Golang存储库中的信息。勇敢探索吧!
