#include <string.h>
#include <fcntl.h>
#include <signal.h>
+
+#include <system.h>
+
+/*************************************
+ * 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. */
}
#endif
-#include <system.h>
+/*************************************
+ * Includes: sysprof data structures *
+ *************************************/
#if HAVE_SYSPROF_6_HEADERS
#include <sysprof-6/sysprof-capture-types.h>
} 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;
#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
#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
assert (fd > -1);
- /* TODO elfutils style: libraries use __lib??_seterrno and ??_E_ENOMEM. */
reader = malloc (sizeof (SysprofReader));
if (reader == NULL)
{
#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
{
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)
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)
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;
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 "<unknown>";
+ 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, "<unknown>");
+ 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
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 "<unknown>";
- 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, "<unknown>");
- 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,
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)
{
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);