预先知识
变量定义先后在栈中的位置
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
-
FP,用来标识函数参数、返回值,编译器维护了基于FP偏移的栈上参数指针,0(FP)表示function的第一个参数,8(FP)表示第二个参数(64位系统上)后台加上偏移量就可以访问更多的参数。要访问具体function的参数,编译器强制要求必须使用name来访问FP,比如 foo+0(FP)获取foo的第一个参数,foo+8(FP)获取第二个参数
-
SB,SB伪寄存器可以理解为原始内存,foo(SB)的意思是用foo来代表内存中的一个地址。foo(SB)可以用来定义全局的function和数据,foo<>(SB)表示foo只在当前文件可见,跟C中的static效果类似。此外可以在引用上加偏移量,如foo+4(SB)表示foo+4bytes的地址。
(个人理解为foo(SB)代表foo函数的起始地址) -
SP,有伪SP和硬件SP的区分:
- 伪SP: 本地变量最高起始地址
- 硬件SP: 函数栈真实栈顶地址