diff options
author | Serhei Makarov <serhei@serhei.io> | 2023-09-03 16:49:29 -0400 |
---|---|---|
committer | Serhei Makarov <serhei@serhei.io> | 2023-09-03 16:49:29 -0400 |
commit | 9817ed70889536dd46f28a923a99b79e891eb8c4 (patch) | |
tree | 6fd3397cb5a207944b770d0e24b0f75650395744 | |
parent | 87294a118756a5cd45b0f9e21d6907f321da92c1 (diff) |
eu-stacktrace WIP: sysprof packet parser for passthrough
Separate and byte-swap the packets as done by sysprof-capture-reader.
-rw-r--r-- | src/stacktrace.c | 388 |
1 files changed, 362 insertions, 26 deletions
diff --git a/src/stacktrace.c b/src/stacktrace.c index 7f522bf3..e9b55829 100644 --- a/src/stacktrace.c +++ b/src/stacktrace.c @@ -13,11 +13,66 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. */ + along with this program. If not, see <http://www.gnu.org/licenses/>. + + This file incorporates work covered by the following copyright and + permission notice: + + Copyright 2016-2019 Christian Hergert <chergert@redhat.com> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + Subject to the terms and conditions of this license, each copyright holder + and contributor hereby grants to those receiving rights under this license + a perpetual, worldwide, non-exclusive, no-charge, royalty-free, + irrevocable (except for failure to satisfy the conditions of this license) + patent license to make, have made, use, offer to sell, sell, import, and + otherwise transfer this software, where such license applies only to those + patent claims, already acquired or hereafter acquired, licensable by such + copyright holder or contributor that are necessarily infringed by: + + (a) their Contribution(s) (the licensed copyrights of copyright holders + and non-copyrightable additions of contributors, in source or binary + form) alone; or + + (b) combination of their Contribution(s) with the work of authorship to + which such Contribution(s) was added by such copyright holder or + contributor, if, at the time the Contribution is added, such addition + causes such combination to be necessarily infringed. The patent license + shall not apply to any other combinations which include the + Contribution. + + Except as expressly stated above, no rights or licenses from any copyright + holder or contributor is granted under this license, whether expressly, by + implication, estoppel or otherwise. + + DISCLAIMER + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <config.h> #include <assert.h> #include <argp.h> +#include <stdbool.h> #include <stdio.h> #include <string.h> #include <fcntl.h> @@ -37,9 +92,6 @@ static int input_fd = -1; static char *output_path = NULL; static int output_fd = -1; -static size_t passthru_buffer_len = 0; -static char *passthru_buffer = NULL; - #define MODE_OPTS "none/passthru" #define MODE_NONE 0x0 #define MODE_PASSTHRU 0x1 @@ -51,6 +103,7 @@ static int processing_mode; #define FORMAT_PERF 0x1 #define FORMAT_SYSPROF 0x2 static int input_format; +static int output_format = FORMAT_SYSPROF; /* TODO: add to cmdline args? */ /* Program exit codes. All samples processed without any errors is GOOD. Some non-fatal errors during processing is an ERROR. A @@ -61,6 +114,264 @@ static int input_format; #define EXIT_BAD 2 #define EXIT_USAGE 64 +/* Sysprof format support. + TODO: Could split into a separate file or even a library. */ + +#ifdef HAVE_SYSPROF_4_HEADERS + +/* 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. */ + +/* A complete passthrough can be implemented based on the following 7 functions: + - sysprof_reader_begin/sysprof_reader_end :: sysprof_capture_reader_new_from_fd + - sysprof_reader_getheader :: sysprof_capture_reader_read_file_header + - sysprof_reader_getframes :: sysprof_capture_reader_discover_end_time, an example main loop that doesn't handle every type of frame + - sysprof_reader_next_frame :: sysprof_capture_reader_peek_frame + sysprof_capture_reader_skip + sysprof_capture_reader_read_basic + - sysprof_reader_ensure_space_for :: sysprof_capture_reader_ensure_space_for + - sysprof_reader_bswap_frame :: sysprof_capture_reader_bswap_frame + */ + +/* Callback results */ +enum +{ + SYSPROF_CB_OK = 0, + SYSPROF_CB_ABORT +}; + +typedef struct +{ + uint8_t *buf; + size_t bufsz; + size_t len; + size_t pos; + size_t fd_off; /* XXX track offset for debugging only */ + int fd; + int endian; + SysprofCaptureFileHeader header; +} SysprofReader; + +/* forward decls */ +SysprofReader *sysprof_reader_begin (int fd); +void sysprof_reader_end (SysprofReader *reader); +bool sysprof_reader_getheader (SysprofReader *reader, + SysprofCaptureFileHeader *header); +void sysprof_reader_bswap_frame (SysprofReader *reader, + SysprofCaptureFrame *frame); +bool sysprof_reader_ensure_space_for (SysprofReader *reader, size_t len); +bool sysprof_reader_skip_frame (SysprofReader *reader); +bool sysprof_reader_peek_frame (SysprofReader *reader, SysprofCaptureFrame *frame); +ptrdiff_t sysprof_reader_getframes (SysprofReader *reader, + int (*callback) (SysprofCaptureFrame *frame, + void *arg), + void *arg); + +SysprofReader * +sysprof_reader_begin (int fd) +{ + SysprofReader *reader; + + assert (fd > -1); + + /* TODO elfutils style: libraries use __lib??_seterrno and ??_E_ENOMEM. */ + reader = malloc (sizeof (SysprofReader)); + if (reader == NULL) + { + errno = ENOMEM; + return NULL; + } + + reader->bufsz = USHRT_MAX * 2; + reader->buf = malloc (reader->bufsz); + if (reader->buf == NULL) + { + free (reader); + errno = ENOMEM; + return NULL; + } + + reader->len = 0; + reader->pos = 0; + reader->fd = fd; + reader->fd_off = 0; + + if (!sysprof_reader_getheader (reader, &reader->header)) + { + int errsv = errno; + sysprof_reader_end (reader); + errno = errsv; + return NULL; + } + + if (reader->header.little_endian) + reader->endian = __LITTLE_ENDIAN; + else + reader->endian = __BIG_ENDIAN; + + return reader; +} + +void +sysprof_reader_end (SysprofReader *reader) +{ + if (reader != NULL) + { + free (reader->buf); + free (reader); + } +} + +bool +sysprof_reader_getheader (SysprofReader *reader, + SysprofCaptureFileHeader *header) +{ + assert (reader != NULL); + assert (header != NULL); + + if (sizeof *header != read (reader->fd, header, sizeof *header)) + { + /* errno is propagated */ + return false; + } + reader->fd_off += sizeof *header; + + if (header->magic != SYSPROF_CAPTURE_MAGIC) + { + errno = EBADMSG; + return false; + } + + header->capture_time[sizeof header->capture_time - 1] = '\0'; + + return true; +} + +void +sysprof_reader_bswap_frame (SysprofReader *reader, SysprofCaptureFrame *frame) +{ + assert (reader != NULL); + assert (frame != NULL); + + if (unlikely (reader->endian != __BYTE_ORDER)) + { + frame->len = bswap_16 (frame->len); + frame->cpu = bswap_16 (frame->cpu); + frame->pid = bswap_32 (frame->pid); + frame->time = bswap_64 (frame->time); + } +} + +bool +sysprof_reader_ensure_space_for (SysprofReader *reader, size_t len) +{ + assert (reader != NULL); + assert (reader->pos <= reader->len); + assert (len > 0); + + /* Ensure alignment of length to read */ + len = (len + SYSPROF_CAPTURE_ALIGN - 1) & ~(SYSPROF_CAPTURE_ALIGN - 1); + + if ((reader->len - reader->pos) < len) + { + ssize_t r; + + if (reader->len > reader->pos) + memmove (reader->buf, + &reader->buf[reader->pos], + reader->len - reader->pos); + reader->len -= reader->pos; + reader->pos = 0; + + while (reader->len < len) + { + assert ((reader->pos + reader->len) < reader->bufsz); + assert (reader->len < reader->bufsz); + + /* Read into our buffer */ + r = read (reader->fd, + &reader->buf[reader->len], + reader->bufsz - reader->len); + + if (r <= 0) + break; + + reader->fd_off += r; + reader->len += r; + } + } + + return (reader->len - reader->pos) >= len; +} + +/* XXX May want to signal errors in more detail with an rc. */ +SysprofCaptureFrame * +sysprof_reader_next_frame (SysprofReader *reader) +{ + SysprofCaptureFrame frame_hdr; + SysprofCaptureFrame *frame = NULL; + + assert (reader != NULL); + assert ((reader->pos % SYSPROF_CAPTURE_ALIGN) == 0); + assert (reader->pos <= reader->len); + assert (reader->pos <= reader->bufsz); + + if (!sysprof_reader_ensure_space_for (reader, sizeof *frame)) + return NULL; + + assert ((reader->pos % SYSPROF_CAPTURE_ALIGN) == 0); + + frame = (SysprofCaptureFrame *)(void *)&reader->buf[reader->pos]; + frame_hdr = *frame; + sysprof_reader_bswap_frame (reader, &frame_hdr); + + if (frame_hdr.len < sizeof (SysprofCaptureFrame)) + return NULL; + + if (!sysprof_reader_ensure_space_for (reader, frame_hdr.len)) + return NULL; + + frame = (SysprofCaptureFrame *)(void *)&reader->buf[reader->pos]; + sysprof_reader_bswap_frame (reader, frame); + + if (frame->len > (reader->len - reader->pos)) + return NULL; + + reader->pos += frame->len; + + if ((reader->pos % SYSPROF_CAPTURE_ALIGN) != 0) + return NULL; + + /* if (frame->type < 0 || frame->type >= SYSPROF_CAPTURE_FRAME_LAST) */ + if (frame->type >= SYSPROF_CAPTURE_FRAME_LAST) + return NULL; + return frame; +} + +ptrdiff_t +sysprof_reader_getframes (SysprofReader *reader, + int (*callback) (SysprofCaptureFrame *, + void *), + void *arg) +{ + SysprofCaptureFrame *frame; + + assert (reader != NULL); + + while ((frame = sysprof_reader_next_frame (reader))) + { + int ok = callback (frame, arg); + if (ok != SYSPROF_CB_OK) + return -1; + } + return 0; +} + +#endif /* HAVE_SYSPROF4_HEADERS */ + +/* Main program. */ + static error_t parse_opt (int key, char *arg __attribute__ ((unused)), struct argp_state *state) @@ -121,6 +432,35 @@ parse_opt (int key, char *arg __attribute__ ((unused)), return 0; } +struct sysprof_passthru_info +{ + int output_fd; + SysprofReader *reader; + int pos; /* TODO for debugging purposes */ +}; + +#ifdef HAVE_SYSPROF_4_HEADERS +int +sysprof_none_cb (SysprofCaptureFrame *frame __attribute__ ((unused)), + void *arg __attribute__ ((unused))) +{ + return SYSPROF_CB_OK; +} + +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 prior 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 + int main (int argc, char **argv) { @@ -159,11 +499,6 @@ Utility is a work-in-progress, see README.eu-stacktrace in the source branch.") argp_parse(&argp, argc, argv, 0, NULL, NULL); -#ifndef HAVE_SYSPROF_4_HEADERS - /* 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.")); -#endif - /* TODO Also handle common expansions e.g. ~/foo instead of /home/user/foo. */ if (strcmp (input_path, "-") == 0) input_fd = STDIN_FILENO; @@ -174,26 +509,27 @@ Utility is a work-in-progress, see README.eu-stacktrace in the source branch.") if (strcmp (output_path, "-") == 0) output_fd = STDOUT_FILENO; else - output_fd = open (output_path, O_WRONLY); + output_fd = open (output_path, O_CREAT | O_WRONLY, 0640); if (output_fd < 0) error (EXIT_BAD, errno, N_("Cannot open output file or FIFO '%s'"), output_path); - /* TODO For now, just pipe data through with no packet separation. Just a quick 'cat' hack. */ - passthru_buffer_len = 8192; - passthru_buffer = (char *)malloc (passthru_buffer_len); - for (;;) - { - ssize_t n_read; - n_read = read (input_fd, passthru_buffer, passthru_buffer_len); - if (n_read < 0) - error (EXIT_BAD, errno, N_("Read error from file or FIFO '%s'"), input_path); - if (n_read == 0) - break; - if (n_read != write (output_fd, passthru_buffer, n_read)) - error (EXIT_BAD, errno, N_("Write error to file or FIFO '%s'"), output_path); - } - free (passthru_buffer); - /* TODO Also support mode none, which outputs nothing. */ +#ifndef HAVE_SYSPROF_4_HEADERS + /* 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.")); +#else + /* TODO: For now, code the processing loop for sysprof only; generalize later. */ + assert (input_format == FORMAT_SYSPROF); + assert (output_format == FORMAT_SYSPROF); + SysprofReader *reader = sysprof_reader_begin (input_fd); + ssize_t n_write = write (output_fd, &reader->header, sizeof reader->header); + if (n_write < 0) + error (EXIT_BAD, errno, N_("Write error to file or FIFO '%s'"), output_path); + struct sysprof_passthru_info spi = { output_fd, reader, sizeof reader->header }; + ptrdiff_t offset = sysprof_reader_getframes (reader, &sysprof_passthru_cb, &spi); + if (offset < 0) + error (EXIT_BAD, errno, N_("No frames in file or FIFO '%s'"), input_path); + sysprof_reader_end (reader); +#endif if (input_fd != -1) close (input_fd); |