ocaml callgraph

KCacheGrind : un outil simple mais diablement efficace pour analyser les callgraph de programmes en C (ou python, php, perl, ...) afin de faire du débogage ou encore de l'analyse de performance.

Situation actuelle avec un programme caml simple

Exemple utilisé :

let bar i =
        1+i
;;

let foo i = 2+(bar i ) ;;

let () = print_int ( foo 3 ) ;;

$ ocamlopt -inline 0 -o foo t.ml
$ valgrind --tool=callgrind ./foo
$ kcachegrind callgrind.out.10624

t-caml-without-patch-callgr.png

Mais voilà, l'assembleur généré par le compilateur caml ne contient pas toutes les instructions utilisées par valgrind lors de l'analyse du programme. Ainsi, aucun label de fonction n'apparait et nous n'avons le droit qu'à des adresses mémoire en hexadécimal :(

/!\ Problème corrigé pour la version 3.11 : détails

Exemple du fonctionnement normal en C

Voyons comment valgrind fonctionne avec du C :

int bar(int a) {
        return 1+a;
}

int foo(int a) { return 2+bar(a); }

int main() { foo(3); }

$ gcc -O0 -o foo t.c
$ valgrind --tool=callgrind ./foo
$ kcachegrind callgrind.out.10719

t-c-callgraph.png

Cette fois-ci, nous obtenons un graphe correct avec le nom des fonctions; Regardons maintenant l'assembleur généré par gcc

 $gcc -O0 -S t.c

Assembleur de GCC :

        .file   "t.c"
        .text
.globl bar
        .type   bar, @function
bar:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        incl    %eax
        popl    %ebp
        ret
        .size   bar, .-bar
.globl foo
        .type   foo, @function
foo:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $4, %esp
        movl    8(%ebp), %eax
        movl    %eax, (%esp)
        call    bar
        addl    $2, %eax
        leave
        ret
        .size   foo, .-foo

Comparons maintenant celui-ci à l'assembleur généré par le compilateur ocaml : Assembleur OCaml

        .text
        .align  16
     .globl     camlT__bar_58
        .type   camlT__bar_58,@function
camlT__bar_58:
.L100:
        addl    $2, %eax
        ret
        .text
        .align  16
     .globl     camlT__foo_60
        .type   camlT__foo_60,@function
camlT__foo_60:
.L101:
        call    camlT__bar_58
.L102:
        addl    $4, %eax
        ret

Dans les deux cas, nous retrouvons bien nos deux fonctions foo et bar avec les instructions : .globl, .type mais il manque .size à la fin des fonctions caml! Ceci est la source du problème pour valgrind, car après analyse de son code source, il ignore les fonctions de taille nulle ...

Solution

Il suffit d'appliquer ce minuscule patch sur le compilateur ocaml pour générer des exécutables ELF valides aux yeux de valgrind : patch-alter_elf_for_valgrind-cvs-080620

(Patch réalisé sur la version CVS, qui correspond à la futur version 3.11)

Nous obtenons alors le callgraph suivant sur le premier exemple : t-caml-valid-callgraph.png

Exemple sur un vrai projet utilisant ocamlnet :

callgraph-with-patch2.png

Comments

You can use your Fediverse (i.e. Mastodon, among many others) account to reply to this post
(Note that comments from locked accounts won't be visible on the blog, but only to me)