【Free Style】深入Go语言模型(2):Go语言汇编指令简介

举报
霜雯 发表于 2017/11/09 10:19:13 2017/11/09
【摘要】 前言在学习深入学习Go语言的过程中,需要大量阅读Go语言的汇编代码来查看Go运行的运行原理,那么本文将简单介绍Go语言的汇编指令语法,让大家后续可以简单读懂Go的汇编指令。简介Go语言的汇编是基于Pan9汇编语言的风格(https://9p.io/sys/doc/asm.html)大部分情况我们是不需要写汇编代码的,所以下面主要简单介绍一下Go的汇编语法生成汇编代码的方法(基于Go1.8)在命令行

前言

在学习深入学习Go语言的过程中,需要大量阅读Go语言的汇编代码来查看Go运行的运行原理,那么本文将简单介绍Go语言的汇编指令语法,让大家后续可以简单读懂Go的汇编指令。

简介

Go语言的汇编是基于Pan9汇编语言的风格(https://9p.io/sys/doc/asm.html)
大部分情况我们是不需要写汇编代码的,所以下面主要简单介绍一下Go的汇编语法

生成汇编代码的方法(基于Go1.8)

在命令行中执行:

go build -gcflags -S main.go 2> main.s

-gcflags表示将后面的参数传给Go编译器,可以通过go tool compile -help来列出了可以被传入编译器的所有的参数列表。

汇编语法介绍

Go语言的汇编指令不一定直接对应机器表示。有一些直接对应,有一些则不是。
编译器产生的是一些中间码,具体的机器指令是在汇编生成之后才定下来的(Linker的工作)。
FUNCDATA和PCDATA是编译器产生的,用于保存一些给垃圾收集的信息。

寄存器

Go语言有4个伪寄存器,实际是对内存位置的一个引用。

  • FP(frame pointer): 帧指针,保存参数和本地变量
    编译器维护了一个虚拟的栈指针,使用对伪寄存器的offsets操作的形式,指向栈上的函数参数。 于是,0(FP)就是第一个参数,8(FP)就是第二个(64位机器)

  • PC:程序指针,负责跳转和流程控制

  • SB(static base): 静态基指针,全局变量
    SB伪寄存器用来表示全局的变量或者函数,比如foo(SB)用来表示foo的地址。加<>表示符号本文件内可见。

  • SP(stack pointer):栈指针,栈顶
    用来指向栈帧本地的变量和为函数调用准备参数。它指向本地栈帧的顶部,所以一个对栈帧的引用必须是一个负值且范围在[-framesize:0]之间,例如: x-8(SP),y-4(SP)。 0(SP)表示第一个局部变量

指令解释

func myfun(a int) int {
    fmt.Println(a)
    return a + 1
}

func main() {
    r := myfun(2)
    fmt.Println(r)
}

我通过编译生成下面的汇编代码:

"".myfun t=1 size=179 args=0x10 locals=0x50
    //定义函数myfun, 栈帧的大小是80字节,16字节的参数和返回值
    0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:7)    TEXT    "".myfun(SB), $80-16
    //将Thread Local Storage传给CX
    0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:7)    MOVQ    TLS, CX
    0x0009 00009 (D:\Demo\Go\src\zhanghui\main.go:7)    MOVQ    (CX)(TLS*2), CX
    //检查当前栈帧额大小是否超过分配的大小
    0x0010 00016 (D:\Demo\Go\src\zhanghui\main.go:7)    CMPQ    SP, 16(CX)
    //如果超过,就跳转到00169调用runtime.morestack_noctxt
    0x0014 00020 (D:\Demo\Go\src\zhanghui\main.go:7)    JLS    169
    //扩大栈帧,80个字节
    0x001a 00026 (D:\Demo\Go\src\zhanghui\main.go:7)    SUBQ    $80, SP
    0x001e 00030 (D:\Demo\Go\src\zhanghui\main.go:7)    MOVQ    BP, 72(SP)
    0x0023 00035 (D:\Demo\Go\src\zhanghui\main.go:7)    LEAQ    72(SP), BP
    //GC标记
    0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:7)    FUNCDATA    $0, gclocals·2589ca35330fc0fce83503f4569854a0(SB)
    0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:7)    FUNCDATA    $1, gclocals·e226d4ae4a7cad8835311c6a4683c14f(SB)

    //生成空的interface,因为println的参数是Empty Interface
    0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    "".a+88(FP), AX
    0x002d 00045 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    AX, ""..autotmp_1+48(SP)
    0x0032 00050 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    $0, ""..autotmp_0+56(SP)
    0x003b 00059 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    $0, ""..autotmp_0+64(SP)
    0x0044 00068 (D:\Demo\Go\src\zhanghui\main.go:8)    LEAQ    type.int(SB), CX
    0x004b 00075 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    CX, (SP)
    0x004f 00079 (D:\Demo\Go\src\zhanghui\main.go:8)    LEAQ    ""..autotmp_1+48(SP), CX
    0x0054 00084 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    CX, 8(SP)
    0x0059 00089 (D:\Demo\Go\src\zhanghui\main.go:8)    PCDATA    $0, $1
    0x0059 00089 (D:\Demo\Go\src\zhanghui\main.go:8)    CALL    runtime.convT2E(SB)
    0x005e 00094 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    16(SP), AX
    0x0063 00099 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    24(SP), CX
    0x0068 00104 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    AX, ""..autotmp_0+56(SP)
    0x006d 00109 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    CX, ""..autotmp_0+64(SP)
    0x0072 00114 (D:\Demo\Go\src\zhanghui\main.go:8)    LEAQ    ""..autotmp_0+56(SP), AX
    0x0077 00119 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    AX, (SP)
    0x007b 00123 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    $1, 8(SP)
    0x0084 00132 (D:\Demo\Go\src\zhanghui\main.go:8)    MOVQ    $1, 16(SP)
    0x008d 00141 (D:\Demo\Go\src\zhanghui\main.go:8)    PCDATA    $0, $1

    //调用println
    0x008d 00141 (D:\Demo\Go\src\zhanghui\main.go:8)    CALL    fmt.Println(SB)
    0x0092 00146 (D:\Demo\Go\src\zhanghui\main.go:9)    MOVQ    "".a+88(FP), AX
    //递增+1
    0x0097 00151 (D:\Demo\Go\src\zhanghui\main.go:9)    INCQ    AX
    //将结果返回给返回值
    0x009a 00154 (D:\Demo\Go\src\zhanghui\main.go:9)    MOVQ    AX, "".~r1+96(FP)
    0x009f 00159 (D:\Demo\Go\src\zhanghui\main.go:9)    MOVQ    72(SP), BP
    0x00a4 00164 (D:\Demo\Go\src\zhanghui\main.go:9)    ADDQ    $80, SP
    0x00a8 00168 (D:\Demo\Go\src\zhanghui\main.go:9)    RET

    //检查栈帧大小后可能会跳转过来,调用morestack_noctxt
    0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:9)    NOP
    0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:7)    PCDATA    $0, $-1
    0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:7)    CALL    runtime.morestack_noctxt(SB)
    0x00ae 00174 (D:\Demo\Go\src\zhanghui\main.go:7)    JMP    0

