| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> |
| * |
| * Updates: |
| * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com> |
| */ |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| |
| #include "trace-local.h" |
| |
| #define TRACING_STR "tracing" |
| #define HEAD_PAGE_STR "header_page" |
| #define HEAD_PAGE_EVENT "header_event" |
| #define HEAD_OPTIONS "options " |
| #define HEAD_LATENCY "latency " |
| #define HEAD_FLYRECORD "flyrecord" |
| |
| #define DUMP_SIZE 1024 |
| |
| static struct tep_handle *tep; |
| static unsigned int trace_cpus; |
| |
| enum dump_items { |
| SUMMARY = (1 << 0), |
| HEAD_PAGE = (1 << 1), |
| HEAD_EVENT = (1 << 2), |
| FTRACE_FORMAT = (1 << 3), |
| EVENT_SYSTEMS = (1 << 4), |
| EVENT_FORMAT = (1 << 5), |
| KALLSYMS = (1 << 6), |
| TRACE_PRINTK = (1 << 7), |
| CMDLINES = (1 << 8), |
| OPTIONS = (1 << 9), |
| FLYRECORD = (1 << 10), |
| }; |
| |
| enum dump_items verbosity; |
| |
| #define DUMP_CHECK(X) ((X) & verbosity) |
| |
| #define do_print(ids, fmt, ...) \ |
| do { \ |
| if (!(ids) || DUMP_CHECK(ids)) \ |
| tracecmd_plog(fmt, ##__VA_ARGS__); \ |
| } while (0) |
| |
| static int read_file_string(int fd, char *dst, int len) |
| { |
| size_t size = 0; |
| int r; |
| |
| do { |
| r = read(fd, dst+size, 1); |
| if (r > 0) { |
| size++; |
| len--; |
| } else |
| break; |
| if (!dst[size - 1]) |
| break; |
| } while (r > 0 && len); |
| |
| if (!size || dst[size - 1]) |
| return -1; |
| return 0; |
| } |
| |
| static int read_file_bytes(int fd, char *dst, int len) |
| { |
| size_t size = 0; |
| int r; |
| |
| do { |
| r = read(fd, dst+size, len); |
| if (r > 0) { |
| size += r; |
| len -= r; |
| } else |
| break; |
| } while (r > 0); |
| |
| if (len) |
| return -1; |
| return 0; |
| } |
| |
| static void read_dump_string(int fd, int size, enum dump_items id) |
| { |
| char buf[DUMP_SIZE]; |
| int lsize; |
| |
| while (size) { |
| lsize = (size < DUMP_SIZE) ? size : DUMP_SIZE - 1; |
| if (read_file_bytes(fd, buf, lsize)) |
| die("cannot read %d bytes", lsize); |
| buf[lsize] = 0; |
| do_print(id, "%s", buf); |
| size -= lsize; |
| } |
| |
| do_print(id, "\n"); |
| } |
| |
| static int read_file_number(int fd, void *digit, int size) |
| { |
| unsigned long long val; |
| char buf[8]; |
| |
| if (size > 8) |
| return -1; |
| |
| if (read_file_bytes(fd, buf, size)) |
| return -1; |
| |
| val = tep_read_number(tep, buf, size); |
| switch (size) { |
| case 1: |
| *((char *)digit) = val; |
| break; |
| case 2: |
| *((unsigned short *)digit) = val; |
| break; |
| case 4: |
| *((unsigned int *)digit) = val; |
| break; |
| case 8: |
| *((unsigned long long *)digit) = val; |
| break; |
| default: |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void dump_initial_format(int fd) |
| { |
| char magic[] = TRACECMD_MAGIC; |
| char buf[DUMP_SIZE]; |
| int val4; |
| |
| do_print(SUMMARY, "\t[Initial format]\n"); |
| |
| /* check initial bytes */ |
| if (read_file_bytes(fd, buf, sizeof(magic))) |
| die("cannot read %d bytes magic", sizeof(magic)); |
| if (memcmp(buf, magic, sizeof(magic)) != 0) |
| die("wrong file magic"); |
| |
| /* check initial tracing string */ |
| if (read_file_bytes(fd, buf, strlen(TRACING_STR))) |
| die("cannot read %d bytes tracing string", strlen(TRACING_STR)); |
| buf[strlen(TRACING_STR)] = 0; |
| if (strncmp(buf, TRACING_STR, strlen(TRACING_STR)) != 0) |
| die("wrong tracing string: %s", buf); |
| |
| /* get file version */ |
| if (read_file_string(fd, buf, DUMP_SIZE)) |
| die("no version string"); |
| |
| do_print(SUMMARY, "\t\t%s\t[Version]\n", buf); |
| |
| /* get file endianness*/ |
| if (read_file_bytes(fd, buf, 1)) |
| die("cannot read file endianness"); |
| do_print(SUMMARY, "\t\t%d\t[%s endian]\n", buf[0], buf[0]?"Big":"Little"); |
| |
| tep_set_file_bigendian(tep, buf[0]); |
| tep_set_local_bigendian(tep, tracecmd_host_bigendian()); |
| |
| /* get file bytes per long*/ |
| if (read_file_bytes(fd, buf, 1)) |
| die("cannot read file bytes per long"); |
| do_print(SUMMARY, "\t\t%d\t[Bytes in a long]\n", buf[0]); |
| |
| if (read_file_number(fd, &val4, 4)) |
| die("cannot read file page size"); |
| do_print(SUMMARY, "\t\t%d\t[Page size, bytes]\n", val4); |
| } |
| |
| static void dump_header_page(int fd) |
| { |
| unsigned long long size; |
| char buf[DUMP_SIZE]; |
| |
| do_print((SUMMARY | HEAD_PAGE), "\t[Header page, "); |
| |
| /* check header string */ |
| if (read_file_bytes(fd, buf, strlen(HEAD_PAGE_STR) + 1)) |
| die("cannot read %d bytes header string", strlen(HEAD_PAGE_STR)); |
| if (strncmp(buf, HEAD_PAGE_STR, strlen(HEAD_PAGE_STR)) != 0) |
| die("wrong header string: %s", buf); |
| |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read the size of the page header information"); |
| |
| do_print((SUMMARY | HEAD_PAGE), "%lld bytes]\n", size); |
| |
| read_dump_string(fd, size, HEAD_PAGE); |
| } |
| |
| static void dump_header_event(int fd) |
| { |
| unsigned long long size; |
| char buf[DUMP_SIZE]; |
| |
| do_print((SUMMARY | HEAD_EVENT), "\t[Header event, "); |
| |
| /* check header string */ |
| if (read_file_bytes(fd, buf, strlen(HEAD_PAGE_EVENT) + 1)) |
| die("cannot read %d bytes header string", strlen(HEAD_PAGE_EVENT)); |
| if (strncmp(buf, HEAD_PAGE_EVENT, strlen(HEAD_PAGE_EVENT)) != 0) |
| die("wrong header string: %s", buf); |
| |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read the size of the page header information"); |
| |
| do_print((SUMMARY | HEAD_EVENT), "%lld bytes]\n", size); |
| |
| read_dump_string(fd, size, HEAD_EVENT); |
| } |
| |
| static void dump_ftrace_events_format(int fd) |
| { |
| unsigned long long size; |
| unsigned int count; |
| |
| do_print((SUMMARY | FTRACE_FORMAT), "\t[Ftrace format, "); |
| if (read_file_number(fd, &count, 4)) |
| die("cannot read the count of the ftrace events"); |
| |
| do_print((SUMMARY | FTRACE_FORMAT), "%d events]\n", count); |
| |
| while (count) { |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read the size of the %d ftrace event", count); |
| read_dump_string(fd, size, FTRACE_FORMAT); |
| count--; |
| } |
| } |
| |
| static void dump_events_format(int fd) |
| { |
| unsigned long long size; |
| unsigned int systems; |
| unsigned int events; |
| char buf[DUMP_SIZE]; |
| |
| do_print((SUMMARY | EVENT_FORMAT | EVENT_SYSTEMS), "\t[Events format, "); |
| |
| if (read_file_number(fd, &systems, 4)) |
| die("cannot read the count of the event systems"); |
| |
| do_print((SUMMARY | EVENT_FORMAT | EVENT_SYSTEMS), "%d systems]\n", systems); |
| |
| while (systems) { |
| |
| if (read_file_string(fd, buf, DUMP_SIZE)) |
| die("cannot read the name of the $dth system", systems); |
| if (read_file_number(fd, &events, 4)) |
| die("cannot read the count of the events in system %s", |
| buf); |
| do_print(EVENT_SYSTEMS, "\t\t%s %d [system, events]\n", buf, events); |
| while (events) { |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read the format size of the %dth event from system %s", |
| events, buf); |
| read_dump_string(fd, size, EVENT_FORMAT); |
| events--; |
| } |
| systems--; |
| } |
| } |
| |
| static void dump_kallsyms(int fd) |
| { |
| unsigned int size; |
| |
| do_print((SUMMARY | KALLSYMS), "\t[Kallsyms, "); |
| |
| if (read_file_number(fd, &size, 4)) |
| die("cannot read the size of the kallsyms"); |
| |
| do_print((SUMMARY | KALLSYMS), "%d bytes]\n", size); |
| |
| read_dump_string(fd, size, KALLSYMS); |
| } |
| |
| static void dump_printk(int fd) |
| { |
| unsigned int size; |
| |
| do_print((SUMMARY | TRACE_PRINTK), "\t[Trace printk, "); |
| |
| if (read_file_number(fd, &size, 4)) |
| die("cannot read the size of the trace printk"); |
| |
| do_print((SUMMARY | TRACE_PRINTK), "%d bytes]\n", size); |
| |
| read_dump_string(fd, size, TRACE_PRINTK); |
| } |
| |
| static void dump_cmdlines(int fd) |
| { |
| unsigned long long size; |
| |
| do_print((SUMMARY | CMDLINES), "\t[Saved command lines, "); |
| |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read the size of the saved command lines"); |
| |
| do_print((SUMMARY | CMDLINES), "%d bytes]\n", size); |
| |
| read_dump_string(fd, size, CMDLINES); |
| } |
| |
| static void dump_cpus_count(int fd) |
| { |
| if (read_file_number(fd, &trace_cpus, 4)) |
| die("cannot read the cpu count"); |
| |
| do_print(SUMMARY, "\t%d [CPUs with tracing data]\n", trace_cpus); |
| } |
| |
| static void dump_option_string(int fd, int size, char *desc) |
| { |
| do_print(OPTIONS, "\t\t[Option %s, %d bytes]\n", desc, size); |
| if (size) |
| read_dump_string(fd, size, OPTIONS); |
| } |
| |
| static void dump_option_buffer(int fd, int size) |
| { |
| unsigned long long offset; |
| |
| if (size < 8) |
| die("broken buffer option with size %d", size); |
| |
| if (read_file_number(fd, &offset, 8)) |
| die("cannot read the offset of the buffer option"); |
| |
| do_print(OPTIONS, "\t\t[Option BUFFER, %d bytes]\n", size); |
| do_print(OPTIONS, "%lld [offset]\n", offset); |
| read_dump_string(fd, size - 8, OPTIONS); |
| } |
| |
| static void dump_option_int(int fd, int size, char *desc) |
| { |
| int val; |
| |
| do_print(OPTIONS, "\t\t[Option %s, %d bytes]\n", desc, size); |
| read_file_number(fd, &val, size); |
| do_print(OPTIONS, "%d\n", val); |
| } |
| |
| static void dump_option_xlong(int fd, int size, char *desc) |
| { |
| long long val; |
| |
| do_print(OPTIONS, "\t\t[Option %s, %d bytes]\n", desc, size); |
| read_file_number(fd, &val, size); |
| do_print(OPTIONS, "0x%llX\n", val); |
| } |
| |
| static void dump_option_timeshift(int fd, int size) |
| { |
| long long *offsets = NULL; |
| long long *times = NULL; |
| long long trace_id; |
| unsigned int count; |
| int i; |
| |
| /* |
| * long long int (8 bytes) trace session ID |
| * int (4 bytes) count of timestamp offsets. |
| * long long array of size [count] of times, |
| * when the offsets were calculated. |
| * long long array of size [count] of timestamp offsets. |
| */ |
| if (size < 12) { |
| do_print(OPTIONS, "Broken time shift option, size %s", size); |
| return; |
| } |
| do_print(OPTIONS, "\t\t[Option TimeShift, %d bytes]\n", size); |
| read_file_number(fd, &trace_id, 8); |
| do_print(OPTIONS, "0x%llX [peer's trace id]\n", trace_id); |
| read_file_number(fd, &count, 4); |
| do_print(OPTIONS, "%lld [samples count]\n", count); |
| times = calloc(count, sizeof(long long)); |
| if (!times) |
| goto out; |
| offsets = calloc(count, sizeof(long long)); |
| if (!offsets) |
| goto out; |
| |
| for (i = 0; i < count; i++) |
| read_file_number(fd, times + i, 8); |
| for (i = 0; i < count; i++) |
| read_file_number(fd, offsets + i, 8); |
| |
| for (i = 0; i < count; i++) |
| do_print(OPTIONS, "\t%lld %lld [offset @ time]\n", |
| offsets[i], times[i]); |
| |
| out: |
| free(times); |
| free(offsets); |
| } |
| |
| void dump_option_guest(int fd, int size) |
| { |
| unsigned long long trace_id; |
| char *buf, *p; |
| int cpu, pid; |
| int cpus; |
| int i; |
| |
| do_print(OPTIONS, "\t\t[Option GUEST, %d bytes]\n", size); |
| |
| /* |
| * Guest name, null terminated string |
| * long long (8 bytes) trace-id |
| * int (4 bytes) number of guest CPUs |
| * array of size number of guest CPUs: |
| * int (4 bytes) Guest CPU id |
| * int (4 bytes) Host PID, running the guest CPU |
| */ |
| buf = calloc(1, size); |
| if (!buf) |
| return; |
| if (read_file_bytes(fd, buf, size)) |
| goto out; |
| |
| p = buf; |
| do_print(OPTIONS, "%s [Guest name]\n", p); |
| size -= strlen(buf) + 1; |
| p += strlen(buf) + 1; |
| |
| if (size < sizeof(long long)) |
| goto out; |
| trace_id = tep_read_number(tep, p, sizeof(long long)); |
| size -= sizeof(long long); |
| p += sizeof(long long); |
| do_print(OPTIONS, "0x%llX [trace id]\n", trace_id); |
| |
| if (size < sizeof(int)) |
| goto out; |
| cpus = tep_read_number(tep, p, sizeof(int)); |
| size -= sizeof(int); |
| p += sizeof(int); |
| do_print(OPTIONS, "%d [Guest CPUs]\n", cpus); |
| |
| for (i = 0; i < cpus; i++) { |
| if (size < 2 * sizeof(int)) |
| goto out; |
| cpu = tep_read_number(tep, p, sizeof(int)); |
| size -= sizeof(int); |
| p += sizeof(int); |
| pid = tep_read_number(tep, p, sizeof(int)); |
| size -= sizeof(int); |
| p += sizeof(int); |
| do_print(OPTIONS, " %d %d [guest cpu, host pid]\n", cpu, pid); |
| } |
| |
| out: |
| free(buf); |
| } |
| |
| static void dump_options(int fd) |
| { |
| unsigned short option; |
| unsigned int size; |
| int count = 0; |
| |
| for (;;) { |
| if (read_file_number(fd, &option, 2)) |
| die("cannot read the option id"); |
| if (!option) |
| break; |
| if (read_file_number(fd, &size, 4)) |
| die("cannot read the option size"); |
| |
| count++; |
| if (!DUMP_CHECK(OPTIONS)) { |
| lseek64(fd, size, SEEK_CUR); |
| continue; |
| } |
| switch (option) { |
| case TRACECMD_OPTION_DATE: |
| dump_option_string(fd, size, "DATE"); |
| break; |
| case TRACECMD_OPTION_CPUSTAT: |
| dump_option_string(fd, size, "CPUSTAT"); |
| break; |
| case TRACECMD_OPTION_BUFFER: |
| dump_option_buffer(fd, size); |
| break; |
| case TRACECMD_OPTION_TRACECLOCK: |
| dump_option_string(fd, size, "TRACECLOCK"); |
| break; |
| case TRACECMD_OPTION_UNAME: |
| dump_option_string(fd, size, "UNAME"); |
| break; |
| case TRACECMD_OPTION_HOOK: |
| dump_option_string(fd, size, "HOOK"); |
| break; |
| case TRACECMD_OPTION_OFFSET: |
| dump_option_string(fd, size, "OFFSET"); |
| break; |
| case TRACECMD_OPTION_CPUCOUNT: |
| dump_option_int(fd, size, "CPUCOUNT"); |
| break; |
| case TRACECMD_OPTION_VERSION: |
| dump_option_string(fd, size, "VERSION"); |
| break; |
| case TRACECMD_OPTION_PROCMAPS: |
| dump_option_string(fd, size, "PROCMAPS"); |
| break; |
| case TRACECMD_OPTION_TRACEID: |
| dump_option_xlong(fd, size, "TRACEID"); |
| break; |
| case TRACECMD_OPTION_TIME_SHIFT: |
| dump_option_timeshift(fd, size); |
| break; |
| case TRACECMD_OPTION_GUEST: |
| dump_option_guest(fd, size); |
| break; |
| default: |
| do_print(OPTIONS, " %d %d\t[Unknown option, size - skipping]\n", |
| option, size); |
| lseek64(fd, size, SEEK_CUR); |
| break; |
| } |
| } |
| do_print(SUMMARY, "\t[%d options]\n", count); |
| |
| } |
| |
| static void dump_latency(int fd) |
| { |
| do_print(SUMMARY, "\t[Latency tracing data]\n"); |
| } |
| |
| static void dump_flyrecord(int fd) |
| { |
| long long cpu_offset; |
| long long cpu_size; |
| int i; |
| |
| do_print((SUMMARY | FLYRECORD), "\t[Flyrecord tracing data]\n"); |
| |
| for (i = 0; i < trace_cpus; i++) { |
| if (read_file_number(fd, &cpu_offset, 8)) |
| die("cannot read the cpu %d offset", i); |
| if (read_file_number(fd, &cpu_size, 8)) |
| die("cannot read the cpu %d size", i); |
| do_print(FLYRECORD, "\t\t %lld %lld\t[offset, size of cpu %d]\n", |
| cpu_offset, cpu_size, i); |
| } |
| } |
| |
| static void dump_therest(int fd) |
| { |
| char str[10]; |
| |
| for (;;) { |
| if (read_file_bytes(fd, str, 10)) |
| die("cannot read the rest of the header"); |
| |
| if (strncmp(str, HEAD_OPTIONS, 10) == 0) |
| dump_options(fd); |
| else if (strncmp(str, HEAD_LATENCY, 10) == 0) |
| dump_latency(fd); |
| else if (strncmp(str, HEAD_FLYRECORD, 10) == 0) |
| dump_flyrecord(fd); |
| else { |
| lseek64(fd, -10, SEEK_CUR); |
| break; |
| } |
| } |
| } |
| |
| static void dump_file(const char *file) |
| { |
| int fd; |
| |
| tep = tep_alloc(); |
| if (!tep) |
| return; |
| |
| fd = open(file, O_RDONLY); |
| if (fd < 0) |
| die("cannot open '%s'\n", file); |
| |
| do_print(SUMMARY, "\n Tracing meta data in file %s:\n", file); |
| |
| dump_initial_format(fd); |
| dump_header_page(fd); |
| dump_header_event(fd); |
| dump_ftrace_events_format(fd); |
| dump_events_format(fd); |
| dump_kallsyms(fd); |
| dump_printk(fd); |
| dump_cmdlines(fd); |
| dump_cpus_count(fd); |
| dump_therest(fd); |
| |
| tep_free(tep); |
| tep = NULL; |
| close(fd); |
| } |
| |
| enum { |
| OPT_all = 244, |
| OPT_summary = 245, |
| OPT_flyrecord = 246, |
| OPT_options = 247, |
| OPT_cmd_lines = 248, |
| OPT_printk = 249, |
| OPT_kallsyms = 250, |
| OPT_events = 251, |
| OPT_systems = 252, |
| OPT_ftrace = 253, |
| OPT_head_event = 254, |
| OPT_head_page = 255, |
| }; |
| |
| void trace_dump(int argc, char **argv) |
| { |
| char *input_file = NULL; |
| bool validate = false; |
| int c; |
| |
| if (argc < 2) |
| usage(argv); |
| |
| if (strcmp(argv[1], "dump") != 0) |
| usage(argv); |
| for (;;) { |
| int option_index = 0; |
| static struct option long_options[] = { |
| {"all", no_argument, NULL, OPT_all}, |
| {"summary", no_argument, NULL, OPT_summary}, |
| {"head-page", no_argument, NULL, OPT_head_page}, |
| {"head-event", no_argument, NULL, OPT_head_event}, |
| {"ftrace-events", no_argument, NULL, OPT_ftrace}, |
| {"systems", no_argument, NULL, OPT_systems}, |
| {"events", no_argument, NULL, OPT_events}, |
| {"kallsyms", no_argument, NULL, OPT_kallsyms}, |
| {"printk", no_argument, NULL, OPT_printk}, |
| {"cmd-lines", no_argument, NULL, OPT_cmd_lines}, |
| {"options", no_argument, NULL, OPT_options}, |
| {"flyrecord", no_argument, NULL, OPT_flyrecord}, |
| {"validate", no_argument, NULL, 'v'}, |
| {"help", no_argument, NULL, '?'}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| c = getopt_long (argc-1, argv+1, "+hvai:", |
| long_options, &option_index); |
| if (c == -1) |
| break; |
| switch (c) { |
| case 'h': |
| usage(argv); |
| break; |
| case 'i': |
| input_file = optarg; |
| break; |
| case 'v': |
| validate = true; |
| break; |
| case OPT_all: |
| verbosity = 0xFFFFFFFF; |
| break; |
| case OPT_summary: |
| verbosity |= SUMMARY; |
| break; |
| case OPT_flyrecord: |
| verbosity |= FLYRECORD; |
| break; |
| case OPT_options: |
| verbosity |= OPTIONS; |
| break; |
| case OPT_cmd_lines: |
| verbosity |= CMDLINES; |
| break; |
| case OPT_printk: |
| verbosity |= TRACE_PRINTK; |
| break; |
| case OPT_kallsyms: |
| verbosity |= KALLSYMS; |
| break; |
| case OPT_events: |
| verbosity |= EVENT_FORMAT; |
| break; |
| case OPT_systems: |
| verbosity |= EVENT_SYSTEMS; |
| break; |
| case OPT_ftrace: |
| verbosity |= FTRACE_FORMAT; |
| break; |
| case OPT_head_event: |
| verbosity |= HEAD_EVENT; |
| break; |
| case OPT_head_page: |
| verbosity |= HEAD_PAGE; |
| break; |
| default: |
| usage(argv); |
| } |
| } |
| |
| if ((argc - optind) >= 2) { |
| if (input_file) |
| usage(argv); |
| input_file = argv[optind + 1]; |
| } |
| |
| if (!input_file) |
| input_file = DEFAULT_INPUT_FILE; |
| |
| if (!verbosity && !validate) |
| verbosity = SUMMARY; |
| |
| dump_file(input_file); |
| |
| if (validate) |
| tracecmd_plog("File %s is a valid trace-cmd file\n", input_file); |
| } |