门外汉系列: Golang Go语言中的Go Assembly解读

发布于 1周前 作者 bupafengyu 来自 Go语言

[GoLang] 门外汉系列: Go Assmebly 解读

Go 有一层抽象的汇编层来屏蔽不同平台和机器的差异.

考虑如下代码:

     1  package main
     2
     3  //go:noinline
     4  func add(a, b int32) (int32, bool) {
     5          return a + b, true
     6  }
     7
     8  func main() {
     9          add(3, 4)
    10  }

通过 go1.17 编译后得到的 Go 汇编代码主体如下:

$ GOOS=linux GOARCH=amd64 go tool compile -S main.go

“”.add STEXT nosplit size=8 args=0x8 locals=0x0 funcid=0x0 0x0000 00000 (main.go:4) TEXT “”.add(SB), NOSPLIT|ABIInternal, $0-8 0x0000 00000 (main.go:4) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (main.go:4) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0000 00000 (main.go:4) FUNCDATA $5, “”.add.arginfo1(SB) 0x0000 00000 (main.go:5) ADDL BX, AX 0x0002 00002 (main.go:5) MOVL $1, BX 0x0007 00007 (main.go:5) RET 0x0000 01 d8 bb 01 00 00 00 c3 … “”.main STEXT size=54 args=0x0 locals=0x10 funcid=0x0 0x0000 00000 (main.go:8) TEXT “”.main(SB), ABIInternal, $16-0 0x0000 00000 (main.go:8) CMPQ SP, 16(R14) 0x0004 00004 (main.go:8) PCDATA $0, $-2 0x0004 00004 (main.go:8) JLS 47 0x0006 00006 (main.go:8) PCDATA $0, $-1 0x0006 00006 (main.go:8) SUBQ $16, SP 0x000a 00010 (main.go:8) MOVQ BP, 8(SP) 0x000f 00015 (main.go:8) LEAQ 8(SP), BP 0x0014 00020 (main.go:8) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0014 00020 (main.go:8) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x0014 00020 (main.go:9) MOVL $3, AX 0x0019 00025 (main.go:9) MOVL $4, BX 0x001e 00030 (main.go:9) PCDATA $1, $0 0x001e 00030 (main.go:9) NOP 0x0020 00032 (main.go:9) CALL “”.add(SB) 0x0025 00037 (main.go:10) MOVQ 8(SP), BP 0x002a 00042 (main.go:10) ADDQ $16, SP 0x002e 00046 (main.go:10) RET 0x002f 00047 (main.go:10) NOP 0x002f 00047 (main.go:8) PCDATA $1, $-1 0x002f 00047 (main.go:8) PCDATA $0, $-2 0x002f 00047 (main.go:8) CALL runtime.morestack_noctxt(SB) 0x0034 00052 (main.go:8) PCDATA $0, $-1 0x0034 00052 (main.go:8) JMP 0 0x0000 49 3b 66 10 76 29 48 83 ec 10 48 89 6c 24 08 48 I;f.v)H…H.l$.H 0x0010 8d 6c 24 08 b8 03 00 00 00 bb 04 00 00 00 66 90 .l$…f. 0x0020 e8 00 00 00 00 48 8b 6c 24 08 48 83 c4 10 c3 e8 …H.l$.H… 0x0030 00 00 00 00 eb ca … rel 33+4 t=7 “”.add+0 rel 48+4 t=7 runtime.morestack_noctxt+0 go.cuinfo.packagename. SDWARFCUINFO dupok size=0 0x0000 6d 61 69 6e main “”…inittask SNOPTRDATA size=24 0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 … 0x0010 00 00 00 00 00 00 00 00 … gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 … “”.add.arginfo1 SRODATA static dupok size=5 0x0000 00 04 04 04 ff …

add

	0x0000 00000 (main.go:4)	TEXT	"".add(SB), NOSPLIT|ABIInternal, $0-8
  • 0x0000 00000: 相对位置
  • TEXT "".add: 向 .text 中添加新的符号 add. 前置的空字符串会在 link 阶段被替换成具体的包名.
  • (SB): SB 是 Go Assembly 中的一个虚拟寄存器, static base pointer.
  • NOSPLIT|ABIInternal: 编译过程中添加的标志位.
  • $0-8, frame 和 argument 的大小, 0 代表函数的 stack frame 为 0 bytes, 8 代表函数的参数占据 8 bytes.
	0x0000 00000 (main.go:4)	FUNCDATA	$0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x0000 00000 (main.go:4)	FUNCDATA	$1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
	0x0000 00000 (main.go:4)	FUNCDATA	$5, "".add.arginfo1(SB)

