万剑归宗从汇编认识GO语言

您所在的位置:网站首页 汇编应用 万剑归宗从汇编认识GO语言

万剑归宗从汇编认识GO语言

#万剑归宗从汇编认识GO语言| 来源: 网络整理| 查看: 265

一、概述

我们给出几个需要从汇编来认识GO语言的原因:

优化提升函数功能的性能; 认识Golang语言内部的实现机制 当我们需要深入去优化代码结构、系统架构,就不得不去深入了解Golang这门语言,去了解Golang内部实现:比如goroutine调度、io调度、map实现、string实现。Golang内部有go实现,也有汇编实现。 为了做更深入的优化,有时候不得不去写汇编,甚至根据特定汇编指令集来做优化。 据悉某节,为了提升JSON性能,借助汇编做了大量优化,带来了整体性能大幅度的提升。 实现一些比较超越语言自身束缚的能力 举例来说,GO这种类似于C/C++的语言产物,导致我们难以实现AOP这样的功能,但是我们利用汇编,可以为GO实现AOP功能。 二、plan9汇编简介

    Golang的开发团队和bell实验室(开发了Unix的那个实验室)开发plan9操作系统开发团队是同一批人,之所以用plan9作为汇编,因为他们很熟悉且具有跨架构的抽象性。 可以带来很多好处,最主要的一点是方便将 Go 移植到新的架构上,

     Go 的汇编器最重要的是要知道 Go 的汇编器不是对底层机器的直接表示。概括来说,特定于机器的指令会以他们的本尊出现, 然而对于一些通用的操作,如内存的移动以及子程序的调用以及返回通常都做了抽象。但细节因架构不同而不一样,官方也对这样的不精确性表示歉意,情况并不明确。汇编器程序的工作是对这样半抽象指令集进行解析并将其转变为可以输入到链接器的指令。

     Golang的基础方法中,使用了大量plan9汇编,其中包含了一些如4个伪寄存器等plan9特有的语法。关于 Go 的汇编器最重要的一点是它不是底层机器的直接表示。 一些细节精确映射到机器,但有些则不然。这是因为编译器套件(请参阅 此说明)不需要在通常的管道中传递汇编程序。相反,编译器在一种半抽象的指令集上运行,指令选择部分发生在代码生成之后。汇编器以半抽象的形式工作,所以当你看到这样的指令时MOV 工具链实际为该操作生成的可能根本不是移动指令,可能是清除指令或加载指令。或者它也可能与具有该名称的机器指令完全对应。 一般来说,特定于机器的操作往往表现为它们自己,而更一般的概念,如内存移动和子程序调用和返回则更抽象。细节因架构而异,对于不精确之处,我们深表歉意;情况不明确。

    汇编程序是一种解析该半抽象指令集的描述,并将其转换为要输入到链接器的指令的方法。如果您想查看给定体系结构(例如 amd64)的汇编指令,标准库的源代码中有很多示例,例如 runtime和 math/big. 您还可以检查编译器作为汇编代码发出的内容(实际输出可能与您在此处看到的不同):

IA64RAXRBXRCXRDXRDIRSIRBPRSPR8R9R10R11R12R13R14RIPPlan9AXBXCXDXDISIBPSPR8R9R10R11R12R13R14PC

    应用代码层面会用到的通用寄存器主要是: AX, BX, CX, DX, DI, SI, R8~R15 这 14 个寄存器,虽然 BP 和 SP 也可以用,不过 BP 和 SP 会被用来管理栈顶和栈底,最好不要拿来进行运算。

    Plan9 汇编的操作数方向和 Intel 汇编相反的,与 AT&T 类似。它的一些特点如下:

没有 push 和 pop,栈的操作通过SP 寄存器进行运算来实现的 常数在 plan9 汇编用 $num 表示,可以为负数,默认情况下为十进制。 操作数方向与intel相反,与AT&T类似 数据搬运的长度由 MOV 的后缀决定,如下 MOVB $1, DI // 1 byte MOVW $0x10, BX // 2 bytes MOVD $1, DX // 4 bytes MOVQ $-10, AX // 8 bytes

下面列出了常用的几个汇编指令(指令后缀Q 说明是 64 位上的汇编指令)

助记符指令种类用途示例MOVQ传送数据传送MOVQ 18, AX // 把 18 传送到 AXJLS转移条件转移指令JLS 0x0181 //左边小于右边,则跳到 0x0181LEAQ传送地址传送LEAQ AX, BX // 把 AX 有效地址传送到 BXPUSHQ传送栈压入PUSHQ AX // 将 AX 内容送入栈顶位置POPQ传送栈弹出POPQ AX // 弹出栈顶数据后修改栈顶指针ADDQ运算相加并赋值ADDQ BX, AX // 等价于 AX+=BXSUBQ运算相减并赋值SUBQ BX, AX // 等价于 AX-=BXCMPQ运算比较大小CMPQ SI CX // 比较 SI 和 CX 的大小CALL转移调用函数CALL runtime.printnl(SB) // 发起调用JMP转移无条件转移指令JMP 0x0181 //无条件转至 0x0181 地址处 三、Golang代码的汇编分析

