通过例子学习在golang中调试程序

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 【7月更文挑战第4天】Go语言支持使用cgo进行汇编调试,官方文档在golang.org/doc/asm。注意,调试Go运行时可能遇到变量不可用或行号错误,需谨慎使用step命令。

1 简介

在Go程序中可能需要使用调试,golang的汇编基于Plan 9风格,包含PC、FP、SP、SB等伪寄存器。

调试工具dlv可用于Go程序的调试,安装可通过go install github.com/go-delve/delve/cmd/dlv@latest
启动调试会话如dlv debug main.go,设置断点break main.main,调试测试用dlv test

mandala曼德罗符号.png

2 使用汇编和调试工具 dlv

在中大型程序中,部分汇编和调试工作可以给我们提供。

  • 汇编简介

    go语言自带了汇编模式cgo,用于调用其他c库和在某些场景调试程序。

    官方文档:

    golang.org/doc/asm

    go汇编需要在go包中使用,必须指明当前包名等信息。

    汇编代码中定义的变量和函数要被其他Go语言引用,还需要通过Go语言代码将汇编定义的符号声明出来。
    用于变量和函数的定义的Go汇编文件,类似与C的 .c文件,用于导出汇编定义符号的Go源文件类似C语言的 .h 文件。

    go 汇编不类似于 8位寄存器,而是引入了 PC,FP,SP,SB 4类伪寄存器。

    4个伪寄存器加其他通用寄存器就是Go汇编语言对CPU的重新抽象。

    因此go的汇编代码也经常被称之为伪汇编代码。

    汇编程序基于 Plan 9 汇编程序的输入样式,在 其他地方有详细记录。
    如果您打算编写汇编语言,您应该阅读该文档,尽管其中大部分内容是特定于 Plan 9 的。

    当前文档提供了语法的摘要以及与该文档中解释内容的差异,并描述了编写汇编代码以与 Go 交互时适用的特殊性。

    关于 Go 的汇编器最重要的一点是它不是底层机器的直接表示。一些细节精确映射到机器,但有些则不然。

    这是因为编译器套件(请参阅 此说明)不需要在通常的管道中传递汇编程序。

    相反,编译器在一种半抽象的指令集上运行,指令选择部分发生在代码生成之后。

    汇编器以半抽象的形式工作,所以当你看到这样的MOV 指令时。
    工具链实际为该操作生成的可能根本不是移动指令,可能是清除指令或加载指令。

    或者它可能与具有该名称的机器指令完全对应。

    一般来说,特定于机器的操作往往表现为它们自己,而更一般的概念,如内存移动和子程序调用和返回则更抽象。

    细节因架构而异,go语言处于发展阶段,因此也经常处于变动中。

    汇编程序是一种解析该半抽象指令集的描述并将其转换为要输入到链接器的指令的方法。

    如果您想查看给定体系结构(例如 amd64)的汇编指令,标准库的源代码中有很多示例,
    例如 runtime和 math/big.

    您还可以检查编译器作为汇编代码发出的内容(实际输出可能与您在此处看到的不同):

    GOOS=linux GOARCH=amd64 go tool compile -S x.go 
    
  • 可以使用tool 工具检查go 程序的伪汇编代码

     go tool compile -S go_pkg.go
    

3 调试工具

安装

  https://github.com/go-delve/delve/tree/master/Documentation/installation

  git clone https://github.com/go-delve/delve

cd .\delve\
go install github.com/go-delve/delve/cmd/dlv...
>> go install github.com/go-delve/delve/cmd/dlv@latest    
    go: downloading github.com/go-delve/delve v1.20.1
    go: downloading golang.org/x/sys v0.0.0-20220908164124-27713097b956

 \delve> go help install

4 使用调试

在任意一个 包括main.go 的 go 模块包中, 使用 dlv debug 开始调试

    dlv debug .\main.go
    Type 'help' for list of commands.
    (dlv) 

要将标志传递给您的程序,请用--:将它们分开dlv debug ./main.go -- -arg1 value。

调用该命令将使 Delve 以最适合调试的方式编译程序,然后执行并附加到程序并开始调试会话。
现在,当调试会话首次启动时,您就处于程序初始化的最开始。

为了到达更有用的地方,您需要设置一个或两个断点并继续执行到该点。

dlv 默认为 panic设置了一个断点。

设置自定义断点和继续执行 main 功能:

    (dlv) break main.main
    Breakpoint 1 set at 0xd1b76a for main.main()  /main.go:116
    (dlv) continue
    > main.main()  /main.go:116 (hits goroutine(1):1 total:1) (PC: 0xd1b76a)
       111:                 }(i)
       112:         }
       113:         wg.Wait()
       114: }
       115:
    => 116: func main() {
       117:         fmt.Printf("gls:%#v \n", gls)
       118:         UsageGls()
       119: }
  • 调试测试, 如果有测试包和文件可以使用 dlv test执行,类似go test的规则。

    给定与上面相同的目录结构,您可以通过执行测试套件来调试代码。

    为此,您可以使用dlv test子命令,它采用与 相同的可选包路径dlv debug,并且如果没有给出任何参数,也将构建当前包。

 $ dlv test  ./main.go
