惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Blog of Author Tim Ferriss
G
Google Developers Blog
小众软件
小众软件
J
Java Code Geeks
V
Visual Studio Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
罗磊的独立博客
美团技术团队
阮一峰的网络日志
阮一峰的网络日志
V
V2EX
博客园 - 叶小钗
N
Netflix TechBlog - Medium
月光博客
月光博客
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
T
The Exploit Database - CXSecurity.com
The Hacker News
The Hacker News
Cisco Talos Blog
Cisco Talos Blog
Hacker News: Ask HN
Hacker News: Ask HN
Hacker News - Newest:
Hacker News - Newest: "LLM"
AWS News Blog
AWS News Blog
Webroot Blog
Webroot Blog
The Last Watchdog
The Last Watchdog
T
Threatpost
I
Intezer
T
Tenable Blog
L
LINUX DO - 热门话题
T
Tailwind CSS Blog
Scott Helme
Scott Helme
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
C
Cybersecurity and Infrastructure Security Agency CISA
Engineering at Meta
Engineering at Meta
S
Schneier on Security
Recent Announcements
Recent Announcements
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
F
Fortinet All Blogs
腾讯CDC
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Troy Hunt's Blog
量子位
H
Hacker News: Front Page
V2EX - 技术
V2EX - 技术
Google Online Security Blog
Google Online Security Blog
人人都是产品经理
人人都是产品经理
博客园 - 【当耐特】
博客园 - Franky
www.infosecurity-magazine.com
www.infosecurity-magazine.com

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型 Kubernetes 核心概念 软件版本语义化
Go Delve
2023-03-09 · via 蛮荆

2023-03-09 Golang debug

简介

Delve 用来调试 Go 语言开发的程序,该工具的目标是为 Go 语言提供一个简单、功能齐全的调试工具。

为什么不推荐 gdb

  • gdb 对 Go 的调试支持是通过一个 python 脚本文件 src/runtime/runtime-gdb.py 扩展的,功能有限
  • gdb 只能做到最基本的变量打印,却理解不了 golang 的一些特殊类型,比如 channel,map,slice 等,gdb 原生是无法调适 goroutine 协程的, 因为这是用户态的调度单位,gdb 只能理解线程,所以只能通过 python 脚本的扩展,把协程结构按照链表输出

安装

$ go install github.com/go-delve/delve/cmd/dlv@latest

# 安装完成后查看版本
$ dlv verison

Delve Debugger
Version: 1.20.1
Build: $Id: 96e65b6c615845d42e0e31d903f6475b0e4ece6e 

常用命令

  • dlv attach - 调试进程
  • dlv core - 调试 core
  • dlv debug - 编译并调试当前目录的 main 包,也可以通过参数指定其他包
  • dlv exec - 调试二进制文件
  • dlv test - 编译并调试测试文件

快速开始

我们首先从一个简单的示例程序开始,改程序打印字符串 hello world, 然后结束并退出。

源文件

// main.go

package main

func main() {
	println("hello world")
}

调试源文件

$ dlv debug main.go

# Type 'help' for list of commands.
(dlv) 

# 输入 help 查看参数说明
(dlv) help
The following commands are available:

Running the program:
    call ------------------------ Resumes process
...
...
 types ---------------------- Print list of types

Type help followed by a command for full documentation.

# 运行程序
(dlv) continue
hello world
Process 3637 has exited with status 0

调试编译后二进制文件

# 编译源文件
$ go build -o main main.go

$ dlv exec ./main

Type 'help' for list of commands.
(dlv) 

# 接下来的步骤和调试源文件的一样,这里不再赘述

调试进程

为了让进程保持在运行状态,我们在程序中加一行休眠代码:

package main

import "time"

func main() {
	time.Sleep(time.Minute)
	println("hello world")
}

调试前先运行程序:

$ go run main.go

# 查看进程 ID
$ ps -ef | grep "go run main.go"

7602 27666  0 21:30 pts/6    00:00:00 go run main.go

# 调试进程
$ dlv attach 7602

Type 'help' for list of commands.
(dlv) 

...

# 1 分钟之后,main.go 并未正常退出,因为当前正在调试
# 输入 continue 继续运行

(dlv) continue 
Process 7602 has exited with status 0

常用调试命令

下列命令是启动 dlv 调试后可用的命令 (也就是当前命令行变为 (dlv) 之后可用)。

运行程序

命令 描述
call 恢复进程,调用函数 (实验阶段)
continue 继续运行程序,直到遇到断点或程序结束
next 单步调试
restart 重新运行
step 单步调试某个函数
step-instruction 单步调试某个 CPU 指令
stepout 从当前函数跳出

