1. 程式人生 > >go原始碼分析(一) 通過除錯看go程式初始化過程

go原始碼分析(一) 通過除錯看go程式初始化過程

參考資料:Go 1.5 原始碼剖析 (書籤版).pdf

編寫go語言test.go

package main
import (
	"fmt"
)
func main(){
	fmt.Println("Hello World")
}

 帶除錯的編譯程式碼

go build -gcflags "-N -l" -o test test.go

 使用gdb進行除錯 輸入info files 檢視入口點,對於同一個程式來說 每一次執行的入口點是一樣的,表明這是一個將對位置

 gdb test
(gdb) info files
Symbols from "/root/test/test".
Local exec file:
	`/root/test/test', file type elf64-x86-64.
	Entry point: 0x44f4d0
	0x0000000000401000 - 0x0000000000482178 is .text
	0x0000000000483000 - 0x00000000004c4a5a is .rodata
	0x00000000004c4b80 - 0x00000000004c56c8 is .typelink
	0x00000000004c56c8 - 0x00000000004c5708 is .itablink
	0x00000000004c5708 - 0x00000000004c5708 is .gosymtab
	0x00000000004c5720 - 0x000000000051343f is .gopclntab
	0x0000000000514000 - 0x0000000000520bdc is .noptrdata
	0x0000000000520be0 - 0x00000000005276f0 is .data
	0x0000000000527700 - 0x0000000000543d88 is .bss
	0x0000000000543da0 - 0x0000000000546438 is .noptrbss
	0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid

 設定斷點 b *0x44f4d0 每個程式的入口點可能不一樣

(gdb) b *0x44f4d0
Breakpoint 1 at 0x44f4d0: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.

 可以檢視檔案 /usr/local/go/src/runtime/rt0_linux_amd64.s

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "textflag.h"

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
	JMP	_rt0_amd64(SB) 上一步的斷點位置,也就是入口

TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0
	JMP	_rt0_amd64_lib(SB)

 設定斷點runtime.rt0_go

(gdb) b runtime.rt0_go
Breakpoint 2 at 0x44be10: file /usr/local/go/src/runtime/asm_amd64.s, line 89.

 檢視/usr/local/go/src/runtime/asm_amd64.s 也就是函式真正的入口,組合語言寫的

TEXT runtime·rt0_go(SB),NOSPLIT,$0
        // copy arguments forward on an even stack
        MOVQ    DI, AX          // argc
        MOVQ    SI, BX          // argv
        SUBQ    $(4*8+7), SP            // 2args 2auto
        ANDQ    $~15, SP
        MOVQ    AX, 16(SP)
        MOVQ    BX, 24(SP)

        // create istack out of the given (operating system) stack.
        // _cgo_init may update stackguard.
        MOVQ    $runtime·g0(SB), DI
        LEAQ    (-64*1024+104)(SP), BX
        MOVQ    BX, g_stackguard0(DI)
        MOVQ    BX, g_stackguard1(DI)
        MOVQ    BX, (g_stack+stack_lo)(DI)
        MOVQ    SP, (g_stack+stack_hi)(DI)

        // find out information about the processor we're on
        MOVL    $0, AX
        CPUID
        MOVL    AX, SI
        CMPL    AX, $0
        JE      nocpuinfo

        // Figure out how to serialize RDTSC.
        // On Intel processors LFENCE is enough. AMD requires MFENCE.
        // Don't know about the rest, so let's do MFENCE.
        CMPL    BX, $0x756E6547  // "Genu"
        JNE     notintel
        CMPL    DX, $0x49656E69  // "ineI"
        JNE     notintel
        CMPL    CX, $0x6C65746E  // "ntel"
        JNE     notintel
        MOVB    $1, runtime·isIntel(SB)
        MOVB    $1, runtime·lfenceBeforeRdtsc(SB)
notintel:

        // Load EAX=1 cpuid flags
        MOVL    $1, AX
        CPUID
        MOVL    AX, runtime·processorVersionInfo(SB)

        TESTL   $(1<<26), DX // SSE2
        SETNE   runtime·support_sse2(SB)

        TESTL   $(1<<9), CX // SSSE3
        SETNE   runtime·support_ssse3(SB)

        TESTL   $(1<<19), CX // SSE4.1
        SETNE   runtime·support_sse41(SB)

        TESTL   $(1<<20), CX // SSE4.2
        SETNE   runtime·support_sse42(SB)

        TESTL   $(1<<23), CX // POPCNT
        SETNE   runtime·support_popcnt(SB)

        TESTL   $(1<<25), CX // AES
        SETNE   runtime·support_aes(SB)

        TESTL   $(1<<27), CX // OSXSAVE
        SETNE   runtime·support_osxsave(SB)

        // If OS support for XMM and YMM is not present
        // support_avx will be set back to false later.
        TESTL   $(1<<28), CX // AVX
        SETNE   runtime·support_avx(SB)

eax7:
        // Load EAX=7/ECX=0 cpuid flags
        CMPL    SI, $7
        JLT     osavx
        MOVL    $7, AX
        MOVL    $0, CX
        CPUID

        TESTL   $(1<<3), BX // BMI1
        SETNE   runtime·support_bmi1(SB)

        // If OS support for XMM and YMM is not present
        // support_avx2 will be set back to false later.
        TESTL   $(1<<5), BX
        SETNE   runtime·support_avx2(SB)

        TESTL   $(1<<8), BX // BMI2
        SETNE   runtime·support_bmi2(SB)

        TESTL   $(1<<9), BX // ERMS
        SETNE   runtime·support_erms(SB)

osavx:
        CMPB    runtime·support_osxsave(SB), $1
        JNE     noavx
        MOVL    $0, CX
        // For XGETBV, OSXSAVE bit is required and sufficient
        XGETBV
        ANDL    $6, AX
        CMPL    AX, $6 // Check for OS support of XMM and YMM registers.
        JE nocpuinfo
noavx:
        MOVB $0, runtime·support_avx(SB)
        MOVB $0, runtime·support_avx2(SB)

nocpuinfo:
        // if there is an _cgo_init, call it.
        MOVQ    _cgo_init(SB), AX
        TESTQ   AX, AX
        JZ      needtls
        // g0 already in DI
        MOVQ    DI, CX  // Win64 uses CX for first parameter
        MOVQ    $setg_gcc<>(SB), SI
        CALL    AX

        // update stackguard after _cgo_init
        MOVQ    $runtime·g0(SB), CX
        MOVQ    (g_stack+stack_lo)(CX), AX
        ADDQ    $const__StackGuard, AX
        MOVQ    AX, g_stackguard0(CX)
        MOVQ    AX, g_stackguard1(CX)

#ifndef GOOS_windows
        JMP ok
#endif
needtls:
#ifdef GOOS_plan9
        // skip TLS setup on Plan 9
        JMP ok
#endif
#ifdef GOOS_solaris
        // skip TLS setup on Solaris
        JMP ok
#endif

        LEAQ    runtime·m0+m_tls(SB), DI
        CALL    runtime·settls(SB)

        // store through it, to make sure it works
        get_tls(BX)
        MOVQ    $0x123, g(BX)
        MOVQ    runtime·m0+m_tls(SB), AX
        CMPQ    AX, $0x123
        JEQ 2(PC)
        MOVL    AX, 0   // abort
ok:
        // set the per-goroutine and per-mach "registers"
        get_tls(BX)
        LEAQ    runtime·g0(SB), CX
        MOVQ    CX, g(BX)
        LEAQ    runtime·m0(SB), AX

        // save m->g0 = g0
        MOVQ    CX, m_g0(AX)
        // save m0 to g0->m
        MOVQ    AX, g_m(CX)

        CLD                             // convention is D is always left cleared
        CALL    runtime·check(SB)

        MOVL    16(SP), AX              // copy argc
        MOVL    AX, 0(SP)
        MOVQ    24(SP), AX              // copy argv
        MOVQ    AX, 8(SP)
        CALL    runtime·args(SB)
        CALL    runtime·osinit(SB)
        CALL    runtime·schedinit(SB)

        // create a new goroutine to start program
        MOVQ    $runtime·mainPC(SB), AX         // entry
        PUSHQ   AX
        PUSHQ   $0                      // arg size
        CALL    runtime·newproc(SB)
        POPQ    AX
        POPQ    AX

        // start this M
        CALL    runtime·mstart(SB)

        MOVL    $0xf1, 0xf1  // crash
        RET

DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL   runtime·mainPC(SB),RODATA,$8

 設定斷點runtime.main,接下來的程式碼時go語言寫的了

(gdb) b runtime.main
Breakpoint 3 at 0x427700: file /usr/local/go/src/runtime/proc.go, line 109

 檢視/usr/local/go/src/runtime/proc.go檔案

func main() {
        g := getg()

        // Racectx of m0->g0 is used only as the parent of the main goroutine.
        // It must not be used for anything else.
        g.m.g0.racectx = 0

        // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
        // Using decimal instead of binary GB and MB because
        // they look nicer in the stack overflow failure message.
        if sys.PtrSize == 8 {//判斷機器是32位還是64位,可以通過指標的長度進行判斷,64位為8
                maxstacksize = 1000000000
        } else {
                maxstacksize = 250000000
        }

        // Allow newproc to start new Ms.
        mainStarted = true

        systemstack(func() {
                newm(sysmon, nil)
        })

        // Lock the main goroutine onto this, the main OS thread,
        // during initialization. Most programs won't care, but a few
        // do require certain calls to be made by the main thread.
        // Those can arrange for main.main to run in the main thread
        // by calling runtime.LockOSThread during initialization
        // to preserve the lock.
        lockOSThread()

        if g.m != &m0 {
                throw("runtime.main not on m0")
        }

        runtime_init() // must be before defer
        if nanotime() == 0 {
                throw("nanotime returning zero")
        }

        // Defer unlock so that runtime.Goexit during init does the unlock too.
        needUnlock := true
        defer func() {
                if needUnlock {
                        unlockOSThread()
                }
        }()

        // Record when the world started. Must be after runtime_init
        // because nanotime on some platforms depends on startNano.
        runtimeInitTime = nanotime()

        gcenable()

        main_init_done = make(chan bool)
        if iscgo {
                if _cgo_thread_start == nil {
                        throw("_cgo_thread_start missing")
                }
                if GOOS != "windows" {
                        if _cgo_setenv == nil {
                                throw("_cgo_setenv missing")
                        }
                        if _cgo_unsetenv == nil {
                                throw("_cgo_unsetenv missing")
                        }
                }
                if _cgo_notify_runtime_init_done == nil {
                        throw("_cgo_notify_runtime_init_done missing")
                }
                // Start the template thread in case we enter Go from
                // a C-created thread and need to create a new thread.
                startTemplateThread()
                cgocall(_cgo_notify_runtime_init_done, nil)
        }

        fn := main_init // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
        fn()
        close(main_init_done)

        needUnlock = false
        unlockOSThread()

        if isarchive || islibrary {
                // A program compiled with -buildmode=c-archive or c-shared
                // has a main, but it is not executed.
                return
        }
        fn = main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
        fn()
        if raceenabled {
                racefini()
        }

        // Make racy client program work: if panicking on
        // another goroutine at the same time as main returns,
        // let the other goroutine finish printing the panic trace.
        // Once it does, it will exit. See issues 3934 and 20018.
        if atomic.Load(&runningPanicDefers) != 0 {
                // Running deferred functions should not take long.
                for c := 0; c < 1000; c++ {
                        if atomic.Load(&runningPanicDefers) == 0 {
                                break
                        }
                        Gosched()
                }
        }
        if atomic.Load(&panicking) != 0 {
                gopark(nil, nil, "panicwait", traceEvGoStop, 1)
        }

        exit(0)
        for {
                var x *int32
                *x = 0
        }
}