本文所使用的Golang为1.14,dlv为1.4.0。
源代码 package main import "fmt" func main() { fmt.Println("Hello") } 开始调试 root@xiamin:~/study# dlv debug test.go Type 'help' for list of commands. (dlv) l > _rt0_amd64_linux() /root/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x465800) Warning: debugging optimized function 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)可以看到最开始是从_rt0_amd64_linux执行,然后直接跳转到_rt0_amd64。执行si进入_rt0_amd64。
(dlv) si > _rt0_amd64() /root/go/src/runtime/asm_amd64.s:15 (PC: 0x461c20) Warning: debugging optimized function 10: // _rt0_amd64 is common startup code for most amd64 systems when using 11: // internal linking. This is the entry point for the program from the 12: // kernel for an ordinary -buildmode=exe program. The stack holds the 13: // number of arguments and the C-style argv. 14: TEXT _rt0_amd64(SB),NOSPLIT,$-8 => 15: MOVQ 0(SP), DI // argc,将参数个数存入DI 16: LEAQ 8(SP), SI // argv,参数数组的地址存入SI 17: JMP runtime·rt0_go(SB)继续执行,runtime.rt0_go() /root/go/src/runtime/asm_amd64.s:89 (PC: 0x461c30)
runtime.rt0_goruntime.rt0_go中代码较多,但我们只关注与调度相关的。
TEXT runtime·rt0_go(SB),NOSPLIT,$0 // 忽略处理命令行参数相关 // 为全局变量g0设置一些栈相关的属性 MOVQ $runtime·g0(SB), DI // 将全局变量g0的存入DI LEAQ (-64*1024+104)(SP), BX // bx = SP-(64*1024+104),g0的栈帧大小 MOVQ BX, g_stackguard0(DI) // g0.stackguard0 = bx MOVQ BX, g_stackguard1(DI) // g0.stackguard1 = bx MOVQ BX, (g_stack+stack_lo)(DI) // g0.stack.lo = bx 栈的低地址 MOVQ SP, (g_stack+stack_hi)(DI) // g0.stack.hi = sp 栈的高地址 // 忽略获取cpu型号等相关与cgo初始化 // 线程本地存储(tls)相关设置 LEAQ runtime·m0+m_tls(SB), DI // di = &m0.tls CALL runtime·settls(SB) // 设置tls,下面有详细分析 // 验证tls是否生效:通过tls设置一个数值,然后m0.tls[0]获取,与设置的值对比。 get_tls(BX) // 获取fs地址到bx MOVQ $0x123, g(BX) // 反编译后 mov qword ptr fs:[0xfffffff8], 0x123,表示设置fs-8地址中的内容为0x123,其实就是m0.tls[0]的地址。 MOVQ runtime·m0+m_tls(SB), AX // ax = m0.tls[0] CMPQ AX, $0x123 // 比较 JEQ 2(PC) CALL runtime·abort(SB) // m0.tls[0] = &g0; g0与m0相互绑定 get_tls(BX) // 获取fs地址到bx LEAQ runtime·g0(SB), CX // cx = &g0 MOVQ CX, g(BX) // m0.tls[0] = &g0 LEAQ runtime·m0(SB), AX // ax = &m0 MOVQ CX, m_g0(AX) // m0.g0 = &g0 MOVQ AX, g_m(CX) // g0.m = &m0 // 忽略copy argc和argv的代码 CALL runtime·args(SB) // 命令行参数相关,暂不关心 CALL runtime·osinit(SB) // 设置全局变量ncpu(cpu个数),全局变量physHugePageSize CALL runtime·schedinit(SB) // 调度器初始化 // 调用runtime·newproc创建goroutine,指向函数为runtime·main MOVQ $runtime·mainPC(SB), AX // runtime·mainPC就是runtime·main PUSHQ AX // newproc的第二个参数,也就是goroutine要执行的函数。 PUSHQ $0 // newproc的第一个参数,表示要传入runtime·main中参数的大小,此处为0。 // 创建 main goroutine。非main goroutine也是此方法创建。 // go编译会将语句 go foo() 编译为 runtime·newproc(SB) 并传入参数。 CALL runtime·newproc(SB) POPQ AX POPQ AX CALL runtime·mstart(SB) // 进入调度循环 CALL runtime·abort(SB) // mstart应该永不返回,如果返回,则是程序出现错误了。 RET MOVQ $runtime·debugCallV1(SB), AX RET DATA runtime·mainPC+0(SB)/8,$runtime·main(SB) GLOBL runtime·mainPC(SB),RODATA,$8 runtime·settls 设置线程本地存储runtime·settls中通过调用arch_prctl系统调用设置FS来实现线程本地存储。
通过syscall指令调用系统调用
rax存放系统调用号,调用返回值也会放在rax中
当系统调用参数小于等于6个时,参数则须按顺序放到寄存器 rdi,rsi,rdx,r10,r8,r9中。
如果系统调用的参数数量大于6个,需将参数保存在一块连续的内存中,并将地址存入rbx中。
新建非m0的m时也会通过runtime·clone调用此函数。
TEXT runtime·settls(SB),NOSPLIT,$32 // 此时di = &m.tls[0] ADDQ $8, DI // ELF 需要使用 -8(FS),di+=8,执行完此指令后 di = &m.tls[1] MOVQ DI, SI // 将地址移动到si中,作为系统调用的第二个参数 MOVQ $0x1002, DI // ARCH_SET_FS表示设置FS,作为系统调用的第一个参数 MOVQ $SYS_arch_prctl, AX // rax存储系统调用号 SYSCALL CMPQ AX, $0xfffffffffffff001 // 比较返回结果 JLS 2(PC) MOVL $0xf1, 0xf1 // crash RET runtime.schedinit 调度初始化runtime.schedinit中包含了很多功能的初始化,本文暂且分析与调度相关的
func schedinit() { _g_ := getg() // 未找到getg()的源代码,通过注释得知getg()返回当前g,此处 _g_为&g0 .......... sched.maxmcount = 10000 // m的最大数量为10000 .......... mcommoninit(_g_.m) // 此处_g_.m即为m0,对m0的一些初始化工作,下面详细分析 .......... // 获取要初始化的p的数量,默认与cpu个数相同,如果指定了GOMAXPROCS,则为GOMAXPROCS procs := ncpu if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 { procs = n } // 初始化allp并为allp中的元素初始化、赋值等,详见下方 if procresize(procs) != nil { throw("unknown runnable goroutine during bootstrap") } .......... } schedinit->mcommoninit func mcommoninit(mp *m) { _g_ := getg() // 获取当前g,也就是g0 // g0 stack won't make sense for user (and is not necessary unwindable). if _g_ != _g_.m.g0 { callers(1, mp.createstack[:]) // 调用栈相关 } lock(&sched.lock) if sched.mnext+1 < sched.mnext { throw("runtime: thread ID overflow") } mp.id = sched.mnext // 设置m的id sched.mnext++ // 加1,以后分配给下一个m checkmcount() // 检查非空闲数量的m是否超过了10000 // rand相关 mp.fastrand[0] = uint32(int64Hash(uint64(mp.id), fastrandseed)) mp.fastrand[1] = uint32(int64Hash(uint64(cputicks()), ^fastrandseed)) if mp.fastrand[0]|mp.fastrand[1] == 0 { mp.fastrand[1] = 1 } // 新建一个32k栈大小的g,赋值给m0.gsignal。并使 m0.gsignal.m = *m0 mpreinit(mp) if mp.gsignal != nil { mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard } // 下面两步将mp放入全局变量allm中,allm是个链表 mp.alllink = allm atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp)) unlock(&sched.lock) // Allocate memory to hold a cgo traceback if the cgo call crashes. if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" { mp.cgoCallers = new(cgoCallers) } }