Go plan9

367 阅读1分钟

预先知识

变量定义先后在栈中的位置

func main() {
	var a int32 = 1   // 0xc000108f74
	var b int32 = 1   // 0xc000108f70
	println(&a)
	println(&b)
}

栈是向下增长的,所以a的地址比b低、高,所以在栈中布局如下

high
     +---------+
 +   |         |
 |   |         |
 |   |         |
 |   +---------+
 |   |    a    |
 |   +---------+ <---------------+ 0xc000108f74
 |   |    b    |
 |   +---------+ <---------------+ 0xc000108f70
 |   |         |
 |   |         |
 |   |         |
 |   |         |
 v   |         |
     |         |
 low +---------+

函数的栈分布

如果没有本地变量,栈分布如下

+--------------+
|  本地变量0    |
+--------------+
|    ....      |
+--------------+
|  本地变量N-1  |
+--------------+
|  本地变量N    |
+--------------+
|   返回值N     |
+--------------+
|    ....      |
+--------------+
|   返回值2     |
+--------------+
|   返回值1     |
+--------------+ <------FP
|  函数返回地址  |
+--------------+ <------伪SP和硬件SP
|              |

TEXT ·SpFp(SB),NOSPLIT,$0-32
    LEAQ (SP), AX
    LEAQ a+0(SP), BX
    LEAQ b+0(FP), CX
    MOVQ AX, ret+0(FP)
    MOVQ BX, ret+8(FP)
    MOVQ CX, ret+16(FP)
    RET
 // 824634330688 824634330688 824634330696

如果有本地变量

                              Caller

   +----------------> +----------------------+
   |                  |   caller parent BP   |
   |   BP/pseudo SP-> +----------------------+
   |                  |    Local Var0       |
   |                  +----------------------+
   |                  |       ......         |
   |                  +----------------------+
   |                  |    Local VarN        |
   |                  +----------------------+
   |                  |    Callee RetN       |
   |                  +----------------------+
   |                  |       ......         |
caller stack frame    +----------------------+
                      |    Callee Ret0       |
   |                  +----------------------+
   |                  |    Callee ParamN     |                                FP = pseudo SP + 16
   |                  +----------------------+                                pseudo SP = SP + localSize
   |                  |       ......         |                                FP = SP + localSize + 16
   |                  +----------------------+
   |                  |    Callee Param0     |
   |                  +----------------------------------------------+  <--------FP(callee use it)
   |                  |    Return Addr       |  parent return addr   |
   |----------------> +----------------------------------------------+  <-------+
                                             |    caller BP          |          |
                   BP/pseudo SP------------> +-----------------------+          |
                                             |     Local Var N       |          |
                                             +-----------------------+          |
                                             |       ....            |          |
                                             +-----------------------+          |
                                             |     Local Var1        |          |
                                             +-----------------------+
                                             |     Local var 0       |  callee stack frame
                          SP real Register-> +-----------------------+          |
                                             |                       |          |
                                             |                       | <--------+

TEXT ·SpFp1(SB),NOSPLIT,$8-32
    LEAQ (SP), AX
    LEAQ a+0(SP), BX
    LEAQ b+0(FP), CX
    MOVQ AX, ret+0(FP)
    MOVQ BX, ret+8(FP)
    MOVQ CX, ret+16(FP)
    RET


TEXT ·SpFp2(SB),NOSPLIT,$16-32
    LEAQ (SP), AX
    LEAQ a+0(SP), BX
    LEAQ b+0(FP), CX
    MOVQ AX, ret+0(FP)
    MOVQ BX, ret+8(FP)
    MOVQ CX, ret+16(FP)
    RET
//  824634330672 824634330680 824634330696
//  824634330664 824634330680 824634330696

定义变量

GLOBEL

GLOBL命令用于将符号导出
GLOBL symbol(SB), width
width为符号对应内存的大小

DATA

DATA用于初始化包变量
DATA symbol+offset(SB)/width, value
offset是符号开始地址的偏移量(为0可以不写),width是要初始化内存的宽度大小,value是要初始化的值

整数变量

#include "textflag.h"

GLOBL ·Id(SB),NOPTR,$8

DATA ·Id(SB)/8,$12

字符串

字符串的底层结构为

type StringHeader struct {
	Data uintptr
	Len  int
}

所以先将前8字节赋值为字符串,后8字节赋值为字符串的长度

GLOBL ·StrData(SB),NOPTR,$8
DATA  ·StrData(SB)/8,$"test"

