Golang ASM简明教程

这几天倒腾了一下Go的ASM,然后写了一个简单的汇编代码,记录一下以防忘记。首先要说明的是Go的ASM是一种中间码,或者说是 众多汇编语言的一种抽象体,但是呢,又不完全是抽象,总之,揉合了Go自定义的一部分,和真实汇编语言。这里主要记录的就是 Go自己定义的那部分。

首先如果你想查看一段Go代码产生的汇编码,可以这样:

$ cat main.go
package main

func add(int64, int64) int64

func main() {
	println(add(1, 2))
}
$ go build -gcflags -S .
# github.com/jiajunhuang/test
"".main STEXT size=107 args=0x0 locals=0x28
	0x0000 00000 (/home/jiajun/Code/test/main.go:5)	TEXT	"".main(SB), ABIInternal, $40-0
	0x0000 00000 (/home/jiajun/Code/test/main.go:5)	MOVQ	(TLS), CX
	0x0009 00009 (/home/jiajun/Code/test/main.go:5)	CMPQ	SP, 16(CX)
	0x000d 00013 (/home/jiajun/Code/test/main.go:5)	PCDATA	$0, $
    ...

首先Go定义了4个虚拟寄存器:

  • FP: Frame pointer: arguments and locals. 这个是用来指向当前frame的起始位置
  • PC: Program counter: jumps and branches. 这个用来指向下一条要执行的指令的位置
  • SB: Static base pointer: global symbols. 这个用来指向全局变量的位置,可以把它看作是程序起始地址
  • SP: Stack pointer: top of stack. 这个指向栈顶

然后Go说了:All user-defined symbols are written as offsets to the pseudo-registers FP (arguments and locals) and SB (globals).

foo+4(SB)
+4<>static
first_arg+0(FP)x-8(SP)
SPSPx-8(SP)-8(SP)

On architectures with a hardware register named SP, the name prefix distinguishes references to the virtual stack pointer from references to the architectural SP register. That is, x-8(SP) and -8(SP) are different memory locations: the first refers to the virtual stack pointer pseudo-register, while the second refers to the hardware’s SP register.

函数声明

Go ASM咋声明一个函数呢?这样:

TEXT ·add(SB), $0-24
    MOVQ arg1+0(FP), CX
    MOVQ arg2+8(FP), BP
    ADDQ CX, BP
    MOVQ BP, result+16(FP)
    RET
.go
func add(int64, int64) int64

把函数体留空,这样Go编译器就知道实现是在汇编里。

addTEXT package_name·function_name(SB),$frame_size-arguments_size·.U+00B7(SB)$0-24-int64

接下来的代码就好理解了:

FP+0arg1+0(FP)FP+8arg2+8(FP)BPresult+16(FP)

看到没,一个简单的 add 函数,汇编就这么复杂了,所以bill gates当年一个人拿纸和笔,用汇编写了一个Basic解释器,那是什么层次 的神才能做到的?我再也不想写汇编了。

变量声明及初始化

数据,分为全局变量和局部变量,咋初始化呢?

DATA	symbol+offset(SB)/width, value
GLOBL runtime·tlsoffset(SB), NOPTR, $4

不过它这个value得是一个内存地址,比如:

DATA divtab<>+0x00(SB)/4, $0xf4f8fcff
DATA divtab<>+0x04(SB)/4, $0xe6eaedf0
...
DATA divtab<>+0x3c(SB)/4, $0x81828384
GLOBL divtab<>(SB), RODATA, $64

GLOBL runtime·tlsoffset(SB), NOPTR, $4
NOSPLIT./src/runtime/textflag.h
// Copyright 2013 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.

// This file defines flags attached to various functions
// and data objects. The compilers, assemblers, and linker must
// all agree on these values.
//
// Keep in sync with src/cmd/internal/obj/textflag.go.

// Don't profile the marked routine. This flag is deprecated.
#define NOPROF	1
// It is ok for the linker to get multiple of these symbols. It will
// pick one of the duplicates to use.
#define DUPOK	2
// Don't insert stack check preamble.
#define NOSPLIT	4
// Put this data in a read-only section.
#define RODATA	8
// This data contains no pointers.
#define NOPTR	16
// This is a wrapper function and should not count as disabling 'recover'.
#define WRAPPER 32
// This function uses its incoming context register.
#define NEEDCTXT 64
// Allocate a word of thread local storage and store the offset from the
// thread local base to the thread local storage in this variable.
#define TLSBSS	256
// Do not insert instructions to allocate a stack frame for this function.
// Only valid on functions that declare a frame size of 0.
// TODO(mwhudson): only implemented for ppc64x at present.
#define NOFRAME 512
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
#define REFLECTMETHOD 1024
// Function is the top of the call stack. Call stack unwinders should stop
// at this function.
#define TOPFRAME 2048

总结

汇编太麻烦了,再也不想写了。

参考资料:

更多文章