diff options
author | Pedro Alves <palves@redhat.com> | 2008-05-02 16:49:54 +0000 |
---|---|---|
committer | Pedro Alves <palves@redhat.com> | 2008-05-02 16:49:54 +0000 |
commit | 237fc4c9cdd1a1df1e53a8321dfd7b147da722fd (patch) | |
tree | a58beb3878b7f1e95d6d4bb0f5dc84025fa8c9b7 /gdb | |
parent | 0428b8f567d7966cd47efe0cc99eb8b5072c625e (diff) | |
download | gdb-237fc4c9cdd1a1df1e53a8321dfd7b147da722fd.zip gdb-237fc4c9cdd1a1df1e53a8321dfd7b147da722fd.tar.gz gdb-237fc4c9cdd1a1df1e53a8321dfd7b147da722fd.tar.bz2 |
Implement displaced stepping.
gdb/
* gdbarch.sh (max_insn_length): New 'variable'.
(displaced_step_copy, displaced_step_fixup)
(displaced_step_free_closure, displaced_step_location): New
functions.
(struct displaced_step_closure): Add forward declaration.
* gdbarch.c, gdbarch.h: Regenerated.
* arch-utils.c: #include "objfiles.h".
(simple_displaced_step_copy_insn)
(simple_displaced_step_free_closure)
(displaced_step_at_entry_point): New functions.
* arch-utils.h (simple_displaced_step_copy_insn)
(simple_displaced_step_free_closure)
(displaced_step_at_entry_point): New prototypes.
* i386-tdep.c (I386_MAX_INSN_LEN): Rename to...
(I386_MAX_MATCHED_INSN_LEN): ... this.
(i386_absolute_jmp_p, i386_absolute_call_p)
(i386_ret_p, i386_call_p, i386_breakpoint_p, i386_syscall_p)
(i386_displaced_step_fixup): New functions.
(struct i386_insn, i386_match_insn): Update.
(i386_gdbarch_init): Set gdbarch_max_insn_length.
* i386-tdep.h (I386_MAX_INSN_LEN): New.
(i386_displaced_step_fixup): New prototype.
* i386-linux-tdep.c (i386_linux_init_abi): Include "arch-utils.h".
Register gdbarch_displaced_step_copy,
gdbarch_displaced_step_fixup, gdbarch_displaced_step_free_closure,
and gdbarch_displaced_step_location functions.
* infrun.c (debug_displaced): New variable.
(show_debug_displaced): New function.
(struct displaced_step_request): New struct.
(displaced_step_request_queue, displaced_step_ptid)
(displaced_step_gdbarch, displaced_step_closure)
(displaced_step_original, displaced_step_copy)
(displaced_step_saved_copy, can_use_displaced_stepping): New
variables.
(show_can_use_displaced_stepping, use_displaced_stepping)
(displaced_step_clear, cleanup_displaced_step_closure)
(displaced_step_dump_bytes, displaced_step_prepare)
(displaced_step_clear_cleanup, write_memory_ptid)
(displaced_step_fixup): New functions.
(resume): Call displaced_step_prepare.
(proceed): Call read_pc once, and remember the value. If using
displaced stepping, don't remove breakpoints.
(handle_inferior_event): Call displaced_step_fixup. Add some
debugging output. When we try to step over a breakpoint, but get
a signal to deliver to the thread instead, ensure the step-resume
breakpoint is actually inserted. If a thread hop is needed, and
displaced stepping is enabled, don't remove breakpoints.
(init_wait_for_inferior): Call displaced_step_clear.
(_initialize_infrun): Add "set debug displaced" command. Add
"maint set can-use-displaced-stepping" command. Clear
displaced_step_ptid.
* inferior.h (debug_displaced): Declare variable.
(displaced_step_dump_bytes): Declare function.
* Makefile.in (arch-utils.o, i386-linux-tdep.o): Update
dependencies.
gdb/testsuite/
* gdb.asm/asmsrc1.s: Add scratch space.
gdb/doc/
* gdb.texinfo (Debugging Output): Document "set/show debug
displaced".
(Maintenance Commands): Document "maint set/show
can-use-displaced-stepping".
Diffstat (limited to 'gdb')
-rw-r--r-- | gdb/ChangeLog | 65 | ||||
-rw-r--r-- | gdb/Makefile.in | 5 | ||||
-rw-r--r-- | gdb/arch-utils.c | 52 | ||||
-rw-r--r-- | gdb/arch-utils.h | 24 | ||||
-rw-r--r-- | gdb/doc/ChangeLog | 7 | ||||
-rw-r--r-- | gdb/doc/gdb.texinfo | 20 | ||||
-rw-r--r-- | gdb/gdbarch.c | 152 | ||||
-rw-r--r-- | gdb/gdbarch.h | 90 | ||||
-rwxr-xr-x | gdb/gdbarch.sh | 70 | ||||
-rw-r--r-- | gdb/i386-linux-tdep.c | 10 | ||||
-rw-r--r-- | gdb/i386-tdep.c | 230 | ||||
-rw-r--r-- | gdb/i386-tdep.h | 10 | ||||
-rw-r--r-- | gdb/inferior.h | 8 | ||||
-rw-r--r-- | gdb/infrun.c | 532 | ||||
-rw-r--r-- | gdb/testsuite/ChangeLog | 4 | ||||
-rw-r--r-- | gdb/testsuite/gdb.asm/asmsrc1.s | 12 |
16 files changed, 1257 insertions, 34 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog index 9c6f600..9c9e09b 100644 --- a/gdb/ChangeLog +++ b/gdb/ChangeLog @@ -1,3 +1,68 @@ +2008-05-02 Jim Blandy <jimb@codesourcery.com> + Pedro Alves <pedro@codesourcery.com> + + Implement displaced stepping. + + * gdbarch.sh (max_insn_length): New 'variable'. + (displaced_step_copy, displaced_step_fixup) + (displaced_step_free_closure, displaced_step_location): New + functions. + (struct displaced_step_closure): Add forward declaration. + * gdbarch.c, gdbarch.h: Regenerated. + + * arch-utils.c: #include "objfiles.h". + (simple_displaced_step_copy_insn) + (simple_displaced_step_free_closure) + (displaced_step_at_entry_point): New functions. + * arch-utils.h (simple_displaced_step_copy_insn) + (simple_displaced_step_free_closure) + (displaced_step_at_entry_point): New prototypes. + + * i386-tdep.c (I386_MAX_INSN_LEN): Rename to... + (I386_MAX_MATCHED_INSN_LEN): ... this. + (i386_absolute_jmp_p, i386_absolute_call_p) + (i386_ret_p, i386_call_p, i386_breakpoint_p, i386_syscall_p) + (i386_displaced_step_fixup): New functions. + (struct i386_insn, i386_match_insn): Update. + (i386_gdbarch_init): Set gdbarch_max_insn_length. + * i386-tdep.h (I386_MAX_INSN_LEN): New. + (i386_displaced_step_fixup): New prototype. + * i386-linux-tdep.c (i386_linux_init_abi): Include "arch-utils.h". + Register gdbarch_displaced_step_copy, + gdbarch_displaced_step_fixup, gdbarch_displaced_step_free_closure, + and gdbarch_displaced_step_location functions. + + * infrun.c (debug_displaced): New variable. + (show_debug_displaced): New function. + (struct displaced_step_request): New struct. + (displaced_step_request_queue, displaced_step_ptid) + (displaced_step_gdbarch, displaced_step_closure) + (displaced_step_original, displaced_step_copy) + (displaced_step_saved_copy, can_use_displaced_stepping): New + variables. + (show_can_use_displaced_stepping, use_displaced_stepping) + (displaced_step_clear, cleanup_displaced_step_closure) + (displaced_step_dump_bytes, displaced_step_prepare) + (displaced_step_clear_cleanup, write_memory_ptid) + (displaced_step_fixup): New functions. + (resume): Call displaced_step_prepare. + (proceed): Call read_pc once, and remember the value. If using + displaced stepping, don't remove breakpoints. + (handle_inferior_event): Call displaced_step_fixup. Add some + debugging output. When we try to step over a breakpoint, but get + a signal to deliver to the thread instead, ensure the step-resume + breakpoint is actually inserted. If a thread hop is needed, and + displaced stepping is enabled, don't remove breakpoints. + (init_wait_for_inferior): Call displaced_step_clear. + (_initialize_infrun): Add "set debug displaced" command. Add + "maint set can-use-displaced-stepping" command. Clear + displaced_step_ptid. + * inferior.h (debug_displaced): Declare variable. + (displaced_step_dump_bytes): Declare function. + + * Makefile.in (arch-utils.o, i386-linux-tdep.o): Update + dependencies. + 2008-05-02 Daniel Jacobowitz <dan@codesourcery.com> * arm-tdep.c (arm_mode_strings, arm_fallback_mode_string) diff --git a/gdb/Makefile.in b/gdb/Makefile.in index c7a3124..16dd2ee 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1917,7 +1917,7 @@ annotate.o: annotate.c $(defs_h) $(annotate_h) $(value_h) $(target_h) \ arch-utils.o: arch-utils.c $(defs_h) $(arch_utils_h) $(buildsym_h) \ $(gdbcmd_h) $(inferior_h) $(gdb_string_h) $(regcache_h) \ $(gdb_assert_h) $(sim_regno_h) $(gdbcore_h) $(osabi_h) $(version_h) \ - $(floatformat_h) $(target_descriptions_h) + $(floatformat_h) $(target_descriptions_h) $(objfiles_h) arm-linux-nat.o: arm-linux-nat.c $(defs_h) $(inferior_h) $(gdbcore_h) \ $(gdb_string_h) $(regcache_h) $(arm_tdep_h) $(gregset_h) \ $(target_h) $(linux_nat_h) $(gdb_proc_service_h) $(arm_linux_tdep_h) \ @@ -2257,7 +2257,8 @@ i386-linux-nat.o: i386-linux-nat.c $(defs_h) $(inferior_h) $(gdbcore_h) \ i386-linux-tdep.o: i386-linux-tdep.c $(defs_h) $(gdbcore_h) $(frame_h) \ $(value_h) $(regcache_h) $(inferior_h) $(osabi_h) $(reggroups_h) \ $(dwarf2_frame_h) $(gdb_string_h) $(i386_tdep_h) \ - $(i386_linux_tdep_h) $(glibc_tdep_h) $(solib_svr4_h) $(symtab_h) + $(i386_linux_tdep_h) $(glibc_tdep_h) $(solib_svr4_h) $(symtab_h) \ + $(arch_utils_h) i386-nat.o: i386-nat.c $(defs_h) $(breakpoint_h) $(command_h) $(gdbcmd_h) \ $(target_h) i386nbsd-nat.o: i386nbsd-nat.c $(defs_h) $(gdbcore_h) $(regcache_h) \ diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c index 2c8b34b..c1816da 100644 --- a/gdb/arch-utils.c +++ b/gdb/arch-utils.c @@ -31,12 +31,64 @@ #include "gdbcore.h" #include "osabi.h" #include "target-descriptions.h" +#include "objfiles.h" #include "version.h" #include "floatformat.h" +struct displaced_step_closure * +simple_displaced_step_copy_insn (struct gdbarch *gdbarch, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs) +{ + size_t len = gdbarch_max_insn_length (gdbarch); + gdb_byte *buf = xmalloc (len); + + read_memory (from, buf, len); + write_memory (to, buf, len); + + if (debug_displaced) + { + fprintf_unfiltered (gdb_stdlog, "displaced: copy 0x%s->0x%s: ", + paddr_nz (from), paddr_nz (to)); + displaced_step_dump_bytes (gdb_stdlog, buf, len); + } + + return (struct displaced_step_closure *) buf; +} + + +void +simple_displaced_step_free_closure (struct gdbarch *gdbarch, + struct displaced_step_closure *closure) +{ + xfree (closure); +} + + +CORE_ADDR +displaced_step_at_entry_point (struct gdbarch *gdbarch) +{ + CORE_ADDR addr; + int bp_len; + + addr = entry_point_address (); + + /* Make certain that the address points at real code, and not a + function descriptor. */ + addr = gdbarch_convert_from_func_ptr_addr (gdbarch, addr, ¤t_target); + + /* Inferior calls also use the entry point as a breakpoint location. + We don't want displaced stepping to interfere with those + breakpoints, so leave space. */ + gdbarch_breakpoint_from_pc (gdbarch, &addr, &bp_len); + addr += bp_len * 2; + + return addr; +} + int legacy_register_sim_regno (struct gdbarch *gdbarch, int regnum) { diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h index dc5fc03..6b7a073 100644 --- a/gdb/arch-utils.h +++ b/gdb/arch-utils.h @@ -30,6 +30,30 @@ struct gdbarch_info; /* gdbarch trace variable */ extern int gdbarch_debug; +/* An implementation of gdbarch_displaced_step_copy_insn for + processors that don't need to modify the instruction before + single-stepping the displaced copy. + + Simply copy gdbarch_max_insn_length (ARCH) bytes from FROM to TO. + The closure is an array of that many bytes containing the + instruction's bytes, allocated with xmalloc. */ +extern struct displaced_step_closure * + simple_displaced_step_copy_insn (struct gdbarch *gdbarch, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs); + +/* Simple implementation of gdbarch_displaced_step_free_closure: Call + xfree. + This is appropriate for use with simple_displaced_step_copy_insn. */ +extern void + simple_displaced_step_free_closure (struct gdbarch *gdbarch, + struct displaced_step_closure *closure); + +/* Possible value for gdbarch_displaced_step_location: + Place displaced instructions at the program's entry point, + leaving space for inferior function call return breakpoints. */ +extern CORE_ADDR displaced_step_at_entry_point (struct gdbarch *gdbarch); + /* The only possible cases for inner_than. */ extern int core_addr_lessthan (CORE_ADDR lhs, CORE_ADDR rhs); extern int core_addr_greaterthan (CORE_ADDR lhs, CORE_ADDR rhs); diff --git a/gdb/doc/ChangeLog b/gdb/doc/ChangeLog index 15d51f4..fd92048 100644 --- a/gdb/doc/ChangeLog +++ b/gdb/doc/ChangeLog @@ -1,3 +1,10 @@ +2008-05-02 Pedro Alves <pedro@codesourcery.com> + + * gdb.texinfo (Debugging Output): Document "set/show debug + displaced". + (Maintenance Commands): Document "maint set/show + can-use-displaced-stepping". + 2008-05-02 Daniel Jacobowitz <dan@codesourcery.com> * gdb.texinfo (ARM): Document set/show arm fallback-mode diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 17c5b11..7abac3c 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -16483,6 +16483,13 @@ Display debugging messages about inner workings of the AIX thread module. @item show debug aix-thread Show the current state of AIX thread debugging info display. +@item set debug displaced +@cindex displaced stepping debugging info +Turns on or off display of @value{GDBN} debugging info for the +displaced stepping support. The default is off. +@item show debug displaced +Displays the current state of displaying @value{GDBN} debugging info +related to displaced stepping. @item set debug event @cindex event debugging info Turns on or off display of @value{GDBN} event debugging info. The @@ -23159,6 +23166,19 @@ Shared library events. @end table +@kindex maint set can-use-displaced-stepping +@kindex maint show can-use-displaced-stepping +@cindex displaced stepping support +@cindex out-of-line single-stepping +@item maint set can-use-displaced-stepping +@itemx maint show can-use-displaced-stepping +Control whether or not @value{GDBN} will do @dfn{displaced stepping} +if the target supports it. The default is on. Displaced stepping is +a way to single-step over breakpoints without removing them from the +inferior, by executing an out-of-line copy of the instruction that was +originally at the breakpoint location. It is also known as +out-of-line single-stepping. + @kindex maint check-symtabs @item maint check-symtabs Check the consistency of psymtabs and symtabs. diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c index dd13de1..0449aaa 100644 --- a/gdb/gdbarch.c +++ b/gdb/gdbarch.c @@ -226,6 +226,11 @@ struct gdbarch int vtable_function_descriptors; int vbit_in_delta; gdbarch_skip_permanent_breakpoint_ftype *skip_permanent_breakpoint; + ULONGEST max_insn_length; + gdbarch_displaced_step_copy_insn_ftype *displaced_step_copy_insn; + gdbarch_displaced_step_fixup_ftype *displaced_step_fixup; + gdbarch_displaced_step_free_closure_ftype *displaced_step_free_closure; + gdbarch_displaced_step_location_ftype *displaced_step_location; gdbarch_overlay_update_ftype *overlay_update; gdbarch_core_read_description_ftype *core_read_description; gdbarch_static_transform_name_ftype *static_transform_name; @@ -350,6 +355,11 @@ struct gdbarch startup_gdbarch = 0, /* vtable_function_descriptors */ 0, /* vbit_in_delta */ 0, /* skip_permanent_breakpoint */ + 0, /* max_insn_length */ + 0, /* displaced_step_copy_insn */ + 0, /* displaced_step_fixup */ + NULL, /* displaced_step_free_closure */ + NULL, /* displaced_step_location */ 0, /* overlay_update */ 0, /* core_read_description */ 0, /* static_transform_name */ @@ -435,6 +445,9 @@ gdbarch_alloc (const struct gdbarch_info *info, gdbarch->coff_make_msymbol_special = default_coff_make_msymbol_special; gdbarch->name_of_malloc = "malloc"; gdbarch->register_reggroup_p = default_register_reggroup_p; + gdbarch->displaced_step_fixup = NULL; + gdbarch->displaced_step_free_closure = NULL; + gdbarch->displaced_step_location = NULL; gdbarch->target_signal_from_host = default_target_signal_from_host; gdbarch->target_signal_to_host = default_target_signal_to_host; /* gdbarch_alloc() */ @@ -592,6 +605,13 @@ verify_gdbarch (struct gdbarch *gdbarch) /* Skip verify of vtable_function_descriptors, invalid_p == 0 */ /* Skip verify of vbit_in_delta, invalid_p == 0 */ /* Skip verify of skip_permanent_breakpoint, has predicate */ + /* Skip verify of max_insn_length, has predicate */ + /* Skip verify of displaced_step_copy_insn, has predicate */ + /* Skip verify of displaced_step_fixup, has predicate */ + if ((! gdbarch->displaced_step_free_closure) != (! gdbarch->displaced_step_copy_insn)) + fprintf_unfiltered (log, "\n\tdisplaced_step_free_closure"); + if ((! gdbarch->displaced_step_location) != (! gdbarch->displaced_step_copy_insn)) + fprintf_unfiltered (log, "\n\tdisplaced_step_location"); /* Skip verify of overlay_update, has predicate */ /* Skip verify of core_read_description, has predicate */ /* Skip verify of static_transform_name, has predicate */ @@ -717,6 +737,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) "gdbarch_dump: deprecated_function_start_offset = 0x%s\n", paddr_nz (gdbarch->deprecated_function_start_offset)); fprintf_unfiltered (file, + "gdbarch_dump: gdbarch_displaced_step_copy_insn_p() = %d\n", + gdbarch_displaced_step_copy_insn_p (gdbarch)); + fprintf_unfiltered (file, + "gdbarch_dump: displaced_step_copy_insn = <0x%lx>\n", + (long) gdbarch->displaced_step_copy_insn); + fprintf_unfiltered (file, + "gdbarch_dump: gdbarch_displaced_step_fixup_p() = %d\n", + gdbarch_displaced_step_fixup_p (gdbarch)); + fprintf_unfiltered (file, + "gdbarch_dump: displaced_step_fixup = <0x%lx>\n", + (long) gdbarch->displaced_step_fixup); + fprintf_unfiltered (file, + "gdbarch_dump: displaced_step_free_closure = <0x%lx>\n", + (long) gdbarch->displaced_step_free_closure); + fprintf_unfiltered (file, + "gdbarch_dump: displaced_step_location = <0x%lx>\n", + (long) gdbarch->displaced_step_location); + fprintf_unfiltered (file, "gdbarch_dump: double_bit = %s\n", paddr_d (gdbarch->double_bit)); fprintf_unfiltered (file, @@ -819,6 +857,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) "gdbarch_dump: long_long_bit = %s\n", paddr_d (gdbarch->long_long_bit)); fprintf_unfiltered (file, + "gdbarch_dump: gdbarch_max_insn_length_p() = %d\n", + gdbarch_max_insn_length_p (gdbarch)); + fprintf_unfiltered (file, + "gdbarch_dump: max_insn_length = %s\n", + paddr_d (gdbarch->max_insn_length)); + fprintf_unfiltered (file, "gdbarch_dump: memory_insert_breakpoint = <0x%lx>\n", (long) gdbarch->memory_insert_breakpoint); fprintf_unfiltered (file, @@ -2907,6 +2951,114 @@ set_gdbarch_skip_permanent_breakpoint (struct gdbarch *gdbarch, } int +gdbarch_max_insn_length_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + return gdbarch->max_insn_length != 0; +} + +ULONGEST +gdbarch_max_insn_length (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + /* Check variable changed from pre-default. */ + gdb_assert (gdbarch->max_insn_length != 0); + if (gdbarch_debug >= 2) + fprintf_unfiltered (gdb_stdlog, "gdbarch_max_insn_length called\n"); + return gdbarch->max_insn_length; +} + +void +set_gdbarch_max_insn_length (struct gdbarch *gdbarch, + ULONGEST max_insn_length) +{ + gdbarch->max_insn_length = max_insn_length; +} + +int +gdbarch_displaced_step_copy_insn_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + return gdbarch->displaced_step_copy_insn != NULL; +} + +struct displaced_step_closure * +gdbarch_displaced_step_copy_insn (struct gdbarch *gdbarch, CORE_ADDR from, CORE_ADDR to, struct regcache *regs) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->displaced_step_copy_insn != NULL); + if (gdbarch_debug >= 2) + fprintf_unfiltered (gdb_stdlog, "gdbarch_displaced_step_copy_insn called\n"); + return gdbarch->displaced_step_copy_insn (gdbarch, from, to, regs); +} + +void +set_gdbarch_displaced_step_copy_insn (struct gdbarch *gdbarch, + gdbarch_displaced_step_copy_insn_ftype displaced_step_copy_insn) +{ + gdbarch->displaced_step_copy_insn = displaced_step_copy_insn; +} + +int +gdbarch_displaced_step_fixup_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + return gdbarch->displaced_step_fixup != NULL; +} + +void +gdbarch_displaced_step_fixup (struct gdbarch *gdbarch, struct displaced_step_closure *closure, CORE_ADDR from, CORE_ADDR to, struct regcache *regs) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->displaced_step_fixup != NULL); + /* Do not check predicate: gdbarch->displaced_step_fixup != NULL, allow call. */ + if (gdbarch_debug >= 2) + fprintf_unfiltered (gdb_stdlog, "gdbarch_displaced_step_fixup called\n"); + gdbarch->displaced_step_fixup (gdbarch, closure, from, to, regs); +} + +void +set_gdbarch_displaced_step_fixup (struct gdbarch *gdbarch, + gdbarch_displaced_step_fixup_ftype displaced_step_fixup) +{ + gdbarch->displaced_step_fixup = displaced_step_fixup; +} + +void +gdbarch_displaced_step_free_closure (struct gdbarch *gdbarch, struct displaced_step_closure *closure) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->displaced_step_free_closure != NULL); + if (gdbarch_debug >= 2) + fprintf_unfiltered (gdb_stdlog, "gdbarch_displaced_step_free_closure called\n"); + gdbarch->displaced_step_free_closure (gdbarch, closure); +} + +void +set_gdbarch_displaced_step_free_closure (struct gdbarch *gdbarch, + gdbarch_displaced_step_free_closure_ftype displaced_step_free_closure) +{ + gdbarch->displaced_step_free_closure = displaced_step_free_closure; +} + +CORE_ADDR +gdbarch_displaced_step_location (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != NULL); + gdb_assert (gdbarch->displaced_step_location != NULL); + if (gdbarch_debug >= 2) + fprintf_unfiltered (gdb_stdlog, "gdbarch_displaced_step_location called\n"); + return gdbarch->displaced_step_location (gdbarch); +} + +void +set_gdbarch_displaced_step_location (struct gdbarch *gdbarch, + gdbarch_displaced_step_location_ftype displaced_step_location) +{ + gdbarch->displaced_step_location = displaced_step_location; +} + +int gdbarch_overlay_update_p (struct gdbarch *gdbarch) { gdb_assert (gdbarch != NULL); diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h index baba7c7..e89bd46 100644 --- a/gdb/gdbarch.h +++ b/gdb/gdbarch.h @@ -50,6 +50,7 @@ struct target_ops; struct obstack; struct bp_target_info; struct target_desc; +struct displaced_step_closure; extern struct gdbarch *current_gdbarch; @@ -663,6 +664,95 @@ typedef void (gdbarch_skip_permanent_breakpoint_ftype) (struct regcache *regcach extern void gdbarch_skip_permanent_breakpoint (struct gdbarch *gdbarch, struct regcache *regcache); extern void set_gdbarch_skip_permanent_breakpoint (struct gdbarch *gdbarch, gdbarch_skip_permanent_breakpoint_ftype *skip_permanent_breakpoint); +/* The maximum length of an instruction on this architecture. */ + +extern int gdbarch_max_insn_length_p (struct gdbarch *gdbarch); + +extern ULONGEST gdbarch_max_insn_length (struct gdbarch *gdbarch); +extern void set_gdbarch_max_insn_length (struct gdbarch *gdbarch, ULONGEST max_insn_length); + +/* Copy the instruction at FROM to TO, and make any adjustments + necessary to single-step it at that address. + + REGS holds the state the thread's registers will have before + executing the copied instruction; the PC in REGS will refer to FROM, + not the copy at TO. The caller should update it to point at TO later. + + Return a pointer to data of the architecture's choice to be passed + to gdbarch_displaced_step_fixup. Or, return NULL to indicate that + the instruction's effects have been completely simulated, with the + resulting state written back to REGS. + + For a general explanation of displaced stepping and how GDB uses it, + see the comments in infrun.c. + + The TO area is only guaranteed to have space for + gdbarch_max_insn_length (arch) bytes, so this function must not + write more bytes than that to that area. + + If you do not provide this function, GDB assumes that the + architecture does not support displaced stepping. + + If your architecture doesn't need to adjust instructions before + single-stepping them, consider using simple_displaced_step_copy_insn + here. */ + +extern int gdbarch_displaced_step_copy_insn_p (struct gdbarch *gdbarch); + +typedef struct displaced_step_closure * (gdbarch_displaced_step_copy_insn_ftype) (struct gdbarch *gdbarch, CORE_ADDR from, CORE_ADDR to, struct regcache *regs); +extern struct displaced_step_closure * gdbarch_displaced_step_copy_insn (struct gdbarch *gdbarch, CORE_ADDR from, CORE_ADDR to, struct regcache *regs); +extern void set_gdbarch_displaced_step_copy_insn (struct gdbarch *gdbarch, gdbarch_displaced_step_copy_insn_ftype *displaced_step_copy_insn); + +/* Fix up the state resulting from successfully single-stepping a + displaced instruction, to give the result we would have gotten from + stepping the instruction in its original location. + + REGS is the register state resulting from single-stepping the + displaced instruction. + + CLOSURE is the result from the matching call to + gdbarch_displaced_step_copy_insn. + + If you provide gdbarch_displaced_step_copy_insn.but not this + function, then GDB assumes that no fixup is needed after + single-stepping the instruction. + + For a general explanation of displaced stepping and how GDB uses it, + see the comments in infrun.c. */ + +extern int gdbarch_displaced_step_fixup_p (struct gdbarch *gdbarch); + +typedef void (gdbarch_displaced_step_fixup_ftype) (struct gdbarch *gdbarch, struct displaced_step_closure *closure, CORE_ADDR from, CORE_ADDR to, struct regcache *regs); +extern void gdbarch_displaced_step_fixup (struct gdbarch *gdbarch, struct displaced_step_closure *closure, CORE_ADDR from, CORE_ADDR to, struct regcache *regs); +extern void set_gdbarch_displaced_step_fixup (struct gdbarch *gdbarch, gdbarch_displaced_step_fixup_ftype *displaced_step_fixup); + +/* Free a closure returned by gdbarch_displaced_step_copy_insn. + + If you provide gdbarch_displaced_step_copy_insn, you must provide + this function as well. + + If your architecture uses closures that don't need to be freed, then + you can use simple_displaced_step_free_closure here. + + For a general explanation of displaced stepping and how GDB uses it, + see the comments in infrun.c. */ + +typedef void (gdbarch_displaced_step_free_closure_ftype) (struct gdbarch *gdbarch, struct displaced_step_closure *closure); +extern void gdbarch_displaced_step_free_closure (struct gdbarch *gdbarch, struct displaced_step_closure *closure); +extern void set_gdbarch_displaced_step_free_closure (struct gdbarch *gdbarch, gdbarch_displaced_step_free_closure_ftype *displaced_step_free_closure); + +/* Return the address of an appropriate place to put displaced + instructions while we step over them. There need only be one such + place, since we're only stepping one thread over a breakpoint at a + time. + + For a general explanation of displaced stepping and how GDB uses it, + see the comments in infrun.c. */ + +typedef CORE_ADDR (gdbarch_displaced_step_location_ftype) (struct gdbarch *gdbarch); +extern CORE_ADDR gdbarch_displaced_step_location (struct gdbarch *gdbarch); +extern void set_gdbarch_displaced_step_location (struct gdbarch *gdbarch, gdbarch_displaced_step_location_ftype *displaced_step_location); + /* Refresh overlay mapped state for section OSECT. */ extern int gdbarch_overlay_update_p (struct gdbarch *gdbarch); diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh index 9f04f08..08e3741 100755 --- a/gdb/gdbarch.sh +++ b/gdb/gdbarch.sh @@ -616,6 +616,75 @@ v:int:vbit_in_delta:::0:0::0 # Advance PC to next instruction in order to skip a permanent breakpoint. F:void:skip_permanent_breakpoint:struct regcache *regcache:regcache +# The maximum length of an instruction on this architecture. +V:ULONGEST:max_insn_length:::0:0 + +# Copy the instruction at FROM to TO, and make any adjustments +# necessary to single-step it at that address. +# +# REGS holds the state the thread's registers will have before +# executing the copied instruction; the PC in REGS will refer to FROM, +# not the copy at TO. The caller should update it to point at TO later. +# +# Return a pointer to data of the architecture's choice to be passed +# to gdbarch_displaced_step_fixup. Or, return NULL to indicate that +# the instruction's effects have been completely simulated, with the +# resulting state written back to REGS. +# +# For a general explanation of displaced stepping and how GDB uses it, +# see the comments in infrun.c. +# +# The TO area is only guaranteed to have space for +# gdbarch_max_insn_length (arch) bytes, so this function must not +# write more bytes than that to that area. +# +# If you do not provide this function, GDB assumes that the +# architecture does not support displaced stepping. +# +# If your architecture doesn't need to adjust instructions before +# single-stepping them, consider using simple_displaced_step_copy_insn +# here. +M:struct displaced_step_closure *:displaced_step_copy_insn:CORE_ADDR from, CORE_ADDR to, struct regcache *regs:from, to, regs + +# Fix up the state resulting from successfully single-stepping a +# displaced instruction, to give the result we would have gotten from +# stepping the instruction in its original location. +# +# REGS is the register state resulting from single-stepping the +# displaced instruction. +# +# CLOSURE is the result from the matching call to +# gdbarch_displaced_step_copy_insn. +# +# If you provide gdbarch_displaced_step_copy_insn.but not this +# function, then GDB assumes that no fixup is needed after +# single-stepping the instruction. +# +# For a general explanation of displaced stepping and how GDB uses it, +# see the comments in infrun.c. +M:void:displaced_step_fixup:struct displaced_step_closure *closure, CORE_ADDR from, CORE_ADDR to, struct regcache *regs:closure, from, to, regs::NULL + +# Free a closure returned by gdbarch_displaced_step_copy_insn. +# +# If you provide gdbarch_displaced_step_copy_insn, you must provide +# this function as well. +# +# If your architecture uses closures that don't need to be freed, then +# you can use simple_displaced_step_free_closure here. +# +# For a general explanation of displaced stepping and how GDB uses it, +# see the comments in infrun.c. +m:void:displaced_step_free_closure:struct displaced_step_closure *closure:closure::NULL::(! gdbarch->displaced_step_free_closure) != (! gdbarch->displaced_step_copy_insn) + +# Return the address of an appropriate place to put displaced +# instructions while we step over them. There need only be one such +# place, since we're only stepping one thread over a breakpoint at a +# time. +# +# For a general explanation of displaced stepping and how GDB uses it, +# see the comments in infrun.c. +m:CORE_ADDR:displaced_step_location:void:::NULL::(! gdbarch->displaced_step_location) != (! gdbarch->displaced_step_copy_insn) + # Refresh overlay mapped state for section OSECT. F:void:overlay_update:struct obj_section *osect:osect @@ -742,6 +811,7 @@ struct target_ops; struct obstack; struct bp_target_info; struct target_desc; +struct displaced_step_closure; extern struct gdbarch *current_gdbarch; EOF diff --git a/gdb/i386-linux-tdep.c b/gdb/i386-linux-tdep.c index 3242bbd..e3e79e1 100644 --- a/gdb/i386-linux-tdep.c +++ b/gdb/i386-linux-tdep.c @@ -34,6 +34,7 @@ #include "glibc-tdep.h" #include "solib-svr4.h" #include "symtab.h" +#include "arch-utils.h" /* Return the name of register REG. */ @@ -446,6 +447,15 @@ i386_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) /* Enable TLS support. */ set_gdbarch_fetch_tls_load_module_address (gdbarch, svr4_fetch_objfile_link_map); + + /* Displaced stepping. */ + set_gdbarch_displaced_step_copy_insn (gdbarch, + simple_displaced_step_copy_insn); + set_gdbarch_displaced_step_fixup (gdbarch, i386_displaced_step_fixup); + set_gdbarch_displaced_step_free_closure (gdbarch, + simple_displaced_step_free_closure); + set_gdbarch_displaced_step_location (gdbarch, + displaced_step_at_entry_point); } /* Provide a prototype to silence -Wmissing-prototypes. */ diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c index 3623a5a..08484ba 100644 --- a/gdb/i386-tdep.c +++ b/gdb/i386-tdep.c @@ -276,6 +276,225 @@ i386_breakpoint_from_pc (struct gdbarch *gdbarch, CORE_ADDR *pc, int *len) return break_insn; } +/* Displaced instruction handling. */ + + +static int +i386_absolute_jmp_p (gdb_byte *insn) +{ + /* jmp far (absolute address in operand) */ + if (insn[0] == 0xea) + return 1; + + if (insn[0] == 0xff) + { + /* jump near, absolute indirect (/4) */ + if ((insn[1] & 0x38) == 0x20) + return 1; + + /* jump far, absolute indirect (/5) */ + if ((insn[1] & 0x38) == 0x28) + return 1; + } + + return 0; +} + +static int +i386_absolute_call_p (gdb_byte *insn) +{ + /* call far, absolute */ + if (insn[0] == 0x9a) + return 1; + + if (insn[0] == 0xff) + { + /* Call near, absolute indirect (/2) */ + if ((insn[1] & 0x38) == 0x10) + return 1; + + /* Call far, absolute indirect (/3) */ + if ((insn[1] & 0x38) == 0x18) + return 1; + } + + return 0; +} + +static int +i386_ret_p (gdb_byte *insn) +{ + switch (insn[0]) + { + case 0xc2: /* ret near, pop N bytes */ + case 0xc3: /* ret near */ + case 0xca: /* ret far, pop N bytes */ + case 0xcb: /* ret far */ + case 0xcf: /* iret */ + return 1; + + default: + return 0; + } +} + +static int +i386_call_p (gdb_byte *insn) +{ + if (i386_absolute_call_p (insn)) + return 1; + + /* call near, relative */ + if (insn[0] == 0xe8) + return 1; + + return 0; +} + +static int +i386_breakpoint_p (gdb_byte *insn) +{ + return insn[0] == 0xcc; /* int 3 */ +} + +/* Return non-zero if INSN is a system call, and set *LENGTHP to its + length in bytes. Otherwise, return zero. */ +static int +i386_syscall_p (gdb_byte *insn, ULONGEST *lengthp) +{ + if (insn[0] == 0xcd) + { + *lengthp = 2; + return 1; + } + + return 0; +} + +/* Fix up the state of registers and memory after having single-stepped + a displaced instruction. */ +void +i386_displaced_step_fixup (struct gdbarch *gdbarch, + struct displaced_step_closure *closure, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs) +{ + /* The offset we applied to the instruction's address. + This could well be negative (when viewed as a signed 32-bit + value), but ULONGEST won't reflect that, so take care when + applying it. */ + ULONGEST insn_offset = to - from; + + /* Since we use simple_displaced_step_copy_insn, our closure is a + copy of the instruction. */ + gdb_byte *insn = (gdb_byte *) closure; + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: fixup (0x%s, 0x%s), " + "insn = 0x%02x 0x%02x ...\n", + paddr_nz (from), paddr_nz (to), insn[0], insn[1]); + + /* The list of issues to contend with here is taken from + resume_execution in arch/i386/kernel/kprobes.c, Linux 2.6.20. + Yay for Free Software! */ + + /* Relocate the %eip, if necessary. */ + + /* Except in the case of absolute or indirect jump or call + instructions, or a return instruction, the new eip is relative to + the displaced instruction; make it relative. Well, signal + handler returns don't need relocation either, but we use the + value of %eip to recognize those; see below. */ + if (! i386_absolute_jmp_p (insn) + && ! i386_absolute_call_p (insn) + && ! i386_ret_p (insn)) + { + ULONGEST orig_eip; + ULONGEST insn_len; + + regcache_cooked_read_unsigned (regs, I386_EIP_REGNUM, &orig_eip); + + /* A signal trampoline system call changes the %eip, resuming + execution of the main program after the signal handler has + returned. That makes them like 'return' instructions; we + shouldn't relocate %eip. + + But most system calls don't, and we do need to relocate %eip. + + Our heuristic for distinguishing these cases: if stepping + over the system call instruction left control directly after + the instruction, the we relocate --- control almost certainly + doesn't belong in the displaced copy. Otherwise, we assume + the instruction has put control where it belongs, and leave + it unrelocated. Goodness help us if there are PC-relative + system calls. */ + if (i386_syscall_p (insn, &insn_len) + && orig_eip != to + insn_len) + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: syscall changed %%eip; " + "not relocating\n"); + } + else + { + ULONGEST eip = (orig_eip - insn_offset) & 0xffffffffUL; + + /* If we have stepped over a breakpoint, set the %eip to + point at the breakpoint instruction itself. + + (gdbarch_decr_pc_after_break was never something the core + of GDB should have been concerned with; arch-specific + code should be making PC values consistent before + presenting them to GDB.) */ + if (i386_breakpoint_p (insn)) + { + fprintf_unfiltered (gdb_stdlog, + "displaced: stepped breakpoint\n"); + eip--; + } + + regcache_cooked_write_unsigned (regs, I386_EIP_REGNUM, eip); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: " + "relocated %%eip from 0x%s to 0x%s\n", + paddr_nz (orig_eip), paddr_nz (eip)); + } + } + + /* If the instruction was PUSHFL, then the TF bit will be set in the + pushed value, and should be cleared. We'll leave this for later, + since GDB already messes up the TF flag when stepping over a + pushfl. */ + + /* If the instruction was a call, the return address now atop the + stack is the address following the copied instruction. We need + to make it the address following the original instruction. */ + if (i386_call_p (insn)) + { + ULONGEST esp; + ULONGEST retaddr; + const ULONGEST retaddr_len = 4; + + regcache_cooked_read_unsigned (regs, I386_ESP_REGNUM, &esp); + retaddr = read_memory_unsigned_integer (esp, retaddr_len); + retaddr = (retaddr - insn_offset) & 0xffffffffUL; + write_memory_unsigned_integer (esp, retaddr_len, retaddr); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: relocated return addr at 0x%s " + "to 0x%s\n", + paddr_nz (esp), + paddr_nz (retaddr)); + } +} + + + #ifdef I386_REGNO_TO_SYMMETRY #error "The Sequent Symmetry is no longer supported." #endif @@ -521,14 +740,14 @@ i386_analyze_stack_align (CORE_ADDR pc, CORE_ADDR current_pc, } /* Maximum instruction length we need to handle. */ -#define I386_MAX_INSN_LEN 6 +#define I386_MAX_MATCHED_INSN_LEN 6 /* Instruction description. */ struct i386_insn { size_t len; - gdb_byte insn[I386_MAX_INSN_LEN]; - gdb_byte mask[I386_MAX_INSN_LEN]; + gdb_byte insn[I386_MAX_MATCHED_INSN_LEN]; + gdb_byte mask[I386_MAX_MATCHED_INSN_LEN]; }; /* Search for the instruction at PC in the list SKIP_INSNS. Return @@ -547,12 +766,12 @@ i386_match_insn (CORE_ADDR pc, struct i386_insn *skip_insns) { if ((op & insn->mask[0]) == insn->insn[0]) { - gdb_byte buf[I386_MAX_INSN_LEN - 1]; + gdb_byte buf[I386_MAX_MATCHED_INSN_LEN - 1]; int insn_matched = 1; size_t i; gdb_assert (insn->len > 1); - gdb_assert (insn->len <= I386_MAX_INSN_LEN); + gdb_assert (insn->len <= I386_MAX_MATCHED_INSN_LEN); target_read_memory (pc + 1, buf, insn->len - 1); for (i = 1; i < insn->len; i++) @@ -2375,6 +2594,7 @@ i386_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) set_gdbarch_breakpoint_from_pc (gdbarch, i386_breakpoint_from_pc); set_gdbarch_decr_pc_after_break (gdbarch, 1); + set_gdbarch_max_insn_length (gdbarch, I386_MAX_INSN_LEN); set_gdbarch_frame_args_skip (gdbarch, 8); diff --git a/gdb/i386-tdep.h b/gdb/i386-tdep.h index c4735f4..227ac66 100644 --- a/gdb/i386-tdep.h +++ b/gdb/i386-tdep.h @@ -164,6 +164,10 @@ extern struct type *i386_sse_type (struct gdbarch *gdbarch); #define I386_SEL_UPL 0x0003 /* User Privilige Level. */ #define I386_SEL_KPL 0x0000 /* Kernel Privilige Level. */ +/* The length of the longest i386 instruction (according to + include/asm-i386/kprobes.h in Linux 2.6. */ +#define I386_MAX_INSN_LEN (16) + /* Functions exported from i386-tdep.c. */ extern CORE_ADDR i386_pe_skip_trampoline_code (CORE_ADDR pc, char *name); @@ -195,6 +199,12 @@ extern const struct regset * i386_regset_from_core_section (struct gdbarch *gdbarch, const char *sect_name, size_t sect_size); + +extern void i386_displaced_step_fixup (struct gdbarch *gdbarch, + struct displaced_step_closure *closure, + CORE_ADDR from, CORE_ADDR to, + struct regcache *regs); + /* Initialize a basic ELF architecture variant. */ extern void i386_elf_init_abi (struct gdbarch_info, struct gdbarch *); diff --git a/gdb/inferior.h b/gdb/inferior.h index e78c25e..83c76fc 100644 --- a/gdb/inferior.h +++ b/gdb/inferior.h @@ -387,6 +387,14 @@ extern struct regcache *stop_registers; than forked. */ extern int attach_flag; + +/* True if we are debugging displaced stepping. */ +extern int debug_displaced; + +/* Dump LEN bytes at BUF in hex to FILE, followed by a newline. */ +void displaced_step_dump_bytes (struct ui_file *file, + const gdb_byte *buf, size_t len); + /* Possible values for gdbarch_call_dummy_location. */ #define ON_STACK 1 diff --git a/gdb/infrun.c b/gdb/infrun.c index 582cb51..a3e7695 100644 --- a/gdb/infrun.c +++ b/gdb/infrun.c @@ -103,6 +103,14 @@ int sync_execution = 0; static ptid_t previous_inferior_ptid; +int debug_displaced = 0; +static void +show_debug_displaced (struct ui_file *file, int from_tty, + struct cmd_list_element *c, const char *value) +{ + fprintf_filtered (file, _("Displace stepping debugging is %s.\n"), value); +} + static int debug_infrun = 0; static void show_debug_infrun (struct ui_file *file, int from_tty, @@ -459,6 +467,377 @@ static int stepping_past_singlestep_breakpoint; stepping the thread user has selected. */ static ptid_t deferred_step_ptid; +/* Displaced stepping. */ + +/* In non-stop debugging mode, we must take special care to manage + breakpoints properly; in particular, the traditional strategy for + stepping a thread past a breakpoint it has hit is unsuitable. + 'Displaced stepping' is a tactic for stepping one thread past a + breakpoint it has hit while ensuring that other threads running + concurrently will hit the breakpoint as they should. + + The traditional way to step a thread T off a breakpoint in a + multi-threaded program in all-stop mode is as follows: + + a0) Initially, all threads are stopped, and breakpoints are not + inserted. + a1) We single-step T, leaving breakpoints uninserted. + a2) We insert breakpoints, and resume all threads. + + In non-stop debugging, however, this strategy is unsuitable: we + don't want to have to stop all threads in the system in order to + continue or step T past a breakpoint. Instead, we use displaced + stepping: + + n0) Initially, T is stopped, other threads are running, and + breakpoints are inserted. + n1) We copy the instruction "under" the breakpoint to a separate + location, outside the main code stream, making any adjustments + to the instruction, register, and memory state as directed by + T's architecture. + n2) We single-step T over the instruction at its new location. + n3) We adjust the resulting register and memory state as directed + by T's architecture. This includes resetting T's PC to point + back into the main instruction stream. + n4) We resume T. + + This approach depends on the following gdbarch methods: + + - gdbarch_max_insn_length and gdbarch_displaced_step_location + indicate where to copy the instruction, and how much space must + be reserved there. We use these in step n1. + + - gdbarch_displaced_step_copy_insn copies a instruction to a new + address, and makes any necessary adjustments to the instruction, + register contents, and memory. We use this in step n1. + + - gdbarch_displaced_step_fixup adjusts registers and memory after + we have successfuly single-stepped the instruction, to yield the + same effect the instruction would have had if we had executed it + at its original address. We use this in step n3. + + - gdbarch_displaced_step_free_closure provides cleanup. + + The gdbarch_displaced_step_copy_insn and + gdbarch_displaced_step_fixup functions must be written so that + copying an instruction with gdbarch_displaced_step_copy_insn, + single-stepping across the copied instruction, and then applying + gdbarch_displaced_insn_fixup should have the same effects on the + thread's memory and registers as stepping the instruction in place + would have. Exactly which responsibilities fall to the copy and + which fall to the fixup is up to the author of those functions. + + See the comments in gdbarch.sh for details. + + Note that displaced stepping and software single-step cannot + currently be used in combination, although with some care I think + they could be made to. Software single-step works by placing + breakpoints on all possible subsequent instructions; if the + displaced instruction is a PC-relative jump, those breakpoints + could fall in very strange places --- on pages that aren't + executable, or at addresses that are not proper instruction + boundaries. (We do generally let other threads run while we wait + to hit the software single-step breakpoint, and they might + encounter such a corrupted instruction.) One way to work around + this would be to have gdbarch_displaced_step_copy_insn fully + simulate the effect of PC-relative instructions (and return NULL) + on architectures that use software single-stepping. + + In non-stop mode, we can have independent and simultaneous step + requests, so more than one thread may need to simultaneously step + over a breakpoint. The current implementation assumes there is + only one scratch space per process. In this case, we have to + serialize access to the scratch space. If thread A wants to step + over a breakpoint, but we are currently waiting for some other + thread to complete a displaced step, we leave thread A stopped and + place it in the displaced_step_request_queue. Whenever a displaced + step finishes, we pick the next thread in the queue and start a new + displaced step operation on it. See displaced_step_prepare and + displaced_step_fixup for details. */ + +/* If this is not null_ptid, this is the thread carrying out a + displaced single-step. This thread's state will require fixing up + once it has completed its step. */ +static ptid_t displaced_step_ptid; + +struct displaced_step_request +{ + ptid_t ptid; + struct displaced_step_request *next; +}; + +/* A queue of pending displaced stepping requests. */ +struct displaced_step_request *displaced_step_request_queue; + +/* The architecture the thread had when we stepped it. */ +static struct gdbarch *displaced_step_gdbarch; + +/* The closure provided gdbarch_displaced_step_copy_insn, to be used + for post-step cleanup. */ +static struct displaced_step_closure *displaced_step_closure; + +/* The address of the original instruction, and the copy we made. */ +static CORE_ADDR displaced_step_original, displaced_step_copy; + +/* Saved contents of copy area. */ +static gdb_byte *displaced_step_saved_copy; + +/* When this is non-zero, we are allowed to use displaced stepping, if + the architecture supports it. When this is zero, we use + traditional the hold-and-step approach. */ +int can_use_displaced_stepping = 1; +static void +show_can_use_displaced_stepping (struct ui_file *file, int from_tty, + struct cmd_list_element *c, + const char *value) +{ + fprintf_filtered (file, _("\ +Debugger's willingness to use displaced stepping to step over " +"breakpoints is %s.\n"), value); +} + +/* Return non-zero if displaced stepping is enabled, and can be used + with GDBARCH. */ +static int +use_displaced_stepping (struct gdbarch *gdbarch) +{ + return (can_use_displaced_stepping + && gdbarch_displaced_step_copy_insn_p (gdbarch)); +} + +/* Clean out any stray displaced stepping state. */ +static void +displaced_step_clear (void) +{ + /* Indicate that there is no cleanup pending. */ + displaced_step_ptid = null_ptid; + + if (displaced_step_closure) + { + gdbarch_displaced_step_free_closure (displaced_step_gdbarch, + displaced_step_closure); + displaced_step_closure = NULL; + } +} + +static void +cleanup_displaced_step_closure (void *ptr) +{ + struct displaced_step_closure *closure = ptr; + + gdbarch_displaced_step_free_closure (current_gdbarch, closure); +} + +/* Dump LEN bytes at BUF in hex to FILE, followed by a newline. */ +void +displaced_step_dump_bytes (struct ui_file *file, + const gdb_byte *buf, + size_t len) +{ + int i; + + for (i = 0; i < len; i++) + fprintf_unfiltered (file, "%02x ", buf[i]); + fputs_unfiltered ("\n", file); +} + +/* Prepare to single-step, using displaced stepping. + + Note that we cannot use displaced stepping when we have a signal to + deliver. If we have a signal to deliver and an instruction to step + over, then after the step, there will be no indication from the + target whether the thread entered a signal handler or ignored the + signal and stepped over the instruction successfully --- both cases + result in a simple SIGTRAP. In the first case we mustn't do a + fixup, and in the second case we must --- but we can't tell which. + Comments in the code for 'random signals' in handle_inferior_event + explain how we handle this case instead. + + Returns 1 if preparing was successful -- this thread is going to be + stepped now; or 0 if displaced stepping this thread got queued. */ +static int +displaced_step_prepare (ptid_t ptid) +{ + struct cleanup *old_cleanups; + struct regcache *regcache = get_thread_regcache (ptid); + struct gdbarch *gdbarch = get_regcache_arch (regcache); + CORE_ADDR original, copy; + ULONGEST len; + struct displaced_step_closure *closure; + + /* We should never reach this function if the architecture does not + support displaced stepping. */ + gdb_assert (gdbarch_displaced_step_copy_insn_p (gdbarch)); + + /* For the first cut, we're displaced stepping one thread at a + time. */ + + if (!ptid_equal (displaced_step_ptid, null_ptid)) + { + /* Already waiting for a displaced step to finish. Defer this + request and place in queue. */ + struct displaced_step_request *req, *new_req; + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: defering step of %s\n", + target_pid_to_str (ptid)); + + new_req = xmalloc (sizeof (*new_req)); + new_req->ptid = ptid; + new_req->next = NULL; + + if (displaced_step_request_queue) + { + for (req = displaced_step_request_queue; + req && req->next; + req = req->next) + ; + req->next = new_req; + } + else + displaced_step_request_queue = new_req; + + return 0; + } + else + { + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: stepping %s now\n", + target_pid_to_str (ptid)); + } + + displaced_step_clear (); + + original = read_pc_pid (ptid); + + copy = gdbarch_displaced_step_location (gdbarch); + len = gdbarch_max_insn_length (gdbarch); + + /* Save the original contents of the copy area. */ + displaced_step_saved_copy = xmalloc (len); + old_cleanups = make_cleanup (free_current_contents, + &displaced_step_saved_copy); + read_memory (copy, displaced_step_saved_copy, len); + if (debug_displaced) + { + fprintf_unfiltered (gdb_stdlog, "displaced: saved 0x%s: ", + paddr_nz (copy)); + displaced_step_dump_bytes (gdb_stdlog, displaced_step_saved_copy, len); + }; + + closure = gdbarch_displaced_step_copy_insn (gdbarch, + original, copy, regcache); + + /* We don't support the fully-simulated case at present. */ + gdb_assert (closure); + + make_cleanup (cleanup_displaced_step_closure, closure); + + /* Resume execution at the copy. */ + write_pc_pid (copy, ptid); + + discard_cleanups (old_cleanups); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: displaced pc to 0x%s\n", + paddr_nz (copy)); + + /* Save the information we need to fix things up if the step + succeeds. */ + displaced_step_ptid = ptid; + displaced_step_gdbarch = gdbarch; + displaced_step_closure = closure; + displaced_step_original = original; + displaced_step_copy = copy; + return 1; +} + +static void +displaced_step_clear_cleanup (void *ignore) +{ + displaced_step_clear (); +} + +static void +write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr, const gdb_byte *myaddr, int len) +{ + struct cleanup *ptid_cleanup = save_inferior_ptid (); + inferior_ptid = ptid; + write_memory (memaddr, myaddr, len); + do_cleanups (ptid_cleanup); +} + +static void +displaced_step_fixup (ptid_t event_ptid, enum target_signal signal) +{ + struct cleanup *old_cleanups; + + /* Was this event for the pid we displaced? */ + if (ptid_equal (displaced_step_ptid, null_ptid) + || ! ptid_equal (displaced_step_ptid, event_ptid)) + return; + + old_cleanups = make_cleanup (displaced_step_clear_cleanup, 0); + + /* Restore the contents of the copy area. */ + { + ULONGEST len = gdbarch_max_insn_length (displaced_step_gdbarch); + write_memory_ptid (displaced_step_ptid, displaced_step_copy, + displaced_step_saved_copy, len); + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, "displaced: restored 0x%s\n", + paddr_nz (displaced_step_copy)); + } + + /* Did the instruction complete successfully? */ + if (signal == TARGET_SIGNAL_TRAP) + { + /* Fix up the resulting state. */ + gdbarch_displaced_step_fixup (displaced_step_gdbarch, + displaced_step_closure, + displaced_step_original, + displaced_step_copy, + get_thread_regcache (displaced_step_ptid)); + } + else + { + /* Since the instruction didn't complete, all we can do is + relocate the PC. */ + CORE_ADDR pc = read_pc_pid (event_ptid); + pc = displaced_step_original + (pc - displaced_step_copy); + write_pc_pid (pc, event_ptid); + } + + do_cleanups (old_cleanups); + + /* Are there any pending displaced stepping requests? If so, run + one now. */ + if (displaced_step_request_queue) + { + struct displaced_step_request *head; + ptid_t ptid; + + head = displaced_step_request_queue; + ptid = head->ptid; + displaced_step_request_queue = head->next; + xfree (head); + + if (debug_displaced) + fprintf_unfiltered (gdb_stdlog, + "displaced: stepping queued %s now\n", + target_pid_to_str (ptid)); + + + displaced_step_ptid = null_ptid; + displaced_step_prepare (ptid); + target_resume (ptid, 1, TARGET_SIGNAL_0); + } +} + + +/* Resuming. */ /* Things to clean up if we QUIT out of resume (). */ static void @@ -510,14 +889,14 @@ resume (int step, enum target_signal sig) { int should_resume = 1; struct cleanup *old_cleanups = make_cleanup (resume_cleanups, 0); + CORE_ADDR pc = read_pc (); QUIT; if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: resume (step=%d, signal=%d)\n", - step, sig); - - /* FIXME: calling breakpoint_here_p (read_pc ()) three times! */ - + fprintf_unfiltered (gdb_stdlog, + "infrun: resume (step=%d, signal=%d), " + "stepping_over_breakpoint=%d\n", + step, sig, stepping_over_breakpoint); /* Some targets (e.g. Solaris x86) have a kernel bug when stepping over an instruction that causes a page fault without triggering @@ -535,7 +914,7 @@ resume (int step, enum target_signal sig) removed or inserted, as appropriate. The exception is if we're sitting at a permanent breakpoint; we need to step over it, but permanent breakpoints can't be removed. So we have to test for it here. */ - if (breakpoint_here_p (read_pc ()) == permanent_breakpoint_here) + if (breakpoint_here_p (pc) == permanent_breakpoint_here) { if (gdbarch_skip_permanent_breakpoint_p (current_gdbarch)) gdbarch_skip_permanent_breakpoint (current_gdbarch, @@ -547,6 +926,24 @@ how to step past a permanent breakpoint on this architecture. Try using\n\ a command like `return' or `jump' to continue execution.")); } + /* If enabled, step over breakpoints by executing a copy of the + instruction at a different address. + + We can't use displaced stepping when we have a signal to deliver; + the comments for displaced_step_prepare explain why. The + comments in the handle_inferior event for dealing with 'random + signals' explain what we do instead. */ + if (use_displaced_stepping (current_gdbarch) + && stepping_over_breakpoint + && sig == TARGET_SIGNAL_0) + { + if (!displaced_step_prepare (inferior_ptid)) + /* Got placed in displaced stepping queue. Will be resumed + later when all the currently queued displaced stepping + requests finish. */ + return; + } + if (step && gdbarch_software_single_step_p (current_gdbarch)) { /* Do it the hard way, w/temp breakpoints */ @@ -558,7 +955,7 @@ a command like `return' or `jump' to continue execution.")); `wait_for_inferior' */ singlestep_breakpoints_inserted_p = 1; singlestep_ptid = inferior_ptid; - singlestep_pc = read_pc (); + singlestep_pc = pc; } } @@ -642,15 +1039,30 @@ a command like `return' or `jump' to continue execution.")); /* Most targets can step a breakpoint instruction, thus executing it normally. But if this one cannot, just continue and we will hit it anyway. */ - if (step && breakpoint_inserted_here_p (read_pc ())) + if (step && breakpoint_inserted_here_p (pc)) step = 0; } + + if (debug_displaced + && use_displaced_stepping (current_gdbarch) + && stepping_over_breakpoint) + { + CORE_ADDR actual_pc = read_pc_pid (resume_ptid); + gdb_byte buf[4]; + + fprintf_unfiltered (gdb_stdlog, "displaced: run 0x%s: ", + paddr_nz (actual_pc)); + read_memory (actual_pc, buf, sizeof (buf)); + displaced_step_dump_bytes (gdb_stdlog, buf, sizeof (buf)); + } + target_resume (resume_ptid, step, sig); } discard_cleanups (old_cleanups); } +/* Proceeding. */ /* Clear out all variables saying what to do when inferior is continued. First do this, then set the ones you want, then call `proceed'. */ @@ -787,17 +1199,20 @@ proceed (CORE_ADDR addr, enum target_signal siggnal, int step) if (oneproc) { - /* We will get a trace trap after one instruction. - Continue it automatically and insert breakpoints then. */ stepping_over_breakpoint = 1; - /* FIXME: if breakpoints are always inserted, we'll trap - if trying to single-step over breakpoint. Disable - all breakpoints. In future, we'd need to invent some - smart way of stepping over breakpoint instruction without - hitting breakpoint. */ - remove_breakpoints (); + /* If displaced stepping is enabled, we can step over the + breakpoint without hitting it, so leave all breakpoints + inserted. Otherwise we need to disable all breakpoints, step + one instruction, and then re-add them when that step is + finished. */ + if (!use_displaced_stepping (current_gdbarch)) + remove_breakpoints (); } - else + + /* We can insert breakpoints if we're not trying to step over one, + or if we are stepping over one but we're using displaced stepping + to do so. */ + if (! stepping_over_breakpoint || use_displaced_stepping (current_gdbarch)) insert_breakpoints (); if (siggnal != TARGET_SIGNAL_DEFAULT) @@ -908,7 +1323,10 @@ init_wait_for_inferior (void) deferred_step_ptid = null_ptid; target_last_wait_ptid = minus_one_ptid; + + displaced_step_clear (); } + /* This enum encodes possible reasons for doing a target_wait, so that wfi can call target_wait in one place. (Ultimately the call will be @@ -1580,10 +1998,31 @@ handle_inferior_event (struct execution_control_state *ecs) return; } + /* Do we need to clean up the state of a thread that has completed a + displaced single-step? (Doing so usually affects the PC, so do + it here, before we set stop_pc.) */ + displaced_step_fixup (ecs->ptid, stop_signal); + stop_pc = read_pc_pid (ecs->ptid); if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: stop_pc = 0x%s\n", paddr_nz (stop_pc)); + { + fprintf_unfiltered (gdb_stdlog, "infrun: stop_pc = 0x%s\n", + paddr_nz (stop_pc)); + if (STOPPED_BY_WATCHPOINT (&ecs->ws)) + { + CORE_ADDR addr; + fprintf_unfiltered (gdb_stdlog, "infrun: stopped by watchpoint\n"); + + if (target_stopped_data_address (¤t_target, &addr)) + fprintf_unfiltered (gdb_stdlog, + "infrun: stopped data address = 0x%s\n", + paddr_nz (addr)); + else + fprintf_unfiltered (gdb_stdlog, + "infrun: (no data address available)\n"); + } + } if (stepping_past_singlestep_breakpoint) { @@ -1731,7 +2170,7 @@ handle_inferior_event (struct execution_control_state *ecs) if (thread_hop_needed) { - int remove_status; + int remove_status = 0; if (debug_infrun) fprintf_unfiltered (gdb_stdlog, "infrun: thread_hop_needed\n"); @@ -1746,7 +2185,11 @@ handle_inferior_event (struct execution_control_state *ecs) singlestep_breakpoints_inserted_p = 0; } - remove_status = remove_breakpoints (); + /* If the arch can displace step, don't remove the + breakpoints. */ + if (!use_displaced_stepping (current_gdbarch)) + remove_status = remove_breakpoints (); + /* Did we fail to remove breakpoints? If so, try to set the PC past the bp. (There's at least one situation in which we can fail to remove @@ -1810,9 +2253,6 @@ handle_inferior_event (struct execution_control_state *ecs) && (HAVE_STEPPABLE_WATCHPOINT || gdbarch_have_nonsteppable_watchpoint (current_gdbarch))) { - if (debug_infrun) - fprintf_unfiltered (gdb_stdlog, "infrun: STOPPED_BY_WATCHPOINT\n"); - /* At this point, we are stopped at an instruction which has attempted to write to a piece of memory under control of a watchpoint. The instruction hasn't actually executed @@ -1915,10 +2355,14 @@ handle_inferior_event (struct execution_control_state *ecs) when we're trying to execute a breakpoint instruction on a non-executable stack. This happens for call dummy breakpoints for architectures like SPARC that place call dummies on the - stack. */ + stack. + If we're doing a displaced step past a breakpoint, then the + breakpoint is always inserted at the original instruction; + non-standard signals can't be explained by the breakpoint. */ if (stop_signal == TARGET_SIGNAL_TRAP - || (breakpoint_inserted_here_p (stop_pc) + || (! stepping_over_breakpoint + && breakpoint_inserted_here_p (stop_pc) && (stop_signal == TARGET_SIGNAL_ILL || stop_signal == TARGET_SIGNAL_SEGV || stop_signal == TARGET_SIGNAL_EMT)) @@ -2045,7 +2489,7 @@ process_event_stop_test: { /* We were just starting a new sequence, attempting to single-step off of a breakpoint and expecting a SIGTRAP. - Intead this signal arrives. This signal will take us out + Instead this signal arrives. This signal will take us out of the stepping range so GDB needs to remember to, when the signal handler returns, resume stepping off that breakpoint. */ @@ -2053,6 +2497,10 @@ process_event_stop_test: code paths as single-step - set a breakpoint at the signal return address and then, once hit, step off that breakpoint. */ + if (debug_infrun) + fprintf_unfiltered (gdb_stdlog, + "infrun: signal arrived while stepping over " + "breakpoint\n"); insert_step_resume_breakpoint_at_frame (get_current_frame ()); ecs->step_after_step_resume_breakpoint = 1; @@ -2076,6 +2524,11 @@ process_event_stop_test: Note that this is only needed for a signal delivered while in the single-step range. Nested signals aren't a problem as they eventually all return. */ + if (debug_infrun) + fprintf_unfiltered (gdb_stdlog, + "infrun: signal may take us out of " + "single-step range\n"); + insert_step_resume_breakpoint_at_frame (get_current_frame ()); keep_going (ecs); return; @@ -2905,7 +3358,11 @@ keep_going (struct execution_control_state *ecs) if (ecs->stepping_over_breakpoint) { - remove_breakpoints (); + if (! use_displaced_stepping (current_gdbarch)) + /* Since we can't do a displaced step, we have to remove + the breakpoint while we step it. To keep things + simple, we remove them all. */ + remove_breakpoints (); } else { @@ -4011,6 +4468,14 @@ When non-zero, inferior specific debugging is enabled."), show_debug_infrun, &setdebuglist, &showdebuglist); + add_setshow_boolean_cmd ("displaced", class_maintenance, &debug_displaced, _("\ +Set displaced stepping debugging."), _("\ +Show displaced stepping debugging."), _("\ +When non-zero, displaced stepping specific debugging is enabled."), + NULL, + show_debug_displaced, + &setdebuglist, &showdebuglist); + numsigs = (int) TARGET_SIGNAL_LAST; signal_stop = (unsigned char *) xmalloc (sizeof (signal_stop[0]) * numsigs); signal_print = (unsigned char *) @@ -4106,9 +4571,22 @@ function is skipped and the step command stops at a different source line."), show_step_stop_if_no_debug, &setlist, &showlist); + add_setshow_boolean_cmd ("can-use-displaced-stepping", class_maintenance, + &can_use_displaced_stepping, _("\ +Set debugger's willingness to use displaced stepping."), _("\ +Show debugger's willingness to use displaced stepping."), _("\ +If zero, gdb will not use to use displaced stepping to step over\n\ +breakpoints, even if such is supported by the target."), + NULL, + show_can_use_displaced_stepping, + &maintenance_set_cmdlist, + &maintenance_show_cmdlist); + + /* ptid initializations */ null_ptid = ptid_build (0, 0, 0); minus_one_ptid = ptid_build (-1, 0, 0); inferior_ptid = null_ptid; target_last_wait_ptid = minus_one_ptid; + displaced_step_ptid = null_ptid; } diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog index 85f6372..dbba688 100644 --- a/gdb/testsuite/ChangeLog +++ b/gdb/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2008-05-02 Jim Blandy <jimb@codesourcery.com> + + * gdb.asm/asmsrc1.s: Add scratch space. + 2007-05-01 Daniel Jacobowitz <dan@codesourcery.com> * gdb.arch/thumb-prologue.exp: Do not expect a saved PC. diff --git a/gdb/testsuite/gdb.asm/asmsrc1.s b/gdb/testsuite/gdb.asm/asmsrc1.s index c2c01f7..8235205 100644 --- a/gdb/testsuite/gdb.asm/asmsrc1.s +++ b/gdb/testsuite/gdb.asm/asmsrc1.s @@ -16,6 +16,18 @@ gdbasm_exit0 gdbasm_end _start + comment "Displaced stepping requires scratch space at _start" + comment "at least as large as the largest instruction. No" + comment "breakpoints should be set within the scratch space." + gdbasm_several_nops + gdbasm_several_nops + gdbasm_several_nops + gdbasm_several_nops + gdbasm_several_nops + gdbasm_several_nops + gdbasm_several_nops + gdbasm_several_nops + comment "main routine for assembly source debugging test" comment "This particular testcase uses macros in <arch>.inc to achieve" comment "machine independence." |