3
头图

Original link: Share how to read Go language source code

foreword

Hello, everyone, my name is asong ; 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 it Go 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语言支持GDBLLDBDelve调试器, 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),我们可以查看所goroutinegoroutine (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 the stack 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 the regs 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 the locals 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


asong
605 声望906 粉丝