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.
Était-ce utile?

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

Le

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

  1. cd /usr/src/linux-`uname -r`/tools/perf
  2. for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
  3. 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
  4. sudo ./perf report -G

liste des fonctions dans le binaire d'enregistrement de données arborescence des appels lors de la sélection de dl_pushlstring, montrant comment main appelle loadfile appelé dl_load appelé programme appelé règle qui appelle littéral qui à son tour appelle d'autres fonctions qui a fini par appeler dl_pushlstring, scan (parent: programme, c’est-à-dire le troisième balayage à partir du haut) qui a appelé dl_pushstring, etc.

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

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. À 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 et f2 sont appelés comme prévu les uns des autres
  • pointed 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.

https://github.com/leviathansecurity/ftrace

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top