英文:
What is a context register in golang?
问题
上下文寄存器是什么,它如何改变Go代码的编译方式?
*背景:*我在一些GOROOT的部分(例如reflect
)中看到了对存根函数的使用,但我不太确定它们是如何工作的。
英文:
What is a context register and how does it change the way Go code is compiled?
Context: I've seen uses of stub functions in some parts of GOROOT, i.e. reflect
, but am not quite sure how these work.
答案1
得分: 6
“上下文寄存器”一词首次出现在提交 b1b67a3(2013年2月,Go 1.1rc2)中,用于实现Go 1.1函数调用的第三步。
更改
reflect.MakeFunc
的实现,以避免运行时代码生成。
它在2014年2月的提交 4a000b9中被采用,用于原生客户端 x86-64 的汇编和系统调用,其中将sigreturn管理为:
NaCl
已放弃其传统的操作系统责任,并拒绝实现"sigreturn
"。
相反,返回到我们程序的执行的唯一方法是自己恢复寄存器。
不幸的是,这在严格的准确性下是不可能的,因为没有办法在不进行以下操作的情况下完成结束序列的最后一次更新:
- (1) 跳转到一个寄存器,这样寄存器最终将保存 PC 值而不是其预期值,或者
- (2) 将
PC
存储在堆栈上并使用RET
,这要求SP
是有效的,并且可以破坏其下面的字。
第二种方法通常是两种方法中较小的邪恶,但在 NaCl 上,链接器必须将
RET
重写为"POP reg; AND $~31, reg; JMP reg
",因此无论哪种方式,我们都将因为传入的信号而丢失一个寄存器。
类似地,没有办法恢复
EFLAGS
;通常的方法是使用POPFL
,但NaCl
拒绝该指令。
我们可以检查位并执行一系列旨在重新创建这些标志设置的指令,但这是很多工作。
幸运的是,Go 的信号处理程序从不尝试直接返回到执行代码,因此所有寄存器和
EFLAGS
都是无效的,可以被破坏。
唯一重要的寄存器是为信号处理程序创建的模拟调用设置的寄存器。
今天,这些寄存器只是PC
和SP
,但是以后可能还会有其他寄存器相关(例如DX
是 Go 函数的“上下文寄存器”),我们尽可能恢复尽可能多的寄存器。
最近(2016年第四季度),为了 Go 1.8,我们有了提交 d5bd797和提交 bf9c71c,用于消除堆栈重新扫描:
morestack
将上下文指针写入gobuf.ctxt
,但由于morestack
是用汇编编写的(必须非常小心地处理状态),因此对于此写入,不调用所需的写入屏障。相反,我们稍后在newstack
中修复这个问题,在那里我们为ctxt
调用一个显式的写入屏障。
这已经需要一些微妙的推理,并且在混合屏障中会变得更加复杂。
通过简化整个机制来修复这个问题。
不要在morestack
中写入gobuf.ctxt
,只需将上下文寄存器的值传递给newstack
,并让它将其写入gobuf.ctxt
。这是一个普通的 Go 指针写入,因此它会得到正常的 Go 写入屏障。不需要微妙的推理。
英文:
The expression "context register" first appeared in commit b1b67a3 (Feb. 2013, Go 1.1rc2) for implementing step 3 of the Go 1.1 Function Calls
> Change the reflect.MakeFunc
implementation to avoid run-time code generation as well
It was picked up in commit 4a000b9 in Feb. 2014, Go 1.3beta1, for assembly and system calls for Native Client x86-64, where sigreturn is managed as:
> NaCl
has abidcated its traditional operating system responsibility and declined to implement 'sigreturn
'.
Instead the only way to return to the execution of our program is to restore the registers ourselves.
> Unfortunately, that is impossible to do with strict fidelity, because there is no way to do the final update of PC that ends the sequence without either
> - (1) jumping to a register, in which case the register ends holding the PC value instead of its intended value, or
- (2) storing the
PC
on the stack and usingRET
, which imposes the requirement thatSP
is valid and that is okay to smash the word below it.
> The second would normally be the lesser of the two evils, except that on NaCl, the linker must rewrite RET
into "POP reg; AND $~31, reg; JMP reg
", so either way we are going to lose a register as a result of the incoming signal.
> Similarly, there is no way to restore EFLAGS
; the usual way is to use POPFL
, but NaCl
rejects that instruction.
We could inspect the bits and execute a sequence of instructions designed to recreate those flag settings, but that's a lot of work.
> Thankfully, Go's signal handlers never try to return directly to the executing code, so all the registers and EFLAGS
are dead and can be smashed.
The only registers that matter are the ones that are setting up for the simulated call that the signal handler has created.
Today those registers are just PC
and SP
, but in case additional registers are relevant in the future (for example DX
is the Go func context register
) we restore as many registers as possible.
Much more recently (Q4 2016), for Go 1.8, we have commit d5bd797 and commit bf9c71c, for eliminating stack rescanning:
> morestack
writes the context pointer to gobuf.ctxt
, but since
morestack
is written in assembly (and has to be very careful with
state), it does not invoke the requisite write barrier for this
write. Instead, we patch this up later, in newstack
, where we invoke
an explicit write barrier for ctxt.
> This already requires some subtle reasoning, and it's going to get a
lot hairier with the hybrid barrier.
> Fix this by simplifying the whole mechanism.
Instead of writing gobuf.ctxt
in morestack
, just pass the value of the context register to newstack
and let it write it to gobuf.ctxt
. This is a normal Go pointer write, so it gets the normal Go write barrier. No subtle reasoning required.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论