/* * Debugger break-points handling * * Copyright 1994 Martin von Loewis * Copyright 1995 Alexandre Julliard */ #include #include #include #include "windows.h" #include "debugger.h" #define INT3 0xcc /* int 3 opcode */ #define MAX_BREAKPOINTS 25 typedef struct { DBG_ADDR addr; BYTE addrlen; BYTE opcode; BOOL enabled; BOOL in_use; } BREAKPOINT; static BREAKPOINT breakpoints[MAX_BREAKPOINTS]; static int next_bp = 1; /* breakpoint 0 is reserved for step-over */ /*********************************************************************** * DEBUG_ChangeOpcode * * Change the opcode at segment:addr. */ static void DEBUG_SetOpcode( const DBG_ADDR *addr, BYTE op ) { if (addr->seg) { *(BYTE *)PTR_SEG_OFF_TO_LIN( addr->seg, addr->off ) = op; } else /* 32-bit code, so we have to change the protection first */ { /* There are a couple of problems with this. On Linux prior to 1.1.62, this call fails (ENOACCESS) due to a bug in fs/exec.c. This code is currently not tested at all on BSD. How do I determine the page size in a more symbolic manner? And why does mprotect need that start address of the page in the first place? Not that portability matters, this code is i386 only anyways... How do I get the old protection in order to restore it later on? */ if (mprotect((caddr_t)(addr->off & (~4095)), 4096, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { perror( "Can't set break point" ); return; } *(BYTE *)addr->off = op; mprotect((caddr_t)(addr->off & ~4095), 4096, PROT_READ | PROT_EXEC ); } } /*********************************************************************** * DEBUG_IsStepOverInstr * * Determine if the instruction at CS:EIP is an instruction that * we need to step over (like a call or a repetitive string move). */ static BOOL DEBUG_IsStepOverInstr( struct sigcontext_struct *context ) { BYTE *instr = (BYTE *)PTR_SEG_OFF_TO_LIN(CS_reg(context),EIP_reg(context)); for (;;) { switch(*instr) { /* Skip all prefixes */ case 0x2e: /* cs: */ case 0x36: /* ss: */ case 0x3e: /* ds: */ case 0x26: /* es: */ case 0x64: /* fs: */ case 0x65: /* gs: */ case 0x66: /* opcode size prefix */ case 0x67: /* addr size prefix */ case 0xf0: /* lock */ case 0xf2: /* repne */ case 0xf3: /* repe */ instr++; continue; /* Handle call instructions */ case 0xe8: /* call */ case 0x9a: /* lcall : */ return TRUE; case 0xff: /* call */ return (((instr[1] & 0x38) == 0x10) || ((instr[1] & 0x38) == 0x18)); /* Handle string instructions */ case 0x6c: /* insb */ case 0x6d: /* insw */ case 0x6e: /* outsb */ case 0x6f: /* outsw */ case 0xa4: /* movsb */ case 0xa5: /* movsw */ case 0xa6: /* cmpsb */ case 0xa7: /* cmpsw */ case 0xaa: /* stosb */ case 0xab: /* stosw */ case 0xac: /* lodsb */ case 0xad: /* lodsw */ case 0xae: /* scasb */ case 0xaf: /* scasw */ return TRUE; default: return FALSE; } } } /*********************************************************************** * DEBUG_SetBreakpoints * * Set or remove all the breakpoints. */ void DEBUG_SetBreakpoints( BOOL set ) { int i; for (i = 0; i < MAX_BREAKPOINTS; i++) { if (breakpoints[i].in_use && breakpoints[i].enabled) { if (DEBUG_IsBadWritePtr( &breakpoints[i].addr, 1 )) { fprintf( stderr, "Invalid address for breakpoint %d, disabling it\n", i ); breakpoints[i].enabled = FALSE; } else DEBUG_SetOpcode( &breakpoints[i].addr, set ? INT3 : breakpoints[i].opcode ); } } } /*********************************************************************** * DEBUG_FindBreakpoint * * Find the breakpoint for a given address. Return the breakpoint * number or -1 if none. */ int DEBUG_FindBreakpoint( const DBG_ADDR *addr ) { int i; for (i = 0; i < MAX_BREAKPOINTS; i++) { if (breakpoints[i].in_use && breakpoints[i].enabled && breakpoints[i].addr.seg == addr->seg && breakpoints[i].addr.off == addr->off) return i; } return -1; } /*********************************************************************** * DEBUG_AddBreakpoint * * Add a breakpoint. */ void DEBUG_AddBreakpoint( const DBG_ADDR *address ) { DBG_ADDR addr = *address; int num; BYTE *p; DBG_FIX_ADDR_SEG( &addr, CS_reg(DEBUG_context) ); if (next_bp < MAX_BREAKPOINTS) num = next_bp++; else /* try to find an empty slot */ { for (num = 1; num < MAX_BREAKPOINTS; num++) if (!breakpoints[num].in_use) break; if (num >= MAX_BREAKPOINTS) { fprintf( stderr, "Too many breakpoints. Please delete some.\n" ); return; } } if (!DBG_CHECK_READ_PTR( &addr, 1 )) return; p = DBG_ADDR_TO_LIN( &addr ); breakpoints[num].addr = addr; breakpoints[num].addrlen = !addr.seg ? 32 : (GET_SEL_FLAGS(addr.seg) & LDT_FLAGS_32BIT) ? 32 : 16; breakpoints[num].opcode = *p; breakpoints[num].enabled = TRUE; breakpoints[num].in_use = TRUE; fprintf( stderr, "Breakpoint %d at ", num ); DEBUG_PrintAddress( &breakpoints[num].addr, breakpoints[num].addrlen ); fprintf( stderr, "\n" ); } /*********************************************************************** * DEBUG_DelBreakpoint * * Delete a breakpoint. */ void DEBUG_DelBreakpoint( int num ) { if ((num <= 0) || (num >= next_bp) || !breakpoints[num].in_use) { fprintf( stderr, "Invalid breakpoint number %d\n", num ); return; } breakpoints[num].enabled = FALSE; breakpoints[num].in_use = FALSE; } /*********************************************************************** * DEBUG_EnableBreakpoint * * Enable or disable a break point. */ void DEBUG_EnableBreakpoint( int num, BOOL enable ) { if ((num <= 0) || (num >= next_bp) || !breakpoints[num].in_use) { fprintf( stderr, "Invalid breakpoint number %d\n", num ); return; } breakpoints[num].enabled = enable; } /*********************************************************************** * DEBUG_InfoBreakpoints * * Display break points information. */ void DEBUG_InfoBreakpoints(void) { int i; fprintf( stderr, "Breakpoints:\n" ); for (i = 1; i < next_bp; i++) { if (breakpoints[i].in_use) { fprintf( stderr, "%d: %c ", i, breakpoints[i].enabled ? 'y' : 'n'); DEBUG_PrintAddress( &breakpoints[i].addr, breakpoints[i].addrlen ); fprintf( stderr, "\n" ); } } } /*********************************************************************** * DEBUG_ShouldContinue * * Determine if we should continue execution after a SIGTRAP signal when * executing in the given mode. */ BOOL DEBUG_ShouldContinue( struct sigcontext_struct *context, enum exec_mode mode ) { DBG_ADDR addr; int bpnum; /* If not single-stepping, back up over the int3 instruction */ if (!(EFL_reg(DEBUG_context) & STEP_FLAG)) EIP_reg(DEBUG_context)--; addr.seg = (CS_reg(DEBUG_context) == WINE_CODE_SELECTOR) ? 0 : CS_reg(DEBUG_context); addr.off = EIP_reg(DEBUG_context); bpnum = DEBUG_FindBreakpoint( &addr ); breakpoints[0].enabled = 0; /* disable the step-over breakpoint */ if ((bpnum != 0) && (bpnum != -1)) { fprintf( stderr, "Stopped on breakpoint %d at ", bpnum ); DEBUG_PrintAddress( &breakpoints[bpnum].addr, breakpoints[bpnum].addrlen ); fprintf( stderr, "\n" ); return FALSE; } /* no breakpoint, continue if in continuous mode */ return (mode == EXEC_CONT); } /*********************************************************************** * DEBUG_RestartExecution * * Set the breakpoints to the correct state to restart execution * in the given mode. */ void DEBUG_RestartExecution( struct sigcontext_struct *context, enum exec_mode mode, int instr_len ) { DBG_ADDR addr; addr.seg = (CS_reg(DEBUG_context) == WINE_CODE_SELECTOR) ? 0 : CS_reg(DEBUG_context); addr.off = EIP_reg(DEBUG_context); if (DEBUG_FindBreakpoint( &addr ) != -1) mode = EXEC_STEP_INSTR; /* If there's a breakpoint, skip it */ switch(mode) { case EXEC_CONT: /* Continuous execution */ EFL_reg(DEBUG_context) &= ~STEP_FLAG; DEBUG_SetBreakpoints( TRUE ); break; case EXEC_STEP_OVER: /* Stepping over a call */ if (DEBUG_IsStepOverInstr(DEBUG_context)) { EFL_reg(DEBUG_context) &= ~STEP_FLAG; addr.off += instr_len; breakpoints[0].addr = addr; breakpoints[0].enabled = TRUE; breakpoints[0].in_use = TRUE; breakpoints[0].opcode = *(BYTE *)DBG_ADDR_TO_LIN( &addr ); DEBUG_SetBreakpoints( TRUE ); break; } /* else fall through to single-stepping */ case EXEC_STEP_INSTR: /* Single-stepping an instruction */ EFL_reg(DEBUG_context) |= STEP_FLAG; break; } }