21

我一直在尝试找到 OCaml 调用约定,以便我可以手动解释 gdb 无法解析的堆栈跟踪。不幸的是,除了一般性观察之外,似乎没有用英语写过任何东西。例如,人们会在博客上评论 OCaml 在寄存器中传递了许多参数。(如果某处有英文文档,将不胜感激。)

所以我一直试图从 ocamlopt 源中解开它。谁能证实这些猜测的准确性?

而且,如果我对在寄存器中传递的前十个参数是正确的,那么通常不可能将参数恢复到函数调用吗?在 C 语言中,只要我回到正确的帧,参数仍然会被推入堆栈的某个地方。在 OCaml 中,被调用者似乎可以自由地销毁调用者的参数。


寄存器分配(来自/asmcomp/amd64/proc.ml

为了调用 OCaml 函数,

  • 前 10 个整数和指针参数在寄存器 rax、rbx、rdi、rsi、rdx、rcx、r8、r9、r10 和 r11 中传递
  • 前 10 个浮点参数在寄存器 xmm0 - xmm9 中传递
  • 额外的参数被推入堆栈(最左边先入?),浮点数和整数以及混合的指针
  • 陷阱指针(参见下面的异常)在 r14 中传递
  • 在 r15 中传递分配指针(可能是本博文中所述的次要堆)
  • 如果是整数或指针,返回值在 rax 中传回,如果是浮点数,返回值在 xmm0 中
  • 所有寄存器都是调用者保存的?

为了调用 C 函数,使用标准 amd64 C 约定:

  • 前六个整数和指针参数在 rdi、rsi、rdx、rcs、r8 和 r9 中传递
  • 前八个浮点参数在 xmm0 - xmm7 中传递
  • 额外的参数被压入堆栈
  • 返回值在 rax 或 xmm0 中传回
  • 寄存器 rbx、rbp 和 r12 - r15 是被调用者保存的

退货地址(来自/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 中。

4

2 回答 2

6

这更像是一个答案而不是一个问题!我对这个主题的了解是通过查看源代码了解到的,就像你一样,所以不要指望进一步的精确度比你的帖子更权威。

是的,我认为 OCaml 仅使用带有调用者保存寄存器的专门调用约定。这种选择的一个好处是它简化了尾调用:当您跳过尾调用时,您不必溢出或重新加载任何寄存器。

¹:对于非自我尾调用,这只适用于没有太多参数的情况,因此我们不需要溢出。如果需要堆栈分配,则调用将变为非尾调用。

请注意,调用约定仍然很大程度上取决于目标体系结构。例如,在 x86 上,当寄存器用尽时和溢出到堆栈上之前,使用少量全局变量来保留尾调用。

我也同意“最左先入”:参数按 in 顺序遍历,按incalling_conventions偏移proc.ml顺序存储;它们从右到左计算,但按顺序返回,在.slot_offsetemit.mlpselectgen.ml

于 2012-07-04T06:38:04.320 回答
4

是的,您无法从调用中恢复参数,因为 OCaml 会尝试尽可能多地重用寄存器,因此如果它在函数的其余部分不再有用,则会破坏它们的内容。调试器无法打印参数,它们只能在函数中的给定点打印仍然存在的变量,但为此,您需要修改 ocamlopt 以转储 DWARF 代码以恢复值。

于 2012-07-04T15:34:59.767 回答