在go语言中使用汇编和调试

举报
码乐 发表于 2024/01/10 09:52:36 2024/01/10
【摘要】 4 使用汇编和调试工具 dlv在中大型程序中,部分汇编和调试工作可以给我们提供便利。 4.0 汇编简介go语言自带了汇编模式cgo,用于调用其他c库和在某些场景调试程序。官方文档: golang.org/doc/asmgo汇编需要在go包中使用,必须指明当前包名等信息。汇编代码中定义的变量和函数要被其他Go语言引用,还需要通过Go语言代码将汇编定义的符号声明出来。用于变量和函数的定义的Go...

4 使用汇编和调试工具 dlv

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

4.0 汇编简介

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
    

4.1 调试工具

安装

  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.2 使用调试

在任意一个 包括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 == <current goroutine id>用作条件。

4.3 常用指令

附加到正在运行的进程并开始调试。

          dlv attach 

使用终端客户端连接到无头调试服务器。

dlv connect 

检查核心转储

dlv core  

启动通过调试适配器协议 (DAP) 进行通信的无头 TCP 服务器。

dlv dap 

编译并开始调试当前目录或指定包中的主包。

dlv debug  

执行预编译的二进制文件,并开始调试会话。

dlv exec 

重播 rr 跟踪。

dlv replay 

已弃用的命令。请改用“调试”。

dlv run  

编译测试二进制文件并开始调试程序。

dlv test 

编译并开始跟踪程序。

dlv trace  

打印版本。

dlv version 

关于日志记录标志的帮助

dlv log   

关于–backend标志的帮助

dlv backend 

查看全部包级别的变量。 因为最终目标程序可能含有大量全局变量。

  vars main  

在main函数入口设置一个 断点

  break main.main 

程序继续运行到下一个断点。

continue   

指令单步执行进入 main() 函数

 next  

进入函数后

  args  // 查看局部变量
  locals  //查看全局变量

4.3.1 组合指令:调试循环条件,和例程 状态

break 和 condition 组合命令 用于在函数循环 内部设置条件断点
比如 在 main.go 10 行设置断点

    break main.go 10
    
    Breakpoint 2 set at main.main

为断点2 设置条件 当 i = 3 生效

     condition 2 i = 3 

然后 continue 运行到 该条件,查看输出。

如果发现循环变量i为3时,切片的前3个元素已经正确初始化,那么可以通过 stack 查看当前函数执行的栈帧信息

        stack
		0  0x000000000084d1e7 in main.main
		   at  /main.go:49
		1  0x00000000001b1428 in runtime.main
		   at /go/src/runtime/proc.go:250
		2  0x00000000001ddac1 in runtime.goexit
		   at /go/src/runtime/asm_amd64.s:1594

或者通过 goroutine 和 goroutines 查看当前 goroutine 相关信息

    goroutine
	Thread 8244 at d:/gospace/strtech/main.go:49
	Goroutine 1:
	        Runtime:  /main.go:49 main.main (0x84d1e7)
	        User:  /main.go:49 main.main (0x84d1e7)
	        Go: <autogenerated>:1 runtime.newproc (0x1dfeec)     
	        Start: /go/src/runtime/proc.go:145 runtime.main (0x1b1260)
    
    (dlv) goroutines
	* Goroutine 1 - User:  /main.go:49 main.main (0x84d1e7) (thread 8244)
	  Goroutine 2 - User: /go/src/runtime/proc.go:364 runtime.gopark (0x1b181d) [force gc (idle)]
	  Goroutine 3 - User: /go/src/runtime/proc.go:364 runtime.gopark (0x1b181d) [GC sweep wait]
	  ...

查看当前代码 ,命令(在命令行界面中)将具有以下结果:

 list main.main 

   (dlv) list main.main
        Showing  ./goid_stored/main.go:116 (PC: 0xb2b76a)
           111:                 }(i)
           112:         }
           113:         wg.Wait()
           114: }
           115:
           116: func main() {
           117:         fmt.Printf("gls:%#v \n", gls)
           118:         UsageGls()
           119: }

4.3.2 反汇编单步查看

disassemble 反汇编计数查看 main函数对应的汇编代码。

   Sending output to pager...
	TEXT main.main(SB) /main.go
	        main.go:41      0x84d100        4c8d6424e8
	   lea r12, ptr [rsp-0x18]
	        main.go:41      0x84d105        4d3b6610
	   cmp r12, qword ptr [r14+0x10]
	        main.go:41      0x84d109        0f86ed000000
	   jbe 0x84d1fc

step-instruction 单步执行汇编指令的命令。

使用break 设置断开,continue到断点停下,或继续

查看全部寄存器状态

  (div) regs
	   Rip = 0x000000000084d1e7
	   Rsp = 0x000000c0004c3ee0
	   Rax = 0x0000000000c93100
	   Rbx = 0x0000000000000006
	   Rcx = 0x0000000000000000
	   Rdx = 0x000000c000536230
	   Rsi = 0x0000000000000000
	   Rdi = 0x000000c00033cb00
	   Rbp = 0x000000c0004c3f70
	    R8 = 0x0000000000000000
	    R9 = 0x000000000000034e
	   R10 = 0x0000000000000000
	   R11 = 0x0000000000000246
	   R12 = 0x000000c0004c3ac8
	   R13 = 0x0000000000000000
	   R14 = 0x000000c000056000
	   R15 = 0x0000000000000020
	Rflags = 0x0000000000000204     [PF IF IOPL=0]
	    Cs = 0x0000000000000033
	    Fs = 0x0000000000000053
	    Gs = 0x000000000000002b

由此推断程序内部定义的 数据地址,然后使用print 可以查看 该地址的数据

   print *(*[5]byte)
  • 退出调试

    quit 
    

小结

本文简单介绍go程序调试的方式,相比c程序 gcc工具 和 python的pdb工具,go提供了更多层级的工具,比如内置的cgo汇编程序,伪汇编代码,应用级的dlv工具等等,对于在某些场景的深度问题定位和功能开发有帮助。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。