]> The Tcpdump Group git mirrors - tcpdump/commitdiff
autoconf: Add an option to help debugging (--enable-instrument-functions)
authorFrancois-Xavier Le Bail <[email protected]>
Mon, 10 Jan 2022 15:37:07 +0000 (16:37 +0100)
committerFrancois-Xavier Le Bail <[email protected]>
Sat, 21 Oct 2023 18:47:42 +0000 (20:47 +0200)
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]

CONTRIBUTING.md
Makefile.in
config.h.in
configure
configure.ac
instrument-functions.c [new file with mode: 0644]
print.c

index 0814894457435e67d147ef58b3525ada0d6bc53c..215e4c6831c4064accf2e127322b40aec0cfc6d2 100644 (file)
@@ -119,12 +119,50 @@ and ask!
 ## Code style and generic remarks
 1) A thorough reading of some other printers code is useful.
 
-2) Put the normative reference if any as comments (RFC, etc.).
+2) To help learn how tcpdump works or to help debugging:
+   You can configure and build tcpdump with the instrumentation of functions:
+   ```
+   $ ./configure --enable-instrument-functions
+   $ make -s clean all
+   ```
+
+   This generates 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, it prints 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.
+
+   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.
+
+3) Put the normative reference if any as comments (RFC, etc.).
 
-3) Put the format of packets/headers/options as comments if there is no
+4) Put the format of packets/headers/options as comments if there is no
    published normative reference.
 
-4) The printer may receive incomplete packet in the buffer, truncated at any
+5) The printer may receive incomplete packet in the buffer, truncated at any
    random position, for example by capturing with `-s size` option.
    This means that an attempt to fetch packet data based on the expected
    format of the packet may run the risk of overrunning the buffer.
@@ -285,7 +323,7 @@ and ask!
    the string is a sequence of XX:XX:... values for the bytes
    of the address.
 
-5) When defining a structure corresponding to a packet or part of a
+6) When defining a structure corresponding to a packet or part of a
    packet, so that a pointer to packet data can be cast to a pointer to
    that structure and that structure pointer used to refer to fields in
    the packet, use the `nd_*` types for the structure members.
@@ -318,20 +356,20 @@ and ask!
    The `nd_*` type for a byte in a sequence of bytes is `nd_byte`; an
    *N*-byte sequence should be declared as `nd_byte[N]`.
 
-6) Do invalid packet checks in code: Think that your code can receive in input
+7) Do invalid packet checks in code: Think that your code can receive in input
    not only a valid packet but any arbitrary random sequence of octets (packet
    * built malformed originally by the sender or by a fuzz tester,
    * became corrupted in transit or for some other reason).
 
    Print with: `nd_print_invalid(ndo); /* to print " (invalid)" */`
 
-7) Use `struct tok` for indexed strings and print them with
+8) Use `struct tok` for indexed strings and print them with
    `tok2str()` or `bittok2str()` (for flags).
    All `struct tok` must end with `{ 0, NULL }`.
 
-8) Avoid empty lines in output of printers.
+9) Avoid empty lines in output of printers.
 
-9) A commit message must have:
+10) A commit message must have:
    ```
    First line: Capitalized short summary in the imperative (50 chars or less)
 
@@ -343,14 +381,14 @@ and ask!
    the body.
    ```
 
-10) Avoid non-ASCII characters in code and commit messages.
+11) Avoid non-ASCII characters in code and commit messages.
 
-11) Use the style of the modified sources.
+12) Use the style of the modified sources.
 
-12) Don't mix declarations and code.
+13) Don't mix declarations and code.
 
-13) tcpdump requires a compiler that supports C99 or later, so C99
+14) tcpdump requires a compiler that supports C99 or later, so C99
    features may be used in code, but C11 or later features should not be
    used.
 
-14) Avoid trailing tabs/spaces
+15) Avoid trailing tabs/spaces
index 3bd3206abc397b9b8b53638d04542f9943ac5050..736d0703c004799d330882bfa8292aa52274dec2 100644 (file)
@@ -329,7 +329,8 @@ TAGHDR = \
 TAGFILES = $(SRC) $(HDR) $(TAGHDR) $(LIBNETDISSECT_SRC) \
        print-smb.c smbutil.c
 
