GO語解惑:從源碼分析GO程序的入口

原由

最近幾天學完了GO語言,可是教材裏給出的信息太少,不能知足須要。因而在網上看了許多博文,這就發現其中有許多衝突之處,令人愈加迷惑。爲了解惑,我深刻分析了一下GO語言而小有心得,想把其中的一些分享給你們,但願能提升你們的學習效率。windows


GO語言的真正入口

GO語言的runtime.Caller方法會提供當前goroutine的棧上的函數調用信息,主要有當前的PC值和調用的文件和行號。若沒法得到信息,第四個返回的值爲false。當咱們在main.main裏使用這個函數(本函數爲轉載):函數

func main() {
    for skip := 0; ; skip++ {
        pc, file, line, ok := runtime.Caller(skip)
        if !ok {
            break
        }
        fmt.Printf("skip = %v, pc = %v, file = %v, line = %v\n", skip, pc, file, line)
    }
    // Output:
    // skip = 0, pc = 4198453, file = caller.go, line = 10
    // skip = 1, pc = 4280066, file = $(GOROOT)/src/pkg/runtime/proc.c, line = 220
    // skip = 2, pc = 4289712, file = $(GOROOT)/src/pkg/runtime/proc.c, line = 1394
}

根據這裏的顯示入口是:runtime.goexit -> runtime.main -> main.main 。工具

這不合常理啊!經過分析runtime.goexit的代碼,沒法確認它調用runtime.main。後來又看到有人講GO語言的入口在src/lib9/main.c。因此我認爲有必要分析一下源碼,找出它真正的入口,由於我太疑惑了。學習


從連接器源碼開始

連接器是最終生成可執行文件的工具,咱們若是要查看入口,就從它開始。本人使用的是32bit windows 1.21版的GO安裝包,因此就從8l的代碼開始。首先定位到它的main函數:GO/src/cmd/8l/obj.c/main,具體細節不表,在這裏它調用了libinit。libinit的最後幾行是關鍵:ui

if(INITENTRY == nil) {
		INITENTRY = mal(strlen(goarch)+strlen(goos)+20);
		if(!flag_shared) {
			sprint(INITENTRY, "_rt0_%s_%s", goarch, goos);
		} else {
			sprint(INITENTRY, "_rt0_%s_%s_lib", goarch, goos);
		}
	}
	lookup(INITENTRY, 0)->type = SXREF;

函數的真正入口就是這個INITENTRY,在個人版本上它被指向GO/src/pkg/runtime/rt0-windows_386.s。在這個libinit以後,INITENTRY被放在HASH表而後又DUMP到可執行文件這些細節跟主題無關,可能有不少人對此有興趣,但這裏我也就不表了。this


彙編入口

PLAN9格式的彙編語言,GO/src/pkg/runtime/rt0_windows_386.s內容以下:
spa

TEXT _rt0_386_windows(SB),NOSPLIT,$12code

MOVL 12(SP), AXorm

LEAL 16(SP), BXip

MOVL AX, 4(SP)

MOVL BX, 8(SP)

MOVL $-1, 0(SP) // return PC for main

JMP main(SB) //這個main在哪裏呢?就在下面!


TEXT main(SB),NOSPLIT,$0

JMP _rt0_go(SB) //這個_rt0_go在哪裏?

繼續轉到GO/src/pkg/runtime/asm_386.s:

TEXT _rt0_go(SB),NOSPLIT,$0

// copy arguments forward on an even stack

MOVL argc+0(FP), AX

MOVL argv+4(FP), BX

SUBL $128, SP // plenty of scratch

ANDL $~15, SP

MOVL AX, 120(SP) // save argc, argv away

MOVL BX, 124(SP)

// set default stack bounds.

// _cgo_init may update stackguard.

MOVL $runtime·g0(SB), BP

LEAL (-64*1024+104)(SP), BX

MOVL BX, g_stackguard(BP)

MOVL BX, g_stackguard0(BP)

MOVL SP, g_stackbase(BP)

