@Golang汇编快速入门

plan9基本指令

常数
常数在 plan9 汇编用 $num 表示,可以为负数,默认情况下为十进制。可以用 $0x123 的形式来表示十六进制数。

栈调整
intel 或 AT&T 汇编提供了 push 和 pop 指令族,plan9 中没有 push 和 pop,栈的调整是通过对硬件 SP 寄存器进行运算来实现的,例如:

SUBQ $20, SP // 对 SP 做减法,为函数分配函数栈帧
ADDQ $20, SP // 对 SP 做加法,清除函数栈帧

数据搬运
搬运的长度是由 MOV 的后缀决定的,操作数的方向是和 intel 汇编相反的,将左值赋值给右值(一般情况下)

MOVB $1, AX      // 1 byte
MOVW $1, AX   // 2 bytes
MOVD $1, AX      // 4 bytes
MOVQ $1, AX     // 8 bytes

地址运算
地址运算也是用 lea 指令,amd64 平台地址都是8个字节,所以直接就用LEAQ

LEAQ    4(AX), AX

其他的不做太多介绍

Golang与plan9,必看

Go 的汇编还引入了 4 个伪寄存器,援引官方文档的描述:

  • FP: Frame pointer: arguments and locals.
  • PC: Program counter: jumpsand branches.
  • SB: Static base pointer: global symbols.
  • SP: Stack pointer: top of stack.
    对看golang的汇编代码无多大影响,这里不做过多介绍,之所以提出来是因为这里有坑
    对于编译输出(go tool compile -S / go tool objdump)的代码来讲,目前所有的 SP 都是硬件寄存器 SP,因此伪SP,基本上没有啥影响。编译输出的汇编代码只有BP和SP
正文,简单举个栗子
在这里插入代码片
package main

func main() {
	a1:= add(3, 2)
	b1:= add(6,4)
	a1=a1+b1
}

func add(a, b int) int {
	return a+b
}
go tool compile -S -N .\test.go
"".main STEXT nosplit size=131 args=0x0 locals=0x58 funcid=0x0
        0x0000 00000 (.\test.go:3)      TEXT    "".main(SB), NOSPLIT|ABIInternal, $88-0 // 定义函数
        0x0000 00000 (.\test.go:3)      SUBQ    $88, SP  // 移动SP,分配栈空间
        0x0004 00004 (.\test.go:3)      MOVQ    BP, 80(SP)  // 保存调用者BP
        0x0009 00009 (.\test.go:3)      LEAQ    80(SP), BP // 移动BP
        0x000e 00014 (.\test.go:3)      FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 
        0x000e 00014 (.\test.go:3)      FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x000e 00014 (.\test.go:4)      MOVQ    $3, "".a+48(SP) // 对第一次add()的传入参数a赋值
        0x0017 00023 (.\test.go:4)      MOVQ    $2, "".b+24(SP) // 同上
        0x0020 00032 (<unknown line number>)    NOP
        0x0020 00032 (.\test.go:10)     MOVQ    "".a+48(SP), AX // 这里事实上是在add()里的计算过程
        0x0025 00037 (.\test.go:10)     ADDQ    $2, AX  // 将计算结果保存到AX里
        0x0029 00041 (.\test.go:4)      MOVQ    AX, ""..autotmp_8+72(SP) // 临时变量,目前还为理解意义在哪
        0x002e 00046 (.\test.go:4)      MOVQ    AX, "".~R0+8(SP)  // 将结果传给返回值 也就是 add() int 的int
        0x0033 00051 (.\test.go:4)      JMP     53 //跳到53
        0x0035 00053 (.\test.go:4)      MOVQ    AX, "".a1+40(SP)  // 第二次add()调用  不做解释
        0x003a 00058 (.\test.go:5)      MOVQ    $6, "".a+56(SP)
        0x0043 00067 (.\test.go:5)      MOVQ    $4, "".b+32(SP)
        0x004c 00076 (<unknown line number>)    NOP
        0x004c 00076 (.\test.go:10)     MOVQ    "".a+56(SP), AX
        0x0051 00081 (.\test.go:10)     LEAQ    4(AX), CX  // 这里是利用LEQ实现计算
        0x0055 00085 (.\test.go:5)      MOVQ    CX, ""..autotmp_9+64(SP)
        0x005a 00090 (.\test.go:5)      MOVQ    CX, "".~R0(SP)
        0x005e 00094 (.\test.go:5)      NOP
        0x0060 00096 (.\test.go:5)      JMP     98
        0x0062 00098 (.\test.go:5)      MOVQ    CX, "".b1+16(SP) //  a1= a1 + b1的计算过程
        0x0067 00103 (.\test.go:6)      MOVQ    "".a1+40(SP), CX 
        0x006c 00108 (.\test.go:6)      LEAQ    (AX)(CX*1), AX
        0x0070 00112 (.\test.go:6)      LEAQ    4(AX), AX
        0x0074 00116 (.\test.go:6)      MOVQ    AX, "".a1+40(SP)  // 将计算结果赋值给a1
        0x0079 00121 (.\test.go:7)      MOVQ    80(SP), BP // 移动BP
        0x007e 00126 (.\test.go:7)      ADDQ    $88, SP  // 缩减栈
        0x0082 00130 (.\test.go:7)      RET // 返回

"".add STEXT nosplit size=25 args=0x18 locals=0x0 funcid=0x0
        0x0000 00000 (.\test.go:9)      TEXT    "".add(SB), NOSPLIT|ABIInternal, $0-24
        0x0000 00000 (.\test.go:9)      FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (.\test.go:9)      FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (.\test.go:9)      MOVQ    $0, "".~r2+24(SP)  // 给返回值赋初值
        0x0009 00009 (.\test.go:10)     MOVQ    "".a+8(SP), AX  
        0x000e 00014 (.\test.go:10)     ADDQ    "".b+16(SP), AX
        0x0013 00019 (.\test.go:10)     MOVQ    AX, "".~r2+24(SP)  // 将结果赋值给返回值
        0x0018 00024 (.\test.go:10)     RET // 返回
TEXT    "".main(SB), NOSPLIT|ABIInternal, $88-0 

TEXT symbol(SB), [flags,] $framesize[-argsize]
TEXT 用于定义函数符号
symbol 函数名符号,由包名和函数名构成,包名可以省略,SB 表示是函数名符号相对于SB伪寄存器的偏移量,二者组合在一起最终是绝对地址
flags 用于指示函数的一些特殊行为 常见的NOSPLIT主要用于指示叶子函数不进行栈分裂
framesize 表示函数的局部变量需要多少栈空间,其中包含调用其他函数时准备调用参数的隐式栈空间
argsize 表示函数参数的大小,包括传入参数和返回值

栈帧分析
mian()

说句实话main()的栈帧我没太明白,可能是函数调用的原因导致的,autotmp,~R0这些东西究竟有何意义,等我那天懂了,在补充一下吧,原谅我是个小菜鸡
add()

TEXT    "".add(SB), NOSPLIT|ABIInternal, $0-24

在这里插入图片描述
add()不存在局部变量所以framesize大小为0,存在3个参数所以argsize大小为24
栈结构
下面是一个典型的函数的栈结构图:
在这里插入图片描述

args:参数部分的内存排布的方式是后出现变量的在高地址,即从左至右对应的从上往下
Caller BP:存放的是调用函数的BP
frame: 存放局部变量,局部变量采取的内存排布方式是按照变量名升序从上往下拍
return addr: 返回地址,即调用位置