mirror of
https://github.com/chai2010/advanced-go-programming-book.git
synced 2025-05-24 04:22:22 +00:00
ch3: 增加Delve调试器内容
This commit is contained in:
parent
1a22fa10c8
commit
fd70b3b73a
3
.gitignore
vendored
3
.gitignore
vendored
@ -31,3 +31,6 @@ _book
|
|||||||
a.out
|
a.out
|
||||||
|
|
||||||
.oracle_jre_usage
|
.oracle_jre_usage
|
||||||
|
|
||||||
|
# Delve
|
||||||
|
debug
|
||||||
|
@ -30,7 +30,8 @@
|
|||||||
* [3.5. 控制流](ch3-asm/ch3-05-control-flow.md)
|
* [3.5. 控制流](ch3-asm/ch3-05-control-flow.md)
|
||||||
* [3.6. 再论函数](ch3-asm/ch3-06-func-again.md)
|
* [3.6. 再论函数](ch3-asm/ch3-06-func-again.md)
|
||||||
* [3.7. 例子:Goroutine ID](ch3-asm/ch3-07-goroutine-id.md)
|
* [3.7. 例子:Goroutine ID](ch3-asm/ch3-07-goroutine-id.md)
|
||||||
* [3.8. 补充说明](ch3-asm/ch3-08-faq.md)
|
* [3.8. Delve调试器](ch3-asm/ch3-08-debug.md)
|
||||||
|
* [3.9. 补充说明](ch3-asm/ch3-09-faq.md)
|
||||||
* [第四章 RPC和Protobuf](ch4-rpc/readme.md)
|
* [第四章 RPC和Protobuf](ch4-rpc/readme.md)
|
||||||
* [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
|
* [4.1. RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
|
||||||
* [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md)
|
* [4.2. Protobuf](ch4-rpc/ch4-02-pb-intro.md)
|
||||||
|
331
ch3-asm/ch3-08-debug.md
Normal file
331
ch3-asm/ch3-08-debug.md
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
# 3.8. Delve调试器
|
||||||
|
|
||||||
|
目前Go语言支持GDB、LLDB和Delve几种调试器。其中GDB是最早支持的调试工具,LLDB是macOS系统推荐的标准调试工具。但是GDB和LLDB对Go语言的专有特性都缺乏很大支持,而只有Delve是专门为Go语言设计开发的调试工具。而且Delve本身也是采用Go语言开发,对Windows平台也提供了一样的支持。本节我们基于Delve简单解释如何调试Go汇编程序。
|
||||||
|
|
||||||
|
## Delve入门
|
||||||
|
|
||||||
|
首先根据官方的文档正确安装Delve调试器。我们会先构造一个简单的Go语言代码,用于熟悉下Delve的简单用法。
|
||||||
|
|
||||||
|
创建main.go文件,main函数先通过循初始化一个切片,然后输出切片的内容:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
nums := make([]int, 5)
|
||||||
|
for i := 0; i < len(nums); i++ {
|
||||||
|
nums[i] = i * i
|
||||||
|
}
|
||||||
|
fmt.Println(nums)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
命令行进入包所在目录,然后输入`dlv debug`命令进入调试:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ dlv debug
|
||||||
|
Type 'help' for list of commands.
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
输入help命令可以查看到Delve提供的调试命令列表:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) help
|
||||||
|
The following commands are available:
|
||||||
|
args ------------------------ Print function arguments.
|
||||||
|
break (alias: b) ------------ Sets a breakpoint.
|
||||||
|
breakpoints (alias: bp) ----- Print out info for active breakpoints.
|
||||||
|
clear ----------------------- Deletes breakpoint.
|
||||||
|
clearall -------------------- Deletes multiple breakpoints.
|
||||||
|
condition (alias: cond) ----- Set breakpoint condition.
|
||||||
|
config ---------------------- Changes configuration parameters.
|
||||||
|
continue (alias: c) --------- Run until breakpoint or program termination.
|
||||||
|
disassemble (alias: disass) - Disassembler.
|
||||||
|
down ------------------------ Move the current frame down.
|
||||||
|
exit (alias: quit | q) ------ Exit the debugger.
|
||||||
|
frame ----------------------- Set the current frame, or execute command on a different frame.
|
||||||
|
funcs ----------------------- Print list of functions.
|
||||||
|
goroutine ------------------- Shows or changes current goroutine
|
||||||
|
goroutines ------------------ List program goroutines.
|
||||||
|
help (alias: h) ------------- Prints the help message.
|
||||||
|
list (alias: ls | l) -------- Show source code.
|
||||||
|
locals ---------------------- Print local variables.
|
||||||
|
next (alias: n) ------------- Step over to next source line.
|
||||||
|
on -------------------------- Executes a command when a breakpoint is hit.
|
||||||
|
print (alias: p) ------------ Evaluate an expression.
|
||||||
|
regs ------------------------ Print contents of CPU registers.
|
||||||
|
restart (alias: r) ---------- Restart process.
|
||||||
|
set ------------------------- Changes the value of a variable.
|
||||||
|
source ---------------------- Executes a file containing a list of delve commands
|
||||||
|
sources --------------------- Print list of source files.
|
||||||
|
stack (alias: bt) ----------- Print stack trace.
|
||||||
|
step (alias: s) ------------- Single step through program.
|
||||||
|
step-instruction (alias: si) Single step a single cpu instruction.
|
||||||
|
stepout --------------------- Step out of the current function.
|
||||||
|
thread (alias: tr) ---------- Switch to the specified thread.
|
||||||
|
threads --------------------- Print out info for every traced thread.
|
||||||
|
trace (alias: t) ------------ Set tracepoint.
|
||||||
|
types ----------------------- Print list of types
|
||||||
|
up -------------------------- Move the current frame up.
|
||||||
|
vars ------------------------ Print package variables.
|
||||||
|
whatis ---------------------- Prints type of an expression.
|
||||||
|
Type help followed by a command for full documentation.
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
每个Go程序的入口是main.main函数,我们可以用break在此设置一个断点:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) break main.main
|
||||||
|
Breakpoint 1 set at 0x10ae9b8 for main.main() ./main.go:7
|
||||||
|
```
|
||||||
|
|
||||||
|
然后通过breakpoints查看已经设置的所有断点:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) breakpoints
|
||||||
|
Breakpoint unrecovered-panic at 0x102a380 for runtime.startpanic() /usr/local/go/src/runtime/panic.go:588 (0)
|
||||||
|
print runtime.curg._panic.arg
|
||||||
|
Breakpoint 1 at 0x10ae9b8 for main.main() ./main.go:7 (0)
|
||||||
|
```
|
||||||
|
|
||||||
|
我们发现除了我们自己设置的main.main邯郸断点外,Delve内部已经为panic异常函数设置了一个断点。
|
||||||
|
|
||||||
|
然后就可以通过continue命令让程序运行到下一个断点处:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) continue
|
||||||
|
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x10ae9b8)
|
||||||
|
2:
|
||||||
|
3: import (
|
||||||
|
4: "fmt"
|
||||||
|
5: )
|
||||||
|
6:
|
||||||
|
=> 7: func main() {
|
||||||
|
8: nums := make([]int, 5)
|
||||||
|
9: for i := 0; i < len(nums); i++ {
|
||||||
|
10: nums[i] = i * i
|
||||||
|
11: }
|
||||||
|
12: fmt.Println(nums)
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
输入next命令单步执行进入main函数内部:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) next
|
||||||
|
> main.main() ./main.go:8 (PC: 0x10ae9cf)
|
||||||
|
3: import (
|
||||||
|
4: "fmt"
|
||||||
|
5: )
|
||||||
|
6:
|
||||||
|
7: func main() {
|
||||||
|
=> 8: nums := make([]int, 5)
|
||||||
|
9: for i := 0; i < len(nums); i++ {
|
||||||
|
10: nums[i] = i * i
|
||||||
|
11: }
|
||||||
|
12: fmt.Println(nums)
|
||||||
|
13: }
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
进入函数之后可以通过args和locals命令查看函数的参数和局部变量:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) args
|
||||||
|
(no args)
|
||||||
|
(dlv) locals
|
||||||
|
nums = []int len: 842350763880, cap: 17491881, nil
|
||||||
|
```
|
||||||
|
|
||||||
|
因为main函数没有参数,因此args命令没有任何输出。而locals命令则输出了局部变量nums切片的值:此时切片还未完成初始化,切片的底层指针为nil,长度和容量都是一个随机数值。
|
||||||
|
|
||||||
|
再次输入next命令单步执行后就可以查看到nums切片初始化之后的结果了:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) next
|
||||||
|
> main.main() ./main.go:9 (PC: 0x10aea12)
|
||||||
|
4: "fmt"
|
||||||
|
5: )
|
||||||
|
6:
|
||||||
|
7: func main() {
|
||||||
|
8: nums := make([]int, 5)
|
||||||
|
=> 9: for i := 0; i < len(nums); i++ {
|
||||||
|
10: nums[i] = i * i
|
||||||
|
11: }
|
||||||
|
12: fmt.Println(nums)
|
||||||
|
13: }
|
||||||
|
(dlv) locals
|
||||||
|
nums = []int len: 5, cap: 5, [...]
|
||||||
|
i = 17601536
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
此时因为调试器已经到了for语句行,因此局部变量出现了还未初始化的循环迭代变量i。
|
||||||
|
|
||||||
|
下面我们通过组合使用break和condition命令,在循环内部设置一个条件断点,当循环变量i等于3时断点生效:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) break main.go:10
|
||||||
|
Breakpoint 2 set at 0x10aea33 for main.main() ./main.go:10
|
||||||
|
(dlv) condition 2 i==3
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
然后通过continue执行到刚设置的条件断点,并且输出局部变量:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) continue
|
||||||
|
> main.main() ./main.go:10 (hits goroutine(1):1 total:1) (PC: 0x10aea33)
|
||||||
|
5: )
|
||||||
|
6:
|
||||||
|
7: func main() {
|
||||||
|
8: nums := make([]int, 5)
|
||||||
|
9: for i := 0; i < len(nums); i++ {
|
||||||
|
=> 10: nums[i] = i * i
|
||||||
|
11: }
|
||||||
|
12: fmt.Println(nums)
|
||||||
|
13: }
|
||||||
|
(dlv) locals
|
||||||
|
nums = []int len: 5, cap: 5, [...]
|
||||||
|
i = 3
|
||||||
|
(dlv) print nums
|
||||||
|
[]int len: 5, cap: 5, [0,1,4,0,0]
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
我们发现当循环变量i等于3时,nums切片的前3个元素已经正确初始化。
|
||||||
|
|
||||||
|
我们还可以通过stack查看当前执行函数的栈帧信息:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) stack
|
||||||
|
0 0x00000000010aea33 in main.main
|
||||||
|
at ./main.go:10
|
||||||
|
1 0x000000000102bd60 in runtime.main
|
||||||
|
at /usr/local/go/src/runtime/proc.go:198
|
||||||
|
2 0x0000000001053bd1 in runtime.goexit
|
||||||
|
at /usr/local/go/src/runtime/asm_amd64.s:2361
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
或者通过goroutine和goroutines命令查看当前Goroutine相关的信息:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) goroutine
|
||||||
|
Thread 101686 at ./main.go:10
|
||||||
|
Goroutine 1:
|
||||||
|
Runtime: ./main.go:10 main.main (0x10aea33)
|
||||||
|
User: ./main.go:10 main.main (0x10aea33)
|
||||||
|
Go: /usr/local/go/src/runtime/asm_amd64.s:258 runtime.rt0_go (0x1051643)
|
||||||
|
Start: /usr/local/go/src/runtime/proc.go:109 runtime.main (0x102bb90)
|
||||||
|
(dlv) goroutines
|
||||||
|
[4 goroutines]
|
||||||
|
* Goroutine 1 - User: ./main.go:10 main.main (0x10aea33) (thread 101686)
|
||||||
|
Goroutine 2 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x102c189)
|
||||||
|
Goroutine 3 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x102c189)
|
||||||
|
Goroutine 4 - User: /usr/local/go/src/runtime/proc.go:292 runtime.gopark (0x102c189)
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
最后完成调试工作后输入quit命令退出调试器。至此我们已经掌握了Delve调试器器的简单用法。
|
||||||
|
|
||||||
|
## 调试汇编程序
|
||||||
|
|
||||||
|
用Delve调试Go汇编程序的过程比调试Go语言程序更加简单。调试汇编程序时,我们需要时刻关注寄存器的状态,如果涉及函数调用或局部变量或参数还需要重点关注栈寄存器SP的状态。
|
||||||
|
|
||||||
|
为了编译演示,我们用汇编重新实现main函数,简单打印一个字符串:
|
||||||
|
|
||||||
|
```
|
||||||
|
#include "textflag.h"
|
||||||
|
#include "funcdata.h"
|
||||||
|
|
||||||
|
// "Hello World!\n"
|
||||||
|
DATA text<>+0(SB)/8,$"Hello Wo"
|
||||||
|
DATA text<>+8(SB)/8,$"rld!\n"
|
||||||
|
GLOBL text<>(SB),NOPTR,$16
|
||||||
|
|
||||||
|
// func main()
|
||||||
|
TEXT ·main(SB), $16-0
|
||||||
|
NO_LOCAL_POINTERS
|
||||||
|
MOVQ $text<>+0(SB), AX
|
||||||
|
MOVQ AX, (SP)
|
||||||
|
MOVQ $16, 8(SP)
|
||||||
|
CALL runtime·printstring(SB)
|
||||||
|
RET
|
||||||
|
```
|
||||||
|
|
||||||
|
然后依然用break命令在main函数设置断点,并且输入continue命令让调试器执行到断点位置停下:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) break main.main
|
||||||
|
Breakpoint 1 set at 0x105018f for main.main() ./main_amd64.s:10
|
||||||
|
(dlv) continue
|
||||||
|
> main.main() ./main_amd64.s:10 (hits goroutine(1):1 total:1) (PC: 0x105018f)
|
||||||
|
5: DATA text<>+0(SB)/8,$"Hello Wo"
|
||||||
|
6: DATA text<>+8(SB)/8,$"rld!\n"
|
||||||
|
7: GLOBL text<>(SB),NOPTR,$16
|
||||||
|
8:
|
||||||
|
9: // func main()
|
||||||
|
=> 10: TEXT ·main(SB), $16-0
|
||||||
|
11: NO_LOCAL_POINTERS
|
||||||
|
12: MOVQ $text<>+0(SB), AX
|
||||||
|
13: MOVQ AX, (SP)
|
||||||
|
14: MOVQ $16, 8(SP)
|
||||||
|
15: CALL runtime·printstring(SB)
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
此时我们可以通过regs查看全部的寄存器状态:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) regs
|
||||||
|
rax = 0x0000000001050180
|
||||||
|
rbx = 0x0000000000000000
|
||||||
|
rcx = 0x000000c420000300
|
||||||
|
rdx = 0x0000000001070bc0
|
||||||
|
rdi = 0x000000c42007c020
|
||||||
|
rsi = 0x0000000000000001
|
||||||
|
rbp = 0x00007fffffe00000
|
||||||
|
rsp = 0x000000c420049f80
|
||||||
|
r8 = 0x7fffffffffffffff
|
||||||
|
r9 = 0xffffffffffffffff
|
||||||
|
r10 = 0x0000000000000100
|
||||||
|
r11 = 0x0000000000000286
|
||||||
|
r12 = 0x000000c41fffff7c
|
||||||
|
r13 = 0x0000000000000000
|
||||||
|
r14 = 0x0000000000000178
|
||||||
|
r15 = 0x0000000000000004
|
||||||
|
rip = 0x000000000105018f
|
||||||
|
rflags = 0x0000000000000202
|
||||||
|
...
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
因为AMD64的各种寄存器非常多,项目的信息中刻意省略了非通用的寄存器。如果再单步执行到13行时,可以发现AX寄存器值的变化。
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) regs
|
||||||
|
rax = 0x00000000010a4060
|
||||||
|
rbx = 0x0000000000000000
|
||||||
|
rcx = 0x000000c420000300
|
||||||
|
...
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
因此我们可以推断汇编程序内部定义的`text<>`数据的地址为0x00000000010a4060。我们可以用过print命令来查看该内存内的数据:
|
||||||
|
|
||||||
|
```
|
||||||
|
(dlv) print *(*[5]byte)(uintptr(0x00000000010a4060))
|
||||||
|
[5]uint8 [72,101,108,108,111]
|
||||||
|
(dlv)
|
||||||
|
```
|
||||||
|
|
||||||
|
我们可以发现输出的`[5]uint8 [72,101,108,108,111]`刚好是对应“Hello”字符串。通过类似的方法,我们可以通过查看SP对应的栈指针位置,然后查看栈中局部变量的值。
|
||||||
|
|
||||||
|
至此我们就掌握了Go汇编程序的简单调试技术。
|
@ -1,3 +1,3 @@
|
|||||||
# 3.8. 补充说明
|
# 3.9. 补充说明
|
||||||
|
|
||||||
得益于Go语言的设计,Go汇编语言的优势也非常明显:跨操作系统、不同CPU之间的用法也非常相似、支持C语言预处理器、支持模块。同时Go汇编语言也存在很多不足:它不是一个独立的语言,底层需要依赖Go语言甚至操作系统;很多高级特性很难通过手工汇编完成。虽然Go语言官方尽量保持Go汇编语言简单,但是汇编语言是一个比较大的话题,大到足以写一本Go汇编语言的教程。本章的目的是让大家对Go汇编语言简单入门,在看到底层汇编代码的时候不会一头雾水,在某些遇到性能受限制的场合能够通过Go汇编突破限制。这只是一个开始,后续版本会继续完善。
|
得益于Go语言的设计,Go汇编语言的优势也非常明显:跨操作系统、不同CPU之间的用法也非常相似、支持C语言预处理器、支持模块。同时Go汇编语言也存在很多不足:它不是一个独立的语言,底层需要依赖Go语言甚至操作系统;很多高级特性很难通过手工汇编完成。虽然Go语言官方尽量保持Go汇编语言简单,但是汇编语言是一个比较大的话题,大到足以写一本Go汇编语言的教程。本章的目的是让大家对Go汇编语言简单入门,在看到底层汇编代码的时候不会一头雾水,在某些遇到性能受限制的场合能够通过Go汇编突破限制。这只是一个开始,后续版本会继续完善。
|
7
vendor/gobook.examples/ch3-08-debug/hello-asm/main.go
vendored
Normal file
7
vendor/gobook.examples/ch3-08-debug/hello-asm/main.go
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// Copyright 2018 <chaishushan{AT}gmail.com>. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main()
|
16
vendor/gobook.examples/ch3-08-debug/hello-asm/main_amd64.s
vendored
Normal file
16
vendor/gobook.examples/ch3-08-debug/hello-asm/main_amd64.s
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include "textflag.h"
|
||||||
|
#include "funcdata.h"
|
||||||
|
|
||||||
|
// "Hello World!\n"
|
||||||
|
DATA text<>+0(SB)/8,$"Hello Wo"
|
||||||
|
DATA text<>+8(SB)/8,$"rld!\n"
|
||||||
|
GLOBL text<>(SB),NOPTR,$16
|
||||||
|
|
||||||
|
// func main()
|
||||||
|
TEXT ·main(SB), $16-0
|
||||||
|
NO_LOCAL_POINTERS
|
||||||
|
MOVQ $text<>+0(SB), AX
|
||||||
|
MOVQ AX, (SP)
|
||||||
|
MOVQ $16, 8(SP)
|
||||||
|
CALL runtime·printstring(SB)
|
||||||
|
RET
|
13
vendor/gobook.examples/ch3-08-debug/hello/main.go
vendored
Normal file
13
vendor/gobook.examples/ch3-08-debug/hello/main.go
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
nums := make([]int, 5)
|
||||||
|
for i := 0; i < len(nums); i++ {
|
||||||
|
nums[i] = i * i
|
||||||
|
}
|
||||||
|
fmt.Println(nums)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user