`cmd/compile:简化将Go函数转换为可构建的Go汇编代码`

dy1byipe  于 2个月前  发布在  Go
关注(0)|答案(6)|浏览(30)

我正在慢慢学习x86汇编以及Go如何使用它,所以看到现有的Go函数是如何转换成汇编语言是非常有用的。
接下来的逻辑步骤是能够对汇编代码进行一些小的修改,以检验我对它的理解是否正确,并尝试手动改进代码。然而,我发现,将一个函数从一个.go文件移动到一个.s文件并没有那么简单。
在阅读了一些在线博客文章和一些尝试和错误之后,我发现大多数函数都可以手工翻译。例如,看一个包含这个非常简单的Go函数的单一文件:

package p

func Add(x, y int) int {
        return x + y
}

使用go tool compile -S f.go,我们可以浏览输出来找到它的汇编代码:

TEXT    "".Add(SB), NOSPLIT|ABIInternal, $0-24
FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
FUNCDATA        $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
PCDATA  $2, $0
PCDATA  $0, $0
MOVQ    "".y+16(SP), AX
MOVQ    "".x+8(SP), CX
ADDQ    CX, AX
MOVQ    AX, "".~r2+24(SP)
RET

然而,我们不能只是将汇编代码添加到源代码中,使Go函数成为一个存根,因为这样做会失败:

$ go build
# foo.bar
./f.s:1: string constant must be an immediate
./f.s:7: string constant must be an immediate
./f.s:8: string constant must be an immediate
./f.s:10: string constant must be an immediate
asm: assembly of ./f.s failed

我们可以通过应用多个更改使其正常工作:

  • 删除FUNCDATAPCDATA
  • ·Func替换"".Func
  • 删除所有寻址模式(?),如NOSPLIT|ABIInternal,以消除类似illegal or missing addressing mode for symbol NOSPLIT这样的错误
  • "".var替换var
  • r2替换"".~r2(我仍然不明白那个波浪线)

最终结果与Go代码一样好:

TEXT    ·Add(SB),$0-24
MOVQ    y+16(SP), AX
MOVQ    x+8(SP), CX
ADDQ    CX, AX
MOVQ    AX, r2+24(SP)
RET

在这种情况下,我们完成了。但是,如果函数有像JMP 90这样的跳转,我们在找到正确的行后必须添加一个标签,如L90,然后修改跳转为JMP L90。即使汇编代码正常工作,go vet也倾向于抱怨上述过程的结果-例如在我们的例子中:

$ go vet
# foo.bar
./f.s:6:1: [amd64] Add: RET without writing to 8-byte ret+16(FP)

我已经成功地在一个较小的范围内对多个函数进行了这种操作,但这很繁琐。更糟糕的是,我甚至还没有能够正确地将一个调用另一个函数的函数翻译成汇编-我一直收到像runtime: unexpected return pc for ...这样的恐慌,这让人非常困惑。
我认为编译器应该支持将Go函数翻译成汇编语言,这样就可以将其直接输出到一个.s文件中,而不需要替换Go实现为存根。它是否能直接处理一些奇怪的边缘情况并不重要;如果至少它能在完成上述繁重工作后给出一个起点,那就已经是一个巨大的改进了。
脑海中浮现出的一些改进思路(感谢@josharian的输入):

  • 允许过滤函数,例如通过正则表达式。类似于GOSSAFUNC
  • 添加一种模式,使得输出理论上/可能可以在一个.s文件中原样构建。

例如,借鉴类似于compile -d的标志,有人可能会想象用类似的方式来完成我上面手动完成的操作。如果这是有意义的,我很乐意在即将到来的周期中做一些工作。我只需要编译器团队的一点帮助,因为我对汇编(尤其是Go汇编语法)的知识有限。
/cc @randall77@griesemer@josharian@mdempsky@martisch,根据 https://dev.golang.org/owners/ 的要求。

hrirmatl

hrirmatl1#

有人在Slack上提到了https://github.com/rsc/tmp/tree/master/go2asm,这似乎是一个非常相似的工具。看起来有点过时,而且似乎只支持amd64架构。我想知道是否乐观地支持所有架构会是一个好主意。/cc @rsc

4smxwvx5

4smxwvx52#

允许对函数进行过滤,这是我经常需要的功能。

toiithl6

toiithl63#

我忘记提到一点了——为什么这应该成为go tool compile的一部分。我个人认为,这与go tool compile -S的实用性相当,甚至可能更高。但如果它能像x/tools一样生活在一个仓库里,只要它仍然得到官方维护并且容易找到,我也会觉得没问题。

s2j5cfk0

s2j5cfk04#

有人知道如何处理例如CALL runtime.convT32(SB)的情况吗?我得到错误expected '(', found .。在这种情况下,有可能从汇编代码中调用其他函数吗?

im9ewurl

im9ewurl5#

. 更改为中间点 ·,将 CALL runtime·convT32(SB) 更改为中间点 x1m3n1x。

dldeef67

dldeef676#

感谢!现在有3个新问题,请问是否可以问一下,因为它们可能相关:

  1. 如何用汇编语言创建一个内联切片,如 x := []byte{1, 2}?我尝试使用 type·[2]uint8(SB),但它根本不喜欢方括号。
  2. 如何处理 autotmp_7?例如 MOVQ ""..autotmp_7+24(SP), AX;这似乎用于尝试写入 *bytes.Buffer 时。🤔
  3. 如何为字节切片调用运行时函数,如 make?例如 CALL runtime·makeslice(SB)relocation target runtime.makeslice not defined for ABI0 (but is defined for ABIInternal) 一起失败,但然后我认为我们不能使用 ABIInternal 对吗?🤔

相关问题