freebsd-nq/sys/ddb/db_run.c
Bruce Evans bd20334ca0 Abort single stepping in ddb if the trap is not for single-stepping.
This is not very easy to do, since ddb didn't know when traps are
for single-stepping.  It more or less assumed that traps are either
breakpoints or single-step, but even for x86 this became inadequate
with the release of the i386 in ~1986, and FreeBSD passes it other
trap types for NMIs and panics.

On x86, teach ddb when a trap is for single stepping using the %dr6
register.  Unknown traps are now treated almost the same as breakpoints
instead of as the same as single-steps.  Previously, the classification
of breakpoints was almost correct and everything else was unknown so
had to be treated as a single-step.  Now the classification of single-
steps is precise, the classification of breakpoints is almost correct
(as before) and everything else is unknown and treated like a
breakpoint.

This fixes:
- breakpoints not set by ddb, including the main one in kdb_enter(),
  were treated as single-steps and not stopped on when stepping
  (except for the usual, simple case of a step with residual count 1).
  As special cases, kdb_enter() didn't stop for fatal traps or panics
- similarly for "hardware breakpoints".

Use a new MD macro IS_SSTEP_TRAP(type, code) to code to classify
single-steps.  This is excessively complicated for bug-for-bug and
backwards compatibilty.  Design errors apparently started in Mach
in ~1990 or perhaps in the FreeBSD interface in ~1993.  Common trap
types like single steps should have a unique MI code (like the TRAP*
codes for user SIGTRAP) so that debuggers don't need macros like
IS_SSTEP_TRAP() to decode them.  But 'type' is actually an ambiguous
MD trap number, and code was always 0 (now it is (int)%dr6 on x86).
So it was impossible to determine the trap type from the args.
Global variables had to be used.

There is already a classification macro db_pc_is_single_step(), but
this just gets in the way.  It is only used to recover from bugs in
IS_BREAKPOINT_TRAP().  On some arches, IS_BREAKPOINT_TRAP() just
duplicates the ambiguity in 'type' and misclassifies single-steps as
breakpoints.  It defaults to 'false', which is the opposite of what is
needed for bug-for-bug compatibility.

When this is cleaned up, MI classification bits should be passed in
'code'.  This could be done now for positive-logic bits, since 'code'
was always 0, but some negative logic is needed for compatibility so
a simple MI classificition is not usable yet.

After reading %dr6, clear the single-step bit in it so that the type
of the next debugger trap can be decoded.  This is a little
ddb-specific.  ddb doesn't understand the need to clear this bit and
doing it before calling kdb is easiest.  gdb would need to reverse
this to support hardware breakpoints, but it just doesn't support
them now since gdbstub doesn't support %dr*.

Fix a bug involving %dr6: when emulating a single-step trap for vm86,
set the bit for it in %dr6.  Userland debuggers need this.  ddb now
needs this for vm86 bios calls.  The bit gets copied to 'code' then
cleared again.

