Go 的源码是用 Go 写的。可以很方便的阅读。
Go 的源码编译需要一个 >= 1.4 版本的编译好的二进制文件来编译,去 golang.org 下载编译包和源码包。
go1.14.6.src.tar.gz
go1.16.4.linux-amd64.tar.gz
当然也可以下载不同版本的源码包和编译包。
解压编译包和源码包
tar -zxvf go1.16.4.linux-amd64.tar.gz
mv go go_bin
tar -zxvf go1.14.6.src.tar.gz
这样得到了两个目录,一个是编译好的 Go,一个源码 Go。查看一下编译好的 Go:
./go_bin/bin/go version
#go version go1.16.4 linux/amd64
设置编译用的目录和参数
export GOROOT_BOOTSTRAP=~/go_bin // 改成自己 go_bin 所在目录
export GO_GCFLAGS="-N -l"
GOROOT_BOOTSTRAP
是放编译好的 Go 二进制文件的目录。 GO_GCFLAGS
是编译用的参数,"-N":表示不需要优化,"-l":表示不需要内联,禁止优化和内联可以让运行时(runtime)的函数更易于调试。
编译 Go 源码
#进入源码
cd ./go/src
#执行编译(调用 make.bash)
./all.bash
编译完成,go/bin 下生成两个可执行文件
go
gofmt
设置环境变量
vim ~/.bashrc
# go 环境
export GOROOT=/home/shijie/go
export GOPATH=/home/shijie/work
export PATH=$PATH:$GOROOT/bin
export PATH=$PATH:$GOPATH/bin
export GO111MODULE=on
export GOPROXY=https://goproxy.io
#应用环境
source ~/.bashrc
Go
go version
#go version go1.16.4 linux/amd64
编译完成
ok,编译完源码,写个小 demo。
package main
import "fmt"
func main(){
strSlice := []string{"bye world","say hi","i hit it"}
newStrSlice := append(strSlice,"fuck u")
fmt.Println(newStrSlice)
}
ok,编译一下 demo,记得不要优化内联
go build -gcflags "-N -l"
编译完,目录下回生成一个 main 的可执行文件。用 gdb 执行它,并开始调试
gdb ./main
# 进入 gdb 调试模式,加断点
# 在main.go 第 6 行断点
(gdb)b main.go:6
# 运行
(gdb)r
# Thread 1 "main" hit Breakpoint 1, main.main () at /home/shijie/work/test/main.go:6
# 24 newStrSlice := append(strSlice,"fuck u")
# 进入 append 源码
(gdb)s
# runtime.growslice (et=0x4a31a0, old=..., cap=7, ~r3=...) at /home/shijie/file/software/go/src/runtime/slice.go:125
# 125 func growslice(et *_type, old slice, cap int) slice {
# 查看代码
(gdb)l
#120 // NOT to the new requested capacity.
#121 // This is for codegen convenience. The old slice's length is used immediately
#122 // to calculate where to write new values during an append.
#123 // TODO: When the old backend is gone, reconsider this decision.
#124 // The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
#125 func growslice(et *_type, old slice, cap int) slice {
#126 if raceenabled {
#127 callerpc := getcallerpc()
#128 racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
#129}
# 这里可以看到进入了源码,在 src/runtime/slice.go 里,append 的动态扩展。
# 查看反汇编
(gdb)disas
Dump of assembler code for function runtime.growslice:
=> 0x0000000000448500 <+0>: mov %fs:0xfffffffffffffff8,%rcx
0x0000000000448509 <+9>: cmp 0x10(%rcx),%rsp
0x000000000044850d <+13>: jbe 0x448c4f <runtime.growslice+1871>
0x0000000000448513 <+19>: sub $0x60,%rsp
0x0000000000448517 <+23>: mov %rbp,0x58(%rsp)
0x000000000044851c <+28>: lea 0x58(%rsp),%rbp
0x0000000000448521 <+33>: mov 0x88(%rsp),%rdx
0x0000000000448529 <+41>: mov 0x80(%rsp),%rbx
0x0000000000448531 <+49>: cmp %rbx,%rdx
0x0000000000448534 <+52>: jl 0x448c32 <runtime.growslice+1842>
0x000000000044853a <+58>: mov 0x68(%rsp),%rsi
0x000000000044853f <+63>: mov (%rsi),%rdi
0x0000000000448542 <+66>: test %rdi,%rdi
0x0000000000448545 <+69>: je 0x448b1b <runtime.growslice+1563>
0x000000000044854b <+75>: lea (%rbx,%rbx,1),%r8
0x000000000044854f <+79>: cmp %r8,%rdx
0x0000000000448552 <+82>: jg 0x448b13 <runtime.growslice+1555>
0x0000000000448558 <+88>: nopl 0x0(%rax,%rax,1)
0x0000000000448560 <+96>: cmp $0x400,%rbx
0x0000000000448567 <+103>: jge 0x448578 <runtime.growslice+120>
还可以 p var 来查看变量的值,gdb 的用法可以网上搜索。在 append 加断点,一直 s 可以看到从 main 里调用 append,整个全过程,从 go 源码到汇编所有的函数调用。
当然你可以修改源码文件,只要不报错,重新编译就可以了。比如:
vim src/runtime/slice.go
// 找到 function growslice,添加 print("test")
然后,进入 go/src 重新编译
./all.bash
在执行 go run main.go,除了输出 clice,还会输出 test。
大家觉得对自己有帮助的,可以 收藏,以后翻出来看。帮转发,更是感激不尽,一起交流学习,互相促进。
据说这个二维码 点击扫描 可以关注。
长按识别二维码,关注 起点编程
觉得有用的点击右下 “在看"