一、资源
1、指令查询:
http://68k.hax.com/
2、命令查询
https://9p.io/magic/man2html/1/8a
3、LEA 和 MOV
LEA:操作地址;
MOV:操作数据
如:
LEAQ 8(SP), SI // argv 把 8(SP)地址放入 SI 寄存器中
MOVQ 0(SP), DI // argc 把0(SP)内容放入 DI 寄存器中
4、对外部数据的引用需要用到 伪寄存器 PC (the virtual program counter) or SB (the ‘static base’ register)。PC counts instructions, not bytes of data,即 2(PC)是跳过一条指令。
5、https://9p.io/plan9/
https://9p.io/sys/doc/
plan9 各种文档,html/ps/pdf 格式供下载
6、操作堆栈(stack)的伪指令或指令
FP:Frame Pointer,0(FP) 表示函数的第一个参数;4(FP)表示第二个参数等;
SP:local Stack Pointer,本地栈指针,保存自动变量(局部变量)。0(SP)表示第一个局部变量,4(SP)表示第二个局部变量等;
TOS:Top-Of-Stack register,用来 push 参数到历程(procedure)中或保存临时值等。
Plan9 汇编器有这样的语法,例如,p+0(FP),根据上面的说明,0(FP)表示第一个参数,同时定义了一个p,它的值是 0(FP);对SP有一样的语法。
7、一般的,函数返回值保存在 EAX 寄存器中(Plan9 中叫 AX)
https://davidwong.fr/goasm/
https://blog.hackercat.ninja/post/quick_intro_to_go_assembly/
SS, SP, BP 三个寄存器
SS:存放栈的段地址;
SP:堆栈寄存器SP(stack pointer)存放栈的偏移地址;
BP: 基数指针寄存器BP(base pointer)是一个寄存器,它的用途有点特殊,是和堆栈指针SP联合使用的,作为SP校准使用的,只有在寻找堆栈里的数据和使用个别的寻址方式时候才能用到
比如说,堆栈中压入了很多数据或者地址,你肯定想通过SP来访问这些数据或者地址,但SP是要指向栈顶的,是不能随便乱改的,这时候你就需要使用BP,把SP的值传递给BP,通过BP来寻找堆栈里数据或者地址.一般除了保存数据外,可以作为指针寄存器用于存储器寻址,此时它默认搭配的段寄存器是SS-堆栈段寄存器.BP是16位的,再扩充16位就是EBP,用于32位编程环境的.一般高级语言的参数传递等等,转换为汇编后经常由BP/EBP来负责寻址\处理.
SP,BP一般与段寄存器SS 联用,以确定堆栈寄存器中某一单元的地址,SP用以指示栈顶的偏移地址,而BP可 作为堆栈区中的一个基地址,用以确定在堆栈中的操作数地址。
基于 amd64 架构。
指令
指令参数长度。
MOVB: 1-byte
MOVW: 2
MOVL: 4
MOVQ: 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 + R2)
MOV 16(R1)(R22), R3 // R3 = (16 + R1 + R22)
MOV runtime·x(SB), R2 // R2 = *runtime·x
跳转指令。
JMP label // 跳转到标签。
JMP 2(PC) // 跳转到 PC + n 行。
JMP -2(PC)
数字常量以 $ 开头,十进制($10)和 十六进制($0x10)。 标签仅在函数內有效。
伪寄存器
伪寄存器(pseudo-register)由语言定义并使用,最终会被编译为硬件寄存器引用。
考虑到平台差异,编译后的机器代码,可能须保存 PC、BP、SP 等物理寄存器值。 在编写汇编代码时,很难事先计算好实际所需偏移量。为此,汇编语言用伪寄存器表示某个相对位置就很有必要。
SB: Static Base Pointer(全局符号)
表示一个全局符号地址,通常应用于全局函数或数据。
例如 CALL add(SB) 表示对应符号名字为 add 的内存地址。
在名字后添加尖括号(add<>(SB)),表示该符号名仅在当前文件內可见。
还可用偏移量表示基于某个符号名字的地址,例如 add+8(SB)。
FP: Frame Pointer(参数地址)
指向由调用方提供的参数列表起始地址,通过偏移量指向不同参数或返回值。
通常在偏移量前包含参数名。例如 MOVQ size+16(FP), AX
SP: Stack Pointer (栈局部变量内存地址)
伪 SP 寄存器表示栈帧內,用于本地局部变量操作的起始地址。
鉴于栈从底开始的操作方式,SP 实际是栈底位置(等同调整后的 BP 地址)。
使用该方式访问局部变量,须添加变量名,如 x-8(SP)。如果省略变量名,则表示硬件寄存器。
PC: Program Counter(指令地址)
可用来按指令行数条转。
比如 JMP 2(PC) 表示以当前位置为 0 基准,往下跳到第 2 行。
考虑到栈帧内存实际上分成局部变量(底)和调用参数(顶)两部分使用,所以用伪 SP 寄存器负值便宜访问局部变量是很自然的做法。 如此,物理寄存器 SP 用来操作调用参数入栈;而伪寄存器 SP 用来访问局部变量。 毕竟 BP 寄存器是可选的。
注意 x+0(FP) 和 gobuf_pc(AX) 宏函数的区别。
CALLEE lo SP +-----------+ ..........................
| | .
+-----------+ .
| | frame size(包括 caller BP)
BP (pseudo SP) +-----------+ .
| caller BP | .
+-----------+ ..........................
| caller PC |
FP +-----------+------------+ SP ..........
| arg0 | call arg0 | .
+-----------+------------+ .
| argn | call argn | argument size
+-----------+------------+ .
| return | call ret | . hi +-----------+------------+ .............
| local var0 |
+------------+
| local varn |
+------------+ BP (pseudo SP)
CALLER 函数 函数定义。
参数及返回值大小
| TEXT runtime·cgocallback(SB),NOSPLIT,$32-32
| | |
包名 函数名 栈帧大小(不包括参数及返回值) 当前包,可省略包名,直接以中心点开始。
由调用者(caller)负责分配目标函数(callee)参数和返回值内存。 调用者须自行保存相关寄存器状态。
示例
使用汇编代码编写一个简单的加法。
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
main.go
package main
func add(x, y int) (z int) // 声明汇编函数原型
func main() {
z := add(0x100, 0x200)
println(z)
}
可以看到编译器插入栈帧调整,环境保存等指令。
$ go build -gcflags “-l”
$ go tool objdump -s “main.add” test
TEXT main.add(SB) add.s
add.s:5 0x104bfe0 SUBQ $0x10, SP // 因为要保存 BP,所以栈帧大小调整到 0x10。
add.s:5 0x104bfe4 MOVQ BP, 0x8(SP)
add.s:5 0x104bfe9 LEAQ 0x8(SP), BP
add.s:6 0x104bfee MOVQ $0x0, 0(SP)
add.s:7 0x104bff6 MOVQ 0x18(SP), AX
add.s:8 0x104bffb MOVQ 0x20(SP), BX
add.s:9 0x104c000 ADDQ AX, BX
add.s:10 0x104c003 MOVQ BX, 0(SP)
add.s:11 0x104c007 MOVQ BX, 0x28(SP)
add.s:12 0x104c00c MOVQ 0x8(SP), BP
add.s:12 0x104c011 ADDQ $0x10, SP // 清除栈帧。
add.s:12 0x104c015 RET 使用 -gcflags -S 输出反汇编时,会有 FUNCDATA 和 PCDATA 信息。 它们是编译器引入,包含垃圾回收器要使用的信息。