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
show_debug_displaced(struct ui_file * file,int from_tty,struct cmd_list_element * c,const char * value)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
prepare(thread_info * thread,CORE_ADDR & displaced_pc)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
write_memory_ptid(ptid_t ptid,CORE_ADDR memaddr,const gdb_byte * myaddr,int len)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
displaced_step_instruction_executed_successfully(gdbarch * arch,gdb_signal signal)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
finish(gdbarch * arch,thread_info * thread,gdb_signal sig)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 *
copy_insn_closure_by_addr(CORE_ADDR addr)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
restore_in_ptid(ptid_t ptid)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
_initialize_displaced_stepping()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 }
306