虽然已经用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.testGoroutine
。main.XXX
就是一种golang内部的命名规则。
可以很清晰的看到,0x1093794 e867d7f9ff CALL runtime.newproc(SB)
就是go关键字的关键。当然,如果有疑问,可以采用对比的方法,将go关键字去掉,再dump对比看看。
runtime.newproc
对应的就是go源代码的runtime包的newproc函数了,在$GO_SRC_ROOT/runtime/proc.go
文件中。
入口找到了,就应该开始分析了,下一篇文章应该从这个入库开始写,逐渐向下分析。