深入golang系列(1) - 工具
golang golang
Lastmod: 2020-09-20

虽然已经用golang在公司写了一套业务开发框架,而且在应用层面取得了一些收益。但是,自己对go的底层了解的还不够。最近看了一些大拿的文章,也加入了一些golang讨论微信群,深感自己对golang的底层了解的不够深入,需要花时间深入了。

这个会是一系列文章,后面怎么写还没想好。本篇文章的主题是确定的,就是工欲善其事必先利其器!先将工具准备齐全了,后续就是静下心来学习、分析和实践了。

go tool objdump

此命令可以将go build后的二进制程序dump为汇编

usage: go tool objdump [-S] [-s symregexp] binary [start end]

  -S	print go code alongside assembly
  -s string
    	only dump symbols matching this regexp

-S选项可以将go源码和汇编代码对应起来,这是一个killer feature!

-s string选项可以让我们在浩瀚的汇编代码中找到自己关心的部分

实例分析1 - go关键字的背后发生了什么

go关键字背后到底发生了什么,这应该是深入了解go底层的一个入口了。 我们可以写一个最简单的代码:


package main

import (
	"fmt"
	"time"
)

func testGoroutine() {
	fmt.Println("1111111")
}

func main() {
	go testGoroutine()

	fmt.Println("2222")
	time.Sleep(1 * time.Hour)
}

然后go build -o main

最后执行go tool objdump -S -s main.main ./main

可以得到以下输出:

TEXT main.main(SB) /Users/...路径.../main.go
func main() {
  0x1093760		65488b0c2530000000	MOVQ GS:0x30, CX
  0x1093769		483b6110		CMPQ 0x10(CX), SP
  0x109376d		0f869c000000		JBE 0x109380f
  0x1093773		4883ec58		SUBQ $0x58, SP
  0x1093777		48896c2450		MOVQ BP, 0x50(SP)
  0x109377c		488d6c2450		LEAQ 0x50(SP), BP
	go testGoroutine()
  0x1093781		c7042400000000		MOVL $0x0, 0(SP)
  0x1093788		488d05b19d0300		LEAQ go.func.*+127(SB), AX
  0x109378f		4889442408		MOVQ AX, 0x8(SP)
  0x1093794		e867d7f9ff		CALL runtime.newproc(SB)
	fmt.Println("2222")
  0x1093799		0f57c0			XORPS X0, X0
  0x109379c		0f11442440		MOVUPS X0, 0x40(SP)
  0x10937a1		488d05780b0100		LEAQ runtime.types+68224(SB), AX
  0x10937a8		4889442440		MOVQ AX, 0x40(SP)
  0x10937ad		488d05dcb00400		LEAQ main.statictmp_1(SB), AX
  0x10937b4		4889442448		MOVQ AX, 0x48(SP)
  0x10937b9		90			NOPL
	return Fprintln(os.Stdout, a...)
  0x10937ba		488b0597de0d00		MOVQ os.Stdout(SB), AX
  0x10937c1		488d0d38c90400		LEAQ go.itab.*os.File,io.Writer(SB), CX
  0x10937c8		48890c24		MOVQ CX, 0(SP)
  0x10937cc		4889442408		MOVQ AX, 0x8(SP)
  0x10937d1		488d442440		LEAQ 0x40(SP), AX
  0x10937d6		4889442410		MOVQ AX, 0x10(SP)
  0x10937db		48c744241801000000	MOVQ $0x1, 0x18(SP)
  0x10937e4		48c744242001000000	MOVQ $0x1, 0x20(SP)
  0x10937ed		e88e98ffff		CALL fmt.Fprintln(SB)
	time.Sleep(1 * time.Hour)
  0x10937f2		48b800a0b83046030000	MOVQ $0x34630b8a000, AX
  0x10937fc		48890424		MOVQ AX, 0(SP)
  0x1093800		e85b00fbff		CALL time.Sleep(SB)
}
  0x1093805		488b6c2450		MOVQ 0x50(SP), BP
  0x109380a		4883c458		ADDQ $0x58, SP
  0x109380e		c3			RET
func main() {
  0x109380f		e88cc1fbff		CALL runtime.morestack_noctxt(SB)
  0x1093814		e947ffffff		JMP main.main(SB)

  0x1093819		cc			INT $0x3
  0x109381a		cc			INT $0x3
  0x109381b		cc			INT $0x3
  0x109381c		cc			INT $0x3
  0x109381d		cc			INT $0x3
  0x109381e		cc			INT $0x3
  0x109381f		cc			INT $0x3

main.main是main.go中main函数的名称。如果想查看某个函数的dump后的内容,比如testGoroutine,可以设置-s参数为main.testGoroutinemain.XXX就是一种golang内部的命名规则。

可以很清晰的看到,0x1093794 e867d7f9ff CALL runtime.newproc(SB)就是go关键字的关键。当然,如果有疑问,可以采用对比的方法,将go关键字去掉,再dump对比看看。

runtime.newproc对应的就是go源代码的runtime包的newproc函数了,在$GO_SRC_ROOT/runtime/proc.go文件中。

入口找到了,就应该开始分析了,下一篇文章应该从这个入库开始写,逐渐向下分析。

参考

Scheduling In Go : Part I - OS Scheduler

Golang的runtime之从汇编说goroutine