305 lines
9.1 KiB
C
305 lines
9.1 KiB
C
/* Displaced stepping related things.
|
|
|
|
Copyright (C) 2020-2022 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 "defs.h"
|
|
#include "displaced-stepping.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. */
|
|
|
|
displaced_step_copy_insn_closure::~displaced_step_copy_insn_closure ()
|
|
= default;
|
|
|
|
bool debug_displaced = false;
|
|
|
|
static void
|
|
show_debug_displaced (struct ui_file *file, int from_tty,
|
|
struct cmd_list_element *c, const char *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 ();
|
|
void
|
|
_initialize_displaced_stepping ()
|
|
{
|
|
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);
|
|
}
|