上面将汇编代码进行了几个简单的描述,下面是main函数的汇编代码,这个里面的就不在详细描述了

"".main t=1 size=179 args=0x0 locals=0x50
    0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:12)    TEXT    "".main(SB), $80-0
    0x0000 00000 (D:\Demo\Go\src\zhanghui\main.go:12)    MOVQ    TLS, CX
    0x0009 00009 (D:\Demo\Go\src\zhanghui\main.go:12)    MOVQ    (CX)(TLS*2), CX
    0x0010 00016 (D:\Demo\Go\src\zhanghui\main.go:12)    CMPQ    SP, 16(CX)
    0x0014 00020 (D:\Demo\Go\src\zhanghui\main.go:12)    JLS    169
    0x001a 00026 (D:\Demo\Go\src\zhanghui\main.go:12)    SUBQ    $80, SP
    0x001e 00030 (D:\Demo\Go\src\zhanghui\main.go:12)    MOVQ    BP, 72(SP)
    0x0023 00035 (D:\Demo\Go\src\zhanghui\main.go:12)    LEAQ    72(SP), BP
    0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:12)    FUNCDATA    $0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
    0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:12)    FUNCDATA    $1, gclocals·e226d4ae4a7cad8835311c6a4683c14f(SB)
    0x0028 00040 (D:\Demo\Go\src\zhanghui\main.go:13)    MOVQ    $2, (SP)
    0x0030 00048 (D:\Demo\Go\src\zhanghui\main.go:13)    PCDATA    $0, $0
    //调用myfun函数
    0x0030 00048 (D:\Demo\Go\src\zhanghui\main.go:13)    CALL    "".myfun(SB)

    0x0035 00053 (D:\Demo\Go\src\zhanghui\main.go:13)    MOVQ    8(SP), AX
    0x003a 00058 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    AX, ""..autotmp_6+48(SP)
    0x003f 00063 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    $0, ""..autotmp_5+56(SP)
    0x0048 00072 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    $0, ""..autotmp_5+64(SP)
    0x0051 00081 (D:\Demo\Go\src\zhanghui\main.go:14)    LEAQ    type.int(SB), AX
    0x0058 00088 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    AX, (SP)
    0x005c 00092 (D:\Demo\Go\src\zhanghui\main.go:14)    LEAQ    ""..autotmp_6+48(SP), AX
    0x0061 00097 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    AX, 8(SP)
    0x0066 00102 (D:\Demo\Go\src\zhanghui\main.go:14)    PCDATA    $0, $1

    //调用convT2E生成Empty Interface
    0x0066 00102 (D:\Demo\Go\src\zhanghui\main.go:14)    CALL    runtime.convT2E(SB)
    0x006b 00107 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    24(SP), AX
    0x0070 00112 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    16(SP), CX
    0x0075 00117 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    CX, ""..autotmp_5+56(SP)
    0x007a 00122 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    AX, ""..autotmp_5+64(SP)
    0x007f 00127 (D:\Demo\Go\src\zhanghui\main.go:14)    LEAQ    ""..autotmp_5+56(SP), AX
    0x0084 00132 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    AX, (SP)
    0x0088 00136 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    $1, 8(SP)
    0x0091 00145 (D:\Demo\Go\src\zhanghui\main.go:14)    MOVQ    $1, 16(SP)
    0x009a 00154 (D:\Demo\Go\src\zhanghui\main.go:14)    PCDATA    $0, $1
    0x009a 00154 (D:\Demo\Go\src\zhanghui\main.go:14)    CALL    fmt.Println(SB)
    0x009f 00159 (D:\Demo\Go\src\zhanghui\main.go:15)    MOVQ    72(SP), BP
    0x00a4 00164 (D:\Demo\Go\src\zhanghui\main.go:15)    ADDQ    $80, SP
    0x00a8 00168 (D:\Demo\Go\src\zhanghui\main.go:15)    RET
    0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:15)    NOP
    0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:12)    PCDATA    $0, $-1
    0x00a9 00169 (D:\Demo\Go\src\zhanghui\main.go:12)    CALL    runtime.morestack_noctxt(SB)
    0x00ae 00174 (D:\Demo\Go\src\zhanghui\main.go:12)    JMP    0

