Go汇编之全局变量

在.go文件中声明全局变量,在.s文件中初始化。先看操作。

第一步:新建main.go和var.s文件
第二步:在main.go中输入如下代码

package main

import (
	"fmt"
)

var Id int

func main() {
	fmt.Println(Id)
}

第三步:在var.s中输入如下代码

#include "textflag.h"

GLOBL ·Id(SB),NOPTR,$8

DATA ·Id+0(SB)/1,$0x37
DATA ·Id+1(SB)/1,$0x25
DATA ·Id+2(SB)/1,$0x00
DATA ·Id+3(SB)/1,$0x00
DATA ·Id+4(SB)/1,$0x00
DATA ·Id+5(SB)/1,$0x00
DATA ·Id+6(SB)/1,$0x00
DATA ·Id+7(SB)/1,$0x00

第四步:go run main.go

如无意外,以上代码将输出9527.


我们主要来看看汇编代码都干了啥。

第一行引入头文件是为了导入NOPTR符号,它表示变量中不包含指针,加这个标识是为了能通过GC扫描,否则编译时会报错。textflag.h文件在Go源码目录的runtime目录下,代码不多,不妨看下。

// 该文件定义的表使可用于函数和数据,由编译器、汇编器、链接器支持
// 和 src/cmd/internal/obj/textflag.go 保持一致

// Don't profile the marked routine. This flag is deprecated.
#define NOPROF	1
// It is ok for the linker to get multiple of these symbols. It will
// pick one of the duplicates to use.
#define DUPOK	2
// Don't insert stack check preamble.
#define NOSPLIT	4
// Put this data in a read-only section.
#define RODATA	8
// This data contains no pointers.
#define NOPTR	16
// This is a wrapper function and should not count as disabling 'recover'.
#define WRAPPER 32
// This function uses its incoming context register.
#define NEEDCTXT 64
// Allocate a word of thread local storage and store the offset from the
// thread local base to the thread local storage in this variable.
#define TLSBSS	256
// Do not insert instructions to allocate a stack frame for this function.
// Only valid on functions that declare a frame size of 0.
// TODO(mwhudson): only implemented for ppc64x at present.
#define NOFRAME 512
// Function can call reflect.Type.Method or reflect.Type.MethodByName.
#define REFLECTMETHOD 1024
// Function is the top of the call stack. Call stack unwinders should stop
// at this function.
#define TOPFRAME 2048

英文比较简单,懒得翻译了,将就看吧。

GLOBL 用来导出变量 Id,这样它才能和main.go中的Id产生关联。接下来是一个·,注意它并不是小数点,而是中文的中点。

似乎有点绕,其实就是中文输入法的反引号,Unicode码是U+00B7。接下来我们思考两个问题:

  1. ·的作用是什么?
  2. 为什么是·而不是.

先说·的作用,其实和小数点是一样的。什么时候会用到小数点呢?当然是访问某个包的某个符号(这里的符号包括变量或函数)时,比如fmt.Println·Id其实就是指当前main包中的Id变量,甚至你也可以写作main·Id,当然后面也要全部加上main前缀,如下:

#include "textflag.h"

GLOBL main·Id(SB),NOPTR,$8

DATA main·Id+0(SB)/1,$0x37
DATA main·Id+1(SB)/1,$0x25
DATA main·Id+2(SB)/1,$0x00
DATA main·Id+3(SB)/1,$0x00
DATA main·Id+4(SB)/1,$0x00
DATA main·Id+5(SB)/1,$0x00
DATA main·Id+6(SB)/1,$0x00
DATA main·Id+7(SB)/1,$0x00

这个故事告诉我们在汇编中访问当前包的变量可以省略包名,但是不能省略·

那么为什么不是小数点,和go代码保持一致呢?众所周知,Go汇编是从plan9汇编移植过来的,为了支持Go的包特性,同时简化汇编器词法扫描程序的实现,就用·代替了小数点以区别浮点数中的小数点。同时被替换的还有/,在Go汇编中被替换为大写除法,它比/要更倾斜一点,Unicode码是U+2215,靠键盘好像还打不出来的样子,可以从这里复制。的作用就是包路径分隔符,在最后会看到它的例子。当然编译后·会被替换回小数点,也会被替换回正常的/

