Golang汇编快速指南

符号

有些符号,例如PCR0SP,是预定义的并且是对一个寄存器的引用。 另外还有两种预定义的符号,SB(static base)和FP(frame pointer)。 所有用户定义的符号,除了标签跳转之外,都是对伪寄存器的offsets操作。

1)SB伪寄存器可以想象成内存的地址,所以符号foo(SB)是一个由foo这个名字代表的内存地址。这种形式一般用来命名全局函数和数据。给名字增加一个<>符号,就像foo<>(SB),会让这个名字只有在当前文件可见,就像在C文件中预定义的static

2)FP伪寄存器是一个虚拟的帧指针,用来指向函数的参数。编译器维护了一个虚拟的栈指针,使用对伪寄存器的offsets操作的形式,指向栈上的函数参数。 于是,0(FP)就是第一个参数,8(FP)就是第二个(64位机器),以此类推。 当用这种方式引用函数参数时,可以很方便的在符号前面加上一个名称,就像first_arg+0(FP)second_arg+8(FP)。有些汇编程序强制使用这种约定,禁止单一的0(FP)8(FP)。在使用Go标准定义的汇编函数中,go vet会检查参数的名字和它们的匹配范围。 在32位系统上,一个64位值的高32和低32位表示为增加_lo_hi这个两个后缀到一个名称,就像arg_lo+0(FP)或者arg_hi+4(FP)。如果一个Go原型函数没有命名它的结果,期待的名字将会被返回。

3)SP伪寄存器是一个虚拟的栈指针,用来指向栈帧本地的变量为函数调用准备参数。它指向本地栈帧的顶部,所以一个对栈帧的引用必须是一个负值且范围在[-framesize:0]之间,例如: x-8(SP)y-4(SP),以此类推。在CPU架构中,存在一个真实的寄存器SP,虚拟的栈寄存器和真实的SP寄存器的区别在于名字的前缀上。就是说,x-8(SP)-8(SP)是不同的内存地址:前者是引用伪栈指针寄存器,但后者是硬件中真实存在的SP寄存器。

指令、寄存器和汇编指令始终使用大写字母表示,提醒你汇编语言编程是非常令人担忧的。(例外:在ARM平台下,代表当前goroutine的g寄存器被重新命名。)

在Go对象文件和二进制文件中,符号的完整名字是包的路径加上一个句点:fmt.Printfmath/rand.Int。但是汇编器会把句点和斜杠当做标点符号来对待,这些字符不能当做符号的标识符。取而代之的是,允许在汇编程序中使用中点字符(Unicode字符00B7)和除法斜杠(原文中是division slash,Unicode字符2215,区别于forward slash)当做标识符并且把它们重写成纯句点和斜杠。 在汇编语言的源文件中,上面的符号写成fmt·Printfmath∕rand.Int。 通过在编译时使用-S标志看到的汇编代码列表中直接显示了句点和斜杠,而不是在汇编程序中需要的Unicode替代字符(指上面的两个特殊Unicode字符)。

大部分手写的汇编文件中,不要在符号名中包含完整的包路径,因为链接器会在任何以句点开头的名字前面插入当前对象文件的路径:在math/rand包的汇编源文件中,rand包的Int函数被当做了·Int来引用。这种便捷性避免了需要在自身的源代码中硬编码导入路径,可以让代码从一个地方移动到另一个地方时变得更容易。

指令

汇编程序中使用多种指令绑定文本和数据到符号名。举个例子,下面有一个简单但是完整的函数定义。TEXT指令声明了符号runtime·profileloop,指令紧接在类似于函数的主体中。TEXT块的最后必须是某种形式的跳转,通常是一个RET(伪)指令。(如果没有,链接器会追加一个跳转到块自身的指令,TEXT块中没有fallthrough) 符号的后面,参数是标志栈帧的大小,是一个常量(但是看下面的代码):

TEXT runtime·profileloop(SB),NOSPLIT,$8
    MOVQ    $runtime·profileloop1(SB), CX
    MOVQ    CX, 0(SP)
    CALL    runtime·externalthreadhandler(SB)
    RET

这个函数的栈帧大小为8字节(MOVQ CX, 0(SP)操作栈指针),没有参数

一般情况下,栈帧的大小跟在参数的大小之后,由一个减法符号分隔。(它不是减号,只是特殊的语法) 栈帧大小是$24-8描述了函数有24字节的栈帧并且需要一个8字节的参数,存在于调用者的栈帧中。如果没有为TEXT指定NOSPLIT标志,必须提供参数大小。在使用Go标准定义的汇编函数中,go vet会检查参数大小是否正确。

注意符号名是使用中点来分割组件的,并且被定义为从伪寄存器SB开始的一个offsets。在Go源码的runtime包中,使用简称profileloop来调用。

全局数据符号使用初始化的一系列DATA指令来定义,并且跟在一个GLOBAL指令之后。每个DATA指令初始化一块指定的内存区域。没有明确初始化的内存区域会被置为零。标准的DATA指令形式为:

DATA    symbol+offset(SB)/width, value

这样就初始化了symbol,内存在指定的offset处,带有指定的width和给定的value。一个symbol中的DATA指令必须是逐渐增长的offsets。

GLOBAL指令将一个symbol声明为全局的。参数是可选的标志和需要声明为全局的数据的大小,并会初始化为零值,除非DATA指令中已经初始化它。GLOBAL指令必须跟在对应的DATA指令之后。

举例:

DATA divtab<>+0x00(SB)/4, $0xf4f8fcff
DATA divtab<>+0x04(SB)/4, $0xe6eaedf0
...
DATA divtab<>+0x3c(SB)/4, $0x81828384
GLOBL divtab<>(SB), RODATA, $64

GLOBL runtime·tlsoffset(SB), NOPTR, $4

声明并且初始化了divtab<>,一个只读的64位table含有4字节的整数值。 并且声明了runtime·tlsoffset,一个4字节并且明确被零值初始化的值,其中不含有指针。

指令可以含有一个或者两个参数。如果有两个参数,第一个是比特掩码的标志,可以写成数字的表达式,多个掩码之间可以相加或者做逻辑或运算,或者可以写成友好可读的形式。这些值定义在头文件textflag.h中:

  • NOPROF = 1 (TEXT项使用.) 不优化NOPROF标记的函数。这个标志已废弃。

  • DUPOK = 2 在二进制文件中允许一个符号的多个实例。链接器会选择其中之一。

  • NOSPLIT = 4 (TEXT项使用.) 不插入预先检测是否将栈空间分裂的代码。程序的栈帧中,如果调用任何其他代码都会增加栈帧的大小,必须在栈顶留出可用空间。用来保护处理栈空间分裂的代码本身。

  • RODATA = 8 (DATA和GLOBAL项使用.) 将这个数据放在只读的块中。

  • NOPTR = 16 这个数据不包含指针所以就不需要垃圾收集器来扫描。

  • WRAPPER = 32 (For TEXT items.) This is a wrapper function and should not count as disabling recover.

此文为官方文档的翻译:https://golang.org/doc/asm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值