This is a creation in Article, where the information may have evolved or changed.
Cause
The go language has been learned in recent days, but the information given in the textbook is too small to meet the needs. So on the Internet read a lot of blog posts, which found that there are many conflicts, making people more confused. In order to dispel doubts, I deeply analyzed the go language and small have experience, want to share some of them to everyone, hope to improve everyone's learning efficiency.
The real entrance to the go language
The Runtime.caller method of the Go language provides the function call information on the current Goroutine stack, mainly with the current PC value and the called File and line number. If the information is not available, the fourth value returned is false. When we use this function in Main.main (this function is reproduced):
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}
According to the display entrance here are: Runtime.goexit, Runtime.main, Main.main.
This is unreasonable! By parsing the Runtime.goexit code, it is not possible to confirm that it calls Runtime.main. Later, I saw someone talking about the entrance to the go language in src/lib9/main.c. so I think it is necessary to analyze the source code to find out its real entrance, because I am too confused.
Start with the linker source code
A linker is a tool that eventually generates an executable file, and we start with it if we want to see the portal. I'm using the Go installer for the 32bit version of Windows 1.21, so start with the 8l code. First navigate to its main function: Go/src/cmd/8l/obj.c/main, the detail is not table, where it calls the Libinit. The last few lines of Libinit are key:
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;
The real entrance to the function is this initentry, which is pointed to go/src/pkg/runtime/rt0-windows_386.s on my version. After this libinit, Initentry is placed in the hash table and then dump to the executable file these details are irrelevant to the subject, and there may be a lot of people interested in it, but I don't watch it here.
Assembly entrance
PLAN9 format of assembly language, GO/SRC/PKG/RUNTIME/RT0_WINDOWS_386.S content as follows:
TEXT _rt0_386_windows (SB), nosplit,$12
movl (SP), AX
LEAL (SP), BX
movl AX, 4 (SP)
movl BX, 8 (SP)
movl $-1, 0 (SP)//return PC for main
JMP Main (SB)//Where is this main? It's down there!
TEXT Main (SB), nosplit,$0
JMP _rt0_go (SB)//Where is this _rt0_go?
Go to Go/src/pkg/runtime/asm_386.s:
TEXT _rt0_go (SB), nosplit,$0
//Copy arguments forward on a even stack
movl argc+0 (FP), AX
movl argv+4 (FP), BX
subl $128, SP //plenty of scratch
Andl $~15, SP
movl AX, (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 $, AX
CPUID
Cmpl AX, $
JE nocpuinfo
movl $, AX
CPUID
movl CX, runtime·cpuid_ecx (SB)
movl DX, Runtime·cpuid_edx (SB)
Nocpuinfo:
//If there is a _cgo_init, call it-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 ·g0 (SB), CX
movl g_stackguard0 (CX), AX
movl AX, G_stackguard (CX)
//Skip Runtime·ldt0setup (SB) and TLS test after _cgo_init for Non-windows
Cmpl runtime·iswindows (SB), $
JEQ OK
NEEDTLS:
//Skip Runtime·ldt0setup (SB) and TLS test on Plan 9 in all cases
Cmpl runtime·isplan9 (SB), $
JEQ OK
//Set up%gs
Call runtime·ldt0setup (SB)
//store through it, to make sure it works
Get_tls (BX)
movl $0x123, g (BX)
movl runtime·tls0 (SB), AX
Cmpl AX, $0x123
JEQ OK
movl AX, 0 //Abort
Ok:
//Set up M and g "registers"
Get_tls (BX)
LEAL runtime·g0 (SB), CX
movl CX, g (BX)
LEAL runtime·m0 (SB), AX
movl AX, M (BX)
//Save m->g0 = G0
movl CX, m_g0 (AX)
Call runtime·emptyfunc (SB) //fault if stack check is wrong
//convention is D are always cleared
CLD
Call Runtime·check (SB)
//Saved argc, argv
movl (SP), AX
movl AX, 0 (SP)
movl 124 (SP), AX
movl AX, 4 (SP)
Call Runtime·args (SB)
Call runtime·osinit (SB)
Call runtime·hashinit (SB)
Call runtime·schedinit (SB)
//Create a new goroutine to start program
pushl $runtime ·main·f (SB) //Entry
pushl //ARG size
argsize (8)
Call Runtime·newproc (SB)
Argsize (-1)
popl AX
popl AX
//start this M
Call Runtime·mstart (SB)
INT $
RET
DATA runtime·main·f+0 (SB)/4, $runtime ·main (SB)
Globl runtime·main·f (SB), rodata,$4
Disassembly verification
Just compile a Go program and verify with 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
Above is the RT0_WINDOWS_386.S section, the following is the ASM_386.S section. By contrast, this is the real entrance to the GO program.