·Id就对应main包的符号Id,再后面是(SB)。这可不是傻逼,它是Go汇编的一个伪寄存器,全称是Static Base pointer,是静态内存的起始地址。全局变量和全局函数就放在静态内存,它们都是通过SB加上一个偏移量来访问的。

刚学C语言的时候有人告诉我变量就是一片内存,可是不知道变量是如何跟内存产生联系的。变量(更准确的说是符号)其实只是一个偏移量,其出现的地方决定了基于谁来偏移,这就是变量的本质。

再后面NOPTR表示Id中不包含指针,$8表示Id变量的宽度是8字节。$8其实就是8,常量必须以$开头,别问为什么,问就是语法。

问题又来了,我们说的符号究竟是Id还是·Id呢?

答案是后者,符号应该包含完整包路径。所以GLOBL的语法是GLOBL 符号(SB),标识,宽度

最后我们通过DATA来初始化内存。·Id我们已经很熟悉了,它是基于SB的一个偏移量。后面又+了一个数字,表示的是基于·Id再偏移的字节数。也就是说·Id+0(SB)就是一个新的偏移量,·Id是在寻址符号Id,+x就是在寻址符号Id内部的第x个字节,因为它占了8个字节。/后的1表示内存宽度,也就是你要初始化几个字节,这里的/是正常的斜线,不是大写除法。最后的常量是你要初始化的值。

所以DATA的语法格式是:DATA 符号+偏移(SB)/宽度,值

这里又两个问题:

  1. 可不可以一次初始化多个字节?
  2. 初始化内存宽度是否可以任意?
  3. 偏移量是否可以超出符号宽度?

首先第一个问题肯定是可以的,否则按照Go少即是多的原则就是不会有/去指定宽度了。比如我们可以将Id的初始化写成下面这样:

DATA ·Id+0(SB)/4,$0x2537
DATA ·Id+4(SB)/4,$0x00

甚至可以是:

DATA ·Id+0(SB)/8,$0x2537

或者只是:

DATA ·Id+0(SB)/2,$0x2537

因为Go内存是默认零值初始化的,所以哪些本身就是0的字节可以不初始化。

关于第二个问题,的确可以,但必须是2的整数次幂,比如1,2,4,8,16这样。

第三个问题只要是非负整数就可以,它并不会限制你初始化的这片内存是不是在该变量区域内,也不会检查偏移量有没有超出变量宽度,因为在汇编里并没有类型的概念,一切皆是内存。但是这样的代码也会带来严重的安全问题,没有人会写出这样有害的代码,而且孙悟空也不会答应。

虽然对偏移量和宽度没什么像样的限制,但是有一条,初始化内存不能相互覆盖。比如像下面这样的代码是不行的:

DATA ·Id+0(SB)/4,$0x2537
DATA ·Id+3(SB)/4,$0x00

最后的最后,还有一个空行。注意,最后的空行是必须的。

到这里我们已经介绍完了示例代码的所有内容,不过似乎少了一点东西。我们还没有看到注释呢,Go汇编的注释和Go是一样的,通过///**/注释。


最后一个例子,看看如何访问其他包的符号。

我们先新建一个variable包,并新建一个variable.go文件,输入以下代码:

package variable

var Count int

main.go修改如下:

package main

import (
	"asm/variable"
	"fmt"
)

var Id int

func main() {
	fmt.Println(Id)
	fmt.Println(variable.Count)
}

var.s修改如下:

#include "textflag.h"

GLOBL ·Id(SB),NOPTR,$8
GLOBL asm∕variable·Count(SB),NOPTR,$8

//初始化Id
DATA ·Id+0(SB)/4,$0x2537
DATA ·Id+4(SB)/4,$0x00

//初始化Count
DATA asm∕variable·Count+0(SB)/2,$0x2537

不出意外,你会看到打印两个9527。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值