Je recherche un outil comme ltrace ou strace qui peut tracer des fonctions définies localement dans un exécutable. ltrace ne trace que les appels de bibliothèque dynamique et strace ne trace que les appels système. Par exemple, étant donné le programme C suivant:
#include <stdio.h>
int triple ( int x )
{
return 3 * x;
}
int main (void)
{
printf("%d\n", triple(10));
return 0;
}
L'exécution du programme avec ltrace
affichera l'appel à printf
car il s'agit d'une fonction de bibliothèque standard (qui est une bibliothèque dynamique sur mon système) et strace
affichera tout le système les appels du code de démarrage, les appels système utilisés pour implémenter printf et le code d'arrêt, mais je veux quelque chose qui me montrera que la fonction triple
a été appelée. En supposant que les fonctions locales n'ont pas été intégrées par un compilateur d'optimisation et que le binaire n'a pas été supprimé (symboles supprimés), existe-t-il un outil qui peut le faire?
Modifier
Quelques clarifications:
En supposant que vous ne souhaitiez être averti que pour des fonctions spécifiques, vous pouvez le faire comme ceci:
compiler avec des informations de débogage (comme vous avez déjà des informations sur les symboles, vous avez probablement aussi suffisamment de débogages)
donné
#include <iostream>
int fac(int n) {
if(n == 0)
return 1;
return n * fac(n-1);
}
int main()
{
for(int i=0;i<4;i++)
std::cout << fac(i) << std::endl;
}
Utilisez gdb pour tracer:
[js@Host2 cpp]$ g++ -g3 test.cpp
[js@Host2 cpp]$ gdb ./a.out
(gdb) b fac
Breakpoint 1 at 0x804866a: file test.cpp, line 4.
(gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>silent
>bt 1
>c
>end
(gdb) run
Starting program: /home/js/cpp/a.out
#0 fac (n=0) at test.cpp:4
1
#0 fac (n=1) at test.cpp:4
#0 fac (n=0) at test.cpp:4
1
#0 fac (n=2) at test.cpp:4
#0 fac (n=1) at test.cpp:4
#0 fac (n=0) at test.cpp:4
2
#0 fac (n=3) at test.cpp:4
#0 fac (n=2) at test.cpp:4
#0 fac (n=1) at test.cpp:4
#0 fac (n=0) at test.cpp:4
6
Program exited normally.
(gdb)
Voici ce que je fais pour collecter toutes les adresses des fonctions:
tmp=$(mktemp)
readelf -s ./a.out | gawk '
{
if($4 == "FUNC" && $2 != 0) {
print "# code for " $NF;
print "b *0x" $2;
print "commands";
print "silent";
print "bt 1";
print "c";
print "end";
print "";
}
}' > $tmp;
gdb --command=$tmp ./a.out;
rm -f $tmp
Notez qu'au lieu d'imprimer simplement le cadre actuel (bt 1
), vous pouvez faire tout ce que vous voulez, imprimer la valeur de certains globaux, exécuter une commande Shell ou envoyer quelque chose s'il atteint le fatal_bomb_exploded
function :) Malheureusement, gcc affiche des messages "Current Language changed" entre les deux. Mais c'est facilement perceptible. Pas grave.
System Tap peut être utilisé sur une machine Linux moderne (Fedora 10, RHEL 5, etc.).
Téléchargez d'abord le script para-callgraph.stp .
Exécutez ensuite:
$ Sudo stap para-callgraph.stp 'process("/bin/ls").function("*")' -c /bin/ls
0 ls(12631):->main argc=0x1 argv=0x7fff1ec3b038
276 ls(12631): ->human_options spec=0x0 opts=0x61a28c block_size=0x61a290
365 ls(12631): <-human_options return=0x0
496 ls(12631): ->clone_quoting_options o=0x0
657 ls(12631): ->xmemdup p=0x61a600 s=0x28
815 ls(12631): ->xmalloc n=0x28
908 ls(12631): <-xmalloc return=0x1efe540
950 ls(12631): <-xmemdup return=0x1efe540
990 ls(12631): <-clone_quoting_options return=0x1efe540
1030 ls(12631): ->get_quoting_style o=0x1efe540
Voir aussi: Observer, mises à jour systemtap et oprofile
En supposant que vous vouliez tracer toutes les fonctions dans ~/Desktop/datalog-2.2/datalog
Lorsque vous l'appelez avec les paramètres -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
cd /usr/src/linux-`uname -r`/tools/perf
for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do Sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
Sudo ./perf record -agR $(for j in $(Sudo ./perf probe -l | cut -d' ' -f3); do echo "-e $j"; done) ~/Desktop/datalog-2.2/datalog -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
Sudo ./perf report -G
En supposant que vous pouvez recompiler (aucun changement de source requis) le code que vous souhaitez tracer avec l'option gcc -finstrument-functions
, vous pouvez utiliser etrace pour obtenir le graphe d'appel de fonction.
Voici à quoi ressemble la sortie:
\-- main
| \-- Crumble_make_Apple_crumble
| | \-- Crumble_buy_stuff
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | | \-- Crumble_buy
| | \-- Crumble_prepare_apples
| | | \-- Crumble_skin_and_dice
| | \-- Crumble_mix
| | \-- Crumble_finalize
| | | \-- Crumble_put
| | | \-- Crumble_put
| | \-- Crumble_cook
| | | \-- Crumble_put
| | | \-- Crumble_bake
Sous Solaris, truss (équivalent strace) a la capacité de filtrer la bibliothèque à tracer. Je suis surpris quand j'ai découvert que Strace n'avait pas une telle capacité.
$ Sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out
Plus: ftrace.1
Si vous externalisez cette fonction dans une bibliothèque externe, vous devriez également pouvoir la voir être appelée, (avec ltrace).
La raison pour laquelle cela fonctionne est que ltrace se met entre votre application et la bibliothèque, et lorsque tout le code est internalisé avec le seul fichier, il ne peut pas intercepter l'appel.
ie: ltrace xterm
crache des trucs à partir des bibliothèques X, et X est à peine système.
En dehors de cela, la seule vraie façon de le faire est l'interception au moment de la compilation via des drapeaux de prof ou des symboles de débogage.
Je viens de parcourir cette application, qui semble intéressante:
http://www.gnu.org/software/cflow/
Mais je ne pense pas que ce soit ce que vous voulez.
Il existe un script Shell pour automatiser les appels de fonction de traçage avec gdb. Mais il ne peut pas s'attacher au processus en cours.
blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/
Copie de la page - http://web.archive.org/web/20090317091725/http://blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project -debugger /
Copie de l'outil - callgraph.tar.gz
http://web.archive.org/web/20090317091725/http://superadditive.com/software/callgraph.tar.gz
Il vide toutes les fonctions du programme et génère un fichier de commandes gdb avec des points d'arrêt sur chaque fonction. A chaque point d'arrêt, "backtrace 2" et "continue" sont exécutés.
Ce script est assez lent sur les gros projets (~ des milliers de fonctions), donc j'ajoute un filtre sur la liste des fonctions (via egrep). C'était très facile, et j'utilise ce script presque tous les jours.
Si les fonctions ne sont pas intégrées, vous pourriez même avoir de la chance en utilisant objdump -d <program>
.
Pour un exemple, prenons un butin au début de la routine main
de GCC 4.3.2:
$ objdump `which gcc` -d | grep '\(call\|main\)'
08053270 <main>:
8053270: 8d 4c 24 04 lea 0x4(%esp),%ecx
--
8053299: 89 1c 24 mov %ebx,(%esp)
805329c: e8 8f 60 ff ff call 8049330 <strlen@plt>
80532a1: 8d 04 03 lea (%ebx,%eax,1),%eax
--
80532cf: 89 04 24 mov %eax,(%esp)
80532d2: e8 b9 c9 00 00 call 805fc90 <xmalloc_set_program_name>
80532d7: 8b 5d 9c mov 0xffffff9c(%ebp),%ebx
--
80532e4: 89 04 24 mov %eax,(%esp)
80532e7: e8 b4 a7 00 00 call 805daa0 <expandargv>
80532ec: 8b 55 9c mov 0xffffff9c(%ebp),%edx
--
8053302: 89 0c 24 mov %ecx,(%esp)
8053305: e8 d6 2a 00 00 call 8055de0 <Prune_options>
805330a: e8 71 ac 00 00 call 805df80 <unlock_std_streams>
805330f: e8 4c 2f 00 00 call 8056260 <gcc_init_libintl>
8053314: c7 44 24 04 01 00 00 movl $0x1,0x4(%esp)
--
805331c: c7 04 24 02 00 00 00 movl $0x2,(%esp)
8053323: e8 78 5e ff ff call 80491a0 <signal@plt>
8053328: 83 e8 01 sub $0x1,%eax
Il faut un peu d'effort pour parcourir l'ensemble de l'assembleur, mais vous pouvez voir tous les appels possibles à partir d'une fonction donnée. Ce n'est pas aussi facile à utiliser que gprof
ou certains des autres utilitaires mentionnés, mais il présente plusieurs avantages distincts:
gprof
n'affichera que les appels de fonction exécutés.Voir traces, un framework de traçage pour les applications Linux C/C++: https://github.com/baruch/traces#readme
Il nécessite de recompiler votre code avec son instrumenteur, mais fournira une liste de toutes les fonctions, leurs paramètres et leurs valeurs de retour. Il y a un interactif pour permettre une navigation facile des échantillons de données volumineux.
Gprof pourrait être ce que vous voulez
KcacheGrind
https://kcachegrind.github.io/html/Home.html
Programme de test:
int f2(int i) { return i + 2; }
int f1(int i) { return f2(2) + i + 1; }
int f0(int i) { return f1(1) + f2(2); }
int pointed(int i) { return i; }
int not_called(int i) { return 0; }
int main(int argc, char **argv) {
int (*f)(int);
f0(1);
f1(1);
f = pointed;
if (argc == 1)
f(1);
if (argc == 2)
not_called(1);
return 0;
}
Usage:
Sudo apt-get install -y kcachegrind valgrind
# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c
# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main
# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234
Vous êtes maintenant laissé à l'intérieur d'un programme GUI génial qui contient beaucoup de données de performances intéressantes.
En bas à droite, sélectionnez l'onglet "Call graph". Cela montre un graphique d'appel interactif qui correspond aux mesures de performances dans d'autres fenêtres lorsque vous cliquez sur les fonctions.
Pour exporter le graphique, faites un clic droit dessus et sélectionnez "Exporter le graphique". Le PNG exporté ressemble à ceci:
De cela, nous pouvons voir que:
_start
, qui est le point d'entrée ELF réel, et contient un passe-partout d'initialisation glibcf0
, f1
et f2
sont appelés comme prévu les uns des autrespointed
est également affiché, même si nous l'avons appelé avec un pointeur de fonction. Il n'aurait peut-être pas été appelé si nous avions passé un argument de ligne de commande.not_called
n'est pas affiché car il n'a pas été appelé lors de l'exécution, car nous n'avons pas passé d'argument de ligne de commande supplémentaire.La chose intéressante à propos de valgrind
est qu'elle ne nécessite aucune option de compilation spéciale.
Par conséquent, vous pouvez l'utiliser même si vous n'avez pas le code source, uniquement l'exécutable.
valgrind
y parvient en exécutant votre code via une "machine virtuelle" légère.
Testé sur Ubuntu 18.04.
REMARQUE: ce n'est pas la ftrace basée sur le noyau Linux, mais plutôt un outil que j'ai récemment conçu pour effectuer le traçage des fonctions locales et le flux de contrôle. Linux ELF x86_64/x86_32 sont pris en charge publiquement.
Espérons que les outils de callgrind ou cachegrind pour Valgrind vous donneront les informations que vous recherchez.