[PATCH] Collect return addresses at tracepoints

Stan Shebs
2011-09-21T21:46:16Z

[-- Attachment #1: Type: text/plain, Size: 1804 bytes --]

This patch introduces a new tracepoint collection directive $_ret, which
asks for the collection of the return address stored in the current
stack frame. It's not the complete solution for what users really want,
which is to collect several frames of the stack, but that's hard to get
right without adding a fancy unwinder to the tracepoint agent. This is
much simpler, needing only an architecture-specific method to generate
appropriate bytecodes.

Not too much controversial here - the testsuite exercises the code, but
it won't fail the test if the caller's name doesn't appear in the
backtrace, since we can't guarantee that the method's heuristic will
always work.

I'll commit this in a few days if there are no objections.

Stan
stan@codesourcery.com

2011-09-21 Stan Shebs <stan@codesourcery.com>

Add return address collection for tracepoints.
* tracepoint.c (encode_actions_1): Add case for $_ret.
(validate_actionline): Check for $_ret.
(trace_dump_actions): Ditto.
* ax-gdb.h (gen_trace_for_return_address): Declare.
* ax-gdb.c: Include arch-utils.h.
(gen_trace_for_return_address): New function.
(agent_command): Add return address special case.
* amd64-tdep.c: Include ax.h and ax-gdb.h.
(amd64_gen_return_address): New function.
(amd64_init_abi): Call it.
* i386-tdep.c: Include ax.h and ax-gdb.h.
(i386_gen_return_address): New function.
(i386_init_abi): Call it.
* arch-utils.h (default_gen_return_address): Declare.
* arch-utils.c (default_gen_return_address): New function.
* gdbarch.sh (gen_return_address): New method.
* gdbarch.h, gdbarch.c: Regenerate.

* gdb.texinfo (Tracepoint Action Lists): Document $_ret.

* gdb.trace/collection.exp: Test collection of $_ret.

[-- Attachment #2: tret-patch-1 --]
[-- Type: text/plain, Size: 17944 bytes --]

Index: amd64-tdep.c
===================================================================
RCS file: /cvs/src/src/gdb/amd64-tdep.c,v
retrieving revision 1.94
diff -p -r1.94 amd64-tdep.c
*** amd64-tdep.c 13 Sep 2011 08:26:11 -0000 1.94
--- amd64-tdep.c 21 Sep 2011 21:27:00 -0000
***************
*** 45,50 ****
--- 45,53 ----
#include "features/i386/amd64.c"
#include "features/i386/amd64-avx.c"

+ #include "ax.h"
+ #include "ax-gdb.h"
+
/* Note that the AMD64 architecture was previously known as x86-64.
The latter is (forever) engraved into the canonical system name as
returned by config.guess, and used as the name for the AMD64 port
*************** static const struct frame_unwind amd64_f
*** 2165,2170 ****
--- 2168,2191 ----
default_frame_sniffer
};
\f
+ /* Generate a bytecode expression to get the value of the saved PC.
+ Since this is supposed to run on the target independently of GDB,
+ we don't have the full power of the unwinding machinery. Instead,
+ make a guess as to the most likely location of the return address,
+ and issue byte codes for that. */
+
+ void
+ amd64_gen_return_address (struct gdbarch *gdbarch,
+ struct agent_expr *ax, struct axs_value *value,
+ CORE_ADDR scope)
+ {
+ ax_reg (ax, AMD64_RBP_REGNUM);
+ ax_const_l (ax, 8);
+ ax_simple (ax, aop_add);
+ value->type = register_type (gdbarch, AMD64_RIP_REGNUM);
+ value->kind = axs_lvalue_memory;
+ }
+ \f

/* Signal trampolines. */

*************** amd64_init_abi (struct gdbarch_info info
*** 2669,2674 ****
--- 2690,2697 ----
set_gdbarch_get_longjmp_target (gdbarch, amd64_get_longjmp_target);

set_gdbarch_relocate_instruction (gdbarch, amd64_relocate_instruction);
+
+ set_gdbarch_gen_return_address (gdbarch, amd64_gen_return_address);
}

/* Provide a prototype to silence -Wmissing-prototypes. */
Index: arch-utils.c
===================================================================
RCS file: /cvs/src/src/gdb/arch-utils.c,v
retrieving revision 1.192
diff -p -r1.192 arch-utils.c
*** arch-utils.c 5 Jan 2011 22:22:47 -0000 1.192
--- arch-utils.c 21 Sep 2011 21:27:00 -0000
*************** default_remote_breakpoint_from_pc (struc
*** 786,791 ****
--- 786,799 ----
gdbarch_breakpoint_from_pc (gdbarch, pcptr, kindptr);
}

+ void
+ default_gen_return_address (struct gdbarch *gdbarch,
+ struct agent_expr *ax, struct axs_value *value,
+ CORE_ADDR scope)
+ {
+ error (_("This architecture has no method to collect a return address."));
+ }
+
/* */

/* -Wmissing-prototypes */
Index: arch-utils.h
===================================================================
RCS file: /cvs/src/src/gdb/arch-utils.h,v
retrieving revision 1.113
diff -p -r1.113 arch-utils.h
*** arch-utils.h 5 Jan 2011 22:22:47 -0000 1.113
--- arch-utils.h 21 Sep 2011 21:27:00 -0000
*************** extern int default_fast_tracepoint_valid
*** 164,169 ****
--- 164,174 ----
extern void default_remote_breakpoint_from_pc (struct gdbarch *,
CORE_ADDR *pcptr, int *kindptr);

+ extern void default_gen_return_address (struct gdbarch *gdbarch,
+ struct agent_expr *ax,
+ struct axs_value *value,
+ CORE_ADDR scope);
+
extern const char *default_auto_charset (void);
extern const char *default_auto_wide_charset (void);

Index: ax-gdb.c
===================================================================
RCS file: /cvs/src/src/gdb/ax-gdb.c,v
retrieving revision 1.88
diff -p -r1.88 ax-gdb.c
*** ax-gdb.c 17 Jun 2011 20:46:47 -0000 1.88
--- ax-gdb.c 21 Sep 2011 21:27:00 -0000
***************
*** 40,45 ****
--- 40,46 ----
#include "breakpoint.h"
#include "tracepoint.h"
#include "cp-support.h"
+ #include "arch-utils.h"

/* To make sense of this file, you should read doc/agentexpr.texi.
Then look at the types and enums in ax-gdb.h. For the code itself,
*************** gen_eval_for_expr (CORE_ADDR scope, stru
*** 2444,2449 ****
--- 2445,2476 ----
return ax;
}

+ struct agent_expr *
+ gen_trace_for_return_address (CORE_ADDR scope, struct gdbarch *gdbarch)
+ {
+ struct cleanup *old_chain = 0;
+ struct agent_expr *ax = new_agent_expr (gdbarch, scope);
+ struct axs_value value;
+
+ old_chain = make_cleanup_free_agent_expr (ax);
+
+ trace_kludge = 1;
+
+ gdbarch_gen_return_address (gdbarch, ax, &value, scope);
+
+ /* Make sure we record the final object, and get rid of it. */
+ gen_traced_pop (gdbarch, ax, &value);
+
+ /* Oh, and terminate. */
+ ax_simple (ax, aop_end);
+
+ /* We have successfully built the agent expr, so cancel the cleanup
+ request. If we add more cleanups that we always want done, this
+ will have to get more complicated. */
+ discard_cleanups (old_chain);
+ return ax;
+ }
+
static void
agent_command (char *exp, int from_tty)
{
*************** agent_command (char *exp, int from_tty)
*** 2462,2471 ****
if (exp == 0)
error_no_arg (_("expression to translate"));

! expr = parse_expression (exp);
! old_chain = make_cleanup (free_current_contents, &expr);
! agent = gen_trace_for_expr (get_frame_pc (fi), expr);
! make_cleanup_free_agent_expr (agent);
ax_reqs (agent);
ax_print (gdb_stdout, agent);

--- 2489,2510 ----
if (exp == 0)
error_no_arg (_("expression to translate"));

! /* Recognize the return address collection directive specially. Note
! that it is not really an expression of any sort. */
! if (strcmp (exp, "$_ret") == 0)
! {
! agent = gen_trace_for_return_address (get_frame_pc (fi),
! get_current_arch ());
! old_chain = make_cleanup_free_agent_expr (agent);
! }
! else
! {
! expr = parse_expression (exp);
! old_chain = make_cleanup (free_current_contents, &expr);
! agent = gen_trace_for_expr (get_frame_pc (fi), expr);
! make_cleanup_free_agent_expr (agent);
! }
!
ax_reqs (agent);
ax_print (gdb_stdout, agent);

Index: ax-gdb.h
===================================================================
RCS file: /cvs/src/src/gdb/ax-gdb.h,v
retrieving revision 1.20
diff -p -r1.20 ax-gdb.h
*** ax-gdb.h 24 Feb 2011 07:39:48 -0000 1.20
--- ax-gdb.h 21 Sep 2011 21:27:00 -0000
*************** extern struct agent_expr *gen_trace_for_
*** 106,111 ****
--- 106,114 ----
extern struct agent_expr *gen_trace_for_var (CORE_ADDR, struct gdbarch *,
struct symbol *);

+ extern struct agent_expr *gen_trace_for_return_address (CORE_ADDR,
+ struct gdbarch *);
+
extern struct agent_expr *gen_eval_for_expr (CORE_ADDR, struct expression *);

extern int trace_kludge;
Index: gdbarch.c
===================================================================
RCS file: /cvs/src/src/gdb/gdbarch.c,v
retrieving revision 1.477
diff -p -r1.477 gdbarch.c
*** gdbarch.c 22 Jul 2011 15:31:50 -0000 1.477
--- gdbarch.c 21 Sep 2011 21:27:00 -0000
*************** struct gdbarch
*** 270,275 ****
--- 270,276 ----
gdbarch_auto_wide_charset_ftype *auto_wide_charset;
const char * solib_symbols_extension;
int has_dos_based_file_system;
+ gdbarch_gen_return_address_ftype *gen_return_address;
};

*************** struct gdbarch startup_gdbarch =
*** 423,428 ****
--- 424,430 ----
default_auto_wide_charset, /* auto_wide_charset */
0, /* solib_symbols_extension */
0, /* has_dos_based_file_system */
+ default_gen_return_address, /* gen_return_address */
/* startup_gdbarch() */
};

