上卷 程序设计
中卷 标准库
- bufio 1.18
- bytes 1.18
- io 1.18
- container 1.18
- encoding 1.18
- crypto 1.18
- hash 1.18
- index 1.18
- sort 1.18
- context 1.18
- database 1.18
- connection
- query
- queryrow
- exec
- prepare
- transaction
- scan & null
- context
- tcp
- udp
- http
- server
- handler
- client
- h2、tls
- url
- rpc
- exec
- signal
- embed 1.18
- plugin 1.18
- reflect 1.18
- runtime 1.18
- KeepAlived
- ReadMemStats
- SetFinalizer
- Stack
- sync 1.18
- atomic
- mutex
- rwmutex
- waitgroup
- cond
- once
- map
- pool
- copycheck
- nocopy
- unsafe 1.18
- fmt 1.18
- log 1.18
- math 1.18
- time 1.18
- timer
下卷 运行时
源码剖析
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
11.1 ASM
基于 Plan 9 汇编风格。
保存在 .s
文件中,编译器自动编译、链接。
从 1.17 开始,使用寄存器传参,详情参考《下卷:调用约定》。
本文依然采用
stack-based
传参模式,注意区分!
指令
Intel AT&T Go -------------------------------------------------------------- mov eax, 1 movl $1, %eax MOVQ $1, AX mov rbx, 0ffh movl $0xff, %rbx MOVQ $(0xff), BX mov ecx, [ebx+3] movl 3(%ebx), %ecx MOVQ 2(BX), CX
指令参数长度。
MOVB
: 1-byteMOVW
: 2MOVL
: 4MOVQ
: 8
数据移动方向:从左往右。
ADD R1, R2 // R2 += R1 SUB R3, R4 // R4 -= R3 SUB R3, R4, R5 // R5 = R4 - R3 MUL $7, R6 // R6 *= 7
内存访问。
MOV (R1), R2 // R2 = *R1 MOV 8(R1), R2 // R2 = *(8 + R1) MOV 16(R1)(R2*2), R3 // R3 = *(16 + R1 + R2*2) MOV runtime·x(SB), R2 // R2 = *runtime·x
跳转指令。
JMP label // 跳转到标签。 JMP 2(PC) // 跳转到 PC + n 行。 JMP -2(PC)
数字常量以 $
开头。十进制( $10
)和 十六进制( $0x10
)。
编译器引入 FUNCDATA
和 PCDATA
,包含垃圾回收信息。
寄存器
伪寄存器(pseudo-register)由汇编语言定义并使用,最终会被编译器替换为硬件寄存器。
go build -gcfalgs -S
输出结果已没有伪寄存器。
go tool objdump
结果也没有伪寄存器,且更干净。
- SB: Static Base Pointer(静态基指针)
表示内存起始位置,通常用于全局函数或数据。
例如,CALL add(SB)
表示函数add
地址。
添加尖括号(add<>(SB)
),表示仅当前文件內可见,私有成员。
还可添加偏移量,表示基于符号的地址,例如add+8(SB)
。
- FP: Frame Pointer(参数地址)
指向参数列表起始地址,以偏移量指向不同参数或返回值。
偏移量前包含参数名:symbol+offset(FP)
。
如果没有参数名,无法编译。
例如,size+16(FP)
表示size = (FP + 16)
。
- SP: Stack Pointer(栈局部变量内存地址)
当前栈帧內,指向本地局部变量起始地址。
鉴于栈从底开始,所有偏移量通常是负数([-framesize, 0)
)。
注意添加变量名,symbol+offset(SP)
。例如,x-8(SP)
。
提示:对应BP
寄存器值,没有符号名时表示硬件SP
寄存器。
- PC: Program Counter(指令地址)
按指令行数跳转。
例如,JMP 2(PC)
以当前位置为0
基准,往下跳2
行。
// add.s #include "textflag.h" // add(x, y int) int TEXT ·add(SB), NOSPLIT, $8-24 MOVQ $0, z-0x8(SP) MOVQ x+0x0(FP), AX MOVQ y+0x8(FP), BX ADDQ AX, BX MOVQ BX, z-0x8(SP) MOVQ BX, ret+0x10(FP) RET
package main func add(x, y int) (z int) // 声明汇编函数原型 func main() { z := add(0x100, 0x200) _ = z }
$ go build -gcflags "-N -l" -o test $ go tool objdump -s "main\.main" ./test TEXT main.main(SB) main.go main.go:5 0x455200 CMPQ 0x10(R14), SP main.go:5 0x455204 JBE 0x45524b main.go:5 0x455206 SUBQ $0x28, SP main.go:5 0x45520a MOVQ BP, 0x20(SP) main.go:5 0x45520f LEAQ 0x20(SP), BP main.go:6 0x455214 MOVQ $0x100, 0(SP) ; arg0 main.go:6 0x45521c MOVQ $0x200, 0x8(SP) ; arg1 main.go:6 0x455225 CALL main.add.abi0(SB) main.go:6 0x45522a XORPS X15, X15 main.go:6 0x45522e MOVQ FS:0xfffffff8, R14 main.go:6 0x455237 MOVQ 0x10(SP), AX ; add_ret main.go:6 0x45523c MOVQ AX, 0x18(SP) ; z main.go:8 0x455241 MOVQ 0x20(SP), BP main.go:8 0x455246 ADDQ $0x28, SP main.go:8 0x45524a RET main.go:5 0x45524b CALL runtime.morestack_noctxt.abi0(SB) main.go:5 0x455250 JMP main.main(SB) $ go tool objdump -s "main\.add" ./test TEXT main.add.abi0(SB) add.s add.s:4 0x455260 SUBQ $0x10, SP add.s:4 0x455264 MOVQ BP, 0x8(SP) add.s:4 0x455269 LEAQ 0x8(SP), BP add.s:5 0x45526e MOVQ $0x0, 0(SP) add.s:6 0x455276 MOVQ 0x18(SP), AX ; arg0 add.s:7 0x45527b MOVQ 0x20(SP), BX ; arg1 add.s:8 0x455280 ADDQ AX, BX add.s:9 0x455283 MOVQ BX, 0(SP) ; z add.s:10 0x455287 MOVQ BX, 0x28(SP) ; ret add.s:11 0x45528c MOVQ 0x8(SP), BP add.s:11 0x455291 ADDQ $0x10, SP add.s:11 0x455295 RET
对函数(callee)而言, FP
实际指向调用者(caller)的栈帧。
就是说,调用者负责参数和返回值内存。
编译器自动计算需要插入的位置偏移(现场保护, BP
/ PC
),无需手工处理。
hardware-register pseudo-register SP 0x0 +=========+ | z | add BP 0x8 +---------+ SP add.z-0x8(SP) | BP | 0x10 +=========+ ------------------- | IP/call | -------------------------- SP 0x18 +=========+ 0x0 FP add.x+0x0(FP) | arg0 | 0x20 +---------+ 0x8 add.y+0x8(FP) | arg1 | 0x28 +---------+ 0x10 main | ret | 0x30 +---------+ 0x18 | | BP +---------+ 0x20 SP | BP | +=========+ 0x28
段
不同于 NASM 使用 section 定义,直接在符号前添加类别。
DATA
: 初始化全局符号内存。(未初始化的为零)GLOBL
: 声明符号是全局的。
DATA symbol+offset(SB)/width, value GLOBL symbol(SB), [width]
DATA world<>+0(SB)/8, $"hello wo" DATA world<>+8(SB)/4, $"rld " GLOBL world<>+0(SB), RODATA, $12
除
RODATA
外,还有NOPTR
表示没有指针,无需 GC。
函数
函数定义,指定栈帧和参数大小。
参数及返回值大小 | TEXT main·add(SB), NOSPLIT, $8-24 | | | 包名 函数名 栈帧大小(不包括参数、返回值,以及环境保存)
- 当前包,可省略包名,直接以 中心点 开始。
- 由调用者(caller)负责分配参数和返回值内存,或直接使用寄存器。
- 调用者保存相关寄存器状态。
- A Quick Guide to Go's Assembler
- A Manual for the Plan 9 assembler
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论