菲佣自有天收 發表於 2021-7-3 01:05:00

Go汇编语言

<p>Go的汇编器继承自Plan9的汇编器,但与Plan9汇编器仍有很多不同之处。</p>
<p>Plan9并不是Go语言中特有的东西,而是指贝尔实验室中开发的一个操作系统。</p>
<blockquote>
<p>贝尔实验室九号项目(英语:Plan 9 from Bell Labs,常简称为Plan 9)是一个分布式操作系统,由贝尔实验室的计算科学研究中心在1980年代中期至2002年开发,以作为UNIX的后继者。它现在仍然被操作系统的研究者和爱好者开发使用。</p>
</blockquote>
<p>Go汇编并不是对底层机器的直接表示,相反,它是一种<strong>半抽象</strong>的汇编语言,是Go编译器的输出,并作为Go汇编器的输入,Go汇编器会将Go汇编表示的指令翻译成具体平台的机器码。所以使用Go汇编编写的程序,在编译完成后可能和原来并不相同。例如Go汇编的<code>MOV</code>指令,在编译完成后可能会变成清除指令或加载指令,再例如,在arm中,为了方便编写汇编,可能使用一条<code>DIV</code>或<code>MOD</code>指令来做算数操作,但实际上其对应的arm指令可能不止一条。 另外,Go汇编也不是个独立的语言,无法独立使用,必须结合Go代码。Go汇编必须以Go包的方式组织,同时包中至少要有一个Go源文件用于指明当前包名等基本包信息。</p>
<p>Go使用Plan9汇编意在提供更好的跨平台特性,让汇编与机器无关,但是并没能实现。对于一些通用的,例如内存操作指令和过程调用指令,由于这些指令在许多平台上基本相同,所以可以进行抽象,但是对一些平台独有的指令,Go汇编却并不能进行抽象。</p>
<p>在命名上,Go汇编必须以CPU的体系结构作为文件名的后缀,如<code>_amd64</code>、<code>arm</code>等。(?)</p>
<p>在Go源码中,<code>src/cmd</code>目录下是一些与Go相关的命令实现,如<code>go fmt</code>、<code>go doc</code>等,而在<code>src/cmd/compile</code>目录下,便是Go编译器的实现。 对于一个Go语言文件,可以使用该目录下的一些工具来查看生成的汇编。如可以使用下面的命令查看一个Go源文件生成的Go汇编。</p>
<pre><code class="language-bash">$ GOOS=linux GOARCH=amd64 go tool compile -S main.go      

$ go build -gcflags -S main.go
</code></pre>
<p>要看最终生成的机器相关的汇编,可以使用<code>go tool objdump</code>:</p>
<pre><code class="language-bash">$ go build -o a.out main.go
$ go tool objdump -s main.main a.out
</code></pre>
<h2 id="伪寄存器">伪寄存器</h2>
<p>在Go汇编中,预定义了4个伪寄存器。它们并不是真正的寄存器,只是为了适配多平台而提供的一种抽象,会在生成机器码的时候映射到具体平台的真正的寄存器上:</p>
<ul>
<li><code>FP</code>: Frame pointer</li>
<li><code>PC</code>: Program counter</li>
<li><code>SB</code>: Static base pointer</li>
<li><code>SP</code>: Stack pointer</li>
</ul>
<p>FP寄存器是一个指向函数参数的栈帧指针,通常用FP加上一个偏移来访问函数的参数和返回值。例如用<code>0(FP)</code>访问第一个参数,<code>8(FP)</code>访问第二个参数(在64位机器上)。但是实际上以这种方式访问函数参数时,还需要再前面加上参数的名称:<code>first_arg+0(FP)</code>、<code>second_arg+8(FP)</code>,其中<code>+</code>并没有任何意义,只用作分隔符。</p>
<p>PC为程序计数器,指向当前程序的执行位置,在x86中它是<code>eip</code>或<code>rip</code>寄存器。</p>
<p>SB寄存器的作用是声明某个符号是内存中的一个地址,例如<code>foo(SB)</code>表示符号<code>foo</code>指代内存中的一个地址,类似x86汇编中的label。SB寄存器通常在全局函数或全局变量的命名中使用。如果在符号后边加一个<code>&lt;&gt;</code>,即<code>foo&lt;&gt;(SB)</code>,表示<code>foo</code>仅在当前文件中可见。SB还可以加上一个偏移使用,如<code>foo+4(SB)</code>表示从<code>foo</code>开始的第4个字节处。注意该处的<code>+</code>与FP的不同,该处确实有“加“的含义。</p>
<p>SP是栈指针,指向当前函数的栈帧,用来访问函数的局部变量或参数。在x86中它是<code>esp</code>或<code>rsp</code></p>
<blockquote>
<p>所有用户定义的符号都会被写成SB和FP加偏移的形式。SP寄存器包含了FP的功能。 其实在我看到的x86下的Go汇编中,编译器一般不使用FP寄存器, 而是使用SP来访问局部变量和函数参数。</p>
</blockquote>
<h2 id="函数与数据定义">函数与数据定义</h2>
<p>Go汇编中,函数的定义和调用必须要包含包名,即使用<code>fmt.Printf</code>或<code>math/rand.Int</code>这种形式,但是在Go汇编中,一些符号有特殊的含义,例如<code>.</code>,所以不能使用这些符号。但是又中点<code>·</code>和除号<code>/</code>,所以可以写成 <code>fmt·Printf</code> 和 <code>math∕rand·Int</code>这种形式。在当前包中,为了简便,可以只写一个中点,而不必写完整的包名,如<code>·Int</code>,但是在某些复杂的情况下,必须要写成这种简便的形式。</p>
<p>与其他风格的汇编类似,Go汇编也需要为函数或数据指明它们所在的节,如TEXT节或DATA节。但是Go汇编在定义每个函数或变量时都要明确指定:</p>
<pre><code>TEXT runtime·profileloop(SB),NOSPLIT,$8
        MOVQ        $runtime·profileloop1(SB), CX
        MOVQ        CX, 0(SP)
        CALL        runtime·externalthreadhandler(SB)
        RET
