随着技术的发展,大多的PHPer都开始转型golang。这个也是golang因为go的一些特别深受大家喜爱。我们团队也在试水,在内部的一些小项目上做了试探。在此同时我作为试探的一员身先士卒,沙场练兵。从中也琢磨到一些技巧希望能和大家一起学习共勉。
一、前期准备:
go版本:go1.13.4
由于是做源码分析调试,我们必须找到适合自己的工具。咱们调试的话可以考虑几种工具
1)gdb linux使用比较合适
2)lldb mac自带,不过建议dlv
3)dlv go开发的调试工具
本文中主要以dlv为主,接下来我们一起来安装一下dlv。
第一步下载
#go get -u github.com/derekparker/delve/cmd/dlv
第二步编译:
#git clone https://github.com/go-delve/delve.git $GOPATH/src/github.com/go-delve/delve
#cd $GOPATH/src/github.com/go-delve/
#make install
$GOPATH 是golang的环境变量,我使用的是Linux,建议大家在/etc/profile设置,mac相同。
windows设置gopath
https://jingyan.baidu.com/article/5d368d1eb616133f60c057bf.html
第三步设置环境变量:
#vim /etc/profile
export PATH=$PATH:/data/gopath/bin
/data/gopath/bin 为你自己的go的bin目录
初始化环境变量
#source /etc/profile
dlv参数介绍(常用)
命令(全) | 命令(简写) | 备注 |
restart | r | 重新启动程序 |
continue | c | 运行到断点或者程序终止 |
break | b | 设置断点 |
breakpoints | bp | 打印设置断点 |
next | n | 执行下一行 |
list | l | 查看当前代码 |
step | s | 进入下一层 |
stack | bt | 当前调用栈 |
p | 打印变量 |
二.调试:
1.编译调试文件
代码内容
package main
import (
"fmt"
)
func main() {
var p **int
var i int = 10
var p1 *int = &i
fmt.Println("p1=", p1)
p = &p1
fmt.Println("p=", p)
}
编译
#go build -gcflags=all="-N -l" pointer.go
必须这样编译,待能用dlv打印导出变量信息;
2.载入文件
#dlv exec ./pointer
设置rt0_go断点,程序入口
(dlv) break runtime.rt0_go
是不是很好奇为什么入口在runtime.rt0_go
可以打开程序后输入r,在输入list,然后在输入si;si是单步cpu指令;
其实go在运行时会根据系统和CPU的不同,找到底层代码下的,rt0_**.s的汇编代码,汇编代码中再去调转到runtime.rt0_go 。
当然由于系统的不同可执行程序的形式不同。
常见的可执行程序可以分为三大类:
1)PE文件
PE文件主要是Windows系列系统,可执行文件索引;
2)ELF文件
ELF文件是linux系列系统,可执行文件索引;
3)mach-o文件
mach-o是mac系列系统的可执行文件格式,苹果系统是基于FreeBSD的,属于unix-like操作系统;
有一篇比较不错的文章可以推荐给大家:
https://blog.csdn.net/abc_12366/article/details/88205670
好的我们继续往下说,我们进入
我们按住n执行停留到212行
我们可以看到runtime.args 、runtime.osinit和runtime.schedinit三个函数调用。我们可以依次输入si进入函数体内查看
(div)si
args函数主要用途整理命令行参数;
osint函数是确定CPU Core数量
schedinit源码如下:
schedinit主要作用是所有运行时环境初始化;我们继续下一个断点,然后往下执行:
(dlv)b runtime.main
(dlv)n
runtime/proc.go部分源码
func main() {
g := getg()
g.m.g0.racectx = 0
// 执行栈最大限制 64位系统1GB,32位系统250MB
if sys.PtrSize == 8 {
maxstacksize = 1000000000
} else {
maxstacksize = 250000000
}
// 允许newproc启动新Ms。
mainStarted = true
if GOARCH != "wasm" { // wasm上还没有线程,所以没有sysmon
// 启动系统后监控(并发任务调度相关)
systemstack(func() {
newm(sysmon, nil)
})
}
...
//启动垃圾回收器后台操作
gcenable()
...
//进行间接调用,因为链接器在放置运行时不知道主包的地址。这个就是我们程序文件入口
//其实就是用户main.main函数
fn := main_main
fn()
//执行结束
exit(0)
}
我们执行到fn()出可以按s
(dlv)s
这样就来到了我们程序的代码;这时我们可以看一下我们的调度栈
执行完成程序完成之后又回到runtime/proc.go中if atomic.Load(&runningPanicDefers) 处
继续往下执行,一直到exit(0)处,按si执行。
程序回到sys_linux_amd64.s汇编代码中的runtime.exit
最终程序结束;
三.调用流程:
非goroutine退出情况