因为是64位CPU,所以是使用MOVQ,而不是MOVL

指令

  1. TEXT指令声明了符号””.add,指令紧接在类似于函数的主体中。TEXT块的最后必须是某种形式的跳转,通常是一个RET(伪)指令。(如果没有,链接器会追加一个跳转到块自身的指令,TEXT块中没有fallthrough) 符号的后面,参数是标志和栈帧的大小.
    比如下面的生成的add方法:

    TEXT    "".add(SB), $0-24
    FUNCDATA    $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    MOVQ    "".b+16(FP), AX
    MOVQ    "".a+8(FP), CX
    ADDQ    CX, AX
    MOVQ    AX, "".~r2+24(FP)
    RET

    add前面的””表示包名
    $0-24表示栈帧的大小,描述了函数有0字节的栈帧,并且需要24字节的参数和返回值的空间

  2. MOVQ指令:
    MOV指令就是将左侧的内容放到右侧的寄存器或者地址中去

  3. LEAQ指令:(Load Effective Address)
    LEAQ type.int(SB), AX
    这个表示将左侧的变量取地址,然后存放在右边的寄存器中

  4. FUNCDATA指令:这个是golang编译器自带的指令,标注做GC的一些变量,用来给GC收集进行提示

    FUNCDATA    $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
    FUNCDATA    $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)

    其中$0,$1用于局部函数调用的参数,需要回收

  5. CALL指令:
    CALL runtime.convT2E(SB)
    这个表示函数调用,将调用runtime包下面convT2E函数

Go中调用汇编

在Go的源码中有很多*.s的文件,这些就是汇编代码文件
举一个简单的例子
创建add.go文件:

package add

func Add(a, b int) int

创建main.go文件

package main

import (
  "fmt"
  "add"
)

func main() {
     fmt.Println(add.Add(10, 9))
}

然后在add.go的的同名目录下创建add_amd64.s(64位系统)

TEXT   ·Add(SB), $0-24
MOVQ    b+8(FP), AX
MOVQ    a+0(FP), CX
ADDQ    CX, AX
MOVQ    AX, res+16(FP)
RET

然后执行main.go,就可以发现正常调用汇编实现的Add方法了

总结

暂时只研究这么多了,我觉得大部分情况下是不用深入到汇编层面的,尤其是现在的这些高级语言,但是在定位一些语言运行机制问题,可以辅助汇编信息,查看具体指令流程,更深入了解语言的底层实现,是有好处的。

前期连接

【Free Style】深入Go语言模型(1):interface的底层详解

https://portal.huaweicloud.com/blogs/a6653d83c4f211e7b8317ca23e93a891


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200