关于golang:Golang-汇编介绍

2次阅读

共计 4787 个字符,预计需要花费 12 分钟才能阅读完成。

在浏览 Golang 源代码时,总是被其中的汇编代码卡住,读起来不晦涩。明天来简要理解下 Golang 中的汇编语言。

汇编分类

按指令集架构分类(针对 CPU)

  1. x86 汇编(32bit): 这种架构常被称为i386, x86
  2. x86 汇编(64bit), 这种架构常被称为 AMD64, Intel64, x86-64, x64, 它是 AMD 设计的, 是 x86 架构的 64 位扩大, 起初公开
  3. ARM 汇编, ARM 处理器因为高性能, 低耗电, 罕用于嵌入式, 挪动设施.

按汇编格局分类(针对人的浏览习惯)

  1. Intel 格局
  2. AT&T 格局

平时咱们说 golang 中汇编属于 plan9 格调,是按第二种形式分类的,其浏览格调(符号)与 Intel 与 AT&T 都有不同。plan9 汇编作者是 unix 操作系统的同一批人,bell 实验室所开发的。

Go 汇编语言是基于 plan9 汇编,然而事实世界还有这么多不同架构的 CPU 在这。所以 golang 汇编在 plan9 格调下,同一个办法还有不同指令集架构的多种实现。

在哪能看到 Golang 汇编代码

  1. Golang 源代码中,如src/runtime/asm_amd64.ssrc/math/big/
  2. go tool compile -S main.go, 把本人编写的代码编译成汇编代码。如:在我的 Mac Intel 机器上,amd64的架构,汇编代码生成如下:
$ cat main.go 
package main

func main() {
        a, b := 0, 0
        println(a + b)
}
$ go tool compile -S main.go 
"".main STEXT size=66 args=0x0 locals=0x10 funcid=0x0
        0x0000 00000 (main.go:3)        TEXT    "".main(SB), ABIInternal, $16-0
        0x0000 00000 (main.go:3)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:3)        PCDATA  $0, $-2
        0x0004 00004 (main.go:3)        JLS     57
        0x0006 00006 (main.go:3)        PCDATA  $0, $-1
        0x0006 00006 (main.go:3)        SUBQ    $16, SP
        0x000a 00010 (main.go:3)        MOVQ    BP, 8(SP)
        0x000f 00015 (main.go:3)        LEAQ    8(SP), BP
        0x0014 00020 (main.go:3)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (main.go:3)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0014 00020 (main.go:5)        PCDATA  $1, $0
        0x0014 00020 (main.go:5)        CALL    runtime.printlock(SB)
        0x0019 00025 (main.go:5)        XORL    AX, AX
...

Go 汇编根底语法

1. 寄存器

通用寄存器

寄存器与物理机架构相干, 不同的架构有不同的物理寄存器。

amd64 架构上提供了 16 个通用寄存器给用户应用

plan9 汇编语言提供了如下映射,在汇编语言中间接援用就可应用物理寄存器了。

amd64 rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 rip
Plan9 AX BX CX DX DI SI BP SP R8 R9 R10 R11 R12 R13 R14 PC

如上文的例子中应用到了:SP,AX,R14,BP

虚构寄存器

Go 汇编引入了 4 个 虚构寄存器

  • FP: Frame pointer: arguments and locals. 帧指针,快速访问函数的参数和返回值
  • PC: Program counter: jumps and branches. 程序计数器,指向下一条指令的地址。在 amd64 其实就是 rip 寄存器
  • SB: Static base pointer: global symbols. 动态基址指针,全局符号。
  • SP: Stack pointer: the highest address within the local stack frame. 栈指针, 指向局部变量

用法

  • FP:0(FP) 示意第一个 参数8(FP) 示意第二个参数(AMD64 架构)。first_arg+0(FP) 示意把第一个参数地址绑定到符号 first_arg
  • SP:localvar0-8(SP) 在 plan9 中示意函数中第一个 局部变量 。物理寄存器中也有 SP,硬件 SP 才是真正示意 栈顶地位。所以为了 辨别 SP 到底是指硬件 SP 还是指虚构寄存器。plan9 代码中须要以特定的格局来辨别。eg:symbol+offset(SP) 示意虚构寄存器 SP。offset(SP) 则示意硬件 SP。如上述例子中的 8(SP) 指的是硬件 SP
  • PC:除个别跳转治理,个别用不到
  • SB:示意全局内存终点。foo(SB) 示意符号 foo 作为内存地址应用。这种模式用于申明全局函数、数据。foo+4(SB)示意 foo 往后 4 字节的地址。<> 限度符号只能在以后源文件应用

从网上偷的图:

2. 指令

1. 变量申明

格局: 应用 DATAGLOBL 来申明一个全局变量

DATA symbol+offset(SB)/width, value
GLOBL symbol(SB), flag, $size

示意意义

  • DATA 局部: 对 symbol 变量中的字节赋值,把 offsetoffset + width 地位的字节赋值为 value
  • GLOBL 局部:必须在 DATA 后,示意申明了一个大小为 size 的全局变量symbolflag 代表变量一些属性,如 RODATA指只读。在 GLOBL 中退出 <>, 如 GLOBL bio<>(SB), RODATA, $16 也是示意这个全局变量只在本文件中失效。

理论例子:

