| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <[email protected]> |
| * |
| * Updates: |
| * Copyright (C) 2019, VMware, Tzvetomir Stoyanov <[email protected]> |
| */ |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <getopt.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <errno.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; |
| static int has_clock; |
| static unsigned long file_version; |
| static bool read_compress; |
| static struct tracecmd_compression *compress; |
| static char *meta_strings; |
| static int meta_strings_size; |
| |
| 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), |
| CLOCK = (1 << 11), |
| SECTIONS = (1 << 12), |
| STRINGS = (1 << 13), |
| }; |
| |
| struct file_section { |
| int id; |
| unsigned long long offset; |
| struct file_section *next; |
| enum dump_items verbosity; |
| }; |
| |
| static struct file_section *sections; |
| |
| 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_fd(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 size; |
| } |
| |
| static int read_compressed(int fd, char *dst, int len) |
| { |
| |
| if (read_compress) |
| return tracecmd_compress_buffer_read(compress, dst, len); |
| |
| return read_fd(fd, dst, len); |
| } |
| |
| static int do_lseek(int fd, int offset, int whence) |
| { |
| if (read_compress) |
| return tracecmd_compress_lseek(compress, offset, whence); |
| |
| return lseek64(fd, offset, whence); |
| } |
| |
| static int read_file_string(int fd, char *dst, int len) |
| { |
| size_t size = 0; |
| int r; |
| |
| do { |
| r = read_compressed(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) |
| { |
| int ret; |
| |
| ret = read_compressed(fd, dst, len); |
| return ret < 0 ? ret : 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 const char *get_metadata_string(int offset) |
| { |
| if (!meta_strings || offset < 0 || meta_strings_size <= offset) |
| return NULL; |
| |
| return meta_strings + offset; |
| } |
| |
| 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 %zu 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 %zu 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); |
| file_version = strtol(buf, NULL, 10); |
| if (!file_version && errno) |
| die("Invalid file version string %s", buf); |
| if (!tracecmd_is_version_supported(file_version)) |
| die("Unsupported file version %lu", file_version); |
| |
| /* 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_compress(int fd) |
| { |
| char zname[DUMP_SIZE]; |
| char zver[DUMP_SIZE]; |
| |
| if (file_version < FILE_VERSION_COMPRESSION) |
| return; |
| |
| /* get compression header */ |
| if (read_file_string(fd, zname, DUMP_SIZE)) |
| die("no compression header"); |
| |
| if (read_file_string(fd, zver, DUMP_SIZE)) |
| die("no compression version"); |
| |
| do_print((SUMMARY), "\t\t%s\t[Compression algorithm]\n", zname); |
| do_print((SUMMARY), "\t\t%s\t[Compression version]\n", zver); |
| |
| if (strcmp(zname, "none")) { |
| compress = tracecmd_compress_alloc(zname, zver, fd, tep, NULL); |
| if (!compress) |
| die("cannot uncompress the file"); |
| } |
| } |
| |
| 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 %zu 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 %zu 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 uncompress_reset(void) |
| { |
| if (compress && file_version >= FILE_VERSION_COMPRESSION) { |
| read_compress = false; |
| tracecmd_compress_reset(compress); |
| } |
| } |
| |
| static int uncompress_block(void) |
| { |
| int ret = 0; |
| |
| if (compress && file_version >= FILE_VERSION_COMPRESSION) { |
| ret = tracecmd_uncompress_block(compress); |
| if (!ret) |
| read_compress = true; |
| |
| } |
| |
| return ret; |
| } |
| |
| 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_section_header(int fd, enum dump_items v, unsigned short *flags) |
| { |
| unsigned long long offset, size; |
| unsigned short fl; |
| unsigned short id; |
| const char *desc; |
| int desc_id; |
| |
| offset = lseek64(fd, 0, SEEK_CUR); |
| if (read_file_number(fd, &id, 2)) |
| die("cannot read the section id"); |
| |
| if (read_file_number(fd, &fl, 2)) |
| die("cannot read the section flags"); |
| |
| if (read_file_number(fd, &desc_id, 4)) |
| die("no section description"); |
| |
| desc = get_metadata_string(desc_id); |
| if (!desc) |
| desc = "Unknown"; |
| |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read section size"); |
| |
| do_print(v, "\t[Section %d @ %lld: \"%s\", flags 0x%X, %lld bytes]\n", |
| id, offset, desc, fl, size); |
| |
| if (flags) |
| *flags = fl; |
| } |
| |
| static void dump_option_buffer(int fd, unsigned short option, int size) |
| { |
| unsigned long long total_size = 0; |
| unsigned long long data_size; |
| unsigned long long current; |
| unsigned long long offset; |
| unsigned short flags; |
| char clock[DUMP_SIZE]; |
| char name[DUMP_SIZE]; |
| int page_size; |
| int cpus = 0; |
| int id; |
| int i; |
| |
| 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"); |
| |
| if (read_file_string(fd, name, DUMP_SIZE)) |
| die("cannot read the name of the buffer option"); |
| |
| if (file_version < FILE_VERSION_SECTIONS) { |
| do_print(OPTIONS|FLYRECORD, "\t\t[Option BUFFER, %d bytes]\n", size); |
| do_print(OPTIONS|FLYRECORD, "%lld [offset]\n", offset); |
| do_print(OPTIONS|FLYRECORD, "\"%s\" [name]\n", name); |
| return; |
| } |
| |
| current = lseek64(fd, 0, SEEK_CUR); |
| if (lseek64(fd, offset, SEEK_SET) == (off_t)-1) |
| die("cannot goto buffer offset %lld", offset); |
| |
| dump_section_header(fd, FLYRECORD, &flags); |
| |
| if (lseek64(fd, current, SEEK_SET) == (off_t)-1) |
| die("cannot go back to buffer option"); |
| |
| do_print(OPTIONS|FLYRECORD, "\t\t[Option BUFFER, %d bytes]\n", size); |
| do_print(OPTIONS|FLYRECORD, "%lld [offset]\n", offset); |
| do_print(OPTIONS|FLYRECORD, "\"%s\" [name]\n", name); |
| |
| if (read_file_string(fd, clock, DUMP_SIZE)) |
| die("cannot read clock of the buffer option"); |
| |
| do_print(OPTIONS|FLYRECORD, "\"%s\" [clock]\n", clock); |
| if (option == TRACECMD_OPTION_BUFFER) { |
| if (read_file_number(fd, &page_size, 4)) |
| die("cannot read the page size of the buffer option"); |
| do_print(OPTIONS|FLYRECORD, "%d [Page size, bytes]\n", page_size); |
| |
| if (read_file_number(fd, &cpus, 4)) |
| die("cannot read the cpu count of the buffer option"); |
| |
| do_print(OPTIONS|FLYRECORD, "%d [CPUs]:\n", cpus); |
| for (i = 0; i < cpus; i++) { |
| if (read_file_number(fd, &id, 4)) |
| die("cannot read the id of cpu %d from the buffer option", i); |
| |
| if (read_file_number(fd, &offset, 8)) |
| die("cannot read the offset of cpu %d from the buffer option", i); |
| |
| if (read_file_number(fd, &data_size, 8)) |
| die("cannot read the data size of cpu %d from the buffer option", i); |
| |
| total_size += data_size; |
| do_print(OPTIONS|FLYRECORD, " %d %lld\t%lld\t[id, data offset and size]\n", |
| id, offset, data_size); |
| } |
| do_print(SUMMARY, "\t\[buffer \"%s\", \"%s\" clock, %d page size, " |
| "%d cpus, %lld bytes flyrecord data]\n", |
| name, clock, page_size, cpus, total_size); |
| } else { |
| do_print(SUMMARY, "\t\[buffer \"%s\", \"%s\" clock, latency data]\n", name, clock); |
| } |
| |
| } |
| |
| 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); |
| } |
| |
| struct time_shift_cpu { |
| unsigned int count; |
| long long *scalings; |
| long long *frac; |
| long long *offsets; |
| unsigned long long *times; |
| }; |
| |
| static void dump_option_timeshift(int fd, int size) |
| { |
| struct time_shift_cpu *cpus_data; |
| long long trace_id; |
| unsigned int flags; |
| unsigned int cpus; |
| int i, j; |
| |
| /* |
| * 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); |
| size -= 8; |
| do_print(OPTIONS, "0x%llX [peer's trace id]\n", trace_id); |
| read_file_number(fd, &flags, 4); |
| size -= 4; |
| do_print(OPTIONS, "0x%llX [peer's protocol flags]\n", flags); |
| read_file_number(fd, &cpus, 4); |
| size -= 4; |
| do_print(OPTIONS, "0x%llX [peer's CPU count]\n", cpus); |
| cpus_data = calloc(cpus, sizeof(struct time_shift_cpu)); |
| if (!cpus_data) |
| return; |
| for (j = 0; j < cpus; j++) { |
| if (size < 4) |
| goto out; |
| read_file_number(fd, &cpus_data[j].count, 4); |
| size -= 4; |
| do_print(OPTIONS, "%lld [samples count for CPU %d]\n", cpus_data[j].count, j); |
| cpus_data[j].times = calloc(cpus_data[j].count, sizeof(long long)); |
| cpus_data[j].offsets = calloc(cpus_data[j].count, sizeof(long long)); |
| cpus_data[j].scalings = calloc(cpus_data[j].count, sizeof(long long)); |
| cpus_data[j].frac = calloc(cpus_data[j].count, sizeof(long long)); |
| if (!cpus_data[j].times || !cpus_data[j].offsets || |
| !cpus_data[j].scalings || !cpus_data[j].frac) |
| goto out; |
| for (i = 0; i < cpus_data[j].count; i++) { |
| if (size < 8) |
| goto out; |
| read_file_number(fd, cpus_data[j].times + i, 8); |
| size -= 8; |
| } |
| for (i = 0; i < cpus_data[j].count; i++) { |
| if (size < 8) |
| goto out; |
| read_file_number(fd, cpus_data[j].offsets + i, 8); |
| size -= 8; |
| } |
| for (i = 0; i < cpus_data[j].count; i++) { |
| if (size < 8) |
| goto out; |
| read_file_number(fd, cpus_data[j].scalings + i, 8); |
| size -= 8; |
| } |
| } |
| |
| if (size > 0) { |
| for (j = 0; j < cpus; j++) { |
| if (!cpus_data[j].frac) |
| goto out; |
| for (i = 0; i < cpus_data[j].count; i++) { |
| if (size < 8) |
| goto out; |
| read_file_number(fd, cpus_data[j].frac + i, 8); |
| size -= 8; |
| } |
| } |
| } |
| |
| for (j = 0; j < cpus; j++) { |
| for (i = 0; i < cpus_data[j].count; i++) |
| do_print(OPTIONS, "\t%lld %lld %llu %llu[offset * scaling >> fraction @ time]\n", |
| cpus_data[j].offsets[i], cpus_data[j].scalings[i], |
| cpus_data[j].frac[i], cpus_data[j].times[i]); |
| |
| } |
| |
| out: |
| if (j < cpus) |
| do_print(OPTIONS, "Broken time shift option\n"); |
| for (j = 0; j < cpus; j++) { |
| free(cpus_data[j].times); |
| free(cpus_data[j].offsets); |
| free(cpus_data[j].scalings); |
| free(cpus_data[j].frac); |
| } |
| free(cpus_data); |
| } |
| |
| 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); |
| } |
| |
| void dump_option_tsc2nsec(int fd, int size) |
| { |
| int mult, shift; |
| unsigned long long offset; |
| |
| do_print(OPTIONS, "\n\t\t[Option TSC2NSEC, %d bytes]\n", size); |
| |
| if (read_file_number(fd, &mult, 4)) |
| die("cannot read tsc2nsec multiplier"); |
| if (read_file_number(fd, &shift, 4)) |
| die("cannot read tsc2nsec shift"); |
| if (read_file_number(fd, &offset, 8)) |
| die("cannot read tsc2nsec offset"); |
| do_print(OPTIONS, "%d %d %llu [multiplier, shift, offset]\n", mult, shift, offset); |
| } |
| |
| static void dump_option_section(int fd, unsigned int size, |
| unsigned short id, char *desc, enum dump_items v) |
| { |
| struct file_section *sec; |
| |
| sec = calloc(1, sizeof(struct file_section)); |
| if (!sec) |
| die("cannot allocate new section"); |
| |
| sec->next = sections; |
| sections = sec; |
| sec->id = id; |
| sec->verbosity = v; |
| if (read_file_number(fd, &sec->offset, 8)) |
| die("cannot read the option %d offset", id); |
| |
| do_print(OPTIONS, "\t\t[Option %s, %d bytes] @ %lld\n", desc, size, sec->offset); |
| } |
| |
| static void dump_sections(int fd, int count) |
| { |
| struct file_section *sec = sections; |
| unsigned short flags; |
| |
| while (sec) { |
| if (lseek64(fd, sec->offset, SEEK_SET) == (off_t)-1) |
| die("cannot goto option offset %lld", sec->offset); |
| |
| dump_section_header(fd, sec->verbosity, &flags); |
| |
| if ((flags & TRACECMD_SEC_FL_COMPRESS) && uncompress_block()) |
| die("cannot uncompress section block"); |
| |
| switch (sec->id) { |
| case TRACECMD_OPTION_HEADER_INFO: |
| dump_header_page(fd); |
| dump_header_event(fd); |
| break; |
| case TRACECMD_OPTION_FTRACE_EVENTS: |
| dump_ftrace_events_format(fd); |
| break; |
| case TRACECMD_OPTION_EVENT_FORMATS: |
| dump_events_format(fd); |
| break; |
| case TRACECMD_OPTION_KALLSYMS: |
| dump_kallsyms(fd); |
| break; |
| case TRACECMD_OPTION_PRINTK: |
| dump_printk(fd); |
| break; |
| case TRACECMD_OPTION_CMDLINES: |
| dump_cmdlines(fd); |
| break; |
| } |
| uncompress_reset(); |
| sec = sec->next; |
| } |
| do_print(SUMMARY|SECTIONS, "\t[%d sections]\n", count); |
| } |
| |
| static int dump_options_read(int fd); |
| |
| static int dump_option_done(int fd, int size) |
| { |
| unsigned long long offset; |
| |
| do_print(OPTIONS, "\t\t[Option DONE, %d bytes]\n", size); |
| |
| if (file_version < FILE_VERSION_SECTIONS || size < 8) |
| return 0; |
| |
| if (read_file_number(fd, &offset, 8)) |
| die("cannot read the next options offset"); |
| |
| do_print(OPTIONS, "%lld\n", offset); |
| if (!offset) |
| return 0; |
| |
| if (lseek64(fd, offset, SEEK_SET) == (off_t)-1) |
| die("cannot goto next options offset %lld", offset); |
| |
| do_print(OPTIONS, "\n\n"); |
| |
| return dump_options_read(fd); |
| } |
| |
| static int dump_options_read(int fd) |
| { |
| unsigned short flags = 0; |
| unsigned short option; |
| unsigned int size; |
| int count = 0; |
| |
| if (file_version >= FILE_VERSION_SECTIONS) |
| dump_section_header(fd, OPTIONS, &flags); |
| |
| if ((flags & TRACECMD_SEC_FL_COMPRESS) && uncompress_block()) |
| die("cannot uncompress file block"); |
| |
| for (;;) { |
| if (read_file_number(fd, &option, 2)) |
| die("cannot read the option id"); |
| if (option == TRACECMD_OPTION_DONE && file_version < FILE_VERSION_SECTIONS) |
| break; |
| if (read_file_number(fd, &size, 4)) |
| die("cannot read the option size"); |
| |
| count++; |
| 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: |
| case TRACECMD_OPTION_BUFFER_TEXT: |
| dump_option_buffer(fd, option, size); |
| break; |
| case TRACECMD_OPTION_TRACECLOCK: |
| do_print(OPTIONS, "\t\t[Option TRACECLOCK, %d bytes]\n", size); |
| read_dump_string(fd, size, OPTIONS | CLOCK); |
| has_clock = 1; |
| 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; |
| case TRACECMD_OPTION_TSC2NSEC: |
| dump_option_tsc2nsec(fd, size); |
| break; |
| case TRACECMD_OPTION_HEADER_INFO: |
| dump_option_section(fd, size, option, "HEADERS", HEAD_PAGE | HEAD_EVENT); |
| break; |
| case TRACECMD_OPTION_FTRACE_EVENTS: |
| dump_option_section(fd, size, option, "FTRACE EVENTS", FTRACE_FORMAT); |
| break; |
| case TRACECMD_OPTION_EVENT_FORMATS: |
| dump_option_section(fd, size, option, |
| "EVENT FORMATS", EVENT_SYSTEMS | EVENT_FORMAT); |
| break; |
| case TRACECMD_OPTION_KALLSYMS: |
| dump_option_section(fd, size, option, "KALLSYMS", KALLSYMS); |
| break; |
| case TRACECMD_OPTION_PRINTK: |
| dump_option_section(fd, size, option, "PRINTK", TRACE_PRINTK); |
| break; |
| case TRACECMD_OPTION_CMDLINES: |
| dump_option_section(fd, size, option, "CMDLINES", CMDLINES); |
| break; |
| case TRACECMD_OPTION_DONE: |
| uncompress_reset(); |
| count += dump_option_done(fd, size); |
| return count; |
| default: |
| do_print(OPTIONS, " %d %d\t[Unknown option, size - skipping]\n", |
| option, size); |
| do_lseek(fd, size, SEEK_CUR); |
| break; |
| } |
| } |
| uncompress_reset(); |
| return count; |
| } |
| |
| static void dump_options(int fd) |
| { |
| int count; |
| |
| count = dump_options_read(fd); |
| do_print(SUMMARY|OPTIONS, "\t[%d options]\n", count); |
| } |
| |
| static void dump_latency(int fd) |
| { |
| do_print(SUMMARY, "\t[Latency tracing data]\n"); |
| } |
| |
| static void dump_clock(int fd) |
| { |
| long long size; |
| char *clock; |
| |
| do_print((SUMMARY | CLOCK), "\t[Tracing clock]\n"); |
| if (!has_clock) { |
| do_print((SUMMARY | CLOCK), "\t\t No tracing clock saved in the file\n"); |
| return; |
| } |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read clock size"); |
| clock = calloc(1, size); |
| if (!clock) |
| die("cannot allocate clock %lld bytes", size); |
| |
| if (read_file_bytes(fd, clock, size)) |
| die("cannot read clock %lld bytes", size); |
| clock[size] = 0; |
| do_print((SUMMARY | CLOCK), "\t\t%s\n", clock); |
| free(clock); |
| } |
| |
| 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 %10.lld %10.lld\t[offset, size of cpu %d]\n", |
| cpu_offset, cpu_size, i); |
| } |
| dump_clock(fd); |
| } |
| |
| 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_v6_file(int 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); |
| } |
| |
| static int read_metadata_strings(int fd, unsigned long long size) |
| { |
| char *str, *strings; |
| int psize; |
| int ret; |
| |
| strings = realloc(meta_strings, meta_strings_size + size); |
| if (!strings) |
| return -1; |
| meta_strings = strings; |
| |
| ret = read_file_bytes(fd, meta_strings + meta_strings_size, size); |
| if (ret < 0) |
| return -1; |
| |
| do_print(STRINGS, "\t[String @ offset]\n"); |
| psize = 0; |
| while (psize < size) { |
| str = meta_strings + meta_strings_size + psize; |
| do_print(STRINGS, "\t\t\"%s\" @ %d\n", str, meta_strings_size + psize); |
| psize += strlen(str) + 1; |
| } |
| |
| meta_strings_size += size; |
| |
| return 0; |
| } |
| |
| static void get_meta_strings(int fd) |
| { |
| unsigned long long offset, size; |
| unsigned int csize, rsize; |
| unsigned short fl, id; |
| int desc_id; |
| |
| offset = lseek64(fd, 0, SEEK_CUR); |
| do { |
| if (read_file_number(fd, &id, 2)) |
| break; |
| if (read_file_number(fd, &fl, 2)) |
| die("cannot read section flags"); |
| if (read_file_number(fd, &desc_id, 4)) |
| die("cannot read section description"); |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read section size"); |
| if (id == TRACECMD_OPTION_STRINGS) { |
| if ((fl & TRACECMD_SEC_FL_COMPRESS)) { |
| read_file_number(fd, &csize, 4); |
| read_file_number(fd, &rsize, 4); |
| lseek64(fd, -8, SEEK_CUR); |
| if (uncompress_block()) |
| break; |
| } else { |
| rsize = size; |
| } |
| read_metadata_strings(fd, rsize); |
| uncompress_reset(); |
| } else { |
| if (lseek64(fd, size, SEEK_CUR) == (off_t)-1) |
| break; |
| } |
| } while (1); |
| |
| if (lseek64(fd, offset, SEEK_SET) == (off_t)-1) |
| die("cannot restore the original file location"); |
| } |
| |
| static int walk_v7_sections(int fd) |
| { |
| unsigned long long offset, soffset, size; |
| unsigned short fl; |
| unsigned short id; |
| int csize, rsize; |
| int count = 0; |
| int desc_id; |
| const char *desc; |
| |
| offset = lseek64(fd, 0, SEEK_CUR); |
| do { |
| soffset = lseek64(fd, 0, SEEK_CUR); |
| if (read_file_number(fd, &id, 2)) |
| break; |
| |
| if (read_file_number(fd, &fl, 2)) |
| die("cannot read section flags"); |
| |
| if (read_file_number(fd, &desc_id, 4)) |
| die("cannot read section description"); |
| |
| desc = get_metadata_string(desc_id); |
| if (!desc) |
| desc = "Unknown"; |
| |
| if (read_file_number(fd, &size, 8)) |
| die("cannot read section size"); |
| |
| if (id >= TRACECMD_OPTION_MAX) |
| do_print(SECTIONS, "Unknown section id %d: %s", id, desc); |
| |
| count++; |
| if (fl & TRACECMD_SEC_FL_COMPRESS) { |
| if (id == TRACECMD_OPTION_BUFFER || |
| id == TRACECMD_OPTION_BUFFER_TEXT) { |
| do_print(SECTIONS, |
| "\t[Section %2d @ %-16lld\t\"%s\", flags 0x%X, " |
| "%lld compressed bytes]\n", |
| id, soffset, desc, fl, size); |
| } else { |
| if (read_file_number(fd, &csize, 4)) |
| die("cannot read section size"); |
| |
| if (read_file_number(fd, &rsize, 4)) |
| die("cannot read section size"); |
| |
| do_print(SECTIONS, "\t[Section %2d @ %-16lld\t\"%s\", flags 0x%X, " |
| "%d compressed, %d uncompressed]\n", |
| id, soffset, desc, fl, csize, rsize); |
| size -= 8; |
| } |
| } else { |
| do_print(SECTIONS, "\t[Section %2d @ %-16lld\t\"%s\", flags 0x%X, %lld bytes]\n", |
| id, soffset, desc, fl, size); |
| } |
| |
| if (lseek64(fd, size, SEEK_CUR) == (off_t)-1) |
| break; |
| } while (1); |
| |
| if (lseek64(fd, offset, SEEK_SET) == (off_t)-1) |
| die("cannot restore the original file location"); |
| |
| return count; |
| } |
| |
| static void dump_v7_file(int fd) |
| { |
| long long offset; |
| int sections; |
| |
| if (read_file_number(fd, &offset, 8)) |
| die("cannot read offset of the first option section"); |
| |
| get_meta_strings(fd); |
| sections = walk_v7_sections(fd); |
| |
| if (lseek64(fd, offset, SEEK_SET) == (off_t)-1) |
| die("cannot goto options offset %lld", offset); |
| |
| dump_options(fd); |
| dump_sections(fd, sections); |
| } |
| |
| static void free_sections(void) |
| { |
| struct file_section *del; |
| |
| while (sections) { |
| del = sections; |
| sections = sections->next; |
| free(del); |
| } |
| } |
| |
| 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_compress(fd); |
| if (file_version < FILE_VERSION_SECTIONS) |
| dump_v6_file(fd); |
| else |
| dump_v7_file(fd); |
| free_sections(); |
| tep_free(tep); |
| tep = NULL; |
| close(fd); |
| } |
| |
| enum { |
| OPT_sections = 240, |
| OPT_strings = 241, |
| OPT_verbose = 242, |
| OPT_clock = 243, |
| 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}, |
| {"clock", no_argument, NULL, OPT_clock}, |
| {"strings", no_argument, NULL, OPT_strings}, |
| {"sections", no_argument, NULL, OPT_sections}, |
| {"validate", no_argument, NULL, 'v'}, |
| {"help", no_argument, NULL, '?'}, |
| {"verbose", optional_argument, NULL, OPT_verbose}, |
| {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; |
| case OPT_clock: |
| verbosity |= CLOCK; |
| break; |
| case OPT_verbose: |
| if (trace_set_verbose(optarg) < 0) |
| die("invalid verbose level %s", optarg); |
| break; |
| case OPT_strings: |
| verbosity |= STRINGS; |
| break; |
| case OPT_sections: |
| verbosity |= SECTIONS; |
| 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); |
| } |