Outil pour tracer les appels de fonctions locales sous Linux
Question
Je recherche un outil comme trace ou strace qui peut tracer des fonctions définies localement dans un exécutable.ltrace trace uniquement les appels de bibliothèque dynamiques et strace trace uniquement 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;
}
Exécuter le programme avec ltrace
affichera l'appel à printf
puisqu'il s'agit d'une fonction de bibliothèque standard (qui est une bibliothèque dynamique sur mon système) et strace
affichera tous les appels système 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é.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 capable de le faire ?
Modifier
Quelques précisions :
- Ce n'est pas grave si l'outil fournit également des informations de trace pour les fonctions non locales.
- Je ne veux pas avoir à recompiler le(s) programme(s) avec la prise en charge d'outils spécifiques, les informations sur les symboles dans l'exécutable devraient suffire.
- Je serais vraiment bien si je pouvais utiliser l'outil pour m'attacher à des processus existants comme je le peux avec ltrace/strace.
La solution
En supposant que vous souhaitiez uniquement être averti pour des fonctions spécifiques, vous pouvez procéder comme ceci :
compiler avec les 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 les adresses de toutes les 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 de simplement imprimer l'image actuelle (bt 1
), vous pouvez faire tout ce que vous voulez, imprimer la valeur d'un global, exécuter une commande shell ou envoyer quelque chose par courrier s'il atteint le fatal_bomb_exploded
function :) Malheureusement, gcc génère des messages "Langue actuelle modifiée" entre les deux.Mais cela se comprend facilement.Ce n'est pas grave.
Autres conseils
système Tap peut être utilisé sur une machine Linux moderne (Fedora 10, RHEL 5, etc.).
Téléchargez d'abord le script para-callgraph.stp .
Puis exécutez:
$ 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: Observez, systématisez et mises à jour oprofile
Utilisation de Upprobes (depuis Linux 3.5)
En supposant que vous souhaitiez suivre toutes les fonctions de ~/Desktop/datalog-2.2/datalog
lors de l'appel 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 puissiez recompiler (aucune modification de source requise) le code que vous souhaitez suivre avec l'option gcc -finstrument-functions
, vous pouvez utiliser etrace pour obtenir le graphe d’appel de fonction.
Voici à quoi ressemble le résultat:
\-- 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) permet de filtrer la bibliothèque à suivre. J'ai été 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).
Cela fonctionne parce que ltrace se place entre votre application et la bibliothèque et que, lorsque tout le code est internalisé dans un fichier, il ne peut pas intercepter l'appel.
c'est-à-dire: ltrace xterm
crache des choses à partir de bibliothèques X, et X n’est guère un système.
En dehors de cela, le seul véritable moyen de le faire est une interception au moment de la compilation via des indicateurs prof ou des symboles de débogage.
Je viens de passer en revue cette application qui a l'air intéressante:
http://www.gnu.org/software/cflow/
Mais je ne pense pas que c'est ce que vous voulez.
Si les fonctions ne sont pas en ligne, 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 tout l'assembleur, mais vous pouvez voir tous les appels possibles 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:
- Vous n'avez généralement pas besoin de recompiler une application pour l'utiliser
- Il montre tous les appels de fonction possibles, alors que quelque chose comme <=> montrera uniquement les appels de fonction exécutés.
Il existe un script shell pour automatiser les appels de fonction de traçage avec gdb. Mais cela ne peut pas être associé 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
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. À chaque point d'arrêt, & Quot; backtrace 2 & Quot; et " continuez " sont exécutés.
Ce script est plutôt lent sur les gros projets (~ milliers de fonctions), alors 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.
Gprof pourrait être ce que vous voulez
Voir les traces, un framework de traçage pour les applications Linux C / C ++: https://github.com/baruch/traces#readme
Il faut recompiler votre code avec son instrumenteur, mais fournira une liste de toutes les fonctions, de leurs paramètres et de leurs valeurs de retour. Un outil interactif facilite la navigation dans de grands échantillons de données.
KcacheGrind
https://kcachegrind.github.io/html/Home.html
Programme d'essais :
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 vous retrouvez maintenant dans un superbe programme GUI qui contient de nombreuses données de performances intéressantes.
En bas à droite, sélectionnez l'onglet "Graphique des appels".Cela affiche 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 là on peut voir que :
- le nœud racine est
_start
, qui est le véritable point d'entrée ELF, et contient le modèle d'initialisation de la glibc f0
,f1
etf2
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 transmis d'argument de ligne de commande supplémentaire.
Ce qui est cool avec valgrind
est qu'il 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, seulement l'exécutable.
valgrind
parvient à le faire en exécutant votre code via une "machine virtuelle" légère.
Testé sur Ubuntu 18.04.
Espérons que les outils callgrind ou cachegrind pour Valgrind vous donnera les informations que vous recherchez.
REMARQUE: il ne s'agit pas de la fonction ftrace basée sur le noyau Linux, mais plutôt d'un outil que j'ai récemment conçu pour effectuer le suivi des fonctions locales et le flux de contrôle. Linux ELF x86_64 / x86_32 sont pris en charge publiquement.