Type 'help' for list of commands.
(dlv) funcs test.Test*
/home/me/go/src/github.com/me/foo/pkg/baz/test.TestHi
(dlv) break TestHi
Breakpoint 1 set at 0x536513 for /home/me/go/src/github.com/me/foo/pkg/baz/test.TestHi() ./test_test.go:5
(dlv) continue
> /home/me/go/src/github.com/me/foo/pkg/baz/test.TestHi() ./bar_test.go:5 (hits goroutine(5):1 total:1) (PC: 0x536513)
     1:    package baz
     2:    
     3:    import "testing"
     4:    
=>   5:    func TestHi(t *testing.T) {
     6:        t.Fatal("implement me!")
     7:    }
(dlv) 

我们开始调试测试二进制文件,通过使用正funcs则表达式过滤函数列表的命令找到我们的测试函数,设置断点,然后继续执行直到遇到该断点。

有关您可以使用的子命令的更多信息,请键入dlv help,一旦在调试会话中,您可以随时通过键入来查看所有可用的命令help 。

  • 使用dlv 调试 go 运行时

    可以使用 Delve 来调试 Go 运行时,但是需要牢记一些注意事项

    1 该runtime包始终使用优化和内联进行编译,所有适用于调试优化二进制文件的警告都适用于运行时包。

    2 特别是一些变量可能不可用或具有陈旧的值,并且它可能会暴露一些编译器为指令分配行号的错误。

    3 接下来,step 和 stepout 尝试跟随当前的 goroutine,如果你在运行时调试其中一个修改 curg 指针的函数,它们会感到困惑。应该改用“步骤指令”命令。

    4 从 g0 执行堆栈跟踪时,Delve 将返回顶部帧,然后立即切换到 goroutine 堆栈。

    5 如果您想查看 g0 堆栈跟踪,请使用

    stack -mode simple.

    6 如果 step 命令已经在运行时函数中,则它只会进入私有运行时函数。

    要进入由编译器插入到用户代码中的私有运行时函数,请设置断点,然后runtime.curg.goid == 用作条件。

目录
相关文章
|
2月前
|
Go
第五章 Golang程序流程控制
第五章 Golang程序流程控制
40 0
|
2月前
|
传感器 监控 物联网
golang开源的可嵌入应用程序高性能的MQTT服务
golang开源的可嵌入应用程序高性能的MQTT服务
246 3
|
8月前
|
NoSQL 小程序 Cloud Native
你是使用什么工具调试 golang 程序的?
你是使用什么工具调试 golang 程序的?
|
2月前
|
网络协议 Go 数据安全/隐私保护
golang开源的可嵌入应用程序高性能的MQTT服务
golang开源的可嵌入应用程序高性能的MQTT服务
330 2
|
2月前
|
Go
Golang深入浅出之-信号(Signals)处理与优雅退出Go程序
【4月更文挑战第23天】在Go语言中,使用`os/signal`包处理信号对实现程序优雅退出和响应中断至关重要。本文介绍了如何注册信号处理器、处理常见问题和错误,以及提供代码示例。常见问题包括未捕获关键信号、信号处理不当导致程序崩溃和忽略清理逻辑。解决方案包括注册信号处理器(如`SIGINT`、`SIGTERM`)、保持信号处理器简洁和执行清理逻辑。理解并正确应用这些原则能增强Go程序的健壮性和可管理性。
48 1
|
2月前
|
Unix Linux Go
Golang深入浅出之-信号(Signals)处理与优雅退出Go程序
【4月更文挑战第25天】Go语言中的信号处理关乎程序对外部事件的响应,尤其是优雅地终止进程。本文介绍了信号基础,如SIGINT、SIGTERM等常见信号,以及处理流程:注册处理器、等待信号、执行清理和优雅退出。强调了三个易错点及避免方法,并提供实战代码示例展示如何监听和响应信号。信号处理应简洁高效,确保程序健壮性和用户体验。
52 0
|
2月前
|
存储 IDE 编译器
编程笔记 GOLANG基础 005 第一个程序:hello world 使用vscode
编程笔记 GOLANG基础 005 第一个程序:hello world 使用vscode
|
10月前
|
存储 编译器 Go
在本地部署Golang应用程序
在本地部署Golang应用程序
127 2
|
9月前
|
Go
100天精通Golang(基础入门篇)——第9天:Go语言程序的循环语句
100天精通Golang(基础入门篇)——第9天:Go语言程序的循环语句
31 0
|
9月前
|
存储 编译器 Go
100天精通Golang(基础入门篇)——第8天:Go语言程序的流程结构和条件语句
100天精通Golang(基础入门篇)——第8天:Go语言程序的流程结构和条件语句
47 0