web-dev-qa-db-fra.com

Outil pour tracer les appels de fonction locaux sous Linux

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:

  • C'est correct 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 le support d'outils spécifiques, les informations de symbole dans l'exécutable devraient être suffisantes.
  • Je serais vraiment sympa si je pouvais utiliser l'outil pour attacher à des processus existants comme je peux avec ltrace/strace.
58
Robert Gamble

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.

52

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

20
callgiraffe

Utilisation de probes (depuis Linux 3.5)

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

  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

list of functions in datalog binarycall tree when selecting dl_pushlstring, showing how main called loadfile called dl_load called program called rule which called literal which in turn called other functions that ended up calling dl_pushlstring, scan (parent: program, that is, the third scan from the top) which called dl_pushstring and so on

11
Janus Troelsen

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é.

9
philant
$ Sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out

Plus: ftrace.1

4
callgiraffe

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.

2
Kent Fredric

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.

2
osgx

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:

  • Vous n'avez généralement pas besoin de recompiler une application pour l'utiliser
  • Il affiche tous les appels de fonction possibles, alors que quelque chose comme gprof n'affichera que les appels de fonction exécutés.
2
Tom

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.

1
Greythorn

Gprof pourrait être ce que vous voulez

1
Sergey Golovchenko

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:

  • le nœud racine est _start, qui est le point d'entrée ELF réel, et contient un passe-partout d'initialisation 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 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.

https://github.com/leviathansecurity/ftrace

0
elfmaster

Espérons que les outils de callgrind ou cachegrind pour Valgrind vous donneront les informations que vous recherchez.

0
activout.se