Original link: Share how to read Go language source code
foreword
Hello, everyone, my name isasong
; I was looking at the source code related to the Go language scheduler recently, and found that reading the source code is really a technical activity, so this article briefly summarizes how to view itGo
Source code, I hope it will help you.
What does the Go source code include?
According to my personal understanding, Go
source code is mainly divided into two parts, one is the official standard library, the other is the underlying implementation of the ---5551e12cdb543ea09dc5fd73759017a8 Go
language, Go
language All source code/standard library/compiler are in the src
directory: https://github.com/golang/go/tree/master/src , you can choose the source code of the library you want to see;
Watch Go
standard library and Go
the source code difficulty of the underlying implementation is also different, we can generally start from the standard library, select the module you are interested in, and put it Understand, after having this foundation, we are looking at Go
the source code of the underlying implementation of the language will be a little easier; let me share my personal learning experience on how to view Go
source code ;
View standard library source code
The source code of the standard library looks a little easier, because the standard library also belongs to the upper-level application, we can use the help of the IDE, it can jump to the source code package on the IDE, we only need to keep jumping back and forth to see the implementation of each function Just take good notes, because some source code designs are more complicated, it is best to help you by drawing when reading, I personally think that drawing UML
is the most helpful to understand and can be more clearly clarified the relationship of the various entities;
Sometimes it is difficult to understand just by looking at the code. At this time, we use online debugging to help us understand. Using the debugger provided by the IDE or GDB
can achieve the purpose, write a simple demo
, once the breakpoint hits, the single-step debugging starts. For example, if you want to view the source code of fmt.Println
, start with a small red dot, and then just a little bit;
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/020ee5d660264362b42dab2a142bc369~tplv-k3u1fbpfcp-zoom-1.image" />
View the underlying implementation of the Go language
People are always curious about the unknown field. After using the language for a period of time Go
, they want to understand some things more deeply, for example: what is the startup process of the Go program, goroutine
is it scheduled, map
how is it implemented, etc Go
The underlying implementation, this kind of jumping and tracing code directly cannot be done by relying on IDE, all of which belong to Go
The internal implementation of the language is mostly implemented in the runtime
src
directory, which implements garbage collection, concurrency control, stack management and other Go The key features of the language, when compiling Go
code is also compiled into machine code, runtime
is Go
The library used when the program is executed, so Some Go
the underlying principles are all in this package, we need to use some methods to view Go
the code when the program is executed, here are two methods: analysis assembly code, dlv debugging;
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/43155623d60042d98539d4885bb73020~tplv-k3u1fbpfcp-zoom-1.image" alt="" />
Analyze assembly code
We have already introduced the Go
language implementation runtime
library, we want to see some Go
language keyword feature corresponding to runtime
For that function, you can view the assembly code, Go
language assembly uses plan9
, and x86
assembly is very different, many friends are not familiar with plan9
The assembly of plan9
, but if you want to understand Go
the source code still needs to be plan9
the assembly has a basic understanding, here we recommend Cao Da 's article: We can look at the source code with a little bit of assembly. For example, if we want to see make
how to initialize slice
, then we can write a simple demo
:
// main.go
import "fmt"
func main() {
s := make([]int, 10, 20)
fmt.Println(s)
}
There are two ways to view assembly code:
1. go tool compile -S -N -l main.go
2. go build main.go && go tool objdump ./main
The first method is to compile the source code into a .o
file, and output the assembly code. The second method is to disassemble. Here, the first method is recommended. After executing the first method, we can see the corresponding assembly code as follows:
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6071b1f1459144f5b891c3c4d60559df~tplv-k3u1fbpfcp-zoom-1.image" />
s := make([]int, 10, 20)
的源代码就是runtime.makeslice(SB)
,这runtime
包下找makeslice
函数,不断追踪下去就可查看The source code is implemented, which can be found in runtime/slice.go
:
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d9905faa0ef2479b997279691ddba98f~tplv-k3u1fbpfcp-zoom-1.image" />
Online debugging
Although the above method can help us locate the source code, the follow-up operation depends entirely on review
is still difficult to understand, if we can debug the trace code online, it can help us understand better, currently Go
语言支持GDB
、 LLDB
、 Delve
调试器, Delve
Go
语言设计开发的调试工具, Delve
可以轻松调试Go
程序, Delve
的入门文章有很多,这篇就不在介绍Delve
For the detailed usage, you can read Cao Da's article to get started: https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-09-debug.html , this article uses a small Let's take a look at the example dlv
how to debug Go
source code, everyone knows that adding elements to a slice nil
will not have any problems, in the source code How is it achieved? Next, let's use dlv
debug and trace, first write a small demo
:
import "fmt"
func main() {
var s []int
s = append(s, 1)
fmt.Println(s)
}
Enter the command line package directory, then enter dlv debug
to enter debugging
$ dlv debug
Type 'help' for list of commands.
(dlv)
Because here we want to see the internal implementation of append
0293ce1ad4c69893683c0f6158548981---, so add a breakpoint on the line append
and execute the following command:
(dlv) break main.go:7
Breakpoint 1 set at 0x10aba57 for main.main() ./main.go:7
Execute the continue
command and run to the breakpoint:
(dlv) continue
> main.main() ./main.go:7 (hits goroutine(1):1 total:1) (PC: 0x10aba57)
2:
3: import "fmt"
4:
5: func main() {
6: var s []int
=> 7: s = append(s, 1)
8: fmt.Println(s)
9: }
Next we execute disassemble
disassembly command to view main
the assembly code corresponding to the function:
(dlv) disassemble
TEXT main.main(SB) /Users/go/src/asong.cloud/Golang_Dream/code_demo/src_code/main.go
main.go:5 0x10aba20 4c8d6424e8 lea r12, ptr [rsp-0x18]
main.go:5 0x10aba25 4d3b6610 cmp r12, qword ptr [r14+0x10]
main.go:5 0x10aba29 0f86f6000000 jbe 0x10abb25
main.go:5 0x10aba2f 4881ec98000000 sub rsp, 0x98
main.go:5 0x10aba36 4889ac2490000000 mov qword ptr [rsp+0x90], rbp
main.go:5 0x10aba3e 488dac2490000000 lea rbp, ptr [rsp+0x90]
main.go:6 0x10aba46 48c744246000000000 mov qword ptr [rsp+0x60], 0x0
main.go:6 0x10aba4f 440f117c2468 movups xmmword ptr [rsp+0x68], xmm15
main.go:7 0x10aba55 eb00 jmp 0x10aba57
=> main.go:7 0x10aba57* 488d05a2740000 lea rax, ptr [rip+0x74a2]
main.go:7 0x10aba5e 31db xor ebx, ebx
main.go:7 0x10aba60 31c9 xor ecx, ecx
main.go:7 0x10aba62 4889cf mov rdi, rcx
main.go:7 0x10aba65 be01000000 mov esi, 0x1
main.go:7 0x10aba6a e871c3f9ff call $runtime.growslice
main.go:7 0x10aba6f 488d5301 lea rdx, ptr [rbx+0x1]
main.go:7 0x10aba73 eb00 jmp 0x10aba75
main.go:7 0x10aba75 48c70001000000 mov qword ptr [rax], 0x1
main.go:7 0x10aba7c 4889442460 mov qword ptr [rsp+0x60], rax
main.go:7 0x10aba81 4889542468 mov qword ptr [rsp+0x68], rdx
main.go:7 0x10aba86 48894c2470 mov qword ptr [rsp+0x70], rcx
main.go:8 0x10aba8b 440f117c2450 movups xmmword ptr [rsp+0x50], xmm15
main.go:8 0x10aba91 488d542450 lea rdx, ptr [rsp+0x50]
main.go:8 0x10aba96 4889542448 mov qword ptr [rsp+0x48], rdx
main.go:8 0x10aba9b 488b442460 mov rax, qword ptr [rsp+0x60]
main.go:8 0x10abaa0 488b5c2468 mov rbx, qword ptr [rsp+0x68]
main.go:8 0x10abaa5 488b4c2470 mov rcx, qword ptr [rsp+0x70]
main.go:8 0x10abaaa e8f1dff5ff call $runtime.convTslice
main.go:8 0x10abaaf 4889442440 mov qword ptr [rsp+0x40], rax
main.go:8 0x10abab4 488b542448 mov rdx, qword ptr [rsp+0x48]
main.go:8 0x10abab9 8402 test byte ptr [rdx], al
main.go:8 0x10ababb 488d35be640000 lea rsi, ptr [rip+0x64be]
main.go:8 0x10abac2 488932 mov qword ptr [rdx], rsi
main.go:8 0x10abac5 488d7a08 lea rdi, ptr [rdx+0x8]
main.go:8 0x10abac9 833d30540d0000 cmp dword ptr [runtime.writeBarrier], 0x0
main.go:8 0x10abad0 7402 jz 0x10abad4
main.go:8 0x10abad2 eb06 jmp 0x10abada
main.go:8 0x10abad4 48894208 mov qword ptr [rdx+0x8], rax
main.go:8 0x10abad8 eb08 jmp 0x10abae2
main.go:8 0x10abada e8213ffbff call $runtime.gcWriteBarrier
main.go:8 0x10abadf 90 nop
main.go:8 0x10abae0 eb00 jmp 0x10abae2
main.go:8 0x10abae2 488b442448 mov rax, qword ptr [rsp+0x48]
main.go:8 0x10abae7 8400 test byte ptr [rax], al
main.go:8 0x10abae9 eb00 jmp 0x10abaeb
main.go:8 0x10abaeb 4889442478 mov qword ptr [rsp+0x78], rax
main.go:8 0x10abaf0 48c784248000000001000000 mov qword ptr [rsp+0x80], 0x1
main.go:8 0x10abafc 48c784248800000001000000 mov qword ptr [rsp+0x88], 0x1
main.go:8 0x10abb08 bb01000000 mov ebx, 0x1
main.go:8 0x10abb0d 4889d9 mov rcx, rbx
main.go:8 0x10abb10 e8aba8ffff call $fmt.Println
main.go:9 0x10abb15 488bac2490000000 mov rbp, qword ptr [rsp+0x90]
main.go:9 0x10abb1d 4881c498000000 add rsp, 0x98
main.go:9 0x10abb24 c3 ret
main.go:5 0x10abb25 e8f61efbff call $runtime.morestack_noctxt
.:0 0x10abb2a e9f1feffff jmp $main.main
From the above content we see that the runtime.growslice
method is called, we add a breakpoint here:
(dlv) break runtime.growslice
Breakpoint 2 set at 0x1047dea for runtime.growslice() /usr/local/opt/go/libexec/src/runtime/slice.go:162
Then we execute again continue
to the breakpoint:
(dlv) continue
> runtime.growslice() /usr/local/opt/go/libexec/src/runtime/slice.go:162 (hits goroutine(1):1 total:1) (PC: 0x1047dea)
Warning: debugging optimized function
157: // NOT to the new requested capacity.
158: // This is for codegen convenience. The old slice's length is used immediately
159: // to calculate where to write new values during an append.
160: // TODO: When the old backend is gone, reconsider this decision.
161: // The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
=> 162: func growslice(et *_type, old slice, cap int) slice {
163: if raceenabled {
164: callerpc := getcallerpc()
165: racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
166: }
167: if msanenabled {
After that, the continuous single-step debugging can see the expansion strategy of the slice; here everyone understands why there is no problem in appending data to the slice of nil
, because when the capacity is not enough, it will call growslice
function is expanded. You can continue to follow the specific expansion rules and slap the nonsense articles on Facebook.
Above we introduced a basic process for debugging assembly, and here are two commands that I often use when looking at source code;
- goroutines命令:
goroutines
命令(简写grs),我们可以查看所goroutine
,goroutine (alias: gr)
查看当前的gourtine
:
(dlv) grs
* Goroutine 1 - User: ./main.go:7 main.main (0x10aba6f) (thread 218565)
Goroutine 2 - User: /usr/local/opt/go/libexec/src/runtime/proc.go:367 runtime.gopark (0x1035232) [force gc (idle)]
Goroutine 3 - User: /usr/local/opt/go/libexec/src/runtime/proc.go:367 runtime.gopark (0x1035232) [GC sweep wait]
Goroutine 4 - User: /usr/local/opt/go/libexec/src/runtime/proc.go:367 runtime.gopark (0x1035232) [GC scavenge wait]
Goroutine 5 - User: /usr/local/opt/go/libexec/src/runtime/proc.go:367 runtime.gopark (0x1035232) [finalizer wait]
-
stack
Command: Through thestack
command (abbreviated bt), we can view the current function call stack information:
(dlv) bt
0 0x0000000001047e15 in runtime.growslice
at /usr/local/opt/go/libexec/src/runtime/slice.go:183
1 0x00000000010aba6f in main.main
at ./main.go:7
2 0x0000000001034e13 in runtime.main
at /usr/local/opt/go/libexec/src/runtime/proc.go:255
3 0x000000000105f9c1 in runtime.goexit
at /usr/local/opt/go/libexec/src/runtime/asm_amd64.s:1581
-
regs
Command: Through theregs
command, you can view all the register status, and you can observe the change of the register by single-step execution:
(dlv) regs
Rip = 0x0000000001047e15
Rsp = 0x000000c00010de68
Rax = 0x00000000010b2f00
Rbx = 0x0000000000000000
Rcx = 0x0000000000000000
Rdx = 0x0000000000000008
Rsi = 0x0000000000000001
Rdi = 0x0000000000000000
Rbp = 0x000000c00010ded0
R8 = 0x0000000000000000
R9 = 0x0000000000000008
R10 = 0x0000000001088c40
R11 = 0x0000000000000246
R12 = 0x000000c00010df60
R13 = 0x0000000000000000
R14 = 0x000000c0000001a0
R15 = 0x00000000000000c8
Rflags = 0x0000000000000202 [IF IOPL=0]
Cs = 0x000000000000002b
Fs = 0x0000000000000000
Gs = 0x0000000000000000
-
locals
Command: Through thelocals
command, you can view all variable values of the current function:
(dlv) locals
newcap = 1
doublecap = 0
Summarize
There is no shortcut to the process of reading the source code. If there is, you can first read some articles about the underlying principles output by the big guys, and then refer to their articles to read the source code step by step. In the end, you have to overcome this difficulty yourself. This article introduces some ways for me to view the source code myself. Do you have an easier way? Welcome to share in the comment area.
Well, this article ends here, I'm asong , see you next time.
Welcome to the public account: Golang Dream Factory
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。