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