]> Git Repo - binutils.git/blob - gdb/displaced-stepping.c
Automatic date update in version.in
[binutils.git] / gdb / displaced-stepping.c
1 /* Displaced stepping related things.
2
3    Copyright (C) 2020-2022 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   gdb_printf (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   /* This marks the buffer as being in use.  */
143   buffer->current_thread = thread;
144
145   /* Save this, now that we know everything went fine.  */
146   buffer->copy_insn_closure = std::move (copy_insn_closure);
147
148   /* Reset the displaced step buffer state if we failed to write PC.
149      Otherwise we will prevent this buffer from being used, as it will
150      always have a thread in buffer->current_thread.  */
151   auto reset_buffer = make_scope_exit
152     ([buffer] ()
153       {
154         buffer->current_thread = nullptr;
155         buffer->copy_insn_closure.reset ();
156       });
157
158   /* Adjust the PC so it points to the displaced step buffer address that will
159      be used.  This needs to be done after we save the copy_insn_closure, as
160      some architectures (Arm, for one) need that information so they can adjust
161      other data as needed.  In particular, Arm needs to know if the instruction
162      being executed in the displaced step buffer is thumb or not.  Without that
163      information, things will be very wrong in a random way.  */
164   regcache_write_pc (regcache, buffer->addr);
165
166   /* PC update successful.  Discard the displaced step state rollback.  */
167   reset_buffer.release ();
168
169   /* Tell infrun not to try preparing a displaced step again for this inferior if
170      all buffers are taken.  */
171   thread->inf->displaced_step_state.unavailable = true;
172   for (const displaced_step_buffer &buf : m_buffers)
173     {
174       if (buf.current_thread == nullptr)
175         {
176           thread->inf->displaced_step_state.unavailable = false;
177           break;
178         }
179     }
180
181   return DISPLACED_STEP_PREPARE_STATUS_OK;
182 }
183
184 static void
185 write_memory_ptid (ptid_t ptid, CORE_ADDR memaddr,
186                    const gdb_byte *myaddr, int len)
187 {
188   scoped_restore save_inferior_ptid = make_scoped_restore (&inferior_ptid);
189
190   inferior_ptid = ptid;
191   write_memory (memaddr, myaddr, len);
192 }
193
194 static bool
195 displaced_step_instruction_executed_successfully (gdbarch *arch,
196                                                   gdb_signal signal)
197 {
198   if (signal != GDB_SIGNAL_TRAP)
199     return false;
200
201   if (target_stopped_by_watchpoint ())
202     {
203       if (gdbarch_have_nonsteppable_watchpoint (arch)
204           || target_have_steppable_watchpoint ())
205         return false;
206     }
207
208   return true;
209 }
210
211 displaced_step_finish_status
212 displaced_step_buffers::finish (gdbarch *arch, thread_info *thread,
213                                 gdb_signal sig)
214 {
215   gdb_assert (thread->displaced_step_state.in_progress ());
216
217   /* Find the buffer this thread was using.  */
218   displaced_step_buffer *buffer = nullptr;
219
220   for (displaced_step_buffer &candidate : m_buffers)
221     {
222       if (candidate.current_thread == thread)
223         {
224           buffer = &candidate;
225           break;
226         }
227     }
228
229   gdb_assert (buffer != nullptr);
230
231   /* Move this to a local variable so it's released in case something goes
232      wrong.  */
233   displaced_step_copy_insn_closure_up copy_insn_closure
234     = std::move (buffer->copy_insn_closure);
235   gdb_assert (copy_insn_closure != nullptr);
236
237   /* Reset BUFFER->CURRENT_THREAD immediately to mark the buffer as available,
238      in case something goes wrong below.  */
239   buffer->current_thread = nullptr;
240
241   /* Now that a buffer gets freed, tell infrun it can ask us to prepare a displaced
242      step again for this inferior.  Do that here in case something goes wrong
243      below.  */
244   thread->inf->displaced_step_state.unavailable = false;
245
246   ULONGEST len = gdbarch_max_insn_length (arch);
247
248   /* Restore memory of the buffer.  */
249   write_memory_ptid (thread->ptid, buffer->addr,
250                      buffer->saved_copy.data (), len);
251
252   displaced_debug_printf ("restored %s %s",
253                           thread->ptid.to_string ().c_str (),
254                           paddress (arch, buffer->addr));
255
256   regcache *rc = get_thread_regcache (thread);
257
258   bool instruction_executed_successfully
259     = displaced_step_instruction_executed_successfully (arch, sig);
260
261   if (instruction_executed_successfully)
262     {
263       gdbarch_displaced_step_fixup (arch, copy_insn_closure.get (),
264                                     buffer->original_pc,
265                                     buffer->addr, rc);
266       return DISPLACED_STEP_FINISH_STATUS_OK;
267     }
268   else
269     {
270       /* Since the instruction didn't complete, all we can do is relocate the
271          PC.  */
272       CORE_ADDR pc = regcache_read_pc (rc);
273       pc = buffer->original_pc + (pc - buffer->addr);
274       regcache_write_pc (rc, pc);
275       return DISPLACED_STEP_FINISH_STATUS_NOT_EXECUTED;
276     }
277 }
278
279 const displaced_step_copy_insn_closure *
280 displaced_step_buffers::copy_insn_closure_by_addr (CORE_ADDR addr)
281 {
282   for (const displaced_step_buffer &buffer : m_buffers)
283     {
284       if (addr == buffer.addr)
285       {
286         /* The closure information should always be available. */
287         gdb_assert (buffer.copy_insn_closure.get () != nullptr);
288         return buffer.copy_insn_closure.get ();
289       }
290     }
291
292   return nullptr;
293 }
294
295 void
296 displaced_step_buffers::restore_in_ptid (ptid_t ptid)
297 {
298   for (const displaced_step_buffer &buffer : m_buffers)
299     {
300       if (buffer.current_thread == nullptr)
301         continue;
302
303       regcache *regcache = get_thread_regcache (buffer.current_thread);
304       gdbarch *arch = regcache->arch ();
305       ULONGEST len = gdbarch_max_insn_length (arch);
306
307       write_memory_ptid (ptid, buffer.addr, buffer.saved_copy.data (), len);
308
309       displaced_debug_printf ("restored in ptid %s %s",
310                               ptid.to_string ().c_str (),
311                               paddress (arch, buffer.addr));
312     }
313 }
314
315 void _initialize_displaced_stepping ();
316 void
317 _initialize_displaced_stepping ()
318 {
319   add_setshow_boolean_cmd ("displaced", class_maintenance,
320                            &debug_displaced, _("\
321 Set displaced stepping debugging."), _("\
322 Show displaced stepping debugging."), _("\
323 When non-zero, displaced stepping specific debugging is enabled."),
324                             NULL,
325                             show_debug_displaced,
326                             &setdebuglist, &showdebuglist);
327 }
This page took 0.042331 seconds and 4 git commands to generate.