| /* Tracepoint code for remote server for GDB. |
| Copyright (C) 2009-2024 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 <http://www.gnu.org/licenses/>. */ |
| |
| #include "tracepoint.h" |
| #include "gdbthread.h" |
| #include "gdbsupport/rsp-low.h" |
| |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <chrono> |
| #include <inttypes.h> |
| #include "ax.h" |
| #include "tdesc.h" |
| |
| #define IPA_SYM_STRUCT_NAME ipa_sym_addresses |
| #include "gdbsupport/agent.h" |
| |
| #define DEFAULT_TRACE_BUFFER_SIZE 5242880 /* 5*1024*1024 */ |
| |
| /* This file is built for both GDBserver, and the in-process |
| agent (IPA), a shared library that includes a tracing agent that is |
| loaded by the inferior to support fast tracepoints. Fast |
| tracepoints (or more accurately, jump based tracepoints) are |
| implemented by patching the tracepoint location with a jump into a |
| small trampoline function whose job is to save the register state, |
| call the in-process tracing agent, and then execute the original |
| instruction that was under the tracepoint jump (possibly adjusted, |
| if PC-relative, or some such). |
| |
| The current synchronization design is pull based. That means, |
| GDBserver does most of the work, by peeking/poking at the inferior |
| agent's memory directly for downloading tracepoint and associated |
| objects, and for uploading trace frames. Whenever the IPA needs |
| something from GDBserver (trace buffer is full, tracing stopped for |
| some reason, etc.) the IPA calls a corresponding hook function |
| where GDBserver has placed a breakpoint. |
| |
| Each of the agents has its own trace buffer. When browsing the |
| trace frames built from slow and fast tracepoints from GDB (tfind |
| mode), there's no guarantee the user is seeing the trace frames in |
| strict chronological creation order, although, GDBserver tries to |
| keep the order relatively reasonable, by syncing the trace buffers |
| at appropriate times. |
| |
| */ |
| |
| #ifdef IN_PROCESS_AGENT |
| |
| static void trace_vdebug (const char *, ...) ATTRIBUTE_PRINTF (1, 2); |
| |
| static void |
| trace_vdebug (const char *fmt, ...) |
| { |
| char buf[1024]; |
| va_list ap; |
| |
| va_start (ap, fmt); |
| vsprintf (buf, fmt, ap); |
| fprintf (stderr, PROG "/tracepoint: %s\n", buf); |
| va_end (ap); |
| } |
| |
| #define trace_debug(fmt, args...) \ |
| do { \ |
| if (debug_threads) \ |
| trace_vdebug ((fmt), ##args); \ |
| } while (0) |
| |
| #else |
| |
| #define trace_debug(fmt, args...) \ |
| do { \ |
| threads_debug_printf ((fmt), ##args); \ |
| } while (0) |
| |
| #endif |
| |
| /* Prefix exported symbols, for good citizenship. All the symbols |
| that need exporting are defined in this module. Note that all |
| these symbols must be tagged with IP_AGENT_EXPORT_*. */ |
| #ifdef IN_PROCESS_AGENT |
| # define gdb_tp_heap_buffer IPA_SYM_EXPORTED_NAME (gdb_tp_heap_buffer) |
| # define gdb_jump_pad_buffer IPA_SYM_EXPORTED_NAME (gdb_jump_pad_buffer) |
| # define gdb_jump_pad_buffer_end IPA_SYM_EXPORTED_NAME (gdb_jump_pad_buffer_end) |
| # define gdb_trampoline_buffer IPA_SYM_EXPORTED_NAME (gdb_trampoline_buffer) |
| # define gdb_trampoline_buffer_end IPA_SYM_EXPORTED_NAME (gdb_trampoline_buffer_end) |
| # define gdb_trampoline_buffer_error IPA_SYM_EXPORTED_NAME (gdb_trampoline_buffer_error) |
| # define collecting IPA_SYM_EXPORTED_NAME (collecting) |
| # define gdb_collect_ptr IPA_SYM_EXPORTED_NAME (gdb_collect_ptr) |
| # define stop_tracing IPA_SYM_EXPORTED_NAME (stop_tracing) |
| # define flush_trace_buffer IPA_SYM_EXPORTED_NAME (flush_trace_buffer) |
| # define about_to_request_buffer_space IPA_SYM_EXPORTED_NAME (about_to_request_buffer_space) |
| # define trace_buffer_is_full IPA_SYM_EXPORTED_NAME (trace_buffer_is_full) |
| # define stopping_tracepoint IPA_SYM_EXPORTED_NAME (stopping_tracepoint) |
| # define expr_eval_result IPA_SYM_EXPORTED_NAME (expr_eval_result) |
| # define error_tracepoint IPA_SYM_EXPORTED_NAME (error_tracepoint) |
| # define tracepoints IPA_SYM_EXPORTED_NAME (tracepoints) |
| # define tracing IPA_SYM_EXPORTED_NAME (tracing) |
| # define trace_buffer_ctrl IPA_SYM_EXPORTED_NAME (trace_buffer_ctrl) |
| # define trace_buffer_ctrl_curr IPA_SYM_EXPORTED_NAME (trace_buffer_ctrl_curr) |
| # define trace_buffer_lo IPA_SYM_EXPORTED_NAME (trace_buffer_lo) |
| # define trace_buffer_hi IPA_SYM_EXPORTED_NAME (trace_buffer_hi) |
| # define traceframe_read_count IPA_SYM_EXPORTED_NAME (traceframe_read_count) |
| # define traceframe_write_count IPA_SYM_EXPORTED_NAME (traceframe_write_count) |
| # define traceframes_created IPA_SYM_EXPORTED_NAME (traceframes_created) |
| # define trace_state_variables IPA_SYM_EXPORTED_NAME (trace_state_variables) |
| # define get_raw_reg_ptr IPA_SYM_EXPORTED_NAME (get_raw_reg_ptr) |
| # define get_trace_state_variable_value_ptr \ |
| IPA_SYM_EXPORTED_NAME (get_trace_state_variable_value_ptr) |
| # define set_trace_state_variable_value_ptr \ |
| IPA_SYM_EXPORTED_NAME (set_trace_state_variable_value_ptr) |
| # define ust_loaded IPA_SYM_EXPORTED_NAME (ust_loaded) |
| # define helper_thread_id IPA_SYM_EXPORTED_NAME (helper_thread_id) |
| # define cmd_buf IPA_SYM_EXPORTED_NAME (cmd_buf) |
| # define ipa_tdesc_idx IPA_SYM_EXPORTED_NAME (ipa_tdesc_idx) |
| #endif |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Addresses of in-process agent's symbols GDBserver cares about. */ |
| |
| struct ipa_sym_addresses |
| { |
| CORE_ADDR addr_gdb_tp_heap_buffer; |
| CORE_ADDR addr_gdb_jump_pad_buffer; |
| CORE_ADDR addr_gdb_jump_pad_buffer_end; |
| CORE_ADDR addr_gdb_trampoline_buffer; |
| CORE_ADDR addr_gdb_trampoline_buffer_end; |
| CORE_ADDR addr_gdb_trampoline_buffer_error; |
| CORE_ADDR addr_collecting; |
| CORE_ADDR addr_gdb_collect_ptr; |
| CORE_ADDR addr_stop_tracing; |
| CORE_ADDR addr_flush_trace_buffer; |
| CORE_ADDR addr_about_to_request_buffer_space; |
| CORE_ADDR addr_trace_buffer_is_full; |
| CORE_ADDR addr_stopping_tracepoint; |
| CORE_ADDR addr_expr_eval_result; |
| CORE_ADDR addr_error_tracepoint; |
| CORE_ADDR addr_tracepoints; |
| CORE_ADDR addr_tracing; |
| CORE_ADDR addr_trace_buffer_ctrl; |
| CORE_ADDR addr_trace_buffer_ctrl_curr; |
| CORE_ADDR addr_trace_buffer_lo; |
| CORE_ADDR addr_trace_buffer_hi; |
| CORE_ADDR addr_traceframe_read_count; |
| CORE_ADDR addr_traceframe_write_count; |
| CORE_ADDR addr_traceframes_created; |
| CORE_ADDR addr_trace_state_variables; |
| CORE_ADDR addr_get_raw_reg_ptr; |
| CORE_ADDR addr_get_trace_state_variable_value_ptr; |
| CORE_ADDR addr_set_trace_state_variable_value_ptr; |
| CORE_ADDR addr_ust_loaded; |
| CORE_ADDR addr_ipa_tdesc_idx; |
| }; |
| |
| static struct |
| { |
| const char *name; |
| int offset; |
| } symbol_list[] = { |
| IPA_SYM(gdb_tp_heap_buffer), |
| IPA_SYM(gdb_jump_pad_buffer), |
| IPA_SYM(gdb_jump_pad_buffer_end), |
| IPA_SYM(gdb_trampoline_buffer), |
| IPA_SYM(gdb_trampoline_buffer_end), |
| IPA_SYM(gdb_trampoline_buffer_error), |
| IPA_SYM(collecting), |
| IPA_SYM(gdb_collect_ptr), |
| IPA_SYM(stop_tracing), |
| IPA_SYM(flush_trace_buffer), |
| IPA_SYM(about_to_request_buffer_space), |
| IPA_SYM(trace_buffer_is_full), |
| IPA_SYM(stopping_tracepoint), |
| IPA_SYM(expr_eval_result), |
| IPA_SYM(error_tracepoint), |
| IPA_SYM(tracepoints), |
| IPA_SYM(tracing), |
| IPA_SYM(trace_buffer_ctrl), |
| IPA_SYM(trace_buffer_ctrl_curr), |
| IPA_SYM(trace_buffer_lo), |
| IPA_SYM(trace_buffer_hi), |
| IPA_SYM(traceframe_read_count), |
| IPA_SYM(traceframe_write_count), |
| IPA_SYM(traceframes_created), |
| IPA_SYM(trace_state_variables), |
| IPA_SYM(get_raw_reg_ptr), |
| IPA_SYM(get_trace_state_variable_value_ptr), |
| IPA_SYM(set_trace_state_variable_value_ptr), |
| IPA_SYM(ust_loaded), |
| IPA_SYM(ipa_tdesc_idx), |
| }; |
| |
| static struct ipa_sym_addresses ipa_sym_addrs; |
| |
| static int read_inferior_integer (CORE_ADDR symaddr, int *val); |
| |
| /* Returns true if both the in-process agent library and the static |
| tracepoints libraries are loaded in the inferior, and agent has |
| capability on static tracepoints. */ |
| |
| static int |
| in_process_agent_supports_ust (void) |
| { |
| int loaded = 0; |
| |
| if (!agent_loaded_p ()) |
| { |
| warning ("In-process agent not loaded"); |
| return 0; |
| } |
| |
| if (agent_capability_check (AGENT_CAPA_STATIC_TRACE)) |
| { |
| /* Agent understands static tracepoint, then check whether UST is in |
| fact loaded in the inferior. */ |
| if (read_inferior_integer (ipa_sym_addrs.addr_ust_loaded, &loaded)) |
| { |
| warning ("Error reading ust_loaded in lib"); |
| return 0; |
| } |
| |
| return loaded; |
| } |
| else |
| return 0; |
| } |
| |
| static void |
| write_e_ipa_not_loaded (char *buffer) |
| { |
| sprintf (buffer, |
| "E.In-process agent library not loaded in process. " |
| "Fast and static tracepoints unavailable."); |
| } |
| |
| /* Write an error to BUFFER indicating that UST isn't loaded in the |
| inferior. */ |
| |
| static void |
| write_e_ust_not_loaded (char *buffer) |
| { |
| #ifdef HAVE_UST |
| sprintf (buffer, |
| "E.UST library not loaded in process. " |
| "Static tracepoints unavailable."); |
| #else |
| sprintf (buffer, "E.GDBserver was built without static tracepoints support"); |
| #endif |
| } |
| |
| /* If the in-process agent library isn't loaded in the inferior, write |
| an error to BUFFER, and return 1. Otherwise, return 0. */ |
| |
| static int |
| maybe_write_ipa_not_loaded (char *buffer) |
| { |
| if (!agent_loaded_p ()) |
| { |
| write_e_ipa_not_loaded (buffer); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* If the in-process agent library and the ust (static tracepoints) |
| library aren't loaded in the inferior, write an error to BUFFER, |
| and return 1. Otherwise, return 0. */ |
| |
| static int |
| maybe_write_ipa_ust_not_loaded (char *buffer) |
| { |
| if (!agent_loaded_p ()) |
| { |
| write_e_ipa_not_loaded (buffer); |
| return 1; |
| } |
| else if (!in_process_agent_supports_ust ()) |
| { |
| write_e_ust_not_loaded (buffer); |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Cache all future symbols that the tracepoints module might request. |
| We can not request symbols at arbitrary states in the remote |
| protocol, only when the client tells us that new symbols are |
| available. So when we load the in-process library, make sure to |
| check the entire list. */ |
| |
| void |
| tracepoint_look_up_symbols (void) |
| { |
| int i; |
| |
| if (agent_loaded_p ()) |
| return; |
| |
| for (i = 0; i < sizeof (symbol_list) / sizeof (symbol_list[0]); i++) |
| { |
| CORE_ADDR *addrp = |
| (CORE_ADDR *) ((char *) &ipa_sym_addrs + symbol_list[i].offset); |
| |
| if (look_up_one_symbol (symbol_list[i].name, addrp, 1) == 0) |
| { |
| threads_debug_printf ("symbol `%s' not found", symbol_list[i].name); |
| return; |
| } |
| } |
| |
| agent_look_up_symbols (NULL); |
| } |
| |
| #endif |
| |
| /* GDBserver places a breakpoint on the IPA's version (which is a nop) |
| of the "stop_tracing" function. When this breakpoint is hit, |
| tracing stopped in the IPA for some reason. E.g., due to |
| tracepoint reaching the pass count, hitting conditional expression |
| evaluation error, etc. |
| |
| The IPA's trace buffer is never in circular tracing mode: instead, |
| GDBserver's is, and whenever the in-process buffer fills, it calls |
| "flush_trace_buffer", which triggers an internal breakpoint. |
| GDBserver reacts to this breakpoint by pulling the meanwhile |
| collected data. Old frames discarding is always handled on the |
| GDBserver side. */ |
| |
| #ifdef IN_PROCESS_AGENT |
| /* See target.h. */ |
| |
| int |
| read_inferior_memory (CORE_ADDR memaddr, unsigned char *myaddr, int len) |
| { |
| memcpy (myaddr, (void *) (uintptr_t) memaddr, len); |
| return 0; |
| } |
| |
| /* Call this in the functions where GDBserver places a breakpoint, so |
| that the compiler doesn't try to be clever and skip calling the |
| function at all. This is necessary, even if we tell the compiler |
| to not inline said functions. */ |
| |
| #if defined(__GNUC__) |
| # define UNKNOWN_SIDE_EFFECTS() asm ("") |
| #else |
| # define UNKNOWN_SIDE_EFFECTS() do {} while (0) |
| #endif |
| |
| /* This is needed for -Wmissing-declarations. */ |
| IP_AGENT_EXPORT_FUNC void stop_tracing (void); |
| |
| IP_AGENT_EXPORT_FUNC void |
| stop_tracing (void) |
| { |
| /* GDBserver places breakpoint here. */ |
| UNKNOWN_SIDE_EFFECTS(); |
| } |
| |
| /* This is needed for -Wmissing-declarations. */ |
| IP_AGENT_EXPORT_FUNC void flush_trace_buffer (void); |
| |
| IP_AGENT_EXPORT_FUNC void |
| flush_trace_buffer (void) |
| { |
| /* GDBserver places breakpoint here. */ |
| UNKNOWN_SIDE_EFFECTS(); |
| } |
| |
| #endif |
| |
| #ifndef IN_PROCESS_AGENT |
| static int |
| tracepoint_handler (CORE_ADDR address) |
| { |
| trace_debug ("tracepoint_handler: tracepoint at 0x%s hit", |
| paddress (address)); |
| return 0; |
| } |
| |
| /* Breakpoint at "stop_tracing" in the inferior lib. */ |
| static struct breakpoint *stop_tracing_bkpt; |
| static int stop_tracing_handler (CORE_ADDR); |
| |
| /* Breakpoint at "flush_trace_buffer" in the inferior lib. */ |
| static struct breakpoint *flush_trace_buffer_bkpt; |
| static int flush_trace_buffer_handler (CORE_ADDR); |
| |
| static void download_trace_state_variables (void); |
| static void upload_fast_traceframes (void); |
| |
| static int run_inferior_command (char *cmd, int len); |
| |
| static int |
| read_inferior_integer (CORE_ADDR symaddr, int *val) |
| { |
| return read_inferior_memory (symaddr, (unsigned char *) val, |
| sizeof (*val)); |
| } |
| |
| struct tracepoint; |
| static int tracepoint_send_agent (struct tracepoint *tpoint); |
| |
| static int |
| read_inferior_uinteger (CORE_ADDR symaddr, unsigned int *val) |
| { |
| return read_inferior_memory (symaddr, (unsigned char *) val, |
| sizeof (*val)); |
| } |
| |
| static int |
| read_inferior_data_pointer (CORE_ADDR symaddr, CORE_ADDR *val) |
| { |
| void *pval = (void *) (uintptr_t) val; |
| int ret; |
| |
| ret = read_inferior_memory (symaddr, (unsigned char *) &pval, sizeof (pval)); |
| *val = (uintptr_t) pval; |
| return ret; |
| } |
| |
| static int |
| write_inferior_data_pointer (CORE_ADDR symaddr, CORE_ADDR val) |
| { |
| void *pval = (void *) (uintptr_t) val; |
| return target_write_memory (symaddr, |
| (unsigned char *) &pval, sizeof (pval)); |
| } |
| |
| static int |
| write_inferior_integer (CORE_ADDR symaddr, int val) |
| { |
| return target_write_memory (symaddr, (unsigned char *) &val, sizeof (val)); |
| } |
| |
| static int |
| write_inferior_int8 (CORE_ADDR symaddr, int8_t val) |
| { |
| return target_write_memory (symaddr, (unsigned char *) &val, sizeof (val)); |
| } |
| |
| static int |
| write_inferior_uinteger (CORE_ADDR symaddr, unsigned int val) |
| { |
| return target_write_memory (symaddr, (unsigned char *) &val, sizeof (val)); |
| } |
| |
| static CORE_ADDR target_malloc (ULONGEST size); |
| |
| #define COPY_FIELD_TO_BUF(BUF, OBJ, FIELD) \ |
| do { \ |
| memcpy (BUF, &(OBJ)->FIELD, sizeof ((OBJ)->FIELD)); \ |
| BUF += sizeof ((OBJ)->FIELD); \ |
| } while (0) |
| |
| #endif |
| |
| /* Base action. Concrete actions inherit this. */ |
| |
| struct tracepoint_action |
| { |
| char type; |
| }; |
| |
| /* An 'M' (collect memory) action. */ |
| struct collect_memory_action |
| { |
| struct tracepoint_action base; |
| |
| ULONGEST addr; |
| ULONGEST len; |
| int32_t basereg; |
| }; |
| |
| /* An 'R' (collect registers) action. */ |
| |
| struct collect_registers_action |
| { |
| struct tracepoint_action base; |
| }; |
| |
| /* An 'X' (evaluate expression) action. */ |
| |
| struct eval_expr_action |
| { |
| struct tracepoint_action base; |
| |
| struct agent_expr *expr; |
| }; |
| |
| /* An 'L' (collect static trace data) action. */ |
| struct collect_static_trace_data_action |
| { |
| struct tracepoint_action base; |
| }; |
| |
| #ifndef IN_PROCESS_AGENT |
| static CORE_ADDR |
| m_tracepoint_action_download (const struct tracepoint_action *action) |
| { |
| CORE_ADDR ipa_action = target_malloc (sizeof (struct collect_memory_action)); |
| |
| target_write_memory (ipa_action, (unsigned char *) action, |
| sizeof (struct collect_memory_action)); |
| |
| return ipa_action; |
| } |
| static char * |
| m_tracepoint_action_send (char *buffer, const struct tracepoint_action *action) |
| { |
| struct collect_memory_action *maction |
| = (struct collect_memory_action *) action; |
| |
| COPY_FIELD_TO_BUF (buffer, maction, addr); |
| COPY_FIELD_TO_BUF (buffer, maction, len); |
| COPY_FIELD_TO_BUF (buffer, maction, basereg); |
| |
| return buffer; |
| } |
| |
| static CORE_ADDR |
| r_tracepoint_action_download (const struct tracepoint_action *action) |
| { |
| CORE_ADDR ipa_action = target_malloc (sizeof (struct collect_registers_action)); |
| |
| target_write_memory (ipa_action, (unsigned char *) action, |
| sizeof (struct collect_registers_action)); |
| |
| return ipa_action; |
| } |
| |
| static char * |
| r_tracepoint_action_send (char *buffer, const struct tracepoint_action *action) |
| { |
| return buffer; |
| } |
| |
| static CORE_ADDR download_agent_expr (struct agent_expr *expr); |
| |
| static CORE_ADDR |
| x_tracepoint_action_download (const struct tracepoint_action *action) |
| { |
| CORE_ADDR ipa_action = target_malloc (sizeof (struct eval_expr_action)); |
| CORE_ADDR expr; |
| |
| target_write_memory (ipa_action, (unsigned char *) action, |
| sizeof (struct eval_expr_action)); |
| expr = download_agent_expr (((struct eval_expr_action *) action)->expr); |
| write_inferior_data_pointer (ipa_action |
| + offsetof (struct eval_expr_action, expr), |
| expr); |
| |
| return ipa_action; |
| } |
| |
| /* Copy agent expression AEXPR to buffer pointed by P. If AEXPR is NULL, |
| copy 0 to P. Return updated header of buffer. */ |
| |
| static char * |
| agent_expr_send (char *p, const struct agent_expr *aexpr) |
| { |
| /* Copy the length of condition first, and then copy its |
| content. */ |
| if (aexpr == NULL) |
| { |
| memset (p, 0, 4); |
| p += 4; |
| } |
| else |
| { |
| memcpy (p, &aexpr->length, 4); |
| p +=4; |
| |
| memcpy (p, aexpr->bytes, aexpr->length); |
| p += aexpr->length; |
| } |
| return p; |
| } |
| |
| static char * |
| x_tracepoint_action_send ( char *buffer, const struct tracepoint_action *action) |
| { |
| struct eval_expr_action *eaction = (struct eval_expr_action *) action; |
| |
| return agent_expr_send (buffer, eaction->expr); |
| } |
| |
| static CORE_ADDR |
| l_tracepoint_action_download (const struct tracepoint_action *action) |
| { |
| CORE_ADDR ipa_action |
| = target_malloc (sizeof (struct collect_static_trace_data_action)); |
| |
| target_write_memory (ipa_action, (unsigned char *) action, |
| sizeof (struct collect_static_trace_data_action)); |
| |
| return ipa_action; |
| } |
| |
| static char * |
| l_tracepoint_action_send (char *buffer, const struct tracepoint_action *action) |
| { |
| return buffer; |
| } |
| |
| static char * |
| tracepoint_action_send (char *buffer, const struct tracepoint_action *action) |
| { |
| switch (action->type) |
| { |
| case 'M': |
| return m_tracepoint_action_send (buffer, action); |
| case 'R': |
| return r_tracepoint_action_send (buffer, action); |
| case 'X': |
| return x_tracepoint_action_send (buffer, action); |
| case 'L': |
| return l_tracepoint_action_send (buffer, action); |
| } |
| error ("Unknown trace action '%c'.", action->type); |
| } |
| |
| static CORE_ADDR |
| tracepoint_action_download (const struct tracepoint_action *action) |
| { |
| switch (action->type) |
| { |
| case 'M': |
| return m_tracepoint_action_download (action); |
| case 'R': |
| return r_tracepoint_action_download (action); |
| case 'X': |
| return x_tracepoint_action_download (action); |
| case 'L': |
| return l_tracepoint_action_download (action); |
| } |
| error ("Unknown trace action '%c'.", action->type); |
| } |
| #endif |
| |
| /* This structure describes a piece of the source-level definition of |
| the tracepoint. The contents are not interpreted by the target, |
| but preserved verbatim for uploading upon reconnection. */ |
| |
| struct source_string |
| { |
| /* The type of string, such as "cond" for a conditional. */ |
| char *type; |
| |
| /* The source-level string itself. For the sake of target |
| debugging, we store it in plaintext, even though it is always |
| transmitted in hex. */ |
| char *str; |
| |
| /* Link to the next one in the list. We link them in the order |
| received, in case some make up an ordered list of commands or |
| some such. */ |
| struct source_string *next; |
| }; |
| |
| enum tracepoint_type |
| { |
| /* Trap based tracepoint. */ |
| trap_tracepoint, |
| |
| /* A fast tracepoint implemented with a jump instead of a trap. */ |
| fast_tracepoint, |
| |
| /* A static tracepoint, implemented by a program call into a tracing |
| library. */ |
| static_tracepoint |
| }; |
| |
| struct tracepoint_hit_ctx; |
| |
| typedef enum eval_result_type (*condfn) (unsigned char *, |
| ULONGEST *); |
| |
| /* The definition of a tracepoint. */ |
| |
| /* Tracepoints may have multiple locations, each at a different |
| address. This can occur with optimizations, template |
| instantiation, etc. Since the locations may be in different |
| scopes, the conditions and actions may be different for each |
| location. Our target version of tracepoints is more like GDB's |
| notion of "breakpoint locations", but we have almost nothing that |
| is not per-location, so we bother having two kinds of objects. The |
| key consequence is that numbers are not unique, and that it takes |
| both number and address to identify a tracepoint uniquely. */ |
| |
| struct tracepoint |
| { |
| /* The number of the tracepoint, as specified by GDB. Several |
| tracepoint objects here may share a number. */ |
| uint32_t number; |
| |
| /* Address at which the tracepoint is supposed to trigger. Several |
| tracepoints may share an address. */ |
| CORE_ADDR address; |
| |
| /* Tracepoint type. */ |
| enum tracepoint_type type; |
| |
| /* True if the tracepoint is currently enabled. */ |
| int8_t enabled; |
| |
| /* The number of single steps that will be performed after each |
| tracepoint hit. */ |
| uint64_t step_count; |
| |
| /* The number of times the tracepoint may be hit before it will |
| terminate the entire tracing run. */ |
| uint64_t pass_count; |
| |
| /* Pointer to the agent expression that is the tracepoint's |
| conditional, or NULL if the tracepoint is unconditional. */ |
| struct agent_expr *cond; |
| |
| /* The list of actions to take when the tracepoint triggers. */ |
| uint32_t numactions; |
| struct tracepoint_action **actions; |
| |
| /* Count of the times we've hit this tracepoint during the run. |
| Note that while-stepping steps are not counted as "hits". */ |
| uint64_t hit_count; |
| |
| /* Cached sum of the sizes of traceframes created by this point. */ |
| uint64_t traceframe_usage; |
| |
| CORE_ADDR compiled_cond; |
| |
| /* Link to the next tracepoint in the list. */ |
| struct tracepoint *next; |
| |
| #ifndef IN_PROCESS_AGENT |
| /* The list of actions to take when the tracepoint triggers, in |
| string/packet form. */ |
| char **actions_str; |
| |
| /* The collection of strings that describe the tracepoint as it was |
| entered into GDB. These are not used by the target, but are |
| reported back to GDB upon reconnection. */ |
| struct source_string *source_strings; |
| |
| /* The number of bytes displaced by fast tracepoints. It may subsume |
| multiple instructions, for multi-byte fast tracepoints. This |
| field is only valid for fast tracepoints. */ |
| uint32_t orig_size; |
| |
| /* Only for fast tracepoints. */ |
| CORE_ADDR obj_addr_on_target; |
| |
| /* Address range where the original instruction under a fast |
| tracepoint was relocated to. (_end is actually one byte past |
| the end). */ |
| CORE_ADDR adjusted_insn_addr; |
| CORE_ADDR adjusted_insn_addr_end; |
| |
| /* The address range of the piece of the jump pad buffer that was |
| assigned to this fast tracepoint. (_end is actually one byte |
| past the end).*/ |
| CORE_ADDR jump_pad; |
| CORE_ADDR jump_pad_end; |
| |
| /* The address range of the piece of the trampoline buffer that was |
| assigned to this fast tracepoint. (_end is actually one byte |
| past the end). */ |
| CORE_ADDR trampoline; |
| CORE_ADDR trampoline_end; |
| |
| /* The list of actions to take while in a stepping loop. These |
| fields are only valid for patch-based tracepoints. */ |
| int num_step_actions; |
| struct tracepoint_action **step_actions; |
| /* Same, but in string/packet form. */ |
| char **step_actions_str; |
| |
| /* Handle returned by the breakpoint or tracepoint module when we |
| inserted the trap or jump, or hooked into a static tracepoint. |
| NULL if we haven't inserted it yet. */ |
| void *handle; |
| #endif |
| |
| }; |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Given `while-stepping', a thread may be collecting data for more |
| than one tracepoint simultaneously. On the other hand, the same |
| tracepoint with a while-stepping action may be hit by more than one |
| thread simultaneously (but not quite, each thread could be handling |
| a different step). Each thread holds a list of these objects, |
| representing the current step of each while-stepping action being |
| collected. */ |
| |
| struct wstep_state |
| { |
| struct wstep_state *next; |
| |
| /* The tracepoint number. */ |
| int tp_number; |
| /* The tracepoint's address. */ |
| CORE_ADDR tp_address; |
| |
| /* The number of the current step in this 'while-stepping' |
| action. */ |
| long current_step; |
| }; |
| |
| #endif |
| |
| extern "C" { |
| |
| /* The linked list of all tracepoints. Marked explicitly as used as |
| the in-process library doesn't use it for the fast tracepoints |
| support. */ |
| IP_AGENT_EXPORT_VAR struct tracepoint *tracepoints; |
| |
| /* The first tracepoint to exceed its pass count. */ |
| |
| IP_AGENT_EXPORT_VAR struct tracepoint *stopping_tracepoint; |
| |
| /* True if the trace buffer is full or otherwise no longer usable. */ |
| |
| IP_AGENT_EXPORT_VAR int trace_buffer_is_full; |
| |
| /* The first error that occurred during expression evaluation. */ |
| |
| /* Stored as an int to avoid the IPA ABI being dependent on whatever |
| the compiler decides to use for the enum's underlying type. Holds |
| enum eval_result_type values. */ |
| IP_AGENT_EXPORT_VAR int expr_eval_result = expr_eval_no_error; |
| |
| } |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Pointer to the last tracepoint in the list, new tracepoints are |
| linked in at the end. */ |
| |
| static struct tracepoint *last_tracepoint; |
| |
| static const char * const eval_result_names[] = |
| { |
| #define AX_RESULT_TYPE(ENUM,STR) STR, |
| #include "ax-result-types.def" |
| #undef AX_RESULT_TYPE |
| }; |
| |
| #endif |
| |
| /* The tracepoint in which the error occurred. */ |
| |
| extern "C" { |
| IP_AGENT_EXPORT_VAR struct tracepoint *error_tracepoint; |
| } |
| |
| struct trace_state_variable |
| { |
| /* This is the name of the variable as used in GDB. The target |
| doesn't use the name, but needs to have it for saving and |
| reconnection purposes. */ |
| char *name; |
| |
| /* This number identifies the variable uniquely. Numbers may be |
| assigned either by the target (in the case of builtin variables), |
| or by GDB, and are presumed unique during the course of a trace |
| experiment. */ |
| int number; |
| |
| /* The variable's initial value, a 64-bit signed integer always. */ |
| LONGEST initial_value; |
| |
| /* The variable's value, a 64-bit signed integer always. */ |
| LONGEST value; |
| |
| /* Pointer to a getter function, used to supply computed values. */ |
| LONGEST (*getter) (void); |
| |
| /* Link to the next variable. */ |
| struct trace_state_variable *next; |
| }; |
| |
| /* Linked list of all trace state variables. */ |
| |
| #ifdef IN_PROCESS_AGENT |
| static struct trace_state_variable *alloced_trace_state_variables; |
| #endif |
| |
| IP_AGENT_EXPORT_VAR struct trace_state_variable *trace_state_variables; |
| |
| /* The results of tracing go into a fixed-size space known as the |
| "trace buffer". Because usage follows a limited number of |
| patterns, we manage it ourselves rather than with malloc. Basic |
| rules are that we create only one trace frame at a time, each is |
| variable in size, they are never moved once created, and we only |
| discard if we are doing a circular buffer, and then only the oldest |
| ones. Each trace frame includes its own size, so we don't need to |
| link them together, and the trace frame number is relative to the |
| first one, so we don't need to record numbers. A trace frame also |
| records the number of the tracepoint that created it. The data |
| itself is a series of blocks, each introduced by a single character |
| and with a defined format. Each type of block has enough |
| type/length info to allow scanners to jump quickly from one block |
| to the next without reading each byte in the block. */ |
| |
| /* Trace buffer management would be simple - advance a free pointer |
| from beginning to end, then stop - were it not for the circular |
| buffer option, which is a useful way to prevent a trace run from |
| stopping prematurely because the buffer filled up. In the circular |
| case, the location of the first trace frame (trace_buffer_start) |
| moves as old trace frames are discarded. Also, since we grow trace |
| frames incrementally as actions are performed, we wrap around to |
| the beginning of the trace buffer. This is per-block, so each |
| block within a trace frame remains contiguous. Things get messy |
| when the wrapped-around trace frame is the one being discarded; the |
| free space ends up in two parts at opposite ends of the buffer. */ |
| |
| #ifndef ATTR_PACKED |
| # if defined(__GNUC__) |
| # define ATTR_PACKED __attribute__ ((packed)) |
| # else |
| # define ATTR_PACKED /* nothing */ |
| # endif |
| #endif |
| |
| /* The data collected at a tracepoint hit. This object should be as |
| small as possible, since there may be a great many of them. We do |
| not need to keep a frame number, because they are all sequential |
| and there are no deletions; so the Nth frame in the buffer is |
| always frame number N. */ |
| |
| struct traceframe |
| { |
| /* Number of the tracepoint that collected this traceframe. A value |
| of 0 indicates the current end of the trace buffer. We make this |
| a 16-bit field because it's never going to happen that GDB's |
| numbering of tracepoints reaches 32,000. */ |
| int tpnum : 16; |
| |
| /* The size of the data in this trace frame. We limit this to 32 |
| bits, even on a 64-bit target, because it's just implausible that |
| one is validly going to collect 4 gigabytes of data at a single |
| tracepoint hit. */ |
| unsigned int data_size : 32; |
| |
| /* The base of the trace data, which is contiguous from this point. */ |
| unsigned char data[0]; |
| |
| } ATTR_PACKED; |
| |
| /* The size of the EOB marker, in bytes. A traceframe with zeroed |
| fields (and no data) marks the end of trace data. */ |
| #define TRACEFRAME_EOB_MARKER_SIZE offsetof (struct traceframe, data) |
| |
| /* This flag is true if the trace buffer is circular, meaning that |
| when it fills, the oldest trace frames are discarded in order to |
| make room. */ |
| |
| #ifndef IN_PROCESS_AGENT |
| static int circular_trace_buffer; |
| #endif |
| |
| /* Size of the trace buffer. */ |
| |
| static LONGEST trace_buffer_size; |
| |
| extern "C" { |
| |
| /* Pointer to the block of memory that traceframes all go into. */ |
| |
| IP_AGENT_EXPORT_VAR unsigned char *trace_buffer_lo; |
| |
| /* Pointer to the end of the trace buffer, more precisely to the byte |
| after the end of the buffer. */ |
| |
| IP_AGENT_EXPORT_VAR unsigned char *trace_buffer_hi; |
| |
| } |
| |
| /* Control structure holding the read/write/etc. pointers into the |
| trace buffer. We need more than one of these to implement a |
| transaction-like mechanism to guarantees that both GDBserver and the |
| in-process agent can try to change the trace buffer |
| simultaneously. */ |
| |
| struct trace_buffer_control |
| { |
| /* Pointer to the first trace frame in the buffer. In the |
| non-circular case, this is equal to trace_buffer_lo, otherwise it |
| moves around in the buffer. */ |
| unsigned char *start; |
| |
| /* Pointer to the free part of the trace buffer. Note that we clear |
| several bytes at and after this pointer, so that traceframe |
| scans/searches terminate properly. */ |
| unsigned char *free; |
| |
| /* Pointer to the byte after the end of the free part. Note that |
| this may be smaller than trace_buffer_free in the circular case, |
| and means that the free part is in two pieces. Initially it is |
| equal to trace_buffer_hi, then is generally equivalent to |
| trace_buffer_start. */ |
| unsigned char *end_free; |
| |
| /* Pointer to the wraparound. If not equal to trace_buffer_hi, then |
| this is the point at which the trace data breaks, and resumes at |
| trace_buffer_lo. */ |
| unsigned char *wrap; |
| }; |
| |
| /* Same as above, to be used by GDBserver when updating the in-process |
| agent. */ |
| struct ipa_trace_buffer_control |
| { |
| uintptr_t start; |
| uintptr_t free; |
| uintptr_t end_free; |
| uintptr_t wrap; |
| }; |
| |
| |
| /* We have possibly both GDBserver and an inferior thread accessing |
| the same IPA trace buffer memory. The IPA is the producer (tries |
| to put new frames in the buffer), while GDBserver occasionally |
| consumes them, that is, flushes the IPA's buffer into its own |
| buffer. Both sides need to update the trace buffer control |
| pointers (current head, tail, etc.). We can't use a global lock to |
| synchronize the accesses, as otherwise we could deadlock GDBserver |
| (if the thread holding the lock stops for a signal, say). So |
| instead of that, we use a transaction scheme where GDBserver writes |
| always prevail over the IPAs writes, and, we have the IPA detect |
| the commit failure/overwrite, and retry the whole attempt. This is |
| mainly implemented by having a global token object that represents |
| who wrote last to the buffer control structure. We need to freeze |
| any inferior writing to the buffer while GDBserver touches memory, |
| so that the inferior can correctly detect that GDBserver had been |
| there, otherwise, it could mistakingly think its commit was |
| successful; that's implemented by simply having GDBserver set a |
| breakpoint the inferior hits if it is the critical region. |
| |
| There are three cycling trace buffer control structure copies |
| (buffer head, tail, etc.), with the token object including an index |
| indicating which is current live copy. The IPA tentatively builds |
| an updated copy in a non-current control structure, while GDBserver |
| always clobbers the current version directly. The IPA then tries |
| to atomically "commit" its version; if GDBserver clobbered the |
| structure meanwhile, that will fail, and the IPA restarts the |
| allocation process. |
| |
| Listing the step in further detail, we have: |
| |
| In-process agent (producer): |
| |
| - passes by `about_to_request_buffer_space' breakpoint/lock |
| |
| - reads current token, extracts current trace buffer control index, |
| and starts tentatively updating the rightmost one (0->1, 1->2, |
| 2->0). Note that only one inferior thread is executing this code |
| at any given time, due to an outer lock in the jump pads. |
| |
| - updates counters, and tries to commit the token. |
| |
| - passes by second `about_to_request_buffer_space' breakpoint/lock, |
| leaving the sync region. |
| |
| - checks if the update was effective. |
| |
| - if trace buffer was found full, hits flush_trace_buffer |
| breakpoint, and restarts later afterwards. |
| |
| GDBserver (consumer): |
| |
| - sets `about_to_request_buffer_space' breakpoint/lock. |
| |
| - updates the token unconditionally, using the current buffer |
| control index, since it knows that the IP agent always writes to |
| the rightmost, and due to the breakpoint, at most one IP thread |
| can try to update the trace buffer concurrently to GDBserver, so |
| there will be no danger of trace buffer control index wrap making |
| the IPA write to the same index as GDBserver. |
| |
| - flushes the IP agent's trace buffer completely, and updates the |
| current trace buffer control structure. GDBserver *always* wins. |
| |
| - removes the `about_to_request_buffer_space' breakpoint. |
| |
| The token is stored in the `trace_buffer_ctrl_curr' variable. |
| Internally, it's bits are defined as: |
| |
| |-------------+-----+-------------+--------+-------------+--------------| |
| | Bit offsets | 31 | 30 - 20 | 19 | 18-8 | 7-0 | |
| |-------------+-----+-------------+--------+-------------+--------------| |
| | What | GSB | PC (11-bit) | unused | CC (11-bit) | TBCI (8-bit) | |
| |-------------+-----+-------------+--------+-------------+--------------| |
| |
| GSB - GDBserver Stamp Bit |
| PC - Previous Counter |
| CC - Current Counter |
| TBCI - Trace Buffer Control Index |
| |
| |
| An IPA update of `trace_buffer_ctrl_curr' does: |
| |
| - read CC from the current token, save as PC. |
| - updates pointers |
| - atomically tries to write PC+1,CC |
| |
| A GDBserver update of `trace_buffer_ctrl_curr' does: |
| |
| - reads PC and CC from the current token. |
| - updates pointers |
| - writes GSB,PC,CC |
| */ |
| |
| /* These are the bits of `trace_buffer_ctrl_curr' that are reserved |
| for the counters described below. The cleared bits are used to |
| hold the index of the items of the `trace_buffer_ctrl' array that |
| is "current". */ |
| #define GDBSERVER_FLUSH_COUNT_MASK 0xfffffff0 |
| |
| /* `trace_buffer_ctrl_curr' contains two counters. The `previous' |
| counter, and the `current' counter. */ |
| |
| #define GDBSERVER_FLUSH_COUNT_MASK_PREV 0x7ff00000 |
| #define GDBSERVER_FLUSH_COUNT_MASK_CURR 0x0007ff00 |
| |
| /* When GDBserver update the IP agent's `trace_buffer_ctrl_curr', it |
| always stamps this bit as set. */ |
| #define GDBSERVER_UPDATED_FLUSH_COUNT_BIT 0x80000000 |
| |
| #ifdef IN_PROCESS_AGENT |
| IP_AGENT_EXPORT_VAR struct trace_buffer_control trace_buffer_ctrl[3]; |
| IP_AGENT_EXPORT_VAR unsigned int trace_buffer_ctrl_curr; |
| |
| # define TRACE_BUFFER_CTRL_CURR \ |
| (trace_buffer_ctrl_curr & ~GDBSERVER_FLUSH_COUNT_MASK) |
| |
| #else |
| |
| /* The GDBserver side agent only needs one instance of this object, as |
| it doesn't need to sync with itself. Define it as array anyway so |
| that the rest of the code base doesn't need to care for the |
| difference. */ |
| static trace_buffer_control trace_buffer_ctrl[1]; |
| # define TRACE_BUFFER_CTRL_CURR 0 |
| #endif |
| |
| /* These are convenience macros used to access the current trace |
| buffer control in effect. */ |
| #define trace_buffer_start (trace_buffer_ctrl[TRACE_BUFFER_CTRL_CURR].start) |
| #define trace_buffer_free (trace_buffer_ctrl[TRACE_BUFFER_CTRL_CURR].free) |
| #define trace_buffer_end_free \ |
| (trace_buffer_ctrl[TRACE_BUFFER_CTRL_CURR].end_free) |
| #define trace_buffer_wrap (trace_buffer_ctrl[TRACE_BUFFER_CTRL_CURR].wrap) |
| |
| |
| /* Macro that returns a pointer to the first traceframe in the buffer. */ |
| |
| #define FIRST_TRACEFRAME() ((struct traceframe *) trace_buffer_start) |
| |
| /* Macro that returns a pointer to the next traceframe in the buffer. |
| If the computed location is beyond the wraparound point, subtract |
| the offset of the wraparound. */ |
| |
| #define NEXT_TRACEFRAME_1(TF) \ |
| (((unsigned char *) (TF)) + sizeof (struct traceframe) + (TF)->data_size) |
| |
| #define NEXT_TRACEFRAME(TF) \ |
| ((struct traceframe *) (NEXT_TRACEFRAME_1 (TF) \ |
| - ((NEXT_TRACEFRAME_1 (TF) >= trace_buffer_wrap) \ |
| ? (trace_buffer_wrap - trace_buffer_lo) \ |
| : 0))) |
| |
| /* The difference between these counters represents the total number |
| of complete traceframes present in the trace buffer. The IP agent |
| writes to the write count, GDBserver writes to read count. */ |
| |
| IP_AGENT_EXPORT_VAR unsigned int traceframe_write_count; |
| IP_AGENT_EXPORT_VAR unsigned int traceframe_read_count; |
| |
| /* Convenience macro. */ |
| |
| #define traceframe_count \ |
| ((unsigned int) (traceframe_write_count - traceframe_read_count)) |
| |
| /* The count of all traceframes created in the current run, including |
| ones that were discarded to make room. */ |
| |
| IP_AGENT_EXPORT_VAR int traceframes_created; |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Read-only regions are address ranges whose contents don't change, |
| and so can be read from target memory even while looking at a trace |
| frame. Without these, disassembly for instance will likely fail, |
| because the program code is not usually collected into a trace |
| frame. This data structure does not need to be very complicated or |
| particularly efficient, it's only going to be used occasionally, |
| and only by some commands. */ |
| |
| struct readonly_region |
| { |
| /* The bounds of the region. */ |
| CORE_ADDR start, end; |
| |
| /* Link to the next one. */ |
| struct readonly_region *next; |
| }; |
| |
| /* Linked list of readonly regions. This list stays in effect from |
| one tstart to the next. */ |
| |
| static struct readonly_region *readonly_regions; |
| |
| #endif |
| |
| /* The global that controls tracing overall. */ |
| |
| IP_AGENT_EXPORT_VAR int tracing; |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Controls whether tracing should continue after GDB disconnects. */ |
| |
| int disconnected_tracing; |
| |
| /* The reason for the last tracing run to have stopped. We initialize |
| to a distinct string so that GDB can distinguish between "stopped |
| after running" and "stopped because never run" cases. */ |
| |
| static const char *tracing_stop_reason = "tnotrun"; |
| |
| static int tracing_stop_tpnum; |
| |
| /* 64-bit timestamps for the trace run's start and finish, expressed |
| in microseconds from the Unix epoch. */ |
| |
| static LONGEST tracing_start_time; |
| static LONGEST tracing_stop_time; |
| |
| /* The (optional) user-supplied name of the user that started the run. |
| This is an arbitrary string, and may be NULL. */ |
| |
| static char *tracing_user_name; |
| |
| /* Optional user-supplied text describing the run. This is |
| an arbitrary string, and may be NULL. */ |
| |
| static char *tracing_notes; |
| |
| /* Optional user-supplied text explaining a tstop command. This is an |
| arbitrary string, and may be NULL. */ |
| |
| static char *tracing_stop_note; |
| |
| #endif |
| |
| /* Functions local to this file. */ |
| |
| /* Base "class" for tracepoint type specific data to be passed down to |
| collect_data_at_tracepoint. */ |
| struct tracepoint_hit_ctx |
| { |
| enum tracepoint_type type; |
| }; |
| |
| #ifdef IN_PROCESS_AGENT |
| |
| /* Fast/jump tracepoint specific data to be passed down to |
| collect_data_at_tracepoint. */ |
| struct fast_tracepoint_ctx |
| { |
| struct tracepoint_hit_ctx base; |
| |
| struct regcache regcache; |
| int regcache_initted; |
| unsigned char *regspace; |
| |
| unsigned char *regs; |
| struct tracepoint *tpoint; |
| }; |
| |
| /* Static tracepoint specific data to be passed down to |
| collect_data_at_tracepoint. */ |
| struct static_tracepoint_ctx |
| { |
| struct tracepoint_hit_ctx base; |
| |
| /* The regcache corresponding to the registers state at the time of |
| the tracepoint hit. Initialized lazily, from REGS. */ |
| struct regcache regcache; |
| int regcache_initted; |
| |
| /* The buffer space REGCACHE above uses. We use a separate buffer |
| instead of letting the regcache malloc for both signal safety and |
| performance reasons; this is allocated on the stack instead. */ |
| unsigned char *regspace; |
| |
| /* The register buffer as passed on by lttng/ust. */ |
| struct registers *regs; |
| |
| /* The "printf" formatter and the args the user passed to the marker |
| call. We use this to be able to collect "static trace data" |
| ($_sdata). */ |
| const char *fmt; |
| va_list *args; |
| |
| /* The GDB tracepoint matching the probed marker that was "hit". */ |
| struct tracepoint *tpoint; |
| }; |
| |
| #else |
| |
| /* Static tracepoint specific data to be passed down to |
| collect_data_at_tracepoint. */ |
| struct trap_tracepoint_ctx |
| { |
| struct tracepoint_hit_ctx base; |
| |
| struct regcache *regcache; |
| }; |
| |
| #endif |
| |
| #ifndef IN_PROCESS_AGENT |
| static CORE_ADDR traceframe_get_pc (struct traceframe *tframe); |
| static int traceframe_read_tsv (int num, LONGEST *val); |
| #endif |
| |
| static int condition_true_at_tracepoint (struct tracepoint_hit_ctx *ctx, |
| struct tracepoint *tpoint); |
| |
| #ifndef IN_PROCESS_AGENT |
| static void clear_readonly_regions (void); |
| static void clear_installed_tracepoints (void); |
| #endif |
| |
| static void collect_data_at_tracepoint (struct tracepoint_hit_ctx *ctx, |
| CORE_ADDR stop_pc, |
| struct tracepoint *tpoint); |
| #ifndef IN_PROCESS_AGENT |
| static void collect_data_at_step (struct tracepoint_hit_ctx *ctx, |
| CORE_ADDR stop_pc, |
| struct tracepoint *tpoint, int current_step); |
| static void compile_tracepoint_condition (struct tracepoint *tpoint, |
| CORE_ADDR *jump_entry); |
| #endif |
| static void do_action_at_tracepoint (struct tracepoint_hit_ctx *ctx, |
| CORE_ADDR stop_pc, |
| struct tracepoint *tpoint, |
| struct traceframe *tframe, |
| struct tracepoint_action *taction); |
| |
| #ifndef IN_PROCESS_AGENT |
| static struct tracepoint *fast_tracepoint_from_ipa_tpoint_address (CORE_ADDR); |
| |
| static void install_tracepoint (struct tracepoint *, char *own_buf); |
| static void download_tracepoint (struct tracepoint *); |
| static int install_fast_tracepoint (struct tracepoint *, char *errbuf); |
| static void clone_fast_tracepoint (struct tracepoint *to, |
| const struct tracepoint *from); |
| #endif |
| |
| static LONGEST get_timestamp (void); |
| |
| #if defined(__GNUC__) |
| # define memory_barrier() asm volatile ("" : : : "memory") |
| #else |
| # define memory_barrier() do {} while (0) |
| #endif |
| |
| /* We only build the IPA if this builtin is supported, and there are |
| no uses of this in GDBserver itself, so we're safe in defining this |
| unconditionally. */ |
| #define cmpxchg(mem, oldval, newval) \ |
| __sync_val_compare_and_swap (mem, oldval, newval) |
| |
| /* Record that an error occurred during expression evaluation. */ |
| |
| static void |
| record_tracepoint_error (struct tracepoint *tpoint, const char *which, |
| enum eval_result_type rtype) |
| { |
| trace_debug ("Tracepoint %d at %s %s eval reports error %d", |
| tpoint->number, paddress (tpoint->address), which, rtype); |
| |
| #ifdef IN_PROCESS_AGENT |
| /* Only record the first error we get. */ |
| if (cmpxchg (&expr_eval_result, |
| expr_eval_no_error, |
| rtype) != expr_eval_no_error) |
| return; |
| #else |
| if (expr_eval_result != expr_eval_no_error) |
| return; |
| #endif |
| |
| error_tracepoint = tpoint; |
| } |
| |
| /* Trace buffer management. */ |
| |
| static void |
| clear_trace_buffer (void) |
| { |
| trace_buffer_start = trace_buffer_lo; |
| trace_buffer_free = trace_buffer_lo; |
| trace_buffer_end_free = trace_buffer_hi; |
| trace_buffer_wrap = trace_buffer_hi; |
| /* A traceframe with zeroed fields marks the end of trace data. */ |
| ((struct traceframe *) trace_buffer_free)->tpnum = 0; |
| ((struct traceframe *) trace_buffer_free)->data_size = 0; |
| traceframe_read_count = traceframe_write_count = 0; |
| traceframes_created = 0; |
| } |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| static void |
| clear_inferior_trace_buffer (void) |
| { |
| CORE_ADDR ipa_trace_buffer_lo; |
| CORE_ADDR ipa_trace_buffer_hi; |
| struct traceframe ipa_traceframe = { 0 }; |
| struct ipa_trace_buffer_control ipa_trace_buffer_ctrl; |
| |
| read_inferior_data_pointer (ipa_sym_addrs.addr_trace_buffer_lo, |
| &ipa_trace_buffer_lo); |
| read_inferior_data_pointer (ipa_sym_addrs.addr_trace_buffer_hi, |
| &ipa_trace_buffer_hi); |
| |
| ipa_trace_buffer_ctrl.start = ipa_trace_buffer_lo; |
| ipa_trace_buffer_ctrl.free = ipa_trace_buffer_lo; |
| ipa_trace_buffer_ctrl.end_free = ipa_trace_buffer_hi; |
| ipa_trace_buffer_ctrl.wrap = ipa_trace_buffer_hi; |
| |
| /* A traceframe with zeroed fields marks the end of trace data. */ |
| target_write_memory (ipa_sym_addrs.addr_trace_buffer_ctrl, |
| (unsigned char *) &ipa_trace_buffer_ctrl, |
| sizeof (ipa_trace_buffer_ctrl)); |
| |
| write_inferior_uinteger (ipa_sym_addrs.addr_trace_buffer_ctrl_curr, 0); |
| |
| /* A traceframe with zeroed fields marks the end of trace data. */ |
| target_write_memory (ipa_trace_buffer_lo, |
| (unsigned char *) &ipa_traceframe, |
| sizeof (ipa_traceframe)); |
| |
| write_inferior_uinteger (ipa_sym_addrs.addr_traceframe_write_count, 0); |
| write_inferior_uinteger (ipa_sym_addrs.addr_traceframe_read_count, 0); |
| write_inferior_integer (ipa_sym_addrs.addr_traceframes_created, 0); |
| } |
| |
| #endif |
| |
| static void |
| init_trace_buffer (LONGEST bufsize) |
| { |
| size_t alloc_size; |
| |
| trace_buffer_size = bufsize; |
| |
| /* Make sure to internally allocate at least space for the EOB |
| marker. */ |
| alloc_size = (bufsize < TRACEFRAME_EOB_MARKER_SIZE |
| ? TRACEFRAME_EOB_MARKER_SIZE : bufsize); |
| trace_buffer_lo = (unsigned char *) xrealloc (trace_buffer_lo, alloc_size); |
| |
| trace_buffer_hi = trace_buffer_lo + trace_buffer_size; |
| |
| clear_trace_buffer (); |
| } |
| |
| #ifdef IN_PROCESS_AGENT |
| |
| /* This is needed for -Wmissing-declarations. */ |
| IP_AGENT_EXPORT_FUNC void about_to_request_buffer_space (void); |
| |
| IP_AGENT_EXPORT_FUNC void |
| about_to_request_buffer_space (void) |
| { |
| /* GDBserver places breakpoint here while it goes about to flush |
| data at random times. */ |
| UNKNOWN_SIDE_EFFECTS(); |
| } |
| |
| #endif |
| |
| /* Carve out a piece of the trace buffer, returning NULL in case of |
| failure. */ |
| |
| static void * |
| trace_buffer_alloc (size_t amt) |
| { |
| unsigned char *rslt; |
| struct trace_buffer_control *tbctrl; |
| unsigned int curr; |
| #ifdef IN_PROCESS_AGENT |
| unsigned int prev, prev_filtered; |
| unsigned int commit_count; |
| unsigned int commit; |
| unsigned int readout; |
| #else |
| struct traceframe *oldest; |
| unsigned char *new_start; |
| #endif |
| |
| trace_debug ("Want to allocate %ld+%ld bytes in trace buffer", |
| (long) amt, (long) sizeof (struct traceframe)); |
| |
| /* Account for the EOB marker. */ |
| amt += TRACEFRAME_EOB_MARKER_SIZE; |
| |
| #ifdef IN_PROCESS_AGENT |
| again: |
| memory_barrier (); |
| |
| /* Read the current token and extract the index to try to write to, |
| storing it in CURR. */ |
| prev = trace_buffer_ctrl_curr; |
| prev_filtered = prev & ~GDBSERVER_FLUSH_COUNT_MASK; |
| curr = prev_filtered + 1; |
| if (curr > 2) |
| curr = 0; |
| |
| about_to_request_buffer_space (); |
| |
| /* Start out with a copy of the current state. GDBserver may be |
| midway writing to the PREV_FILTERED TBC, but, that's OK, we won't |
| be able to commit anyway if that happens. */ |
| trace_buffer_ctrl[curr] |
| = trace_buffer_ctrl[prev_filtered]; |
| trace_debug ("trying curr=%u", curr); |
| #else |
| /* The GDBserver's agent doesn't need all that syncing, and always |
| updates TCB 0 (there's only one, mind you). */ |
| curr = 0; |
| #endif |
| tbctrl = &trace_buffer_ctrl[curr]; |
| |
| /* Offsets are easier to grok for debugging than raw addresses, |
| especially for the small trace buffer sizes that are useful for |
| testing. */ |
| trace_debug ("Trace buffer [%d] start=%d free=%d endfree=%d wrap=%d hi=%d", |
| curr, |
| (int) (tbctrl->start - trace_buffer_lo), |
| (int) (tbctrl->free - trace_buffer_lo), |
| (int) (tbctrl->end_free - trace_buffer_lo), |
| (int) (tbctrl->wrap - trace_buffer_lo), |
| (int) (trace_buffer_hi - trace_buffer_lo)); |
| |
| /* The algorithm here is to keep trying to get a contiguous block of |
| the requested size, possibly discarding older traceframes to free |
| up space. Since free space might come in one or two pieces, |
| depending on whether discarded traceframes wrapped around at the |
| high end of the buffer, we test both pieces after each |
| discard. */ |
| while (1) |
| { |
| /* First, if we have two free parts, try the upper one first. */ |
| if (tbctrl->end_free < tbctrl->free) |
| { |
| if (tbctrl->free + amt <= trace_buffer_hi) |
| /* We have enough in the upper part. */ |
| break; |
| else |
| { |
| /* Our high part of free space wasn't enough. Give up |
| on it for now, set wraparound. We will recover the |
| space later, if/when the wrapped-around traceframe is |
| discarded. */ |
| trace_debug ("Upper part too small, setting wraparound"); |
| tbctrl->wrap = tbctrl->free; |
| tbctrl->free = trace_buffer_lo; |
| } |
| } |
| |
| /* The normal case. */ |
| if (tbctrl->free + amt <= tbctrl->end_free) |
| break; |
| |
| #ifdef IN_PROCESS_AGENT |
| /* The IP Agent's buffer is always circular. It isn't used |
| currently, but `circular_trace_buffer' could represent |
| GDBserver's mode. If we didn't find space, ask GDBserver to |
| flush. */ |
| |
| flush_trace_buffer (); |
| memory_barrier (); |
| if (tracing) |
| { |
| trace_debug ("gdbserver flushed buffer, retrying"); |
| goto again; |
| } |
| |
| /* GDBserver cancelled the tracing. Bail out as well. */ |
| return NULL; |
| #else |
| /* If we're here, then neither part is big enough, and |
| non-circular trace buffers are now full. */ |
| if (!circular_trace_buffer) |
| { |
| trace_debug ("Not enough space in the trace buffer"); |
| return NULL; |
| } |
| |
| trace_debug ("Need more space in the trace buffer"); |
| |
| /* If we have a circular buffer, we can try discarding the |
| oldest traceframe and see if that helps. */ |
| oldest = FIRST_TRACEFRAME (); |
| if (oldest->tpnum == 0) |
| { |
| /* Not good; we have no traceframes to free. Perhaps we're |
| asking for a block that is larger than the buffer? In |
| any case, give up. */ |
| trace_debug ("No traceframes to discard"); |
| return NULL; |
| } |
| |
| /* We don't run this code in the in-process agent currently. |
| E.g., we could leave the in-process agent in autonomous |
| circular mode if we only have fast tracepoints. If we do |
| that, then this bit becomes racy with GDBserver, which also |
| writes to this counter. */ |
| --traceframe_write_count; |
| |
| new_start = (unsigned char *) NEXT_TRACEFRAME (oldest); |
| /* If we freed the traceframe that wrapped around, go back |
| to the non-wrap case. */ |
| if (new_start < tbctrl->start) |
| { |
| trace_debug ("Discarding past the wraparound"); |
| tbctrl->wrap = trace_buffer_hi; |
| } |
| tbctrl->start = new_start; |
| tbctrl->end_free = tbctrl->start; |
| |
| trace_debug ("Discarded a traceframe\n" |
| "Trace buffer [%d], start=%d free=%d " |
| "endfree=%d wrap=%d hi=%d", |
| curr, |
| (int) (tbctrl->start - trace_buffer_lo), |
| (int) (tbctrl->free - trace_buffer_lo), |
| (int) (tbctrl->end_free - trace_buffer_lo), |
| (int) (tbctrl->wrap - trace_buffer_lo), |
| (int) (trace_buffer_hi - trace_buffer_lo)); |
| |
| /* Now go back around the loop. The discard might have resulted |
| in either one or two pieces of free space, so we want to try |
| both before freeing any more traceframes. */ |
| #endif |
| } |
| |
| /* If we get here, we know we can provide the asked-for space. */ |
| |
| rslt = tbctrl->free; |
| |
| /* Adjust the request back down, now that we know we have space for |
| the marker, but don't commit to AMT yet, we may still need to |
| restart the operation if GDBserver touches the trace buffer |
| (obviously only important in the in-process agent's version). */ |
| tbctrl->free += (amt - sizeof (struct traceframe)); |
| |
| /* Or not. If GDBserver changed the trace buffer behind our back, |
| we get to restart a new allocation attempt. */ |
| |
| #ifdef IN_PROCESS_AGENT |
| /* Build the tentative token. */ |
| commit_count = (((prev & GDBSERVER_FLUSH_COUNT_MASK_CURR) + 0x100) |
| & GDBSERVER_FLUSH_COUNT_MASK_CURR); |
| commit = (((prev & GDBSERVER_FLUSH_COUNT_MASK_CURR) << 12) |
| | commit_count |
| | curr); |
| |
| /* Try to commit it. */ |
| readout = cmpxchg (&trace_buffer_ctrl_curr, prev, commit); |
| if (readout != prev) |
| { |
| trace_debug ("GDBserver has touched the trace buffer, restarting." |
| " (prev=%08x, commit=%08x, readout=%08x)", |
| prev, commit, readout); |
| goto again; |
| } |
| |
| /* Hold your horses here. Even if that change was committed, |
| GDBserver could come in, and clobber it. We need to hold to be |
| able to tell if GDBserver clobbers before or after we committed |
| the change. Whenever GDBserver goes about touching the IPA |
| buffer, it sets a breakpoint in this routine, so we have a sync |
| point here. */ |
| about_to_request_buffer_space (); |
| |
| /* Check if the change has been effective, even if GDBserver stopped |
| us at the breakpoint. */ |
| |
| { |
| unsigned int refetch; |
| |
| memory_barrier (); |
| |
| refetch = trace_buffer_ctrl_curr; |
| |
| if (refetch == commit |
| || ((refetch & GDBSERVER_FLUSH_COUNT_MASK_PREV) >> 12) == commit_count) |
| { |
| /* effective */ |
| trace_debug ("change is effective: (prev=%08x, commit=%08x, " |
| "readout=%08x, refetch=%08x)", |
| prev, commit, readout, refetch); |
| } |
| else |
| { |
| trace_debug ("GDBserver has touched the trace buffer, not effective." |
| " (prev=%08x, commit=%08x, readout=%08x, refetch=%08x)", |
| prev, commit, readout, refetch); |
| goto again; |
| } |
| } |
| #endif |
| |
| /* We have a new piece of the trace buffer. Hurray! */ |
| |
| /* Add an EOB marker just past this allocation. */ |
| ((struct traceframe *) tbctrl->free)->tpnum = 0; |
| ((struct traceframe *) tbctrl->free)->data_size = 0; |
| |
| /* Adjust the request back down, now that we know we have space for |
| the marker. */ |
| amt -= sizeof (struct traceframe); |
| |
| if (debug_threads) |
| { |
| trace_debug ("Allocated %d bytes", (int) amt); |
| trace_debug ("Trace buffer [%d] start=%d free=%d " |
| "endfree=%d wrap=%d hi=%d", |
| curr, |
| (int) (tbctrl->start - trace_buffer_lo), |
| (int) (tbctrl->free - trace_buffer_lo), |
| (int) (tbctrl->end_free - trace_buffer_lo), |
| (int) (tbctrl->wrap - trace_buffer_lo), |
| (int) (trace_buffer_hi - trace_buffer_lo)); |
| } |
| |
| return rslt; |
| } |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Return the total free space. This is not necessarily the largest |
| block we can allocate, because of the two-part case. */ |
| |
| static int |
| free_space (void) |
| { |
| if (trace_buffer_free <= trace_buffer_end_free) |
| return trace_buffer_end_free - trace_buffer_free; |
| else |
| return ((trace_buffer_end_free - trace_buffer_lo) |
| + (trace_buffer_hi - trace_buffer_free)); |
| } |
| |
| /* An 'S' in continuation packets indicates remainder are for |
| while-stepping. */ |
| |
| static int seen_step_action_flag; |
| |
| /* Create a tracepoint (location) with given number and address. Add this |
| new tracepoint to list and sort this list. */ |
| |
| static struct tracepoint * |
| add_tracepoint (int num, CORE_ADDR addr) |
| { |
| struct tracepoint *tpoint, **tp_next; |
| |
| tpoint = XNEW (struct tracepoint); |
| tpoint->number = num; |
| tpoint->address = addr; |
| tpoint->numactions = 0; |
| tpoint->actions = NULL; |
| tpoint->actions_str = NULL; |
| tpoint->cond = NULL; |
| tpoint->num_step_actions = 0; |
| tpoint->step_actions = NULL; |
| tpoint->step_actions_str = NULL; |
| /* Start all off as regular (slow) tracepoints. */ |
| tpoint->type = trap_tracepoint; |
| tpoint->orig_size = -1; |
| tpoint->source_strings = NULL; |
| tpoint->compiled_cond = 0; |
| tpoint->handle = NULL; |
| tpoint->next = NULL; |
| |
| /* Find a place to insert this tracepoint into list in order to keep |
| the tracepoint list still in the ascending order. There may be |
| multiple tracepoints at the same address as TPOINT's, and this |
| guarantees TPOINT is inserted after all the tracepoints which are |
| set at the same address. For example, fast tracepoints A, B, C are |
| set at the same address, and D is to be insert at the same place as |
| well, |
| |
| -->| A |--> | B |-->| C |->... |
| |
| One jump pad was created for tracepoint A, B, and C, and the target |
| address of A is referenced/used in jump pad. So jump pad will let |
| inferior jump to A. If D is inserted in front of A, like this, |
| |
| -->| D |-->| A |--> | B |-->| C |->... |
| |
| without updating jump pad, D is not reachable during collect, which |
| is wrong. As we can see, the order of B, C and D doesn't matter, but |
| A should always be the `first' one. */ |
| for (tp_next = &tracepoints; |
| (*tp_next) != NULL && (*tp_next)->address <= tpoint->address; |
| tp_next = &(*tp_next)->next) |
| ; |
| tpoint->next = *tp_next; |
| *tp_next = tpoint; |
| last_tracepoint = tpoint; |
| |
| seen_step_action_flag = 0; |
| |
| return tpoint; |
| } |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Return the tracepoint with the given number and address, or NULL. */ |
| |
| static struct tracepoint * |
| find_tracepoint (int id, CORE_ADDR addr) |
| { |
| struct tracepoint *tpoint; |
| |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| if (tpoint->number == id && tpoint->address == addr) |
| return tpoint; |
| |
| return NULL; |
| } |
| |
| /* Remove TPOINT from global list. */ |
| |
| static void |
| remove_tracepoint (struct tracepoint *tpoint) |
| { |
| struct tracepoint *tp, *tp_prev; |
| |
| for (tp = tracepoints, tp_prev = NULL; tp && tp != tpoint; |
| tp_prev = tp, tp = tp->next) |
| ; |
| |
| if (tp) |
| { |
| if (tp_prev) |
| tp_prev->next = tp->next; |
| else |
| tracepoints = tp->next; |
| |
| xfree (tp); |
| } |
| } |
| |
| /* There may be several tracepoints with the same number (because they |
| are "locations", in GDB parlance); return the next one after the |
| given tracepoint, or search from the beginning of the list if the |
| first argument is NULL. */ |
| |
| static struct tracepoint * |
| find_next_tracepoint_by_number (struct tracepoint *prev_tp, int num) |
| { |
| struct tracepoint *tpoint; |
| |
| if (prev_tp) |
| tpoint = prev_tp->next; |
| else |
| tpoint = tracepoints; |
| for (; tpoint; tpoint = tpoint->next) |
| if (tpoint->number == num) |
| return tpoint; |
| |
| return NULL; |
| } |
| |
| #endif |
| |
| /* Append another action to perform when the tracepoint triggers. */ |
| |
| static void |
| add_tracepoint_action (struct tracepoint *tpoint, const char *packet) |
| { |
| const char *act; |
| |
| if (*packet == 'S') |
| { |
| seen_step_action_flag = 1; |
| ++packet; |
| } |
| |
| act = packet; |
| |
| while (*act) |
| { |
| const char *act_start = act; |
| struct tracepoint_action *action = NULL; |
| |
| switch (*act) |
| { |
| case 'M': |
| { |
| struct collect_memory_action *maction = |
| XNEW (struct collect_memory_action); |
| ULONGEST basereg; |
| int is_neg; |
| |
| maction->base.type = *act; |
| action = &maction->base; |
| |
| ++act; |
| is_neg = (*act == '-'); |
| if (*act == '-') |
| ++act; |
| act = unpack_varlen_hex (act, &basereg); |
| ++act; |
| act = unpack_varlen_hex (act, &maction->addr); |
| ++act; |
| act = unpack_varlen_hex (act, &maction->len); |
| maction->basereg = (is_neg |
| ? - (int) basereg |
| : (int) basereg); |
| trace_debug ("Want to collect %s bytes at 0x%s (basereg %d)", |
| pulongest (maction->len), |
| paddress (maction->addr), maction->basereg); |
| break; |
| } |
| case 'R': |
| { |
| struct collect_registers_action *raction = |
| XNEW (struct collect_registers_action); |
| |
| raction->base.type = *act; |
| action = &raction->base; |
| |
| trace_debug ("Want to collect registers"); |
| ++act; |
| /* skip past hex digits of mask for now */ |
| while (isxdigit(*act)) |
| ++act; |
| break; |
| } |
| case 'L': |
| { |
| struct collect_static_trace_data_action *raction = |
| XNEW (struct collect_static_trace_data_action); |
| |
| raction->base.type = *act; |
| action = &raction->base; |
| |
| trace_debug ("Want to collect static trace data"); |
| ++act; |
| break; |
| } |
| case 'S': |
| trace_debug ("Unexpected step action, ignoring"); |
| ++act; |
| break; |
| case 'X': |
| { |
| struct eval_expr_action *xaction = XNEW (struct eval_expr_action); |
| |
| xaction->base.type = *act; |
| action = &xaction->base; |
| |
| trace_debug ("Want to evaluate expression"); |
| xaction->expr = gdb_parse_agent_expr (&act); |
| break; |
| } |
| default: |
| trace_debug ("unknown trace action '%c', ignoring...", *act); |
| break; |
| case '-': |
| break; |
| } |
| |
| if (action == NULL) |
| break; |
| |
| if (seen_step_action_flag) |
| { |
| tpoint->num_step_actions++; |
| |
| tpoint->step_actions |
| = XRESIZEVEC (struct tracepoint_action *, tpoint->step_actions, |
| tpoint->num_step_actions); |
| tpoint->step_actions_str |
| = XRESIZEVEC (char *, tpoint->step_actions_str, |
| tpoint->num_step_actions); |
| tpoint->step_actions[tpoint->num_step_actions - 1] = action; |
| tpoint->step_actions_str[tpoint->num_step_actions - 1] |
| = savestring (act_start, act - act_start); |
| } |
| else |
| { |
| tpoint->numactions++; |
| tpoint->actions |
| = XRESIZEVEC (struct tracepoint_action *, tpoint->actions, |
| tpoint->numactions); |
| tpoint->actions_str |
| = XRESIZEVEC (char *, tpoint->actions_str, tpoint->numactions); |
| tpoint->actions[tpoint->numactions - 1] = action; |
| tpoint->actions_str[tpoint->numactions - 1] |
| = savestring (act_start, act - act_start); |
| } |
| } |
| } |
| |
| #endif |
| |
| /* Find or create a trace state variable with the given number. */ |
| |
| static struct trace_state_variable * |
| get_trace_state_variable (int num) |
| { |
| struct trace_state_variable *tsv; |
| |
| #ifdef IN_PROCESS_AGENT |
| /* Search for an existing variable. */ |
| for (tsv = alloced_trace_state_variables; tsv; tsv = tsv->next) |
| if (tsv->number == num) |
| return tsv; |
| #endif |
| |
| /* Search for an existing variable. */ |
| for (tsv = trace_state_variables; tsv; tsv = tsv->next) |
| if (tsv->number == num) |
| return tsv; |
| |
| return NULL; |
| } |
| |
| /* Find or create a trace state variable with the given number. */ |
| |
| static struct trace_state_variable * |
| create_trace_state_variable (int num, int gdb) |
| { |
| struct trace_state_variable *tsv; |
| |
| tsv = get_trace_state_variable (num); |
| if (tsv != NULL) |
| return tsv; |
| |
| /* Create a new variable. */ |
| tsv = XNEW (struct trace_state_variable); |
| tsv->number = num; |
| tsv->initial_value = 0; |
| tsv->value = 0; |
| tsv->getter = NULL; |
| tsv->name = NULL; |
| #ifdef IN_PROCESS_AGENT |
| if (!gdb) |
| { |
| tsv->next = alloced_trace_state_variables; |
| alloced_trace_state_variables = tsv; |
| } |
| else |
| #endif |
| { |
| tsv->next = trace_state_variables; |
| trace_state_variables = tsv; |
| } |
| return tsv; |
| } |
| |
| /* This is needed for -Wmissing-declarations. */ |
| IP_AGENT_EXPORT_FUNC LONGEST get_trace_state_variable_value (int num); |
| |
| IP_AGENT_EXPORT_FUNC LONGEST |
| get_trace_state_variable_value (int num) |
| { |
| struct trace_state_variable *tsv; |
| |
| tsv = get_trace_state_variable (num); |
| |
| if (!tsv) |
| { |
| trace_debug ("No trace state variable %d, skipping value get", num); |
| return 0; |
| } |
| |
| /* Call a getter function if we have one. While it's tempting to |
| set up something to only call the getter once per tracepoint hit, |
| it could run afoul of thread races. Better to let the getter |
| handle it directly, if necessary to worry about it. */ |
| if (tsv->getter) |
| tsv->value = (tsv->getter) (); |
| |
| trace_debug ("get_trace_state_variable_value(%d) ==> %s", |
| num, plongest (tsv->value)); |
| |
| return tsv->value; |
| } |
| |
| /* This is needed for -Wmissing-declarations. */ |
| IP_AGENT_EXPORT_FUNC void set_trace_state_variable_value (int num, |
| LONGEST val); |
| |
| IP_AGENT_EXPORT_FUNC void |
| set_trace_state_variable_value (int num, LONGEST val) |
| { |
| struct trace_state_variable *tsv; |
| |
| tsv = get_trace_state_variable (num); |
| |
| if (!tsv) |
| { |
| trace_debug ("No trace state variable %d, skipping value set", num); |
| return; |
| } |
| |
| tsv->value = val; |
| } |
| |
| LONGEST |
| agent_get_trace_state_variable_value (int num) |
| { |
| return get_trace_state_variable_value (num); |
| } |
| |
| void |
| agent_set_trace_state_variable_value (int num, LONGEST val) |
| { |
| set_trace_state_variable_value (num, val); |
| } |
| |
| static void |
| set_trace_state_variable_name (int num, const char *name) |
| { |
| struct trace_state_variable *tsv; |
| |
| tsv = get_trace_state_variable (num); |
| |
| if (!tsv) |
| { |
| trace_debug ("No trace state variable %d, skipping name set", num); |
| return; |
| } |
| |
| tsv->name = (char *) name; |
| } |
| |
| static void |
| set_trace_state_variable_getter (int num, LONGEST (*getter) (void)) |
| { |
| struct trace_state_variable *tsv; |
| |
| tsv = get_trace_state_variable (num); |
| |
| if (!tsv) |
| { |
| trace_debug ("No trace state variable %d, skipping getter set", num); |
| return; |
| } |
| |
| tsv->getter = getter; |
| } |
| |
| /* Add a raw traceframe for the given tracepoint. */ |
| |
| static struct traceframe * |
| add_traceframe (struct tracepoint *tpoint) |
| { |
| struct traceframe *tframe; |
| |
| tframe |
| = (struct traceframe *) trace_buffer_alloc (sizeof (struct traceframe)); |
| |
| if (tframe == NULL) |
| return NULL; |
| |
| tframe->tpnum = tpoint->number; |
| tframe->data_size = 0; |
| |
| return tframe; |
| } |
| |
| /* Add a block to the traceframe currently being worked on. */ |
| |
| static unsigned char * |
| add_traceframe_block (struct traceframe *tframe, |
| struct tracepoint *tpoint, int amt) |
| { |
| unsigned char *block; |
| |
| if (!tframe) |
| return NULL; |
| |
| block = (unsigned char *) trace_buffer_alloc (amt); |
| |
| if (!block) |
| return NULL; |
| |
| gdb_assert (tframe->tpnum == tpoint->number); |
| |
| tframe->data_size += amt; |
| tpoint->traceframe_usage += amt; |
| |
| return block; |
| } |
| |
| /* Flag that the current traceframe is finished. */ |
| |
| static void |
| finish_traceframe (struct traceframe *tframe) |
| { |
| ++traceframe_write_count; |
| ++traceframes_created; |
| } |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Given a traceframe number NUM, find the NUMth traceframe in the |
| buffer. */ |
| |
| static struct traceframe * |
| find_traceframe (int num) |
| { |
| struct traceframe *tframe; |
| int tfnum = 0; |
| |
| for (tframe = FIRST_TRACEFRAME (); |
| tframe->tpnum != 0; |
| tframe = NEXT_TRACEFRAME (tframe)) |
| { |
| if (tfnum == num) |
| return tframe; |
| ++tfnum; |
| } |
| |
| return NULL; |
| } |
| |
| static CORE_ADDR |
| get_traceframe_address (struct traceframe *tframe) |
| { |
| CORE_ADDR addr; |
| struct tracepoint *tpoint; |
| |
| addr = traceframe_get_pc (tframe); |
| |
| if (addr) |
| return addr; |
| |
| /* Fallback strategy, will be incorrect for while-stepping frames |
| and multi-location tracepoints. */ |
| tpoint = find_next_tracepoint_by_number (NULL, tframe->tpnum); |
| return tpoint->address; |
| } |
| |
| /* Search for the next traceframe whose address is inside or outside |
| the given range. */ |
| |
| static struct traceframe * |
| find_next_traceframe_in_range (CORE_ADDR lo, CORE_ADDR hi, int inside_p, |
| int *tfnump) |
| { |
| client_state &cs = get_client_state (); |
| struct traceframe *tframe; |
| CORE_ADDR tfaddr; |
| |
| *tfnump = cs.current_traceframe + 1; |
| tframe = find_traceframe (*tfnump); |
| /* The search is not supposed to wrap around. */ |
| if (!tframe) |
| { |
| *tfnump = -1; |
| return NULL; |
| } |
| |
| for (; tframe->tpnum != 0; tframe = NEXT_TRACEFRAME (tframe)) |
| { |
| tfaddr = get_traceframe_address (tframe); |
| if (inside_p |
| ? (lo <= tfaddr && tfaddr <= hi) |
| : (lo > tfaddr || tfaddr > hi)) |
| return tframe; |
| ++*tfnump; |
| } |
| |
| *tfnump = -1; |
| return NULL; |
| } |
| |
| /* Search for the next traceframe recorded by the given tracepoint. |
| Note that for multi-location tracepoints, this will find whatever |
| location appears first. */ |
| |
| static struct traceframe * |
| find_next_traceframe_by_tracepoint (int num, int *tfnump) |
| { |
| client_state &cs = get_client_state (); |
| struct traceframe *tframe; |
| |
| *tfnump = cs.current_traceframe + 1; |
| tframe = find_traceframe (*tfnump); |
| /* The search is not supposed to wrap around. */ |
| if (!tframe) |
| { |
| *tfnump = -1; |
| return NULL; |
| } |
| |
| for (; tframe->tpnum != 0; tframe = NEXT_TRACEFRAME (tframe)) |
| { |
| if (tframe->tpnum == num) |
| return tframe; |
| ++*tfnump; |
| } |
| |
| *tfnump = -1; |
| return NULL; |
| } |
| |
| #endif |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Clear all past trace state. */ |
| |
| static void |
| cmd_qtinit (char *packet) |
| { |
| client_state &cs = get_client_state (); |
| struct trace_state_variable *tsv, *prev, *next; |
| |
| /* Can't do this command without a pid attached. */ |
| if (current_thread == NULL) |
| { |
| write_enn (packet); |
| return; |
| } |
| |
| /* Make sure we don't try to read from a trace frame. */ |
| cs.current_traceframe = -1; |
| |
| stop_tracing (); |
| |
| trace_debug ("Initializing the trace"); |
| |
| clear_installed_tracepoints (); |
| clear_readonly_regions (); |
| |
| tracepoints = NULL; |
| last_tracepoint = NULL; |
| |
| /* Clear out any leftover trace state variables. Ones with target |
| defined getters should be kept however. */ |
| prev = NULL; |
| tsv = trace_state_variables; |
| while (tsv) |
| { |
| trace_debug ("Looking at var %d", tsv->number); |
| if (tsv->getter == NULL) |
| { |
| next = tsv->next; |
| if (prev) |
| prev->next = next; |
| else |
| trace_state_variables = next; |
| trace_debug ("Deleting var %d", tsv->number); |
| free (tsv); |
| tsv = next; |
| } |
| else |
| { |
| prev = tsv; |
| tsv = tsv->next; |
| } |
| } |
| |
| clear_trace_buffer (); |
| clear_inferior_trace_buffer (); |
| |
| write_ok (packet); |
| } |
| |
| /* Unprobe the UST marker at ADDRESS. */ |
| |
| static void |
| unprobe_marker_at (CORE_ADDR address) |
| { |
| char cmd[IPA_CMD_BUF_SIZE]; |
| |
| sprintf (cmd, "unprobe_marker_at:%s", paddress (address)); |
| run_inferior_command (cmd, strlen (cmd) + 1); |
| } |
| |
| /* Restore the program to its pre-tracing state. This routine may be called |
| in error situations, so it needs to be careful about only restoring |
| from known-valid bits. */ |
| |
| static void |
| clear_installed_tracepoints (void) |
| { |
| struct tracepoint *tpoint; |
| struct tracepoint *prev_stpoint; |
| |
| target_pause_all (true); |
| |
| prev_stpoint = NULL; |
| |
| /* Restore any bytes overwritten by tracepoints. */ |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| { |
| /* Catch the case where we might try to remove a tracepoint that |
| was never actually installed. */ |
| if (tpoint->handle == NULL) |
| { |
| trace_debug ("Tracepoint %d at 0x%s was " |
| "never installed, nothing to clear", |
| tpoint->number, paddress (tpoint->address)); |
| continue; |
| } |
| |
| switch (tpoint->type) |
| { |
| case trap_tracepoint: |
| { |
| struct breakpoint *bp |
| = (struct breakpoint *) tpoint->handle; |
| |
| delete_breakpoint (bp); |
| } |
| break; |
| case fast_tracepoint: |
| { |
| struct fast_tracepoint_jump *jump |
| = (struct fast_tracepoint_jump *) tpoint->handle; |
| |
| delete_fast_tracepoint_jump (jump); |
| } |
| break; |
| case static_tracepoint: |
| if (prev_stpoint != NULL |
| && prev_stpoint->address == tpoint->address) |
| /* Nothing to do. We already unprobed a tracepoint set at |
| this marker address (and there can only be one probe |
| per marker). */ |
| ; |
| else |
| { |
| unprobe_marker_at (tpoint->address); |
| prev_stpoint = tpoint; |
| } |
| break; |
| } |
| |
| tpoint->handle = NULL; |
| } |
| |
| target_unpause_all (true); |
| } |
| |
| /* Parse a packet that defines a tracepoint. */ |
| |
| static void |
| cmd_qtdp (char *own_buf) |
| { |
| int tppacket; |
| /* Whether there is a trailing hyphen at the end of the QTDP packet. */ |
| int trail_hyphen = 0; |
| ULONGEST num; |
| ULONGEST addr; |
| ULONGEST count; |
| struct tracepoint *tpoint; |
| const char *packet = own_buf; |
| |
| packet += strlen ("QTDP:"); |
| |
| /* A hyphen at the beginning marks a packet specifying actions for a |
| tracepoint already supplied. */ |
| tppacket = 1; |
| if (*packet == '-') |
| { |
| tppacket = 0; |
| ++packet; |
| } |
| packet = unpack_varlen_hex (packet, &num); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &addr); |
| ++packet; /* skip a colon */ |
| |
| /* See if we already have this tracepoint. */ |
| tpoint = find_tracepoint (num, addr); |
| |
| if (tppacket) |
| { |
| /* Duplicate tracepoints are never allowed. */ |
| if (tpoint) |
| { |
| trace_debug ("Tracepoint error: tracepoint %d" |
| " at 0x%s already exists", |
| (int) num, paddress (addr)); |
| write_enn (own_buf); |
| return; |
| } |
| |
| tpoint = add_tracepoint (num, addr); |
| |
| tpoint->enabled = (*packet == 'E'); |
| ++packet; /* skip 'E' */ |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &count); |
| tpoint->step_count = count; |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &count); |
| tpoint->pass_count = count; |
| /* See if we have any of the additional optional fields. */ |
| while (*packet == ':') |
| { |
| ++packet; |
| if (*packet == 'F') |
| { |
| tpoint->type = fast_tracepoint; |
| ++packet; |
| packet = unpack_varlen_hex (packet, &count); |
| tpoint->orig_size = count; |
| } |
| else if (*packet == 'S') |
| { |
| tpoint->type = static_tracepoint; |
| ++packet; |
| } |
| else if (*packet == 'X') |
| { |
| tpoint->cond = gdb_parse_agent_expr (&packet); |
| } |
| else if (*packet == '-') |
| break; |
| else if (*packet == '\0') |
| break; |
| else |
| trace_debug ("Unknown optional tracepoint field"); |
| } |
| if (*packet == '-') |
| { |
| trail_hyphen = 1; |
| trace_debug ("Also has actions\n"); |
| } |
| |
| trace_debug ("Defined %stracepoint %d at 0x%s, " |
| "enabled %d step %" PRIu64 " pass %" PRIu64, |
| tpoint->type == fast_tracepoint ? "fast " |
| : tpoint->type == static_tracepoint ? "static " : "", |
| tpoint->number, paddress (tpoint->address), tpoint->enabled, |
| tpoint->step_count, tpoint->pass_count); |
| } |
| else if (tpoint) |
| add_tracepoint_action (tpoint, packet); |
| else |
| { |
| trace_debug ("Tracepoint error: tracepoint %d at 0x%s not found", |
| (int) num, paddress (addr)); |
| write_enn (own_buf); |
| return; |
| } |
| |
| /* Install tracepoint during tracing only once for each tracepoint location. |
| For each tracepoint loc, GDB may send multiple QTDP packets, and we can |
| determine the last QTDP packet for one tracepoint location by checking |
| trailing hyphen in QTDP packet. */ |
| if (tracing && !trail_hyphen) |
| { |
| struct tracepoint *tp = NULL; |
| |
| /* Pause all threads temporarily while we patch tracepoints. */ |
| target_pause_all (false); |
| |
| /* download_tracepoint will update global `tracepoints' |
| list, so it is unsafe to leave threads in jump pad. */ |
| target_stabilize_threads (); |
| |
| /* Freeze threads. */ |
| target_pause_all (true); |
| |
| |
| if (tpoint->type != trap_tracepoint) |
| { |
| /* Find another fast or static tracepoint at the same address. */ |
| for (tp = tracepoints; tp; tp = tp->next) |
| { |
| if (tp->address == tpoint->address && tp->type == tpoint->type |
| && tp->number != tpoint->number) |
| break; |
| } |
| |
| /* TPOINT is installed at the same address as TP. */ |
| if (tp) |
| { |
| if (tpoint->type == fast_tracepoint) |
| clone_fast_tracepoint (tpoint, tp); |
| else if (tpoint->type == static_tracepoint) |
| tpoint->handle = (void *) -1; |
| } |
| } |
| |
| if (use_agent && tpoint->type == fast_tracepoint |
| && agent_capability_check (AGENT_CAPA_FAST_TRACE)) |
| { |
| /* Download and install fast tracepoint by agent. */ |
| if (tracepoint_send_agent (tpoint) == 0) |
| write_ok (own_buf); |
| else |
| { |
| write_enn (own_buf); |
| remove_tracepoint (tpoint); |
| } |
| } |
| else |
| { |
| download_tracepoint (tpoint); |
| |
| if (tpoint->type == trap_tracepoint || tp == NULL) |
| { |
| install_tracepoint (tpoint, own_buf); |
| if (strcmp (own_buf, "OK") != 0) |
| remove_tracepoint (tpoint); |
| } |
| else |
| write_ok (own_buf); |
| } |
| |
| target_unpause_all (true); |
| return; |
| } |
| |
| write_ok (own_buf); |
| } |
| |
| static void |
| cmd_qtdpsrc (char *own_buf) |
| { |
| ULONGEST num, addr, start, slen; |
| struct tracepoint *tpoint; |
| const char *packet = own_buf; |
| const char *saved; |
| char *srctype, *src; |
| size_t nbytes; |
| struct source_string *last, *newlast; |
| |
| packet += strlen ("QTDPsrc:"); |
| |
| packet = unpack_varlen_hex (packet, &num); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &addr); |
| ++packet; /* skip a colon */ |
| |
| /* See if we already have this tracepoint. */ |
| tpoint = find_tracepoint (num, addr); |
| |
| if (!tpoint) |
| { |
| trace_debug ("Tracepoint error: tracepoint %d at 0x%s not found", |
| (int) num, paddress (addr)); |
| write_enn (own_buf); |
| return; |
| } |
| |
| saved = packet; |
| packet = strchr (packet, ':'); |
| srctype = (char *) xmalloc (packet - saved + 1); |
| memcpy (srctype, saved, packet - saved); |
| srctype[packet - saved] = '\0'; |
| ++packet; |
| packet = unpack_varlen_hex (packet, &start); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &slen); |
| ++packet; /* skip a colon */ |
| src = (char *) xmalloc (slen + 1); |
| nbytes = hex2bin (packet, (gdb_byte *) src, strlen (packet) / 2); |
| src[nbytes] = '\0'; |
| |
| newlast = XNEW (struct source_string); |
| newlast->type = srctype; |
| newlast->str = src; |
| newlast->next = NULL; |
| /* Always add a source string to the end of the list; |
| this keeps sequences of actions/commands in the right |
| order. */ |
| if (tpoint->source_strings) |
| { |
| for (last = tpoint->source_strings; last->next; last = last->next) |
| ; |
| last->next = newlast; |
| } |
| else |
| tpoint->source_strings = newlast; |
| |
| write_ok (own_buf); |
| } |
| |
| static void |
| cmd_qtdv (char *own_buf) |
| { |
| ULONGEST num, val, builtin; |
| char *varname; |
| size_t nbytes; |
| struct trace_state_variable *tsv; |
| const char *packet = own_buf; |
| |
| packet += strlen ("QTDV:"); |
| |
| packet = unpack_varlen_hex (packet, &num); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &val); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &builtin); |
| ++packet; /* skip a colon */ |
| |
| nbytes = strlen (packet) / 2; |
| varname = (char *) xmalloc (nbytes + 1); |
| nbytes = hex2bin (packet, (gdb_byte *) varname, nbytes); |
| varname[nbytes] = '\0'; |
| |
| tsv = create_trace_state_variable (num, 1); |
| tsv->initial_value = (LONGEST) val; |
| tsv->name = varname; |
| |
| set_trace_state_variable_value (num, (LONGEST) val); |
| |
| write_ok (own_buf); |
| } |
| |
| static void |
| cmd_qtenable_disable (char *own_buf, int enable) |
| { |
| const char *packet = own_buf; |
| ULONGEST num, addr; |
| struct tracepoint *tp; |
| |
| packet += strlen (enable ? "QTEnable:" : "QTDisable:"); |
| packet = unpack_varlen_hex (packet, &num); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &addr); |
| |
| tp = find_tracepoint (num, addr); |
| |
| if (tp) |
| { |
| if ((enable && tp->enabled) || (!enable && !tp->enabled)) |
| { |
| trace_debug ("Tracepoint %d at 0x%s is already %s", |
| (int) num, paddress (addr), |
| enable ? "enabled" : "disabled"); |
| write_ok (own_buf); |
| return; |
| } |
| |
| trace_debug ("%s tracepoint %d at 0x%s", |
| enable ? "Enabling" : "Disabling", |
| (int) num, paddress (addr)); |
| |
| tp->enabled = enable; |
| |
| if (tp->type == fast_tracepoint || tp->type == static_tracepoint) |
| { |
| int offset = offsetof (struct tracepoint, enabled); |
| CORE_ADDR obj_addr = tp->obj_addr_on_target + offset; |
| |
| int ret = write_inferior_int8 (obj_addr, enable); |
| if (ret) |
| { |
| trace_debug ("Cannot write enabled flag into " |
| "inferior process memory"); |
| write_enn (own_buf); |
| return; |
| } |
| } |
| |
| write_ok (own_buf); |
| } |
| else |
| { |
| trace_debug ("Tracepoint %d at 0x%s not found", |
| (int) num, paddress (addr)); |
| write_enn (own_buf); |
| } |
| } |
| |
| static void |
| cmd_qtv (char *own_buf) |
| { |
| client_state &cs = get_client_state (); |
| ULONGEST num; |
| LONGEST val = 0; |
| int err; |
| char *packet = own_buf; |
| |
| packet += strlen ("qTV:"); |
| unpack_varlen_hex (packet, &num); |
| |
| if (cs.current_traceframe >= 0) |
| { |
| err = traceframe_read_tsv ((int) num, &val); |
| if (err) |
| { |
| strcpy (own_buf, "U"); |
| return; |
| } |
| } |
| /* Only make tsv's be undefined before the first trace run. After a |
| trace run is over, the user might want to see the last value of |
| the tsv, and it might not be available in a traceframe. */ |
| else if (!tracing && strcmp (tracing_stop_reason, "tnotrun") == 0) |
| { |
| strcpy (own_buf, "U"); |
| return; |
| } |
| else |
| val = get_trace_state_variable_value (num); |
| |
| sprintf (own_buf, "V%s", phex_nz (val, 0)); |
| } |
| |
| /* Clear out the list of readonly regions. */ |
| |
| static void |
| clear_readonly_regions (void) |
| { |
| struct readonly_region *roreg; |
| |
| while (readonly_regions) |
| { |
| roreg = readonly_regions; |
| readonly_regions = readonly_regions->next; |
| free (roreg); |
| } |
| } |
| |
| /* Parse the collection of address ranges whose contents GDB believes |
| to be unchanging and so can be read directly from target memory |
| even while looking at a traceframe. */ |
| |
| static void |
| cmd_qtro (char *own_buf) |
| { |
| ULONGEST start, end; |
| struct readonly_region *roreg; |
| const char *packet = own_buf; |
| |
| trace_debug ("Want to mark readonly regions"); |
| |
| clear_readonly_regions (); |
| |
| packet += strlen ("QTro"); |
| |
| while (*packet == ':') |
| { |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &start); |
| ++packet; /* skip a comma */ |
| packet = unpack_varlen_hex (packet, &end); |
| |
| roreg = XNEW (struct readonly_region); |
| roreg->start = start; |
| roreg->end = end; |
| roreg->next = readonly_regions; |
| readonly_regions = roreg; |
| trace_debug ("Added readonly region from 0x%s to 0x%s", |
| paddress (roreg->start), paddress (roreg->end)); |
| } |
| |
| write_ok (own_buf); |
| } |
| |
| /* Test to see if the given range is in our list of readonly ranges. |
| We only test for being entirely within a range, GDB is not going to |
| send a single memory packet that spans multiple regions. */ |
| |
| int |
| in_readonly_region (CORE_ADDR addr, ULONGEST length) |
| { |
| struct readonly_region *roreg; |
| |
| for (roreg = readonly_regions; roreg; roreg = roreg->next) |
| if (roreg->start <= addr && (addr + length - 1) <= roreg->end) |
| return 1; |
| |
| return 0; |
| } |
| |
| static CORE_ADDR gdb_jump_pad_head; |
| |
| /* Return the address of the next free jump space. */ |
| |
| static CORE_ADDR |
| get_jump_space_head (void) |
| { |
| if (gdb_jump_pad_head == 0) |
| { |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_jump_pad_buffer, |
| &gdb_jump_pad_head)) |
| { |
| internal_error ("error extracting jump_pad_buffer"); |
| } |
| } |
| |
| return gdb_jump_pad_head; |
| } |
| |
| /* Reserve USED bytes from the jump space. */ |
| |
| static void |
| claim_jump_space (ULONGEST used) |
| { |
| trace_debug ("claim_jump_space reserves %s bytes at %s", |
| pulongest (used), paddress (gdb_jump_pad_head)); |
| gdb_jump_pad_head += used; |
| } |
| |
| static CORE_ADDR trampoline_buffer_head = 0; |
| static CORE_ADDR trampoline_buffer_tail; |
| |
| /* Reserve USED bytes from the trampoline buffer and return the |
| address of the start of the reserved space in TRAMPOLINE. Returns |
| non-zero if the space is successfully claimed. */ |
| |
| int |
| claim_trampoline_space (ULONGEST used, CORE_ADDR *trampoline) |
| { |
| if (!trampoline_buffer_head) |
| { |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer, |
| &trampoline_buffer_tail)) |
| { |
| internal_error ("error extracting trampoline_buffer"); |
| } |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer_end, |
| &trampoline_buffer_head)) |
| { |
| internal_error ("error extracting trampoline_buffer_end"); |
| } |
| } |
| |
| /* Start claiming space from the top of the trampoline space. If |
| the space is located at the bottom of the virtual address space, |
| this reduces the possibility that corruption will occur if a null |
| pointer is used to write to memory. */ |
| if (trampoline_buffer_head - trampoline_buffer_tail < used) |
| { |
| trace_debug ("claim_trampoline_space failed to reserve %s bytes", |
| pulongest (used)); |
| return 0; |
| } |
| |
| trampoline_buffer_head -= used; |
| |
| trace_debug ("claim_trampoline_space reserves %s bytes at %s", |
| pulongest (used), paddress (trampoline_buffer_head)); |
| |
| *trampoline = trampoline_buffer_head; |
| return 1; |
| } |
| |
| /* Returns non-zero if there is space allocated for use in trampolines |
| for fast tracepoints. */ |
| |
| int |
| have_fast_tracepoint_trampoline_buffer (char *buf) |
| { |
| CORE_ADDR trampoline_end, errbuf; |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer_end, |
| &trampoline_end)) |
| { |
| internal_error ("error extracting trampoline_buffer_end"); |
| } |
| |
| if (buf) |
| { |
| buf[0] = '\0'; |
| strcpy (buf, "was claiming"); |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer_error, |
| &errbuf)) |
| { |
| internal_error ("error extracting errbuf"); |
| } |
| |
| read_inferior_memory (errbuf, (unsigned char *) buf, 100); |
| } |
| |
| return trampoline_end != 0; |
| } |
| |
| /* Ask the IPA to probe the marker at ADDRESS. Returns -1 if running |
| the command fails, or 0 otherwise. If the command ran |
| successfully, but probing the marker failed, ERROUT will be filled |
| with the error to reply to GDB, and -1 is also returned. This |
| allows directly passing IPA errors to GDB. */ |
| |
| static int |
| probe_marker_at (CORE_ADDR address, char *errout) |
| { |
| char cmd[IPA_CMD_BUF_SIZE]; |
| int err; |
| |
| sprintf (cmd, "probe_marker_at:%s", paddress (address)); |
| err = run_inferior_command (cmd, strlen (cmd) + 1); |
| |
| if (err == 0) |
| { |
| if (*cmd == 'E') |
| { |
| strcpy (errout, cmd); |
| return -1; |
| } |
| } |
| |
| return err; |
| } |
| |
| static void |
| clone_fast_tracepoint (struct tracepoint *to, const struct tracepoint *from) |
| { |
| to->jump_pad = from->jump_pad; |
| to->jump_pad_end = from->jump_pad_end; |
| to->trampoline = from->trampoline; |
| to->trampoline_end = from->trampoline_end; |
| to->adjusted_insn_addr = from->adjusted_insn_addr; |
| to->adjusted_insn_addr_end = from->adjusted_insn_addr_end; |
| to->handle = from->handle; |
| |
| gdb_assert (from->handle); |
| inc_ref_fast_tracepoint_jump ((struct fast_tracepoint_jump *) from->handle); |
| } |
| |
| #define MAX_JUMP_SIZE 20 |
| |
| /* Install fast tracepoint. Return 0 if successful, otherwise return |
| non-zero. */ |
| |
| static int |
| install_fast_tracepoint (struct tracepoint *tpoint, char *errbuf) |
| { |
| CORE_ADDR jentry, jump_entry; |
| CORE_ADDR trampoline; |
| CORE_ADDR collect; |
| ULONGEST trampoline_size; |
| int err = 0; |
| /* The jump to the jump pad of the last fast tracepoint |
| installed. */ |
| unsigned char fjump[MAX_JUMP_SIZE]; |
| ULONGEST fjump_size; |
| |
| if (tpoint->orig_size < target_get_min_fast_tracepoint_insn_len ()) |
| { |
| trace_debug ("Requested a fast tracepoint on an instruction " |
| "that is of less than the minimum length."); |
| return 0; |
| } |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_collect_ptr, |
| &collect)) |
| { |
| error ("error extracting gdb_collect_ptr"); |
| return 1; |
| } |
| |
| jentry = jump_entry = get_jump_space_head (); |
| |
| trampoline = 0; |
| trampoline_size = 0; |
| |
| /* Install the jump pad. */ |
| err = target_install_fast_tracepoint_jump_pad |
| (tpoint->obj_addr_on_target, tpoint->address, collect, |
| ipa_sym_addrs.addr_collecting, tpoint->orig_size, &jentry, |
| &trampoline, &trampoline_size, fjump, &fjump_size, |
| &tpoint->adjusted_insn_addr, &tpoint->adjusted_insn_addr_end, errbuf); |
| |
| if (err) |
| return 1; |
| |
| /* Wire it in. */ |
| tpoint->handle = set_fast_tracepoint_jump (tpoint->address, fjump, |
| fjump_size); |
| |
| if (tpoint->handle != NULL) |
| { |
| tpoint->jump_pad = jump_entry; |
| tpoint->jump_pad_end = jentry; |
| tpoint->trampoline = trampoline; |
| tpoint->trampoline_end = trampoline + trampoline_size; |
| |
| /* Pad to 8-byte alignment. */ |
| jentry = ((jentry + 7) & ~0x7); |
| claim_jump_space (jentry - jump_entry); |
| } |
| |
| return 0; |
| } |
| |
| |
| /* Install tracepoint TPOINT, and write reply message in OWN_BUF. */ |
| |
| static void |
| install_tracepoint (struct tracepoint *tpoint, char *own_buf) |
| { |
| tpoint->handle = NULL; |
| *own_buf = '\0'; |
| |
| if (tpoint->type == trap_tracepoint) |
| { |
| /* Tracepoints are installed as memory breakpoints. Just go |
| ahead and install the trap. The breakpoints module |
| handles duplicated breakpoints, and the memory read |
| routine handles un-patching traps from memory reads. */ |
| tpoint->handle = set_breakpoint_at (tpoint->address, |
| tracepoint_handler); |
| } |
| else if (tpoint->type == fast_tracepoint || tpoint->type == static_tracepoint) |
| { |
| if (!agent_loaded_p ()) |
| { |
| trace_debug ("Requested a %s tracepoint, but fast " |
| "tracepoints aren't supported.", |
| tpoint->type == static_tracepoint ? "static" : "fast"); |
| write_e_ipa_not_loaded (own_buf); |
| return; |
| } |
| if (tpoint->type == static_tracepoint |
| && !in_process_agent_supports_ust ()) |
| { |
| trace_debug ("Requested a static tracepoint, but static " |
| "tracepoints are not supported."); |
| write_e_ust_not_loaded (own_buf); |
| return; |
| } |
| |
| if (tpoint->type == fast_tracepoint) |
| install_fast_tracepoint (tpoint, own_buf); |
| else |
| { |
| if (probe_marker_at (tpoint->address, own_buf) == 0) |
| tpoint->handle = (void *) -1; |
| } |
| |
| } |
| else |
| internal_error ("Unknown tracepoint type"); |
| |
| if (tpoint->handle == NULL) |
| { |
| if (*own_buf == '\0') |
| write_enn (own_buf); |
| } |
| else |
| write_ok (own_buf); |
| } |
| |
| static void download_tracepoint_1 (struct tracepoint *tpoint); |
| |
| static void |
| cmd_qtstart (char *packet) |
| { |
| struct tracepoint *tpoint, *prev_ftpoint, *prev_stpoint; |
| CORE_ADDR tpptr = 0, prev_tpptr = 0; |
| |
| trace_debug ("Starting the trace"); |
| |
| /* Pause all threads temporarily while we patch tracepoints. */ |
| target_pause_all (false); |
| |
| /* Get threads out of jump pads. Safe to do here, since this is a |
| top level command. And, required to do here, since we're |
| deleting/rewriting jump pads. */ |
| |
| target_stabilize_threads (); |
| |
| /* Freeze threads. */ |
| target_pause_all (true); |
| |
| /* Sync the fast tracepoints list in the inferior ftlib. */ |
| if (agent_loaded_p ()) |
| download_trace_state_variables (); |
| |
| /* No previous fast tpoint yet. */ |
| prev_ftpoint = NULL; |
| |
| /* No previous static tpoint yet. */ |
| prev_stpoint = NULL; |
| |
| *packet = '\0'; |
| |
| if (agent_loaded_p ()) |
| { |
| /* Tell IPA about the correct tdesc. */ |
| if (write_inferior_integer (ipa_sym_addrs.addr_ipa_tdesc_idx, |
| target_get_ipa_tdesc_idx ())) |
| error ("Error setting ipa_tdesc_idx variable in lib"); |
| } |
| |
| /* Start out empty. */ |
| if (agent_loaded_p ()) |
| write_inferior_data_pointer (ipa_sym_addrs.addr_tracepoints, 0); |
| |
| /* Download and install tracepoints. */ |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| { |
| /* Ensure all the hit counts start at zero. */ |
| tpoint->hit_count = 0; |
| tpoint->traceframe_usage = 0; |
| |
| if (tpoint->type == trap_tracepoint) |
| { |
| /* Tracepoints are installed as memory breakpoints. Just go |
| ahead and install the trap. The breakpoints module |
| handles duplicated breakpoints, and the memory read |
| routine handles un-patching traps from memory reads. */ |
| tpoint->handle = set_breakpoint_at (tpoint->address, |
| tracepoint_handler); |
| } |
| else if (tpoint->type == fast_tracepoint |
| || tpoint->type == static_tracepoint) |
| { |
| if (maybe_write_ipa_not_loaded (packet)) |
| { |
| trace_debug ("Requested a %s tracepoint, but fast " |
| "tracepoints aren't supported.", |
| tpoint->type == static_tracepoint |
| ? "static" : "fast"); |
| break; |
| } |
| |
| if (tpoint->type == fast_tracepoint) |
| { |
| int use_agent_p |
| = use_agent && agent_capability_check (AGENT_CAPA_FAST_TRACE); |
| |
| if (prev_ftpoint != NULL |
| && prev_ftpoint->address == tpoint->address) |
| { |
| if (use_agent_p) |
| tracepoint_send_agent (tpoint); |
| else |
| download_tracepoint_1 (tpoint); |
| |
| clone_fast_tracepoint (tpoint, prev_ftpoint); |
| } |
| else |
| { |
| /* Tracepoint is installed successfully? */ |
| int installed = 0; |
| |
| /* Download and install fast tracepoint by agent. */ |
| if (use_agent_p) |
| installed = !tracepoint_send_agent (tpoint); |
| else |
| { |
| download_tracepoint_1 (tpoint); |
| installed = !install_fast_tracepoint (tpoint, packet); |
| } |
| |
| if (installed) |
| prev_ftpoint = tpoint; |
| } |
| } |
| else |
| { |
| if (!in_process_agent_supports_ust ()) |
| { |
| trace_debug ("Requested a static tracepoint, but static " |
| "tracepoints are not supported."); |
| break; |
| } |
| |
| download_tracepoint_1 (tpoint); |
| /* Can only probe a given marker once. */ |
| if (prev_stpoint != NULL |
| && prev_stpoint->address == tpoint->address) |
| tpoint->handle = (void *) -1; |
| else |
| { |
| if (probe_marker_at (tpoint->address, packet) == 0) |
| { |
| tpoint->handle = (void *) -1; |
| |
| /* So that we can handle multiple static tracepoints |
| at the same address easily. */ |
| prev_stpoint = tpoint; |
| } |
| } |
| } |
| |
| prev_tpptr = tpptr; |
| tpptr = tpoint->obj_addr_on_target; |
| |
| if (tpoint == tracepoints) |
| /* First object in list, set the head pointer in the |
| inferior. */ |
| write_inferior_data_pointer (ipa_sym_addrs.addr_tracepoints, tpptr); |
| else |
| write_inferior_data_pointer (prev_tpptr |
| + offsetof (struct tracepoint, next), |
| tpptr); |
| } |
| |
| /* Any failure in the inner loop is sufficient cause to give |
| up. */ |
| if (tpoint->handle == NULL) |
| break; |
| } |
| |
| /* Any error in tracepoint insertion is unacceptable; better to |
| address the problem now, than end up with a useless or misleading |
| trace run. */ |
| if (tpoint != NULL) |
| { |
| clear_installed_tracepoints (); |
| if (*packet == '\0') |
| write_enn (packet); |
| target_unpause_all (true); |
| return; |
| } |
| |
| stopping_tracepoint = NULL; |
| trace_buffer_is_full = 0; |
| expr_eval_result = expr_eval_no_error; |
| error_tracepoint = NULL; |
| tracing_start_time = get_timestamp (); |
| |
| /* Tracing is now active, hits will now start being logged. */ |
| tracing = 1; |
| |
| if (agent_loaded_p ()) |
| { |
| if (write_inferior_integer (ipa_sym_addrs.addr_tracing, 1)) |
| { |
| internal_error ("Error setting tracing variable in lib"); |
| } |
| |
| if (write_inferior_data_pointer (ipa_sym_addrs.addr_stopping_tracepoint, |
| 0)) |
| { |
| internal_error ("Error clearing stopping_tracepoint variable" |
| " in lib"); |
| } |
| |
| if (write_inferior_integer (ipa_sym_addrs.addr_trace_buffer_is_full, 0)) |
| { |
| internal_error ("Error clearing trace_buffer_is_full variable" |
| " in lib"); |
| } |
| |
| stop_tracing_bkpt = set_breakpoint_at (ipa_sym_addrs.addr_stop_tracing, |
| stop_tracing_handler); |
| if (stop_tracing_bkpt == NULL) |
| error ("Error setting stop_tracing breakpoint"); |
| |
| flush_trace_buffer_bkpt |
| = set_breakpoint_at (ipa_sym_addrs.addr_flush_trace_buffer, |
| flush_trace_buffer_handler); |
| if (flush_trace_buffer_bkpt == NULL) |
| error ("Error setting flush_trace_buffer breakpoint"); |
| } |
| |
| target_unpause_all (true); |
| |
| write_ok (packet); |
| } |
| |
| /* End a tracing run, filling in a stop reason to report back to GDB, |
| and removing the tracepoints from the code. */ |
| |
| void |
| stop_tracing (void) |
| { |
| if (!tracing) |
| { |
| trace_debug ("Tracing is already off, ignoring"); |
| return; |
| } |
| |
| trace_debug ("Stopping the trace"); |
| |
| /* Pause all threads before removing fast jumps from memory, |
| breakpoints, and touching IPA state variables (inferior memory). |
| Some thread may hit the internal tracing breakpoints, or be |
| collecting this moment, but that's ok, we don't release the |
| tpoint object's memory or the jump pads here (we only do that |
| when we're sure we can move all threads out of the jump pads). |
| We can't now, since we may be getting here due to the inferior |
| agent calling us. */ |
| target_pause_all (true); |
| |
| /* Stop logging. Tracepoints can still be hit, but they will not be |
| recorded. */ |
| tracing = 0; |
| if (agent_loaded_p ()) |
| { |
| if (write_inferior_integer (ipa_sym_addrs.addr_tracing, 0)) |
| { |
| internal_error ("Error clearing tracing variable in lib"); |
| } |
| } |
| |
| tracing_stop_time = get_timestamp (); |
| tracing_stop_reason = "t???"; |
| tracing_stop_tpnum = 0; |
| if (stopping_tracepoint) |
| { |
| trace_debug ("Stopping the trace because " |
| "tracepoint %d was hit %" PRIu64 " times", |
| stopping_tracepoint->number, |
| stopping_tracepoint->pass_count); |
| tracing_stop_reason = "tpasscount"; |
| tracing_stop_tpnum = stopping_tracepoint->number; |
| } |
| else if (trace_buffer_is_full) |
| { |
| trace_debug ("Stopping the trace because the trace buffer is full"); |
| tracing_stop_reason = "tfull"; |
| } |
| else if (expr_eval_result != expr_eval_no_error) |
| { |
| trace_debug ("Stopping the trace because of an expression eval error"); |
| tracing_stop_reason = eval_result_names[expr_eval_result]; |
| tracing_stop_tpnum = error_tracepoint->number; |
| } |
| #ifndef IN_PROCESS_AGENT |
| else if (!gdb_connected ()) |
| { |
| trace_debug ("Stopping the trace because GDB disconnected"); |
| tracing_stop_reason = "tdisconnected"; |
| } |
| #endif |
| else |
| { |
| trace_debug ("Stopping the trace because of a tstop command"); |
| tracing_stop_reason = "tstop"; |
| } |
| |
| stopping_tracepoint = NULL; |
| error_tracepoint = NULL; |
| |
| /* Clear out the tracepoints. */ |
| clear_installed_tracepoints (); |
| |
| if (agent_loaded_p ()) |
| { |
| /* Pull in fast tracepoint trace frames from the inferior lib |
| buffer into our buffer, even if our buffer is already full, |
| because we want to present the full number of created frames |
| in addition to what fit in the trace buffer. */ |
| upload_fast_traceframes (); |
| } |
| |
| if (stop_tracing_bkpt != NULL) |
| { |
| delete_breakpoint (stop_tracing_bkpt); |
| stop_tracing_bkpt = NULL; |
| } |
| |
| if (flush_trace_buffer_bkpt != NULL) |
| { |
| delete_breakpoint (
|