-CLEANFILES = $(PROG) $(OBJ) $(LIBNETDISSECT_OBJ) print-smb.o smbutil.o
+CLEANFILES = $(PROG) $(OBJ) $(LIBNETDISSECT_OBJ) \
+       print-smb.o smbutil.o instrument-functions.o
 
 EXTRA_DIST = \
        CHANGES \
@@ -360,6 +361,7 @@ EXTRA_DIST = \
        doc/README.solaris.md \
        doc/README.Win32.md \
        install-sh \
+       instrument-functions.c \
        lbl/os-osf4.h \
        lbl/os-solaris2.h \
        lbl/os-sunos4.h \
index edc7dd297bbb08e89e84d96541ffe3b76da1fe9a..af45c606b29c6d0b2b71f8fae237dba1038d8f8c 100644 (file)
@@ -3,6 +3,9 @@
 /* Define to 1 if arpa/inet.h declares `ether_ntohost' */
 #undef ARPA_INET_H_DECLARES_ETHER_NTOHOST
 
+/* define if you want to build the instrument functions code */
+#undef ENABLE_INSTRUMENT_FUNCTIONS
+
 /* define if you want to build the possibly-buggy SMB printer */
 #undef ENABLE_SMB
 
index 3071a3daa2d198ad063e350cf6fbe256c04e8c87..d35f29abbf551967c99fbd91175dfc76e1225406 100755 (executable)
--- a/configure
+++ b/configure
@@ -701,6 +701,7 @@ enable_option_checking
 with_gcc
 enable_universal
 with_smi
+enable_instrument_functions
 enable_smb
 with_user
 with_chroot
@@ -1332,6 +1333,8 @@ Optional Features:
   --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
   --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
   --disable-universal     don't build universal on macOS
+  --enable-instrument-functions
+                          enable instrument functions code [default=no]
   --enable-smb            enable possibly-buggy SMB printer [default=no]
   --disable-local-libpcap don't look for a local libpcap [default=check for a
                           local libpcap]
@@ -4345,6 +4348,76 @@ fi
 
 fi
 
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable the instrument functions code" >&5
+$as_echo_n "checking whether to enable the instrument functions code... " >&6; }
+# Check whether --enable-instrument-functions was given.
+if test "${enable_instrument_functions+set}" = set; then :
+  enableval=$enable_instrument_functions;
+else
+  enableval=no
+fi
+
+case "$enableval" in
+yes)   { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+       { $as_echo "$as_me:${as_lineno-$LINENO}: checking for bfd_init in -lbfd" >&5
+$as_echo_n "checking for bfd_init in -lbfd... " >&6; }
+if ${ac_cv_lib_bfd_bfd_init+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lbfd  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char bfd_init ();
+int
+main ()
+{
+return bfd_init ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_bfd_bfd_init=yes
+else
+  ac_cv_lib_bfd_bfd_init=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bfd_bfd_init" >&5
+$as_echo "$ac_cv_lib_bfd_bfd_init" >&6; }
+if test "x$ac_cv_lib_bfd_bfd_init" = xyes; then :
+  true
+else
+  as_fn_error $? "--enable-instrument-functions was given, but test for library libbfd failed. Please install the 'binutils-dev' package." "$LINENO" 5
+fi
+
+
+$as_echo "#define ENABLE_INSTRUMENT_FUNCTIONS 1" >>confdefs.h
+
+       LOCALSRC="$LOCALSRC instrument-functions.c"
+       # Add '-finstrument-functions' instrumentation option to generate
+       # instrumentation calls for entry and exit to functions.
+       # Try to avoid Address Space Layout Randomization (ALSR).
+       CFLAGS="$CFLAGS -O0 -ggdb -finstrument-functions -fno-stack-protector -fno-pic"
+       LDFLAGS="$LDFLAGS -O0 -ggdb -fno-stack-protector -no-pie"
+       LIBS="$LIBS -lbfd"
+       ;;
+*)     { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+       ;;
+esac
+
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to enable the possibly-buggy SMB printer" >&5
 $as_echo_n "checking whether to enable the possibly-buggy SMB printer... " >&6; }
 # Check whether --enable-smb was given.
index 1eed05a14277562946bf972eac1580e6ad571a3c..4d717b8a24b3986b3dbbb902f519c994c57a6e28 100644 (file)
@@ -137,6 +137,33 @@ int main()
        ])
 fi
 
