我一直在尝试找到OCaml调用约定,以便我可以手动解释gdb无法解析的堆栈跟踪。不幸的是,除了一般观察之外,似乎没有任何东西用英语写下来。例如,人们将在博客上评论OCaml在寄存器中传递了许多参数。 (如果某处有英文文档,我们非常感谢链接。)
所以我一直试图从ocamlopt来源解决它。任何人都可以确认这些猜测的准确性吗?
而且,如果我对寄存器中传递的前十个参数是对的,那么通常不可能恢复函数调用的参数吗?在C中,如果只是向后走到正确的帧,参数仍会被推到堆栈的某个地方。在OCaml中,似乎被调用者可以自由地破坏他们的呼叫者的论点。
注册分配(来自/asmcomp/amd64/proc.ml
)
用于调用OCaml函数,
对于调用C函数,使用标准的amd64 C约定:
返回地址(来自/asmcomp/amd64/emit.mlp
)
返回地址是按照amd64 C约定推入调用帧的第一个指针。 (我猜测ret
指令采用这种布局。)
例外(来自/asmcomp/linearize.ml
)
代码try (...body...) with (...handler...); (...rest...)
如下所示线性化:
Lsetuptrap .body
(...handler...)
Lbranch .join
Llabel .body
Lpushtrap
(...body...)
Lpoptrap
Llabel .join
(...rest...)
然后以像这样的程序集(右边的目的地)发出:
call .body
(...handler...)
jmp .join
.body:
pushq %r14
movq %rsp, %r14
(...body...)
popq %r14
addq %rsp, 8
.join:
(...rest...)
在身体的某个地方,有一个线性化的操作码Lraise
,它会像这个精确的程序集一样被发出:
movq %r14, %rsp
popq %r14
ret
哪个真的很整洁!我们创建了一个伪帧,而不是这个setjmp / longjmp业务,它的返回地址是异常处理程序,其唯一的本地是前一个这样的虚拟帧。 /asmcomp/amd64/proc.ml
有一个注释调用$ r14“陷阱指针”所以我将这个虚拟帧称为陷阱帧。当我们想要引发异常时,我们将堆栈指针设置为最近的陷阱帧,在此之前将陷阱指针设置为陷阱帧,然后“返回”到异常处理程序中。我敢打赌,如果异常处理程序无法处理此异常,它只会重新加载它。
例外是%eax。
答案 0 :(得分:6)
这是一个答案,而不是一个问题!关于这个话题我知道的一点,我通过查看源代码来学习,就像你一样,所以不要指望进一步的精确性比你的帖子更具权威性。
是的,我认为OCaml仅使用专门的调用约定和调用者保存寄存器。这种选择的好处是它简化了尾调用:当你跳过尾调用¹时,你不必溢出或重新加载任何寄存器。
¹:对于非自我尾调用,这只适用于没有太多参数的情况,因此我们不需要泄漏。如果需要堆栈分配,则呼叫将变为非尾部呼叫。
请注意,调用约定仍然强烈依赖于目标体系结构。例如,在x86上,当寄存器耗尽并且在堆栈上溢出之前,会使用少量的全局变量来保留尾调用。
我也同意“leftmost-first-in”:calling_conventions
中的proc.ml
按{0}}顺序遍历参数,slot_offset
中的emit.mlp
按偏移顺序存储;它们从右向左计算,但按顺序返回selectgen.ml
。
答案 1 :(得分:4)
是的,你无法从调用中恢复参数,因为OCaml试图尽可能多地重用寄存器,并且如果它在函数的剩余部分中不再有用,则会破坏它们的内容。调试器无法打印参数,它们只能在函数的给定点打印仍然存在的变量,但为此,您需要修改ocamlopt以转储DWARF代码以恢复值。