操作断点

命令 描述
break 设置断点
breakpoints 打印所有断点
clear 删除断点
clearall 删除所有断点
condition 设置条件断点
on 设置一个断点触发时执行的命令
toggle 打开/关闭 断点

查看变量或内存

命令 描述
args 打印函数参数
display 每次程序停止时打印表达式的值
examinemem 解析给定地址的内存
locals 打印本地变量
print 解析一个表达式
regs 打印寄存器信息
set 设置变量的值
vars 打印包内变量
whatis 打印类型信息

线程 / goroutine 的展示与切换

命令 描述
goroutine 打印或切换 goroutine
goroutines 打印所有 goroutine
thread 切换到指定的线程
threads 打印所有线程信息

调用堆栈

命令 描述
deferred 在 defer 上下文中执行命令
frame 设置当前帧,或在不同的帧上执行命令
stack 打印堆栈信息

其他命令

命令 描述
config 更改配置参数
disassemble 反汇编
dump dump core
exit 结束调试,也可以用 quit

综合示例

最后,我们使用一个的小例子,熟悉下常用的几个命令。

示例程序代码如下:

// main.go

package main

var (
	x = 1024
)

func main() {
	for i := 0; i < 5; i++ {
		println(i)
	}
}

# 开始调试
$ dlv debug main.go

Type 'help' for list of commands.
(dlv)
 
# 增加断点
(dlv) b main.main
Breakpoint 1 set at 0x45f0c6 for main.main() ./main.go:7

# 查看断点
(dlv) bp
...
Breakpoint 2 (enabled) at 0x45f0c6 for main.main() ./main.go:7 (0)

# 运行程序
(dlv) continue
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x45f0c6)
     2:
     3: var (
     4:         x = 1024
     5: )
     6:
=>   7: func main() {
     8:         for i := 0; i < 5; i++ {
     9:                 println(i)
    10:         }
    11: }
    12:
# 可以看到,程序停在了设置的断点上

# 打印包变量
(dlv) vars vars main.x
...
main.x = 1024
...

# 单步调试
(dlv) next
> main.main() ./main.go:8 (PC: 0x45f0d4)
     3: var (
     4:         x = 1024
     5: )
     6:
     7: func main() {
=>   8:         x = 0
     9:         for i := 0; i < 5; i++ {
    10:                 println(i)
    11:         }
    12: }
    13:

# 再次单步调试
(dlv) next
> main.main() ./main.go:9 (PC: 0x45f0df)
     4:         x = 1024
     5: )
     6:
     7: func main() {
     8:         x = 0
=>   9:         for i := 0; i < 5; i++ {
    10:                 println(i)
    11:         }
    12: }
    13:
    14: //timeout := time.After(time.Minute)

# 可以看到,程序停在了循环语句

# 打印包变量
(dlv) vars main.x
main.x = 0

# 再次单步调试
(dlv) next
> main.main() ./main.go:10 (PC: 0x45f0f4)
     5: )
     6:
     7: func main() {
     8:         x = 0
     9:         for i := 0; i < 5; i++ {
=>  10:                 println(i)
    11:         }
    12: }
    13:
    14: //timeout := time.After(time.Minute)
    15: //


# 打印本地变量
(dlv) locals
i = 0

# 查看堆栈信息
(dlv) stack
0  0x000000000045f0f4 in main.main
   at ./main.go:10
1  0x00000000004358b8 in runtime.main
   at /usr/local/go/src/runtime/proc.go:250
2  0x000000000045c0c1 in runtime.goexit
   at /usr/local/go/src/runtime/asm_amd64.s:1594
   
   
# 打印 goroutine 信息
(dlv) goroutine
Thread 27873 at ./main.go:10
Goroutine 1:
...

# 删除所有断点
(dlv) clearall
Breakpoint 1 cleared at 0x45f0c6 for main.main() ./main.go:7

# 继续执行
(dlv) continue
1
2
3
4
Process 27873 has exited with status 0

# 退出调试
(dlv) exit

与 IDE 集成

Delve 支持以插件的形式集成到主流的 IDE 里面,具体的支持列表请看 这个页面

常见问题

单点调试总是执行非预期的代码?

一般是被编译器优化了,比如内联会导致 dlv 单步调试无法打印某些变量,解决方法是禁止编译优化。

# 禁用内联和优化 (细节可以阅读引用文章列表)
go run -gcflags "-N -l" main.go

Reference

扩展阅读