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