</code></pre>
<p>TEXT指令后面分别是函数名、函数标记(flags)和帧大小。通常情况下,帧大小后面跟着一个参数大小,并用负号分隔,如<code>$24-8</code>,其中帧大小为24,参数大小为8。由于这里使用了<code>NOSPLIT</code>标示,可以不提供参数大小。有时候函数没有栈帧,可以将帧大小设置为0。同样的,有些函数也没有参数和返回值,可以将参数大小设置为0。在函数的最后,必须是短跳转指令或<code>RET</code>指令,如果不是,Go链接器会在函数后面添加一个跳转到该函数自身的指令。</p>
<p>数据的定义可以使用DATA指令:</p>
<pre><code>DATA        symbol+offset(SB)/width, value
</code></pre>
<p>其中<code>offset</code>为相对于<code>symbol</code>的偏移,<code>width</code>为数据宽度,<code>value</code>为数据的值。</p>
<h2 id="go_asmh"><code>go_asm.h</code></h2>
<p>在调用<code>go build</code>时,如果当前包下有<code>.s</code>文件,那么Go编译器会生成一个特殊的<code>go_asm.h</code>文件。该文件中使用<code>#define</code>定义了一些常量,其中包含了当前包中<code>const</code>的大部分定义、Go结构体的大小和字段偏移。</p>
<p>常量的形式为<code>const_name</code>,例如对于Go定义<code>const bufSize = 1024</code>,在Go汇编中可以使用<code>const_bufSize</code>来访问。</p>
<p>结构体大小的形式为<code>type_size</code>,结构体字段的偏移的形式为<code>type_field</code>。例如对于下面的结构体:</p>
<pre><code>type reader struct {
        buf byte
        r   int
}
</code></pre>
<p>可以在Go汇编中使用<code>reader_size</code>获取该结构体的大小,使用<code>reader_buf</code>和<code>reader_r</code>访问该结构体的字段。</p>
<p>如果Go编译器在生成<code>go_asm.h</code>文件时出现命名冲突的话,将会触发”宏重定义“错误。</p>
<hr>
<p>关于更多Go汇编的介绍可以查看:</p>
<p>https://golang.org/doc/asm</p>
<p>https://chai2010.cn/advanced-go-programming-book/ch3-asm/readme.html</p>
<p>关于Plan9汇编器的详细语法可以查看:</p>
<p>http://doc.cat-v.org/plan_9/4th_edition/papers/asm</p><br><br>
来源:https://www.cnblogs.com/tcctw/p/14965412.html
頁: [1]
查看完整版本: Go汇编语言