tcpdump/instrument-functions.c
Francois-Xavier Le Bail 9ca86a0927 instrument functions: Use an environment variable instead of config files
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

The configuration with --enable-instrument-functions remains.

Note that before this change, the default was to print all functions.
Now it is to print nothing. So by default 'make check' runs without errors.

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

This also allows to run the statically compiled binary on another host
after copying it.

It is no longer necessary to modify the configuration with:
$ make instrument_all
$ make instrument_global
$ make instrument_off
(Targets removed.)

Update .gitignore, CONTRIBUTING.md and Makefile.in accordingly.

Moreover:
Reduce the scope of a variable.
Rename a variable.
Remove '\n' in the perror() call.
Remove 2 spaces in function calls (style).

[skip ci]
2023-03-13 10:24:43 +01:00

248 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 char *instrument_type;
static int instrument_set;
static int instrument_off;
static int instrument_global;
int i;
if (!instrument_set) {
/* 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(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;
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 {
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 : */