21

gdb が解析できないスタック トレースを手動で解釈できるように、OCaml 呼び出し規約を見つけようとしています。残念ながら、一般的な観察以外に英語で書かれたものはないようです。たとえば、人々は OCaml が多くの引数をレジスタで渡すブログにコメントするでしょう。(どこかに英語のドキュメントがある場合は、リンクをいただければ幸いです。)

だから私は ocamlopt ソースからそれをパズルしようとしてきました。誰かがこれらの推測の正確性を確認できますか?

そして、最初の 10 個の引数がレジスタに渡されることについて私が正しければ、関数呼び出しに引数を復元することは一般的に不可能ですか? C では、正しいフレームに戻れば、引数はスタックのどこかにプッシュされます。OCaml では、呼び出し先は呼び出し元の引数を自由に破棄できるように見えます。


レジスタ割り当て(から/asmcomp/amd64/proc.ml)

OCaml 関数を呼び出すには、

  • 最初の 10 個の整数およびポインター引数は、レジスター rax、rbx、rdi、rsi、rdx、rcx、r8、r9、r10、および r11 に渡されます。
  • 最初の 10 個の浮動小数点引数はレジスタ xmm0 ~ xmm9 で渡されます。
  • 追加の引数がスタックにプッシュされ ( leftmost-first-in? )、float と int とポインターが混在します。
  • トラップ ポインター (以下の「例外」を参照) は r14 で渡されます。
  • 割り当てポインター (おそらく、このブログ投稿で説明されているマイナー ヒープ用) は r15 で渡されます。
  • 戻り値は、整数またはポインターの場合は rax に返され、浮動小数点の場合は xmm0 に返されます。
  • すべてのレジスタは呼び出し元保存ですか?

C 関数の呼び出しには、標準の amd64 C 規則が使用されます。

  • 最初の 6 つの整数およびポインター引数は、rdi、rsi、rdx、rcs、r8、および r9 で渡されます。
  • 最初の 8 つの float 引数は xmm0 から xmm7 で渡されます
  • 追加の引数はスタックにプッシュされます
  • 戻り値はraxまたはxmm0で返されます
  • レジスタ rbx、rbp、および r12 ~ r15 は callee-save です。

返送先住所(から/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 は caller-save レジスタのみを使用する特殊な呼び出し規則を使用していると思います。この選択の利点は、末尾呼び出しが簡素化されることです。末尾呼び出し¹ をジャンプするときに、レジスタをスピルまたはリロードする必要はありません。

¹: 非自己末尾呼び出しの場合、これは引数があまりない場合にのみ機能するため、スピルする必要はありません。スタック割り当てが必要な場合、呼び出しは非テール呼び出しに変わります。

呼び出し規約は依然としてターゲット アーキテクチャに強く依存していることに注意してください。たとえば x86 では、レジスタが使い果たされたとき、スタックにスピルする前に少数のグローバルが使用され、テールコールが保持されます。

「左端から先入れ」についても同意します。引数は in によって順番にトラバースされ、 calling_conventionsinproc.mlによってオフセット順に格納さslot_offsetemit.mlpます。それらは右から左に計算されましたが、順番に返されましたselectgen.ml

于 2012-07-04T06:38:04.320 に答える
4

はい、OCamlはレジスタを可能な限り再利用しようとするため、呼び出しから引数を回復することはできません。したがって、関数の残りの部分で役に立たなくなった場合、コンテンツは破棄されます。デバッガーには引数を出力する方法がありません。関数内の特定の時点で、まだ有効な変数を出力することしかできませんが、そのためには、値を回復するためにDWARFコードをダンプするようにocamloptを変更する必要があります。

于 2012-07-04T15:34:59.767 に答える