xref: /openbsd-src/sys/ddb/db_run.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: db_run.c,v 1.26 2016/01/25 14:30:30 mpi Exp $	*/
2 /*	$NetBSD: db_run.c,v 1.8 1996/02/05 01:57:12 christos Exp $	*/
3 
4 /*
5  * Mach Operating System
6  * Copyright (c) 1993,1992,1991,1990 Carnegie Mellon University
7  * All Rights Reserved.
8  *
9  * Permission to use, copy, modify and distribute this software and its
10  * documentation is hereby granted, provided that both the copyright
11  * notice and this permission notice appear in all copies of the
12  * software, derivative works or modified versions, and any portions
13  * thereof, and that both notices appear in supporting documentation.
14  *
15  * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
16  * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
17  * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
18  *
19  * Carnegie Mellon requests users of this software to return to
20  *
21  *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
22  *  School of Computer Science
23  *  Carnegie Mellon University
24  *  Pittsburgh PA 15213-3890
25  *
26  * any improvements or extensions that they make and grant Carnegie Mellon
27  * the rights to redistribute these changes.
28  *
29  * 	Author: David B. Golub, Carnegie Mellon University
30  *	Date:	7/90
31  */
32 
33 /*
34  * Commands to run process.
35  */
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 
39 #include <machine/db_machdep.h>
40 
41 #include <ddb/db_run.h>
42 #include <ddb/db_break.h>
43 #include <ddb/db_access.h>
44 
45 #ifdef SOFTWARE_SSTEP
46 db_breakpoint_t	db_not_taken_bkpt = 0;
47 db_breakpoint_t	db_taken_bkpt = 0;
48 #endif
49 
50 int		db_inst_count;
51 
52 #ifndef KGDB
53 
54 #include <ddb/db_watch.h>
55 #include <ddb/db_output.h>
56 #include <ddb/db_sym.h>
57 #include <ddb/db_extern.h>
58 
59 int	db_run_mode;
60 #define	STEP_NONE	0
61 #define	STEP_ONCE	1
62 #define	STEP_RETURN	2
63 #define	STEP_CALLT	3
64 #define	STEP_CONTINUE	4
65 #define STEP_INVISIBLE	5
66 #define	STEP_COUNT	6
67 
68 boolean_t	db_sstep_print;
69 int		db_loop_count;
70 int		db_call_depth;
71 
72 boolean_t
73 db_stop_at_pc(db_regs_t *regs, boolean_t *is_breakpoint)
74 {
75 	db_addr_t	pc, old_pc;
76 	db_breakpoint_t	bkpt;
77 
78 	db_clear_breakpoints();
79 	db_clear_watchpoints();
80 	old_pc = pc = PC_REGS(regs);
81 
82 #ifdef	FIXUP_PC_AFTER_BREAK
83 	if (*is_breakpoint) {
84 		/*
85 		 * Breakpoint trap.  Fix up the PC if the
86 		 * machine requires it.
87 		 */
88 		FIXUP_PC_AFTER_BREAK(regs);
89 		pc = PC_REGS(regs);
90 	}
91 #endif
92 
93 	/*
94 	 * Now check for a breakpoint at this address.
95 	 */
96 	bkpt = db_find_breakpoint(pc);
97 	if (bkpt) {
98 		if (--bkpt->count == 0) {
99 			db_clear_single_step(regs);
100 			bkpt->count = bkpt->init_count;
101 			*is_breakpoint = TRUE;
102 			return (TRUE);	/* stop here */
103 		}
104 	} else if (*is_breakpoint
105 #ifdef SOFTWARE_SSTEP
106 	    && !((db_taken_bkpt && db_taken_bkpt->address == pc) ||
107 	    (db_not_taken_bkpt && db_not_taken_bkpt->address == pc))
108 #endif
109 	    ) {
110 #ifdef PC_ADVANCE
111 		PC_ADVANCE(regs);
112 #else
113 # ifdef SET_PC_REGS
114 		SET_PC_REGS(regs, old_pc);
115 # else
116 		PC_REGS(regs) = old_pc;
117 # endif
118 #endif
119 	}
120 	db_clear_single_step(regs);
121 
122 	*is_breakpoint = FALSE;
123 
124 	if (db_run_mode == STEP_INVISIBLE) {
125 		db_run_mode = STEP_CONTINUE;
126 		return (FALSE);	/* continue */
127 	}
128 	if (db_run_mode == STEP_COUNT) {
129 		return (FALSE); /* continue */
130 	}
131 	if (db_run_mode == STEP_ONCE) {
132 		if (--db_loop_count > 0) {
133 			if (db_sstep_print) {
134 				db_printf("\t\t");
135 				db_print_loc_and_inst(pc);
136 				db_printf("\n");
137 			}
138 			return (FALSE);	/* continue */
139 		}
140 	}
141 	if (db_run_mode == STEP_RETURN) {
142 	    db_expr_t ins = db_get_value(pc, sizeof(int), FALSE);
143 
144 	    /* continue until matching return */
145 
146 	    if (!inst_trap_return(ins) &&
147 		(!inst_return(ins) || --db_call_depth != 0)) {
148 		if (db_sstep_print) {
149 		    if (inst_call(ins) || inst_return(ins)) {
150 			int i;
151 
152 			db_printf("[after %6d]     ", db_inst_count);
153 			for (i = db_call_depth; --i > 0; )
154 			    db_printf("  ");
155 			db_print_loc_and_inst(pc);
156 			db_printf("\n");
157 		    }
158 		}
159 		if (inst_call(ins))
160 		    db_call_depth++;
161 		return (FALSE);	/* continue */
162 	    }
163 	}
164 	if (db_run_mode == STEP_CALLT) {
165 	    db_expr_t ins = db_get_value(pc, sizeof(int), FALSE);
166 
167 	    /* continue until call or return */
168 
169 	    if (!inst_call(ins) && !inst_return(ins) &&
170 		!inst_trap_return(ins)) {
171 		return (FALSE);	/* continue */
172 	    }
173 	}
174 	db_run_mode = STEP_NONE;
175 	return (TRUE);
176 }
177 
178 void
179 db_restart_at_pc(db_regs_t *regs, boolean_t watchpt)
180 {
181 	db_addr_t pc = PC_REGS(regs);
182 
183 	if ((db_run_mode == STEP_COUNT) || (db_run_mode == STEP_RETURN) ||
184 	    (db_run_mode == STEP_CALLT)) {
185 		db_expr_t	ins;
186 
187 		/*
188 		 * We are about to execute this instruction,
189 		 * so count it now.
190 		 */
191 		ins = db_get_value(pc, sizeof(int), FALSE);
192 		db_inst_count++;
193 #ifdef	SOFTWARE_SSTEP
194 		/* XXX works on mips, but... */
195 		if (inst_branch(ins) || inst_call(ins)) {
196 			ins = db_get_value(next_instr_address(pc, 1),
197 			    sizeof(int), FALSE);
198 			db_inst_count++;
199 		}
200 #endif	/* SOFTWARE_SSTEP */
201 	}
202 
203 	if (db_run_mode == STEP_CONTINUE) {
204 		if (watchpt || db_find_breakpoint(pc)) {
205 			/*
206 			 * Step over breakpoint/watchpoint.
207 			 */
208 			db_run_mode = STEP_INVISIBLE;
209 			db_set_single_step(regs);
210 		} else {
211 			db_set_breakpoints();
212 			db_set_watchpoints();
213 		}
214 	} else {
215 		db_set_single_step(regs);
216 	}
217 }
218 
219 void
220 db_single_step(db_regs_t *regs)
221 {
222 	if (db_run_mode == STEP_CONTINUE) {
223 	    db_run_mode = STEP_INVISIBLE;
224 	    db_set_single_step(regs);
225 	}
226 }
227 
228 /* single-step */
229 /*ARGSUSED*/
230 void
231 db_single_step_cmd(db_expr_t addr, int have_addr, db_expr_t count, char *modif)
232 {
233 	boolean_t	print = FALSE;
234 
235 	if (count == -1)
236 	    count = 1;
237 
238 	if (modif[0] == 'p')
239 	    print = TRUE;
240 
241 	db_run_mode = STEP_ONCE;
242 	db_loop_count = count;
243 	db_sstep_print = print;
244 	db_inst_count = 0;
245 
246 	db_cmd_loop_done = 1;
247 }
248 
249 /* trace and print until call/return */
250 /*ARGSUSED*/
251 void
252 db_trace_until_call_cmd(db_expr_t addr, int have_addr, db_expr_t count,
253     char *modif)
254 {
255 	boolean_t	print = FALSE;
256 
257 	if (modif[0] == 'p')
258 	    print = TRUE;
259 
260 	db_run_mode = STEP_CALLT;
261 	db_sstep_print = print;
262 	db_inst_count = 0;
263 
264 	db_cmd_loop_done = 1;
265 }
266 
267 /*ARGSUSED*/
268 void
269 db_trace_until_matching_cmd(db_expr_t addr, int have_addr, db_expr_t count,
270     char *modif)
271 {
272 	boolean_t	print = FALSE;
273 
274 	if (modif[0] == 'p')
275 	    print = TRUE;
276 
277 	db_run_mode = STEP_RETURN;
278 	db_call_depth = 1;
279 	db_sstep_print = print;
280 	db_inst_count = 0;
281 
282 	db_cmd_loop_done = 1;
283 }
284 
285 /* continue */
286 /*ARGSUSED*/
287 void
288 db_continue_cmd(db_expr_t addr, int have_addr, db_expr_t count, char *modif)
289 {
290 	if (modif[0] == 'c')
291 	    db_run_mode = STEP_COUNT;
292 	else
293 	    db_run_mode = STEP_CONTINUE;
294 	db_inst_count = 0;
295 
296 	db_cmd_loop_done = 1;
297 }
298 #endif /* NO KGDB */
299 
300 #ifdef	SOFTWARE_SSTEP
301 /*
302  *	Software implementation of single-stepping.
303  *	If your machine does not have a trace mode
304  *	similar to the vax or sun ones you can use
305  *	this implementation, done for the mips.
306  *	Just define the above conditional and provide
307  *	the functions/macros defined below.
308  *
309  * extern boolean_t
310  *	inst_branch(ins),	returns true if the instruction might branch
311  * extern unsigned
312  *	branch_taken(ins, pc, getreg_val, regs),
313  *				return the address the instruction might
314  *				branch to
315  *	getreg_val(regs, reg),	return the value of a user register,
316  *				as indicated in the hardware instruction
317  *				encoding, e.g. 8 for r8
318  *
319  * next_instr_address(pc, bd)	returns the address of the first
320  *				instruction following the one at "pc",
321  *				which is either in the taken path of
322  *				the branch (bd==1) or not.  This is
323  *				for machines (mips) with branch delays.
324  *
325  *	A single-step may involve at most 2 breakpoints -
326  *	one for branch-not-taken and one for branch taken.
327  *	If one of these addresses does not already have a breakpoint,
328  *	we allocate a breakpoint and save it here.
329  *	These breakpoints are deleted on return.
330  */
331 
332 void
333 db_set_single_step(db_regs_t *regs)
334 {
335 	db_addr_t pc = PC_REGS(regs);
336 #ifndef SOFTWARE_SSTEP_EMUL
337 	db_addr_t brpc;
338 	u_int inst;
339 
340 	/*
341 	 * User was stopped at pc, e.g. the instruction
342 	 * at pc was not executed.
343 	 */
344 	inst = db_get_value(pc, sizeof(int), FALSE);
345 	if (inst_branch(inst) || inst_call(inst) || inst_return(inst)) {
346 	    brpc = branch_taken(inst, pc, getreg_val, regs);
347 	    if (brpc != pc) {	/* self-branches are hopeless */
348 		db_taken_bkpt = db_set_temp_breakpoint(brpc);
349 	    }
350 #if 0
351 	    /* XXX this seems like a true bug, no?  */
352 	    pc = next_instr_address(pc, 1);
353 #endif
354 	}
355 #endif /*SOFTWARE_SSTEP_EMUL*/
356 	pc = next_instr_address(pc, 0);
357 	db_not_taken_bkpt = db_set_temp_breakpoint(pc);
358 }
359 
360 void
361 db_clear_single_step(db_regs_t *regs)
362 {
363 	if (db_taken_bkpt != 0) {
364 	    db_delete_temp_breakpoint(db_taken_bkpt);
365 	    db_taken_bkpt = 0;
366 	}
367 	if (db_not_taken_bkpt != 0) {
368 	    db_delete_temp_breakpoint(db_not_taken_bkpt);
369 	    db_not_taken_bkpt = 0;
370 	}
371 }
372 
373 #endif	/* SOFTWARE_SSTEP */
374