我正在慢慢学习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
我们可以通过应用多个更改使其正常工作:
- 删除
FUNCDATA
和PCDATA
行 - 用
·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/ 的要求。
6条答案
按热度按时间hrirmatl1#
有人在Slack上提到了https://github.com/rsc/tmp/tree/master/go2asm,这似乎是一个非常相似的工具。看起来有点过时,而且似乎只支持amd64架构。我想知道是否乐观地支持所有架构会是一个好主意。/cc @rsc
4smxwvx52#
允许对函数进行过滤,这是我经常需要的功能。
toiithl63#
我忘记提到一点了——为什么这应该成为
go tool compile
的一部分。我个人认为,这与go tool compile -S
的实用性相当,甚至可能更高。但如果它能像x/tools
一样生活在一个仓库里,只要它仍然得到官方维护并且容易找到,我也会觉得没问题。s2j5cfk04#
有人知道如何处理例如
CALL runtime.convT32(SB)
的情况吗?我得到错误expected '(', found .
。在这种情况下,有可能从汇编代码中调用其他函数吗?im9ewurl5#
将
.
更改为中间点·
,将CALL runtime·convT32(SB)
更改为中间点 x1m3n1x。dldeef676#
感谢!现在有3个新问题,请问是否可以问一下,因为它们可能相关:
x := []byte{1, 2}
?我尝试使用type·[2]uint8(SB)
,但它根本不喜欢方括号。autotmp_7
?例如MOVQ ""..autotmp_7+24(SP), AX
;这似乎用于尝试写入*bytes.Buffer
时。🤔make
?例如CALL runtime·makeslice(SB)
与relocation target runtime.makeslice not defined for ABI0 (but is defined for ABIInternal)
一起失败,但然后我认为我们不能使用ABIInternal
对吗?🤔