*************** gdbarch_alloc (const struct gdbarch_info
*** 513,518 ****
--- 515,521 ----
gdbarch->fast_tracepoint_valid_at = default_fast_tracepoint_valid_at;
gdbarch->auto_charset = default_auto_charset;
gdbarch->auto_wide_charset = default_auto_wide_charset;
+ gdbarch->gen_return_address = default_gen_return_address;
/* gdbarch_alloc() */

return gdbarch;
*************** verify_gdbarch (struct gdbarch *gdbarch)
*** 707,712 ****
--- 710,716 ----
/* Skip verify of auto_charset, invalid_p == 0 */
/* Skip verify of auto_wide_charset, invalid_p == 0 */
/* Skip verify of has_dos_based_file_system, invalid_p == 0 */
+ /* Skip verify of gen_return_address, invalid_p == 0 */
buf = ui_file_xstrdup (log, &length);
make_cleanup (xfree, buf);
if (length > 0)
*************** gdbarch_dump (struct gdbarch *gdbarch, s
*** 947,952 ****
--- 951,959 ----
"gdbarch_dump: gcore_bfd_target = %s\n",
gdbarch->gcore_bfd_target);
fprintf_unfiltered (file,
+ "gdbarch_dump: gen_return_address = <%s>\n",
+ host_address_to_string (gdbarch->gen_return_address));
+ fprintf_unfiltered (file,
"gdbarch_dump: gdbarch_get_longjmp_target_p() = %d\n",
gdbarch_get_longjmp_target_p (gdbarch));
fprintf_unfiltered (file,
*************** set_gdbarch_has_dos_based_file_system (s
*** 3863,3868 ****
--- 3870,3892 ----
gdbarch->has_dos_based_file_system = has_dos_based_file_system;
}

+ void
+ gdbarch_gen_return_address (struct gdbarch *gdbarch, struct agent_expr *ax, struct axs_value *value, CORE_ADDR scope)
+ {
+ gdb_assert (gdbarch != NULL);
+ gdb_assert (gdbarch->gen_return_address != NULL);
+ if (gdbarch_debug >= 2)
+ fprintf_unfiltered (gdb_stdlog, "gdbarch_gen_return_address called\n");
+ gdbarch->gen_return_address (gdbarch, ax, value, scope);
+ }
+
+ void
+ set_gdbarch_gen_return_address (struct gdbarch *gdbarch,
+ gdbarch_gen_return_address_ftype gen_return_address)
+ {
+ gdbarch->gen_return_address = gen_return_address;
+ }
+

/* Keep a registry of per-architecture data-pointers required by GDB
modules. */
Index: gdbarch.h
===================================================================
RCS file: /cvs/src/src/gdb/gdbarch.h,v
retrieving revision 1.425
diff -p -r1.425 gdbarch.h
*** gdbarch.h 22 Jul 2011 15:31:50 -0000 1.425
--- gdbarch.h 21 Sep 2011 21:27:00 -0000
*************** struct displaced_step_closure;
*** 54,59 ****
--- 54,60 ----
struct core_regset_section;
struct syscall;
struct agent_expr;
+ struct axs_value;

/* The architecture associated with the connection to the target.

*************** extern void set_gdbarch_solib_symbols_ex
*** 1014,1019 ****
--- 1015,1026 ----
extern int gdbarch_has_dos_based_file_system (struct gdbarch *gdbarch);
extern void set_gdbarch_has_dos_based_file_system (struct gdbarch *gdbarch, int has_dos_based_file_system);

+ /* Generate bytecodes to collect the return address in a frame. */
+
+ typedef void (gdbarch_gen_return_address_ftype) (struct gdbarch *gdbarch, struct agent_expr *ax, struct axs_value *value, CORE_ADDR scope);
+ extern void gdbarch_gen_return_address (struct gdbarch *gdbarch, struct agent_expr *ax, struct axs_value *value, CORE_ADDR scope);
+ extern void set_gdbarch_gen_return_address (struct gdbarch *gdbarch, gdbarch_gen_return_address_ftype *gen_return_address);
+
/* Definition for an unknown syscall, used basically in error-cases. */
#define UNKNOWN_SYSCALL (-1)

Index: gdbarch.sh
===================================================================
RCS file: /cvs/src/src/gdb/gdbarch.sh,v
retrieving revision 1.523
diff -p -r1.523 gdbarch.sh
*** gdbarch.sh 22 Jul 2011 15:31:50 -0000 1.523
--- gdbarch.sh 21 Sep 2011 21:27:00 -0000
*************** v:const char *:solib_symbols_extension::
*** 820,825 ****
--- 820,829 ----
# is, absolute paths include a drive name, and the backslash is
# considered a directory separator.
v:int:has_dos_based_file_system:::0:0::0
+
+ # Generate bytecodes to collect the return address in a frame.
+ m:void:gen_return_address:struct agent_expr *ax, struct axs_value *value, CORE_ADDR scope:ax, value, scope::default_gen_return_address::0
+
EOF
}

*************** struct displaced_step_closure;
*** 934,939 ****
--- 938,944 ----
struct core_regset_section;
struct syscall;
struct agent_expr;
+ struct axs_value;

/* The architecture associated with the connection to the target.

Index: i386-tdep.c
===================================================================
RCS file: /cvs/src/src/gdb/i386-tdep.c,v
retrieving revision 1.336
diff -p -r1.336 i386-tdep.c
*** i386-tdep.c 22 Jul 2011 15:31:51 -0000 1.336
--- i386-tdep.c 21 Sep 2011 21:27:00 -0000
***************
*** 60,65 ****
--- 60,68 ----
#include "features/i386/i386-avx.c"
#include "features/i386/i386-mmx.c"

+ #include "ax.h"
+ #include "ax-gdb.h"
+
/* Register names. */

static const char *i386_register_names[] =
*************** static const struct frame_unwind i386_st
*** 2074,2079 ****
--- 2077,2100 ----
i386_stack_tramp_frame_sniffer
};
\f
+ /* Generate a bytecode expression to get the value of the saved PC.
+ Since this is supposed to run on the target independently of GDB,
+ we don't have the full power of the unwinding machinery. Instead,
+ make a guess as to the most likely location of the return address,
+ and issue byte codes for that. */
+
+ void
+ i386_gen_return_address (struct gdbarch *gdbarch,
+ struct agent_expr *ax, struct axs_value *value,
+ CORE_ADDR scope)
+ {
+ ax_reg (ax, I386_EBP_REGNUM);
+ ax_const_l (ax, 4);
+ ax_simple (ax, aop_add);
+ value->type = register_type (gdbarch, I386_EIP_REGNUM);
+ value->kind = axs_lvalue_memory;
+ }
+ \f