// src/runtime/asm_amd64.s, 这里申明的 argc,argv 是 Go 程序的入参
DATA _rt0_amd64_lib_argc<>(SB)/8, $0
GLOBL _rt0_amd64_lib_argc<>(SB),NOPTR, $8
DATA _rt0_amd64_lib_argv<>(SB)/8, $0
GLOBL _rt0_amd64_lib_argv<>(SB),NOPTR, $8

NOPTR 这个示意不是指针,不须要垃圾回收扫描

局部变量 :其在栈帧中,不须要申明。间接依附 offset 取出应用。例如0(FP) 代表函数第一个参数,localvar0-8(SP) 函数中第一个局部变量。

2. 函数申明

格局:

TEXT pkgname·funname(SB),flag,$framesize-argsize

示意意义:

pkgname : 能够省略,最好省略。不然批改包名还要级联批改;

funname: 申明的函数名

flag: 标记位,如 NOSPLIT,咱们晓得 Go Runtime 会追踪每个 stack 的应用状况,而后动静自增。而 NOSPLIT 标记位禁止查看,节俭开销,然而写程序的人要保障这个函数是平安的。

framesize: 函数栈帧大小 = 局部变量 + 调用其它函数参数空间的总大小

argsize: 一些参考资料说这里是 参数 + 返回值 大小,但在试验中已有些许差别。这个应该和 GO 1.7 的更新无关,GO1.7 基于寄存器的调用规约 GO 1.7 的优化

  • GO 1.7 之前 参数 + 返回值都存在栈帧中
  • GO 1.7 更新后,优先应用 9 个 通用寄存器传递参数与返回值,超出局部再存在栈中。并且寄存器中返回值会笼罩参数中的值
  • 参数 + 返回值少于 9 个,argsize 值是参数的大小
  • 返回值 > 9 个,argsize = 参数大小 + 返回值超出 9 个的局部

理论例子:

$ cat main.go
package main

func main() {}

func add(a int64, b int64) int64 {return a + b}

$ go tool compile -S main.go
"".main STEXT nosplit size=1 args=0x0 locals=0x0 funcid=0x0
...
"".add STEXT nosplit size=4 args=0x10 locals=0x0 funcid=0x0
        0x0000 00000 (main.go:6)        TEXT    "".add(SB), NOSPLIT|ABIInternal, $0-16
        0x0000 00000 (main.go:6)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:6)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:6)        FUNCDATA        $5, "".add.arginfo1(SB)
        0x0000 00000 (main.go:7)        ADDQ    BX, AX
        0x0003 00003 (main.go:7)        RET
        ...

上述例子:函数内不需寄存局部变量,framesize = 0,两个 int64 的参数,argsize = 16

3. 常见操作指令

自主查问链接

上面介绍下常见的

1. 数据搬运
  • MOV 指令:其后缀示意搬运长度, $NUM 示意具体的数字,如上面例子
MOVB $1, DI          // 1 byte,  DI=1
MOVW $0x10, BX       // 2 bytes, BX=10
MOVD $1, DX          // 4 bytes, DX=1
MOVQ $-10, AX     // 8 bytes, AX=-10
  • LEA, 将无效地址加载到指定的地址寄存器中
// ret+24(FP) 这代表了第三个函数参数,是个地址
LEAQ    ret+24(FP), AX    // 把 ret+24(FP) 地址移到 AX 寄存器中
2. 计算指令
  • ADDSUB,IMULQ,如上面例子
ADDQ  AX, BX   // BX += AX
SUBQ  AX, BX   // BX -= AX
IMULQ AX, BX   // BX *= AX
  • 能够利用计算指令来调整栈空间,咱们晓得 SP 指向栈顶地位,调整 SP中的值即可。
// 栈空间: 高地址向低地址
SUBQ $0x18, SP // 对 SP 做减法,为函数调配函数栈帧
ADDQ $0x18, SP // 对 SP 做加法,革除函数栈帧
3. 条件跳转 / 无条件跳转
  • JMPJZ,JLS
// 无条件跳转
JMP addr   // 跳转到地址,地址可为代码中的地址,不过实际上手写不会呈现这种货色
JMP label  // 跳转到标签,能够跳转到同一函数内的标签地位
JMP 2(PC)  // 以以后指令为根底,向前 / 后跳转 x 行
JMP -2(PC) // 同上

// 有条件跳转
JZ target // 如果 zero flag 被 set 过,则跳转
JLS num        // 如果上一行的比拟后果,右边小于左边则执行跳到 num 地址处
4. 其它
  • 比拟:CMP, 与挑战指令搭配应用

    CMPQ    BX, $0    // 比拟与 BX 与 0 的大小
    JNE    3(PC)            // 右边小于左边则执行跳到以后 PC 指令后第三条指令的地位
  • 位运算:AND,OR,XOR

总结

通过这篇文章,置信你曾经能大抵读懂一些简略的汇编程序了。这里举荐几个源代码的汇编浏览。

  • Go 程序的终点:src/runtime/asm_amd64.s 中的 rt0_go(SB) 函数
  • Go 原子包:src/runtime/internal/atomic_amd64.s 中的 Case 函数

参考

  1. https://segmentfault.com/a/11…
  2. https://go.dev/doc/asm
  3. https://medium.com/martinombu…
  4. https://xargin.com/go-and-pla…
  5. https://kcode.icu/posts/go/20…
  6. https://mioto.me/2021/01/plan…
  7. https://www.symbolcrash.com/2…
正文完
 0