+AC_MSG_CHECKING([whether to enable the instrument functions code])
+AC_ARG_ENABLE([instrument-functions],
+   [AS_HELP_STRING([--enable-instrument-functions],
+     [enable instrument functions code [default=no]])],
+   [],
+   [enableval=no])
+case "$enableval" in
+yes)   AC_MSG_RESULT(yes)
+       AC_CHECK_LIB([bfd], [bfd_init],
+           [true],
+           [AC_MSG_ERROR(
+              [--enable-instrument-functions was given, but test for library libbfd failed. Please install the 'binutils-dev' package.])],
+           [])
+       AC_DEFINE(ENABLE_INSTRUMENT_FUNCTIONS, 1,
+           [define if you want to build the instrument functions code])
+       LOCALSRC="$LOCALSRC instrument-functions.c"
+       # Add '-finstrument-functions' instrumentation option to generate
+       # instrumentation calls for entry and exit to functions.
+       # Try to avoid Address Space Layout Randomization (ALSR).
+       CFLAGS="$CFLAGS -O0 -ggdb -finstrument-functions -fno-stack-protector -fno-pic"
+       LDFLAGS="$LDFLAGS -O0 -ggdb -fno-stack-protector -no-pie"
+       LIBS="$LIBS -lbfd"
+       ;;
+*)     AC_MSG_RESULT(no)
+       ;;
+esac
+
 AC_MSG_CHECKING([whether to enable the possibly-buggy SMB printer])
 AC_ARG_ENABLE([smb],
    [AS_HELP_STRING([--enable-smb],
diff --git a/instrument-functions.c b/instrument-functions.c
new file mode 100644 (file)
index 0000000..ba0a56a
--- /dev/null
@@ -0,0 +1,250 @@
+/*
+ * 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 : */
diff --git a/print.c b/print.c
index 78f2bf3efb42bb0d7ab09a9c9a35d2ba4a5a55de..08578132f4a9121ccd8e2eaae12bac80d6c5cff5 100644 (file)
--- a/print.c
+++ b/print.c
@@ -321,6 +321,11 @@ get_if_printer(int type)
        return printer;
 }
 
+#ifdef ENABLE_INSTRUMENT_FUNCTIONS
+extern int profile_func_level;
+static int pretty_print_packet_level = -1;
+#endif
+
 void
 pretty_print_packet(netdissect_options *ndo, const struct pcap_pkthdr *h,
                    const u_char *sp, u_int packets_captured)
@@ -328,6 +333,11 @@ pretty_print_packet(netdissect_options *ndo, const struct pcap_pkthdr *h,
        u_int hdrlen = 0;
        int invalid_header = 0;
 
+#ifdef ENABLE_INSTRUMENT_FUNCTIONS
+       if (pretty_print_packet_level == -1)
+               pretty_print_packet_level = profile_func_level;
+#endif
+
        if (ndo->ndo_packet_number)
                ND_PRINT("%5u  ", packets_captured);
 
@@ -416,6 +426,10 @@ pretty_print_packet(netdissect_options *ndo, const struct pcap_pkthdr *h,
                nd_print_trunc(ndo);
                /* Print the full packet */
                ndo->ndo_ll_hdr_len = 0;
+#ifdef ENABLE_INSTRUMENT_FUNCTIONS
+               /* truncation => reassignment */
+               profile_func_level = pretty_print_packet_level;
+#endif
                break;
        }
        hdrlen = ndo->ndo_ll_hdr_len;