Go 源码学习 引导(一)


参照雨痕的GO1.5源码剖析

$ go version
go version go1.18.3 linux/amd64
$ lsb_release -d
Description:    CentOS Linux release 7.8.2003 (Core)
$ gdb --version
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
package main

import (
	"fmt"
)

func main() {
	fmt.Println("hello world")
}

建议使⽤ -gcflags “-N -l” 参数关闭编译器代码优化和函数内联,避免断点和单步执⾏⽆法准确对应源码⾏,避免⼩函数和局部变量被优化掉。


go build -gcflags "-N -l" -o helloworld helloworld.go
$ gdb helloworld
(gdb) info files
Symbols from "/home/go/test/helloworld/helloworld".
Local exec file:
        `/home/go/test/helloworld/helloworld', file type elf64-x86-64.
        Entry point: 0x45bfc0
        0x0000000000401000 - 0x000000000047e0d7 is .text
        0x000000000047f000 - 0x00000000004b4149 is .rodata
        0x00000000004b42e0 - 0x00000000004b47a0 is .typelink
        0x00000000004b47a0 - 0x00000000004b47f8 is .itablink
        0x00000000004b47f8 - 0x00000000004b47f8 is .gosymtab
        0x00000000004b4800 - 0x0000000000507fd0 is .gopclntab
        0x0000000000508000 - 0x0000000000508130 is .go.buildinfo
        0x0000000000508140 - 0x0000000000518720 is .noptrdata
        0x0000000000518720 - 0x000000000051ff30 is .data
        0x000000000051ff40 - 0x000000000054eea0 is .bss
        0x000000000054eea0 - 0x0000000000554040 is .noptrbss
        0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid

在entry point打断点

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

运行

(gdb) r
Starting program: /home/go/test/helloworld/helloworld

Breakpoint 1, _rt0_amd64_linux () at /usr/local/go/src/runtime/rt0_linux_amd64.s:8
8               JMP     _rt0_amd64(SB)
(gdb) l
3       // license that can be found in the LICENSE file.
4
5       #include "textflag.h"
6
7       TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
8               JMP     _rt0_amd64(SB)
9
10      TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0
11              JMP     _rt0_amd64_lib(SB)

在 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)

JMP -> _rt0_amd64

_rt0_amd64

TEXT _rt0_amd64(SB),NOSPLIT,$-8
	MOVQ	0(SP), DI	// argc
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)

rt0_go 相对就要繁琐很多了

	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)

大致意思是说设置一个root goroutine. 具体参考 https://www.altoros.com/blog/golang-internals-part-5-the-runtime-bootstrap-process/


这一部分之后应该就是设置对应cpu型号

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

	CMPL	BX, $0x756E6547  // "Genu"
	JNE	notintel
	CMPL	DX, $0x49656E69  // "ineI"
	JNE	notintel
	CMPL	CX, $0x6C65746E  // "ntel"
	JNE	notintel
	MOVB	$1, runtime·isIntel(SB)

最后重要的部分check 及 一些初始化了

	CALL	runtime·check(SB)

	MOVL	24(SP), AX		// copy argc
	MOVL	AX, 0(SP)
	MOVQ	32(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
	CALL	runtime·newproc(SB)
	POPQ	AX

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

	CALL	runtime·abort(SB)	// mstart should never return
	RET

总结: begin -> _rt0_amd64_linux -> _rt0_amd64 -> runtime·rt0_go