图片


关注爱因诗贤

每天进步一点点


导读

    随着技术的发展,大多的PHPer都开始转型golang。这个也是golang因为go的一些特别深受大家喜爱。我们团队也在试水,在内部的一些小项目上做了试探。在此同时我作为试探的一员身先士卒,沙场练兵。从中也琢磨到一些技巧希望能和大家一起学习共勉。


一、前期准备:

      go版本:go1.13.4

        由于是做源码分析调试,我们必须找到适合自己的工具。咱们调试的话可以考虑几种工具

        1)gdb    linux使用比较合适

        2)lldb   mac自带,不过建议dlv

        3)dlv    go开发的调试工具


        本文中主要以dlv为主,接下来我们一起来安装一下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参数介绍(常用)


命令(全)命令(简写)备注restartr重新启动程序continuec运行到断点或者程序终止breakb设置断点breakpointsbp打印设置断点nextn执行下一行listl查看当前代码steps进入下一层stackbt当前调用栈printp打印变量

 

图片

 


二.调试:

     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退出情况

 

图片


本文使用 文章同步助手 同步