From: Serhei Makarov Date: Wed, 21 Aug 2024 14:16:34 +0000 (-0400) Subject: eu-stacktrace WIP: rearrange the code into sections X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ba8f21197fe84ef76d9a245219df0e43c19cc7cf;p=thirdparty%2Felfutils.git eu-stacktrace WIP: rearrange the code into sections Put things in a more logical order as previous incremental changes led to slightly arbitrary sequence of declarations. --- diff --git a/src/stacktrace.c b/src/stacktrace.c index 3ca9eaf5..6fb9f78b 100644 --- a/src/stacktrace.c +++ b/src/stacktrace.c @@ -77,6 +77,13 @@ #include #include #include + +#include + +/************************************* + * Includes: libdwfl data structures * + *************************************/ + /* #include ELFUTILS_HEADER(dwfl) */ #include "../libdwfl/libdwflP.h" /* XXX: Private header needed for sysprof_find_procfile, sysprof_init_dwfl, XXX LIBDWFL_TRACKS_UNWOUND_SOURCE. */ @@ -100,7 +107,9 @@ unwound_source_str (Dwfl_Unwound_Source unwound_source) } #endif -#include +/************************************* + * Includes: sysprof data structures * + *************************************/ #if HAVE_SYSPROF_6_HEADERS #include @@ -142,11 +151,16 @@ typedef struct } SysprofCaptureUserRegs SYSPROF_ALIGNED_END(1); -static int maxframes = 256; - #endif /* SYSPROF_CAPTURE_FRAME_STACK_USER */ + #endif /* HAVE_SYSPROF_HEADERS */ +/************************** + * Global data structures * + **************************/ + +static int maxframes = 256; + static char *input_path = NULL; static int input_fd = -1; static char *output_path = NULL; @@ -158,14 +172,14 @@ static int signal_count = 0; #define MODE_NONE 0x0 #define MODE_PASSTHRU 0x1 #define MODE_NAIVE 0x2 -#define MODE_CACHING 0x3 +/* TODO: #define MODE_CACHING 0x3 */ static int processing_mode = MODE_NAIVE; #define FORMAT_OPTS "sysprof" #define FORMAT_PERF 0x1 #define FORMAT_SYSPROF 0x2 static int input_format; -static int output_format = FORMAT_SYSPROF; /* TODO: add to cmdline args? */ +static int output_format = FORMAT_SYSPROF; /* TODO: add to cmdline args */ /* non-printable argp options. */ #define OPT_DEBUG 0x100 @@ -194,16 +208,20 @@ static bool show_summary = true; /* TODO: disable by default in release version #define EXIT_BAD 2 #define EXIT_USAGE 64 -/* Sysprof format support. - TODO: Could split into a separate file or even a library. */ - -#if HAVE_SYSPROF_HEADERS +/************************** + * Sysprof format support * + **************************/ /* XXX based on sysprof src/libsysprof-capture/sysprof-capture-reader.c Note: BSD license attribution at the top of the file applies to this - segment. If moving the code to a separate library, feel free to - move the notice together with it. */ + segment. Could split into a separate file or even a library, + in which case the attribution notice will move along with it. */ + +/* TODO: elfutils (internal) libraries use libNN_set_errno and _E_WHATEVER; + this code sets errno variable directly and uses standard EWHATEVER. */ + +#if HAVE_SYSPROF_HEADERS /* A complete passthrough can be implemented based on the following 7 functions: - sysprof_reader_begin/sysprof_reader_end :: sysprof_capture_reader_new_from_fd @@ -254,7 +272,6 @@ sysprof_reader_begin (int fd) assert (fd > -1); - /* TODO elfutils style: libraries use __lib??_seterrno and ??_E_ENOMEM. */ reader = malloc (sizeof (SysprofReader)); if (reader == NULL) { @@ -450,147 +467,9 @@ sysprof_reader_getframes (SysprofReader *reader, #endif /* HAVE_SYSPROF_HEADERS */ -/* Required to match our signal handling with that of a sysprof parent process. */ -static void sigint_handler (int /* signo */) -{ - if (signal_count >= 2) - { - exit(1); - } - - if (signal_count == 0) - { - fprintf (stderr, "%s\n", "Waiting for input to finish. Press twice more ^C to force exit."); - } - - signal_count ++; -} - -/* Main program. */ - -static error_t -parse_opt (int key, char *arg __attribute__ ((unused)), - struct argp_state *state) -{ - switch (key) - { - case 'i': - input_path = arg; - break; - - case 'o': - output_path = arg; - break; - - case 'm': - if (strcmp (arg, "none") == 0) - { - processing_mode = MODE_NONE; - } - else if (strcmp (arg, "passthru") == 0) - { - processing_mode = MODE_PASSTHRU; - } - else if (strcmp (arg, "naive") == 0) - { - processing_mode = MODE_NAIVE; - } - else - { - argp_error (state, N_("Unsupported -m '%s', should be " MODE_OPTS "."), arg); - } - break; - - case 'f': - if (strcmp (arg, "sysprof") == 0) - { - input_format = FORMAT_SYSPROF; - } - else - { - argp_error (state, N_("Unsupported -f '%s', should be " FORMAT_OPTS "."), arg); - } - break; - - case OPT_DEBUG: - show_memory_reads = false; - show_frames = true; - FALLTHROUGH; - case 'v': - show_samples = true; - show_failures = true; - show_summary = true; - break; - - case ARGP_KEY_END: - if (input_path == NULL) - input_path = "-"; /* default to stdin */ - - if (output_path == NULL) - output_path = "-"; /* default to stdout */ - - if (processing_mode == 0) - processing_mode = MODE_PASSTHRU; - /* TODO: Change the default to MODE_NAIVE once unwinding works reliably. */ - - if (input_format == 0) - input_format = FORMAT_SYSPROF; - break; - - default: - return ARGP_ERR_UNKNOWN; - } - return 0; -} - -#if HAVE_SYSPROF_HEADERS - -int -sysprof_none_cb (SysprofCaptureFrame *frame __attribute__ ((unused)), - void *arg __attribute__ ((unused))) -{ - return SYSPROF_CB_OK; -} - -struct sysprof_passthru_info -{ - int output_fd; - SysprofReader *reader; - int pos; /* for diagnostic purposes */ -}; - -int -sysprof_passthru_cb (SysprofCaptureFrame *frame, void *arg) -{ - struct sysprof_passthru_info *spi = (struct sysprof_passthru_info *)arg; - sysprof_reader_bswap_frame (spi->reader, frame); /* reverse the earlier bswap */ - ssize_t n_write = write (spi->output_fd, frame, frame->len); - spi->pos += frame->len; - assert ((spi->pos % SYSPROF_CAPTURE_ALIGN) == 0); - if (n_write < 0) - error (EXIT_BAD, errno, N_("Write error to file or FIFO '%s'"), output_path); - return SYSPROF_CB_OK; -} - -#define UNWIND_ADDR_INCREMENT 512 -struct sysprof_unwind_info -{ - int output_fd; - SysprofReader *reader; - int pos; /* for diagnostic purposes */ - int n_addrs; - int max_addrs; /* for diagnostic purposes */ - Dwarf_Addr last_base; /* for diagnostic purposes */ - Dwarf_Addr last_sp; /* for diagnostic purposes */ -#ifdef DEBUG_MODULES - Dwfl *last_dwfl; /* for diagnostic purposes */ -#endif -#ifdef LIBDWFL_TRACKS_UNWOUND_SOURCE - int last_pid; /* for diagnostic purposes, to provide access to dwfltab */ -#endif - Dwarf_Addr *addrs; /* allocate blocks of UNWIND_ADDR_INCREMENT */ - void *outbuf; -}; +/******************************************* + * Memory read interface for stack samples * + *******************************************/ struct __sample_arg { @@ -603,10 +482,10 @@ struct __sample_arg Dwarf_Addr *regs; }; -/* The next few functions Imitate the corefile interface for a single +/* The next few functions imitate the corefile interface for a single stack sample, with very restricted access to registers and memory. */ -/* Just yields the single thread id matching the sample. */ +/* Just yield the single thread id matching the sample. */ static pid_t sample_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, void **thread_argp) @@ -621,7 +500,7 @@ sample_next_thread (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg, return 0; } -/* Just checks that the thread id matches the sample. */ +/* Just check that the thread id matches the sample. */ static bool sample_getthread (Dwfl *dwfl __attribute__ ((unused)), pid_t tid, void *dwfl_arg, void **thread_argp) @@ -645,7 +524,7 @@ sample_memory_read (Dwfl *dwfl __attribute__ ((unused)), Dwarf_Addr addr, Dwarf_ sample_arg->base_addr, addr - sample_arg->base_addr, sample_arg->size); /* Imitate read_cached_memory() with the stack sample data as the cache. */ if (addr < sample_arg->base_addr || addr - sample_arg->base_addr >= sample_arg->size) - return false; + return false; /* TODO: also read from Elf files we happen to be aware of */ uint8_t *d = &sample_arg->data[addr - sample_arg->base_addr]; if ((((uintptr_t) d) & (sizeof (unsigned long) - 1)) == 0) *result = *(unsigned long *)d; @@ -679,14 +558,210 @@ sample_detach (Dwfl *dwfl __attribute__ ((unused)), void *dwfl_arg) free (sample_arg); } -static const Dwfl_Thread_Callbacks sample_thread_callbacks = +static const Dwfl_Thread_Callbacks sample_thread_callbacks = +{ + sample_next_thread, + sample_getthread, + sample_memory_read, + sample_set_initial_registers, + sample_detach, + NULL, /* sample_thread_detach */ +}; + +/**************************************************** + * Dwfl and statistics table for multiple processes * + ****************************************************/ + +/* TODO: This echoes lib/dynamicsizehash.* with some modifications. */ +typedef struct +{ + bool used; + pid_t pid; + Dwfl *dwfl; + char *comm; + int max_frames; /* for diagnostic purposes */ + int total_samples; /* for diagnostic purposes */ + int lost_samples; /* for diagnostic purposes */ + int shown_error; /* already shown an error for this pid? TODO */ +#ifdef LIBDWFL_TRACKS_UNWOUND_SOURCE + Dwfl_Unwound_Source last_unwound; /* track CFI source with enum above, for diagnostic purposes */ + Dwfl_Unwound_Source worst_unwound; /* track CFI source with enum above, for diagnostic purposes */ +#endif +} dwfltab_ent; + +typedef struct +{ + ssize_t size; + ssize_t filled; + dwfltab_ent *table; +} dwfltab; + +/* TODO: Store in sui, update below functions. */ +/* XXX initial size must be a prime */ +#define DWFLTAB_DEFAULT_SIZE 1021 +dwfltab default_table; + +/* XXX based on lib/dynamicsizehash.* *_init */ +void dwfltab_init(void) +{ + dwfltab *htab = &default_table; + htab->size = DWFLTAB_DEFAULT_SIZE; + htab->filled = 0; + htab->table = calloc ((htab->size + 1), sizeof(htab->table[0])); +} + +/* XXX based on lib/dynamicsizehash.* *_find */ +dwfltab_ent *dwfltab_find(pid_t pid) +{ + dwfltab *htab = &default_table; + ssize_t idx = 1 + (htab->size > (ssize_t)pid ? (ssize_t)pid : (ssize_t)pid % htab->size); + + if (!htab->table[idx].used) + goto found; + if (htab->table[idx].pid == pid) + goto found; + + int64_t hash = 1 + pid % (htab->size - 2); + do + { + if (idx <= hash) + idx = htab->size + idx - hash; + else + idx -= hash; + + if (!htab->table[idx].used) + goto found; + if (htab->table[idx].pid == pid) + goto found; + } + while (true); + + found: + if (htab->table[idx].pid == 0) + { + /* TODO: Implement resizing or LRU eviction? */ + if (100 * htab->filled > 90 * htab->size) + return NULL; + htab->table[idx].used = true; + htab->table[idx].pid = pid; + htab->filled += 1; + } + return &htab->table[idx]; +} + +Dwfl * +pid_find_dwfl (pid_t pid) +{ + dwfltab_ent *entry = dwfltab_find(pid); + if (entry == NULL) + return NULL; + return entry->dwfl; +} + +char * +pid_find_comm (pid_t pid) +{ + dwfltab_ent *entry = dwfltab_find(pid); + if (entry == NULL) + return ""; + if (entry->comm != NULL) + return entry->comm; + char name[64]; + int i = snprintf (name, sizeof(name), "/proc/%ld/comm", (long) pid); + FILE *procfile = fopen(name, "r"); + if (procfile == NULL) + goto fail; + size_t linelen = 0; + i = getline(&entry->comm, &linelen, procfile); + if (i < 0) + { + free(entry->comm); + goto fail; + } + for (i = linelen - 1; i > 0; i--) + if (entry->comm[i] == '\n') + entry->comm[i] = '\0'; + fclose(procfile); + goto done; + fail: + entry->comm = (char *)malloc(16); + snprintf (entry->comm, 16, ""); + done: + return entry->comm; +} + +/* Cache dwfl structs in a basic hash table. */ +void +pid_store_dwfl (pid_t pid, Dwfl *dwfl) +{ + dwfltab_ent *entry = dwfltab_find(pid); + if (entry == NULL) + return; + entry->pid = pid; + entry->dwfl = dwfl; + pid_find_comm(pid); + return; +} + +/***************************************************** + * Sysprof backend: basic none/passthrough callbacks * + *****************************************************/ + +#if HAVE_SYSPROF_HEADERS + +int +sysprof_none_cb (SysprofCaptureFrame *frame __attribute__ ((unused)), + void *arg __attribute__ ((unused))) +{ + return SYSPROF_CB_OK; +} + +struct sysprof_passthru_info +{ + int output_fd; + SysprofReader *reader; + int pos; /* for diagnostic purposes */ +}; + +int +sysprof_passthru_cb (SysprofCaptureFrame *frame, void *arg) +{ + struct sysprof_passthru_info *spi = (struct sysprof_passthru_info *)arg; + sysprof_reader_bswap_frame (spi->reader, frame); /* reverse the earlier bswap */ + ssize_t n_write = write (spi->output_fd, frame, frame->len); + spi->pos += frame->len; + assert ((spi->pos % SYSPROF_CAPTURE_ALIGN) == 0); + if (n_write < 0) + error (EXIT_BAD, errno, N_("Write error to file or FIFO '%s'"), output_path); + return SYSPROF_CB_OK; +} + +#endif /* HAVE_SYSPROF_HEADERS */ + +/**************************************** + * Sysprof backend: unwinding callbacks * + ****************************************/ + +#if HAVE_SYSPROF_HEADERS + +#define UNWIND_ADDR_INCREMENT 512 +struct sysprof_unwind_info { - sample_next_thread, - sample_getthread, - sample_memory_read, - sample_set_initial_registers, - sample_detach, - NULL, /* sample_thread_detach */ + int output_fd; + SysprofReader *reader; + int pos; /* for diagnostic purposes */ + int n_addrs; + int max_addrs; /* for diagnostic purposes */ + Dwarf_Addr last_base; /* for diagnostic purposes */ + Dwarf_Addr last_sp; /* for diagnostic purposes */ +#ifdef DEBUG_MODULES + Dwfl *last_dwfl; /* for diagnostic purposes */ +#endif +#ifdef LIBDWFL_TRACKS_UNWOUND_SOURCE + int last_pid; /* for diagnostic purposes, to provide access to dwfltab */ +#endif + Dwarf_Addr *addrs; /* allocate blocks of UNWIND_ADDR_INCREMENT */ + void *outbuf; }; #ifdef FIND_DEBUGINFO @@ -815,137 +890,6 @@ sysprof_find_procfile (Dwfl *dwfl, pid_t *pid, Elf **elf, int *elf_fd) return 0; } -/* TODO: This echoes lib/dynamicsizehash.* with some modifications. */ -typedef struct -{ - bool used; - pid_t pid; - Dwfl *dwfl; - char *comm; - int max_frames; /* for diagnostic purposes */ - int total_samples; /* for diagnostic purposes */ - int lost_samples; /* for diagnostic purposes */ - int shown_error; /* already shown an error for this pid? TODO */ -#ifdef LIBDWFL_TRACKS_UNWOUND_SOURCE - Dwfl_Unwound_Source last_unwound; /* track CFI source with enum above, for diagnostic purposes */ - Dwfl_Unwound_Source worst_unwound; /* track CFI source with enum above, for diagnostic purposes */ -#endif -} dwfltab_ent; - -typedef struct -{ - ssize_t size; - ssize_t filled; - dwfltab_ent *table; -} dwfltab; - -/* TODO: Store in sui, update below functions. */ -/* XXX initial size must be a prime */ -#define DWFLTAB_DEFAULT_SIZE 1021 -dwfltab default_table; - -/* XXX based on lib/dynamicsizehash.* *_init */ -void dwfltab_init(void) -{ - dwfltab *htab = &default_table; - htab->size = DWFLTAB_DEFAULT_SIZE; - htab->filled = 0; - htab->table = calloc ((htab->size + 1), sizeof(htab->table[0])); -} - -/* XXX based on lib/dynamicsizehash.* *_find */ -dwfltab_ent *dwfltab_find(pid_t pid) -{ - dwfltab *htab = &default_table; - ssize_t idx = 1 + (htab->size > (ssize_t)pid ? (ssize_t)pid : (ssize_t)pid % htab->size); - - if (!htab->table[idx].used) - goto found; - if (htab->table[idx].pid == pid) - goto found; - - int64_t hash = 1 + pid % (htab->size - 2); - do - { - if (idx <= hash) - idx = htab->size + idx - hash; - else - idx -= hash; - - if (!htab->table[idx].used) - goto found; - if (htab->table[idx].pid == pid) - goto found; - } - while (true); - - found: - if (htab->table[idx].pid == 0) - { - /* TODO: Implement resizing or LRU eviction? */ - if (100 * htab->filled > 90 * htab->size) - return NULL; - htab->table[idx].used = true; - htab->table[idx].pid = pid; - htab->filled += 1; - } - return &htab->table[idx]; -} - -Dwfl * -pid_find_dwfl (pid_t pid) -{ - dwfltab_ent *entry = dwfltab_find(pid); - if (entry == NULL) - return NULL; - return entry->dwfl; -} - -char * -pid_find_comm (pid_t pid) -{ - dwfltab_ent *entry = dwfltab_find(pid); - if (entry == NULL) - return ""; - if (entry->comm != NULL) - return entry->comm; - char name[64]; - int i = snprintf (name, sizeof(name), "/proc/%ld/comm", (long) pid); - FILE *procfile = fopen(name, "r"); - if (procfile == NULL) - goto fail; - size_t linelen = 0; - i = getline(&entry->comm, &linelen, procfile); - if (i < 0) - { - free(entry->comm); - goto fail; - } - for (i = linelen - 1; i > 0; i--) - if (entry->comm[i] == '\n') - entry->comm[i] = '\0'; - fclose(procfile); - goto done; - fail: - entry->comm = (char *)malloc(16); - snprintf (entry->comm, 16, ""); - done: - return entry->comm; -} - -/* Cache dwfl structs in a basic hash table. */ -void -pid_store_dwfl (pid_t pid, Dwfl *dwfl) -{ - dwfltab_ent *entry = dwfltab_find(pid); - if (entry == NULL) - return; - entry->pid = pid; - entry->dwfl = dwfl; - pid_find_comm(pid); - return; -} - Dwfl * sysprof_init_dwfl (struct sysprof_unwind_info *sui, SysprofCaptureStackUser *ev, @@ -1236,8 +1180,103 @@ sysprof_unwind_cb (SysprofCaptureFrame *frame, void *arg) error (EXIT_BAD, errno, N_("Write error to file or FIFO '%s'"), output_path); return SYSPROF_CB_OK; } + #endif /* HAVE_SYSPROF_HEADERS */ +/**************** + * Main program * + ****************/ + +/* Required to match our signal handling with that of a sysprof parent process. */ +static void sigint_handler (int /* signo */) +{ + if (signal_count >= 2) + { + exit(1); + } + + if (signal_count == 0) + { + fprintf (stderr, "%s\n", "Waiting for input to finish. Press twice more ^C to force exit."); + } + + signal_count ++; +} + +static error_t +parse_opt (int key, char *arg __attribute__ ((unused)), + struct argp_state *state) +{ + switch (key) + { + case 'i': + input_path = arg; + break; + + case 'o': + output_path = arg; + break; + + case 'm': + if (strcmp (arg, "none") == 0) + { + processing_mode = MODE_NONE; + } + else if (strcmp (arg, "passthru") == 0) + { + processing_mode = MODE_PASSTHRU; + } + else if (strcmp (arg, "naive") == 0) + { + processing_mode = MODE_NAIVE; + } + else + { + argp_error (state, N_("Unsupported -m '%s', should be " MODE_OPTS "."), arg); + } + break; + + case 'f': + if (strcmp (arg, "sysprof") == 0) + { + input_format = FORMAT_SYSPROF; + } + else + { + argp_error (state, N_("Unsupported -f '%s', should be " FORMAT_OPTS "."), arg); + } + break; + + case OPT_DEBUG: + show_memory_reads = false; + show_frames = true; + FALLTHROUGH; + case 'v': + show_samples = true; + show_failures = true; + show_summary = true; + break; + + case ARGP_KEY_END: + if (input_path == NULL) + input_path = "-"; /* default to stdin */ + + if (output_path == NULL) + output_path = "-"; /* default to stdout */ + + if (processing_mode == 0) + processing_mode = MODE_NAIVE; + + if (input_format == 0) + input_format = FORMAT_SYSPROF; + break; + + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + int main (int argc, char **argv) { @@ -1301,8 +1340,14 @@ Utility is a work-in-progress, see README.eu-stacktrace in the source branch.") error (EXIT_BAD, errno, N_("Cannot set signal handler for SIGINT")); #if !(HAVE_SYSPROF_HEADERS) - /* TODO: Should hide corresponding command line options when this is the case. */ + /* TODO: Should hide corresponding command line options when this is the case? */ error (EXIT_BAD, 0, N_("Sysprof support is not available in this version.")); + + /* XXX: The following are not specific to the Sysprof backend; + avoid unused-variable warnings while it is the only backend. */ + (void)sample_thread_callbacks; + (void)output_format; + (void)maxframes; #else /* TODO: For now, code the processing loop for sysprof only; generalize later. */ assert (input_format == FORMAT_SYSPROF);