/* Trace file support in GDB. Copyright (C) 1997-2015 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 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 . */ #include "defs.h" #include "tracefile.h" #include "ctf.h" #include "exec.h" #include "regcache.h" /* Helper macros. */ #define TRACE_WRITE_R_BLOCK(writer, buf, size) \ writer->ops->frame_ops->write_r_block ((writer), (buf), (size)) #define TRACE_WRITE_M_BLOCK_HEADER(writer, addr, size) \ writer->ops->frame_ops->write_m_block_header ((writer), (addr), \ (size)) #define TRACE_WRITE_M_BLOCK_MEMORY(writer, buf, size) \ writer->ops->frame_ops->write_m_block_memory ((writer), (buf), \ (size)) #define TRACE_WRITE_V_BLOCK(writer, num, val) \ writer->ops->frame_ops->write_v_block ((writer), (num), (val)) /* Free trace file writer. */ static void trace_file_writer_xfree (void *arg) { struct trace_file_writer *writer = arg; writer->ops->dtor (writer); xfree (writer); } /* Save tracepoint data to file named FILENAME through WRITER. WRITER determines the trace file format. If TARGET_DOES_SAVE is non-zero, the save is performed on the target, otherwise GDB obtains all trace data and saves it locally. */ static void trace_save (const char *filename, struct trace_file_writer *writer, int target_does_save) { struct trace_status *ts = current_trace_status (); int status; struct uploaded_tp *uploaded_tps = NULL, *utp; struct uploaded_tsv *uploaded_tsvs = NULL, *utsv; ULONGEST offset = 0; #define MAX_TRACE_UPLOAD 2000 gdb_byte buf[MAX_TRACE_UPLOAD]; int written; enum bfd_endian byte_order = gdbarch_byte_order (target_gdbarch ()); /* If the target is to save the data to a file on its own, then just send the command and be done with it. */ if (target_does_save) { if (!writer->ops->target_save (writer, filename)) error (_("Target failed to save trace data to '%s'."), filename); return; } /* Get the trace status first before opening the file, so if the target is losing, we can get out without touching files. */ status = target_get_trace_status (ts); writer->ops->start (writer, filename); writer->ops->write_header (writer); /* Write descriptive info. */ /* Write out the size of a register block. */ writer->ops->write_regblock_type (writer, trace_regblock_size); /* Write out status of the tracing run (aka "tstatus" info). */ writer->ops->write_status (writer, ts); /* Note that we want to upload tracepoints and save those, rather than simply writing out the local ones, because the user may have changed tracepoints in GDB in preparation for a future tracing run, or maybe just mass-deleted all types of breakpoints as part of cleaning up. So as not to contaminate the session, leave the data in its uploaded form, don't make into real tracepoints. */ /* Get trace state variables first, they may be checked when parsing uploaded commands. */ target_upload_trace_state_variables (&uploaded_tsvs); for (utsv = uploaded_tsvs; utsv; utsv = utsv->next) writer->ops->write_uploaded_tsv (writer, utsv); free_uploaded_tsvs (&uploaded_tsvs); target_upload_tracepoints (&uploaded_tps); for (utp = uploaded_tps; utp; utp = utp->next) target_get_tracepoint_status (NULL, utp); for (utp = uploaded_tps; utp; utp = utp->next) writer->ops->write_uploaded_tp (writer, utp); free_uploaded_tps (&uploaded_tps); /* Mark the end of the definition section. */ writer->ops->write_definition_end (writer); /* Get and write the trace data proper. */ while (1) { LONGEST gotten = 0; /* The writer supports writing the contents of trace buffer directly to trace file. Don't parse the contents of trace buffer. */ if (writer->ops->write_trace_buffer != NULL) { /* We ask for big blocks, in the hopes of efficiency, but will take less if the target has packet size limitations or some such. */ gotten = target_get_raw_trace_data (buf, offset, MAX_TRACE_UPLOAD); if (gotten < 0) error (_("Failure to get requested trace buffer data")); /* No more data is forthcoming, we're done. */ if (gotten == 0) break; writer->ops->write_trace_buffer (writer, buf, gotten); offset += gotten; } else { uint16_t tp_num; uint32_t tf_size; /* Parse the trace buffers according to how data are stored in trace buffer in GDBserver. */ gotten = target_get_raw_trace_data (buf, offset, 6); if (gotten == 0) break; /* Read the first six bytes in, which is the tracepoint number and trace frame size. */ tp_num = (uint16_t) extract_unsigned_integer (&buf[0], 2, byte_order); tf_size = (uint32_t) extract_unsigned_integer (&buf[2], 4, byte_order); writer->ops->frame_ops->start (writer, tp_num); gotten = 6; if (tf_size > 0) { unsigned int block; offset += 6; for (block = 0; block < tf_size; ) { gdb_byte block_type; /* We'll fetch one block each time, in order to handle the extremely large 'M' block. We first fetch one byte to get the type of the block. */ gotten = target_get_raw_trace_data (buf, offset, 1); if (gotten < 1) error (_("Failure to get requested trace buffer data")); gotten = 1; block += 1; offset += 1; block_type = buf[0]; switch (block_type) { case 'R': gotten = target_get_raw_trace_data (buf, offset, trace_regblock_size); if (gotten < trace_regblock_size) error (_("Failure to get requested trace" " buffer data")); TRACE_WRITE_R_BLOCK (writer, buf, trace_regblock_size); break; case 'M': { unsigned short mlen; ULONGEST addr; LONGEST t; int j; t = target_get_raw_trace_data (buf,offset, 10); if (t < 10) error (_("Failure to get requested trace" " buffer data")); offset += 10; block += 10; gotten = 0; addr = (ULONGEST) extract_unsigned_integer (buf, 8, byte_order); mlen = (unsigned short) extract_unsigned_integer (&buf[8], 2, byte_order); TRACE_WRITE_M_BLOCK_HEADER (writer, addr, mlen); /* The memory contents in 'M' block may be very large. Fetch the data from the target and write them into file one by one. */ for (j = 0; j < mlen; ) { unsigned int read_length; if (mlen - j > MAX_TRACE_UPLOAD) read_length = MAX_TRACE_UPLOAD; else read_length = mlen - j; t = target_get_raw_trace_data (buf, offset + j, read_length); if (t < read_length) error (_("Failure to get requested" " trace buffer data")); TRACE_WRITE_M_BLOCK_MEMORY (writer, buf, read_length); j += read_length; gotten += read_length; } break; } case 'V': { int vnum; LONGEST val; gotten = target_get_raw_trace_data (buf, offset, 12); if (gotten < 12) error (_("Failure to get requested" " trace buffer data")); vnum = (int) extract_signed_integer (buf, 4, byte_order); val = extract_signed_integer (&buf[4], 8, byte_order); TRACE_WRITE_V_BLOCK (writer, vnum, val); } break; default: error (_("Unknown block type '%c' (0x%x) in" " trace frame"), block_type, block_type); } block += gotten; offset += gotten; } } else offset += gotten; writer->ops->frame_ops->end (writer); } } writer->ops->end (writer); } static void trace_save_command (char *args, int from_tty) { int target_does_save = 0; char **argv; char *filename = NULL; struct cleanup *back_to; int generate_ctf = 0; struct trace_file_writer *writer = NULL; if (args == NULL) error_no_arg (_("file in which to save trace data")); argv = gdb_buildargv (args); back_to = make_cleanup_freeargv (argv); for (; *argv; ++argv) { if (strcmp (*argv, "-r") == 0) target_does_save = 1; if (strcmp (*argv, "-ctf") == 0) generate_ctf = 1; else if (**argv == '-') error (_("unknown option `%s'"), *argv); else filename = *argv; } if (!filename) error_no_arg (_("file in which to save trace data")); if (generate_ctf) writer = ctf_trace_file_writer_new (); else writer = tfile_trace_file_writer_new (); make_cleanup (trace_file_writer_xfree, writer); trace_save (filename, writer, target_does_save); if (from_tty) printf_filtered (_("Trace data saved to %s '%s'.\n"), generate_ctf ? "directory" : "file", filename); do_cleanups (back_to); } /* Save the trace data to file FILENAME of tfile format. */ void trace_save_tfile (const char *filename, int target_does_save) { struct trace_file_writer *writer; struct cleanup *back_to; writer = tfile_trace_file_writer_new (); back_to = make_cleanup (trace_file_writer_xfree, writer); trace_save (filename, writer, target_does_save); do_cleanups (back_to); } /* Save the trace data to dir DIRNAME of ctf format. */ void trace_save_ctf (const char *dirname, int target_does_save) { struct trace_file_writer *writer; struct cleanup *back_to; writer = ctf_trace_file_writer_new (); back_to = make_cleanup (trace_file_writer_xfree, writer); trace_save (dirname, writer, target_does_save); do_cleanups (back_to); } /* Fetch register data from tracefile, shared for both tfile and ctf. */ void tracefile_fetch_registers (struct regcache *regcache, int regno) { struct gdbarch *gdbarch = get_regcache_arch (regcache); int regn, pc_regno; /* We get here if no register data has been found. Mark registers as unavailable. */ for (regn = 0; regn < gdbarch_num_regs (gdbarch); regn++) regcache_raw_supply (regcache, regn, NULL); /* We can often usefully guess that the PC is going to be the same as the address of the tracepoint. */ pc_regno = gdbarch_pc_regnum (gdbarch); /* XXX This guessing code below only works if the PC register isn't a pseudo-register. The value of a pseudo-register isn't stored in the (non-readonly) regcache -- instead it's recomputed (probably from some other cached raw register) whenever the register is read. This guesswork should probably move to some higher layer. */ if (pc_regno < 0 || pc_regno >= gdbarch_num_regs (gdbarch)) return; if (regno == -1 || regno == pc_regno) { struct tracepoint *tp = get_tracepoint (get_tracepoint_number ()); gdb_byte *regs; if (tp && tp->base.loc) { /* But don't try to guess if tracepoint is multi-location... */ if (tp->base.loc->next) { warning (_("Tracepoint %d has multiple " "locations, cannot infer $pc"), tp->base.number); return; } /* ... or does while-stepping. */ if (tp->step_count > 0) { warning (_("Tracepoint %d does while-stepping, " "cannot infer $pc"), tp->base.number); return; } regs = alloca (register_size (gdbarch, pc_regno)); store_unsigned_integer (regs, register_size (gdbarch, pc_regno), gdbarch_byte_order (gdbarch), tp->base.loc->address); regcache_raw_supply (regcache, pc_regno, regs); } } } /* This is the implementation of target_ops method to_has_all_memory. */ static int tracefile_has_all_memory (struct target_ops *ops) { return 1; } /* This is the implementation of target_ops method to_has_memory. */ static int tracefile_has_memory (struct target_ops *ops) { return 1; } /* This is the implementation of target_ops method to_has_stack. The target has a stack when GDB has already selected one trace frame. */ static int tracefile_has_stack (struct target_ops *ops) { return get_traceframe_number () != -1; } /* This is the implementation of target_ops method to_has_registers. The target has registers when GDB has already selected one trace frame. */ static int tracefile_has_registers (struct target_ops *ops) { return get_traceframe_number () != -1; } /* This is the implementation of target_ops method to_thread_alive. tracefile has one thread faked by GDB. */ static int tracefile_thread_alive (struct target_ops *ops, ptid_t ptid) { return 1; } /* This is the implementation of target_ops method to_get_trace_status. The trace status for a file is that tracing can never be run. */ static int tracefile_get_trace_status (struct target_ops *self, struct trace_status *ts) { /* Other bits of trace status were collected as part of opening the trace files, so nothing to do here. */ return -1; } /* Initialize OPS for tracefile related targets. */ void init_tracefile_ops (struct target_ops *ops) { ops->to_stratum = process_stratum; ops->to_get_trace_status = tracefile_get_trace_status; ops->to_has_all_memory = tracefile_has_all_memory; ops->to_has_memory = tracefile_has_memory; ops->to_has_stack = tracefile_has_stack; ops->to_has_registers = tracefile_has_registers; ops->to_thread_alive = tracefile_thread_alive; ops->to_magic = OPS_MAGIC; } extern initialize_file_ftype _initialize_tracefile; void _initialize_tracefile (void) { add_com ("tsave", class_trace, trace_save_command, _("\ Save the trace data to a file.\n\ Use the '-ctf' option to save the data to CTF format.\n\ Use the '-r' option to direct the target to save directly to the file,\n\ using its own filesystem.")); }