| 1 | /* Displaced stepping related things. |
| 2 | |
| 3 | Copyright (C) 2020-2021 Free Software Foundation, Inc. |
| 4 | |
| 5 | This file is part of GDB. |
| 6 | |
| 7 | This program is free software; you can redistribute it and/or modify |
| 8 | it under the terms of the GNU General Public License as published by |
| 9 | the Free Software Foundation; either version 3 of the License, or |
| 10 | (at your option) any later version. |
| 11 | |
| 12 | This program is distributed in the hope that it will be useful, |
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | GNU General Public License for more details. |
| 16 | |
| 17 | You should have received a copy of the GNU General Public License |
| 18 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| 19 | |
| 20 | #include "defs.h" |
| 21 | #include "displaced-stepping.h" |
| 22 | |
| 23 | #include "cli/cli-cmds.h" |
| 24 | #include "command.h" |
| 25 | #include "gdbarch.h" |
| 26 | #include "gdbcore.h" |
| 27 | #include "gdbthread.h" |
| 28 | #include "inferior.h" |
| 29 | #include "regcache.h" |
| 30 | #include "target/target.h" |
| 31 | |
| 32 | /* Default destructor for displaced_step_copy_insn_closure. */ |
| 33 | |
| 34 | displaced_step_copy_insn_closure::~displaced_step_copy_insn_closure () |
| 35 | = default; |
| 36 | |
| 37 | bool debug_displaced = false; |
| 38 | |
| 39 | static void |
| 40 | show_debug_displaced (struct ui_file *file, int from_tty, |
| 41 | struct cmd_list_element *c, const char *value) |
| 42 | { |
| 43 | fprintf_filtered (file, _("Displace stepping debugging is %s.\n"), value); |
| 44 | } |
| 45 | |
| 46 | displaced_step_prepare_status |
| 47 | displaced_step_buffers::prepare (thread_info *thread, CORE_ADDR &displaced_pc) |
| 48 | { |
| 49 | gdb_assert (!thread->displaced_step_state.in_progress ()); |
| 50 | |
| 51 | /* Sanity check: the thread should not be using a buffer at this point. */ |
| 52 | for (displaced_step_buffer &buf : m_buffers) |
| 53 | gdb_assert (buf.current_thread != thread); |
| 54 | |
| 55 | regcache *regcache = get_thread_regcache (thread); |
| 56 | const address_space *aspace = regcache->aspace (); |
| 57 | gdbarch *arch = regcache->arch (); |
| 58 | ULONGEST len = gdbarch_max_insn_length (arch); |
| 59 | |
| 60 | /* Search for an unused buffer. */ |
| 61 | displaced_step_buffer *buffer = nullptr; |
| 62 | displaced_step_prepare_status fail_status |
| 63 | = DISPLACED_STEP_PREPARE_STATUS_CANT; |
| 64 | |
| 65 | for (displaced_step_buffer &candidate : m_buffers) |
| 66 | { |
| 67 | bool bp_in_range = breakpoint_in_range_p (aspace, candidate.addr, len); |
| 68 | bool is_free = candidate.current_thread == nullptr; |
| 69 | |
| 70 | if (!bp_in_range) |
| 71 | { |
| 72 | if (is_free) |
| 73 | { |
| 74 | buffer = &candidate; |
| 75 | break; |
| 76 | } |
| 77 | else |
| 78 | { |
| 79 | /* This buffer would be suitable, but it's used right now. */ |
| 80 | fail_status = DISPLACED_STEP_PREPARE_STATUS_UNAVAILABLE; |
| 81 | } |
| 82 | } |
| 83 | else |
| 84 | { |
| 85 | /* There's a breakpoint set in the scratch pad location range |
| 86 | (which is usually around the entry point). We'd either |
| 87 | install it before resuming, which would overwrite/corrupt the |
| 88 | scratch pad, or if it was already inserted, this displaced |
| 89 | step would overwrite it. The latter is OK in the sense that |
| 90 | we already assume that no thread is going to execute the code |
| 91 | in the scratch pad range (after initial startup) anyway, but |
| 92 | the former is unacceptable. Simply punt and fallback to |
| 93 | stepping over this breakpoint in-line. */ |
| 94 | displaced_debug_printf ("breakpoint set in displaced stepping " |
| 95 | "buffer at %s, can't use.", |
| 96 | paddress (arch, candidate.addr)); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | if (buffer == nullptr) |
| 101 | return fail_status; |
| 102 | |
| 103 | displaced_debug_printf ("selected buffer at %s", |
| 104 | paddress (arch, buffer->addr)); |
| 105 | |
| 106 | /* Save the original PC of the thread. */ |
| 107 | buffer->original_pc = regcache_read_pc (regcache); |
| 108 | |
| 109 | /* Return displaced step buffer address to caller. */ |
| 110 | displaced_pc = buffer->addr; |
| 111 | |
| 112 | /* Save the original contents of the displaced stepping buffer. */ |
| 113 | buffer->saved_copy.resize (len); |
| 114 | |
| 115 | int status = target_read_memory (buffer->addr, |
| 116 | buffer->saved_copy.data (), len); |
| 117 | if (status != 0) |
| 118 | throw_error (MEMORY_ERROR, |
| 119 | _("Error accessing memory address %s (%s) for " |
| 120 | "displaced-stepping scratch space."), |
| 121 | paddress (arch, buffer->addr), safe_strerror (status)); |
| 122 | |
| 123 | displaced_debug_printf ("saved %s: %s", |
| 124 | paddress (arch, buffer->addr), |
| 125 | displaced_step_dump_bytes |
| 126 | (buffer->saved_copy.data (), len).c_str ()); |
| 127 | |
| 128 | /* Save this in a local variable first, so it's released if code below |
| 129 | throws. */ |
| 130 | displaced_step_copy_insn_closure_up copy_insn_closure |
| 131 | = gdbarch_displaced_step_copy_insn (arch, buffer->original_pc, |
| 132 | buffer->addr, regcache); |
| 133 | |
| 134 | if (copy_insn_closure == nullptr) |
| 135 | { |
| 136 | /* The architecture doesn't know how or want to displaced step |
| 137 | this instruction or instruction sequence. Fallback to |
| 138 | stepping over the breakpoint in-line. */ |
| 139 | return DISPLACED_STEP_PREPARE_STATUS_CANT; |
| 140 | } |
| 141 | |
| 142 | /* Resume execution at the copy. */ |
| 143 | regcache_write_pc (regcache, buffer->addr); |
| 144 | |
| 145 | /* This marks the buffer as being in use. */ |
| 146 | buffer->current_thread = thread; |
| 147 | |
| 148 | /* Save this, now that we know everything went fine. */ |
| 149 | buffer->copy_insn_closure = std::move (copy_insn_closure); |
| 150 | |
| 151 | /* Tell infrun not to try preparing a displaced step again for this inferior if |
| 152 | all buffers are taken. */ |
| 153 | thread->inf->displaced_step_state.unavailable = true; |
| 154 | for (const displaced_step_buffer &buf : m_buffers) |
| 155 | { |
| 156 | if (buf.current_thread == nullptr) |
| 157 | { |
| 158 | thread->inf->displaced_step_state.unavailable = false; |
| 159 | break; |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | return DISPLACED_STEP_PREPARE_STATUS_OK; |
| 164 | } |
| 165 | |
| 166 | static void |
| 167 | write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr, |
| 168 | const gdb_byte *myaddr, int len) |
| 169 | { |
| 170 | scoped_restore save_inferior_ptid = make_scoped_restore (&inferior_ptid); |
| 171 | |
| 172 | inferior_ptid = ptid; |
| 173 | write_memory (memaddr, myaddr, len); |
| 174 | } |
| 175 | |
| 176 | static bool |
| 177 | displaced_step_instruction_executed_successfully (gdbarch *arch, |
| 178 | gdb_signal signal) |
| 179 | { |
| 180 | if (signal != GDB_SIGNAL_TRAP) |
| 181 | return false; |
| 182 | |
| 183 | if (target_stopped_by_watchpoint ()) |
| 184 | { |
| 185 | if (gdbarch_have_nonsteppable_watchpoint (arch) |
| 186 | || target_have_steppable_watchpoint ()) |
| 187 | return false; |
| 188 | } |
| 189 | |
| 190 | return true; |
| 191 | } |
| 192 | |
| 193 | displaced_step_finish_status |
| 194 | displaced_step_buffers::finish (gdbarch *arch, thread_info *thread, |
| 195 | gdb_signal sig) |
| 196 | { |
| 197 | gdb_assert (thread->displaced_step_state.in_progress ()); |
| 198 | |
| 199 | /* Find the buffer this thread was using. */ |
| 200 | displaced_step_buffer *buffer = nullptr; |
| 201 | |
| 202 | for (displaced_step_buffer &candidate : m_buffers) |
| 203 | { |
| 204 | if (candidate.current_thread == thread) |
| 205 | { |
| 206 | buffer = &candidate; |
| 207 | break; |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | gdb_assert (buffer != nullptr); |
| 212 | |
| 213 | /* Move this to a local variable so it's released in case something goes |
| 214 | wrong. */ |
| 215 | displaced_step_copy_insn_closure_up copy_insn_closure |
| 216 | = std::move (buffer->copy_insn_closure); |
| 217 | gdb_assert (copy_insn_closure != nullptr); |
| 218 | |
| 219 | /* Reset BUFFER->CURRENT_THREAD immediately to mark the buffer as available, |
| 220 | in case something goes wrong below. */ |
| 221 | buffer->current_thread = nullptr; |
| 222 | |
| 223 | /* Now that a buffer gets freed, tell infrun it can ask us to prepare a displaced |
| 224 | step again for this inferior. Do that here in case something goes wrong |
| 225 | below. */ |
| 226 | thread->inf->displaced_step_state.unavailable = false; |
| 227 | |
| 228 | ULONGEST len = gdbarch_max_insn_length (arch); |
| 229 | |
| 230 | /* Restore memory of the buffer. */ |
| 231 | write_memory_ptid (thread->ptid, buffer->addr, |
| 232 | buffer->saved_copy.data (), len); |
| 233 | |
| 234 | displaced_debug_printf ("restored %s %s", |
| 235 | target_pid_to_str (thread->ptid).c_str (), |
| 236 | paddress (arch, buffer->addr)); |
| 237 | |
| 238 | regcache *rc = get_thread_regcache (thread); |
| 239 | |
| 240 | bool instruction_executed_successfully |
| 241 | = displaced_step_instruction_executed_successfully (arch, sig); |
| 242 | |
| 243 | if (instruction_executed_successfully) |
| 244 | { |
| 245 | gdbarch_displaced_step_fixup (arch, copy_insn_closure.get (), |
| 246 | buffer->original_pc, |
| 247 | buffer->addr, rc); |
| 248 | return DISPLACED_STEP_FINISH_STATUS_OK; |
| 249 | } |
| 250 | else |
| 251 | { |
| 252 | /* Since the instruction didn't complete, all we can do is relocate the |
| 253 | PC. */ |
| 254 | CORE_ADDR pc = regcache_read_pc (rc); |
| 255 | pc = buffer->original_pc + (pc - buffer->addr); |
| 256 | regcache_write_pc (rc, pc); |
| 257 | return DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | const displaced_step_copy_insn_closure * |
| 262 | displaced_step_buffers::copy_insn_closure_by_addr (CORE_ADDR addr) |
| 263 | { |
| 264 | for (const displaced_step_buffer &buffer : m_buffers) |
| 265 | { |
| 266 | if (addr == buffer.addr) |
| 267 | return buffer.copy_insn_closure.get (); |
| 268 | } |
| 269 | |
| 270 | return nullptr; |
| 271 | } |
| 272 | |
| 273 | void |
| 274 | displaced_step_buffers::restore_in_ptid (ptid_t ptid) |
| 275 | { |
| 276 | for (const displaced_step_buffer &buffer : m_buffers) |
| 277 | { |
| 278 | if (buffer.current_thread == nullptr) |
| 279 | continue; |
| 280 | |
| 281 | regcache *regcache = get_thread_regcache (buffer.current_thread); |
| 282 | gdbarch *arch = regcache->arch (); |
| 283 | ULONGEST len = gdbarch_max_insn_length (arch); |
| 284 | |
| 285 | write_memory_ptid (ptid, buffer.addr, buffer.saved_copy.data (), len); |
| 286 | |
| 287 | displaced_debug_printf ("restored in ptid %s %s", |
| 288 | target_pid_to_str (ptid).c_str (), |
| 289 | paddress (arch, buffer.addr)); |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | void _initialize_displaced_stepping (); |
| 294 | void |
| 295 | _initialize_displaced_stepping () |
| 296 | { |
| 297 | add_setshow_boolean_cmd ("displaced", class_maintenance, |
| 298 | &debug_displaced, _("\ |
| 299 | Set displaced stepping debugging."), _("\ |
| 300 | Show displaced stepping debugging."), _("\ |
| 301 | When non-zero, displaced stepping specific debugging is enabled."), |
| 302 | NULL, |
| 303 | show_debug_displaced, |
| 304 | &setdebuglist, &showdebuglist); |
| 305 | } |