GLOBL ·Str(SB),NOPTR,$16
DATA  ·Str+0(SB)/8,$·StrData(SB)
DATA  ·Str+8(SB)/8,$4

或者(直接不导出StrData字段了,外界就无法修改了)

GLOBL ·Str(SB),NOPTR,$24
DATA  ·Str+0(SB)/8,$·Str+16(SB)
DATA  ·Str+8(SB)/8,$4
DATA  ·Str+16(SB)/8,$"test"

数组类型

go代码

var Num [2]byte

汇编代码

GLOBL ·Num(SB),NOPTR,$2
DATA  ·Num(SB)/1,$10
DATA  ·Num+1(SB)/1,$2

bool变量

GLOBL ·BoolValue(SB),NOPTR,$1
DATA  ·BoolValue(SB)/1,$1

string变量

<>为后缀名是当前.s文件的私有变量

GLOBL textdata<>(SB),NOPTR,$12
DATA  textdata<>(SB)/8, $"12345678"
DATA  textdata<>+8(SB)/4, $"1234"

GLOBL ·TextStr(SB),NOPTR,$16
DATA  ·TextStr(SB)/8,$textdata<>(SB)
DATA  ·TextStr+8(SB)/8,$12

slice变量

type reflect.SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

pkg.go

var Slice []byte

pkg_amd64.s

GLOBL sliceData<>(SB),NOPTR,$12
DATA  sliceData<>(SB)/8,$"12345678"
DATA  sliceData<>+8(SB)/4,$"4321"

GLOBL ·Slice(SB),NOPTR,$24
DATA  ·Slice(SB)/8,$sliceData<>(SB)
DATA  ·Slice+8(SB)/8,$12
DATA  ·Slice+16(SB)/8,$20

输出

println(string(pkg.Slice))  //Hello World!
println(len(pkg.Slice))		//12
println(cap(pkg.Slice))		//16

函数

swap

func Swap(a, b int) (ret0, ret1 int)
#include "textflag.h"

TEXT ·Swap(SB),NOSPLIT,$0-32
    MOVQ a+0(FP), AX
    MOVQ b+8(FP), BX
    MOVQ AX, ret1+24(FP)
    MOVQ BX, ret1+16(FP)
    RET

对于swap的调用栈

|                |
+----------------+
|                |
|                |
|                |     BP 指向调用者的栈底
+----------------+  <-------------------
|                |
|   ret1         |
|                |        FP+16
+----------------+ <---------------------
|                |
|   ret0         |
|                |        FP+8
+----------------+ <---------------------
|                |
|   arg1(b)      |
|                |
+----------------+
|                |
|   arg0(a)      |
|                |         FP
+----------------+  <---------------------
|                |
|  return addr   |
|                |   pseudo SP == hardware SP
+----------------+  <---------------------
|                |
|                |

复杂参数的函数

func Foo(a bool, b int16) (c []byte) 参数和返回值内存布局如下

  +---------------------------------------+
  |               c.Cap                   |
  |                64bit                  |
  +---------------------------------------+ <-------C.Cap+24(FP)
  |               c.Len                   |
  |                64bit                  |
  +---------------------------------------+ <-------C.Len+16(FP)
  |               c.Data                  |
  |                64bit                  |
  +----+----+---------+-------------------+ <-------C.Data+8(FP)
  |  a |    |   b     |                   |
  |8bit|    | 16bit   |      32bit        |
  +---------+---------+-------------------+

  ^         ^
  |         |
a+0(FP)   b+2(FP)
  |         |
  +         +

利用宏函数实现swap

代码

├── controlflow
│   ├── pkg.go
│   └── pkg_amd64.s
├── go.mod
└── main.go

pkg.go

package controlflow

import "fmt"

func print(a int) {
	fmt.Println(a)
}

func println() {
	fmt.Println()
}
// 大写用于导出
func Cal()

pkg_amd64.s

#include "textflag.h"

TEXT ·Test(SB),$16-0
    // 初始化
    MOVQ $10, +8(SP)    // init a
    MOVQ $10, +16(SP)   // init b

    // 赋值a=10
    MOVQ $10, AX    
    MOVQ AX, +8(SP)

    MOVQ AX, (SP)
    CALL ·print(SB)

    //防止AX/BX 被污染
    MOVQ +8(SP), AX     // AX=a
    MOVQ +16(SP), BX    // BX=b

    //计算
    MOVQ AX, BX     // BX=AX    => b=a
    ADDQ BX, BX     // BX+= BX  => b+=b
    IMULQ AX, BX    // BX*=AX   =>b*=a
    // 这行有没有都行
    MOVQ BX, +8(SP) // b=BX     =>b=b

    //输出
    MOVQ BX, (SP)
    CALL ·print(SB)
    RET

