我一直在尝试找到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...)
Run Code Online (Sandbox Code Playgroud)
然后像这样发出组件(右边的目的地):
call .body
(...handler...)
jmp .join
.body:
pushq %r14
movq %rsp, %r14
(...body...)
popq %r14
addq %rsp, 8
.join:
(...rest...)
Run Code Online (Sandbox Code Playgroud)
在身体的某个地方,有一个线性化的操作码Lraise,它作为这个精确的组件发出:
movq %r14, %rsp
popq %r14
ret
Run Code Online (Sandbox Code Playgroud)
哪个真的很整洁!我们创建了一个伪帧,而不是这个setjmp/longjmp业务,它的返回地址是异常处理程序,其唯一的本地是前一个这样的虚拟帧.该/asmcomp/amd64/proc.ml有评论呼吁$ R14"陷阱指针",所以我会打电话给这个假帧陷阱框架.当我们想要引发异常时,我们将堆栈指针设置为最近的陷阱帧,在此之前将陷阱指针设置为陷阱帧,然后"返回"到异常处理程序中.我敢打赌,如果异常处理程序无法处理此异常,它只会重新加载它.
例外是在%eax中.
这是一个答案而不是一个问题!关于这个主题我知道的一点,我通过查看源代码学习,就像你一样,所以不要指望进一步的精确度比你的帖子更具权威性.
是的,我认为OCaml仅使用具有调用者保存寄存器的专用调用约定.这种选择的好处是它简化了尾调用:当你跳过尾调用¹时,你不必溢出或重新加载任何寄存器.
¹:对于非自我尾调用,这只适用于没有太多参数的情况,因此我们不需要泄漏.如果需要堆栈分配,则将呼叫转换为非尾调用.
请注意,调用约定仍然强烈依赖于目标体系结构.例如,在x86上,当寄存器耗尽并溢出堆栈之前,会使用少量的全局变量来保留尾调用.
我也同意"最左边的先在":参数,以便通过遍历calling_conventions中proc.ml,通过存储在偏置秩序slot_offset的emit.mlp; 他们从右到左计算,但按顺序返回selectgen.ml.