查看方式

go tool compile -S .\main.go 也可 go build -gcflags -S .\main.go

-l 禁止内联 -N 编译时,禁止优化 -S 输出汇编代码

说明:下面仅简要演示一个Golang函数的汇编分析方式.

# cat x.go package main func main() { println(3) } # go tool compile -S .\main.go main.main STEXT size=66 args=0x0 locals=0x10 funcid=0x0 align=0x0 0x0000 00000 (G:\main.go:3) TEXT main.main(SB), ABIInternal, $16-0 0x0000 00000 (G:\main.go:3) CMPQ SP, 16(R14) 0x0004 00004 (G:\main.go:3) PCDATA $0, $-2 0x0004 00004 (G:\main.go:3) JLS 57 0x0006 00006 (G:\main.go:3) PCDATA $0, $-1 0x0006 00006 (G:\main.go:3) SUBQ $16, SP 0x000a 00010 (G:\main.go:3) MOVQ BP, 8(SP) 0x000f 00015 (G:\main.go:3) LEAQ 8(SP), BP 0x0014 00020 (G:\main.go:3) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB) 0x0014 00020 (G:\main.go:3) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB) 0x0014 00020 (G:\main.go:4) PCDATA $1, $0 0x0014 00020 (G:\main.go:4) CALL runtime.printlock(SB) 0x0019 00025 (G:\main.go:4) MOVL $3, AX 0x001e 00030 (G:\main.go:4) NOP 0x0020 00032 (G:\main.go:4) CALL runtime.printint(SB) 0x0025 00037 (G:\main.go:4) CALL runtime.printnl(SB) 0x002a 00042 (G:\main.go:4) CALL runtime.printunlock(SB) 0x002f 00047 (G:\main.go:5) MOVQ 8(SP), BP 0x0034 00052 (G:\main.go:5) ADDQ $16, SP 0x0038 00056 (G:\main.go:5) RET 0x0039 00057 (G:\main.go:5) NOP 0x0039 00057 (G:\main.go:3) PCDATA $1, $-1 0x0039 00057 (G:\main.go:3) PCDATA $0, $-2 0x0039 00057 (G:\main.go:3) CALL runtime.morestack_noctxt(SB) 0x003e 00062 (G:\main.go:3) PCDATA $0, $-1 0x003e 00062 (G:\main.go:3) NOP 0x0040 00064 (G:\main.go:3) JMP 0 0x0000 49 3b 66 10 76 33 48 83 ec 10 48 89 6c 24 08 48 I;f.v3H...H.l$.H 0x0010 8d 6c 24 08 e8 00 00 00 00 b8 03 00 00 00 66 90 .l$...........f. 0x0020 e8 00 00 00 00 e8 00 00 00 00 e8 00 00 00 00 48 ...............H 0x0030 8b 6c 24 08 48 83 c4 10 c3 e8 00 00 00 00 66 90 .l$.H.........f. 0x0040 eb be .. rel 21+4 t=7 runtime.printlock+0 rel 33+4 t=7 runtime.printint+0 rel 38+4 t=7 runtime.printnl+0 rel 43+4 t=7 runtime.printunlock+0 rel 58+4 t=7 runtime.morestack_noctxt+0 go:cuinfo.producer. SDWARFCUINFO dupok size=0 0x0000 72 65 67 61 62 69 regabi go:cuinfo.packagename.main SDWARFCUINFO dupok size=0 0x0000 6d 61 69 6e main main..inittask SNOPTRDATA size=24 0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x0010 00 00 00 00 00 00 00 00 ........ gclocals·g2BeySu+wFnoycgXfElmcg== SRODATA dupok size=8 0x0000 01 00 00 00 00 00 00 00 ........

说明: FUNCDATA and PCDATA含垃圾收集器使用的信息;它们由编译器引入。

函数的调用过程

image.png Golang一律使用栈来传输入参与出参,所以函数调用有一定的性能损耗,通过函数内联来缓解这个问题的影响,这和C/CPP有点差异.

Golang的编译过程

词法分析:根据空格等符号分词 语法分析:生成AST 语义分析:类型检查+逃逸分析+内联等 (禁止函数内联就是操作这个步骤) 中间码生成:替换一些底层函数(如判断使用makeslice64或makeslice) 代码优化:顾名思义,就是搞提升并行,指令优化,利用寄存器等代码优化 机器代码生成:根据GOARCH,生成plan9,最终生成本地汇编 plan9的常量

尽管go汇编器的指导来自 Plan 9 汇编器,但它却是一个不同的程序,因此存在一些差异。 汇编器中的常量表达式使用 Go 的运算符优先级进行解析,而不是原始的类 C 优先级。因此3&1



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3