[go]汇编语言

文章目录

    • 计算机结构
    • 常量与变量
      • 全局变量
      • 常量
      • 数组
      • 字符串
    • 函数
      • 参数与返回值
      • goroutine

Go汇编程序无法独立使用,必须以Go包的方式组织,同时包中至少要有一个Go语言文件用于指明当前包名等基本包信息。如果Go汇编代码中定义的变量和函数要被其它Go语言代码引用,还需要通过Go语言代码将汇编中定义的符号声明出来。

查看汇编代码:go tool compile -S asm.go

计算机结构

X86 CPU体系结构
[go]汇编语言_第1张图片

左边为常见内存布局:

  • text一般为代码段(只读),存储要执行的指令数据;
  • rodata(只读)与data数据段,一般存放全局数据;
  • heap段用于动态管理数据;
  • stack段为栈数据(一般函数调用与局部变量);

中间为X86寄存器:

  • BP记录当前函数帧的开始位置(函数调用会隐式影响其值);
  • SP对应当前栈(顶)指针的位置;

Go汇编为了简化汇编代码的编写,引入了PC、FP、SP、SB四个伪寄存器(详见《go汇编ASM简介》)

[go]汇编语言_第2张图片

在AMD64环境:

  • 伪PC寄存器是IP指令计数器寄存器的别名;
  • 伪FP寄存器对应的是函数的帧指针,一般用来访问函数的参数和返回值;
  • 伪SP栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量(真SP寄存器对应的是栈的顶部);
    • 使用SP时有一个临时标识符前缀(一般为局部变量名)就是伪SP:如a(SP)和b-8(SP)
    • 没有临时标识符前缀的是真SP寄存器:如(SP)和+8(SP)

常量与变量

Go汇编语言提供了DATA命令用于初始化包变量:

  • symbol为变量标识符;
  • offset为地址偏移;
  • width为内存宽度(大小)
  • value是初始化值(常量需以$开始,如$0xA1)
DATA symbol+offset(SB)/width, value

全局变量

全局变量是包一级的变量,全局变量一般有着较为固定的内存地址,声明周期跨越整个程序运行时间。

GLOBL symbol(SB), width

定义好的变量需要导出后,才可供其他代码引用(在汇编中定义变量,然后在go中使用):

// asm.go
package asm

var count int32  // 声明,会关联到.s中对应变量
var Name string

// asm_amd64.s  是amd64的汇编
#include "textflag.h"

GLOBL ·Id(SB),NOPTR,$4

DATA ·count+0(SB)/1,$1
DATA ·count+1(SB)/1,$2
DATA ·count+2(SB)/1,$3
DATA ·count+3(SB)/1,$4
// 或者(小端)
// DATA ·count+0(SB)/4,$0x04030201

GLOBL ·Name(SB),NOPTR,$24

DATA ·Name+0(SB)/8,$·Name+16(SB)
DATA ·Name+8(SB)/8,$6
DATA ·Name+16(SB)/8,$"gopher"

常量

Go汇编语言中常量以$美元符号为前缀。常量的类型有整数、浮点数、字符和字符串等几种类型。数值型常量,可通过表达式构造新的常量:

$1           // 十进制
$0x1234ABCD  // 十六进制
$1.5         // 浮点数
$'a'         // 字符
$"abcd"      // 字符串

$2+2      // == $4
$(3&1)<<2 // == $4

数组

Go语言中数组是一种扁平内存结构的基础类型:

var num [2]int

// asm
GLOBL ·num(SB),$16
DATA ·num+0(SB)/8,$0
DATA ·num+8(SB)/8,$0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mOFliZ1F-1670753220575)(pitures/2022-go-asm-array-2int.png)]

:切片与字符串类似,是一种带Header的结构

字符串

字符串(只读类型,要避免在汇编中直接修改字符串底层数据的内容)在go中是一个struct(包含数据地址与长度);amd64环境中StringHeader有16个字节大小,因此在汇编中定义一个16字节大小的变量(·helloworld):

// data of string
GLOBL text<>(SB),NOPTR,$16
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!"

// header of string
GLOBL ·helloworld(SB),$16
DATA ·helloworld+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld+8(SB)/8,$12         // StringHeader.Len

函数

函数标识符通过TEXT汇编指令定义,其后的指令一般对应函数的实现:
TEXT symbol(SB), [flags,] $framesize[-argsize]

各部分说明:

  • symbol:函数名(当前包的路径可以省略)后面是(SB),表示是函数名符号相对于SB伪寄存器的偏移量,二者组合在一起形成最终的绝对地址。
  • flags:标志(textlags.h文件中定义):
    • NOSPLIT:不生成或包含栈分裂(栈大小检测)代码(一般用于没有任何其它函数调用的叶子函数);
    • WRAPPER:表示这个是一个包装函数,在panic或runtime.caller等某些处理函数帧的地方不会增加函数帧计数;
    • NEEDCTXT:表示需要一个上下文参数,一般用于闭包函数;
  • framesize:表示函数的局部变量需要多少栈空间,包含调用其它函数时准备调用参数的隐式栈空间;
    [go]汇编语言_第3张图片

参数与返回值

Go汇编语言要求,任何通过FP伪寄存器访问的变量必和一个临时标识符前缀组合后才能有效,一般使用参数对应的变量名作为前缀。函数的第一个参数和第一个返回值会分别进行一次地址对齐。

[go]汇编语言_第4张图片

goroutine

Goroutine是Go中最基本的执行单元。goroutine就是一段代码加一个函数入口,以及在堆上为其分配的堆栈(初始4k,可随需要增长收缩);因此其非常廉价。

goroutine调用与普通函数类似:

  • 先设定参数;
  • 将函数地址(f)和参数大小(12)压栈;
  • 调用runtime.newproc
    • 新建一个栈空间,将参数复制到新栈;
    • 保存函数(f存放到strcut G的entry中);
    • 等待调度;
  • 恢复栈
  MOVL    $1, 0(SP)
  MOVL    $2, 4(SP)
  MOVL    $3, 8(SP)
  PUSHQ   $f(SB)
  PUSHQ   $12
  CALL    runtime.newproc(SB)
  POPQ    AX
  POPQ    AX

你可能感兴趣的:(Go,golang,asm,汇编,调用栈)