/* Displaced stepping related things.
- Copyright (C) 2020 Free Software Foundation, Inc.
+ Copyright (C) 2020-2022 Free Software Foundation, Inc.
This file is part of GDB.
#include "defs.h"
#include "displaced-stepping.h"
-#include "cli/cli-cmds.h"
+#include "cli/cli-cmds.h"
#include "command.h"
+#include "gdbarch.h"
+#include "gdbcore.h"
+#include "gdbthread.h"
+#include "inferior.h"
+#include "regcache.h"
+#include "target/target.h"
/* Default destructor for displaced_step_copy_insn_closure. */
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);
+ gdb_printf (file, _("Displace stepping debugging is %s.\n"), value);
+}
+
+displaced_step_prepare_status
+displaced_step_buffers::prepare (thread_info *thread, CORE_ADDR &displaced_pc)
+{
+ gdb_assert (!thread->displaced_step_state.in_progress ());
+
+ /* Sanity check: the thread should not be using a buffer at this point. */
+ for (displaced_step_buffer &buf : m_buffers)
+ gdb_assert (buf.current_thread != thread);
+
+ regcache *regcache = get_thread_regcache (thread);
+ const address_space *aspace = regcache->aspace ();
+ gdbarch *arch = regcache->arch ();
+ ULONGEST len = gdbarch_max_insn_length (arch);
+
+ /* Search for an unused buffer. */
+ displaced_step_buffer *buffer = nullptr;
+ displaced_step_prepare_status fail_status
+ = DISPLACED_STEP_PREPARE_STATUS_CANT;
+
+ for (displaced_step_buffer &candidate : m_buffers)
+ {
+ bool bp_in_range = breakpoint_in_range_p (aspace, candidate.addr, len);
+ bool is_free = candidate.current_thread == nullptr;
+
+ if (!bp_in_range)
+ {
+ if (is_free)
+ {
+ buffer = &candidate;
+ break;
+ }
+ else
+ {
+ /* This buffer would be suitable, but it's used right now. */
+ fail_status = DISPLACED_STEP_PREPARE_STATUS_UNAVAILABLE;
+ }
+ }
+ else
+ {
+ /* There's a breakpoint set in the scratch pad location range
+ (which is usually around the entry point). We'd either
+ install it before resuming, which would overwrite/corrupt the
+ scratch pad, or if it was already inserted, this displaced
+ step would overwrite it. The latter is OK in the sense that
+ we already assume that no thread is going to execute the code
+ in the scratch pad range (after initial startup) anyway, but
+ the former is unacceptable. Simply punt and fallback to
+ stepping over this breakpoint in-line. */
+ displaced_debug_printf ("breakpoint set in displaced stepping "
+ "buffer at %s, can't use.",
+ paddress (arch, candidate.addr));
+ }
+ }
+
+ if (buffer == nullptr)
+ return fail_status;
+
+ displaced_debug_printf ("selected buffer at %s",
+ paddress (arch, buffer->addr));
+
+ /* Save the original PC of the thread. */
+ buffer->original_pc = regcache_read_pc (regcache);
+
+ /* Return displaced step buffer address to caller. */
+ displaced_pc = buffer->addr;
+
+ /* Save the original contents of the displaced stepping buffer. */
+ buffer->saved_copy.resize (len);
+
+ int status = target_read_memory (buffer->addr,
+ buffer->saved_copy.data (), len);
+ if (status != 0)
+ throw_error (MEMORY_ERROR,
+ _("Error accessing memory address %s (%s) for "
+ "displaced-stepping scratch space."),
+ paddress (arch, buffer->addr), safe_strerror (status));
+
+ displaced_debug_printf ("saved %s: %s",
+ paddress (arch, buffer->addr),
+ displaced_step_dump_bytes
+ (buffer->saved_copy.data (), len).c_str ());
+
+ /* Save this in a local variable first, so it's released if code below
+ throws. */
+ displaced_step_copy_insn_closure_up copy_insn_closure
+ = gdbarch_displaced_step_copy_insn (arch, buffer->original_pc,
+ buffer->addr, regcache);
+
+ if (copy_insn_closure == nullptr)
+ {
+ /* The architecture doesn't know how or want to displaced step
+ this instruction or instruction sequence. Fallback to
+ stepping over the breakpoint in-line. */
+ return DISPLACED_STEP_PREPARE_STATUS_CANT;
+ }
+
+ /* Resume execution at the copy. */
+ regcache_write_pc (regcache, buffer->addr);
+
+ /* This marks the buffer as being in use. */
+ buffer->current_thread = thread;
+
+ /* Save this, now that we know everything went fine. */
+ buffer->copy_insn_closure = std::move (copy_insn_closure);
+
+ /* Tell infrun not to try preparing a displaced step again for this inferior if
+ all buffers are taken. */
+ thread->inf->displaced_step_state.unavailable = true;
+ for (const displaced_step_buffer &buf : m_buffers)
+ {
+ if (buf.current_thread == nullptr)
+ {
+ thread->inf->displaced_step_state.unavailable = false;
+ break;
+ }
+ }
+
+ return DISPLACED_STEP_PREPARE_STATUS_OK;
+}
+
+static void
+write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr,
+ const gdb_byte *myaddr, int len)
+{
+ scoped_restore save_inferior_ptid = make_scoped_restore (&inferior_ptid);
+
+ inferior_ptid = ptid;
+ write_memory (memaddr, myaddr, len);
+}
+
+static bool
+displaced_step_instruction_executed_successfully (gdbarch *arch,
+ gdb_signal signal)
+{
+ if (signal != GDB_SIGNAL_TRAP)
+ return false;
+
+ if (target_stopped_by_watchpoint ())
+ {
+ if (gdbarch_have_nonsteppable_watchpoint (arch)
+ || target_have_steppable_watchpoint ())
+ return false;
+ }
+
+ return true;
+}
+
+displaced_step_finish_status
+displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
+ gdb_signal sig)
+{
+ gdb_assert (thread->displaced_step_state.in_progress ());
+
+ /* Find the buffer this thread was using. */
+ displaced_step_buffer *buffer = nullptr;
+
+ for (displaced_step_buffer &candidate : m_buffers)
+ {
+ if (candidate.current_thread == thread)
+ {
+ buffer = &candidate;
+ break;
+ }
+ }
+
+ gdb_assert (buffer != nullptr);
+
+ /* Move this to a local variable so it's released in case something goes
+ wrong. */
+ displaced_step_copy_insn_closure_up copy_insn_closure
+ = std::move (buffer->copy_insn_closure);
+ gdb_assert (copy_insn_closure != nullptr);
+
+ /* Reset BUFFER->CURRENT_THREAD immediately to mark the buffer as available,
+ in case something goes wrong below. */
+ buffer->current_thread = nullptr;
+
+ /* Now that a buffer gets freed, tell infrun it can ask us to prepare a displaced
+ step again for this inferior. Do that here in case something goes wrong
+ below. */
+ thread->inf->displaced_step_state.unavailable = false;
+
+ ULONGEST len = gdbarch_max_insn_length (arch);
+
+ /* Restore memory of the buffer. */
+ write_memory_ptid (thread->ptid, buffer->addr,
+ buffer->saved_copy.data (), len);
+
+ displaced_debug_printf ("restored %s %s",
+ thread->ptid.to_string ().c_str (),
+ paddress (arch, buffer->addr));
+
+ regcache *rc = get_thread_regcache (thread);
+
+ bool instruction_executed_successfully
+ = displaced_step_instruction_executed_successfully (arch, sig);
+
+ if (instruction_executed_successfully)
+ {
+ gdbarch_displaced_step_fixup (arch, copy_insn_closure.get (),
+ buffer->original_pc,
+ buffer->addr, rc);
+ return DISPLACED_STEP_FINISH_STATUS_OK;
+ }
+ else
+ {
+ /* Since the instruction didn't complete, all we can do is relocate the
+ PC. */
+ CORE_ADDR pc = regcache_read_pc (rc);
+ pc = buffer->original_pc + (pc - buffer->addr);
+ regcache_write_pc (rc, pc);
+ return DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED;
+ }
+}
+
+const displaced_step_copy_insn_closure *
+displaced_step_buffers::copy_insn_closure_by_addr (CORE_ADDR addr)
+{
+ for (const displaced_step_buffer &buffer : m_buffers)
+ {
+ if (addr == buffer.addr)
+ return buffer.copy_insn_closure.get ();
+ }
+
+ return nullptr;
+}
+
+void
+displaced_step_buffers::restore_in_ptid (ptid_t ptid)
+{
+ for (const displaced_step_buffer &buffer : m_buffers)
+ {
+ if (buffer.current_thread == nullptr)
+ continue;
+
+ regcache *regcache = get_thread_regcache (buffer.current_thread);
+ gdbarch *arch = regcache->arch ();
+ ULONGEST len = gdbarch_max_insn_length (arch);
+
+ write_memory_ptid (ptid, buffer.addr, buffer.saved_copy.data (), len);
+
+ displaced_debug_printf ("restored in ptid %s %s",
+ ptid.to_string ().c_str (),
+ paddress (arch, buffer.addr));
+ }
}
void _initialize_displaced_stepping ();