FUNCDATA 和后续的 PCDATA 都是编译器引入的为 GC 提供额外信息的指令, 后续不再额外讨论.

	0x0000 00000 (main.go:5)	ADDL	BX, AX
	0x0002 00002 (main.go:5)	MOVL	$1, BX

按照 Go 的调用约定(call convention), caller 会将 add 的两个参数存放到寄存器 AX, BX. ADDL 将寄存器 AX, BX 相加并将结果存放到 AX. MOVL 将 1(true) 存放到 BX.

	0x0007 00007 (main.go:5)	RET

RET 返回 Caller. 同样的按照调用约定, 返回的两个参数被放到寄存器 AX, BX.

main

	0x0000 00000 (main.go:8)	CMPQ	SP, 16(R14)
	0x0004 00004 (main.go:8)	JLS	47
    ...
	0x002f 00047 (main.go:10)	NOP
	0x002f 00047 (main.go:8)	CALL	runtime.morestack_noctxt(SB)
	0x0034 00052 (main.go:8)	JMP	0

SP 是一个虚拟的指针, stack pointer. 寄存 R14 代表 g structure, 偏移 14bytes 则对应 stackguard0. JLS 对应 x86 中的 JBE, jump if below or equal. 整体而言, 这儿是判断 g stack 是否有足够的空间, 如果没有则扩容后再执行后续.

	0x0006 00006 (main.go:8)	SUBQ	$16, SP

stack 分配 16bytes, 注意因为 stack 从高位往低位扩展, 所以扩容对应 SUB.

	0x000a 00010 (main.go:8)	MOVQ	BP, 8(SP)
	0x000f 00015 (main.go:8)	LEAQ	8(SP), BP
	0x0014 00020 (main.go:9)	MOVL	$3, AX
	0x0019 00025 (main.go:9)	MOVL	$4, BX
    ...
	0x0020 00032 (main.go:9)	CALL	"".add(SB)
	0x0025 00037 (main.go:10)	MOVQ	8(SP), BP

按照调用约定, 更新 BP, 并将调用参数存放到寄存器 AX, BX. 随后调用函数 add 后, 再恢复原来的 BP.

	0x002a 00042 (main.go:10)	ADDQ	$16, SP

stack 回收之前分配的 16bytes.

如果对 Go Assembly 感兴趣, 或者困惑与上述不专业的论述, 一些专业和高质量的资料:


门外汉系列: Golang Go语言中的Go Assembly解读

更多关于门外汉系列: Golang Go语言中的Go Assembly解读的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于门外汉系列: Golang Go语言中的Go Assembly解读的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


对于门外汉系列关于Golang中Go Assembly的解读,这里提供一个简明扼要的概述。

Go语言,作为一门现代编程语言,以其简洁、高效和强大的并发处理能力著称。然而,在某些高性能或底层系统编程场景中,有时需要直接操作硬件或进行极致优化,这时Go的内置汇编语言(Go Assembly)就显得尤为重要。

Go Assembly并不是传统意义上的汇编语言,它更像是一种高级汇编的抽象,旨在简化汇编编程的复杂性。Go的汇编代码使用伪指令和特殊的语法结构,这些指令最终会被编译成目标平台的原生汇编代码。

在Go中,汇编代码通常用于实现一些关键算法或系统调用,以提高执行效率。例如,在加密算法、数据库操作或网络传输等场景中,汇编代码能够显著提升性能。

编写Go Assembly代码需要一定的汇编基础和对目标平台架构的了解。Go提供了专门的汇编工具链,支持编写、编译和调试汇编代码。此外,Go的文档和社区资源也提供了丰富的汇编编程指南和示例代码。

对于门外汉来说,学习Go Assembly可能会有一定的难度。但不必担心,通过逐步深入和实践,你一定能够掌握这门强大的工具。同时,也要明确的是,Go Assembly并不是万能的,它应该在合适的场景下被合理使用,以充分发挥其性能优势。

总之,Go Assembly是Go语言生态系统中的重要组成部分,它为开发者提供了在需要时深入底层、优化性能的能力。

回到顶部