Skip to content

Commit c8b48f1

Browse files
committed
修改排版 ch3/*
1 parent 4ae96e8 commit c8b48f1

11 files changed

+425
-425
lines changed

ch3-asm/ch3-01-basic.md

Lines changed: 48 additions & 48 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-02-arch.md

Lines changed: 45 additions & 45 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-03-const-and-var.md

Lines changed: 66 additions & 66 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-04-func.md

Lines changed: 43 additions & 43 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-05-control-flow.md

Lines changed: 42 additions & 42 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-06-func-again.md

Lines changed: 54 additions & 54 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-07-hack-asm.md

Lines changed: 31 additions & 31 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-08-goroutine-id.md

Lines changed: 46 additions & 46 deletions
Large diffs are not rendered by default.

ch3-asm/ch3-09-debug.md

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# 3.9 Delve调试器
1+
# 3.9 Delve 调试器
22

3-
目前Go语言支持GDB、LLDB和Delve几种调试器。其中GDB是最早支持的调试工具,LLDB是macOS系统推荐的标准调试工具。但是GDB和LLDB对Go语言的专有特性都缺乏很大支持,而只有Delve是专门为Go语言设计开发的调试工具。而且Delve本身也是采用Go语言开发,对Windows平台也提供了一样的支持。本节我们基于Delve简单解释如何调试Go汇编程序
3+
目前 Go 语言支持 GDB、LLDB 和 Delve 几种调试器。其中 GDB 是最早支持的调试工具,LLDB 是 macOS 系统推荐的标准调试工具。但是 GDB 和 LLDB 对 Go 语言的专有特性都缺乏很大支持,而只有 Delve 是专门为 Go 语言设计开发的调试工具。而且 Delve 本身也是采用 Go 语言开发,对 Windows 平台也提供了一样的支持。本节我们基于 Delve 简单解释如何调试 Go 汇编程序
44

5-
## 3.9.1 Delve入门
5+
## 3.9.1 Delve 入门
66

7-
首先根据官方的文档正确安装Delve调试器。我们会先构造一个简单的Go语言代码,用于熟悉下Delve的简单用法
7+
首先根据官方的文档正确安装 Delve 调试器。我们会先构造一个简单的 Go 语言代码,用于熟悉下 Delve 的简单用法
88

9-
创建main.go文件,main函数先通过循初始化一个切片,然后输出切片的内容:
9+
创建 main.go 文件,main 函数先通过循初始化一个切片,然后输出切片的内容:
1010

1111
```go
1212
package main
@@ -17,22 +17,22 @@ import (
1717

1818
func main() {
1919
nums := make([]int, 5)
20-
for i := 0; i < len(nums); i++ {
20+
for i := 0; i <len(nums); i++ {
2121
nums[i] = i * i
2222
}
2323
fmt.Println(nums)
2424
}
2525
```
2626

27-
命令行进入包所在目录,然后输入`dlv debug`命令进入调试:
27+
命令行进入包所在目录,然后输入 `dlv debug` 命令进入调试:
2828

2929
```
3030
$ dlv debug
3131
Type 'help' for list of commands.
3232
(dlv)
3333
```
3434

35-
输入help命令可以查看到Delve提供的调试命令列表
35+
输入 help 命令可以查看到 Delve 提供的调试命令列表
3636

3737
```
3838
(dlv) help
@@ -78,14 +78,14 @@ Type help followed by a command for full documentation.
7878
(dlv)
7979
```
8080

81-
每个Go程序的入口是main.main函数,我们可以用break在此设置一个断点
81+
每个 Go 程序的入口是 main.main 函数,我们可以用 break 在此设置一个断点
8282

8383
```
8484
(dlv) break main.main
8585
Breakpoint 1 set at 0x10ae9b8 for main.main() ./main.go:7
8686
```
8787

88-
然后通过breakpoints查看已经设置的所有断点
88+
然后通过 breakpoints 查看已经设置的所有断点
8989

9090
```
9191
(dlv) breakpoints
@@ -95,9 +95,9 @@ Breakpoint unrecovered-panic at 0x102a380 for runtime.startpanic()
9595
Breakpoint 1 at 0x10ae9b8 for main.main() ./main.go:7 (0)
9696
```
9797

98-
我们发现除了我们自己设置的main.main函数断点外,Delve内部已经为panic异常函数设置了一个断点
98+
我们发现除了我们自己设置的 main.main 函数断点外,Delve 内部已经为 panic 异常函数设置了一个断点
9999

100-
通过vars命令可以查看全部包级的变量。因为最终的目标程序可能含有大量的全局变量,我们可以通过一个正则参数选择想查看的全局变量:
100+
通过 vars 命令可以查看全部包级的变量。因为最终的目标程序可能含有大量的全局变量,我们可以通过一个正则参数选择想查看的全局变量:
101101

102102
```
103103
(dlv) vars main
@@ -107,7 +107,7 @@ runtime.mainStarted = true
107107
(dlv)
108108
```
109109

110-
然后就可以通过continue命令让程序运行到下一个断点处
110+
然后就可以通过 continue 命令让程序运行到下一个断点处
111111

112112
```
113113
(dlv) continue
@@ -119,14 +119,14 @@ runtime.mainStarted = true
119119
6:
120120
=> 7: func main() {
121121
8: nums := make([]int, 5)
122-
9: for i := 0; i < len(nums); i++ {
122+
9: for i := 0; i <len(nums); i++ {
123123
10: nums[i] = i * i
124124
11: }
125125
12: fmt.Println(nums)
126126
(dlv)
127127
```
128128

129-
输入next命令单步执行进入main函数内部
129+
输入 next 命令单步执行进入 main 函数内部
130130

131131
```
132132
(dlv) next
@@ -137,15 +137,15 @@ runtime.mainStarted = true
137137
6:
138138
7: func main() {
139139
=> 8: nums := make([]int, 5)
140-
9: for i := 0; i < len(nums); i++ {
140+
9: for i := 0; i <len(nums); i++ {
141141
10: nums[i] = i * i
142142
11: }
143143
12: fmt.Println(nums)
144144
13: }
145145
(dlv)
146146
```
147147

148-
进入函数之后可以通过args和locals命令查看函数的参数和局部变量
148+
进入函数之后可以通过 args 和 locals 命令查看函数的参数和局部变量
149149

150150
```
151151
(dlv) args
@@ -154,9 +154,9 @@ runtime.mainStarted = true
154154
nums = []int len: 842350763880, cap: 17491881, nil
155155
```
156156

157-
因为main函数没有参数,因此args命令没有任何输出。而locals命令则输出了局部变量nums切片的值:此时切片还未完成初始化,切片的底层指针为nil,长度和容量都是一个随机数值。
157+
因为 main 函数没有参数,因此 args 命令没有任何输出。而 locals 命令则输出了局部变量 nums 切片的值:此时切片还未完成初始化,切片的底层指针为 nil,长度和容量都是一个随机数值。
158158

159-
再次输入next命令单步执行后就可以查看到nums切片初始化之后的结果了
159+
再次输入 next 命令单步执行后就可以查看到 nums 切片初始化之后的结果了
160160

161161
```
162162
(dlv) next
@@ -166,7 +166,7 @@ nums = []int len: 842350763880, cap: 17491881, nil
166166
6:
167167
7: func main() {
168168
8: nums := make([]int, 5)
169-
=> 9: for i := 0; i < len(nums); i++ {
169+
=> 9: for i := 0; i <len(nums); i++ {
170170
10: nums[i] = i * i
171171
11: }
172172
12: fmt.Println(nums)
@@ -177,9 +177,9 @@ i = 17601536
177177
(dlv)
178178
```
179179

180-
此时因为调试器已经到了for语句行,因此局部变量出现了还未初始化的循环迭代变量i
180+
此时因为调试器已经到了 for 语句行,因此局部变量出现了还未初始化的循环迭代变量 i
181181

182-
下面我们通过组合使用break和condition命令,在循环内部设置一个条件断点,当循环变量i等于3时断点生效
182+
下面我们通过组合使用 break 和 condition 命令,在循环内部设置一个条件断点,当循环变量 i 等于 3 时断点生效
183183

184184
```
185185
(dlv) break main.go:10
@@ -188,7 +188,7 @@ Breakpoint 2 set at 0x10aea33 for main.main() ./main.go:10
188188
(dlv)
189189
```
190190

191-
然后通过continue执行到刚设置的条件断点,并且输出局部变量:
191+
然后通过 continue 执行到刚设置的条件断点,并且输出局部变量:
192192

193193
```
194194
(dlv) continue
@@ -197,7 +197,7 @@ Breakpoint 2 set at 0x10aea33 for main.main() ./main.go:10
197197
6:
198198
7: func main() {
199199
8: nums := make([]int, 5)
200-
9: for i := 0; i < len(nums); i++ {
200+
9: for i := 0; i <len(nums); i++ {
201201
=> 10: nums[i] = i * i
202202
11: }
203203
12: fmt.Println(nums)
@@ -210,9 +210,9 @@ i = 3
210210
(dlv)
211211
```
212212

213-
我们发现当循环变量i等于3时,nums切片的前3个元素已经正确初始化
213+
我们发现当循环变量 i 等于 3 时,nums 切片的前 3 个元素已经正确初始化
214214

215-
我们还可以通过stack查看当前执行函数的栈帧信息
215+
我们还可以通过 stack 查看当前执行函数的栈帧信息
216216

217217
```
218218
(dlv) stack
@@ -225,7 +225,7 @@ i = 3
225225
(dlv)
226226
```
227227

228-
或者通过goroutine和goroutines命令查看当前Goroutine相关的信息
228+
或者通过 goroutine 和 goroutines 命令查看当前 Goroutine 相关的信息
229229

230230
```
231231
(dlv) goroutine
@@ -247,13 +247,13 @@ Goroutine 1:
247247
(dlv)
248248
```
249249

250-
最后完成调试工作后输入quit命令退出调试器。至此我们已经掌握了Delve调试器器的简单用法
250+
最后完成调试工作后输入 quit 命令退出调试器。至此我们已经掌握了 Delve 调试器器的简单用法
251251

252252
## 3.9.2 调试汇编程序
253253

254-
用Delve调试Go汇编程序的过程比调试Go语言程序更加简单。调试汇编程序时,我们需要时刻关注寄存器的状态,如果涉及函数调用或局部变量或参数还需要重点关注栈寄存器SP的状态
254+
用 Delve 调试 Go 汇编程序的过程比调试 Go 语言程序更加简单。调试汇编程序时,我们需要时刻关注寄存器的状态,如果涉及函数调用或局部变量或参数还需要重点关注栈寄存器 SP 的状态
255255

256-
为了编译演示,我们重新实现一个更简单的main函数
256+
为了编译演示,我们重新实现一个更简单的 main 函数
257257

258258
```go
259259
package main
@@ -263,9 +263,9 @@ func main() { asmSayHello() }
263263
func asmSayHello()
264264
```
265265

266-
在main函数中调用汇编语言实现的asmSayHello函数输出一个字符串
266+
在 main 函数中调用汇编语言实现的 asmSayHello 函数输出一个字符串
267267

268-
asmSayHello函数在main_amd64.s文件中实现
268+
asmSayHello 函数在 main_amd64.s 文件中实现
269269

270270
```
271271
#include "textflag.h"
@@ -286,7 +286,7 @@ TEXT ·asmSayHello(SB), $16-0
286286
RET
287287
```
288288

289-
参考前面的调试流程,在执行到main函数断点时,可以disassemble反汇编命令查看main函数对应的汇编代码
289+
参考前面的调试流程,在执行到 main 函数断点时,可以 disassemble 反汇编命令查看 main 函数对应的汇编代码
290290

291291
```
292292
(dlv) break main.main
@@ -315,11 +315,11 @@ TEXT main.main(SB) /path/to/pkg/main.go
315315
(dlv)
316316
```
317317

318-
虽然main函数内部只有一行函数调用语句,但是却生成了很多汇编指令。在函数的开头通过比较rsp寄存器判断栈空间是否不足,如果不足则跳转到0x1050139地址调用runtime.morestack函数进行栈扩容,然后跳回到main函数开始位置重新进行栈空间测试。而在asmSayHello函数调用之前,先扩展rsp空间用于临时存储rbp寄存器的状态,在函数返回后通过栈恢复rbp的值并回收临时栈空间。通过对比Go语言代码和对应的汇编代码,我们可以加深对Go汇编语言的理解
318+
虽然 main 函数内部只有一行函数调用语句,但是却生成了很多汇编指令。在函数的开头通过比较 rsp 寄存器判断栈空间是否不足,如果不足则跳转到 0x1050139 地址调用 runtime.morestack 函数进行栈扩容,然后跳回到 main 函数开始位置重新进行栈空间测试。而在 asmSayHello 函数调用之前,先扩展 rsp 空间用于临时存储 rbp 寄存器的状态,在函数返回后通过栈恢复 rbp 的值并回收临时栈空间。通过对比 Go 语言代码和对应的汇编代码,我们可以加深对 Go 汇编语言的理解
319319

320-
从汇编语言角度深刻Go语言各种特性的工作机制对调试工作也是一个很大的帮助。如果希望在汇编指令层面调试Go代码,Delve还提供了一个step-instruction单步执行汇编指令的命令
320+
从汇编语言角度深刻 Go 语言各种特性的工作机制对调试工作也是一个很大的帮助。如果希望在汇编指令层面调试 Go 代码,Delve 还提供了一个 step-instruction 单步执行汇编指令的命令
321321

322-
现在我们依然用break命令在asmSayHello函数设置断点,并且输入continue命令让调试器执行到断点位置停下
322+
现在我们依然用 break 命令在 asmSayHello 函数设置断点,并且输入 continue 命令让调试器执行到断点位置停下
323323

324324
```
325325
(dlv) break main.asmSayHello
@@ -340,7 +340,7 @@ Breakpoint 2 set at 0x10501bf for main.asmSayHello() ./main_amd64.s:10
340340
(dlv)
341341
```
342342

343-
此时我们可以通过regs查看全部的寄存器状态
343+
此时我们可以通过 regs 查看全部的寄存器状态
344344

345345
```
346346
(dlv) regs
@@ -366,7 +366,7 @@ Breakpoint 2 set at 0x10501bf for main.asmSayHello() ./main_amd64.s:10
366366
(dlv)
367367
```
368368

369-
因为AMD64的各种寄存器非常多,项目的信息中刻意省略了非通用的寄存器。如果再单步执行到13行时,可以发现AX寄存器值的变化
369+
因为 AMD64 的各种寄存器非常多,项目的信息中刻意省略了非通用的寄存器。如果再单步执行到 13 行时,可以发现 AX 寄存器值的变化
370370

371371
```
372372
(dlv) regs
@@ -377,14 +377,14 @@ Breakpoint 2 set at 0x10501bf for main.asmSayHello() ./main_amd64.s:10
377377
(dlv)
378378
```
379379

380-
因此我们可以推断汇编程序内部定义的`text<>`数据的地址为0x00000000010a4060。我们可以用过print命令来查看该内存内的数据
380+
因此我们可以推断汇编程序内部定义的 `text<>` 数据的地址为 0x00000000010a4060。我们可以用过 print 命令来查看该内存内的数据
381381

382382
```
383383
(dlv) print *(*[5]byte)(uintptr(0x00000000010a4060))
384384
[5]uint8 [72,101,108,108,111]
385385
(dlv)
386386
```
387387

388-
我们可以发现输出的`[5]uint8 [72,101,108,108,111]`刚好是对应“Hello”字符串。通过类似的方法,我们可以通过查看SP对应的栈指针位置,然后查看栈中局部变量的值。
388+
我们可以发现输出的 `[5]uint8 [72,101,108,108,111]` 刚好是对应 “Hello” 字符串。通过类似的方法,我们可以通过查看 SP 对应的栈指针位置,然后查看栈中局部变量的值。
389389

390-
至此我们就掌握了Go汇编程序的简单调试技术
390+
至此我们就掌握了 Go 汇编程序的简单调试技术

ch3-asm/ch3-10-ext.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
## 3.10 补充说明
22

3-
如果是纯粹学习汇编语言,则可以从《深入理解程序设计:使用Linux汇编语言》开始,该书讲述了如何以C语言的思维实现汇编程序。如果是学习X86汇编,则可以从《汇编语言:基于x86处理器》开始,然后再结合《现代x86汇编语言程序设计》学习AVX等高级汇编指令的使用
3+
如果是纯粹学习汇编语言,则可以从《深入理解程序设计:使用 Linux 汇编语言》开始,该书讲述了如何以 C 语言的思维实现汇编程序。如果是学习 X86 汇编,则可以从《汇编语言:基于 x86 处理器》开始,然后再结合《现代 x86 汇编语言程序设计》学习 AVX 等高级汇编指令的使用
44

5-
Go汇编语言的官方文档非常匮乏。其中“A Quick Guide to Go's Assembler”是唯一的一篇系统讲述Go汇编语言的官方文章,该文章中又引入了另外两篇Plan9的文档:A Manual for the Plan 9 assembler 和 Plan 9 C Compilers。Plan9的两篇文档分别讲述了汇编语言以及和汇编有关联的C语言编译器的细节。看过这几篇文档之后会对Go汇编语言有了一些模糊的概念,剩下的就是在实战中通过代码学习了。
5+
Go 汇编语言的官方文档非常匮乏。其中 “A Quick Guide to Go's Assembler” 是唯一的一篇系统讲述 Go 汇编语言的官方文章,该文章中又引入了另外两篇 Plan9 的文档:A Manual for the Plan 9 assembler 和 Plan 9 C Compilers。Plan9 的两篇文档分别讲述了汇编语言以及和汇编有关联的 C 语言编译器的细节。看过这几篇文档之后会对 Go 汇编语言有了一些模糊的概念,剩下的就是在实战中通过代码学习了。
66

7-
Go语言的编译器和汇编器都带了一个`-S`参数,可以查看生成的最终目标代码。通过对比目标代码和原始的Go语言或Go汇编语言代码的差异可以加深对底层实现的理解。同时Go语言连接器的实现代码也包含了很多相关的信息。Go汇编语言是依托Go语言的语言,因此理解Go语言的工作原理是也是必要的。比较重要的部分是Go语言runtime和reflect包的实现原理。如果读者了解CGO技术,那么对Go汇编语言的学习也是一个巨大的帮助。最后是要了解syscall包是如何实现系统调用的
7+
Go 语言的编译器和汇编器都带了一个 `-S` 参数,可以查看生成的最终目标代码。通过对比目标代码和原始的 Go 语言或 Go 汇编语言代码的差异可以加深对底层实现的理解。同时 Go 语言连接器的实现代码也包含了很多相关的信息。Go 汇编语言是依托 Go 语言的语言,因此理解 Go 语言的工作原理是也是必要的。比较重要的部分是 Go 语言 runtime 和 reflect 包的实现原理。如果读者了解 CGO 技术,那么对 Go 汇编语言的学习也是一个巨大的帮助。最后是要了解 syscall 包是如何实现系统调用的
88

9-
得益于Go语言的设计,Go汇编语言的优势也非常明显:跨操作系统、不同CPU之间的用法也非常相似、支持C语言预处理器、支持模块。同时Go汇编语言也存在很多不足:它不是一个独立的语言,底层需要依赖Go语言甚至操作系统;很多高级特性很难通过手工汇编完成。虽然Go语言官方尽量保持Go汇编语言简单,但是汇编语言是一个比较大的话题,大到足以写一本Go汇编语言的教程。本章的目的是让大家对Go汇编语言简单入门,在看到底层汇编代码的时候不会一头雾水,在某些遇到性能受限制的场合能够通过Go汇编突破限制
9+
得益于 Go 语言的设计,Go 汇编语言的优势也非常明显:跨操作系统、不同 CPU 之间的用法也非常相似、支持 C 语言预处理器、支持模块。同时 Go 汇编语言也存在很多不足:它不是一个独立的语言,底层需要依赖 Go 语言甚至操作系统;很多高级特性很难通过手工汇编完成。虽然 Go 语言官方尽量保持 Go 汇编语言简单,但是汇编语言是一个比较大的话题,大到足以写一本 Go 汇编语言的教程。本章的目的是让大家对 Go 汇编语言简单入门,在看到底层汇编代码的时候不会一头雾水,在某些遇到性能受限制的场合能够通过 Go 汇编突破限制
1010

0 commit comments

Comments
 (0)