]>
Commit | Line | Data |
---|---|---|
c7acb87b SM |
1 | /* Displaced stepping related things. |
2 | ||
213516ef | 3 | Copyright (C) 2020-2023 Free Software Foundation, Inc. |
c7acb87b SM |
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" | |
c7acb87b | 22 | |
187b041e | 23 | #include "cli/cli-cmds.h" |
c7acb87b | 24 | #include "command.h" |
187b041e SM |
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" | |
c7acb87b SM |
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 | { | |
6cb06a8c | 43 | gdb_printf (file, _("Displace stepping debugging is %s.\n"), value); |
c7acb87b SM |
44 | } |
45 | ||
187b041e | 46 | displaced_step_prepare_status |
480af54c | 47 | displaced_step_buffers::prepare (thread_info *thread, CORE_ADDR &displaced_pc) |
187b041e SM |
48 | { |
49 | gdb_assert (!thread->displaced_step_state.in_progress ()); | |
50 | ||
480af54c SM |
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); | |
187b041e SM |
54 | |
55 | regcache *regcache = get_thread_regcache (thread); | |
56 | const address_space *aspace = regcache->aspace (); | |
57 | gdbarch *arch = regcache->arch (); | |
deb65a3c | 58 | ULONGEST len = gdbarch_displaced_step_buffer_length (arch); |
187b041e | 59 | |
480af54c SM |
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; | |
187b041e | 64 | |
480af54c SM |
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 | } | |
187b041e SM |
98 | } |
99 | ||
480af54c SM |
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; | |
187b041e SM |
111 | |
112 | /* Save the original contents of the displaced stepping buffer. */ | |
480af54c | 113 | buffer->saved_copy.resize (len); |
187b041e | 114 | |
480af54c SM |
115 | int status = target_read_memory (buffer->addr, |
116 | buffer->saved_copy.data (), len); | |
187b041e SM |
117 | if (status != 0) |
118 | throw_error (MEMORY_ERROR, | |
119 | _("Error accessing memory address %s (%s) for " | |
120 | "displaced-stepping scratch space."), | |
480af54c | 121 | paddress (arch, buffer->addr), safe_strerror (status)); |
187b041e SM |
122 | |
123 | displaced_debug_printf ("saved %s: %s", | |
480af54c | 124 | paddress (arch, buffer->addr), |
a6e5abae | 125 | bytes_to_string (buffer->saved_copy).c_str ()); |
187b041e SM |
126 | |
127 | /* Save this in a local variable first, so it's released if code below | |
128 | throws. */ | |
129 | displaced_step_copy_insn_closure_up copy_insn_closure | |
480af54c SM |
130 | = gdbarch_displaced_step_copy_insn (arch, buffer->original_pc, |
131 | buffer->addr, regcache); | |
187b041e SM |
132 | |
133 | if (copy_insn_closure == nullptr) | |
134 | { | |
135 | /* The architecture doesn't know how or want to displaced step | |
480af54c SM |
136 | this instruction or instruction sequence. Fallback to |
137 | stepping over the breakpoint in-line. */ | |
187b041e SM |
138 | return DISPLACED_STEP_PREPARE_STATUS_CANT; |
139 | } | |
140 | ||
187b041e | 141 | /* This marks the buffer as being in use. */ |
480af54c | 142 | buffer->current_thread = thread; |
187b041e SM |
143 | |
144 | /* Save this, now that we know everything went fine. */ | |
480af54c | 145 | buffer->copy_insn_closure = std::move (copy_insn_closure); |
187b041e | 146 | |
1e5ccb9c LM |
147 | /* Reset the displaced step buffer state if we failed to write PC. |
148 | Otherwise we will prevent this buffer from being used, as it will | |
149 | always have a thread in buffer->current_thread. */ | |
150 | auto reset_buffer = make_scope_exit | |
151 | ([buffer] () | |
152 | { | |
153 | buffer->current_thread = nullptr; | |
154 | buffer->copy_insn_closure.reset (); | |
155 | }); | |
156 | ||
157 | /* Adjust the PC so it points to the displaced step buffer address that will | |
158 | be used. This needs to be done after we save the copy_insn_closure, as | |
159 | some architectures (Arm, for one) need that information so they can adjust | |
160 | other data as needed. In particular, Arm needs to know if the instruction | |
161 | being executed in the displaced step buffer is thumb or not. Without that | |
162 | information, things will be very wrong in a random way. */ | |
163 | regcache_write_pc (regcache, buffer->addr); | |
164 | ||
165 | /* PC update successful. Discard the displaced step state rollback. */ | |
166 | reset_buffer.release (); | |
167 | ||
480af54c SM |
168 | /* Tell infrun not to try preparing a displaced step again for this inferior if |
169 | all buffers are taken. */ | |
187b041e | 170 | thread->inf->displaced_step_state.unavailable = true; |
480af54c SM |
171 | for (const displaced_step_buffer &buf : m_buffers) |
172 | { | |
173 | if (buf.current_thread == nullptr) | |
174 | { | |
175 | thread->inf->displaced_step_state.unavailable = false; | |
176 | break; | |
177 | } | |
178 | } | |
187b041e SM |
179 | |
180 | return DISPLACED_STEP_PREPARE_STATUS_OK; | |
181 | } | |
182 | ||
183 | static void | |
184 | write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr, | |
185 | const gdb_byte *myaddr, int len) | |
186 | { | |
187 | scoped_restore save_inferior_ptid = make_scoped_restore (&inferior_ptid); | |
188 | ||
189 | inferior_ptid = ptid; | |
190 | write_memory (memaddr, myaddr, len); | |
191 | } | |
192 | ||
193 | static bool | |
58c01087 PA |
194 | displaced_step_instruction_executed_successfully |
195 | (gdbarch *arch, const target_waitstatus &status) | |
187b041e | 196 | { |
58c01087 PA |
197 | if (status.kind () == TARGET_WAITKIND_STOPPED |
198 | && status.sig () != GDB_SIGNAL_TRAP) | |
187b041e SM |
199 | return false; |
200 | ||
58c01087 PA |
201 | /* All other (thread event) waitkinds can only happen if the |
202 | instruction fully executed. For example, a fork, or a syscall | |
203 | entry can only happen if the syscall instruction actually | |
204 | executed. */ | |
205 | ||
187b041e SM |
206 | if (target_stopped_by_watchpoint ()) |
207 | { | |
208 | if (gdbarch_have_nonsteppable_watchpoint (arch) | |
209 | || target_have_steppable_watchpoint ()) | |
210 | return false; | |
211 | } | |
212 | ||
213 | return true; | |
214 | } | |
215 | ||
216 | displaced_step_finish_status | |
480af54c | 217 | displaced_step_buffers::finish (gdbarch *arch, thread_info *thread, |
58c01087 | 218 | const target_waitstatus &status) |
187b041e SM |
219 | { |
220 | gdb_assert (thread->displaced_step_state.in_progress ()); | |
480af54c SM |
221 | |
222 | /* Find the buffer this thread was using. */ | |
223 | displaced_step_buffer *buffer = nullptr; | |
224 | ||
225 | for (displaced_step_buffer &candidate : m_buffers) | |
226 | { | |
227 | if (candidate.current_thread == thread) | |
228 | { | |
229 | buffer = &candidate; | |
230 | break; | |
231 | } | |
232 | } | |
233 | ||
234 | gdb_assert (buffer != nullptr); | |
187b041e SM |
235 | |
236 | /* Move this to a local variable so it's released in case something goes | |
237 | wrong. */ | |
238 | displaced_step_copy_insn_closure_up copy_insn_closure | |
480af54c | 239 | = std::move (buffer->copy_insn_closure); |
187b041e SM |
240 | gdb_assert (copy_insn_closure != nullptr); |
241 | ||
480af54c SM |
242 | /* Reset BUFFER->CURRENT_THREAD immediately to mark the buffer as available, |
243 | in case something goes wrong below. */ | |
244 | buffer->current_thread = nullptr; | |
187b041e SM |
245 | |
246 | /* Now that a buffer gets freed, tell infrun it can ask us to prepare a displaced | |
247 | step again for this inferior. Do that here in case something goes wrong | |
248 | below. */ | |
249 | thread->inf->displaced_step_state.unavailable = false; | |
250 | ||
deb65a3c | 251 | ULONGEST len = gdbarch_displaced_step_buffer_length (arch); |
187b041e | 252 | |
480af54c SM |
253 | /* Restore memory of the buffer. */ |
254 | write_memory_ptid (thread->ptid, buffer->addr, | |
255 | buffer->saved_copy.data (), len); | |
187b041e SM |
256 | |
257 | displaced_debug_printf ("restored %s %s", | |
e53c95d4 | 258 | thread->ptid.to_string ().c_str (), |
480af54c | 259 | paddress (arch, buffer->addr)); |
187b041e | 260 | |
21d48304 PA |
261 | /* If the thread exited while stepping, we are done. The code above |
262 | made the buffer available again, and we restored the bytes in the | |
263 | buffer. We don't want to run the fixup: since the thread is now | |
264 | dead there's nothing to adjust. */ | |
265 | if (status.kind () == TARGET_WAITKIND_THREAD_EXITED) | |
266 | return DISPLACED_STEP_FINISH_STATUS_OK; | |
267 | ||
187b041e SM |
268 | regcache *rc = get_thread_regcache (thread); |
269 | ||
270 | bool instruction_executed_successfully | |
58c01087 | 271 | = displaced_step_instruction_executed_successfully (arch, status); |
187b041e | 272 | |
cf141dd8 AB |
273 | gdbarch_displaced_step_fixup (arch, copy_insn_closure.get (), |
274 | buffer->original_pc, buffer->addr, | |
275 | rc, instruction_executed_successfully); | |
276 | ||
277 | return (instruction_executed_successfully | |
278 | ? DISPLACED_STEP_FINISH_STATUS_OK | |
279 | : DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED); | |
187b041e SM |
280 | } |
281 | ||
282 | const displaced_step_copy_insn_closure * | |
480af54c | 283 | displaced_step_buffers::copy_insn_closure_by_addr (CORE_ADDR addr) |
187b041e | 284 | { |
480af54c SM |
285 | for (const displaced_step_buffer &buffer : m_buffers) |
286 | { | |
5d4a870e LM |
287 | /* Make sure we have active buffers to compare to. */ |
288 | if (buffer.current_thread != nullptr && addr == buffer.addr) | |
1e5ccb9c LM |
289 | { |
290 | /* The closure information should always be available. */ | |
291 | gdb_assert (buffer.copy_insn_closure.get () != nullptr); | |
480af54c | 292 | return buffer.copy_insn_closure.get (); |
1e5ccb9c | 293 | } |
480af54c SM |
294 | } |
295 | ||
296 | return nullptr; | |
187b041e SM |
297 | } |
298 | ||
299 | void | |
480af54c | 300 | displaced_step_buffers::restore_in_ptid (ptid_t ptid) |
187b041e | 301 | { |
480af54c | 302 | for (const displaced_step_buffer &buffer : m_buffers) |
187b041e | 303 | { |
480af54c SM |
304 | if (buffer.current_thread == nullptr) |
305 | continue; | |
306 | ||
307 | regcache *regcache = get_thread_regcache (buffer.current_thread); | |
187b041e | 308 | gdbarch *arch = regcache->arch (); |
deb65a3c | 309 | ULONGEST len = gdbarch_displaced_step_buffer_length (arch); |
187b041e | 310 | |
480af54c | 311 | write_memory_ptid (ptid, buffer.addr, buffer.saved_copy.data (), len); |
187b041e SM |
312 | |
313 | displaced_debug_printf ("restored in ptid %s %s", | |
e53c95d4 | 314 | ptid.to_string ().c_str (), |
480af54c | 315 | paddress (arch, buffer.addr)); |
187b041e SM |
316 | } |
317 | } | |
318 | ||
c7acb87b SM |
319 | void _initialize_displaced_stepping (); |
320 | void | |
321 | _initialize_displaced_stepping () | |
322 | { | |
323 | add_setshow_boolean_cmd ("displaced", class_maintenance, | |
324 | &debug_displaced, _("\ | |
325 | Set displaced stepping debugging."), _("\ | |
326 | Show displaced stepping debugging."), _("\ | |
327 | When non-zero, displaced stepping specific debugging is enabled."), | |
328 | NULL, | |
329 | show_debug_displaced, | |
330 | &setdebuglist, &showdebuglist); | |
331 | } |