/* Signal trampolines. */

*************** i386_gdbarch_init (struct gdbarch_info i
*** 7400,7405 ****
--- 7421,7428 ----

set_gdbarch_relocate_instruction (gdbarch, i386_relocate_instruction);

+ set_gdbarch_gen_return_address (gdbarch, i386_gen_return_address);
+
/* Hook in ABI-specific overrides, if they have been registered. */
info.tdep_info = (void *) tdesc_data;
gdbarch_init_osabi (info, gdbarch);
Index: tracepoint.c
===================================================================
RCS file: /cvs/src/src/gdb/tracepoint.c,v
retrieving revision 1.231
diff -p -r1.231 tracepoint.c
*** tracepoint.c 24 Aug 2011 09:24:10 -0000 1.231
--- tracepoint.c 21 Sep 2011 21:27:00 -0000
*************** validate_actionline (char **line, struct
*** 667,672 ****
--- 667,673 ----
if (0 == strncasecmp ("reg", p + 1, 3)
|| 0 == strncasecmp ("arg", p + 1, 3)
|| 0 == strncasecmp ("loc", p + 1, 3)
+ || 0 == strncasecmp ("_ret", p + 1, 4)
|| 0 == strncasecmp ("_sdata", p + 1, 6))
{
p = strchr (p, ',');
*************** encode_actions_1 (struct command_line *a
*** 1344,1349 ****
--- 1345,1387 ----
'L');
action_exp = strchr (action_exp, ','); /* more? */
}
+ else if (0 == strncasecmp ("$_ret", action_exp, 5))
+ {
+ struct cleanup *old_chain1 = NULL;
+
+ aexpr = gen_trace_for_return_address (tloc->address,
+ t->gdbarch);
+
+ old_chain1 = make_cleanup_free_agent_expr (aexpr);
+
+ ax_reqs (aexpr);
+ report_agent_reqs_errors (aexpr);
+
+ discard_cleanups (old_chain1);
+ add_aexpr (collect, aexpr);
+
+ /* take care of the registers */
+ if (aexpr->reg_mask_len > 0)
+ {
+ int ndx1, ndx2;
+
+ for (ndx1 = 0; ndx1 < aexpr->reg_mask_len; ndx1++)
+ {
+ QUIT; /* allow user to bail out with ^C */
+ if (aexpr->reg_mask[ndx1] != 0)
+ {
+ /* assume chars have 8 bits */
+ for (ndx2 = 0; ndx2 < 8; ndx2++)
+ if (aexpr->reg_mask[ndx1] & (1 << ndx2))
+ /* it's used -- record it */
+ add_register (collect,
+ ndx1 * 8 + ndx2);
+ }
+ }
+ }
+
+ action_exp = strchr (action_exp, ','); /* more? */
+ }
else if (0 == strncasecmp ("$_sdata", action_exp, 7))
{
add_static_trace_data (collect);
*************** trace_dump_actions (struct command_line
*** 2555,2560 ****
--- 2593,2600 ----

if (0 == strncasecmp (action_exp, "$reg", 4))
registers_info (NULL, from_tty);
+ else if (0 == strncasecmp (action_exp, "$_ret", 5))
+ ;
else if (0 == strncasecmp (action_exp, "$loc", 4))
locals_info (NULL, from_tty);
else if (0 == strncasecmp (action_exp, "$arg", 4))
Index: testsuite/gdb.trace/collection.exp
===================================================================
RCS file: /cvs/src/src/gdb/testsuite/gdb.trace/collection.exp,v
retrieving revision 1.26
diff -p -r1.26 collection.exp
*** testsuite/gdb.trace/collection.exp 8 Sep 2011 17:40:53 -0000 1.26
--- testsuite/gdb.trace/collection.exp 21 Sep 2011 21:27:01 -0000
*************** proc gdb_collect_global_in_pieces_test {
*** 588,593 ****
--- 588,624 ----
"collect global in pieces: cease trace debugging"
}

+ proc gdb_collect_return_test { } {
+
+ prepare_for_trace_test
+
+ # We'll simply re-use the args_test_function for this test
+ gdb_test "trace args_test_func" \
+ "Tracepoint \[0-9\]+ at .*" \
+ "collect \$_ret: set tracepoint"
+ gdb_trace_setactions "collect \$_ret: define actions" \
+ "" \
+ "collect \$_ret" "^$"
+
+ # Begin the test.
+ run_trace_experiment \$_ret args_test_func
+
+ # Since we can't guarantee that $_ret will give us the caller,
+ # pass either way, but giving different messages.
+ gdb_test_multiple "backtrace" "" {
+ -re ".*#1 .* in main .*" {
+ pass "collect \$_ret: backtrace lists main"
+ }
+ -re ".*#1 .* in ?? .*" {
+ pass "collect \$_ret: backtrace not listing main"
+ }
+ }
+
+ gdb_test "tfind none" \
+ "#0 end .*" \
+ "collect \$_ret: cease trace debugging"
+ }
+
proc gdb_trace_collection_test {} {
global fpreg
global spreg
*************** proc gdb_trace_collection_test {} {
*** 696,701 ****
--- 727,733 ----
gdb_collect_expression_test globals_test_func \
"globalarr\[\(l6, l7\)\]" "7" "a\[\(b, c\)\]"

+ gdb_collect_return_test
}

clean_restart $executable