tcpdump/instrument-functions.c
Francois-Xavier Le Bail 37ae1bef66 autoconf: Add an option to help debugging (--enable-instrument-functions)
Generate instrumentation calls for entry and exit to functions.
Just after function entry and just before function exit, these
profiling functions are called and print the function names with
indentation and call level.

If entering in a function, print also the calling function name with
file name and line number. There may be a small shift in the line number.

In some cases, with Clang 11, the file number is unknown (printed '??')
or the line number is unknown (printed '?'). In this case, use GCC.

Usage:
$ ./configure --enable-instrument-functions
$ make -s clean all

If the environment variable INSTRUMENT is
- unset or set to an empty string, print nothing, like with no
  instrumentation
- set to "all" or "a", print all the functions names
- set to "global" or "g", print only the global functions names

This allows to run:
$ INSTRUMENT=a ./tcpdump ...
$ INSTRUMENT=g ./tcpdump ...
$ INSTRUMENT= ./tcpdump ...
or
$ export INSTRUMENT=global
$ ./tcpdump ...

The library libbfd is used, therefore the binutils-dev package is required.

(backported from main)

[skip ci]
2023-10-21 20:47:42 +02:00

251 lines
6.5 KiB
C

/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code
* distributions retain the above copyright notice and this paragraph
* in its entirety, and (2) distributions including binary code include
* the above copyright notice and this paragraph in its entirety in
* the documentation or other materials provided with the distribution.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND
* WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
* LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <bfd.h>
/*
* Generate instrumentation calls for entry and exit to functions.
* Just after function entry and just before function exit, the
* following profiling functions are called with the address of the
* current function and its call site (currently not use).
*
* The attribute 'no_instrument_function' causes this instrumentation is
* not done.
*
* These profiling functions call print_debug(). This function prints the
* current function name with indentation and call level.
* If entering in a function it prints also the calling function name with
* file name and line number.
*
* If the environment variable INSTRUMENT is
* unset or set to an empty string, print nothing, like with no instrumentation
* set to "all" or "a", print all the functions names
* set to "global" or "g", print only the global functions names
*/
#define ND_NO_INSTRUMENT __attribute__((no_instrument_function))
/* Store the function call level, used also in pretty_print_packet() */
extern int profile_func_level;
int profile_func_level = -1;
typedef enum {
ENTER,
EXIT
} action_type;
void __cyg_profile_func_enter(void *this_fn, void *call_site) ND_NO_INSTRUMENT;
void __cyg_profile_func_exit(void *this_fn, void *call_site) ND_NO_INSTRUMENT;
static void print_debug(void *this_fn, void *call_site, action_type action)
ND_NO_INSTRUMENT;
void
__cyg_profile_func_enter(void *this_fn, void *call_site)
{
print_debug(this_fn, call_site, ENTER);
}
void
__cyg_profile_func_exit(void *this_fn, void *call_site)
{
print_debug(this_fn, call_site, EXIT);
}
static void print_debug(void *this_fn, void *call_site, action_type action)
{
static bfd* abfd;
static asymbol **symtab;
static long symcount;
static asection *text;
static bfd_vma vma;
static int instrument_set;
static int instrument_off;
static int instrument_global;
if (!instrument_set) {
static char *instrument_type;
/* Get the configuration environment variable INSTRUMENT value if any */
instrument_type = getenv("INSTRUMENT");
/* unset or set to an empty string ? */
if (instrument_type == NULL ||
!strncmp(instrument_type, "", sizeof(""))) {
instrument_off = 1;
} else {
/* set to "global" or "g" ? */
if (!strncmp(instrument_type, "global", sizeof("global")) ||
!strncmp(instrument_type, "g", sizeof("g")))
instrument_global = 1;
else if (strncmp(instrument_type, "all", sizeof("all")) &&
strncmp(instrument_type, "a", sizeof("a"))) {
fprintf(stderr, "INSTRUMENT can be only \"\", \"all\", \"a\", "
"\"global\" or \"g\".\n");
exit(1);
}
}
instrument_set = 1;
}
if (instrument_off)
return;
/* If no errors, this block should be executed one time */
if (!abfd) {
char pgm_name[1024];
long symsize;
ssize_t ret = readlink("/proc/self/exe", pgm_name, sizeof(pgm_name));
if (ret == -1) {
perror("failed to find executable");
return;
}
if (ret == sizeof(pgm_name)) {
/* no space for the '\0' */
printf("truncation may have occurred\n");
return;
}
pgm_name[ret] = '\0';
bfd_init();
abfd = bfd_openr(pgm_name, NULL);
if (!abfd) {
bfd_perror("bfd_openr");
return;
}
if (!bfd_check_format(abfd, bfd_object)) {
bfd_perror("bfd_check_format");
return;
}
if((symsize = bfd_get_symtab_upper_bound(abfd)) == -1) {
bfd_perror("bfd_get_symtab_upper_bound");
return;
}
symtab = (asymbol **)malloc((size_t)symsize);
symcount = bfd_canonicalize_symtab(abfd, symtab);
if (symcount < 0) {
free(symtab);
bfd_perror("bfd_canonicalize_symtab");
return;
}
if ((text = bfd_get_section_by_name(abfd, ".text")) == NULL) {
bfd_perror("bfd_get_section_by_name");
return;
}
vma = text->vma;
}
if (instrument_global) {
symbol_info syminfo;
int found;
long i;
i = 0;
found = 0;
while (i < symcount && !found) {
bfd_get_symbol_info(abfd, symtab[i], &syminfo);
if ((void *)syminfo.value == this_fn) {
found = 1;
}
i++;
}
/* type == 'T' for a global function */
if (found == 1 && syminfo.type != 'T')
return;
}
/* Current function */
if ((bfd_vma)this_fn < vma) {
printf("[ERROR address this_fn]");
} else {
const char *file;
const char *func;
unsigned int line;
if (!bfd_find_nearest_line(abfd, text, symtab, (bfd_vma)this_fn - vma,
&file, &func, &line)) {
printf("[ERROR bfd_find_nearest_line this_fn]");
} else {
int i;
if (action == ENTER)
profile_func_level += 1;
/* Indentation */
for (i = 0 ; i < profile_func_level ; i++)
putchar(' ');
if (action == ENTER)
printf("[>> ");
else
printf("[<< ");
/* Function name */
if (func == NULL || *func == '\0')
printf("???");
else
printf("%s", func);
printf(" (%d)", profile_func_level);
/* Print the "from" part except for the main function) */
if (action == ENTER && func != NULL &&
strncmp(func, "main", sizeof("main"))) {
/* Calling function */
if ((bfd_vma)call_site < vma) {
printf("[ERROR address call_site]");
} else {
if (!bfd_find_nearest_line(abfd, text, symtab,
(bfd_vma)call_site - vma, &file,
&func, &line)) {
printf("[ERROR bfd_find_nearest_line call_site]");
} else {
printf(" from ");
/* Function name */
if (func == NULL || *func == '\0')
printf("???");
else
printf("%s", func);
/* File name */
if (file == NULL || *file == '\0')
printf(" ??:");
else {
char *slashp = strrchr(file, '/');
if (slashp != NULL)
file = slashp + 1;
printf(" %s:", file);
}
/* Line number */
if (line == 0)
printf("?");
else
printf("%u", line);
printf("]");
}
}
}
putchar('\n');
if (action == EXIT)
profile_func_level -= 1;
}
}
fflush(stdout);
}
/* vi: set tabstop=4 softtabstop=0 shiftwidth=4 smarttab autoindent : */