这篇是看雨痕大佬的书所做练习的笔记,(其实后面部分基本都是抄的,但是都实践了)由于电脑抽风,使用的是win10的Linux子系统,功能不完善,很多跟踪支持性不好(可以算是抄的原因)。
    要想看内核源码剖析的可以到雨痕大神的github。

    1.新建hello.go编译得到hello
    2.gdb调试,输入 gdb hello 命令行得到:((gdb) 后都表示输入的))
    先是Linux系统下的:
    (gdb) info files
    Symbols from “C:\Users\WnagoiYy\hello”.
    Local exec file:
    ‘C:\Users\WnagoiYy\hello’, file type elf64-x86-64.
    Entry point: 0x41bc50
    0x0000000000400c00 - 0x000000000041c0ba is .text
    0x000000000041d000 - 0x0000000000435a00 is .rodata
    0x0000000000435a00 - 0x0000000000435af8 is .typelink
    0x0000000000435b00 - 0x0000000000440c91 is .gosymtab
    0x0000000000440ca0 - 0x0000000000454c46 is .gopclntab
    0x0000000000455000 - 0x0000000000455030 is .noptrdata
    0x0000000000455040 - 0x0000000000459688 is .data
    0x00000000004596a0 - 0x0000000000461698 is .bss
    0x00000000004616a0 - 0x0000000000476bf8 is .noptrbss

    接下来是windows下的编译后文件gdb:
    (gdb) info files
    Symbols from “C:\Users\WnagoiYy\go\hello”.
    Local exec file:
    ‘C:\Users\WnagoiYy\go\hello’, file type pei-x86-64.
    Entry point: 0x44d730
    0x0000000000401000 - 0x00000000004953ad is .text
    0x0000000000496000 - 0x0000000000496c00 is .data
    0x0000000000506000 - 0x00000000005064fc is .idata

    Entry point: 0x41bc50 入口地址在.text段

    file type elf64-x86-64. 文件类型为elf

    .text
    代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

    .rodata
    存放字符串和#define定义的常量

    .data
    数据段(datasegment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

    .bss
    BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文BlockStarted by Symbol的简称。BSS段属于静态内存分配。

    (gdb) b 0x41bc50
    Breakpoint 1 at 0x41bc50: file /usr/lib/go/src/pkg/runtime/rt0linux_amd64.s, line 8.
    入口打上一个断点,因为是win10下的linux子系统,所以文件路径不对
    hejing@DESKTOP-EP9L18K:/mnt/c/Go/src/runtime$ ls rt0


    rt0_darwin_arm64.s rt0_linux_amd64.s rt0_nacl_386.s
    rt0_openbsd_amd64.s rt0_windows_amd64.s

    先cd到/mnt/c/Go/src/runtime,然后输入ls rt0_*,会找到许多文件,我们需要上面断点的rt0_linux_amd64.s

    打开rt0_linux_amd64.s,跳到具体行查看代码:

    TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
    LEAQ 8(SP), SI // argv
    MOVQ 0(SP), DI // argc
    MOVQ $main(SB), AX
    JMP AX

    TEXT main(SB),NOSPLIT,$-8
    MOVQ $runtime·rt0_go(SB), AX
    JMP AX

    查看MOVQ $runtime·rt0_go(SB), AX里具体的runtime.rt0_go
    (gdb) b runtime.rt0_go
    Breakpoint 2 at 0x44a780: file /usr/local/go/src/runtime/asm_amd64.s, line 12.
    正是asm_amd64.s完成了初始化和运行时启动:
    TEXT runtime·rt0_go(SB),NOSPLIT,$0

    //调用初始化函数
    CALL runtime·args(SB)
    CALL runtime·osinit(SB)
    CALL runtime·schedinit(SB)
    //创建 main goroutine 用于执行 runtime.main
    MOVQ $runtime·mainPC(SB), AX
    PUSHQ AX
    PUSHQ $0
    CALL runtime·newproc(SB)
    POPQ AX
    POPQ AX
    //让当前线程开始执行 main goroutine
    CALL runtime·mstart(SB)
    RET
    DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
    GLOBL runtime·mainPC(SB),RODATA,$8

    到此,汇编引导全部完成,剩下的由golang实现
    (gdb) b runtime.main
    Breakpoint 3 at 0x423250: file /usr/local/go/src/runtime/proc.go, line 28.

    接下来查看初始化的相关内容:
    由上得知调用了以下初始化函数:
    CALL runtime·args(SB)
    CALL runtime·osinit(SB)
    CALL runtime·schedinit(SB)
    现在查看args函数的位置和内容:
    (gdb) b runtime.args
    Breakpoint 7 at 0x42ebf0: file /usr/local/go/src/runtime/runtime1.go, line 48.

    在runtime1.go可以看见初始化参数函数:

    func args(c int32, v byte) {//整理命令行参数
    argc = c
    argv = v
    sysargs(c, v)
    }
    现在查看osinit函数的位置和内容:
    Breakpoint 8 at 0x41e9d0: file /usr/local/go/src/runtime/os1_linux.go, line 172.
    1
    os1linux.go中可以看见:
    func osinit() { //确定CPU Core的数量
    ncpu = getproccount()
    }
    最为关键的就是
    schedinit,所有运行环境的初始化都在这里:
    Breakpoint 9 at 0x424590: file /usr/local/go/src/runtime/proc1.go, line 40.
    1
    跟进
    proc1.go**:
    // The bootstrap sequence is:
    //
    // call osinit
    // call schedinit
    // make & queue new G
    // call runtime·mstart
    //
    // The new G calls runtime·main.
    func schedinit() {
    // raceinit must be the first call to race detector.
    // In particular, it must be done before mallocinit below calls racemapshadow.
    _g
    := getg()
    if raceenabled {
    g.racectx, raceprocctx0 = raceinit()
    }

    1. //最大系统线程数量限制,具体查看 runtime/debug.SetMaxThreads<br /> // maximum number of m's allowed (or die)<br /> sched.maxmcount = 10000 //sched结构体位于rutime2.go中
    2. //栈,内存分配器,调度器相关初始化<br /> tracebackinit()<br /> moduledataverify()<br /> stackinit()<br /> mallocinit()<br /> mcommoninit(_g_.m)<br /> alginit() // maps must not be used before this call<br /> typelinksinit() // uses maps<br /> itabsinit()
    3. msigsave(_g_.m)<br /> initSigmask = _g_.m.sigmask
    4. //处理命令行参数和环境变量<br /> goargs()<br /> goenvs()
    5. //处理 GODEBUG?GOTRACEBACK 调试相关的环境变量设置<br /> parsedebugvars()
    6. //垃圾回收初始化<br /> gcinit()
    7. //通过CPU Core 和 GOMAXPROCS 环境变量确定 P 数量<br /> sched.lastpoll = uint64(nanotime())<br /> procs := int(ncpu)<br /> if procs > _MaxGomaxprocs {<br /> procs = _MaxGomaxprocs<br /> }<br /> if n := atoi(gogetenv("GOMAXPROCS")); n > 0 {<br /> if n > _MaxGomaxprocs {<br /> n = _MaxGomaxprocs<br /> }<br /> procs = n<br /> }
    8. //调整p的数量<br /> if procresize(int32(procs)) != nil {<br /> throw("unknown runnable goroutine during bootstrap")<br /> }
    9. if buildVersion == "" {<br /> // Condition should never trigger. This code just serves<br /> // to ensure runtime·buildVersion is kept in the resulting binary.<br /> buildVersion = "unknown"<br /> }<br />}<br />初始化操作到此并未结束,因为接下来要执⾏的是 runtime.main,⽽不是⽤户逻 辑⼊⼜函数 main.main。<br />(gdb) b runtime.main<br />Breakpoint 10 at 0x423250: file /usr/local/go/src/runtime/proc.go, line 28.<br />1<br />2<br />查看**proc.go**里的main函数:<br />func main() {<br /> g := getg()<br /> g.m.g0.racectx = 0<br /> //执行栈的最大限制:1GB-on 64bit, 250MB-on 32bit<br /> if sys.PtrSize == 8 {<br /> maxstacksize = 1000000000<br /> } else {<br /> maxstacksize = 250000000<br /> }
    10. runtimeInitTime = nanotime()
    11. //启动系统后台监控(定期垃圾回收,以及并发任务相关)<br /> systemstack(func() {<br /> newm(sysmon, nil)<br /> })
    12. lockOSThread()
    13. if g.m != &m0 {<br /> throw("runtime.main not on m0")<br /> }
    14. //执行runtime里面的所有init函数<br /> runtime_init() // must be before defer
    15. needUnlock := true<br /> defer func() {<br /> if needUnlock {<br /> unlockOSThread()<br /> }<br /> }()
    16. //启动垃圾回收器后台操作<br /> gcenable()
    17. main_init_done = make(chan bool)<br /> if iscgo {<br /> if _cgo_thread_start == nil {<br /> throw("_cgo_thread_start missing")<br /> }<br /> if GOOS != "windows" {<br /> if _cgo_setenv == nil {<br /> throw("_cgo_setenv missing")<br /> }<br /> if _cgo_unsetenv == nil {<br /> throw("_cgo_unsetenv missing")<br /> }<br /> }<br /> if _cgo_notify_runtime_init_done == nil {<br /> throw("_cgo_notify_runtime_init_done missing")<br /> }<br /> cgocall(_cgo_notify_runtime_init_done, nil)<br /> }
    18. //用户main包的init函数初始化调用<br /> main_init()<br /> close(main_init_done)
    19. needUnlock = false<br /> unlockOSThread()
    20. if isarchive || islibrary {<br /> return<br /> }
    21. //执行用户的main函数<br /> main_main()<br /> if raceenabled {<br /> racefini()<br /> }
    22. if panicking != 0 {<br /> gopark(nil, nil, "panicwait", traceEvGoStop, 1)<br /> }
    23. //执行结束,返回函数状态码<br /> exit(0)<br /> for {<br /> var x *int32<br /> *x = 0<br /> }<br />}<br />与之相关的就是 runtime_init 和 main_init 这两个函数,它们都是由编译器动态⽣成<br />//go:linkname runtime_init runtime.init<br />func runtime_init()<br />//go:linkname main_init main.init<br />func main_init()<br />//go:linkname main_main main.main<br />func main_main()<br />注意链接后符号名的变化:runtime_init > runtime.init。<br />我们准备⼀个稍微复杂点的⽰例,看看编译器究竟⼲了什么。<br />src<br />|+- main.go, test.go<br />|+- lib<br />|+- sum.go

    lib/sum.go

    package lib
    func init(){
    println(“sum.init”)
    }
    func Sum(x …int)int{
    n:=0
    for ,i:=range x{
    n+=i
    }
    return n
    }
    test.go
    package main
    import ( “lib”)
    func init() {
    println(“test.init”)
    }
    func test() {
    println(lib.Sum(1, 2, 3))
    }
    main.go
    package main
    import (
    “net/http”
    )
    func init() {
    println(“main.init.2”)
    }
    func main() {
    test()
    }
    func init() {
    println(“main.init.1”)
    }
    编译,执⾏输出。
    $ go build -gcflags “-N -l” -o test
    $ ./test
    sum.init
    main.init.2
    main.init.1
    test.init
    6
    接下来我们⽤反汇编⼯具,看看最终动态⽣成代码的真实⾯⽬:
    TEXT runtime.init.1(SB) c:/go/src/runtime/mstats.go

    mstats.go:175 0x41fc00 CALL runtime.printlock(SB)

    mstats.go:175 0x41fc10 CALL runtime.printint(SB)
    mstats.go:175 0x41fc15 CALL runtime.printsp(SB)
    mstats.go:175 0x41fc1a MOVQ $0x1690, 0(SP)
    mstats.go:175 0x41fc22 CALL runtime.printint(SB)
    mstats.go:175 0x41fc27 CALL runtime.printnl(SB)
    mstats.go:175 0x41fc2c CALL runtime.printunlock(SB)
    mstats.go:176 0x41fc31 LEAQ 0x49ffd(IP), AX
    mstats.go:176 0x41fc38 MOVQ AX, 0(SP)
    mstats.go:176 0x41fc3c MOVQ $0x24, 0x8(SP)
    mstats.go:176 0x41fc45 CALL runtime.throw(SB)
    mstats.go:176 0x41fc4a UD2
    mstats.go:172 0x41fc4c CALL runtime.morestack_noctxt(SB)
    mstats.go:172 0x41fc51 JMP runtime.init.1(SB)
    :-1 0x41fc56 INT $0x3

    TEXT runtime.init.2(SB) c:/go/src/runtime/panic.go

    panic.go:177 0x423966 CALL runtime.writebarrierptr(SB)
    panic.go:178 0x42396b JMP 0x42394c
    panic.go:174 0x42396d CALL runtime.morestack_noctxt(SB)
    panic.go:174 0x423972 JMP runtime.init.2(SB)
    :-1 0x423977 INT $0x3
    ….

    TEXT runtime.init.3(SB) c:/go/src/runtime/proc.go

    proc.go:213 0x426857 CALL runtime.newproc(SB)
    proc.go:214 0x42685c MOVQ 0x10(SP), BP
    proc.go:214 0x426861 ADDQ $0x18, SP
    proc.go:214 0x426865 RET
    proc.go:212 0x426866 CALL runtime.morestack_noctxt(SB)
    proc.go:212 0x42686b JMP runtime.init.3(SB)
    :-1 0x42686d INT $0x3

    TEXT runtime.init(SB) c:/go/src/runtime/zcallback_windows.go

    zcallback_windows.go:6 0x445d53 CALL runtime.throwinit(SB)

    panic.go:23 0x445d95 CALL runtime.convT2I(SB)

    select.go:48 0x445fa9 CALL runtime.funcPC(SB)

    select.go:49 0x445fd1 CALL runtime.funcPC(SB)

    zcallback_windows.go:6 0x445fe2 CALL runtime.init.1(SB)
    zcallback_windows.go:6 0x445fe7 CALL runtime.init.2(SB)
    zcallback_windows.go:6 0x445fec CALL runtime.init.3(SB)

    panic.go:23 0x44609a CALL runtime.writebarrierptr(SB)
    panic.go:30 0x44609f JMP 0x445dc0
    zcallback_windows.go:6 0x4460a4 CALL runtime.morestack_noctxt(SB)
    zcallback_windows.go:6 0x4460a9 JMP runtime.init(SB)
    :-1 0x4460ae INT $0x3
    runtime里面的多个init被赋予唯一的函数名,再由 runtime.init(SB)统一调用,⾄于 main.init,情况基本⼀致。区别在于它负责调⽤⾮ runtime 包的初始化函数:

    $ go tool objdump -s “main.init\b” test
    TEXT main.init.1(SB) src/main.go
    main.go:7 …
    TEXT main.init.2(SB) src/main.go
    main.go:15 …
    TEXT main.init.3(SB) src/test.go
    test.go:7 …
    TEXT main.init(SB) src/test.go
    test.go:13 …
    test.go:13 CALL net/http.init(SB)
    test.go:13 CALL test/lib.init(SB)
    test.go:13 CALL main.init.1(SB)
    test.go:13 CALL main.init.2(SB)
    test.go:13 CALL main.init.3(SB)
    test.go:13 MOVL $0x2, 0x48d543(IP)
    test.go:13 RET
    被引⽤的包,包括 lib 和标准库 net/http ⾥的 init 函数都被 main.init 调⽤。
    *虽然从当前版本的编译器⾓度来说,init 的执⾏顺序和依赖关系、⽂件名以及定义顺序有关。但这种次序⾮常不便于维护和理解,极易造成潜在错误,所以强烈要求让 init 只做该做的事情:局部初始化。
    最后需要记住:
    -所有 init 函数都在同⼀个 goroutine 内执⾏。
    -所有 init 函数结束后才会执⾏ main.main 函数。
    ————————————————
    版权声明:本文为CSDN博主「TenYears"」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/whh995496580/article/details/53423215