| /* Tracepoint code for remote server for GDB. |
| Copyright (C) 2009-2021 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 "server.h" |
| #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_1(level, fmt, args...) \ |
| do { \ |
| if (level <= debug_threads) \ |
| trace_vdebug ((fmt), ##args); \ |
| } while (0) |
| |
| #else |
| |
| #define trace_debug_1(level, fmt, args...) \ |
| do { \ |
| if (level <= debug_threads) \ |
| { \ |
| debug_printf ((fmt), ##args); \ |
| debug_printf ("\n"); \ |
| } \ |
| } while (0) |
| |
| #endif |
| |
| #define trace_debug(FMT, args...) \ |
| trace_debug_1 (1, FMT, ##args) |
| |
| /* 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) |
| { |
| if (debug_threads) |
| debug_printf ("symbol `%s' not found\n", 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 |
| 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_PUSH |
| |
| /* 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; |
| |
| EXTERN_C_POP |
| |
| #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[] = |
| { |
| "terror:in the attic", /* this should never be reported */ |
| "terror:empty expression", |
| "terror:empty stack", |
| "terror:stack overflow", |
| "terror:stack underflow", |
| "terror:unhandled opcode", |
| "terror:unrecognized opcode", |
| "terror:divide by zero" |
| }; |
| |
| #endif |
| |
| /* The tracepoint in which the error occurred. */ |
| |
| EXTERN_C_PUSH |
| IP_AGENT_EXPORT_VAR struct tracepoint *error_tracepoint; |
| EXTERN_C_POP |
| |
| 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_PUSH |
| |
| /* 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; |
| |
| EXTERN_C_POP |
| |
| /* 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 ret; |
| int offset = offsetof (struct tracepoint, enabled); |
| CORE_ADDR obj_addr = tp->obj_addr_on_target + offset; |
| |
| ret = prepare_to_access_memory (); |
| if (ret) |
| { |
| trace_debug ("Failed to temporarily stop inferior threads"); |
| write_enn (own_buf); |
| return; |
| } |
| |
| ret = write_inferior_int8 (obj_addr, enable); |
| done_accessing_memory (); |
| |
| 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 (__FILE__, __LINE__, |
| "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 (__FILE__, __LINE__, |
| "error extracting trampoline_buffer"); |
| } |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer_end, |
| &trampoline_buffer_head)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "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 (__FILE__, __LINE__, |
| "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 (__FILE__, __LINE__, |
| "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 (__FILE__, __LINE__, "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 (__FILE__, __LINE__, |
| "Error setting tracing variable in lib"); |
| } |
| |
| if (write_inferior_data_pointer (ipa_sym_addrs.addr_stopping_tracepoint, |
| 0)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "Error clearing stopping_tracepoint variable" |
| " in lib"); |
| } |
| |
| if (write_inferior_integer (ipa_sym_addrs.addr_trace_buffer_is_full, 0)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "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 (__FILE__, __LINE__, |
| "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 (flush_trace_buffer_bkpt); |
| flush_trace_buffer_bkpt = NULL; |
| } |
| |
| target_unpause_all (true); |
| } |
| |
| static int |
| stop_tracing_handler (CORE_ADDR addr) |
| { |
| trace_debug ("lib hit stop_tracing"); |
| |
| /* Don't actually handle it here. When we stop tracing we remove |
| breakpoints from the inferior, and that is not allowed in a |
| breakpoint handler (as the caller is walking the breakpoint |
| list). */ |
| return 0; |
| } |
| |
| static int |
| flush_trace_buffer_handler (CORE_ADDR addr) |
| { |
| trace_debug ("lib hit flush_trace_buffer"); |
| return 0; |
| } |
| |
| static void |
| cmd_qtstop (char *packet) |
| { |
| stop_tracing (); |
| write_ok (packet); |
| } |
| |
| static void |
| cmd_qtdisconnected (char *own_buf) |
| { |
| ULONGEST setting; |
| char *packet = own_buf; |
| |
| packet += strlen ("QTDisconnected:"); |
| |
| unpack_varlen_hex (packet, &setting); |
| |
| write_ok (own_buf); |
| |
| disconnected_tracing = setting; |
| } |
| |
| static void |
| cmd_qtframe (char *own_buf) |
| { |
| client_state &cs = get_client_state (); |
| ULONGEST frame, pc, lo, hi, num; |
| int tfnum, tpnum; |
| struct traceframe *tframe; |
| const char *packet = own_buf; |
| |
| packet += strlen ("QTFrame:"); |
| |
| if (startswith (packet, "pc:")) |
| { |
| packet += strlen ("pc:"); |
| unpack_varlen_hex (packet, &pc); |
| trace_debug ("Want to find next traceframe at pc=0x%s", paddress (pc)); |
| tframe = find_next_traceframe_in_range (pc, pc, 1, &tfnum); |
| } |
| else if (startswith (packet, "range:")) |
| { |
| packet += strlen ("range:"); |
| packet = unpack_varlen_hex (packet, &lo); |
| ++packet; |
| unpack_varlen_hex (packet, &hi); |
| trace_debug ("Want to find next traceframe in the range 0x%s to 0x%s", |
| paddress (lo), paddress (hi)); |
| tframe = find_next_traceframe_in_range (lo, hi, 1, &tfnum); |
| } |
| else if (startswith (packet, "outside:")) |
| { |
| packet += strlen ("outside:"); |
| packet = unpack_varlen_hex (packet, &lo); |
| ++packet; |
| unpack_varlen_hex (packet, &hi); |
| trace_debug ("Want to find next traceframe " |
| "outside the range 0x%s to 0x%s", |
| paddress (lo), paddress (hi)); |
| tframe = find_next_traceframe_in_range (lo, hi, 0, &tfnum); |
| } |
| else if (startswith (packet, "tdp:")) |
| { |
| packet += strlen ("tdp:"); |
| unpack_varlen_hex (packet, &num); |
| tpnum = (int) num; |
| trace_debug ("Want to find next traceframe for tracepoint %d", tpnum); |
| tframe = find_next_traceframe_by_tracepoint (tpnum, &tfnum); |
| } |
| else |
| { |
| unpack_varlen_hex (packet, &frame); |
| tfnum = (int) frame; |
| if (tfnum == -1) |
| { |
| trace_debug ("Want to stop looking at traceframes"); |
| cs.current_traceframe = -1; |
| write_ok (own_buf); |
| return; |
| } |
| trace_debug ("Want to look at traceframe %d", tfnum); |
| tframe = find_traceframe (tfnum); |
| } |
| |
| if (tframe) |
| { |
| cs.current_traceframe = tfnum; |
| sprintf (own_buf, "F%xT%x", tfnum, tframe->tpnum); |
| } |
| else |
| sprintf (own_buf, "F-1"); |
| } |
| |
| static void |
| cmd_qtstatus (char *packet) |
| { |
| char *stop_reason_rsp = NULL; |
| char *buf1, *buf2, *buf3; |
| const char *str; |
| int slen; |
| |
| /* Translate the plain text of the notes back into hex for |
| transmission. */ |
| |
| str = (tracing_user_name ? tracing_user_name : ""); |
| slen = strlen (str); |
| buf1 = (char *) alloca (slen * 2 + 1); |
| bin2hex ((gdb_byte *) str, buf1, slen); |
| |
| str = (tracing_notes ? tracing_notes : ""); |
| slen = strlen (str); |
| buf2 = (char *) alloca (slen * 2 + 1); |
| bin2hex ((gdb_byte *) str, buf2, slen); |
| |
| str = (tracing_stop_note ? tracing_stop_note : ""); |
| slen = strlen (str); |
| buf3 = (char *) alloca (slen * 2 + 1); |
| bin2hex ((gdb_byte *) str, buf3, slen); |
| |
| trace_debug ("Returning trace status as %d, stop reason %s", |
| tracing, tracing_stop_reason); |
| |
| if (agent_loaded_p ()) |
| { |
| target_pause_all (true); |
| |
| upload_fast_traceframes (); |
| |
| target_unpause_all (true); |
| } |
| |
| stop_reason_rsp = (char *) tracing_stop_reason; |
| |
| /* The user visible error string in terror needs to be hex encoded. |
| We leave it as plain string in `tracing_stop_reason' to ease |
| debugging. */ |
| if (startswith (stop_reason_rsp, "terror:")) |
| { |
| const char *result_name; |
| int hexstr_len; |
| char *p; |
| |
| result_name = stop_reason_rsp + strlen ("terror:"); |
| hexstr_len = strlen (result_name) * 2; |
| p = stop_reason_rsp |
| = (char *) alloca (strlen ("terror:") + hexstr_len + 1); |
| strcpy (p, "terror:"); |
| p += strlen (p); |
| bin2hex ((gdb_byte *) result_name, p, strlen (result_name)); |
| } |
| |
| /* If this was a forced stop, include any stop note that was supplied. */ |
| if (strcmp (stop_reason_rsp, "tstop") == 0) |
| { |
| stop_reason_rsp = (char *) alloca (strlen ("tstop:") + strlen (buf3) + 1); |
| strcpy (stop_reason_rsp, "tstop:"); |
| strcat (stop_reason_rsp, buf3); |
| } |
| |
| sprintf (packet, |
| "T%d;" |
| "%s:%x;" |
| "tframes:%x;tcreated:%x;" |
| "tfree:%x;tsize:%s;" |
| "circular:%d;" |
| "disconn:%d;" |
| "starttime:%s;stoptime:%s;" |
| "username:%s;notes:%s:", |
| tracing ? 1 : 0, |
| stop_reason_rsp, tracing_stop_tpnum, |
| traceframe_count, traceframes_created, |
| free_space (), phex_nz (trace_buffer_hi - trace_buffer_lo, 0), |
| circular_trace_buffer, |
| disconnected_tracing, |
| phex_nz (tracing_start_time, sizeof (tracing_start_time)), |
| phex_nz (tracing_stop_time, sizeof (tracing_stop_time)), |
| buf1, buf2); |
| } |
| |
| static void |
| cmd_qtp (char *own_buf) |
| { |
| ULONGEST num, addr; |
| struct tracepoint *tpoint; |
| const char *packet = own_buf; |
| |
| packet += strlen ("qTP:"); |
| |
| packet = unpack_varlen_hex (packet, &num); |
| ++packet; /* skip a colon */ |
| packet = unpack_varlen_hex (packet, &addr); |
| |
| /* 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; |
| } |
| |
| sprintf (own_buf, "V%" PRIu64 ":%" PRIu64 "", tpoint->hit_count, |
| tpoint->traceframe_usage); |
| } |
| |
| /* State variables to help return all the tracepoint bits. */ |
| static struct tracepoint *cur_tpoint; |
| static unsigned int cur_action; |
| static unsigned int cur_step_action; |
| static struct source_string *cur_source_string; |
| static struct trace_state_variable *cur_tsv; |
| |
| /* Compose a response that is an imitation of the syntax by which the |
| tracepoint was originally downloaded. */ |
| |
| static void |
| response_tracepoint (char *packet, struct tracepoint *tpoint) |
| { |
| char *buf; |
| |
| sprintf (packet, "T%x:%s:%c:%" PRIx64 ":%" PRIx64, tpoint->number, |
| paddress (tpoint->address), |
| (tpoint->enabled ? 'E' : 'D'), tpoint->step_count, |
| tpoint->pass_count); |
| if (tpoint->type == fast_tracepoint) |
| sprintf (packet + strlen (packet), ":F%x", tpoint->orig_size); |
| else if (tpoint->type == static_tracepoint) |
| sprintf (packet + strlen (packet), ":S"); |
| |
| if (tpoint->cond) |
| { |
| buf = gdb_unparse_agent_expr (tpoint->cond); |
| sprintf (packet + strlen (packet), ":X%x,%s", |
| tpoint->cond->length, buf); |
| free (buf); |
| } |
| } |
| |
| /* Compose a response that is an imitation of the syntax by which the |
| tracepoint action was originally downloaded (with the difference |
| that due to the way we store the actions, this will output a packet |
| per action, while GDB could have combined more than one action |
| per-packet. */ |
| |
| static void |
| response_action (char *packet, struct tracepoint *tpoint, |
| char *taction, int step) |
| { |
| sprintf (packet, "%c%x:%s:%s", |
| (step ? 'S' : 'A'), tpoint->number, paddress (tpoint->address), |
| taction); |
| } |
| |
| /* Compose a response that is an imitation of the syntax by which the |
| tracepoint source piece was originally downloaded. */ |
| |
| static void |
| response_source (char *packet, |
| struct tracepoint *tpoint, struct source_string *src) |
| { |
| char *buf; |
| int len; |
| |
| len = strlen (src->str); |
| buf = (char *) alloca (len * 2 + 1); |
| bin2hex ((gdb_byte *) src->str, buf, len); |
| |
| sprintf (packet, "Z%x:%s:%s:%x:%x:%s", |
| tpoint->number, paddress (tpoint->address), |
| src->type, 0, len, buf); |
| } |
| |
| /* Return the first piece of tracepoint definition, and initialize the |
| state machine that will iterate through all the tracepoint |
| bits. */ |
| |
| static void |
| cmd_qtfp (char *packet) |
| { |
| trace_debug ("Returning first tracepoint definition piece"); |
| |
| cur_tpoint = tracepoints; |
| cur_action = cur_step_action = 0; |
| cur_source_string = NULL; |
| |
| if (cur_tpoint) |
| response_tracepoint (packet, cur_tpoint); |
| else |
| strcpy (packet, "l"); |
| } |
| |
| /* Return additional pieces of tracepoint definition. Each action and |
| stepping action must go into its own packet, because of packet size |
| limits, and so we use state variables to deliver one piece at a |
| time. */ |
| |
| static void |
| cmd_qtsp (char *packet) |
| { |
| trace_debug ("Returning subsequent tracepoint definition piece"); |
| |
| if (!cur_tpoint) |
| { |
| /* This case would normally never occur, but be prepared for |
| GDB misbehavior. */ |
| strcpy (packet, "l"); |
| } |
| else if (cur_action < cur_tpoint->numactions) |
| { |
| response_action (packet, cur_tpoint, |
| cur_tpoint->actions_str[cur_action], 0); |
| ++cur_action; |
| } |
| else if (cur_step_action < cur_tpoint->num_step_actions) |
| { |
| response_action (packet, cur_tpoint, |
| cur_tpoint->step_actions_str[cur_step_action], 1); |
| ++cur_step_action; |
| } |
| else if ((cur_source_string |
| ? cur_source_string->next |
| : cur_tpoint->source_strings)) |
| { |
| if (cur_source_string) |
| cur_source_string = cur_source_string->next; |
| else |
| cur_source_string = cur_tpoint->source_strings; |
| response_source (packet, cur_tpoint, cur_source_string); |
| } |
| else |
| { |
| cur_tpoint = cur_tpoint->next; |
| cur_action = cur_step_action = 0; |
| cur_source_string = NULL; |
| if (cur_tpoint) |
| response_tracepoint (packet, cur_tpoint); |
| else |
| strcpy (packet, "l"); |
| } |
| } |
| |
| /* Compose a response that is an imitation of the syntax by which the |
| trace state variable was originally downloaded. */ |
| |
| static void |
| response_tsv (char *packet, struct trace_state_variable *tsv) |
| { |
| char *buf = (char *) ""; |
| int namelen; |
| |
| if (tsv->name) |
| { |
| namelen = strlen (tsv->name); |
| buf = (char *) alloca (namelen * 2 + 1); |
| bin2hex ((gdb_byte *) tsv->name, buf, namelen); |
| } |
| |
| sprintf (packet, "%x:%s:%x:%s", tsv->number, phex_nz (tsv->initial_value, 0), |
| tsv->getter ? 1 : 0, buf); |
| } |
| |
| /* Return the first trace state variable definition, and initialize |
| the state machine that will iterate through all the tsv bits. */ |
| |
| static void |
| cmd_qtfv (char *packet) |
| { |
| trace_debug ("Returning first trace state variable definition"); |
| |
| cur_tsv = trace_state_variables; |
| |
| if (cur_tsv) |
| response_tsv (packet, cur_tsv); |
| else |
| strcpy (packet, "l"); |
| } |
| |
| /* Return additional trace state variable definitions. */ |
| |
| static void |
| cmd_qtsv (char *packet) |
| { |
| trace_debug ("Returning additional trace state variable definition"); |
| |
| if (cur_tsv) |
| { |
| cur_tsv = cur_tsv->next; |
| if (cur_tsv) |
| response_tsv (packet, cur_tsv); |
| else |
| strcpy (packet, "l"); |
| } |
| else |
| strcpy (packet, "l"); |
| } |
| |
| /* Return the first static tracepoint marker, and initialize the state |
| machine that will iterate through all the static tracepoints |
| markers. */ |
| |
| static void |
| cmd_qtfstm (char *packet) |
| { |
| if (!maybe_write_ipa_ust_not_loaded (packet)) |
| run_inferior_command (packet, strlen (packet) + 1); |
| } |
| |
| /* Return additional static tracepoints markers. */ |
| |
| static void |
| cmd_qtsstm (char *packet) |
| { |
| if (!maybe_write_ipa_ust_not_loaded (packet)) |
| run_inferior_command (packet, strlen (packet) + 1); |
| } |
| |
| /* Return the definition of the static tracepoint at a given address. |
| Result packet is the same as qTsST's. */ |
| |
| static void |
| cmd_qtstmat (char *packet) |
| { |
| if (!maybe_write_ipa_ust_not_loaded (packet)) |
| run_inferior_command (packet, strlen (packet) + 1); |
| } |
| |
| /* Sent the agent a command to close it. */ |
| |
| void |
| gdb_agent_about_to_close (int pid) |
| { |
| char buf[IPA_CMD_BUF_SIZE]; |
| |
| if (!maybe_write_ipa_not_loaded (buf)) |
| { |
| struct thread_info *saved_thread; |
| |
| saved_thread = current_thread; |
| |
| /* Find any thread which belongs to process PID. */ |
| current_thread = find_any_thread_of_pid (pid); |
| |
| strcpy (buf, "close"); |
| |
| run_inferior_command (buf, strlen (buf) + 1); |
| |
| current_thread = saved_thread; |
| } |
| } |
| |
| /* Return the minimum instruction size needed for fast tracepoints as a |
| hexadecimal number. */ |
| |
| static void |
| cmd_qtminftpilen (char *packet) |
| { |
| if (current_thread == NULL) |
| { |
| /* Indicate that the minimum length is currently unknown. */ |
| strcpy (packet, "0"); |
| return; |
| } |
| |
| sprintf (packet, "%x", target_get_min_fast_tracepoint_insn_len ()); |
| } |
| |
| /* Respond to qTBuffer packet with a block of raw data from the trace |
| buffer. GDB may ask for a lot, but we are allowed to reply with |
| only as much as will fit within packet limits or whatever. */ |
| |
| static void |
| cmd_qtbuffer (char *own_buf) |
| { |
| ULONGEST offset, num, tot; |
| unsigned char *tbp; |
| const char *packet = own_buf; |
| |
| packet += strlen ("qTBuffer:"); |
| |
| packet = unpack_varlen_hex (packet, &offset); |
| ++packet; /* skip a comma */ |
| unpack_varlen_hex (packet, &num); |
| |
| trace_debug ("Want to get trace buffer, %d bytes at offset 0x%s", |
| (int) num, phex_nz (offset, 0)); |
| |
| tot = (trace_buffer_hi - trace_buffer_lo) - free_space (); |
| |
| /* If we're right at the end, reply specially that we're done. */ |
| if (offset == tot) |
| { |
| strcpy (own_buf, "l"); |
| return; |
| } |
| |
| /* Object to any other out-of-bounds request. */ |
| if (offset > tot) |
| { |
| write_enn (own_buf); |
| return; |
| } |
| |
| /* Compute the pointer corresponding to the given offset, accounting |
| for wraparound. */ |
| tbp = trace_buffer_start + offset; |
| if (tbp >= trace_buffer_wrap) |
| tbp -= (trace_buffer_wrap - trace_buffer_lo); |
| |
| /* Trim to the remaining bytes if we're close to the end. */ |
| if (num > tot - offset) |
| num = tot - offset; |
| |
| /* Trim to available packet size. */ |
| if (num >= (PBUFSIZ - 16) / 2 ) |
| num = (PBUFSIZ - 16) / 2; |
| |
| bin2hex (tbp, own_buf, num); |
| } |
| |
| static void |
| cmd_bigqtbuffer_circular (char *own_buf) |
| { |
| ULONGEST val; |
| char *packet = own_buf; |
| |
| packet += strlen ("QTBuffer:circular:"); |
| |
| unpack_varlen_hex (packet, &val); |
| circular_trace_buffer = val; |
| trace_debug ("Trace buffer is now %s", |
| circular_trace_buffer ? "circular" : "linear"); |
| write_ok (own_buf); |
| } |
| |
| static void |
| cmd_bigqtbuffer_size (char *own_buf) |
| { |
| ULONGEST val; |
| LONGEST sval; |
| char *packet = own_buf; |
| |
| /* Can't change the size during a tracing run. */ |
| if (tracing) |
| { |
| write_enn (own_buf); |
| return; |
| } |
| |
| packet += strlen ("QTBuffer:size:"); |
| |
| /* -1 is sent as literal "-1". */ |
| if (strcmp (packet, "-1") == 0) |
| sval = DEFAULT_TRACE_BUFFER_SIZE; |
| else |
| { |
| unpack_varlen_hex (packet, &val); |
| sval = (LONGEST) val; |
| } |
| |
| init_trace_buffer (sval); |
| trace_debug ("Trace buffer is now %s bytes", |
| plongest (trace_buffer_size)); |
| write_ok (own_buf); |
| } |
| |
| static void |
| cmd_qtnotes (char *own_buf) |
| { |
| size_t nbytes; |
| char *saved, *user, *notes, *stopnote; |
| char *packet = own_buf; |
| |
| packet += strlen ("QTNotes:"); |
| |
| while (*packet) |
| { |
| if (startswith (packet, "user:")) |
| { |
| packet += strlen ("user:"); |
| saved = packet; |
| packet = strchr (packet, ';'); |
| nbytes = (packet - saved) / 2; |
| user = (char *) xmalloc (nbytes + 1); |
| nbytes = hex2bin (saved, (gdb_byte *) user, nbytes); |
| user[nbytes] = '\0'; |
| ++packet; /* skip the semicolon */ |
| trace_debug ("User is '%s'", user); |
| xfree (tracing_user_name); |
| tracing_user_name = user; |
| } |
| else if (startswith (packet, "notes:")) |
| { |
| packet += strlen ("notes:"); |
| saved = packet; |
| packet = strchr (packet, ';'); |
| nbytes = (packet - saved) / 2; |
| notes = (char *) xmalloc (nbytes + 1); |
| nbytes = hex2bin (saved, (gdb_byte *) notes, nbytes); |
| notes[nbytes] = '\0'; |
| ++packet; /* skip the semicolon */ |
| trace_debug ("Notes is '%s'", notes); |
| xfree (tracing_notes); |
| tracing_notes = notes; |
| } |
| else if (startswith (packet, "tstop:")) |
| { |
| packet += strlen ("tstop:"); |
| saved = packet; |
| packet = strchr (packet, ';'); |
| nbytes = (packet - saved) / 2; |
| stopnote = (char *) xmalloc (nbytes + 1); |
| nbytes = hex2bin (saved, (gdb_byte *) stopnote, nbytes); |
| stopnote[nbytes] = '\0'; |
| ++packet; /* skip the semicolon */ |
| trace_debug ("tstop note is '%s'", stopnote); |
| xfree (tracing_stop_note); |
| tracing_stop_note = stopnote; |
| } |
| else |
| break; |
| } |
| |
| write_ok (own_buf); |
| } |
| |
| int |
| handle_tracepoint_general_set (char *packet) |
| { |
| if (strcmp ("QTinit", packet) == 0) |
| { |
| cmd_qtinit (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTDP:")) |
| { |
| cmd_qtdp (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTDPsrc:")) |
| { |
| cmd_qtdpsrc (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTEnable:")) |
| { |
| cmd_qtenable_disable (packet, 1); |
| return 1; |
| } |
| else if (startswith (packet, "QTDisable:")) |
| { |
| cmd_qtenable_disable (packet, 0); |
| return 1; |
| } |
| else if (startswith (packet, "QTDV:")) |
| { |
| cmd_qtdv (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTro:")) |
| { |
| cmd_qtro (packet); |
| return 1; |
| } |
| else if (strcmp ("QTStart", packet) == 0) |
| { |
| cmd_qtstart (packet); |
| return 1; |
| } |
| else if (strcmp ("QTStop", packet) == 0) |
| { |
| cmd_qtstop (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTDisconnected:")) |
| { |
| cmd_qtdisconnected (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTFrame:")) |
| { |
| cmd_qtframe (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTBuffer:circular:")) |
| { |
| cmd_bigqtbuffer_circular (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTBuffer:size:")) |
| { |
| cmd_bigqtbuffer_size (packet); |
| return 1; |
| } |
| else if (startswith (packet, "QTNotes:")) |
| { |
| cmd_qtnotes (packet); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| int |
| handle_tracepoint_query (char *packet) |
| { |
| if (strcmp ("qTStatus", packet) == 0) |
| { |
| cmd_qtstatus (packet); |
| return 1; |
| } |
| else if (startswith (packet, "qTP:")) |
| { |
| cmd_qtp (packet); |
| return 1; |
| } |
| else if (strcmp ("qTfP", packet) == 0) |
| { |
| cmd_qtfp (packet); |
| return 1; |
| } |
| else if (strcmp ("qTsP", packet) == 0) |
| { |
| cmd_qtsp (packet); |
| return 1; |
| } |
| else if (strcmp ("qTfV", packet) == 0) |
| { |
| cmd_qtfv (packet); |
| return 1; |
| } |
| else if (strcmp ("qTsV", packet) == 0) |
| { |
| cmd_qtsv (packet); |
| return 1; |
| } |
| else if (startswith (packet, "qTV:")) |
| { |
| cmd_qtv (packet); |
| return 1; |
| } |
| else if (startswith (packet, "qTBuffer:")) |
| { |
| cmd_qtbuffer (packet); |
| return 1; |
| } |
| else if (strcmp ("qTfSTM", packet) == 0) |
| { |
| cmd_qtfstm (packet); |
| return 1; |
| } |
| else if (strcmp ("qTsSTM", packet) == 0) |
| { |
| cmd_qtsstm (packet); |
| return 1; |
| } |
| else if (startswith (packet, "qTSTMat:")) |
| { |
| cmd_qtstmat (packet); |
| return 1; |
| } |
| else if (strcmp ("qTMinFTPILen", packet) == 0) |
| { |
| cmd_qtminftpilen (packet); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| #endif |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Call this when thread TINFO has hit the tracepoint defined by |
| TP_NUMBER and TP_ADDRESS, and that tracepoint has a while-stepping |
| action. This adds a while-stepping collecting state item to the |
| threads' collecting state list, so that we can keep track of |
| multiple simultaneous while-stepping actions being collected by the |
| same thread. This can happen in cases like: |
| |
| ff0001 INSN1 <-- TP1, while-stepping 10 collect $regs |
| ff0002 INSN2 |
| ff0003 INSN3 <-- TP2, collect $regs |
| ff0004 INSN4 <-- TP3, while-stepping 10 collect $regs |
| ff0005 INSN5 |
| |
| Notice that when instruction INSN5 is reached, the while-stepping |
| actions of both TP1 and TP3 are still being collected, and that TP2 |
| had been collected meanwhile. The whole range of ff0001-ff0005 |
| should be single-stepped, due to at least TP1's while-stepping |
| action covering the whole range. */ |
| |
| static void |
| add_while_stepping_state (struct thread_info *tinfo, |
| int tp_number, CORE_ADDR tp_address) |
| { |
| struct wstep_state *wstep = XNEW (struct wstep_state); |
| |
| wstep->next = tinfo->while_stepping; |
| |
| wstep->tp_number = tp_number; |
| wstep->tp_address = tp_address; |
| wstep->current_step = 0; |
| |
| tinfo->while_stepping = wstep; |
| } |
| |
| /* Release the while-stepping collecting state WSTEP. */ |
| |
| static void |
| release_while_stepping_state (struct wstep_state *wstep) |
| { |
| free (wstep); |
| } |
| |
| /* Release all while-stepping collecting states currently associated |
| with thread TINFO. */ |
| |
| void |
| release_while_stepping_state_list (struct thread_info *tinfo) |
| { |
| struct wstep_state *head; |
| |
| while (tinfo->while_stepping) |
| { |
| head = tinfo->while_stepping; |
| tinfo->while_stepping = head->next; |
| release_while_stepping_state (head); |
| } |
| } |
| |
| /* If TINFO was handling a 'while-stepping' action, the step has |
| finished, so collect any step data needed, and check if any more |
| steps are required. Return true if the thread was indeed |
| collecting tracepoint data, false otherwise. */ |
| |
| int |
| tracepoint_finished_step (struct thread_info *tinfo, CORE_ADDR stop_pc) |
| { |
| struct tracepoint *tpoint; |
| struct wstep_state *wstep; |
| struct wstep_state **wstep_link; |
| struct trap_tracepoint_ctx ctx; |
| |
| /* Pull in fast tracepoint trace frames from the inferior lib buffer into |
| our buffer. */ |
| if (agent_loaded_p ()) |
| upload_fast_traceframes (); |
| |
| /* Check if we were indeed collecting data for one of more |
| tracepoints with a 'while-stepping' count. */ |
| if (tinfo->while_stepping == NULL) |
| return 0; |
| |
| if (!tracing) |
| { |
| /* We're not even tracing anymore. Stop this thread from |
| collecting. */ |
| release_while_stepping_state_list (tinfo); |
| |
| /* The thread had stopped due to a single-step request indeed |
| explained by a tracepoint. */ |
| return 1; |
| } |
| |
| wstep = tinfo->while_stepping; |
| wstep_link = &tinfo->while_stepping; |
| |
| trace_debug ("Thread %s finished a single-step for tracepoint %d at 0x%s", |
| target_pid_to_str (tinfo->id), |
| wstep->tp_number, paddress (wstep->tp_address)); |
| |
| ctx.base.type = trap_tracepoint; |
| ctx.regcache = get_thread_regcache (tinfo, 1); |
| |
| while (wstep != NULL) |
| { |
| tpoint = find_tracepoint (wstep->tp_number, wstep->tp_address); |
| if (tpoint == NULL) |
| { |
| trace_debug ("NO TRACEPOINT %d at 0x%s FOR THREAD %s!", |
| wstep->tp_number, paddress (wstep->tp_address), |
| target_pid_to_str (tinfo->id)); |
| |
| /* Unlink. */ |
| *wstep_link = wstep->next; |
| release_while_stepping_state (wstep); |
| wstep = *wstep_link; |
| continue; |
| } |
| |
| /* We've just finished one step. */ |
| ++wstep->current_step; |
| |
| /* Collect data. */ |
| collect_data_at_step ((struct tracepoint_hit_ctx *) &ctx, |
| stop_pc, tpoint, wstep->current_step); |
| |
| if (wstep->current_step >= tpoint->step_count) |
| { |
| /* The requested numbers of steps have occurred. */ |
| trace_debug ("Thread %s done stepping for tracepoint %d at 0x%s", |
| target_pid_to_str (tinfo->id), |
| wstep->tp_number, paddress (wstep->tp_address)); |
| |
| /* Unlink the wstep. */ |
| *wstep_link = wstep->next; |
| release_while_stepping_state (wstep); |
| wstep = *wstep_link; |
| |
| /* Only check the hit count now, which ensure that we do all |
| our stepping before stopping the run. */ |
| if (tpoint->pass_count > 0 |
| && tpoint->hit_count >= tpoint->pass_count |
| && stopping_tracepoint == NULL) |
| stopping_tracepoint = tpoint; |
| } |
| else |
| { |
| /* Keep single-stepping until the requested numbers of steps |
| have occurred. */ |
| wstep_link = &wstep->next; |
| wstep = *wstep_link; |
| } |
| |
| if (stopping_tracepoint |
| || trace_buffer_is_full |
| || expr_eval_result != expr_eval_no_error) |
| { |
| stop_tracing (); |
| break; |
| } |
| } |
| |
| return 1; |
| } |
| |
| /* Handle any internal tracing control breakpoint hits. That means, |
| pull traceframes from the IPA to our buffer, and syncing both |
| tracing agents when the IPA's tracing stops for some reason. */ |
| |
| int |
| handle_tracepoint_bkpts (struct thread_info *tinfo, CORE_ADDR stop_pc) |
| { |
| /* Pull in fast tracepoint trace frames from the inferior in-process |
| agent's buffer into our buffer. */ |
| |
| if (!agent_loaded_p ()) |
| return 0; |
| |
| upload_fast_traceframes (); |
| |
| /* Check if the in-process agent had decided we should stop |
| tracing. */ |
| if (stop_pc == ipa_sym_addrs.addr_stop_tracing) |
| { |
| int ipa_trace_buffer_is_full; |
| CORE_ADDR ipa_stopping_tracepoint; |
| int ipa_expr_eval_result; |
| CORE_ADDR ipa_error_tracepoint; |
| |
| trace_debug ("lib stopped at stop_tracing"); |
| |
| read_inferior_integer (ipa_sym_addrs.addr_trace_buffer_is_full, |
| &ipa_trace_buffer_is_full); |
| |
| read_inferior_data_pointer (ipa_sym_addrs.addr_stopping_tracepoint, |
| &ipa_stopping_tracepoint); |
| write_inferior_data_pointer (ipa_sym_addrs.addr_stopping_tracepoint, 0); |
| |
| read_inferior_data_pointer (ipa_sym_addrs.addr_error_tracepoint, |
| &ipa_error_tracepoint); |
| write_inferior_data_pointer (ipa_sym_addrs.addr_error_tracepoint, 0); |
| |
| read_inferior_integer (ipa_sym_addrs.addr_expr_eval_result, |
| &ipa_expr_eval_result); |
| write_inferior_integer (ipa_sym_addrs.addr_expr_eval_result, 0); |
| |
| trace_debug ("lib: trace_buffer_is_full: %d, " |
| "stopping_tracepoint: %s, " |
| "ipa_expr_eval_result: %d, " |
| "error_tracepoint: %s, ", |
| ipa_trace_buffer_is_full, |
| paddress (ipa_stopping_tracepoint), |
| ipa_expr_eval_result, |
| paddress (ipa_error_tracepoint)); |
| |
| if (debug_threads) |
| { |
| if (ipa_trace_buffer_is_full) |
| trace_debug ("lib stopped due to full buffer."); |
| if (ipa_stopping_tracepoint) |
| trace_debug ("lib stopped due to tpoint"); |
| if (ipa_error_tracepoint) |
| trace_debug ("lib stopped due to error"); |
| } |
| |
| if (ipa_stopping_tracepoint != 0) |
| { |
| stopping_tracepoint |
| = fast_tracepoint_from_ipa_tpoint_address (ipa_stopping_tracepoint); |
| } |
| else if (ipa_expr_eval_result != expr_eval_no_error) |
| { |
| expr_eval_result = ipa_expr_eval_result; |
| error_tracepoint |
| = fast_tracepoint_from_ipa_tpoint_address (ipa_error_tracepoint); |
| } |
| stop_tracing (); |
| return 1; |
| } |
| else if (stop_pc == ipa_sym_addrs.addr_flush_trace_buffer) |
| { |
| trace_debug ("lib stopped at flush_trace_buffer"); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Return true if TINFO just hit a tracepoint. Collect data if |
| so. */ |
| |
| int |
| tracepoint_was_hit (struct thread_info *tinfo, CORE_ADDR stop_pc) |
| { |
| struct tracepoint *tpoint; |
| int ret = 0; |
| struct trap_tracepoint_ctx ctx; |
| |
| /* Not tracing, don't handle. */ |
| if (!tracing) |
| return 0; |
| |
| ctx.base.type = trap_tracepoint; |
| ctx.regcache = get_thread_regcache (tinfo, 1); |
| |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| { |
| /* Note that we collect fast tracepoints here as well. We'll |
| step over the fast tracepoint jump later, which avoids the |
| double collect. However, we don't collect for static |
| tracepoints here, because UST markers are compiled in program, |
| and probes will be executed in program. So static tracepoints |
| are collected there. */ |
| if (tpoint->enabled && stop_pc == tpoint->address |
| && tpoint->type != static_tracepoint) |
| { |
| trace_debug ("Thread %s at address of tracepoint %d at 0x%s", |
| target_pid_to_str (tinfo->id), |
| tpoint->number, paddress (tpoint->address)); |
| |
| /* Test the condition if present, and collect if true. */ |
| if (!tpoint->cond |
| || (condition_true_at_tracepoint |
| ((struct tracepoint_hit_ctx *) &ctx, tpoint))) |
| collect_data_at_tracepoint ((struct tracepoint_hit_ctx *) &ctx, |
| stop_pc, tpoint); |
| |
| if (stopping_tracepoint |
| || trace_buffer_is_full |
| || expr_eval_result != expr_eval_no_error) |
| { |
| stop_tracing (); |
| } |
| /* If the tracepoint had a 'while-stepping' action, then set |
| the thread to collect this tracepoint on the following |
| single-steps. */ |
| else if (tpoint->step_count > 0) |
| { |
| add_while_stepping_state (tinfo, |
| tpoint->number, tpoint->address); |
| } |
| |
| ret = 1; |
| } |
| } |
| |
| return ret; |
| } |
| |
| #endif |
| |
| #if defined IN_PROCESS_AGENT && defined HAVE_UST |
| struct ust_marker_data; |
| static void collect_ust_data_at_tracepoint (struct tracepoint_hit_ctx *ctx, |
| struct traceframe *tframe); |
| #endif |
| |
| /* Create a trace frame for the hit of the given tracepoint in the |
| given thread. */ |
| |
| static void |
| collect_data_at_tracepoint (struct tracepoint_hit_ctx *ctx, CORE_ADDR stop_pc, |
| struct tracepoint *tpoint) |
| { |
| struct traceframe *tframe; |
| int acti; |
| |
| /* Only count it as a hit when we actually collect data. */ |
| tpoint->hit_count++; |
| |
| /* If we've exceeded a defined pass count, record the event for |
| later, and finish the collection for this hit. This test is only |
| for nonstepping tracepoints, stepping tracepoints test at the end |
| of their while-stepping loop. */ |
| if (tpoint->pass_count > 0 |
| && tpoint->hit_count >= tpoint->pass_count |
| && tpoint->step_count == 0 |
| && stopping_tracepoint == NULL) |
| stopping_tracepoint = tpoint; |
| |
| trace_debug ("Making new traceframe for tracepoint %d at 0x%s, hit %" PRIu64, |
| tpoint->number, paddress (tpoint->address), tpoint->hit_count); |
| |
| tframe = add_traceframe (tpoint); |
| |
| if (tframe) |
| { |
| for (acti = 0; acti < tpoint->numactions; ++acti) |
| { |
| #ifndef IN_PROCESS_AGENT |
| trace_debug ("Tracepoint %d at 0x%s about to do action '%s'", |
| tpoint->number, paddress (tpoint->address), |
| tpoint->actions_str[acti]); |
| #endif |
| |
| do_action_at_tracepoint (ctx, stop_pc, tpoint, tframe, |
| tpoint->actions[acti]); |
| } |
| |
| finish_traceframe (tframe); |
| } |
| |
| if (tframe == NULL && tracing) |
| trace_buffer_is_full = 1; |
| } |
| |
| #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) |
| { |
| struct traceframe *tframe; |
| int acti; |
| |
| trace_debug ("Making new step traceframe for " |
| "tracepoint %d at 0x%s, step %d of %" PRIu64 ", hit %" PRIu64, |
| tpoint->number, paddress (tpoint->address), |
| current_step, tpoint->step_count, |
| tpoint->hit_count); |
| |
| tframe = add_traceframe (tpoint); |
| |
| if (tframe) |
| { |
| for (acti = 0; acti < tpoint->num_step_actions; ++acti) |
| { |
| trace_debug ("Tracepoint %d at 0x%s about to do step action '%s'", |
| tpoint->number, paddress (tpoint->address), |
| tpoint->step_actions_str[acti]); |
| |
| do_action_at_tracepoint (ctx, stop_pc, tpoint, tframe, |
| tpoint->step_actions[acti]); |
| } |
| |
| finish_traceframe (tframe); |
| } |
| |
| if (tframe == NULL && tracing) |
| trace_buffer_is_full = 1; |
| } |
| |
| #endif |
| |
| #ifdef IN_PROCESS_AGENT |
| /* The target description index for IPA. Passed from gdbserver, used |
| to select ipa_tdesc. */ |
| EXTERN_C_PUSH |
| IP_AGENT_EXPORT_VAR int ipa_tdesc_idx; |
| EXTERN_C_POP |
| #endif |
| |
| static struct regcache * |
| get_context_regcache (struct tracepoint_hit_ctx *ctx) |
| { |
| struct regcache *regcache = NULL; |
| #ifdef IN_PROCESS_AGENT |
| const struct target_desc *ipa_tdesc = get_ipa_tdesc (ipa_tdesc_idx); |
| |
| if (ctx->type == fast_tracepoint) |
| { |
| struct fast_tracepoint_ctx *fctx = (struct fast_tracepoint_ctx *) ctx; |
| if (!fctx->regcache_initted) |
| { |
| fctx->regcache_initted = 1; |
| init_register_cache (&fctx->regcache, ipa_tdesc, fctx->regspace); |
| supply_regblock (&fctx->regcache, NULL); |
| supply_fast_tracepoint_registers (&fctx->regcache, fctx->regs); |
| } |
| regcache = &fctx->regcache; |
| } |
| #ifdef HAVE_UST |
| if (ctx->type == static_tracepoint) |
| { |
| struct static_tracepoint_ctx *sctx |
| = (struct static_tracepoint_ctx *) ctx; |
| |
| if (!sctx->regcache_initted) |
| { |
| sctx->regcache_initted = 1; |
| init_register_cache (&sctx->regcache, ipa_tdesc, sctx->regspace); |
| supply_regblock (&sctx->regcache, NULL); |
| /* Pass down the tracepoint address, because REGS doesn't |
| include the PC, but we know what it must have been. */ |
| supply_static_tracepoint_registers (&sctx->regcache, |
| (const unsigned char *) |
| sctx->regs, |
| sctx->tpoint->address); |
| } |
| regcache = &sctx->regcache; |
| } |
| #endif |
| #else |
| if (ctx->type == trap_tracepoint) |
| { |
| struct trap_tracepoint_ctx *tctx = (struct trap_tracepoint_ctx *) ctx; |
| regcache = tctx->regcache; |
| } |
| #endif |
| |
| gdb_assert (regcache != NULL); |
| |
| return regcache; |
| } |
| |
| 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) |
| { |
| enum eval_result_type err; |
| |
| switch (taction->type) |
| { |
| case 'M': |
| { |
| struct collect_memory_action *maction; |
| struct eval_agent_expr_context ax_ctx; |
| |
| maction = (struct collect_memory_action *) taction; |
| ax_ctx.regcache = NULL; |
| ax_ctx.tframe = tframe; |
| ax_ctx.tpoint = tpoint; |
| |
| trace_debug ("Want to collect %s bytes at 0x%s (basereg %d)", |
| pulongest (maction->len), |
| paddress (maction->addr), maction->basereg); |
| /* (should use basereg) */ |
| agent_mem_read (&ax_ctx, NULL, (CORE_ADDR) maction->addr, |
| maction->len); |
| break; |
| } |
| case 'R': |
| { |
| unsigned char *regspace; |
| struct regcache tregcache; |
| struct regcache *context_regcache; |
| int regcache_size; |
| |
| trace_debug ("Want to collect registers"); |
| |
| context_regcache = get_context_regcache (ctx); |
| regcache_size = register_cache_size (context_regcache->tdesc); |
| |
| /* Collect all registers for now. */ |
| regspace = add_traceframe_block (tframe, tpoint, 1 + regcache_size); |
| if (regspace == NULL) |
| { |
| trace_debug ("Trace buffer block allocation failed, skipping"); |
| break; |
| } |
| /* Identify a register block. */ |
| *regspace = 'R'; |
| |
| /* Wrap the regblock in a register cache (in the stack, we |
| don't want to malloc here). */ |
| init_register_cache (&tregcache, context_regcache->tdesc, |
| regspace + 1); |
| |
| /* Copy the register data to the regblock. */ |
| regcache_cpy (&tregcache, context_regcache); |
| |
| #ifndef IN_PROCESS_AGENT |
| /* On some platforms, trap-based tracepoints will have the PC |
| pointing to the next instruction after the trap, but we |
| don't want the user or GDB trying to guess whether the |
| saved PC needs adjusting; so always record the adjusted |
| stop_pc. Note that we can't use tpoint->address instead, |
| since it will be wrong for while-stepping actions. This |
| adjustment is a nop for fast tracepoints collected from the |
| in-process lib (but not if GDBserver is collecting one |
| preemptively), since the PC had already been adjusted to |
| contain the tracepoint's address by the jump pad. */ |
| trace_debug ("Storing stop pc (0x%s) in regblock", |
| paddress (stop_pc)); |
| |
| /* This changes the regblock, not the thread's |
| regcache. */ |
| regcache_write_pc (&tregcache, stop_pc); |
| #endif |
| } |
| break; |
| case 'X': |
| { |
| struct eval_expr_action *eaction; |
| struct eval_agent_expr_context ax_ctx; |
| |
| eaction = (struct eval_expr_action *) taction; |
| ax_ctx.regcache = get_context_regcache (ctx); |
| ax_ctx.tframe = tframe; |
| ax_ctx.tpoint = tpoint; |
| |
| trace_debug ("Want to evaluate expression"); |
| |
| err = gdb_eval_agent_expr (&ax_ctx, eaction->expr, NULL); |
| |
| if (err != expr_eval_no_error) |
| { |
| record_tracepoint_error (tpoint, "action expression", err); |
| return; |
| } |
| } |
| break; |
| case 'L': |
| { |
| #if defined IN_PROCESS_AGENT && defined HAVE_UST |
| trace_debug ("Want to collect static trace data"); |
| collect_ust_data_at_tracepoint (ctx, tframe); |
| #else |
| trace_debug ("warning: collecting static trace data, " |
| "but static tracepoints are not supported"); |
| #endif |
| } |
| break; |
| default: |
| trace_debug ("unknown trace action '%c', ignoring", taction->type); |
| break; |
| } |
| } |
| |
| static int |
| condition_true_at_tracepoint (struct tracepoint_hit_ctx *ctx, |
| struct tracepoint *tpoint) |
| { |
| ULONGEST value = 0; |
| enum eval_result_type err; |
| |
| /* Presently, gdbserver doesn't run compiled conditions, only the |
| IPA does. If the program stops at a fast tracepoint's address |
| (e.g., due to a breakpoint, trap tracepoint, or stepping), |
| gdbserver preemptively collect the fast tracepoint. Later, on |
| resume, gdbserver steps over the fast tracepoint like it steps |
| over breakpoints, so that the IPA doesn't see that fast |
| tracepoint. This avoids double collects of fast tracepoints in |
| that stopping scenario. Having gdbserver itself handle the fast |
| tracepoint gives the user a consistent view of when fast or trap |
| tracepoints are collected, compared to an alternative where only |
| trap tracepoints are collected on stop, and fast tracepoints on |
| resume. When a fast tracepoint is being processed by gdbserver, |
| it is always the non-compiled condition expression that is |
| used. */ |
| #ifdef IN_PROCESS_AGENT |
| if (tpoint->compiled_cond) |
| { |
| struct fast_tracepoint_ctx *fctx = (struct fast_tracepoint_ctx *) ctx; |
| err = ((condfn) (uintptr_t) (tpoint->compiled_cond)) (fctx->regs, &value); |
| } |
| else |
| #endif |
| { |
| struct eval_agent_expr_context ax_ctx; |
| |
| ax_ctx.regcache = get_context_regcache (ctx); |
| ax_ctx.tframe = NULL; |
| ax_ctx.tpoint = tpoint; |
| |
| err = gdb_eval_agent_expr (&ax_ctx, tpoint->cond, &value); |
| } |
| if (err != expr_eval_no_error) |
| { |
| record_tracepoint_error (tpoint, "condition", err); |
| /* The error case must return false. */ |
| return 0; |
| } |
| |
| trace_debug ("Tracepoint %d at 0x%s condition evals to %s", |
| tpoint->number, paddress (tpoint->address), |
| pulongest (value)); |
| return (value ? 1 : 0); |
| } |
| |
| /* Do memory copies for bytecodes. */ |
| /* Do the recording of memory blocks for actions and bytecodes. */ |
| |
| int |
| agent_mem_read (struct eval_agent_expr_context *ctx, |
| unsigned char *to, CORE_ADDR from, ULONGEST len) |
| { |
| unsigned char *mspace; |
| ULONGEST remaining = len; |
| unsigned short blocklen; |
| |
| /* If a 'to' buffer is specified, use it. */ |
| if (to != NULL) |
| { |
| read_inferior_memory (from, to, len); |
| return 0; |
| } |
| |
| /* Otherwise, create a new memory block in the trace buffer. */ |
| while (remaining > 0) |
| { |
| size_t sp; |
| |
| blocklen = (remaining > 65535 ? 65535 : remaining); |
| sp = 1 + sizeof (from) + sizeof (blocklen) + blocklen; |
| mspace = add_traceframe_block (ctx->tframe, ctx->tpoint, sp); |
| if (mspace == NULL) |
| return 1; |
| /* Identify block as a memory block. */ |
| *mspace = 'M'; |
| ++mspace; |
| /* Record address and size. */ |
| memcpy (mspace, &from, sizeof (from)); |
| mspace += sizeof (from); |
| memcpy (mspace, &blocklen, sizeof (blocklen)); |
| mspace += sizeof (blocklen); |
| /* Record the memory block proper. */ |
| read_inferior_memory (from, mspace, blocklen); |
| trace_debug ("%d bytes recorded", blocklen); |
| remaining -= blocklen; |
| from += blocklen; |
| } |
| return 0; |
| } |
| |
| int |
| agent_mem_read_string (struct eval_agent_expr_context *ctx, |
| unsigned char *to, CORE_ADDR from, ULONGEST len) |
| { |
| unsigned char *buf, *mspace; |
| ULONGEST remaining = len; |
| unsigned short blocklen, i; |
| |
| /* To save a bit of space, block lengths are 16-bit, so break large |
| requests into multiple blocks. Bordering on overkill for strings, |
| but it could happen that someone specifies a large max length. */ |
| while (remaining > 0) |
| { |
| size_t sp; |
| |
| blocklen = (remaining > 65535 ? 65535 : remaining); |
| /* We want working space to accumulate nonzero bytes, since |
| traceframes must have a predecided size (otherwise it gets |
| harder to wrap correctly for the circular case, etc). */ |
| buf = (unsigned char *) xmalloc (blocklen + 1); |
| for (i = 0; i < blocklen; ++i) |
| { |
| /* Read the string one byte at a time, in case the string is |
| at the end of a valid memory area - we don't want a |
| correctly-terminated string to engender segvio |
| complaints. */ |
| read_inferior_memory (from + i, buf + i, 1); |
| |
| if (buf[i] == '\0') |
| { |
| blocklen = i + 1; |
| /* Make sure outer loop stops now too. */ |
| remaining = blocklen; |
| break; |
| } |
| } |
| sp = 1 + sizeof (from) + sizeof (blocklen) + blocklen; |
| mspace = add_traceframe_block (ctx->tframe, ctx->tpoint, sp); |
| if (mspace == NULL) |
| { |
| xfree (buf); |
| return 1; |
| } |
| /* Identify block as a memory block. */ |
| *mspace = 'M'; |
| ++mspace; |
| /* Record address and size. */ |
| memcpy ((void *) mspace, (void *) &from, sizeof (from)); |
| mspace += sizeof (from); |
| memcpy ((void *) mspace, (void *) &blocklen, sizeof (blocklen)); |
| mspace += sizeof (blocklen); |
| /* Copy the string contents. */ |
| memcpy ((void *) mspace, (void *) buf, blocklen); |
| remaining -= blocklen; |
| from += blocklen; |
| xfree (buf); |
| } |
| return 0; |
| } |
| |
| /* Record the value of a trace state variable. */ |
| |
| int |
| agent_tsv_read (struct eval_agent_expr_context *ctx, int n) |
| { |
| unsigned char *vspace; |
| LONGEST val; |
| |
| vspace = add_traceframe_block (ctx->tframe, ctx->tpoint, |
| 1 + sizeof (n) + sizeof (LONGEST)); |
| if (vspace == NULL) |
| return 1; |
| /* Identify block as a variable. */ |
| *vspace = 'V'; |
| /* Record variable's number and value. */ |
| memcpy (vspace + 1, &n, sizeof (n)); |
| val = get_trace_state_variable_value (n); |
| memcpy (vspace + 1 + sizeof (n), &val, sizeof (val)); |
| trace_debug ("Variable %d recorded", n); |
| return 0; |
| } |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Callback for traceframe_walk_blocks, used to find a given block |
| type in a traceframe. */ |
| |
| static int |
| match_blocktype (char blocktype, unsigned char *dataptr, void *data) |
| { |
| char *wantedp = (char *) data; |
| |
| if (*wantedp == blocktype) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Walk over all traceframe blocks of the traceframe buffer starting |
| at DATABASE, of DATASIZE bytes long, and call CALLBACK for each |
| block found, passing in DATA unmodified. If CALLBACK returns true, |
| this returns a pointer to where the block is found. Returns NULL |
| if no callback call returned true, indicating that all blocks have |
| been walked. */ |
| |
| static unsigned char * |
| traceframe_walk_blocks (unsigned char *database, unsigned int datasize, |
| int tfnum, |
| int (*callback) (char blocktype, |
| unsigned char *dataptr, |
| void *data), |
| void *data) |
| { |
| unsigned char *dataptr; |
| |
| if (datasize == 0) |
| { |
| trace_debug ("traceframe %d has no data", tfnum); |
| return NULL; |
| } |
| |
| /* Iterate through a traceframe's blocks, looking for a block of the |
| requested type. */ |
| for (dataptr = database; |
| dataptr < database + datasize; |
| /* nothing */) |
| { |
| char blocktype; |
| unsigned short mlen; |
| |
| if (dataptr == trace_buffer_wrap) |
| { |
| /* Adjust to reflect wrapping part of the frame around to |
| the beginning. */ |
| datasize = dataptr - database; |
| dataptr = database = trace_buffer_lo; |
| } |
| |
| blocktype = *dataptr++; |
| |
| if ((*callback) (blocktype, dataptr, data)) |
| return dataptr; |
| |
| switch (blocktype) |
| { |
| case 'R': |
| /* Skip over the registers block. */ |
| dataptr += current_target_desc ()->registers_size; |
| break; |
| case 'M': |
| /* Skip over the memory block. */ |
| dataptr += sizeof (CORE_ADDR); |
| memcpy (&mlen, dataptr, sizeof (mlen)); |
| dataptr += (sizeof (mlen) + mlen); |
| break; |
| case 'V': |
| /* Skip over the TSV block. */ |
| dataptr += (sizeof (int) + sizeof (LONGEST)); |
| break; |
| case 'S': |
| /* Skip over the static trace data block. */ |
| memcpy (&mlen, dataptr, sizeof (mlen)); |
| dataptr += (sizeof (mlen) + mlen); |
| break; |
| default: |
| trace_debug ("traceframe %d has unknown block type 0x%x", |
| tfnum, blocktype); |
| return NULL; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* Look for the block of type TYPE_WANTED in the traceframe starting |
| at DATABASE of DATASIZE bytes long. TFNUM is the traceframe |
| number. */ |
| |
| static unsigned char * |
| traceframe_find_block_type (unsigned char *database, unsigned int datasize, |
| int tfnum, char type_wanted) |
| { |
| return traceframe_walk_blocks (database, datasize, tfnum, |
| match_blocktype, &type_wanted); |
| } |
| |
| static unsigned char * |
| traceframe_find_regblock (struct traceframe *tframe, int tfnum) |
| { |
| unsigned char *regblock; |
| |
| regblock = traceframe_find_block_type (tframe->data, |
| tframe->data_size, |
| tfnum, 'R'); |
| |
| if (regblock == NULL) |
| trace_debug ("traceframe %d has no register data", tfnum); |
| |
| return regblock; |
| } |
| |
| /* Get registers from a traceframe. */ |
| |
| int |
| fetch_traceframe_registers (int tfnum, struct regcache *regcache, int regnum) |
| { |
| unsigned char *dataptr; |
| struct tracepoint *tpoint; |
| struct traceframe *tframe; |
| |
| tframe = find_traceframe (tfnum); |
| |
| if (tframe == NULL) |
| { |
| trace_debug ("traceframe %d not found", tfnum); |
| return 1; |
| } |
| |
| dataptr = traceframe_find_regblock (tframe, tfnum); |
| if (dataptr == NULL) |
| { |
| /* Mark registers unavailable. */ |
| supply_regblock (regcache, NULL); |
| |
| /* We can generally guess at a PC, although this will be |
| misleading for while-stepping frames and multi-location |
| tracepoints. */ |
| tpoint = find_next_tracepoint_by_number (NULL, tframe->tpnum); |
| if (tpoint != NULL) |
| regcache_write_pc (regcache, tpoint->address); |
| } |
| else |
| supply_regblock (regcache, dataptr); |
| |
| return 0; |
| } |
| |
| static CORE_ADDR |
| traceframe_get_pc (struct traceframe *tframe) |
| { |
| struct regcache regcache; |
| unsigned char *dataptr; |
| const struct target_desc *tdesc = current_target_desc (); |
| |
| dataptr = traceframe_find_regblock (tframe, -1); |
| if (dataptr == NULL) |
| return 0; |
| |
| init_register_cache (®cache, tdesc, dataptr); |
| return regcache_read_pc (®cache); |
| } |
| |
| /* Read a requested block of memory from a trace frame. */ |
| |
| int |
| traceframe_read_mem (int tfnum, CORE_ADDR addr, |
| unsigned char *buf, ULONGEST length, |
| ULONGEST *nbytes) |
| { |
| struct traceframe *tframe; |
| unsigned char *database, *dataptr; |
| unsigned int datasize; |
| CORE_ADDR maddr; |
| unsigned short mlen; |
| |
| trace_debug ("traceframe_read_mem"); |
| |
| tframe = find_traceframe (tfnum); |
| |
| if (!tframe) |
| { |
| trace_debug ("traceframe %d not found", tfnum); |
| return 1; |
| } |
| |
| datasize = tframe->data_size; |
| database = dataptr = &tframe->data[0]; |
| |
| /* Iterate through a traceframe's blocks, looking for memory. */ |
| while ((dataptr = traceframe_find_block_type (dataptr, |
| datasize |
| - (dataptr - database), |
| tfnum, 'M')) != NULL) |
| { |
| memcpy (&maddr, dataptr, sizeof (maddr)); |
| dataptr += sizeof (maddr); |
| memcpy (&mlen, dataptr, sizeof (mlen)); |
| dataptr += sizeof (mlen); |
| trace_debug ("traceframe %d has %d bytes at %s", |
| tfnum, mlen, paddress (maddr)); |
| |
| /* If the block includes the first part of the desired range, |
| return as much it has; GDB will re-request the remainder, |
| which might be in a different block of this trace frame. */ |
| if (maddr <= addr && addr < (maddr + mlen)) |
| { |
| ULONGEST amt = (maddr + mlen) - addr; |
| if (amt > length) |
| amt = length; |
| |
| memcpy (buf, dataptr + (addr - maddr), amt); |
| *nbytes = amt; |
| return 0; |
| } |
| |
| /* Skip over this block. */ |
| dataptr += mlen; |
| } |
| |
| trace_debug ("traceframe %d has no memory data for the desired region", |
| tfnum); |
| |
| *nbytes = 0; |
| return 0; |
| } |
| |
| static int |
| traceframe_read_tsv (int tsvnum, LONGEST *val) |
| { |
| client_state &cs = get_client_state (); |
| int tfnum; |
| struct traceframe *tframe; |
| unsigned char *database, *dataptr; |
| unsigned int datasize; |
| int vnum; |
| int found = 0; |
| |
| trace_debug ("traceframe_read_tsv"); |
| |
| tfnum = cs.current_traceframe; |
| |
| if (tfnum < 0) |
| { |
| trace_debug ("no current traceframe"); |
| return 1; |
| } |
| |
| tframe = find_traceframe (tfnum); |
| |
| if (tframe == NULL) |
| { |
| trace_debug ("traceframe %d not found", tfnum); |
| return 1; |
| } |
| |
| datasize = tframe->data_size; |
| database = dataptr = &tframe->data[0]; |
| |
| /* Iterate through a traceframe's blocks, looking for the last |
| matched tsv. */ |
| while ((dataptr = traceframe_find_block_type (dataptr, |
| datasize |
| - (dataptr - database), |
| tfnum, 'V')) != NULL) |
| { |
| memcpy (&vnum, dataptr, sizeof (vnum)); |
| dataptr += sizeof (vnum); |
| |
| trace_debug ("traceframe %d has variable %d", tfnum, vnum); |
| |
| /* Check that this is the variable we want. */ |
| if (tsvnum == vnum) |
| { |
| memcpy (val, dataptr, sizeof (*val)); |
| found = 1; |
| } |
| |
| /* Skip over this block. */ |
| dataptr += sizeof (LONGEST); |
| } |
| |
| if (!found) |
| trace_debug ("traceframe %d has no data for variable %d", |
| tfnum, tsvnum); |
| return !found; |
| } |
| |
| /* Read a requested block of static tracepoint data from a trace |
| frame. */ |
| |
| int |
| traceframe_read_sdata (int tfnum, ULONGEST offset, |
| unsigned char *buf, ULONGEST length, |
| ULONGEST *nbytes) |
| { |
| struct traceframe *tframe; |
| unsigned char *database, *dataptr; |
| unsigned int datasize; |
| unsigned short mlen; |
| |
| trace_debug ("traceframe_read_sdata"); |
| |
| tframe = find_traceframe (tfnum); |
| |
| if (!tframe) |
| { |
| trace_debug ("traceframe %d not found", tfnum); |
| return 1; |
| } |
| |
| datasize = tframe->data_size; |
| database = &tframe->data[0]; |
| |
| /* Iterate through a traceframe's blocks, looking for static |
| tracepoint data. */ |
| dataptr = traceframe_find_block_type (database, datasize, |
| tfnum, 'S'); |
| if (dataptr != NULL) |
| { |
| memcpy (&mlen, dataptr, sizeof (mlen)); |
| dataptr += sizeof (mlen); |
| if (offset < mlen) |
| { |
| if (offset + length > mlen) |
| length = mlen - offset; |
| |
| memcpy (buf, dataptr, length); |
| *nbytes = length; |
| } |
| else |
| *nbytes = 0; |
| return 0; |
| } |
| |
| trace_debug ("traceframe %d has no static trace data", tfnum); |
| |
| *nbytes = 0; |
| return 0; |
| } |
| |
| /* Callback for traceframe_walk_blocks. Builds a traceframe-info |
| object. DATA is pointer to a struct buffer holding the |
| traceframe-info object being built. */ |
| |
| static int |
| build_traceframe_info_xml (char blocktype, unsigned char *dataptr, void *data) |
| { |
| struct buffer *buffer = (struct buffer *) data; |
| |
| switch (blocktype) |
| { |
| case 'M': |
| { |
| unsigned short mlen; |
| CORE_ADDR maddr; |
| |
| memcpy (&maddr, dataptr, sizeof (maddr)); |
| dataptr += sizeof (maddr); |
| memcpy (&mlen, dataptr, sizeof (mlen)); |
| dataptr += sizeof (mlen); |
| buffer_xml_printf (buffer, |
| "<memory start=\"0x%s\" length=\"0x%s\"/>\n", |
| paddress (maddr), phex_nz (mlen, sizeof (mlen))); |
| break; |
| } |
| case 'V': |
| { |
| int vnum; |
| |
| memcpy (&vnum, dataptr, sizeof (vnum)); |
| buffer_xml_printf (buffer, "<tvar id=\"%d\"/>\n", vnum); |
| break; |
| } |
| case 'R': |
| case 'S': |
| { |
| break; |
| } |
| default: |
| warning ("Unhandled trace block type (%d) '%c ' " |
| "while building trace frame info.", |
| blocktype, blocktype); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| /* Build a traceframe-info object for traceframe number TFNUM into |
| BUFFER. */ |
| |
| int |
| traceframe_read_info (int tfnum, struct buffer *buffer) |
| { |
| struct traceframe *tframe; |
| |
| trace_debug ("traceframe_read_info"); |
| |
| tframe = find_traceframe (tfnum); |
| |
| if (!tframe) |
| { |
| trace_debug ("traceframe %d not found", tfnum); |
| return 1; |
| } |
| |
| buffer_grow_str (buffer, "<traceframe-info>\n"); |
| traceframe_walk_blocks (tframe->data, tframe->data_size, |
| tfnum, build_traceframe_info_xml, buffer); |
| buffer_grow_str0 (buffer, "</traceframe-info>\n"); |
| return 0; |
| } |
| |
| /* Return the first fast tracepoint whose jump pad contains PC. */ |
| |
| static struct tracepoint * |
| fast_tracepoint_from_jump_pad_address (CORE_ADDR pc) |
| { |
| struct tracepoint *tpoint; |
| |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| if (tpoint->type == fast_tracepoint) |
| if (tpoint->jump_pad <= pc && pc < tpoint->jump_pad_end) |
| return tpoint; |
| |
| return NULL; |
| } |
| |
| /* Return the first fast tracepoint whose trampoline contains PC. */ |
| |
| static struct tracepoint * |
| fast_tracepoint_from_trampoline_address (CORE_ADDR pc) |
| { |
| struct tracepoint *tpoint; |
| |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| { |
| if (tpoint->type == fast_tracepoint |
| && tpoint->trampoline <= pc && pc < tpoint->trampoline_end) |
| return tpoint; |
| } |
| |
| return NULL; |
| } |
| |
| /* Return GDBserver's tracepoint that matches the IP Agent's |
| tracepoint object that lives at IPA_TPOINT_OBJ in the IP Agent's |
| address space. */ |
| |
| static struct tracepoint * |
| fast_tracepoint_from_ipa_tpoint_address (CORE_ADDR ipa_tpoint_obj) |
| { |
| struct tracepoint *tpoint; |
| |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| if (tpoint->type == fast_tracepoint) |
| if (tpoint->obj_addr_on_target == ipa_tpoint_obj) |
| return tpoint; |
| |
| return NULL; |
| } |
| |
| #endif |
| |
| /* The type of the object that is used to synchronize fast tracepoint |
| collection. */ |
| |
| typedef struct collecting_t |
| { |
| /* The fast tracepoint number currently collecting. */ |
| uintptr_t tpoint; |
| |
| /* A number that GDBserver can use to identify the thread that is |
| presently holding the collect lock. This need not (and usually |
| is not) the thread id, as getting the current thread ID usually |
| requires a system call, which we want to avoid like the plague. |
| Usually this is thread's TCB, found in the TLS (pseudo-) |
| register, which is readable with a single insn on several |
| architectures. */ |
| uintptr_t thread_area; |
| } collecting_t; |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| void |
| force_unlock_trace_buffer (void) |
| { |
| write_inferior_data_pointer (ipa_sym_addrs.addr_collecting, 0); |
| } |
| |
| /* Check if the thread identified by THREAD_AREA which is stopped at |
| STOP_PC, is presently locking the fast tracepoint collection, and |
| if so, gather some status of said collection. Returns 0 if the |
| thread isn't collecting or in the jump pad at all. 1, if in the |
| jump pad (or within gdb_collect) and hasn't executed the adjusted |
| original insn yet (can set a breakpoint there and run to it). 2, |
| if presently executing the adjusted original insn --- in which |
| case, if we want to move the thread out of the jump pad, we need to |
| single-step it until this function returns 0. */ |
| |
| fast_tpoint_collect_result |
| fast_tracepoint_collecting (CORE_ADDR thread_area, |
| CORE_ADDR stop_pc, |
| struct fast_tpoint_collect_status *status) |
| { |
| CORE_ADDR ipa_collecting; |
| CORE_ADDR ipa_gdb_jump_pad_buffer, ipa_gdb_jump_pad_buffer_end; |
| CORE_ADDR ipa_gdb_trampoline_buffer; |
| CORE_ADDR ipa_gdb_trampoline_buffer_end; |
| struct tracepoint *tpoint; |
| int needs_breakpoint; |
| |
| /* The thread THREAD_AREA is either: |
| |
| 0. not collecting at all, not within the jump pad, or within |
| gdb_collect or one of its callees. |
| |
| 1. in the jump pad and haven't reached gdb_collect |
| |
| 2. within gdb_collect (out of the jump pad) (collect is set) |
| |
| 3. we're in the jump pad, after gdb_collect having returned, |
| possibly executing the adjusted insns. |
| |
| For cases 1 and 3, `collecting' may or not be set. The jump pad |
| doesn't have any complicated jump logic, so we can tell if the |
| thread is executing the adjust original insn or not by just |
| matching STOP_PC with known jump pad addresses. If we it isn't |
| yet executing the original insn, set a breakpoint there, and let |
| the thread run to it, so to quickly step over a possible (many |
| insns) gdb_collect call. Otherwise, or when the breakpoint is |
| hit, only a few (small number of) insns are left to be executed |
| in the jump pad. Single-step the thread until it leaves the |
| jump pad. */ |
| |
| again: |
| tpoint = NULL; |
| needs_breakpoint = 0; |
| trace_debug ("fast_tracepoint_collecting"); |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_jump_pad_buffer, |
| &ipa_gdb_jump_pad_buffer)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "error extracting `gdb_jump_pad_buffer'"); |
| } |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_jump_pad_buffer_end, |
| &ipa_gdb_jump_pad_buffer_end)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "error extracting `gdb_jump_pad_buffer_end'"); |
| } |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer, |
| &ipa_gdb_trampoline_buffer)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "error extracting `gdb_trampoline_buffer'"); |
| } |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_trampoline_buffer_end, |
| &ipa_gdb_trampoline_buffer_end)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "error extracting `gdb_trampoline_buffer_end'"); |
| } |
| |
| if (ipa_gdb_jump_pad_buffer <= stop_pc |
| && stop_pc < ipa_gdb_jump_pad_buffer_end) |
| { |
| /* We can tell which tracepoint(s) the thread is collecting by |
| matching the jump pad address back to the tracepoint. */ |
| tpoint = fast_tracepoint_from_jump_pad_address (stop_pc); |
| if (tpoint == NULL) |
| { |
| warning ("in jump pad, but no matching tpoint?"); |
| return fast_tpoint_collect_result::not_collecting; |
| } |
| else |
| { |
| trace_debug ("in jump pad of tpoint (%d, %s); jump_pad(%s, %s); " |
| "adj_insn(%s, %s)", |
| tpoint->number, paddress (tpoint->address), |
| paddress (tpoint->jump_pad), |
| paddress (tpoint->jump_pad_end), |
| paddress (tpoint->adjusted_insn_addr), |
| paddress (tpoint->adjusted_insn_addr_end)); |
| } |
| |
| /* Definitely in the jump pad. May or may not need |
| fast-exit-jump-pad breakpoint. */ |
| if (tpoint->jump_pad <= stop_pc |
| && stop_pc < tpoint->adjusted_insn_addr) |
| needs_breakpoint = 1; |
| } |
| else if (ipa_gdb_trampoline_buffer <= stop_pc |
| && stop_pc < ipa_gdb_trampoline_buffer_end) |
| { |
| /* We can tell which tracepoint(s) the thread is collecting by |
| matching the trampoline address back to the tracepoint. */ |
| tpoint = fast_tracepoint_from_trampoline_address (stop_pc); |
| if (tpoint == NULL) |
| { |
| warning ("in trampoline, but no matching tpoint?"); |
| return fast_tpoint_collect_result::not_collecting; |
| } |
| else |
| { |
| trace_debug ("in trampoline of tpoint (%d, %s); trampoline(%s, %s)", |
| tpoint->number, paddress (tpoint->address), |
| paddress (tpoint->trampoline), |
| paddress (tpoint->trampoline_end)); |
| } |
| |
| /* Have not reached jump pad yet, but treat the trampoline as a |
| part of the jump pad that is before the adjusted original |
| instruction. */ |
| needs_breakpoint = 1; |
| } |
| else |
| { |
| collecting_t ipa_collecting_obj; |
| |
| /* If `collecting' is set/locked, then the THREAD_AREA thread |
| may or not be the one holding the lock. We have to read the |
| lock to find out. */ |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_collecting, |
| &ipa_collecting)) |
| { |
| trace_debug ("fast_tracepoint_collecting:" |
| " failed reading 'collecting' in the inferior"); |
| return fast_tpoint_collect_result::not_collecting; |
| } |
| |
| if (!ipa_collecting) |
| { |
| trace_debug ("fast_tracepoint_collecting: not collecting" |
| " (and nobody is)."); |
| return fast_tpoint_collect_result::not_collecting; |
| } |
| |
| /* Some thread is collecting. Check which. */ |
| if (read_inferior_memory (ipa_collecting, |
| (unsigned char *) &ipa_collecting_obj, |
| sizeof (ipa_collecting_obj)) != 0) |
| goto again; |
| |
| if (ipa_collecting_obj.thread_area != thread_area) |
| { |
| trace_debug ("fast_tracepoint_collecting: not collecting " |
| "(another thread is)"); |
| return fast_tpoint_collect_result::not_collecting; |
| } |
| |
| tpoint |
| = fast_tracepoint_from_ipa_tpoint_address (ipa_collecting_obj.tpoint); |
| if (tpoint == NULL) |
| { |
| warning ("fast_tracepoint_collecting: collecting, " |
| "but tpoint %s not found?", |
| paddress ((CORE_ADDR) ipa_collecting_obj.tpoint)); |
| return fast_tpoint_collect_result::not_collecting; |
| } |
| |
| /* The thread is within `gdb_collect', skip over the rest of |
| fast tracepoint collection quickly using a breakpoint. */ |
| needs_breakpoint = 1; |
| } |
| |
| /* The caller wants a bit of status detail. */ |
| if (status != NULL) |
| { |
| status->tpoint_num = tpoint->number; |
| status->tpoint_addr = tpoint->address; |
| status->adjusted_insn_addr = tpoint->adjusted_insn_addr; |
| status->adjusted_insn_addr_end = tpoint->adjusted_insn_addr_end; |
| } |
| |
| if (needs_breakpoint) |
| { |
| /* Hasn't executed the original instruction yet. Set breakpoint |
| there, and wait till it's hit, then single-step until exiting |
| the jump pad. */ |
| |
| trace_debug ("\ |
| fast_tracepoint_collecting, returning continue-until-break at %s", |
| paddress (tpoint->adjusted_insn_addr)); |
| |
| return fast_tpoint_collect_result::before_insn; /* continue */ |
| } |
| else |
| { |
| /* Just single-step until exiting the jump pad. */ |
| |
| trace_debug ("fast_tracepoint_collecting, returning " |
| "need-single-step (%s-%s)", |
| paddress (tpoint->adjusted_insn_addr), |
| paddress (tpoint->adjusted_insn_addr_end)); |
| |
| return fast_tpoint_collect_result::at_insn; /* single-step */ |
| } |
| } |
| |
| #endif |
| |
| #ifdef IN_PROCESS_AGENT |
| |
| /* The global fast tracepoint collect lock. Points to a collecting_t |
| object built on the stack by the jump pad, if presently locked; |
| NULL if it isn't locked. Note that this lock *must* be set while |
| executing any *function other than the jump pad. See |
| fast_tracepoint_collecting. */ |
| EXTERN_C_PUSH |
| IP_AGENT_EXPORT_VAR collecting_t *collecting; |
| EXTERN_C_POP |
| |
| /* This is needed for -Wmissing-declarations. */ |
| IP_AGENT_EXPORT_FUNC void gdb_collect (struct tracepoint *tpoint, |
| unsigned char *regs); |
| |
| /* This routine, called from the jump pad (in asm) is designed to be |
| called from the jump pads of fast tracepoints, thus it is on the |
| critical path. */ |
| |
| IP_AGENT_EXPORT_FUNC void |
| gdb_collect (struct tracepoint *tpoint, unsigned char *regs) |
| { |
| struct fast_tracepoint_ctx ctx; |
| const struct target_desc *ipa_tdesc; |
| |
| /* Don't do anything until the trace run is completely set up. */ |
| if (!tracing) |
| return; |
| |
| ipa_tdesc = get_ipa_tdesc (ipa_tdesc_idx); |
| ctx.base.type = fast_tracepoint; |
| ctx.regs = regs; |
| ctx.regcache_initted = 0; |
| /* Wrap the regblock in a register cache (in the stack, we don't |
| want to malloc here). */ |
| ctx.regspace = (unsigned char *) alloca (ipa_tdesc->registers_size); |
| if (ctx.regspace == NULL) |
| { |
| trace_debug ("Trace buffer block allocation failed, skipping"); |
| return; |
| } |
| |
| for (ctx.tpoint = tpoint; |
| ctx.tpoint != NULL && ctx.tpoint->address == tpoint->address; |
| ctx.tpoint = ctx.tpoint->next) |
| { |
| if (!ctx.tpoint->enabled) |
| continue; |
| |
| /* Multiple tracepoints of different types, such as fast tracepoint and |
| static tracepoint, can be set at the same address. */ |
| if (ctx.tpoint->type != tpoint->type) |
| continue; |
| |
| /* Test the condition if present, and collect if true. */ |
| if (ctx.tpoint->cond == NULL |
| || condition_true_at_tracepoint ((struct tracepoint_hit_ctx *) &ctx, |
| ctx.tpoint)) |
| { |
| collect_data_at_tracepoint ((struct tracepoint_hit_ctx *) &ctx, |
| ctx.tpoint->address, ctx.tpoint); |
| |
| /* Note that this will cause original insns to be written back |
| to where we jumped from, but that's OK because we're jumping |
| back to the next whole instruction. This will go badly if |
| instruction restoration is not atomic though. */ |
| if (stopping_tracepoint |
| || trace_buffer_is_full |
| || expr_eval_result != expr_eval_no_error) |
| { |
| stop_tracing (); |
| break; |
| } |
| } |
| else |
| { |
| /* If there was a condition and it evaluated to false, the only |
| way we would stop tracing is if there was an error during |
| condition expression evaluation. */ |
| if (expr_eval_result != expr_eval_no_error) |
| { |
| stop_tracing (); |
| break; |
| } |
| } |
| } |
| } |
| |
| /* These global variables points to the corresponding functions. This is |
| necessary on powerpc64, where asking for function symbol address from gdb |
| results in returning the actual code pointer, instead of the descriptor |
| pointer. */ |
| |
| typedef void (*gdb_collect_ptr_type) (struct tracepoint *, unsigned char *); |
| typedef ULONGEST (*get_raw_reg_ptr_type) (const unsigned char *, int); |
| typedef LONGEST (*get_trace_state_variable_value_ptr_type) (int); |
| typedef void (*set_trace_state_variable_value_ptr_type) (int, LONGEST); |
| |
| EXTERN_C_PUSH |
| IP_AGENT_EXPORT_VAR gdb_collect_ptr_type gdb_collect_ptr = gdb_collect; |
| IP_AGENT_EXPORT_VAR get_raw_reg_ptr_type get_raw_reg_ptr = get_raw_reg; |
| IP_AGENT_EXPORT_VAR get_trace_state_variable_value_ptr_type |
| get_trace_state_variable_value_ptr = get_trace_state_variable_value; |
| IP_AGENT_EXPORT_VAR set_trace_state_variable_value_ptr_type |
| set_trace_state_variable_value_ptr = set_trace_state_variable_value; |
| EXTERN_C_POP |
| |
| #endif |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| CORE_ADDR |
| get_raw_reg_func_addr (void) |
| { |
| CORE_ADDR res; |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_get_raw_reg_ptr, &res)) |
| { |
| error ("error extracting get_raw_reg_ptr"); |
| return 0; |
| } |
| return res; |
| } |
| |
| CORE_ADDR |
| get_get_tsv_func_addr (void) |
| { |
| CORE_ADDR res; |
| if (read_inferior_data_pointer ( |
| ipa_sym_addrs.addr_get_trace_state_variable_value_ptr, &res)) |
| { |
| error ("error extracting get_trace_state_variable_value_ptr"); |
| return 0; |
| } |
| return res; |
| } |
| |
| CORE_ADDR |
| get_set_tsv_func_addr (void) |
| { |
| CORE_ADDR res; |
| if (read_inferior_data_pointer ( |
| ipa_sym_addrs.addr_set_trace_state_variable_value_ptr, &res)) |
| { |
| error ("error extracting set_trace_state_variable_value_ptr"); |
| return 0; |
| } |
| return res; |
| } |
| |
| static void |
| compile_tracepoint_condition (struct tracepoint *tpoint, |
| CORE_ADDR *jump_entry) |
| { |
| CORE_ADDR entry_point = *jump_entry; |
| enum eval_result_type err; |
| |
| trace_debug ("Starting condition compilation for tracepoint %d\n", |
| tpoint->number); |
| |
| /* Initialize the global pointer to the code being built. */ |
| current_insn_ptr = *jump_entry; |
| |
| emit_prologue (); |
| |
| err = compile_bytecodes (tpoint->cond); |
| |
| if (err == expr_eval_no_error) |
| { |
| emit_epilogue (); |
| |
| /* Record the beginning of the compiled code. */ |
| tpoint->compiled_cond = entry_point; |
| |
| trace_debug ("Condition compilation for tracepoint %d complete\n", |
| tpoint->number); |
| } |
| else |
| { |
| /* Leave the unfinished code in situ, but don't point to it. */ |
| |
| tpoint->compiled_cond = 0; |
| |
| trace_debug ("Condition compilation for tracepoint %d failed, " |
| "error code %d", |
| tpoint->number, err); |
| } |
| |
| /* Update the code pointer passed in. Note that we do this even if |
| the compile fails, so that we can look at the partial results |
| instead of letting them be overwritten. */ |
| *jump_entry = current_insn_ptr; |
| |
| /* Leave a gap, to aid dump decipherment. */ |
| *jump_entry += 16; |
| } |
| |
| /* The base pointer of the IPA's heap. This is the only memory the |
| IPA is allowed to use. The IPA should _not_ call the inferior's |
| `malloc' during operation. That'd be slow, and, most importantly, |
| it may not be safe. We may be collecting a tracepoint in a signal |
| handler, for example. */ |
| static CORE_ADDR target_tp_heap; |
| |
| /* Allocate at least SIZE bytes of memory from the IPA heap, aligned |
| to 8 bytes. */ |
| |
| static CORE_ADDR |
| target_malloc (ULONGEST size) |
| { |
| CORE_ADDR ptr; |
| |
| if (target_tp_heap == 0) |
| { |
| /* We have the pointer *address*, need what it points to. */ |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_gdb_tp_heap_buffer, |
| &target_tp_heap)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "couldn't get target heap head pointer"); |
| } |
| } |
| |
| ptr = target_tp_heap; |
| target_tp_heap += size; |
| |
| /* Pad to 8-byte alignment. */ |
| target_tp_heap = ((target_tp_heap + 7) & ~0x7); |
| |
| return ptr; |
| } |
| |
| static CORE_ADDR |
| download_agent_expr (struct agent_expr *expr) |
| { |
| CORE_ADDR expr_addr; |
| CORE_ADDR expr_bytes; |
| |
| expr_addr = target_malloc (sizeof (*expr)); |
| target_write_memory (expr_addr, (unsigned char *) expr, sizeof (*expr)); |
| |
| expr_bytes = target_malloc (expr->length); |
| write_inferior_data_pointer (expr_addr + offsetof (struct agent_expr, bytes), |
| expr_bytes); |
| target_write_memory (expr_bytes, expr->bytes, expr->length); |
| |
| return expr_addr; |
| } |
| |
| /* Align V up to N bits. */ |
| #define UALIGN(V, N) (((V) + ((N) - 1)) & ~((N) - 1)) |
| |
| /* Sync tracepoint with IPA, but leave maintenance of linked list to caller. */ |
| |
| static void |
| download_tracepoint_1 (struct tracepoint *tpoint) |
| { |
| struct tracepoint target_tracepoint; |
| CORE_ADDR tpptr = 0; |
| |
| gdb_assert (tpoint->type == fast_tracepoint |
| || tpoint->type == static_tracepoint); |
| |
| if (tpoint->cond != NULL && target_emit_ops () != NULL) |
| { |
| CORE_ADDR jentry, jump_entry; |
| |
| jentry = jump_entry = get_jump_space_head (); |
| |
| if (tpoint->cond != NULL) |
| { |
| /* Pad to 8-byte alignment. (needed?) */ |
| /* Actually this should be left for the target to |
| decide. */ |
| jentry = UALIGN (jentry, 8); |
| |
| compile_tracepoint_condition (tpoint, &jentry); |
| } |
| |
| /* Pad to 8-byte alignment. */ |
| jentry = UALIGN (jentry, 8); |
| claim_jump_space (jentry - jump_entry); |
| } |
| |
| target_tracepoint = *tpoint; |
| |
| tpptr = target_malloc (sizeof (*tpoint)); |
| tpoint->obj_addr_on_target = tpptr; |
| |
| /* Write the whole object. We'll fix up its pointers in a bit. |
| Assume no next for now. This is fixed up above on the next |
| iteration, if there's any. */ |
| target_tracepoint.next = NULL; |
| /* Need to clear this here too, since we're downloading the |
| tracepoints before clearing our own copy. */ |
| target_tracepoint.hit_count = 0; |
| |
| target_write_memory (tpptr, (unsigned char *) &target_tracepoint, |
| sizeof (target_tracepoint)); |
| |
| if (tpoint->cond) |
| write_inferior_data_pointer (tpptr |
| + offsetof (struct tracepoint, cond), |
| download_agent_expr (tpoint->cond)); |
| |
| if (tpoint->numactions) |
| { |
| int i; |
| CORE_ADDR actions_array; |
| |
| /* The pointers array. */ |
| actions_array |
| = target_malloc (sizeof (*tpoint->actions) * tpoint->numactions); |
| write_inferior_data_pointer (tpptr + offsetof (struct tracepoint, |
| actions), |
| actions_array); |
| |
| /* Now for each pointer, download the action. */ |
| for (i = 0; i < tpoint->numactions; i++) |
| { |
| struct tracepoint_action *action = tpoint->actions[i]; |
| CORE_ADDR ipa_action = tracepoint_action_download (action); |
| |
| if (ipa_action != 0) |
| write_inferior_data_pointer (actions_array |
| + i * sizeof (*tpoint->actions), |
| ipa_action); |
| } |
| } |
| } |
| |
| #define IPA_PROTO_FAST_TRACE_FLAG 0 |
| #define IPA_PROTO_FAST_TRACE_ADDR_ON_TARGET 2 |
| #define IPA_PROTO_FAST_TRACE_JUMP_PAD 10 |
| #define IPA_PROTO_FAST_TRACE_FJUMP_SIZE 18 |
| #define IPA_PROTO_FAST_TRACE_FJUMP_INSN 22 |
| |
| /* Send a command to agent to download and install tracepoint TPOINT. */ |
| |
| static int |
| tracepoint_send_agent (struct tracepoint *tpoint) |
| { |
| char buf[IPA_CMD_BUF_SIZE]; |
| char *p; |
| int i, ret; |
| |
| p = buf; |
| strcpy (p, "FastTrace:"); |
| p += 10; |
| |
| COPY_FIELD_TO_BUF (p, tpoint, number); |
| COPY_FIELD_TO_BUF (p, tpoint, address); |
| COPY_FIELD_TO_BUF (p, tpoint, type); |
| COPY_FIELD_TO_BUF (p, tpoint, enabled); |
| COPY_FIELD_TO_BUF (p, tpoint, step_count); |
| COPY_FIELD_TO_BUF (p, tpoint, pass_count); |
| COPY_FIELD_TO_BUF (p, tpoint, numactions); |
| COPY_FIELD_TO_BUF (p, tpoint, hit_count); |
| COPY_FIELD_TO_BUF (p, tpoint, traceframe_usage); |
| COPY_FIELD_TO_BUF (p, tpoint, compiled_cond); |
| COPY_FIELD_TO_BUF (p, tpoint, orig_size); |
| |
| /* condition */ |
| p = agent_expr_send (p, tpoint->cond); |
| |
| /* tracepoint_action */ |
| for (i = 0; i < tpoint->numactions; i++) |
| { |
| struct tracepoint_action *action = tpoint->actions[i]; |
| |
| p[0] = action->type; |
| p = tracepoint_action_send (&p[1], action); |
| } |
| |
| get_jump_space_head (); |
| /* Copy the value of GDB_JUMP_PAD_HEAD to command buffer, so that |
| agent can use jump pad from it. */ |
| if (tpoint->type == fast_tracepoint) |
| { |
| memcpy (p, &gdb_jump_pad_head, 8); |
| p += 8; |
| } |
| |
| ret = run_inferior_command (buf, (int) (ptrdiff_t) (p - buf)); |
| if (ret) |
| return ret; |
| |
| if (!startswith (buf, "OK")) |
| return 1; |
| |
| /* The value of tracepoint's target address is stored in BUF. */ |
| memcpy (&tpoint->obj_addr_on_target, |
| &buf[IPA_PROTO_FAST_TRACE_ADDR_ON_TARGET], 8); |
| |
| if (tpoint->type == fast_tracepoint) |
| { |
| unsigned char *insn |
| = (unsigned char *) &buf[IPA_PROTO_FAST_TRACE_FJUMP_INSN]; |
| int fjump_size; |
| |
| trace_debug ("agent: read from cmd_buf 0x%x 0x%x\n", |
| (unsigned int) tpoint->obj_addr_on_target, |
| (unsigned int) gdb_jump_pad_head); |
| |
| memcpy (&gdb_jump_pad_head, &buf[IPA_PROTO_FAST_TRACE_JUMP_PAD], 8); |
| |
| /* This has been done in agent. We should also set up record for it. */ |
| memcpy (&fjump_size, &buf[IPA_PROTO_FAST_TRACE_FJUMP_SIZE], 4); |
| /* Wire it in. */ |
| tpoint->handle |
| = set_fast_tracepoint_jump (tpoint->address, insn, fjump_size); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| download_tracepoint (struct tracepoint *tpoint) |
| { |
| struct tracepoint *tp, *tp_prev; |
| |
| if (tpoint->type != fast_tracepoint |
| && tpoint->type != static_tracepoint) |
| return; |
| |
| download_tracepoint_1 (tpoint); |
| |
| /* Find the previous entry of TPOINT, which is fast tracepoint or |
| static tracepoint. */ |
| tp_prev = NULL; |
| for (tp = tracepoints; tp != tpoint; tp = tp->next) |
| { |
| if (tp->type == fast_tracepoint || tp->type == static_tracepoint) |
| tp_prev = tp; |
| } |
| |
| if (tp_prev) |
| { |
| CORE_ADDR tp_prev_target_next_addr; |
| |
| /* Insert TPOINT after TP_PREV in IPA. */ |
| if (read_inferior_data_pointer (tp_prev->obj_addr_on_target |
| + offsetof (struct tracepoint, next), |
| &tp_prev_target_next_addr)) |
| { |
| internal_error (__FILE__, __LINE__, |
| "error reading `tp_prev->next'"); |
| } |
| |
| /* tpoint->next = tp_prev->next */ |
| write_inferior_data_pointer (tpoint->obj_addr_on_target |
| + offsetof (struct tracepoint, next), |
| tp_prev_target_next_addr); |
| /* tp_prev->next = tpoint */ |
| write_inferior_data_pointer (tp_prev->obj_addr_on_target |
| + offsetof (struct tracepoint, next), |
| tpoint->obj_addr_on_target); |
| } |
| else |
| /* First object in list, set the head pointer in the |
| inferior. */ |
| write_inferior_data_pointer (ipa_sym_addrs.addr_tracepoints, |
| tpoint->obj_addr_on_target); |
| |
| } |
| |
| static void |
| download_trace_state_variables (void) |
| { |
| CORE_ADDR ptr = 0, prev_ptr = 0; |
| struct trace_state_variable *tsv; |
| |
| /* Start out empty. */ |
| write_inferior_data_pointer (ipa_sym_addrs.addr_trace_state_variables, 0); |
| |
| for (tsv = trace_state_variables; tsv != NULL; tsv = tsv->next) |
| { |
| struct trace_state_variable target_tsv; |
| |
| /* TSV's with a getter have been initialized equally in both the |
| inferior and GDBserver. Skip them. */ |
| if (tsv->getter != NULL) |
| continue; |
| |
| target_tsv = *tsv; |
| |
| prev_ptr = ptr; |
| ptr = target_malloc (sizeof (*tsv)); |
| |
| if (tsv == trace_state_variables) |
| { |
| /* First object in list, set the head pointer in the |
| inferior. */ |
| |
| write_inferior_data_pointer (ipa_sym_addrs.addr_trace_state_variables, |
| ptr); |
| } |
| else |
| { |
| write_inferior_data_pointer (prev_ptr |
| + offsetof (struct trace_state_variable, |
| next), |
| ptr); |
| } |
| |
| /* Write the whole object. We'll fix up its pointers in a bit. |
| Assume no next, fixup when needed. */ |
| target_tsv.next = NULL; |
| |
| target_write_memory (ptr, (unsigned char *) &target_tsv, |
| sizeof (target_tsv)); |
| |
| if (tsv->name != NULL) |
| { |
| size_t size = strlen (tsv->name) + 1; |
| CORE_ADDR name_addr = target_malloc (size); |
| target_write_memory (name_addr, |
| (unsigned char *) tsv->name, size); |
| write_inferior_data_pointer (ptr |
| + offsetof (struct trace_state_variable, |
| name), |
| name_addr); |
| } |
| |
| gdb_assert (tsv->getter == NULL); |
| } |
| |
| if (prev_ptr != 0) |
| { |
| /* Fixup the next pointer in the last item in the list. */ |
| write_inferior_data_pointer (prev_ptr |
| + offsetof (struct trace_state_variable, |
| next), 0); |
| } |
| } |
| |
| /* Upload complete trace frames out of the IP Agent's trace buffer |
| into GDBserver's trace buffer. This always uploads either all or |
| no trace frames. This is the counter part of |
| `trace_alloc_trace_buffer'. See its description of the atomic |
| syncing mechanism. */ |
| |
| static void |
| upload_fast_traceframes (void) |
| { |
| unsigned int ipa_traceframe_read_count, ipa_traceframe_write_count; |
| unsigned int ipa_traceframe_read_count_racy, ipa_traceframe_write_count_racy; |
| CORE_ADDR tf; |
| struct ipa_trace_buffer_control ipa_trace_buffer_ctrl; |
| unsigned int curr_tbctrl_idx; |
| unsigned int ipa_trace_buffer_ctrl_curr; |
| unsigned int ipa_trace_buffer_ctrl_curr_old; |
| CORE_ADDR ipa_trace_buffer_ctrl_addr; |
| struct breakpoint *about_to_request_buffer_space_bkpt; |
| CORE_ADDR ipa_trace_buffer_lo; |
| CORE_ADDR ipa_trace_buffer_hi; |
| |
| if (read_inferior_uinteger (ipa_sym_addrs.addr_traceframe_read_count, |
| &ipa_traceframe_read_count_racy)) |
| { |
| /* This will happen in most targets if the current thread is |
| running. */ |
| return; |
| } |
| |
| if (read_inferior_uinteger (ipa_sym_addrs.addr_traceframe_write_count, |
| &ipa_traceframe_write_count_racy)) |
| return; |
| |
| trace_debug ("ipa_traceframe_count (racy area): %d (w=%d, r=%d)", |
| ipa_traceframe_write_count_racy |
| - ipa_traceframe_read_count_racy, |
| ipa_traceframe_write_count_racy, |
| ipa_traceframe_read_count_racy); |
| |
| if (ipa_traceframe_write_count_racy == ipa_traceframe_read_count_racy) |
| return; |
| |
| about_to_request_buffer_space_bkpt |
| = set_breakpoint_at (ipa_sym_addrs.addr_about_to_request_buffer_space, |
| NULL); |
| |
| if (read_inferior_uinteger (ipa_sym_addrs.addr_trace_buffer_ctrl_curr, |
| &ipa_trace_buffer_ctrl_curr)) |
| return; |
| |
| ipa_trace_buffer_ctrl_curr_old = ipa_trace_buffer_ctrl_curr; |
| |
| curr_tbctrl_idx = ipa_trace_buffer_ctrl_curr & ~GDBSERVER_FLUSH_COUNT_MASK; |
| |
| { |
| unsigned int prev, counter; |
| |
| /* Update the token, with new counters, and the GDBserver stamp |
| bit. Alway reuse the current TBC index. */ |
| prev = ipa_trace_buffer_ctrl_curr & GDBSERVER_FLUSH_COUNT_MASK_CURR; |
| counter = (prev + 0x100) & GDBSERVER_FLUSH_COUNT_MASK_CURR; |
| |
| ipa_trace_buffer_ctrl_curr = (GDBSERVER_UPDATED_FLUSH_COUNT_BIT |
| | (prev << 12) |
| | counter |
| | curr_tbctrl_idx); |
| } |
| |
| if (write_inferior_uinteger (ipa_sym_addrs.addr_trace_buffer_ctrl_curr, |
| ipa_trace_buffer_ctrl_curr)) |
| return; |
| |
| trace_debug ("Lib: Committed %08x -> %08x", |
| ipa_trace_buffer_ctrl_curr_old, |
| ipa_trace_buffer_ctrl_curr); |
| |
| /* Re-read these, now that we've installed the |
| `about_to_request_buffer_space' breakpoint/lock. A thread could |
| have finished a traceframe between the last read of these |
| counters and setting the breakpoint above. If we start |
| uploading, we never want to leave this function with |
| traceframe_read_count != 0, otherwise, GDBserver could end up |
| incrementing the counter tokens more than once (due to event loop |
| nesting), which would break the IP agent's "effective" detection |
| (see trace_alloc_trace_buffer). */ |
| if (read_inferior_uinteger (ipa_sym_addrs.addr_traceframe_read_count, |
| &ipa_traceframe_read_count)) |
| return; |
| if (read_inferior_uinteger (ipa_sym_addrs.addr_traceframe_write_count, |
| &ipa_traceframe_write_count)) |
| return; |
| |
| if (debug_threads) |
| { |
| trace_debug ("ipa_traceframe_count (blocked area): %d (w=%d, r=%d)", |
| ipa_traceframe_write_count - ipa_traceframe_read_count, |
| ipa_traceframe_write_count, ipa_traceframe_read_count); |
| |
| if (ipa_traceframe_write_count != ipa_traceframe_write_count_racy |
| || ipa_traceframe_read_count != ipa_traceframe_read_count_racy) |
| trace_debug ("note that ipa_traceframe_count's parts changed"); |
| } |
| |
| /* Get the address of the current TBC object (the IP agent has an |
| array of 3 such objects). The index is stored in the TBC |
| token. */ |
| ipa_trace_buffer_ctrl_addr = ipa_sym_addrs.addr_trace_buffer_ctrl; |
| ipa_trace_buffer_ctrl_addr |
| += sizeof (struct ipa_trace_buffer_control) * curr_tbctrl_idx; |
| |
| if (read_inferior_memory (ipa_trace_buffer_ctrl_addr, |
| (unsigned char *) &ipa_trace_buffer_ctrl, |
| sizeof (struct ipa_trace_buffer_control))) |
| return; |
| |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_trace_buffer_lo, |
| &ipa_trace_buffer_lo)) |
| return; |
| if (read_inferior_data_pointer (ipa_sym_addrs.addr_trace_buffer_hi, |
| &ipa_trace_buffer_hi)) |
| return; |
| |
| /* Offsets are easier to grok for debugging than raw addresses, |
| especially for the small trace buffer sizes that are useful for |
| testing. */ |
| trace_debug ("Lib: Trace buffer [%d] start=%d free=%d " |
| "endfree=%d wrap=%d hi=%d", |
| curr_tbctrl_idx, |
| (int) (ipa_trace_buffer_ctrl.start - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.free - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.end_free - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.wrap - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_hi - ipa_trace_buffer_lo)); |
| |
| /* Note that the IPA's buffer is always circular. */ |
| |
| #define IPA_FIRST_TRACEFRAME() (ipa_trace_buffer_ctrl.start) |
| |
| #define IPA_NEXT_TRACEFRAME_1(TF, TFOBJ) \ |
| ((TF) + sizeof (struct traceframe) + (TFOBJ)->data_size) |
| |
| #define IPA_NEXT_TRACEFRAME(TF, TFOBJ) \ |
| (IPA_NEXT_TRACEFRAME_1 (TF, TFOBJ) \ |
| - ((IPA_NEXT_TRACEFRAME_1 (TF, TFOBJ) >= ipa_trace_buffer_ctrl.wrap) \ |
| ? (ipa_trace_buffer_ctrl.wrap - ipa_trace_buffer_lo) \ |
| : 0)) |
| |
| tf = IPA_FIRST_TRACEFRAME (); |
| |
| while (ipa_traceframe_write_count - ipa_traceframe_read_count) |
| { |
| struct tracepoint *tpoint; |
| struct traceframe *tframe; |
| unsigned char *block; |
| struct traceframe ipa_tframe; |
| |
| if (read_inferior_memory (tf, (unsigned char *) &ipa_tframe, |
| offsetof (struct traceframe, data))) |
| error ("Uploading: couldn't read traceframe at %s\n", paddress (tf)); |
| |
| if (ipa_tframe.tpnum == 0) |
| { |
| internal_error (__FILE__, __LINE__, |
| "Uploading: No (more) fast traceframes, but" |
| " ipa_traceframe_count == %u??\n", |
| ipa_traceframe_write_count |
| - ipa_traceframe_read_count); |
| } |
| |
| /* Note that this will be incorrect for multi-location |
| tracepoints... */ |
| tpoint = find_next_tracepoint_by_number (NULL, ipa_tframe.tpnum); |
| |
| tframe = add_traceframe (tpoint); |
| if (tframe == NULL) |
| { |
| trace_buffer_is_full = 1; |
| trace_debug ("Uploading: trace buffer is full"); |
| } |
| else |
| { |
| /* Copy the whole set of blocks in one go for now. FIXME: |
| split this in smaller blocks. */ |
| block = add_traceframe_block (tframe, tpoint, |
| ipa_tframe.data_size); |
| if (block != NULL) |
| { |
| if (read_inferior_memory (tf |
| + offsetof (struct traceframe, data), |
| block, ipa_tframe.data_size)) |
| error ("Uploading: Couldn't read traceframe data at %s\n", |
| paddress (tf + offsetof (struct traceframe, data))); |
| } |
| |
| trace_debug ("Uploading: traceframe didn't fit"); |
| finish_traceframe (tframe); |
| } |
| |
| tf = IPA_NEXT_TRACEFRAME (tf, &ipa_tframe); |
| |
| /* If we freed the traceframe that wrapped around, go back |
| to the non-wrap case. */ |
| if (tf < ipa_trace_buffer_ctrl.start) |
| { |
| trace_debug ("Lib: Discarding past the wraparound"); |
| ipa_trace_buffer_ctrl.wrap = ipa_trace_buffer_hi; |
| } |
| ipa_trace_buffer_ctrl.start = tf; |
| ipa_trace_buffer_ctrl.end_free = ipa_trace_buffer_ctrl.start; |
| ++ipa_traceframe_read_count; |
| |
| if (ipa_trace_buffer_ctrl.start == ipa_trace_buffer_ctrl.free |
| && ipa_trace_buffer_ctrl.start == ipa_trace_buffer_ctrl.end_free) |
| { |
| trace_debug ("Lib: buffer is fully empty. " |
| "Trace buffer [%d] start=%d free=%d endfree=%d", |
| curr_tbctrl_idx, |
| (int) (ipa_trace_buffer_ctrl.start |
| - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.free |
| - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.end_free |
| - ipa_trace_buffer_lo)); |
| |
| 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; |
| } |
| |
| trace_debug ("Uploaded a traceframe\n" |
| "Lib: Trace buffer [%d] start=%d free=%d " |
| "endfree=%d wrap=%d hi=%d", |
| curr_tbctrl_idx, |
| (int) (ipa_trace_buffer_ctrl.start - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.free - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.end_free |
| - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_ctrl.wrap - ipa_trace_buffer_lo), |
| (int) (ipa_trace_buffer_hi - ipa_trace_buffer_lo)); |
| } |
| |
| if (target_write_memory (ipa_trace_buffer_ctrl_addr, |
| (unsigned char *) &ipa_trace_buffer_ctrl, |
| sizeof (struct ipa_trace_buffer_control))) |
| return; |
| |
| write_inferior_integer (ipa_sym_addrs.addr_traceframe_read_count, |
| ipa_traceframe_read_count); |
| |
| trace_debug ("Done uploading traceframes [%d]\n", curr_tbctrl_idx); |
| |
| target_pause_all (true); |
| |
| delete_breakpoint (about_to_request_buffer_space_bkpt); |
| about_to_request_buffer_space_bkpt = NULL; |
| |
| target_unpause_all (true); |
| |
| if (trace_buffer_is_full) |
| stop_tracing (); |
| } |
| #endif |
| |
| #ifdef IN_PROCESS_AGENT |
| |
| IP_AGENT_EXPORT_VAR int ust_loaded; |
| IP_AGENT_EXPORT_VAR char cmd_buf[IPA_CMD_BUF_SIZE]; |
| |
| #ifdef HAVE_UST |
| |
| /* Static tracepoints. */ |
| |
| /* UST puts a "struct tracepoint" in the global namespace, which |
| conflicts with our tracepoint. Arguably, being a library, it |
| shouldn't take ownership of such a generic name. We work around it |
| here. */ |
| #define tracepoint ust_tracepoint |
| #include <ust/ust.h> |
| #undef tracepoint |
| |
| extern int serialize_to_text (char *outbuf, int bufsize, |
| const char *fmt, va_list ap); |
| |
| #define GDB_PROBE_NAME "gdb" |
| |
| /* We dynamically search for the UST symbols instead of linking them |
| in. This lets the user decide if the application uses static |
| tracepoints, instead of always pulling libust.so in. This vector |
| holds pointers to all functions we care about. */ |
| |
| static struct |
| { |
| int (*serialize_to_text) (char *outbuf, int bufsize, |
| const char *fmt, va_list ap); |
| |
| int (*ltt_probe_register) (struct ltt_available_probe *pdata); |
| int (*ltt_probe_unregister) (struct ltt_available_probe *pdata); |
| |
| int (*ltt_marker_connect) (const char *channel, const char *mname, |
| const char *pname); |
| int (*ltt_marker_disconnect) (const char *channel, const char *mname, |
| const char *pname); |
| |
| void (*marker_iter_start) (struct marker_iter *iter); |
| void (*marker_iter_next) (struct marker_iter *iter); |
| void (*marker_iter_stop) (struct marker_iter *iter); |
| void (*marker_iter_reset) (struct marker_iter *iter); |
| } ust_ops; |
| |
| #include <dlfcn.h> |
| |
| /* Cast through typeof to catch incompatible API changes. Since UST |
| only builds with gcc, we can freely use gcc extensions here |
| too. */ |
| #define GET_UST_SYM(SYM) \ |
| do \ |
| { \ |
| if (ust_ops.SYM == NULL) \ |
| ust_ops.SYM = (typeof (&SYM)) dlsym (RTLD_DEFAULT, #SYM); \ |
| if (ust_ops.SYM == NULL) \ |
| return 0; \ |
| } while (0) |
| |
| #define USTF(SYM) ust_ops.SYM |
| |
| /* Get pointers to all libust.so functions we care about. */ |
| |
| static int |
| dlsym_ust (void) |
| { |
| GET_UST_SYM (serialize_to_text); |
| |
| GET_UST_SYM (ltt_probe_register); |
| GET_UST_SYM (ltt_probe_unregister); |
| GET_UST_SYM (ltt_marker_connect); |
| GET_UST_SYM (ltt_marker_disconnect); |
| |
| GET_UST_SYM (marker_iter_start); |
| GET_UST_SYM (marker_iter_next); |
| GET_UST_SYM (marker_iter_stop); |
| GET_UST_SYM (marker_iter_reset); |
| |
| ust_loaded = 1; |
| return 1; |
| } |
| |
| /* Given an UST marker, return the matching gdb static tracepoint. |
| The match is done by address. */ |
| |
| static struct tracepoint * |
| ust_marker_to_static_tracepoint (const struct marker *mdata) |
| { |
| struct tracepoint *tpoint; |
| |
| for (tpoint = tracepoints; tpoint; tpoint = tpoint->next) |
| { |
| if (tpoint->type != static_tracepoint) |
| continue; |
| |
| if (tpoint->address == (uintptr_t) mdata->location) |
| return tpoint; |
| } |
| |
| return NULL; |
| } |
| |
| /* The probe function we install on lttng/ust markers. Whenever a |
| probed ust marker is hit, this function is called. This is similar |
| to gdb_collect, only for static tracepoints, instead of fast |
| tracepoints. */ |
| |
| static void |
| gdb_probe (const struct marker *mdata, void *probe_private, |
| struct registers *regs, void *call_private, |
| const char *fmt, va_list *args) |
| { |
| struct tracepoint *tpoint; |
| struct static_tracepoint_ctx ctx; |
| const struct target_desc *ipa_tdesc; |
| |
| /* Don't do anything until the trace run is completely set up. */ |
| if (!tracing) |
| { |
| trace_debug ("gdb_probe: not tracing\n"); |
| return; |
| } |
| |
| ipa_tdesc = get_ipa_tdesc (ipa_tdesc_idx); |
| ctx.base.type = static_tracepoint; |
| ctx.regcache_initted = 0; |
| ctx.regs = regs; |
| ctx.fmt = fmt; |
| ctx.args = args; |
| |
| /* Wrap the regblock in a register cache (in the stack, we don't |
| want to malloc here). */ |
| ctx.regspace = alloca (ipa_tdesc->registers_size); |
| if (ctx.regspace == NULL) |
| { |
| trace_debug ("Trace buffer block allocation failed, skipping"); |
| return; |
| } |
| |
| tpoint = ust_marker_to_static_tracepoint (mdata); |
| if (tpoint == NULL) |
| { |
| trace_debug ("gdb_probe: marker not known: " |
| "loc:0x%p, ch:\"%s\",n:\"%s\",f:\"%s\"", |
| mdata->location, mdata->channel, |
| mdata->name, mdata->format); |
| return; |
| } |
| |
| if (!tpoint->enabled) |
| { |
| trace_debug ("gdb_probe: tracepoint disabled"); |
| return; |
| } |
| |
| ctx.tpoint = tpoint; |
| |
| trace_debug ("gdb_probe: collecting marker: " |
| "loc:0x%p, ch:\"%s\",n:\"%s\",f:\"%s\"", |
| mdata->location, mdata->channel, |
| mdata->name, mdata->format); |
| |
| /* Test the condition if present, and collect if true. */ |
| if (tpoint->cond == NULL |
| || condition_true_at_tracepoint ((struct tracepoint_hit_ctx *) &ctx, |
| tpoint)) |
| { |
| collect_data_at_tracepoint ((struct tracepoint_hit_ctx *) &ctx, |
| tpoint->address, tpoint); |
| |
| if (stopping_tracepoint |
| || trace_buffer_is_full |
| || expr_eval_result != expr_eval_no_error) |
| stop_tracing (); |
| } |
| else |
| { |
| /* If there was a condition and it evaluated to false, the only |
| way we would stop tracing is if there was an error during |
| condition expression evaluation. */ |
| if (expr_eval_result != expr_eval_no_error) |
| stop_tracing (); |
| } |
| } |
| |
| /* Called if the gdb static tracepoint requested collecting "$_sdata", |
| static tracepoint string data. This is a string passed to the |
| tracing library by the user, at the time of the tracepoint marker |
| call. E.g., in the UST marker call: |
| |
| trace_mark (ust, bar33, "str %s", "FOOBAZ"); |
| |
| the collected data is "str FOOBAZ". |
| */ |
| |
| static void |
| collect_ust_data_at_tracepoint (struct tracepoint_hit_ctx *ctx, |
| struct traceframe *tframe) |
| { |
| struct static_tracepoint_ctx *umd = (struct static_tracepoint_ctx *) ctx; |
| unsigned char *bufspace; |
| int size; |
| va_list copy; |
| unsigned short blocklen; |
| |
| if (umd == NULL) |
| { |
| trace_debug ("Wanted to collect static trace data, " |
| "but there's no static trace data"); |
| return; |
| } |
| |
| va_copy (copy, *umd->args); |
| size = USTF(serialize_to_text) (NULL, 0, umd->fmt, copy); |
| va_end (copy); |
| |
| trace_debug ("Want to collect ust data"); |
| |
| /* 'S' + size + string */ |
| bufspace = add_traceframe_block (tframe, umd->tpoint, |
| 1 + sizeof (blocklen) + size + 1); |
| if (bufspace == NULL) |
| { |
| trace_debug ("Trace buffer block allocation failed, skipping"); |
| return; |
| } |
| |
| /* Identify a static trace data block. */ |
| *bufspace = 'S'; |
| |
| blocklen = size + 1; |
| memcpy (bufspace + 1, &blocklen, sizeof (blocklen)); |
| |
| va_copy (copy, *umd->args); |
| USTF(serialize_to_text) ((char *) bufspace + 1 + sizeof (blocklen), |
| size + 1, umd->fmt, copy); |
| va_end (copy); |
| |
| trace_debug ("Storing static tracepoint data in regblock: %s", |
| bufspace + 1 + sizeof (blocklen)); |
| } |
| |
| /* The probe to register with lttng/ust. */ |
| static struct ltt_available_probe gdb_ust_probe = |
| { |
| GDB_PROBE_NAME, |
| NULL, |
| gdb_probe, |
| }; |
| |
| #endif /* HAVE_UST */ |
| #endif /* IN_PROCESS_AGENT */ |
| |
| #ifndef IN_PROCESS_AGENT |
| |
| /* Ask the in-process agent to run a command. Since we don't want to |
| have to handle the IPA hitting breakpoints while running the |
| command, we pause all threads, remove all breakpoints, and then set |
| the helper thread re-running. We communicate with the helper |
| thread by means of direct memory xfering, and a socket for |
| synchronization. */ |
| |
| static int |
| run_inferior_command (char *cmd, int len) |
| { |
| int err = -1; |
| int pid = current_ptid.pid (); |
| |
| trace_debug ("run_inferior_command: running: %s", cmd); |
| |
| target_pause_all (false); |
| uninsert_all_breakpoints (); |
| |
| err = agent_run_command (pid, (const char *) cmd, len); |
| |
| reinsert_all_breakpoints (); |
| target_unpause_all (false); |
| |
| return err; |
| } |
| |
| #else /* !IN_PROCESS_AGENT */ |
| |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| |
| #ifndef UNIX_PATH_MAX |
| #define UNIX_PATH_MAX sizeof(((struct sockaddr_un *) NULL)->sun_path) |
| #endif |
| |
| /* Where we put the socked used for synchronization. */ |
| #define SOCK_DIR P_tmpdir |
| |
| /* Thread ID of the helper thread. GDBserver reads this to know which |
| is the help thread. This is an LWP id on Linux. */ |
| EXTERN_C_PUSH |
| IP_AGENT_EXPORT_VAR int helper_thread_id; |
| EXTERN_C_POP |
| |
| static int |
| init_named_socket (const char *name) |
| { |
| int result, fd; |
| struct sockaddr_un addr; |
| |
| result = fd = socket (PF_UNIX, SOCK_STREAM, 0); |
| if (result == -1) |
| { |
| warning ("socket creation failed: %s", safe_strerror (errno)); |
| return -1; |
| } |
| |
| addr.sun_family = AF_UNIX; |
| |
| strncpy (addr.sun_path, name, UNIX_PATH_MAX); |
| addr.sun_path[UNIX_PATH_MAX - 1] = '\0'; |
| |
| result = access (name, F_OK); |
| if (result == 0) |
| { |
| /* File exists. */ |
| result = unlink (name); |
| if (result == -1) |
| { |
| warning ("unlink failed: %s", safe_strerror (errno)); |
| close (fd); |
| return -1; |
| } |
| warning ("socket %s already exists; overwriting", name); |
| } |
| |
| result = bind (fd, (struct sockaddr *) &addr, sizeof (addr)); |
| if (result == -1) |
| { |
| warning ("bind failed: %s", safe_strerror (errno)); |
| close (fd); |
| return -1; |
| } |
| |
| result = listen (fd, 1); |
| if (result == -1) |
| { |
| warning ("listen: %s", safe_strerror (errno)); |
| close (fd); |
| return -1; |
| } |
| |
| return fd; |
| } |
| |
| static char agent_socket_name[UNIX_PATH_MAX]; |
| |
| static int |
| gdb_agent_socket_init (void) |
| { |
| int result, fd; |
| |
| result = xsnprintf (agent_socket_name, UNIX_PATH_MAX, "%s/gdb_ust%d", |
| SOCK_DIR, getpid ()); |
| if (result >= UNIX_PATH_MAX) |
| { |
| trace_debug ("string overflow allocating socket name"); |
| return -1; |
| } |
| |
| fd = init_named_socket (agent_socket_name); |
| if (fd < 0) |
| warning ("Error initializing named socket (%s) for communication with the " |
| "ust helper thread. Check that directory exists and that it " |
| "is writable.", agent_socket_name); |
| |
| return fd; |
| } |
| |
| #ifdef HAVE_UST |
| |
| /* The next marker to be returned on a qTsSTM command. */ |
| static const struct marker *next_st; |
| |
| /* Returns the first known marker. */ |
| |
| struct marker * |
| first_marker (void) |
| { |
| struct marker_iter iter; |
| |
| USTF(marker_iter_reset) (&iter); |
| USTF(marker_iter_start) (&iter); |
| |
| return iter.marker; |
| } |
| |
| /* Returns the marker following M. */ |
| |
| const struct marker * |
| next_marker (const struct marker *m) |
| { |
| struct marker_iter iter; |
| |
| USTF(marker_iter_reset) (&iter); |
| USTF(marker_iter_start) (&iter); |
| |
| for (; iter.marker != NULL; USTF(marker_iter_next) (&iter)) |
| { |
| if (iter.marker == m) |
| { |
| USTF(marker_iter_next) (&iter); |
| return iter.marker; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* Return an hexstr version of the STR C string, fit for sending to |
| GDB. */ |
| |
| static char * |
| cstr_to_hexstr (const char *str) |
| { |
| int len = strlen (str); |
| char *hexstr = xmalloc (len * 2 + 1); |
| bin2hex ((gdb_byte *) str, hexstr, len); |
| return hexstr; |
| } |
| |
| /* Compose packet that is the response to the qTsSTM/qTfSTM/qTSTMat |
| packets. */ |
| |
| static void |
| response_ust_marker (char *packet, const struct marker *st) |
| { |
| char *strid, *format, *tmp; |
| |
| next_st = next_marker (st); |
| |
| tmp = xmalloc (strlen (st->channel) + 1 + |
| strlen (st->name) + 1); |
| sprintf (tmp, "%s/%s", st->channel, st->name); |
| |
| strid = cstr_to_hexstr (tmp); |
| free (tmp); |
| |
| format = cstr_to_hexstr (st->format); |
| |
| sprintf (packet, "m%s:%s:%s", |
| paddress ((uintptr_t) st->location), |
| strid, |
| format); |
| |
| free (strid); |
| free (format); |
| } |
| |
| /* Return the first static tracepoint, and initialize the state |
| machine that will iterate through all the static tracepoints. */ |
| |
| static void |
| cmd_qtfstm (char *packet) |
| { |
| trace_debug ("Returning first trace state variable definition"); |
| |
| if (first_marker ()) |
| response_ust_marker (packet, first_marker ()); |
| else |
| strcpy (packet, "l"); |
| } |
| |
| /* Return additional trace state variable definitions. */ |
| |
| static void |
| cmd_qtsstm (char *packet) |
| { |
| trace_debug ("Returning static tracepoint"); |
| |
| if (next_st) |
| response_ust_marker (packet, next_st); |
| else |
| strcpy (packet, "l"); |
| } |
| |
| /* Disconnect the GDB probe from a marker at a given address. */ |
| |
| static void |
| unprobe_marker_at (char *packet) |
| { |
| char *p = packet; |
| ULONGEST address; |
| struct marker_iter iter; |
| |
| p += sizeof ("unprobe_marker_at:") - 1; |
| |
| p = unpack_varlen_hex (p, &address); |
| |
| USTF(marker_iter_reset) (&iter); |
| USTF(marker_iter_start) (&iter); |
| for (; iter.marker != NULL; USTF(marker_iter_next) (&iter)) |
| if ((uintptr_t ) iter.marker->location == address) |
| { |
| int result; |
| |
| result = USTF(ltt_marker_disconnect) (iter.marker->channel, |
| iter.marker->name, |
| GDB_PROBE_NAME); |
| if (result < 0) |
| warning ("could not disable marker %s/%s", |
| iter.marker->channel, iter.marker->name); |
| break; |
| } |
| } |
| |
| /* Connect the GDB probe to a marker at a given address. */ |
| |
| static int |
| probe_marker_at (char *packet) |
| { |
| char *p = packet; |
| ULONGEST address; |
| struct marker_iter iter; |
| struct marker *m; |
| |
| p += sizeof ("probe_marker_at:") - 1; |
| |
| p = unpack_varlen_hex (p, &address); |
| |
| USTF(marker_iter_reset) (&iter); |
| |
| for (USTF(marker_iter_start) (&iter), m = iter.marker; |
| m != NULL; |
| USTF(marker_iter_next) (&iter), m = iter.marker) |
| if ((uintptr_t ) m->location == address) |
| { |
| int result; |
| |
| trace_debug ("found marker for address. " |
| "ltt_marker_connect (marker = %s/%s)", |
| m->channel, m->name); |
| |
| result = USTF(ltt_marker_connect) (m->channel, m->name, |
| GDB_PROBE_NAME); |
| if (result && result != -EEXIST) |
| trace_debug ("ltt_marker_connect (marker = %s/%s, errno = %d)", |
| m->channel, m->name, -result); |
| |
| if (result < 0) |
| { |
| sprintf (packet, "E.could not connect marker: channel=%s, name=%s", |
| m->channel, m->name); |
| return -1; |
| } |
| |
| strcpy (packet, "OK"); |
| return 0; |
| } |
| |
| sprintf (packet, "E.no marker found at 0x%s", paddress (address)); |
| return -1; |
| } |
| |
| static int |
| cmd_qtstmat (char *packet) |
| { |
| char *p = packet; |
| ULONGEST address; |
| struct marker_iter iter; |
| struct marker *m; |
| |
| p += sizeof ("qTSTMat:") - 1; |
| |
| p = unpack_varlen_hex (p, &address); |
| |
| USTF(marker_iter_reset) (&iter); |
| |
| for (USTF(marker_iter_start) (&iter), m = iter.marker; |
| m != NULL; |
| USTF(marker_iter_next) (&iter), m = iter.marker) |
| if ((uintptr_t ) m->location == address) |
| { |
| response_ust_marker (packet, m); |
| return 0; |
| } |
| |
| strcpy (packet, "l"); |
| return -1; |
| } |
| |
| static void |
| gdb_ust_init (void) |
| { |
| if (!dlsym_ust ()) |
| return; |
| |
| USTF(ltt_probe_register) (&gdb_ust_probe); |
| } |
| |
| #endif /* HAVE_UST */ |
| |
| #include <sys/syscall.h> |
| |
| static void |
| gdb_agent_remove_socket (void) |
| { |
| unlink (agent_socket_name); |
| } |
| |
| /* Helper thread of agent. */ |
| |
| static void * |
| gdb_agent_helper_thread (void *arg) |
| { |
| int listen_fd; |
| |
| atexit (gdb_agent_remove_socket); |
| |
| while (1) |
| { |
| listen_fd = gdb_agent_socket_init (); |
| |
| if (helper_thread_id == 0) |
| helper_thread_id = syscall (SYS_gettid); |
| |
| if (listen_fd == -1) |
| { |
| warning ("could not create sync socket"); |
| break; |
| } |
| |
| while (1) |
| { |
| socklen_t tmp; |
| struct sockaddr_un sockaddr; |
| int fd; |
| char buf[1]; |
| int ret; |
| int stop_loop = 0; |
| |
| tmp = sizeof (sockaddr); |
| |
| do |
| { |
| fd = accept (listen_fd, (struct sockaddr *) &sockaddr, &tmp); |
| } |
| /* It seems an ERESTARTSYS can escape out of accept. */ |
| while (fd == -512 || (fd == -1 && errno == EINTR)); |
| |
| if (fd < 0) |
| { |
| warning ("Accept returned %d, error: %s", |
| fd, safe_strerror (errno)); |
| break; |
| } |
| |
| do |
| { |
| ret = read (fd, buf, 1); |
| } while (ret == -1 && errno == EINTR); |
| |
| if (ret == -1) |
| { |
| warning ("reading socket (fd=%d) failed with %s", |
| fd, safe_strerror (errno)); |
| close (fd); |
| break; |
| } |
| |
| if (cmd_buf[0]) |
| { |
| if (startswith (cmd_buf, "close")) |
| { |
| stop_loop = 1; |
| } |
| #ifdef HAVE_UST |
| else if (strcmp ("qTfSTM", cmd_buf) == 0) |
| { |
| cmd_qtfstm (cmd_buf); |
| } |
| else if (strcmp ("qTsSTM", cmd_buf) == 0) |
| { |
| cmd_qtsstm (cmd_buf); |
| } |
| else if (startswith (cmd_buf, "unprobe_marker_at:")) |
| { |
| unprobe_marker_at (cmd_buf); |
| } |
| else if (startswith (cmd_buf, "probe_marker_at:")) |
| { |
| probe_marker_at (cmd_buf); |
| } |
| else if (startswith (cmd_buf, "qTSTMat:")) |
| { |
| cmd_qtstmat (cmd_buf); |
| } |
| #endif /* HAVE_UST */ |
| } |
| |
| /* Fix compiler's warning: ignoring return value of 'write'. */ |
| ret = write (fd, buf, 1); |
| close (fd); |
| |
| if (stop_loop) |
| { |
| close (listen_fd); |
| unlink (agent_socket_name); |
| |
| /* Sleep endlessly to wait the whole inferior stops. This |
| thread can not exit because GDB or GDBserver may still need |
| 'current_thread' (representing this thread) to access |
| inferior memory. Otherwise, this thread exits earlier than |
| other threads, and 'current_thread' is set to NULL. */ |
| while (1) |
| sleep (10); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| #include <signal.h> |
| #include <pthread.h> |
| |
| EXTERN_C_PUSH |
| IP_AGENT_EXPORT_VAR int gdb_agent_capability = AGENT_CAPA_STATIC_TRACE; |
| EXTERN_C_POP |
| |
| static void |
| gdb_agent_init (void) |
| { |
| int res; |
| pthread_t thread; |
| sigset_t new_mask; |
| sigset_t orig_mask; |
| |
| /* We want the helper thread to be as transparent as possible, so |
| have it inherit an all-signals-blocked mask. */ |
| |
| sigfillset (&new_mask); |
| res = pthread_sigmask (SIG_SETMASK, &new_mask, &orig_mask); |
| if (res) |
| perror_with_name ("pthread_sigmask (1)"); |
| |
| res = pthread_create (&thread, |
| NULL, |
| gdb_agent_helper_thread, |
| NULL); |
| |
| res = pthread_sigmask (SIG_SETMASK, &orig_mask, NULL); |
| if (res) |
| perror_with_name ("pthread_sigmask (2)"); |
| |
| while (helper_thread_id == 0) |
| usleep (1); |
| |
| #ifdef HAVE_UST |
| gdb_ust_init (); |
| #endif |
| } |
| |
| #include <sys/mman.h> |
| |
| IP_AGENT_EXPORT_VAR char *gdb_tp_heap_buffer; |
| IP_AGENT_EXPORT_VAR char *gdb_jump_pad_buffer; |
| IP_AGENT_EXPORT_VAR char *gdb_jump_pad_buffer_end; |
| IP_AGENT_EXPORT_VAR char *gdb_trampoline_buffer; |
| IP_AGENT_EXPORT_VAR char *gdb_trampoline_buffer_end; |
| IP_AGENT_EXPORT_VAR char *gdb_trampoline_buffer_error; |
| |
| /* Record the result of getting buffer space for fast tracepoint |
| trampolines. Any error message is copied, since caller may not be |
| using persistent storage. */ |
| |
| void |
| set_trampoline_buffer_space (CORE_ADDR begin, CORE_ADDR end, char *errmsg) |
| { |
| gdb_trampoline_buffer = (char *) (uintptr_t) begin; |
| gdb_trampoline_buffer_end = (char *) (uintptr_t) end; |
| if (errmsg) |
| strncpy (gdb_trampoline_buffer_error, errmsg, 99); |
| else |
| strcpy (gdb_trampoline_buffer_error, "no buffer passed"); |
| } |
| |
| static void __attribute__ ((constructor)) |
| initialize_tracepoint_ftlib (void) |
| { |
| initialize_tracepoint (); |
| |
| gdb_agent_init (); |
| } |
| |
| #ifndef HAVE_GETAUXVAL |
| /* Retrieve the value of TYPE from the auxiliary vector. If TYPE is not |
| found, 0 is returned. This function is provided if glibc is too old. */ |
| |
| unsigned long |
| getauxval (unsigned long type) |
| { |
| unsigned long data[2]; |
| FILE *f = fopen ("/proc/self/auxv", "r"); |
| unsigned long value = 0; |
| |
| if (f == NULL) |
| return 0; |
| |
| while (fread (data, sizeof (data), 1, f) > 0) |
| { |
| if (data[0] == type) |
| { |
| value = data[1]; |
| break; |
| } |
| } |
| |
| fclose (f); |
| return value; |
| } |
| #endif |
| |
| #endif /* IN_PROCESS_AGENT */ |
| |
| /* Return a timestamp, expressed as microseconds of the usual Unix |
| time. (As the result is a 64-bit number, it will not overflow any |
| time soon.) */ |
| |
| static LONGEST |
| get_timestamp (void) |
| { |
| using namespace std::chrono; |
| |
| steady_clock::time_point now = steady_clock::now (); |
| return duration_cast<microseconds> (now.time_since_epoch ()).count (); |
| } |
| |
| void |
| initialize_tracepoint (void) |
| { |
| /* Start with the default size. */ |
| init_trace_buffer (DEFAULT_TRACE_BUFFER_SIZE); |
| |
| /* Wire trace state variable 1 to be the timestamp. This will be |
| uploaded to GDB upon connection and become one of its trace state |
| variables. (In case you're wondering, if GDB already has a trace |
| variable numbered 1, it will be renumbered.) */ |
| create_trace_state_variable (1, 0); |
| set_trace_state_variable_name (1, "trace_timestamp"); |
| set_trace_state_variable_getter (1, get_timestamp); |
| |
| #ifdef IN_PROCESS_AGENT |
| { |
| int pagesize; |
| size_t jump_pad_size; |
| |
| pagesize = sysconf (_SC_PAGE_SIZE); |
| if (pagesize == -1) |
| perror_with_name ("sysconf"); |
| |
| #define SCRATCH_BUFFER_NPAGES 20 |
| |
| jump_pad_size = pagesize * SCRATCH_BUFFER_NPAGES; |
| |
| gdb_tp_heap_buffer = (char *) xmalloc (5 * 1024 * 1024); |
| gdb_jump_pad_buffer = (char *) alloc_jump_pad_buffer (jump_pad_size); |
| if (gdb_jump_pad_buffer == NULL) |
| perror_with_name ("mmap"); |
| gdb_jump_pad_buffer_end = gdb_jump_pad_buffer + jump_pad_size; |
| } |
| |
| gdb_trampoline_buffer = gdb_trampoline_buffer_end = 0; |
| |
| /* It's not a fatal error for something to go wrong with trampoline |
| buffer setup, but it can be mysterious, so create a channel to |
| report back on what went wrong, using a fixed size since we may |
| not be able to allocate space later when the problem occurs. */ |
| gdb_trampoline_buffer_error = (char *) xmalloc (IPA_BUFSIZ); |
| |
| strcpy (gdb_trampoline_buffer_error, "No errors reported"); |
| |
| initialize_low_tracepoint (); |
| #endif |
| } |