Share how to read Go language source code

Original link: Share how to read Go language source code

foreword

Hello, everyone, I am asong; I have been looking at the source code related to the Go language scheduler recently, and found that looking at the source code is really a technical task, so this article briefly summarizes how to view the Gosource code, I hope it will help you.

What does the Go source code include?

In my personal understanding, the Gosource code is mainly divided into two parts, one is the official standard library, and the other is Gothe underlying implementation of the Golanguage. All the source code/standard library/compiler of the language are in the srcdirectory: github.com/golang/go/ t…

The difficulty of viewing the source code of the Gostandard library and Gothe underlying implementation is also different. We can generally start with the standard library, select the modules you are interested in, and understand it thoroughly. With this foundation, we will look at Gothe underlying implementation of the language. The source code will be a little easier; the following is a little bit of my personal learning experience to share how to view the Gosource 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 notes, because some source codes are relatively complicated in design, it is best to help you by drawing pictures when reading. I personally think that drawing UMLis the most helpful for understanding, and can clarify the relationship between various entities more clearly;

Sometimes it is difficult to understand just by looking at the code. At this time, we use online debugging to help us understand. We can use the debugger provided by the IDE to GDBachieve the goal. Write a simple demoone, hit a dozen breakpoints, and step through the debugging process, such as fmt.PrintlnThe source code you want to view , start with a little red dot, and then a little dot;

View the underlying implementation of the Go language

人都是会对未知领域充满好奇,当使用一段时间Go语言后,就想更深入的搞明白一些事情,例如:Go程序的启动过程是怎样的,goroutine是怎么调度的,map是怎么实现的等等一些Go底层的实现,这种直接依靠IDE跳转追溯代码是办不到的,这些都属于Go语言的内部实现,大都在src目录下的runtime包内实现,其实现了垃圾回收,并发控制, 栈管理以及其他一些 Go 语言的关键特性,在编译Go代码为机器代码时也会将其也编译进来,runtime就是Go程序执行时候使用的库,所以一些Go底层原理都在这个包内,我们需要借助一些方式才能查看到Go程序执行时的代码,这里分享两种方式:分析汇编代码、dlv调试;

分析汇编代码

前面我们已经介绍了Go语言实现了runtime库,我们想看到一些Go语言关键字特性对应runtime里的那个函数,可以查看汇编代码,Go语言的汇编使用的plan9,与x86汇编差别还是很大,很多朋友都不熟悉plan9的汇编,但是要想看懂Go源码还是要对plan9汇编有一个基本的了解的,这里推荐曹大的文章:plan9 assembly 完全解析,会一点汇编我们就可以看源代码了,比如想在我们想看make是怎么初始化slice的,这时我们可以先写一个简单的demo

// main.go
import "fmt"

func main() {
	s := make([]int, 10, 20)
	fmt.Println(s)
}
复制代码

有两种方式可以查看汇编代码:

1. go tool compile -S -N -l main.go
2. go build main.go && go tool objdump ./main
复制代码

方式一是将源代码编译成.o文件,并输出汇编代码,方式二是反汇编,这里推荐使用方式一,执行方式一命令后,我们可以看到对应的汇编代码如下:

s := make([]int, 10, 20)对应的源代码就是 runtime.makeslice(SB),这时候我们就去runtime包下找makeslice函数,不断追踪下去就可查看源码实现了,可在runtime/slice.go中找到:

在线调试

虽然上面的方法可以帮助我们定位到源代码,但是后续的操作全靠review还是难于理解的,如果能在线调试跟踪代码可以更好助于我们理解,目前Go语言支持GDBLLDBDelve调试器,但只有Delve是专门为Go语言设计开发的调试工具,所以使用Delve可以轻松调试Go汇编程序,Delve的入门文章有很多,这篇就不在介绍Delve的详细使用方法,入门大家可以看曹大的文章:chai2010.cn/advanced-go…

import "fmt"

func main() {
	var s []int
	s = append(s, 1)
	fmt.Println(s)
}
复制代码

进入命令行包目录,然后输入dlv debug进入调试

$ dlv debug
Type 'help' for list of commands.
(dlv)
复制代码

因为这里我们想看到append的内部实现,所以在append那行加上断点,执行如下命令:

(dlv) break main.go:7
Breakpoint 1 set at 0x10aba57 for main.main() ./main.go:7
复制代码

执行continue命令,运行到断点处:

(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: }
复制代码

接下来我们执行disassemble反汇编命令查看main函数对应的汇编代码:

(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
复制代码

从以上内容我们看到调用了runtime.growslice方法,我们在这里加一个断点:

(dlv) break runtime.growslice
Breakpoint 2 set at 0x1047dea for runtime.growslice() /usr/local/opt/go/libexec/src/runtime/slice.go:162
复制代码

之后我们再次执行continue执行到该断点处:

(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, you can see the expansion strategy of the slice through continuous single-step debugging. At this point, everyone understands why nilthere is no problem in appending data to the slice, because when the capacity is not enough, the growslicefunction will be called to expand the capacity. You can continue to track and slap those blindly written articles on the face of the Internet.

Above we introduced a basic process for debugging assembly, and here are two commands that I often use when looking at source code;

  • goroutines command: Through the goroutinescommand (abbreviated grs), we can view the current goroutine, through the goroutine (alias: gr)command, we can view the current 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]
复制代码
  • stackCommand: Through the stackcommand (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
复制代码
  • regsCommand: regsYou can view all register states through commands, and you can observe the changes of registers 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
复制代码
  • localsCommand: Through the localscommand, 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

Guess you like

Origin juejin.im/post/7098350828540887047