| Index: gdb/ctf.c
|
| diff --git a/gdb/ctf.c b/gdb/ctf.c
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b886d67fa18238bbc74425b9a4332cab7f084340
|
| --- /dev/null
|
| +++ b/gdb/ctf.c
|
| @@ -0,0 +1,1853 @@
|
| +/* CTF format support.
|
| +
|
| + Copyright (C) 2012-2013 Free Software Foundation, Inc.
|
| + Contributed by Hui Zhu <hui_zhu@mentor.com>
|
| + Contributed by Yao Qi <yao@codesourcery.com>
|
| +
|
| + 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 <http://www.gnu.org/licenses/>. */
|
| +
|
| +#include "defs.h"
|
| +#include "ctf.h"
|
| +#include "tracepoint.h"
|
| +#include "regcache.h"
|
| +#include <sys/stat.h>
|
| +#include "exec.h"
|
| +#include "completer.h"
|
| +
|
| +#include <ctype.h>
|
| +
|
| +/* GDB saves trace buffers and other information (such as trace
|
| + status) got from the remote target into Common Trace Format (CTF).
|
| + The following types of information are expected to save in CTF:
|
| +
|
| + 1. The length (in bytes) of register cache. Event "register" will
|
| + be defined in metadata, which includes the length.
|
| +
|
| + 2. Trace status. Event "status" is defined in metadata, which
|
| + includes all aspects of trace status.
|
| +
|
| + 3. Uploaded trace variables. Event "tsv_def" is defined in
|
| + metadata, which is about all aspects of a uploaded trace variable.
|
| + Uploaded tracepoints. Event "tp_def" is defined in meta, which
|
| + is about all aspects of an uploaded tracepoint. Note that the
|
| + "sequence" (a CTF type, which is a dynamically-sized array.) is
|
| + used for "actions" "step_actions" and "cmd_strings".
|
| +
|
| + 4. Trace frames. Each trace frame is composed by several blocks
|
| + of different types ('R', 'M', 'V'). One trace frame is saved in
|
| + one CTF packet and the blocks of this frame are saved as events.
|
| + 4.1: The trace frame related information (such as the number of
|
| + tracepoint associated with this frame) is saved in the packet
|
| + context.
|
| + 4.2: The block 'M', 'R' and 'V' are saved in event "memory",
|
| + "register" and "tsv" respectively.
|
| + 4.3: When iterating over events, babeltrace can't tell iterator
|
| + goes to a new packet, so we need a marker or anchor to tell GDB
|
| + that iterator goes into a new packet or frame. We define event
|
| + "frame". */
|
| +
|
| +#define CTF_MAGIC 0xC1FC1FC1
|
| +#define CTF_SAVE_MAJOR 1
|
| +#define CTF_SAVE_MINOR 8
|
| +
|
| +#define CTF_METADATA_NAME "metadata"
|
| +#define CTF_DATASTREAM_NAME "datastream"
|
| +
|
| +/* Reserved event id. */
|
| +
|
| +#define CTF_EVENT_ID_REGISTER 0
|
| +#define CTF_EVENT_ID_TSV 1
|
| +#define CTF_EVENT_ID_MEMORY 2
|
| +#define CTF_EVENT_ID_FRAME 3
|
| +#define CTF_EVENT_ID_STATUS 4
|
| +#define CTF_EVENT_ID_TSV_DEF 5
|
| +#define CTF_EVENT_ID_TP_DEF 6
|
| +
|
| +/* The state kept while writing the CTF datastream file. */
|
| +
|
| +struct trace_write_handler
|
| +{
|
| + /* File descriptor of metadata. */
|
| + FILE *metadata_fd;
|
| + /* File descriptor of traceframes. */
|
| + FILE *datastream_fd;
|
| +
|
| + /* This is the content size of the current packet. */
|
| + size_t content_size;
|
| +
|
| + /* This is the start offset of current packet. */
|
| + long packet_start;
|
| +};
|
| +
|
| +/* Write metadata in FORMAT. */
|
| +
|
| +static void
|
| +ctf_save_write_metadata (struct trace_write_handler *handler,
|
| + const char *format, ...)
|
| +{
|
| + va_list args;
|
| +
|
| + va_start (args, format);
|
| + if (vfprintf (handler->metadata_fd, format, args) < 0)
|
| + error (_("Unable to write metadata file (%s)"),
|
| + safe_strerror (errno));
|
| + va_end (args);
|
| +}
|
| +
|
| +/* Write BUF of length SIZE to datastream file represented by
|
| + HANDLER. */
|
| +
|
| +static int
|
| +ctf_save_write (struct trace_write_handler *handler,
|
| + const gdb_byte *buf, size_t size)
|
| +{
|
| + if (fwrite (buf, size, 1, handler->datastream_fd) != 1)
|
| + error (_("Unable to write file for saving trace data (%s)"),
|
| + safe_strerror (errno));
|
| +
|
| + handler->content_size += size;
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* Write a unsigned 32-bit integer to datastream file represented by
|
| + HANDLER. */
|
| +
|
| +#define ctf_save_write_uint32(HANDLER, U32) \
|
| + ctf_save_write (HANDLER, (gdb_byte *) &U32, 4)
|
| +
|
| +/* Write a signed 32-bit integer to datastream file represented by
|
| + HANDLER. */
|
| +
|
| +#define ctf_save_write_int32(HANDLER, INT32) \
|
| + ctf_save_write ((HANDLER), (gdb_byte *) &(INT32), 4)
|
| +
|
| +/* Set datastream file position. Update HANDLER->content_size
|
| + if WHENCE is SEEK_CUR. */
|
| +
|
| +static int
|
| +ctf_save_fseek (struct trace_write_handler *handler, long offset,
|
| + int whence)
|
| +{
|
| + gdb_assert (whence != SEEK_END);
|
| + gdb_assert (whence != SEEK_SET
|
| + || offset <= handler->content_size + handler->packet_start);
|
| +
|
| + if (fseek (handler->datastream_fd, offset, whence))
|
| + error (_("Unable to seek file for saving trace data (%s)"),
|
| + safe_strerror (errno));
|
| +
|
| + if (whence == SEEK_CUR)
|
| + handler->content_size += offset;
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* Change the datastream file position to align on ALIGN_SIZE,
|
| + and write BUF to datastream file. The size of BUF is SIZE. */
|
| +
|
| +static int
|
| +ctf_save_align_write (struct trace_write_handler *handler,
|
| + const gdb_byte *buf,
|
| + size_t size, size_t align_size)
|
| +{
|
| + long offset
|
| + = (align_up (handler->content_size, align_size)
|
| + - handler->content_size);
|
| +
|
| + if (ctf_save_fseek (handler, offset, SEEK_CUR))
|
| + return -1;
|
| +
|
| + if (ctf_save_write (handler, buf, size))
|
| + return -1;
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +/* Write events to next new packet. */
|
| +
|
| +static void
|
| +ctf_save_next_packet (struct trace_write_handler *handler)
|
| +{
|
| + handler->packet_start += (handler->content_size + 4);
|
| + ctf_save_fseek (handler, handler->packet_start, SEEK_SET);
|
| + handler->content_size = 0;
|
| +}
|
| +
|
| +/* Write the CTF metadata header. */
|
| +
|
| +static void
|
| +ctf_save_metadata_header (struct trace_write_handler *handler)
|
| +{
|
| + const char metadata_fmt[] =
|
| + "\ntrace {\n"
|
| + " major = %u;\n"
|
| + " minor = %u;\n"
|
| + " byte_order = %s;\n" /* be or le */
|
| + " packet.header := struct {\n"
|
| + " uint32_t magic;\n"
|
| + " };\n"
|
| + "};\n"
|
| + "\n"
|
| + "stream {\n"
|
| + " packet.context := struct {\n"
|
| + " uint32_t content_size;\n"
|
| + " uint32_t packet_size;\n"
|
| + " uint16_t tpnum;\n"
|
| + " };\n"
|
| + " event.header := struct {\n"
|
| + " uint32_t id;\n"
|
| + " };\n"
|
| + "};\n";
|
| +
|
| + ctf_save_write_metadata (handler, "/* CTF %d.%d */\n",
|
| + CTF_SAVE_MAJOR, CTF_SAVE_MINOR);
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 8; align = 8; "
|
| + "signed = false; encoding = ascii;}"
|
| + " := ascii;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 8; align = 8; "
|
| + "signed = false; }"
|
| + " := uint8_t;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 16; align = 16;"
|
| + "signed = false; } := uint16_t;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 32; align = 32;"
|
| + "signed = false; } := uint32_t;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 64; align = 64;"
|
| + "signed = false; base = hex;}"
|
| + " := uint64_t;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 32; align = 32;"
|
| + "signed = true; } := int32_t;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias integer { size = 64; align = 64;"
|
| + "signed = true; } := int64_t;\n");
|
| + ctf_save_write_metadata (handler,
|
| + "typealias string { encoding = ascii;"
|
| + " } := chars;\n");
|
| + ctf_save_write_metadata (handler, "\n");
|
| +
|
| + /* Get the byte order of the host and write CTF data in this byte
|
| + order. */
|
| +#if WORDS_BIGENDIAN
|
| +#define HOST_ENDIANNESS "be"
|
| +#else
|
| +#define HOST_ENDIANNESS "le"
|
| +#endif
|
| +
|
| + ctf_save_write_metadata (handler, metadata_fmt,
|
| + CTF_SAVE_MAJOR, CTF_SAVE_MINOR,
|
| + HOST_ENDIANNESS);
|
| + ctf_save_write_metadata (handler, "\n");
|
| +}
|
| +
|
| +/* CTF trace writer. */
|
| +
|
| +struct ctf_trace_file_writer
|
| +{
|
| + struct trace_file_writer base;
|
| +
|
| + /* States related to writing CTF trace file. */
|
| + struct trace_write_handler tcs;
|
| +};
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + dtor. */
|
| +
|
| +static void
|
| +ctf_dtor (struct trace_file_writer *self)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| +
|
| + if (writer->tcs.metadata_fd != NULL)
|
| + fclose (writer->tcs.metadata_fd);
|
| +
|
| + if (writer->tcs.datastream_fd != NULL)
|
| + fclose (writer->tcs.datastream_fd);
|
| +
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + target_save. */
|
| +
|
| +static int
|
| +ctf_target_save (struct trace_file_writer *self,
|
| + const char *dirname)
|
| +{
|
| + /* Don't support save trace file to CTF format in the target. */
|
| + return 0;
|
| +}
|
| +
|
| +#ifdef USE_WIN32API
|
| +#undef mkdir
|
| +#define mkdir(pathname, mode) mkdir (pathname)
|
| +#endif
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + start. It creates the directory DIRNAME, metadata and datastream
|
| + in the directory. */
|
| +
|
| +static void
|
| +ctf_start (struct trace_file_writer *self, const char *dirname)
|
| +{
|
| + char *file_name;
|
| + struct cleanup *old_chain;
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + int i;
|
| + mode_t hmode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH;
|
| +
|
| + /* Create DIRNAME. */
|
| + if (mkdir (dirname, hmode) && errno != EEXIST)
|
| + error (_("Unable to open directory '%s' for saving trace data (%s)"),
|
| + dirname, safe_strerror (errno));
|
| +
|
| + memset (&writer->tcs, '\0', sizeof (writer->tcs));
|
| +
|
| + file_name = xstrprintf ("%s/%s", dirname, CTF_METADATA_NAME);
|
| + old_chain = make_cleanup (xfree, file_name);
|
| +
|
| + writer->tcs.metadata_fd = fopen (file_name, "w");
|
| + if (writer->tcs.metadata_fd == NULL)
|
| + error (_("Unable to open file '%s' for saving trace data (%s)"),
|
| + file_name, safe_strerror (errno));
|
| + do_cleanups (old_chain);
|
| +
|
| + ctf_save_metadata_header (&writer->tcs);
|
| +
|
| + file_name = xstrprintf ("%s/%s", dirname, CTF_DATASTREAM_NAME);
|
| + old_chain = make_cleanup (xfree, file_name);
|
| + writer->tcs.datastream_fd = fopen (file_name, "w");
|
| + if (writer->tcs.datastream_fd == NULL)
|
| + error (_("Unable to open file '%s' for saving trace data (%s)"),
|
| + file_name, safe_strerror (errno));
|
| + do_cleanups (old_chain);
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + write_header. Write the types of events on trace variable and
|
| + frame. */
|
| +
|
| +static void
|
| +ctf_write_header (struct trace_file_writer *self)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| +
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"memory\";\n\tid = %u;\n"
|
| + "\tfields := struct { \n"
|
| + "\t\tuint64_t address;\n"
|
| + "\t\tuint16_t length;\n"
|
| + "\t\tuint8_t contents[length];\n"
|
| + "\t};\n"
|
| + "};\n", CTF_EVENT_ID_MEMORY);
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"tsv\";\n\tid = %u;\n"
|
| + "\tfields := struct { \n"
|
| + "\t\tuint64_t val;\n"
|
| + "\t\tuint32_t num;\n"
|
| + "\t};\n"
|
| + "};\n", CTF_EVENT_ID_TSV);
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"frame\";\n\tid = %u;\n"
|
| + "\tfields := struct { \n"
|
| + "\t};\n"
|
| + "};\n", CTF_EVENT_ID_FRAME);
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"tsv_def\";\n"
|
| + "\tid = %u;\n\tfields := struct { \n"
|
| + "\t\tint64_t initial_value;\n"
|
| + "\t\tint32_t number;\n"
|
| + "\t\tint32_t builtin;\n"
|
| + "\t\tchars name;\n"
|
| + "\t};\n"
|
| + "};\n", CTF_EVENT_ID_TSV_DEF);
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"tp_def\";\n"
|
| + "\tid = %u;\n\tfields := struct { \n"
|
| + "\t\tuint64_t addr;\n"
|
| + "\t\tuint64_t traceframe_usage;\n"
|
| + "\t\tint32_t number;\n"
|
| + "\t\tint32_t enabled;\n"
|
| + "\t\tint32_t step;\n"
|
| + "\t\tint32_t pass;\n"
|
| + "\t\tint32_t hit_count;\n"
|
| + "\t\tint32_t type;\n"
|
| + "\t\tchars cond;\n"
|
| +
|
| + "\t\tuint32_t action_num;\n"
|
| + "\t\tchars actions[action_num];\n"
|
| +
|
| + "\t\tuint32_t step_action_num;\n"
|
| + "\t\tchars step_actions[step_action_num];\n"
|
| +
|
| + "\t\tchars at_string;\n"
|
| + "\t\tchars cond_string;\n"
|
| +
|
| + "\t\tuint32_t cmd_num;\n"
|
| + "\t\tchars cmd_strings[cmd_num];\n"
|
| + "\t};\n"
|
| + "};\n", CTF_EVENT_ID_TP_DEF);
|
| +
|
| + gdb_assert (writer->tcs.content_size == 0);
|
| + gdb_assert (writer->tcs.packet_start == 0);
|
| +
|
| + /* Create a new packet to contain this event. */
|
| + self->ops->frame_ops->start (self, 0);
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + write_regblock_type. Write the type of register event in
|
| + metadata. */
|
| +
|
| +static void
|
| +ctf_write_regblock_type (struct trace_file_writer *self, int size)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| +
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"register\";\n\tid = %u;\n"
|
| + "\tfields := struct { \n"
|
| + "\t\tascii contents[%d];\n"
|
| + "\t};\n"
|
| + "};\n",
|
| + CTF_EVENT_ID_REGISTER, size);
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + write_status. */
|
| +
|
| +static void
|
| +ctf_write_status (struct trace_file_writer *self,
|
| + struct trace_status *ts)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + uint32_t id;
|
| + int32_t int32;
|
| +
|
| + ctf_save_write_metadata (&writer->tcs, "\n");
|
| + ctf_save_write_metadata (&writer->tcs,
|
| + "event {\n\tname = \"status\";\n\tid = %u;\n"
|
| + "\tfields := struct { \n"
|
| + "\t\tint32_t stop_reason;\n"
|
| + "\t\tint32_t stopping_tracepoint;\n"
|
| + "\t\tint32_t traceframe_count;\n"
|
| + "\t\tint32_t traceframes_created;\n"
|
| + "\t\tint32_t buffer_free;\n"
|
| + "\t\tint32_t buffer_size;\n"
|
| + "\t\tint32_t disconnected_tracing;\n"
|
| + "\t\tint32_t circular_buffer;\n"
|
| + "\t};\n"
|
| + "};\n",
|
| + CTF_EVENT_ID_STATUS);
|
| +
|
| + id = CTF_EVENT_ID_STATUS;
|
| + /* Event Id. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &id, 4, 4);
|
| +
|
| + ctf_save_write_int32 (&writer->tcs, ts->stop_reason);
|
| + ctf_save_write_int32 (&writer->tcs, ts->stopping_tracepoint);
|
| + ctf_save_write_int32 (&writer->tcs, ts->traceframe_count);
|
| + ctf_save_write_int32 (&writer->tcs, ts->traceframes_created);
|
| + ctf_save_write_int32 (&writer->tcs, ts->buffer_free);
|
| + ctf_save_write_int32 (&writer->tcs, ts->buffer_size);
|
| + ctf_save_write_int32 (&writer->tcs, ts->disconnected_tracing);
|
| + ctf_save_write_int32 (&writer->tcs, ts->circular_buffer);
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + write_uploaded_tsv. */
|
| +
|
| +static void
|
| +ctf_write_uploaded_tsv (struct trace_file_writer *self,
|
| + struct uploaded_tsv *tsv)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + int32_t int32;
|
| + int64_t int64;
|
| + unsigned int len;
|
| + const gdb_byte zero = 0;
|
| +
|
| + /* Event Id. */
|
| + int32 = CTF_EVENT_ID_TSV_DEF;
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &int32, 4, 4);
|
| +
|
| + /* initial_value */
|
| + int64 = tsv->initial_value;
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &int64, 8, 8);
|
| +
|
| + /* number */
|
| + ctf_save_write_int32 (&writer->tcs, tsv->number);
|
| +
|
| + /* builtin */
|
| + ctf_save_write_int32 (&writer->tcs, tsv->builtin);
|
| +
|
| + /* name */
|
| + if (tsv->name != NULL)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) tsv->name,
|
| + strlen (tsv->name));
|
| + ctf_save_write (&writer->tcs, &zero, 1);
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + write_uploaded_tp. */
|
| +
|
| +static void
|
| +ctf_write_uploaded_tp (struct trace_file_writer *self,
|
| + struct uploaded_tp *tp)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + int32_t int32;
|
| + int64_t int64;
|
| + uint32_t u32;
|
| + const gdb_byte zero = 0;
|
| + int a;
|
| + char *act;
|
| +
|
| + /* Event Id. */
|
| + int32 = CTF_EVENT_ID_TP_DEF;
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &int32, 4, 4);
|
| +
|
| + /* address */
|
| + int64 = tp->addr;
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &int64, 8, 8);
|
| +
|
| + /* traceframe_usage */
|
| + int64 = tp->traceframe_usage;
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &int64, 8, 8);
|
| +
|
| + /* number */
|
| + ctf_save_write_int32 (&writer->tcs, tp->number);
|
| +
|
| + /* enabled */
|
| + ctf_save_write_int32 (&writer->tcs, tp->enabled);
|
| +
|
| + /* step */
|
| + ctf_save_write_int32 (&writer->tcs, tp->step);
|
| +
|
| + /* pass */
|
| + ctf_save_write_int32 (&writer->tcs, tp->pass);
|
| +
|
| + /* hit_count */
|
| + ctf_save_write_int32 (&writer->tcs, tp->hit_count);
|
| +
|
| + /* type */
|
| + ctf_save_write_int32 (&writer->tcs, tp->type);
|
| +
|
| + /* condition */
|
| + if (tp->cond != NULL)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) tp->cond, strlen (tp->cond));
|
| + ctf_save_write (&writer->tcs, &zero, 1);
|
| +
|
| + /* actions */
|
| + u32 = VEC_length (char_ptr, tp->actions);
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &u32, 4, 4);
|
| + for (a = 0; VEC_iterate (char_ptr, tp->actions, a, act); ++a)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) act, strlen (act) + 1);
|
| +
|
| + /* step_actions */
|
| + u32 = VEC_length (char_ptr, tp->step_actions);
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &u32, 4, 4);
|
| + for (a = 0; VEC_iterate (char_ptr, tp->step_actions, a, act); ++a)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) act, strlen (act) + 1);
|
| +
|
| + /* at_string */
|
| + if (tp->at_string != NULL)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) tp->at_string,
|
| + strlen (tp->at_string));
|
| + ctf_save_write (&writer->tcs, &zero, 1);
|
| +
|
| + /* cond_string */
|
| + if (tp->cond_string != NULL)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) tp->cond_string,
|
| + strlen (tp->cond_string));
|
| + ctf_save_write (&writer->tcs, &zero, 1);
|
| +
|
| + /* cmd_strings */
|
| + u32 = VEC_length (char_ptr, tp->cmd_strings);
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &u32, 4, 4);
|
| + for (a = 0; VEC_iterate (char_ptr, tp->cmd_strings, a, act); ++a)
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) act, strlen (act) + 1);
|
| +
|
| +}
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + write_definition_end. */
|
| +
|
| +static void
|
| +ctf_write_definition_end (struct trace_file_writer *self)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| +
|
| + self->ops->frame_ops->end (self);
|
| +}
|
| +
|
| +/* The minimal file size of data stream. It is required by
|
| + babeltrace. */
|
| +
|
| +#define CTF_FILE_MIN_SIZE 4096
|
| +
|
| +/* This is the implementation of trace_file_write_ops method
|
| + end. */
|
| +
|
| +static void
|
| +ctf_end (struct trace_file_writer *self)
|
| +{
|
| + struct ctf_trace_file_writer *writer = (struct ctf_trace_file_writer *) self;
|
| +
|
| + gdb_assert (writer->tcs.content_size == 0);
|
| + /* The babeltrace requires or assumes that the size of datastream
|
| + file is greater than 4096 bytes. If we don't generate enough
|
| + packets and events, create a fake packet which has zero event,
|
| + to use up the space. */
|
| + if (writer->tcs.packet_start < CTF_FILE_MIN_SIZE)
|
| + {
|
| + uint32_t u32;
|
| +
|
| + /* magic. */
|
| + u32 = CTF_MAGIC;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| +
|
| + /* content_size. */
|
| + u32 = 0;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| +
|
| + /* packet_size. */
|
| + u32 = 12;
|
| + if (writer->tcs.packet_start + u32 < CTF_FILE_MIN_SIZE)
|
| + u32 = CTF_FILE_MIN_SIZE - writer->tcs.packet_start;
|
| +
|
| + u32 *= TARGET_CHAR_BIT;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| +
|
| + /* tpnum. */
|
| + u32 = 0;
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) &u32, 2);
|
| +
|
| + /* Enlarge the file to CTF_FILE_MIN_SIZE is it is still less
|
| + than that. */
|
| + if (CTF_FILE_MIN_SIZE
|
| + > (writer->tcs.packet_start + writer->tcs.content_size))
|
| + {
|
| + gdb_byte b = 0;
|
| +
|
| + /* Fake the content size to avoid assertion failure in
|
| + ctf_save_fseek. */
|
| + writer->tcs.content_size = (CTF_FILE_MIN_SIZE
|
| + - 1 - writer->tcs.packet_start);
|
| + ctf_save_fseek (&writer->tcs, CTF_FILE_MIN_SIZE - 1,
|
| + SEEK_SET);
|
| + ctf_save_write (&writer->tcs, &b, 1);
|
| + }
|
| + }
|
| +}
|
| +
|
| +/* This is the implementation of trace_frame_write_ops method
|
| + start. */
|
| +
|
| +static void
|
| +ctf_write_frame_start (struct trace_file_writer *self, uint16_t tpnum)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + uint32_t id = CTF_EVENT_ID_FRAME;
|
| + uint32_t u32;
|
| +
|
| + /* Step 1: Write packet context. */
|
| + /* magic. */
|
| + u32 = CTF_MAGIC;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| + /* content_size and packet_size.. We still don't know the value,
|
| + write it later. */
|
| + ctf_save_fseek (&writer->tcs, 4, SEEK_CUR);
|
| + ctf_save_fseek (&writer->tcs, 4, SEEK_CUR);
|
| + /* Tracepoint number. */
|
| + ctf_save_write (&writer->tcs, (gdb_byte *) &tpnum, 2);
|
| +
|
| + /* Step 2: Write event "frame". */
|
| + /* Event Id. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &id, 4, 4);
|
| +}
|
| +
|
| +/* This is the implementation of trace_frame_write_ops method
|
| + write_r_block. */
|
| +
|
| +static void
|
| +ctf_write_frame_r_block (struct trace_file_writer *self,
|
| + gdb_byte *buf, int32_t size)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + uint32_t id = CTF_EVENT_ID_REGISTER;
|
| +
|
| + /* Event Id. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &id, 4, 4);
|
| +
|
| + /* array contents. */
|
| + ctf_save_align_write (&writer->tcs, buf, size, 1);
|
| +}
|
| +
|
| +/* This is the implementation of trace_frame_write_ops method
|
| + write_m_block_header. */
|
| +
|
| +static void
|
| +ctf_write_frame_m_block_header (struct trace_file_writer *self,
|
| + uint64_t addr, uint16_t length)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + uint32_t event_id = CTF_EVENT_ID_MEMORY;
|
| +
|
| + /* Event Id. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &event_id, 4, 4);
|
| +
|
| + /* Address. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &addr, 8, 8);
|
| +
|
| + /* Length. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &length, 2, 2);
|
| +}
|
| +
|
| +/* This is the implementation of trace_frame_write_ops method
|
| + write_m_block_memory. */
|
| +
|
| +static void
|
| +ctf_write_frame_m_block_memory (struct trace_file_writer *self,
|
| + gdb_byte *buf, uint16_t length)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| +
|
| + /* Contents. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) buf, length, 1);
|
| +}
|
| +
|
| +/* This is the implementation of trace_frame_write_ops method
|
| + write_v_block. */
|
| +
|
| +static void
|
| +ctf_write_frame_v_block (struct trace_file_writer *self,
|
| + int32_t num, uint64_t val)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + uint32_t id = CTF_EVENT_ID_TSV;
|
| +
|
| + /* Event Id. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &id, 4, 4);
|
| +
|
| + /* val. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &val, 8, 8);
|
| + /* num. */
|
| + ctf_save_align_write (&writer->tcs, (gdb_byte *) &num, 4, 4);
|
| +}
|
| +
|
| +/* This is the implementation of trace_frame_write_ops method
|
| + end. */
|
| +
|
| +static void
|
| +ctf_write_frame_end (struct trace_file_writer *self)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = (struct ctf_trace_file_writer *) self;
|
| + uint32_t u32;
|
| + uint32_t t;
|
| +
|
| + /* Write the content size to packet header. */
|
| + ctf_save_fseek (&writer->tcs, writer->tcs.packet_start + 4,
|
| + SEEK_SET);
|
| + u32 = writer->tcs.content_size * TARGET_CHAR_BIT;
|
| +
|
| + t = writer->tcs.content_size;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| +
|
| + /* Write the packet size. */
|
| + u32 += 4 * TARGET_CHAR_BIT;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| +
|
| + writer->tcs.content_size = t;
|
| +
|
| + /* Write zero at the end of the packet. */
|
| + ctf_save_fseek (&writer->tcs, writer->tcs.packet_start + t,
|
| + SEEK_SET);
|
| + u32 = 0;
|
| + ctf_save_write_uint32 (&writer->tcs, u32);
|
| + writer->tcs.content_size = t;
|
| +
|
| + ctf_save_next_packet (&writer->tcs);
|
| +}
|
| +
|
| +/* Operations to write various types of trace frames into CTF
|
| + format. */
|
| +
|
| +static const struct trace_frame_write_ops ctf_write_frame_ops =
|
| +{
|
| + ctf_write_frame_start,
|
| + ctf_write_frame_r_block,
|
| + ctf_write_frame_m_block_header,
|
| + ctf_write_frame_m_block_memory,
|
| + ctf_write_frame_v_block,
|
| + ctf_write_frame_end,
|
| +};
|
| +
|
| +/* Operations to write trace buffers into CTF format. */
|
| +
|
| +static const struct trace_file_write_ops ctf_write_ops =
|
| +{
|
| + ctf_dtor,
|
| + ctf_target_save,
|
| + ctf_start,
|
| + ctf_write_header,
|
| + ctf_write_regblock_type,
|
| + ctf_write_status,
|
| + ctf_write_uploaded_tsv,
|
| + ctf_write_uploaded_tp,
|
| + ctf_write_definition_end,
|
| + NULL,
|
| + &ctf_write_frame_ops,
|
| + ctf_end,
|
| +};
|
| +
|
| +/* Return a trace writer for CTF format. */
|
| +
|
| +struct trace_file_writer *
|
| +ctf_trace_file_writer_new (void)
|
| +{
|
| + struct ctf_trace_file_writer *writer
|
| + = xmalloc (sizeof (struct ctf_trace_file_writer));
|
| +
|
| + writer->base.ops = &ctf_write_ops;
|
| +
|
| + return (struct trace_file_writer *) writer;
|
| +}
|
| +
|
| +#if HAVE_LIBBABELTRACE
|
| +/* Use libbabeltrace to read CTF data. The libbabeltrace provides
|
| + iterator to iterate over each event in CTF data and APIs to get
|
| + details of event and packet, so it is very convenient to use
|
| + libbabeltrace to access events in CTF. */
|
| +
|
| +#include <babeltrace/babeltrace.h>
|
| +#include <babeltrace/ctf/events.h>
|
| +#include <babeltrace/ctf/iterator.h>
|
| +
|
| +/* The struct pointer for current CTF directory. */
|
| +static struct bt_context *ctx = NULL;
|
| +static struct bt_ctf_iter *ctf_iter = NULL;
|
| +/* The position of the first packet containing trace frame. */
|
| +static struct bt_iter_pos *start_pos;
|
| +
|
| +/* The name of CTF directory. */
|
| +static char *trace_dirname;
|
| +
|
| +static struct target_ops ctf_ops;
|
| +
|
| +/* Destroy ctf iterator and context. */
|
| +
|
| +static void
|
| +ctf_destroy (void)
|
| +{
|
| + if (ctf_iter != NULL)
|
| + {
|
| + bt_ctf_iter_destroy (ctf_iter);
|
| + ctf_iter = NULL;
|
| + }
|
| + if (ctx != NULL)
|
| + {
|
| + bt_context_put (ctx);
|
| + ctx = NULL;
|
| + }
|
| +}
|
| +
|
| +/* Open CTF trace data in DIRNAME. */
|
| +
|
| +static void
|
| +ctf_open_dir (char *dirname)
|
| +{
|
| + int ret;
|
| + struct bt_iter_pos begin_pos;
|
| + struct bt_iter_pos *pos;
|
| +
|
| + ctx = bt_context_create ();
|
| + if (ctx == NULL)
|
| + error (_("Unable to create bt_context"));
|
| + ret = bt_context_add_trace (ctx, dirname, "ctf", NULL, NULL, NULL);
|
| + if (ret < 0)
|
| + {
|
| + ctf_destroy ();
|
| + error (_("Unable to use libbabeltrace on directory \"%s\""),
|
| + dirname);
|
| + }
|
| +
|
| + begin_pos.type = BT_SEEK_BEGIN;
|
| + ctf_iter = bt_ctf_iter_create (ctx, &begin_pos, NULL);
|
| + if (ctf_iter == NULL)
|
| + {
|
| + ctf_destroy ();
|
| + error (_("Unable to create bt_iterator"));
|
| + }
|
| +
|
| + /* Iterate over events, and look for an event for register block
|
| + to set trace_regblock_size. */
|
| +
|
| + /* Save the current position. */
|
| + pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (pos->type == BT_SEEK_RESTORE);
|
| +
|
| + while (1)
|
| + {
|
| + const char *name;
|
| + struct bt_ctf_event *event;
|
| +
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| +
|
| + name = bt_ctf_event_name (event);
|
| +
|
| + if (name == NULL)
|
| + break;
|
| + else if (strcmp (name, "register") == 0)
|
| + {
|
| + const struct bt_definition *scope
|
| + = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| + const struct bt_definition *array
|
| + = bt_ctf_get_field (event, scope, "contents");
|
| +
|
| + trace_regblock_size
|
| + = bt_ctf_get_array_len (bt_ctf_get_decl_from_def (array));
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| +}
|
| +
|
| +#define SET_INT32_FIELD(EVENT, SCOPE, VAR, FIELD) \
|
| + (VAR)->FIELD = (int) bt_ctf_get_int64 (bt_ctf_get_field ((EVENT), \
|
| + (SCOPE), \
|
| + #FIELD))
|
| +
|
| +/* EVENT is the "status" event and TS is filled in. */
|
| +
|
| +static void
|
| +ctf_read_status (struct bt_ctf_event *event, struct trace_status *ts)
|
| +{
|
| + const struct bt_definition *scope
|
| + = bt_ctf_get_top_level_scope (event, BT_EVENT_FIELDS);
|
| +
|
| + SET_INT32_FIELD (event, scope, ts, stop_reason);
|
| + SET_INT32_FIELD (event, scope, ts, stopping_tracepoint);
|
| + SET_INT32_FIELD (event, scope, ts, traceframe_count);
|
| + SET_INT32_FIELD (event, scope, ts, traceframes_created);
|
| + SET_INT32_FIELD (event, scope, ts, buffer_free);
|
| + SET_INT32_FIELD (event, scope, ts, buffer_size);
|
| + SET_INT32_FIELD (event, scope, ts, disconnected_tracing);
|
| + SET_INT32_FIELD (event, scope, ts, circular_buffer);
|
| +
|
| + bt_iter_next (bt_ctf_get_iter (ctf_iter));
|
| +}
|
| +
|
| +/* Read the events "tsv_def" one by one, extract its contents and fill
|
| + in the list UPLOADED_TSVS. */
|
| +
|
| +static void
|
| +ctf_read_tsv (struct uploaded_tsv **uploaded_tsvs)
|
| +{
|
| + gdb_assert (ctf_iter != NULL);
|
| +
|
| + while (1)
|
| + {
|
| + struct bt_ctf_event *event;
|
| + const struct bt_definition *scope;
|
| + const struct bt_definition *def;
|
| + uint32_t event_id;
|
| + struct uploaded_tsv *utsv = NULL;
|
| +
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_STREAM_EVENT_HEADER);
|
| + event_id = bt_ctf_get_uint64 (bt_ctf_get_field (event, scope,
|
| + "id"));
|
| + if (event_id != CTF_EVENT_ID_TSV_DEF)
|
| + break;
|
| +
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| +
|
| + def = bt_ctf_get_field (event, scope, "number");
|
| + utsv = get_uploaded_tsv ((int32_t) bt_ctf_get_int64 (def),
|
| + uploaded_tsvs);
|
| +
|
| + def = bt_ctf_get_field (event, scope, "builtin");
|
| + utsv->builtin = (int32_t) bt_ctf_get_int64 (def);
|
| + def = bt_ctf_get_field (event, scope, "initial_value");
|
| + utsv->initial_value = bt_ctf_get_int64 (def);
|
| +
|
| + def = bt_ctf_get_field (event, scope, "name");
|
| + utsv->name = xstrdup (bt_ctf_get_string (def));
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| +}
|
| +
|
| +/* Read the value of element whose index is NUM from CTF and write it
|
| + to the corresponding VAR->ARRAY. */
|
| +
|
| +#define SET_ARRAY_FIELD(EVENT, SCOPE, VAR, NUM, ARRAY) \
|
| + do \
|
| + { \
|
| + uint32_t u32, i; \
|
| + const struct bt_definition *def; \
|
| + \
|
| + u32 = (uint32_t) bt_ctf_get_uint64 (bt_ctf_get_field ((EVENT), \
|
| + (SCOPE), \
|
| + #NUM)); \
|
| + def = bt_ctf_get_field ((EVENT), (SCOPE), #ARRAY); \
|
| + for (i = 0; i < u32; i++) \
|
| + { \
|
| + const struct bt_definition *element \
|
| + = bt_ctf_get_index ((EVENT), def, i); \
|
| + \
|
| + VEC_safe_push (char_ptr, (VAR)->ARRAY, \
|
| + xstrdup (bt_ctf_get_string (element))); \
|
| + } \
|
| + } \
|
| + while (0)
|
| +
|
| +/* Read a string from CTF and set VAR->FIELD. If the length of string
|
| + is zero, set VAR->FIELD to NULL. */
|
| +
|
| +#define SET_STRING_FIELD(EVENT, SCOPE, VAR, FIELD) \
|
| + do \
|
| + { \
|
| + const char *p = bt_ctf_get_string (bt_ctf_get_field ((EVENT), \
|
| + (SCOPE), \
|
| + #FIELD)); \
|
| + \
|
| + if (strlen (p) > 0) \
|
| + (VAR)->FIELD = xstrdup (p); \
|
| + else \
|
| + (VAR)->FIELD = NULL; \
|
| + } \
|
| + while (0)
|
| +
|
| +/* Read the events "tp_def" one by one, extract its contents and fill
|
| + in the list UPLOADED_TPS. */
|
| +
|
| +static void
|
| +ctf_read_tp (struct uploaded_tp **uploaded_tps)
|
| +{
|
| + gdb_assert (ctf_iter != NULL);
|
| +
|
| + while (1)
|
| + {
|
| + struct bt_ctf_event *event;
|
| + const struct bt_definition *scope;
|
| + uint32_t u32;
|
| + int32_t int32;
|
| + uint64_t u64;
|
| + struct uploaded_tp *utp = NULL;
|
| +
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_STREAM_EVENT_HEADER);
|
| + u32 = bt_ctf_get_uint64 (bt_ctf_get_field (event, scope,
|
| + "id"));
|
| + if (u32 != CTF_EVENT_ID_TP_DEF)
|
| + break;
|
| +
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| + int32 = (int32_t) bt_ctf_get_int64 (bt_ctf_get_field (event,
|
| + scope,
|
| + "number"));
|
| + u64 = bt_ctf_get_uint64 (bt_ctf_get_field (event, scope,
|
| + "addr"));
|
| + utp = get_uploaded_tp (int32, u64, uploaded_tps);
|
| +
|
| + SET_INT32_FIELD (event, scope, utp, enabled);
|
| + SET_INT32_FIELD (event, scope, utp, step);
|
| + SET_INT32_FIELD (event, scope, utp, pass);
|
| + SET_INT32_FIELD (event, scope, utp, hit_count);
|
| + SET_INT32_FIELD (event, scope, utp, type);
|
| +
|
| + /* Read 'cmd_strings'. */
|
| + SET_ARRAY_FIELD (event, scope, utp, cmd_num, cmd_strings);
|
| + /* Read 'actions'. */
|
| + SET_ARRAY_FIELD (event, scope, utp, action_num, actions);
|
| + /* Read 'step_actions'. */
|
| + SET_ARRAY_FIELD (event, scope, utp, step_action_num,
|
| + step_actions);
|
| +
|
| + SET_STRING_FIELD(event, scope, utp, at_string);
|
| + SET_STRING_FIELD(event, scope, utp, cond_string);
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method to_open. Open CTF
|
| + trace data, read trace status, trace state variables and tracepoint
|
| + definitions from the first packet. Set the start position at the
|
| + second packet which contains events on trace blocks. */
|
| +
|
| +static void
|
| +ctf_open (char *dirname, int from_tty)
|
| +{
|
| + struct bt_ctf_event *event;
|
| + uint32_t event_id;
|
| + const struct bt_definition *scope;
|
| + struct uploaded_tsv *uploaded_tsvs = NULL;
|
| + struct uploaded_tp *uploaded_tps = NULL;
|
| +
|
| + if (!dirname)
|
| + error (_("No CTF directory specified."));
|
| +
|
| + ctf_open_dir (dirname);
|
| +
|
| + target_preopen (from_tty);
|
| +
|
| + /* Skip the first packet which about the trace status. The first
|
| + event is "frame". */
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| + scope = bt_ctf_get_top_level_scope (event, BT_STREAM_EVENT_HEADER);
|
| + event_id = bt_ctf_get_uint64 (bt_ctf_get_field (event, scope, "id"));
|
| + if (event_id != CTF_EVENT_ID_FRAME)
|
| + error (_("Wrong event id of the first event"));
|
| + /* The second event is "status". */
|
| + bt_iter_next (bt_ctf_get_iter (ctf_iter));
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| + scope = bt_ctf_get_top_level_scope (event, BT_STREAM_EVENT_HEADER);
|
| + event_id = bt_ctf_get_uint64 (bt_ctf_get_field (event, scope, "id"));
|
| + if (event_id != CTF_EVENT_ID_STATUS)
|
| + error (_("Wrong event id of the second event"));
|
| + ctf_read_status (event, current_trace_status ());
|
| +
|
| + ctf_read_tsv (&uploaded_tsvs);
|
| +
|
| + ctf_read_tp (&uploaded_tps);
|
| +
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| + /* EVENT can be NULL if we've already gone to the end of stream of
|
| + events. */
|
| + if (event != NULL)
|
| + {
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_STREAM_EVENT_HEADER);
|
| + event_id = bt_ctf_get_uint64 (bt_ctf_get_field (event,
|
| + scope, "id"));
|
| + if (event_id != CTF_EVENT_ID_FRAME)
|
| + error (_("Wrong event id of the first event of the second packet"));
|
| + }
|
| +
|
| + start_pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (start_pos->type == BT_SEEK_RESTORE);
|
| +
|
| + trace_dirname = xstrdup (dirname);
|
| + push_target (&ctf_ops);
|
| +
|
| + merge_uploaded_trace_state_variables (&uploaded_tsvs);
|
| + merge_uploaded_tracepoints (&uploaded_tps);
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method to_close. Destroy
|
| + CTF iterator and context. */
|
| +
|
| +static void
|
| +ctf_close (void)
|
| +{
|
| + ctf_destroy ();
|
| + xfree (trace_dirname);
|
| + trace_dirname = NULL;
|
| +
|
| + trace_reset_local_state ();
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method to_files_info.
|
| + Print the directory name of CTF trace data. */
|
| +
|
| +static void
|
| +ctf_files_info (struct target_ops *t)
|
| +{
|
| + printf_filtered ("\t`%s'\n", trace_dirname);
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method to_fetch_registers.
|
| + Iterate over events whose name is "register" in current frame,
|
| + extract contents from events, and set REGCACHE with the contents.
|
| + If no matched events are found, mark registers unavailable. */
|
| +
|
| +static void
|
| +ctf_fetch_registers (struct target_ops *ops,
|
| + struct regcache *regcache, int regno)
|
| +{
|
| + struct gdbarch *gdbarch = get_regcache_arch (regcache);
|
| + int offset, regn, regsize, pc_regno;
|
| + gdb_byte *regs = NULL;
|
| + struct bt_ctf_event *event = NULL;
|
| + struct bt_iter_pos *pos;
|
| +
|
| + /* An uninitialized reg size says we're not going to be
|
| + successful at getting register blocks. */
|
| + if (trace_regblock_size == 0)
|
| + return;
|
| +
|
| + gdb_assert (ctf_iter != NULL);
|
| + /* Save the current position. */
|
| + pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (pos->type == BT_SEEK_RESTORE);
|
| +
|
| + while (1)
|
| + {
|
| + const char *name;
|
| + struct bt_ctf_event *event1;
|
| +
|
| + event1 = bt_ctf_iter_read_event (ctf_iter);
|
| +
|
| + name = bt_ctf_event_name (event1);
|
| +
|
| + if (name == NULL || strcmp (name, "frame") == 0)
|
| + break;
|
| + else if (strcmp (name, "register") == 0)
|
| + {
|
| + event = event1;
|
| + break;
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| +
|
| + if (event != NULL)
|
| + {
|
| + const struct bt_definition *scope
|
| + = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| + const struct bt_definition *array
|
| + = bt_ctf_get_field (event, scope, "contents");
|
| +
|
| + regs = (gdb_byte *) bt_ctf_get_char_array (array);
|
| + /* Assume the block is laid out in GDB register number order,
|
| + each register with the size that it has in GDB. */
|
| + offset = 0;
|
| + for (regn = 0; regn < gdbarch_num_regs (gdbarch); regn++)
|
| + {
|
| + regsize = register_size (gdbarch, regn);
|
| + /* Make sure we stay within block bounds. */
|
| + if (offset + regsize >= trace_regblock_size)
|
| + break;
|
| + if (regcache_register_status (regcache, regn) == REG_UNKNOWN)
|
| + {
|
| + if (regno == regn)
|
| + {
|
| + regcache_raw_supply (regcache, regno, regs + offset);
|
| + break;
|
| + }
|
| + else if (regno == -1)
|
| + {
|
| + regcache_raw_supply (regcache, regn, regs + offset);
|
| + }
|
| + }
|
| + offset += regsize;
|
| + }
|
| + return;
|
| + }
|
| +
|
| + regs = alloca (trace_regblock_size);
|
| +
|
| + /* 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);
|
| + if (pc_regno >= 0 && (regno == -1 || regno == pc_regno))
|
| + {
|
| + struct tracepoint *tp = get_tracepoint (get_tracepoint_number ());
|
| +
|
| + if (tp != NULL && tp->base.loc)
|
| + {
|
| + /* But don't try to guess if tracepoint is multi-location... */
|
| + if (tp->base.loc->next != NULL)
|
| + {
|
| + 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;
|
| + }
|
| +
|
| + 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_xfer_partial.
|
| + Iterate over events whose name is "memory" in
|
| + current frame, extract the address and length from events. If
|
| + OFFSET is within the range, read the contents from events to
|
| + READBUF. */
|
| +
|
| +static LONGEST
|
| +ctf_xfer_partial (struct target_ops *ops, enum target_object object,
|
| + const char *annex, gdb_byte *readbuf,
|
| + const gdb_byte *writebuf, ULONGEST offset,
|
| + LONGEST len)
|
| +{
|
| + /* We're only doing regular memory for now. */
|
| + if (object != TARGET_OBJECT_MEMORY)
|
| + return -1;
|
| +
|
| + if (readbuf == NULL)
|
| + error (_("ctf_xfer_partial: trace file is read-only"));
|
| +
|
| + if (get_traceframe_number () != -1)
|
| + {
|
| + struct bt_iter_pos *pos;
|
| + int i = 0;
|
| +
|
| + gdb_assert (ctf_iter != NULL);
|
| + /* Save the current position. */
|
| + pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (pos->type == BT_SEEK_RESTORE);
|
| +
|
| + /* Iterate through the traceframe's blocks, looking for
|
| + memory. */
|
| + while (1)
|
| + {
|
| + ULONGEST amt;
|
| + uint64_t maddr;
|
| + uint16_t mlen;
|
| + enum bfd_endian byte_order
|
| + = gdbarch_byte_order (target_gdbarch ());
|
| + const struct bt_definition *scope;
|
| + const struct bt_definition *def;
|
| + struct bt_ctf_event *event
|
| + = bt_ctf_iter_read_event (ctf_iter);
|
| + const char *name = bt_ctf_event_name (event);
|
| +
|
| + if (strcmp (name, "frame") == 0)
|
| + break;
|
| + else if (strcmp (name, "memory") != 0)
|
| + {
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| +
|
| + continue;
|
| + }
|
| +
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| +
|
| + def = bt_ctf_get_field (event, scope, "address");
|
| + maddr = bt_ctf_get_uint64 (def);
|
| + def = bt_ctf_get_field (event, scope, "length");
|
| + mlen = (uint16_t) bt_ctf_get_uint64 (def);
|
| +
|
| + /* If the block includes the first part of the desired
|
| + range, return as much it has; GDB will re-request the
|
| + remainder, which might be in a different block of this
|
| + trace frame. */
|
| + if (maddr <= offset && offset < (maddr + mlen))
|
| + {
|
| + const struct bt_definition *array
|
| + = bt_ctf_get_field (event, scope, "contents");
|
| + const struct bt_declaration *decl
|
| + = bt_ctf_get_decl_from_def (array);
|
| + gdb_byte *contents;
|
| + int k;
|
| +
|
| + contents = xmalloc (mlen);
|
| +
|
| + for (k = 0; k < mlen; k++)
|
| + {
|
| + const struct bt_definition *element
|
| + = bt_ctf_get_index (event, array, k);
|
| +
|
| + contents[k] = (gdb_byte) bt_ctf_get_uint64 (element);
|
| + }
|
| +
|
| + amt = (maddr + mlen) - offset;
|
| + if (amt > len)
|
| + amt = len;
|
| +
|
| + memcpy (readbuf, &contents[offset - maddr], amt);
|
| +
|
| + xfree (contents);
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| +
|
| + return amt;
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| + }
|
| +
|
| + /* It's unduly pedantic to refuse to look at the executable for
|
| + read-only pieces; so do the equivalent of readonly regions aka
|
| + QTro packet. */
|
| + if (exec_bfd != NULL)
|
| + {
|
| + asection *s;
|
| + bfd_size_type size;
|
| + bfd_vma vma;
|
| +
|
| + for (s = exec_bfd->sections; s; s = s->next)
|
| + {
|
| + if ((s->flags & SEC_LOAD) == 0
|
| + || (s->flags & SEC_READONLY) == 0)
|
| + continue;
|
| +
|
| + vma = s->vma;
|
| + size = bfd_get_section_size (s);
|
| + if (vma <= offset && offset < (vma + size))
|
| + {
|
| + ULONGEST amt;
|
| +
|
| + amt = (vma + size) - offset;
|
| + if (amt > len)
|
| + amt = len;
|
| +
|
| + amt = bfd_get_section_contents (exec_bfd, s,
|
| + readbuf, offset - vma, amt);
|
| + return amt;
|
| + }
|
| + }
|
| + }
|
| +
|
| + /* Indicate failure to find the requested memory block. */
|
| + return -1;
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method
|
| + to_get_trace_state_variable_value.
|
| + Iterate over events whose name is "tsv" in current frame. When the
|
| + trace variable is found, set the value of it to *VAL and return
|
| + true, otherwise return false. */
|
| +
|
| +static int
|
| +ctf_get_trace_state_variable_value (int tsvnum, LONGEST *val)
|
| +{
|
| + struct bt_iter_pos *pos;
|
| + int found = 0;
|
| +
|
| + gdb_assert (ctf_iter != NULL);
|
| + /* Save the current position. */
|
| + pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (pos->type == BT_SEEK_RESTORE);
|
| +
|
| + /* Iterate through the traceframe's blocks, looking for 'V'
|
| + block. */
|
| + while (1)
|
| + {
|
| + struct bt_ctf_event *event
|
| + = bt_ctf_iter_read_event (ctf_iter);
|
| + const char *name = bt_ctf_event_name (event);
|
| +
|
| + if (name == NULL || strcmp (name, "frame") == 0)
|
| + break;
|
| + else if (strcmp (name, "tsv") == 0)
|
| + {
|
| + const struct bt_definition *scope;
|
| + const struct bt_definition *def;
|
| +
|
| + scope = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| +
|
| + def = bt_ctf_get_field (event, scope, "num");
|
| + if (tsvnum == (int32_t) bt_ctf_get_uint64 (def))
|
| + {
|
| + def = bt_ctf_get_field (event, scope, "val");
|
| + *val = bt_ctf_get_uint64 (def);
|
| +
|
| + found = 1;
|
| + }
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| +
|
| + return found;
|
| +}
|
| +
|
| +/* Return the tracepoint number in "frame" event. */
|
| +
|
| +static int
|
| +ctf_get_tpnum_from_frame_event (struct bt_ctf_event *event)
|
| +{
|
| + /* The packet context of events has a field "tpnum". */
|
| + const struct bt_definition *scope
|
| + = bt_ctf_get_top_level_scope (event, BT_STREAM_PACKET_CONTEXT);
|
| + uint64_t tpnum
|
| + = bt_ctf_get_uint64 (bt_ctf_get_field (event, scope, "tpnum"));
|
| +
|
| + return (int) tpnum;
|
| +}
|
| +
|
| +/* Return the address at which the current frame was collected. */
|
| +
|
| +static CORE_ADDR
|
| +ctf_get_traceframe_address (void)
|
| +{
|
| + struct bt_ctf_event *event = NULL;
|
| + struct bt_iter_pos *pos;
|
| + CORE_ADDR addr = 0;
|
| +
|
| + gdb_assert (ctf_iter != NULL);
|
| + pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (pos->type == BT_SEEK_RESTORE);
|
| +
|
| + while (1)
|
| + {
|
| + const char *name;
|
| + struct bt_ctf_event *event1;
|
| +
|
| + event1 = bt_ctf_iter_read_event (ctf_iter);
|
| +
|
| + name = bt_ctf_event_name (event1);
|
| +
|
| + if (name == NULL)
|
| + break;
|
| + else if (strcmp (name, "frame") == 0)
|
| + {
|
| + event = event1;
|
| + break;
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| + if (event != NULL)
|
| + {
|
| + int tpnum = ctf_get_tpnum_from_frame_event (event);
|
| + struct tracepoint *tp
|
| + = get_tracepoint_by_number_on_target (tpnum);
|
| +
|
| + if (tp && tp->base.loc)
|
| + addr = tp->base.loc->address;
|
| + }
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| +
|
| + return addr;
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method to_trace_find.
|
| + Iterate the events whose name is "frame", extract the tracepoint
|
| + number in it. Return traceframe number when matched. */
|
| +
|
| +static int
|
| +ctf_trace_find (enum trace_find_type type, int num,
|
| + CORE_ADDR addr1, CORE_ADDR addr2, int *tpp)
|
| +{
|
| + int ret = -1;
|
| + int tfnum = 0;
|
| + int found = 0;
|
| + struct bt_iter_pos pos;
|
| +
|
| + if (num == -1)
|
| + {
|
| + if (tpp != NULL)
|
| + *tpp = -1;
|
| + return -1;
|
| + }
|
| +
|
| + gdb_assert (ctf_iter != NULL);
|
| + /* Set iterator back to the start. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), start_pos);
|
| +
|
| + while (1)
|
| + {
|
| + int id;
|
| + struct bt_ctf_event *event;
|
| + const char *name;
|
| +
|
| + event = bt_ctf_iter_read_event (ctf_iter);
|
| +
|
| + name = bt_ctf_event_name (event);
|
| +
|
| + if (event == NULL || name == NULL)
|
| + break;
|
| +
|
| + if (strcmp (name, "frame") == 0)
|
| + {
|
| + CORE_ADDR tfaddr;
|
| +
|
| + if (type == tfind_number)
|
| + {
|
| + /* Looking for a specific trace frame. */
|
| + if (tfnum == num)
|
| + found = 1;
|
| + }
|
| + else
|
| + {
|
| + /* Start from the _next_ trace frame. */
|
| + if (tfnum > get_traceframe_number ())
|
| + {
|
| + switch (type)
|
| + {
|
| + case tfind_tp:
|
| + {
|
| + struct tracepoint *tp = get_tracepoint (num);
|
| +
|
| + if (tp != NULL
|
| + && (tp->number_on_target
|
| + == ctf_get_tpnum_from_frame_event (event)))
|
| + found = 1;
|
| + break;
|
| + }
|
| + case tfind_pc:
|
| + tfaddr = ctf_get_traceframe_address ();
|
| + if (tfaddr == addr1)
|
| + found = 1;
|
| + break;
|
| + case tfind_range:
|
| + tfaddr = ctf_get_traceframe_address ();
|
| + if (addr1 <= tfaddr && tfaddr <= addr2)
|
| + found = 1;
|
| + break;
|
| + case tfind_outside:
|
| + tfaddr = ctf_get_traceframe_address ();
|
| + if (!(addr1 <= tfaddr && tfaddr <= addr2))
|
| + found = 1;
|
| + break;
|
| + default:
|
| + internal_error (__FILE__, __LINE__, _("unknown tfind type"));
|
| + }
|
| + }
|
| + }
|
| + if (found)
|
| + {
|
| + if (tpp != NULL)
|
| + *tpp = ctf_get_tpnum_from_frame_event (event);
|
| +
|
| + /* Skip the event "frame". */
|
| + bt_iter_next (bt_ctf_get_iter (ctf_iter));
|
| +
|
| + return tfnum;
|
| + }
|
| + tfnum++;
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| +
|
| + 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
|
| +ctf_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
|
| +ctf_has_registers (struct target_ops *ops)
|
| +{
|
| + return get_traceframe_number () != -1;
|
| +}
|
| +
|
| +/* This is the implementation of target_ops method to_traceframe_info.
|
| + Iterate the events whose name is "memory", in current
|
| + frame, extract memory range information, and return them in
|
| + traceframe_info. */
|
| +
|
| +static struct traceframe_info *
|
| +ctf_traceframe_info (void)
|
| +{
|
| + struct traceframe_info *info = XCNEW (struct traceframe_info);
|
| + const char *name;
|
| + struct bt_iter_pos *pos;
|
| +
|
| + gdb_assert (ctf_iter != NULL);
|
| + /* Save the current position. */
|
| + pos = bt_iter_get_pos (bt_ctf_get_iter (ctf_iter));
|
| + gdb_assert (pos->type == BT_SEEK_RESTORE);
|
| +
|
| + do
|
| + {
|
| + struct bt_ctf_event *event
|
| + = bt_ctf_iter_read_event (ctf_iter);
|
| +
|
| + name = bt_ctf_event_name (event);
|
| +
|
| + if (name == NULL || strcmp (name, "register") == 0
|
| + || strcmp (name, "frame") == 0)
|
| + ;
|
| + else if (strcmp (name, "memory") == 0)
|
| + {
|
| + const struct bt_definition *scope
|
| + = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| + const struct bt_definition *def;
|
| + struct mem_range *r;
|
| +
|
| + r = VEC_safe_push (mem_range_s, info->memory, NULL);
|
| + def = bt_ctf_get_field (event, scope, "address");
|
| + r->start = bt_ctf_get_uint64 (def);
|
| +
|
| + def = bt_ctf_get_field (event, scope, "length");
|
| + r->length = (uint16_t) bt_ctf_get_uint64 (def);
|
| + }
|
| + else if (strcmp (name, "tsv") == 0)
|
| + {
|
| + int vnum;
|
| + const struct bt_definition *scope
|
| + = bt_ctf_get_top_level_scope (event,
|
| + BT_EVENT_FIELDS);
|
| + const struct bt_definition *def;
|
| +
|
| + def = bt_ctf_get_field (event, scope, "num");
|
| + vnum = (int) bt_ctf_get_int64 (def);
|
| + VEC_safe_push (int, info->tvars, vnum);
|
| + }
|
| + else
|
| + {
|
| + warning (_("Unhandled trace block type (%s) "
|
| + "while building trace frame info."),
|
| + name);
|
| + }
|
| +
|
| + if (bt_iter_next (bt_ctf_get_iter (ctf_iter)) < 0)
|
| + break;
|
| + }
|
| + while (name != NULL && strcmp (name, "frame") != 0);
|
| +
|
| + /* Restore the position. */
|
| + bt_iter_set_pos (bt_ctf_get_iter (ctf_iter), pos);
|
| +
|
| + return info;
|
| +}
|
| +
|
| +/* 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
|
| +ctf_get_trace_status (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;
|
| +}
|
| +
|
| +static void
|
| +init_ctf_ops (void)
|
| +{
|
| + memset (&ctf_ops, 0, sizeof (ctf_ops));
|
| +
|
| + ctf_ops.to_shortname = "ctf";
|
| + ctf_ops.to_longname = "CTF file";
|
| + ctf_ops.to_doc = "Use a CTF directory as a target.\n\
|
| +Specify the filename of the CTF directory.";
|
| + ctf_ops.to_open = ctf_open;
|
| + ctf_ops.to_close = ctf_close;
|
| + ctf_ops.to_fetch_registers = ctf_fetch_registers;
|
| + ctf_ops.to_xfer_partial = ctf_xfer_partial;
|
| + ctf_ops.to_files_info = ctf_files_info;
|
| + ctf_ops.to_get_trace_status = ctf_get_trace_status;
|
| + ctf_ops.to_trace_find = ctf_trace_find;
|
| + ctf_ops.to_get_trace_state_variable_value
|
| + = ctf_get_trace_state_variable_value;
|
| + ctf_ops.to_stratum = process_stratum;
|
| + ctf_ops.to_has_stack = ctf_has_stack;
|
| + ctf_ops.to_has_registers = ctf_has_registers;
|
| + ctf_ops.to_traceframe_info = ctf_traceframe_info;
|
| + ctf_ops.to_magic = OPS_MAGIC;
|
| +}
|
| +
|
| +#endif
|
| +
|
| +/* -Wmissing-prototypes */
|
| +
|
| +extern initialize_file_ftype _initialize_ctf;
|
| +
|
| +/* module initialization */
|
| +
|
| +void
|
| +_initialize_ctf (void)
|
| +{
|
| +#if HAVE_LIBBABELTRACE
|
| + init_ctf_ops ();
|
| +
|
| + add_target_with_completer (&ctf_ops, filename_completer);
|
| +#endif
|
| +}
|
|
|