【Golang源码分析】golang的启动原理

前言:

随着技术的发展,大多的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

当前调用栈

print

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值