He estado tratando de encontrar la convención de llamadas OCaml para poder interpretar manualmente los rastreos de pila que gdb no puede analizar. Desafortunadamente, parece que nunca se ha escrito nada en inglés, excepto por observaciones generales. Por ejemplo, la gente comentará en los blogs que OCaml pasa muchos argumentos en los registros. (Si hay documentación en inglés en alguna parte, un enlace sería muy apreciado.)Convención de llamadas OCaml: ¿es este un resumen preciso?
He intentado descifrarlo de la fuente de ocamlopt. ¿Alguien podría confirmar la precisión de estas conjeturas?
Y, si estoy en lo cierto acerca de los primeros diez argumentos que se pasan en los registros, ¿no es generalmente posible recuperar los argumentos a una llamada a función? En C, los argumentos aún se insertarían en la pila en algún lugar, si solo vuelvo al cuadro correcto. En OCaml, parece que las calles son libres de destruir los argumentos de sus interlocutores.
Registro asignación (de /asmcomp/amd64/proc.ml
)
Para poner en funciones OCaml,
- se pasan Los primeros argumentos 10 enteros y el puntero en los registros Rax, rbx, RDI, RSI , rdx, rcx, r8, r9, r10 y r11
- Los 10 primeros argumentos de coma flotante se pasan en los registros xmm0 - xmm9
- Se insertan argumentos adicionales en la pila (leftmost-first-in?), flotadores y ints y punteros entremezclados
- El puntero trampa (ver excepciones a continuación) se pasa en r14
- El puntero de asignación (presumiblemente para el montón menor como se describe en esta blog post) se pasa en r15
- El el valor de retorno se transfiere en rax si es un entero o un puntero, y en xmm0 si es un float
- ¿Todos los registros guardan la llamada?
Para poner en funciones de C, se utiliza la convención amd64 C estándar:
- La primera de seis enteros y puntero argumentos se pasan en RDI, RSI, RDX, rcs, r8, y r9
- los primeros ocho argumentos flotador se pasan en xmm0 - xmm7
- argumentos adicionales se insertan en la pila
- el valor de retorno se pasa de nuevo en rax o xmm0
- Los registros Rbx, RBP, y R12 - R15 son destinatario de la llamada a guardar
Dirección de devolución (de /asmcomp/amd64/emit.mlp
)
La dirección de retorno es el primer puntero empujado en el marco de llamada, de acuerdo con amd64 C convención. (Supongo que la instrucción ret
asume este diseño.)
excepciones (de /asmcomp/linearize.ml
)
El código try (...body...) with (...handler...); (...rest...)
consigue linealizado como esto:
Lsetuptrap .body
(...handler...)
Lbranch .join
Llabel .body
Lpushtrap
(...body...)
Lpoptrap
Llabel .join
(...rest...)
y luego se emite en forma de montaje como éste (destinos a la derecha):
call .body
(...handler...)
jmp .join
.body:
pushq %r14
movq %rsp, %r14
(...body...)
popq %r14
addq %rsp, 8
.join:
(...rest...)
En algún lugar del cuerpo, hay un código de operación linealizado Lraise
que se emite como este montaje exacto:
movq %r14, %rsp
popq %r14
ret
¿Qué es realmente bueno! En lugar de este negocio setjmp/longjmp, creamos un marco ficticio cuya dirección de retorno es el manejador de excepciones y cuyo único local es el marco ficticio anterior. El /asmcomp/amd64/proc.ml
tiene un comentario que llama a $ r14 el "puntero de trampa", así que llamaré a este marco falso el marco de trampa. Cuando queremos plantear una excepción, establecemos el puntero de pila en el marco de captura más reciente, establecemos el puntero de trampa en el marco de captura antes de eso y luego "regresamos" al manejador de excepciones. Y apuesto a que si el manejador de excepciones no puede manejar esta excepción, simplemente lo sube.
La excepción está en% eax.