// find out information about the processor we're on

MOVL $0, AX

CPUID

CMPL AX, $0

JE nocpuinfo

MOVL $1, AX

CPUID

MOVL CX, runtime·cpuid_ecx(SB)

MOVL DX, runtime·cpuid_edx(SB)

nocpuinfo:


// if there is an _cgo_init, call it to let it

// initialize and to set up GS.  if not,

// we set up GS ourselves.

MOVL _cgo_init(SB), AX

TESTL AX, AX

JZ needtls

MOVL $setmg_gcc<>(SB), BX

MOVL BX, 4(SP)

MOVL BP, 0(SP)

CALL AX

// update stackguard after _cgo_init

MOVL $runtime&middot;g0(SB), CX

MOVL g_stackguard0(CX), AX

MOVL AX, g_stackguard(CX)

// skip runtime&middot;ldt0setup(SB) and tls test after _cgo_init for non-windows

CMPL runtime&middot;iswindows(SB), $0

JEQ ok

needtls:

// skip runtime&middot;ldt0setup(SB) and tls test on Plan 9 in all cases

CMPL runtime&middot;isplan9(SB), $1

JEQ ok


// set up %gs

CALL runtime&middot;ldt0setup(SB)


// store through it, to make sure it works

get_tls(BX)

MOVL $0x123, g(BX)

MOVL runtime&middot;tls0(SB), AX

CMPL AX, $0x123

JEQ ok

MOVL AX, 0 // abort

ok:

// set up m and g "registers"

get_tls(BX)

LEAL runtime&middot;g0(SB), CX

MOVL CX, g(BX)

LEAL runtime&middot;m0(SB), AX

MOVL AX, m(BX)


// save m->g0 = g0

MOVL CX, m_g0(AX)


CALL runtime&middot;emptyfunc(SB) // fault if stack check is wrong


// convention is D is always cleared

CLD


CALL runtime&middot;check(SB)


// saved argc, argv

MOVL 120(SP), AX

MOVL AX, 0(SP)

MOVL 124(SP), AX

MOVL AX, 4(SP)

CALL runtime&middot;args(SB)

CALL runtime&middot;osinit(SB)

CALL runtime&middot;hashinit(SB)

CALL runtime&middot;schedinit(SB)


// create a new goroutine to start program

PUSHL $runtime&middot;main&middot;f(SB) // entry

PUSHL $0 // arg size

ARGSIZE(8)

CALL runtime&middot;newproc(SB)

ARGSIZE(-1)

POPL AX

POPL AX


// start this M

CALL runtime&middot;mstart(SB)


INT $3

RET


DATA runtime&middot;main&middot;f+0(SB)/4,$runtime&middot;main(SB)

GLOBL runtime&middot;main&middot;f(SB),RODATA,$4


反彙編驗證

隨便編譯一個GO程序,用W32ASM驗證一下。

200419560 sub esp, 0000000C


:00419563 mov eax, dword pt: [esp+0C]

:O0419$67 lea ebx, dword ptr [esp+10]

:00419$6B mov dword pct [esp+04], eax

:0041956F mnv dword pt: [esp+08], ebx

:00419S73 mov dword ptr [esp], FFFFFFFF

:00419$7A jmp test.00419S80

.......

:00417C90 mov eax, dword pt: [esp+04]

:0O417C94 mov ebx, dword pct [esp+08]

200417098 sub esp, 00000080

:00417C9E and esp, FFFFFFFO

:00417CA1 mov dword ptr [esp+78], eax

:0O417CA9 mov ebp, 00447300

:00417CAE lea ebx, dword pt: [esp-0000FF98]

:00417CB5 mov dword pt: [ebp+40], ebx

:00417CB8 mov dword pt: [ebp+00], ebx

:0O417CBB mov dword pct [ebp+O4], esp

上面是rt0_windows_386.s部分,下面是asm_386.s部分。對比可知,這纔是GO程序的真正入口。

相關文章
相關標籤/搜索