Fix related style bugs:
- when clearing bits for hardware breakpoints in %dr6, spell the mask
  as ~0xf on both amd64 and i386 to get the correct number of bits
  using sign extension and not need a comment about using the wrong
  mask on amd64 (amd64 traps for invalid results but clearing the
  reserved top bits didn't trap since they are 0).
- rewrite my old wrong comments about using %dr6 for ddb watchpoints.
2016-09-15 17:24:23 +00:00

408 lines
9.3 KiB
C

/*-
* Mach Operating System
* Copyright (c) 1991,1990 Carnegie Mellon University
* All Rights Reserved.
*
* Permission to use, copy, modify and distribute this software and its
* documentation is hereby granted, provided that both the copyright
* notice and this permission notice appear in all copies of the
* software, derivative works or modified versions, and any portions
* thereof, and that both notices appear in supporting documentation.
*
* CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
* CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
* ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
*
* Carnegie Mellon requests users of this software to return to
*
* Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
* School of Computer Science
* Carnegie Mellon University
* Pittsburgh PA 15213-3890
*
* any improvements or extensions that they make and grant Carnegie the
* rights to redistribute these changes.
*/
/*
* Author: David B. Golub, Carnegie Mellon University
* Date: 7/90
*/
/*
* Commands to run process.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/kdb.h>
#include <sys/proc.h>
#include <machine/kdb.h>
#include <machine/pcb.h>
#include <vm/vm.h>
#include <ddb/ddb.h>
#include <ddb/db_break.h>
#include <ddb/db_access.h>
static int db_run_mode;
#define STEP_NONE 0
#define STEP_ONCE 1
#define STEP_RETURN 2
#define STEP_CALLT 3
#define STEP_CONTINUE 4
#define STEP_INVISIBLE 5
#define STEP_COUNT 6
static bool db_sstep_multiple;
static bool db_sstep_print;
static int db_loop_count;
static int db_call_depth;
int db_inst_count;
int db_load_count;
int db_store_count;
#ifdef SOFTWARE_SSTEP
db_breakpoint_t db_not_taken_bkpt = 0;
db_breakpoint_t db_taken_bkpt = 0;
#endif
#ifndef db_set_single_step
void db_set_single_step(void);
#endif
#ifndef db_clear_single_step
void db_clear_single_step(void);
#endif
#ifndef db_pc_is_singlestep
static bool
db_pc_is_singlestep(db_addr_t pc)
{
#ifdef SOFTWARE_SSTEP
if ((db_not_taken_bkpt != 0 && pc == db_not_taken_bkpt->address)
|| (db_taken_bkpt != 0 && pc == db_taken_bkpt->address))
return (true);
#endif
return (false);
}
#endif
bool
db_stop_at_pc(int type, int code, bool *is_breakpoint, bool *is_watchpoint)
{
db_addr_t pc;
db_breakpoint_t bkpt;
*is_breakpoint = IS_BREAKPOINT_TRAP(type, code);
*is_watchpoint = IS_WATCHPOINT_TRAP(type, code);
pc = PC_REGS();
if (db_pc_is_singlestep(pc))
*is_breakpoint = false;
db_clear_single_step();
db_clear_breakpoints();
db_clear_watchpoints();
#ifdef FIXUP_PC_AFTER_BREAK
if (*is_breakpoint) {
/*
* Breakpoint trap. Fix up the PC if the
* machine requires it.
*/
FIXUP_PC_AFTER_BREAK
pc = PC_REGS();
}
#endif
/*
* Now check for a breakpoint at this address.
*/
bkpt = db_find_breakpoint_here(pc);
if (bkpt) {
if (--bkpt->count == 0) {
bkpt->count = bkpt->init_count;
*is_breakpoint = true;
return (true); /* stop here */
}
return (false); /* continue the countdown */
} else if (*is_breakpoint) {
#ifdef BKPT_SKIP
BKPT_SKIP;
#endif
}
*is_breakpoint = false; /* might be a breakpoint, but not ours */
/*
* If stepping, then abort if the trap type is unexpected.
* Breakpoints owned by us are expected and were handled above.
* Single-steps are expected and are handled below. All others
* are unexpected.
*
* If the MD layer doesn't tell us when it is stepping, use the
* bad historical default that all unexepected traps.
*/
#ifndef IS_SSTEP_TRAP
#define IS_SSTEP_TRAP(type, code) true
#endif
if (db_run_mode != STEP_CONTINUE && !IS_SSTEP_TRAP(type, code)) {
printf("Stepping aborted\n");
db_run_mode = STEP_NONE;
return (true);
}
if (db_run_mode == STEP_INVISIBLE) {
db_run_mode = STEP_CONTINUE;
return (false); /* continue */
}
if (db_run_mode == STEP_COUNT) {
return (false); /* continue */
}
if (db_run_mode == STEP_ONCE) {
if (--db_loop_count > 0) {
if (db_sstep_print) {
db_printf("\t\t");
db_print_loc_and_inst(pc);
}
return (false); /* continue */
}
}
if (db_run_mode == STEP_RETURN) {
/* continue until matching return */
db_expr_t ins;
ins = db_get_value(pc, sizeof(int), false);
if (!inst_trap_return(ins) &&
(!inst_return(ins) || --db_call_depth != 0)) {
if (db_sstep_print) {
if (inst_call(ins) || inst_return(ins)) {
int i;
db_printf("[after %6d] ", db_inst_count);
for (i = db_call_depth; --i > 0; )
db_printf(" ");
db_print_loc_and_inst(pc);
}
}
if (inst_call(ins))
db_call_depth++;
return (false); /* continue */
}
}
if (db_run_mode == STEP_CALLT) {
/* continue until call or return */
db_expr_t ins;
ins = db_get_value(pc, sizeof(int), false);
if (!inst_call(ins) &&
!inst_return(ins) &&
!inst_trap_return(ins)) {
return (false); /* continue */
}
}
db_run_mode = STEP_NONE;
return (true);
}
void
db_restart_at_pc(bool watchpt)
{
db_addr_t pc = PC_REGS();
if ((db_run_mode == STEP_COUNT) ||
((db_run_mode == STEP_ONCE) && db_sstep_multiple) ||
(db_run_mode == STEP_RETURN) ||
(db_run_mode == STEP_CALLT)) {
/*
* We are about to execute this instruction,
* so count it now.
*/
#ifdef SOFTWARE_SSTEP
db_expr_t ins =
#endif
db_get_value(pc, sizeof(int), false);
db_inst_count++;
db_load_count += inst_load(ins);
db_store_count += inst_store(ins);
#ifdef SOFTWARE_SSTEP
/* XXX works on mips, but... */
if (inst_branch(ins) || inst_call(ins)) {
ins = db_get_value(next_instr_address(pc,1),
sizeof(int), false);
db_inst_count++;
db_load_count += inst_load(ins);
db_store_count += inst_store(ins);
}
#endif /* SOFTWARE_SSTEP */
}
if (db_run_mode == STEP_CONTINUE) {
if (watchpt || db_find_breakpoint_here(pc)) {
/*
* Step over breakpoint/watchpoint.
*/
db_run_mode = STEP_INVISIBLE;
db_set_single_step();
} else {
db_set_breakpoints();
db_set_watchpoints();
}
} else {
db_set_single_step();
}
}
#ifdef SOFTWARE_SSTEP
/*
* Software implementation of single-stepping.
* If your machine does not have a trace mode
* similar to the vax or sun ones you can use
* this implementation, done for the mips.
* Just define the above conditional and provide
* the functions/macros defined below.
*
* extern bool
* inst_branch(), returns true if the instruction might branch
* extern unsigned
* branch_taken(), return the address the instruction might
* branch to
* db_getreg_val(); return the value of a user register,
* as indicated in the hardware instruction
* encoding, e.g. 8 for r8
*
* next_instr_address(pc,bd) returns the address of the first
* instruction following the one at "pc",
* which is either in the taken path of
* the branch (bd==1) or not. This is
* for machines (mips) with branch delays.
*
* A single-step may involve at most 2 breakpoints -
* one for branch-not-taken and one for branch taken.
* If one of these addresses does not already have a breakpoint,
* we allocate a breakpoint and save it here.
* These breakpoints are deleted on return.
*/
void
db_set_single_step(void)
{
db_addr_t pc = PC_REGS(), brpc;
unsigned inst;
/*
* User was stopped at pc, e.g. the instruction
* at pc was not executed.
*/
inst = db_get_value(pc, sizeof(int), false);
if (inst_branch(inst) || inst_call(inst) || inst_return(inst)) {
brpc = branch_taken(inst, pc);
if (brpc != pc) { /* self-branches are hopeless */
db_taken_bkpt = db_set_temp_breakpoint(brpc);
}
pc = next_instr_address(pc, 1);
}
pc = next_instr_address(pc, 0);
db_not_taken_bkpt = db_set_temp_breakpoint(pc);
}
void
db_clear_single_step(void)
{
if (db_not_taken_bkpt != 0) {
db_delete_temp_breakpoint(db_not_taken_bkpt);
db_not_taken_bkpt = 0;
}
if (db_taken_bkpt != 0) {
db_delete_temp_breakpoint(db_taken_bkpt);
db_taken_bkpt = 0;
}
}
#endif /* SOFTWARE_SSTEP */
extern int db_cmd_loop_done;
/* single-step */
/*ARGSUSED*/
void
db_single_step_cmd(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
{
bool print = false;
if (count == -1)
count = 1;
if (modif[0] == 'p')
print = true;
db_run_mode = STEP_ONCE;
db_loop_count = count;
db_sstep_multiple = (count != 1);
db_sstep_print = print;
db_inst_count = 0;
db_load_count = 0;
db_store_count = 0;
db_cmd_loop_done = 1;
}
/* trace and print until call/return */
/*ARGSUSED*/
void
db_trace_until_call_cmd(db_expr_t addr, bool have_addr, db_expr_t count,
char *modif)
{
bool print = false;
if (modif[0] == 'p')
print = true;
db_run_mode = STEP_CALLT;
db_sstep_print = print;
db_inst_count = 0;
db_load_count = 0;
db_store_count = 0;
db_cmd_loop_done = 1;
}
/*ARGSUSED*/
void
db_trace_until_matching_cmd(db_expr_t addr, bool have_addr, db_expr_t count,
char *modif)
{
bool print = false;
if (modif[0] == 'p')
print = true;
db_run_mode = STEP_RETURN;
db_call_depth = 1;
db_sstep_print = print;
db_inst_count = 0;
db_load_count = 0;
db_store_count = 0;
db_cmd_loop_done = 1;
}
/* continue */
/*ARGSUSED*/
void
db_continue_cmd(db_expr_t addr, bool have_addr, db_expr_t count, char *modif)
{
if (modif[0] == 'c')
db_run_mode = STEP_COUNT;
else
db_run_mode = STEP_CONTINUE;
db_inst_count = 0;
db_load_count = 0;
db_store_count = 0;
db_cmd_loop_done = 1;
}