println 停止线程
您上一次尝试学习一种新的编程语言是什么时候? 您是否坚持尝试和真实,还是在新产品发布后立即尝试的勇敢灵魂之一? 无论哪种方式,学习一种新语言都是非常有用的,而且很有趣。
您可以尝试一个简单的“世界,您好!” 然后继续编写一些示例代码并执行它,在此过程中进行较小的更改,然后从那里继续进行。 无论我们使用哪种技术,我都相信我们都经历了这段经历。 虽然如果您确实设法坚持使用某种语言,并且希望精通该语言,则有一些方法可以帮助您。
其中之一是调试器。 有些人更喜欢在代码中使用简单的“打印”语句进行调试,并且对于一些简单的行程序也很好。 但是,如果您在一个有多个开发人员和数千行代码的大型项目中工作,那么投资调试器是有意义的。
我最近开始学习Go编程语言,在本文中,我们将探索一个名为Delve的调试器。 Delve是用于调试Go程序的特殊实用程序,我们将使用一些示例Go代码来介绍其某些功能。 不用担心这里提供的Go代码示例; 即使您以前从未在Go中进行编程,它们也是可以理解的。 Go的目标之一是简单,因此代码是一致的,这可以更容易理解和解释。
用自己的话说:
Delve是Go编程语言的调试器。 该项目的目标是为Go提供一个简单的,功能齐全的调试工具。 Delve应该易于调用和易于使用。 如果您使用的是调试器,那么事情就不会顺其自然。 考虑到这一点,Delve应该尽可能远离您。
让我们仔细看看。
我的测试系统是一台运行Fedora Linux以及以下Go编译器版本的笔记本电脑:
$ cat
/ etc
/ fedora
- release
Fedora release
30
( Thirty
)
$
$
go version
go version go1
.
12
.
17 linux
/ amd64
$
如果您尚未安装Go,则可以通过简单地运行以下命令从已配置的存储库中获取它来获取它。
$ dnf install golang . x86_64
或者,您可以访问安装页面以获取适合您的操作系统发行版的其他安装选项。
在开始之前,请确保按照Go工具的要求设置了以下必需的PATHS。 如果未设置这些路径,则某些示例可能不起作用。 这些可以在SHELL的RC文件中轻松设置为环境变量,例如我的$HOME/bashrc
文件。
$
go env
| grep GOPATH
GOPATH
=
"/home/user/go"
$
$
go env
| grep GOBIN
GOBIN
=
"/home/user/go/gobin"
$
您可以通过运行一个简单的go get
命令来安装Delve,如下所示。 go get
是从外部源下载和安装所需软件包的Golang方法。 如果确实遇到安装问题,请在此处参考Delve的安装说明。
$
go get
- u github
. com
/ go
- delve
/ delve
/ cmd
/ dlv
$
运行上面的命令会将Delve下载到$ GOPATH位置,在默认情况下,该位置恰好是$ HOME / go。 如果将$ GOPATH设置为其他值,则将有所不同。
您可以转到go /目录,在bin /目录下将看到dlv
。
$ ls
- l $HOME
/
go
total
8
drwxrwxr
- x
.
2 user user
4096 May
25
19
:
11 bin
drwxrwxr
- 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
$
由于Delve是在$ GOPATH下安装的,因此它也可以作为常规的shell命令使用,因此您不必每次都移至其安装目录。 您可以通过使用version
选项运行dlv
来验证dlv
是否已正确安装。 它安装的版本是1.4.1。
$ which dlv
~
/
go
/ bin
/ dlv
$
$ dlv version
Delve Debugger
Version
:
1
.
4
.
1
Build
: $Id
: bda606147ff48b58bde39e20b9e11378eaa4db46 $
$
现在,让我们将Delve与一些Go程序一起使用以了解其功能以及如何使用它们。 在处理所有程序时,让我们从一个简单的“ Hello,world!”开始。 消息,在Go中称为hello.go
。
记住,我将这些示例程序放在$ GOBIN目录中。
$ pwd
/ home
/ user
/
go
/ gobin
$
$ cat hello
.
go
package main
import
"fmt"
func main
()
{
fmt
. Println
(
"Hello, world!"
)
}
$
要构建Go程序,请运行build
命令并使用.go扩展名将其与源文件一起提供。 如果程序没有任何语法问题,则Go编译器会对其进行编译并输出二进制文件或可执行文件。 然后可以直接执行此文件,我们将看到“您好,世界!” 屏幕上显示的消息。
$
go build hello
.
go
$
$ ls
- l hello
- rwxrwxr
- x
.
1 user user
1997284 May
26
12
:
13 hello
$
$
./ hello
Hello
, world
!
$
有两种方法可以将程序加载到Delve调试器中。
当源代码尚未编译为二进制时,请使用debug参数。
第一种方法是仅在需要源文件时使用debug命令。 Delve为您编译一个名为__debug_bin的二进制文件,并将其加载到调试器中。
在此示例中,移至hello.go所在的目录,然后运行dlv debug命令。 如果目录中有多个Go源文件,并且每个源文件都有自己的主要功能,则Delve可能会引发错误,期望使用单个程序或单个项目来构建二进制文件。 如果发生这种情况,最好使用下面显示的第二个选项。
$
ls
-l
total
4
-rw-rw-r--.
1 user user
74 Jun
4
11 :
48 hello.go
$
$ dlv debug
Type
'help'
for list of commands.
( dlv
)
现在打开另一个终端并列出同一目录的内容。 您会看到一个附加的__debug_bin二进制文件,该二进制文件是从源代码编译并加载到调试器中的。 现在,您可以移至dlv提示符,以继续使用Delve。
$
ls
-l
total
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参数
将程序加载到Delve中的第二种方法在您具有预编译的Go二进制文件或已经使用go build命令进行了编译并且不希望Delve将其编译为`__debug_bin`二进制文件时很有用。 在这种情况下,请使用exec参数将二进制目录加载到Delve调试器中。
$
ls
-l
total
4
-rw-rw-r--.
1 user user
74 Jun
4
11 :
48 hello.go
$
$ go build hello.go
$
$
ls
-l
total
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 .
/ hello
Type
'help'
for list of commands.
( dlv
)
在dlv提示符下,您可以运行help
以查看Delve中可用的各种帮助选项。 命令列表非常广泛,我们将在此处介绍一些重要功能。 以下是Delve功能的概述。
( dlv
) help
The 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
)
现在,我们已经在Delve调试器中加载了hello.go程序,让我们在主函数上设置断点,然后进行确认。 在Go中,主程序以main.main
,因此您需要将此名称提供给break command
。 接下来,我们将查看是否使用breakpoints
命令正确设置了breakpoints
。
另外,请记住,您可以使用命令的简写形式,因此break main.main
使用break main.main
,还可以使用b main.main
来达到相同的效果,或者使用bp
代替breakpoints
。 要查找命令的确切速记,请通过运行help
命令来参考帮助部分。
( dlv
)
break main
. main
Breakpoint
1 set at
0x4a228f
for main
. main
()
./ hello
.
go
:
5
( dlv
) breakpoints
Breakpoint 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
. arg
Breakpoint
1 at
0x4a228f
for main
. main
()
./ hello
.
go
:
5
(
0
)
( dlv
)
现在,让我们继续使用“继续”运行程序。 它会一直运行到到达断点为止,在本例中,该断点是main.main
或main函数。 从那里,我们可以使用next
一条命令逐行执行程序。 请注意,一旦移至fmt.Println("Hello, world!")
,我们就可以看到Hello, world!
当我们仍在调试器会话中时,将打印到屏幕上。
( dlv
)
continue
> main
. main
()
./ hello
.
go
:
5
( hits goroutine
(
1
):
1 total
:
1
)
( PC
:
0x4a228f
)
1
:
package main
2
:
3
:
import
"fmt"
4
:
= >
5
:
func main
()
{
6
: fmt
. Println
(
"Hello, world!"
)
7
:
}
( dlv
) next
> main
. main
()
./ hello
.
go
:
6
( PC
:
0x4a229d
)
1
:
package main
2
:
3
:
import
"fmt"
4
:
5
:
func main
()
{
= >
6
: fmt
. Println
(
"Hello, world!"
)
7
:
}
( dlv
) next
Hello
, world
!
> main
. main
()
./ hello
.
go
:
7
( PC
:
0x4a22ff
)
2
:
3
:
import
"fmt"
4
:
5
:
func main
()
{
6
: fmt
. Println
(
"Hello, world!"
)
= >
7
:
}
( dlv
)
如果您希望随时退出调试器,则可以运行quit
命令,您将返回到shell提示符。 很简单,对吧?
( dlv
) quit
$
让我们使用其他一些Go程序来探索其他Delve功能。 这次,我们将从Golang旅游中挑选一个节目。 如果您正在学习Go,那么Golang游览应该是您的第一站。
以下程序functions.go
只是显示了如何在Go程序中定义和调用函数。 在这里,我们有一个简单的add()
函数,该函数将两个数字相加并返回它们的值。 您可以构建程序并执行它,如下所示。
$ cat functions
.
go
package main
import
"fmt"
func add
( x
int
, y
int
)
int
{
return x
+ y
}
func main
()
{
fmt
. Println
( add
(
42
,
13
))
}
$
您可以构建程序并执行,如下所示。
$
go build functions
.
go &&
./ functions
55
$
如前所述,让我们使用前面提到的选项之一将二进制文件加载到Delve调试器中,再次在main.main
设置一个断点,并在main.main
断点时继续运行程序。 然后点击next
直到到达fmt.Println(add(42, 13))
; 在这里,我们调用add()
函数。 我们可以使用Delve step
命令从main
函数移至add()
函数,如下所示。
$ dlv debug
Type
'help'
for list of commands
.
( dlv
)
break main
. main
Breakpoint
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
+ y
7
:
}
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
+ y
7
:
}
8
:
9
:
func main
()
{
= >
10
: fmt
. Println
( add
(
42
,
13
))
11
:
}
( dlv
) step
> main
. add
()
./ functions
.
go
:
5
( PC
:
0x4a2280
)
1
:
package main
2
:
3
:
import
"fmt"
4
:
= >
5
:
func add
( x
int
, y
int
)
int
{
6
:
return x
+ y
7
:
}
8
:
9
:
func main
()
{
10
: fmt
. Println
( add
(
42
,
13
))
( dlv
)
上面,我们经历了main
,然后移到add()
函数,但是您也可以使用filename:linenumber
组合直接在需要的地方设置断点。 以下是在add()
函数开始时设置断点的另一种方法。
( dlv
)
break functions
.
go
:
5
Breakpoint
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 main
2
:
3
:
import
"fmt"
4
:
= >
5
:
func add
( x
int
, y
int
)
int
{
6
:
return x
+ y
7
:
}
8
:
9
:
func main
()
{
10
: fmt
. Println
( add
(
42
,
13
))
( dlv
)
现在我们已经在add()
函数上了,我们可以使用Delve中的stack
命令查看堆栈的当前内容。 这显示了最上面的函数add()
在0处,其次是main.main
在1从那里调用add()
函数。 main.main
下面的main.main
属于Go运行时,该运行时负责加载和执行程序。
( dlv
) stack
0
0x00000000004a2280 in main
. add
at
./ functions
.
go
:
5
1
0x00000000004a22d7 in main
. main
at
./ functions
.
go
:
10
2
0x000000000042dd1f in runtime
. main
at
/ usr
/ lib
/ golang
/ src
/ runtime
/ proc
.
go
:
200
3
0x0000000000458171 in runtime
. goexit
at
/ usr
/ lib
/ golang
/ src
/ runtime
/ asm_amd64
. s
:
1337
( dlv
)
使用Delve中的frame
命令,我们可以随意在上述帧之间切换。 在下面的示例中,使用frame 1
将我们从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 main
2
:
3
:
import
"fmt"
4
:
= >
5
:
func add
( x
int
, y
int
)
int
{
6
:
return x
+ y
7
:
}
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
+ y
7
:
}
8
:
9
:
func main
()
{
= >
10
: fmt
. Println
( add
(
42
,
13
))
11
:
}
( dlv
)
打印功能参数
函数通常接受多个参数进行处理。 对于add()
函数,它接受两个整数。 Delve有一个方便的命令args
,它显示传递给该函数的命令行参数。
( dlv
) args
x
=
42
y
=
13
~r2
=
824633786832
( dlv
)
由于我们正在处理编译的二进制文件,因此能够查看由编译器生成的汇编语言指令非常有帮助。 Delve提供了一个disassemble
命令来查看这些内容。 在下面的示例中,我们使用它来查看add()
函数的反汇编指令。
( dlv
) step
> main
. add
()
./ functions
.
go
:
5
( PC
:
0x4a2280
)
1
:
package main
2
:
3
:
import
"fmt"
4
:
= >
5
:
func add
( x
int
, y
int
)
int
{
6
:
return x
+ y
7
:
}
8
:
9
:
func main
()
{
10
: fmt
. Println
( add
(
42
,
13
))
( dlv
) disassemble
TEXT main
. add
( SB
)
/ home
/ user
/
go
/ gobin
/ functions
.
go
= > functions
.
go
:
5
0x4a2280 48c744241800000000 mov qword ptr
[ rsp
+
0x18
],
0x0
functions
.
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
], rax
functions
.
go
:
6
0x4a2298 c3 ret
( dlv
)
另一个功能是stepout
,它使我们能够返回到调用该函数的位置。 在我们的例子,如果我们希望回到main.main
功能,我们可以简单地运行stepout
命令,它会带我们回去。 这可能是一个非常方便的工具,可以帮助您在大型代码库中移动。
( dlv
) stepout
> main
. main
()
./ functions
.
go
:
10
( PC
:
0x4a22d7
)
Values returned
:
~r2
:
55
5
:
func add
( x
int
, y
int
)
int
{
6
:
return x
+ y
7
:
}
8
:
9
:
func main
()
{
= >
10
: fmt
. Println
( add
(
42
,
13
))
11
:
}
( dlv
)
让我们使用Go导览中的另一个示例程序来了解Delve如何处理Go中的变量。 下面的示例程序定义并初始化了一些不同类型的变量。 您可以构建程序并执行它。
$ cat variables
.
go
package main
import
"fmt"
var
i
, j
int
=
1
,
2
func main
()
{
var c
, python
, java
=
true
,
false
,
"no!"
fmt
. Println
(
i
, j
, c
, python
, java
)
}
$
$
go build variables
.
go &&
./ variables
1
2
true
false no
!
$
如前所述,使用delve debug
将程序加载到调试器中。 您可以在Delve中使用print
命令以及变量名来显示其当前值。
( dlv
)
print c
true
( dlv
)
print java
"no!"
( dlv
)
另外,您可以使用locals
命令来打印函数中的所有局部变量。
( dlv
) locals
python
=
false
c
=
true
java
=
"no!"
( dlv
)
如果您不知道变量的类型,则可以将whatis
命令与变量名一起使用以打印类型。
( dlv
) whatis python
bool
( dlv
) whatis c
bool
( dlv
) whatis java
string
( dlv
)
到目前为止,我们仅涉及了Delve提供的功能。 您可以参考help
部分并尝试其他各种命令。 其他一些有用的功能包括将Delve附加到正在运行的Go程序(守护程序!),甚至在安装了Go源代码包的情况下,甚至使用Delve来探索Golang库的某些内部组件。 继续探索!
翻译自: https://opensource.com/article/20/6/debug-go-delve
println 停止线程