main.go

func main() {
    controlflow.Test()  \\ 200
}

控制流

if

 +---------+
 |         |
 +---------+
 |   ret0  |
 +---------+
 |    b    |
 +---------+
 |    a    |
 +---------+
 |    ok   |
 +---------+  <--- FP
 |         |
 +---------+

pkg_adm64.s

TEXT ·If(SB),$0-32
    MOVQ ok+0(FP), CX       // ok
    MOVQ a+8(FP), AX        // a
    MOVQ a+16(FP), BX       // b

    CMPQ CX,$0
    JZ L                   // if ok ==0 goto L
    MOVQ BX, ret0+24(FP) // return a
L:
    MOVQ AX, ret0+24(FP) // return b
    RET
    

pkg.go

func If(ok int, a, b int) int

for

函数堆栈

 +-------------+
 |             |
 +-------------+
 |  ret0       |
 +-------------+
 |  step       |
 +-------------+
 |   v0        |
 +-------------+
 |   cnt       |
 +-------------+  <------------FP
 | return addr |
 +-------------+
 | caller's BP |
 +-------------+  <------------pseudo SP
 |      i      |
 +-------------+
 |   result    |
 +-------------+  <------------SP
func LoopAdd(cnt, v0, step int) int {
    result := v0
    for i := 0; i < cnt; i++ {
        result += step
    }
    return result
}
#include "textflag.h"

TEXT ·LoopAdd(SB),NOSPLIT,$16-32
    MOVQ cnt+0(FP), AX      //AX=cnt
    MOVQ v0+8(FP), BX       //BX=v0
    MOVQ step+16(FP), CX       //CX=step

LOOP_BEGIN:
    MOVQ $0, +8(SP)       //i    i-8(SP)
    MOVQ +8(SP), DX
    MOVQ BX, (SP)         //result  result-16(SP)
    MOVQ (SP), BX
LOOP_IF:
    CMPQ DX,AX
    JL LOOP_BODY
    JMP LOOP_END

LOOP_BODY:
    ADDQ CX, BX   
    ADDQ $1, DX 
    JMP LOOP_IF
LOOP_END:
    MOVQ BX, ret0+24(FP)
    RET

汇编函数调用汇编函数(go tool compile -N -l -S main.go)

pkg_amd64.s

TEXT ·AA(SB),NOSPLIT,$16-0
    MOVQ a+0(FP), AX
    MOVQ b+8(FP), BX


    MOVQ AX, 0(SP)   // 需要将AX寄存器的值放到0(SP)位置,这个位置的变量将在调用函数时作为它的参数
    MOVQ BX, 0x8(SP)

    CALL ·Sum(SB)
    MOVQ 16(SP), AX     //这里没搞懂为啥要+16
    MOVQ AX,ret1+16(FP)
    RET




TEXT ·Sum(SB),NOSPLIT,$0-0
    MOVQ arg1+0(FP), AX      // AX = arg1
    MOVQ arg2+8(FP), BX      // BX = arg2
    ADDQ AX, BX              // BX += AX
    MOVQ BX, ret1+16(FP)
   // MOVQ AX, (SP)
   // CALL ·print(SB)
    RET

pkg.go

func AA(a, b int) int

寄存器

FP(Frame pointer):arguments and locals
PC(Program counter): jumps and branches
SB(Static base pointer):global symbols
SP(Stack pointer):top of stack
  1. FP,用来标识函数参数、返回值,编译器维护了基于FP偏移的栈上参数指针,0(FP)表示function的第一个参数,8(FP)表示第二个参数(64位系统上)后台加上偏移量就可以访问更多的参数。要访问具体function的参数,编译器强制要求必须使用name来访问FP,比如 foo+0(FP)获取foo的第一个参数,foo+8(FP)获取第二个参数

  2. SB,SB伪寄存器可以理解为原始内存,foo(SB)的意思是用foo来代表内存中的一个地址。foo(SB)可以用来定义全局的function和数据,foo<>(SB)表示foo只在当前文件可见,跟C中的static效果类似。此外可以在引用上加偏移量,如foo+4(SB)表示foo+4bytes的地址。(个人理解为foo(SB)代表foo函数的起始地址)

  3. SP,有伪SP和硬件SP的区分:

    • 伪SP: 本地变量最高起始地址
    • 硬件SP: 函数栈真实栈顶地址