/*++ Copyright (c) 1992 Microsoft Corporation Module Name: mach.c Abstract: This file contains the PPC601 specific code for dealing with the process of stepping a single instruction. This includes determination of the next offset to be stopped at and if the instruction is all call type instruction. Author: Kent Forschmiedt (kentf) Farooq Butt (fmbutt@engage.sps.mot.com) Environment: Win32 - User Notes: --*/ #include "precomp.h" #pragma hdrstop //setup a couple of macros // The below macro is used to do subscripting operations // len_item is the length of the embedded word that we are // interested in subscripting #define NTH_BIT(word,n,len_item) \ ( ((word) >> ((len_item) - (n) - 1)) & 0x01) // // Stuff for debug registers // // The debug register architecture is represented to NT as // nearly identical to the x86. // As of this writing, there is one debug register, and it only // supports a data length of 8. // typedef struct _DR7 *PDR7; typedef struct _DR7 { DWORD L0 : 1; DWORD G0 : 1; DWORD L1 : 1; DWORD G1 : 1; DWORD L2 : 1; DWORD G2 : 1; DWORD L3 : 1; DWORD G3 : 1; DWORD LE : 1; DWORD GE : 1; DWORD Pad1 : 3; DWORD GD : 1; DWORD Pad2 : 1; DWORD Pad3 : 1; DWORD Rwe0 : 2; DWORD Len0 : 2; DWORD Rwe1 : 2; DWORD Len1 : 2; DWORD Rwe2 : 2; DWORD Len2 : 2; DWORD Rwe3 : 2; DWORD Len3 : 2; } DR7; #define RWE_EXEC 0x00 #define RWE_WRITE 0x01 #define RWE_RESERVED 0x02 #define RWE_READWRITE 0x03 DWORD LenMask[ MAX_DEBUG_REG_DATA_SIZE + 1 ] = DEBUG_REG_LENGTH_MASKS; extern LPDM_MSG LpDmMsg; BOOL IsRet( HTHDX hthd, LPADDR addr ) { DWORD instr; DWORD cBytes; if (!AddrReadMemory( hthd->hprc, hthd, addr, &instr, 4, &cBytes )) { return FALSE; } return (instr == 0x4e800020); // bclr branch always } void IsCall ( HTHDX hthd, LPADDR lpaddr, LPINT lpf, BOOL fStepOver ) /*++ Routine Description: IsCall Arguments: hthd - Supplies the handle to the thread lpaddr - Supplies the address to be check for a call instruction lpf - Returns class of instruction: CALL BREAKPOINT_INSTRUCTION SOFTWARE_INTERRUPT FALSE fStepOver Return Value: None. --*/ { ULONG opcode; ADDR iaraddr = *lpaddr; DWORD length; PPC_INSTRUCTION disinstr; BOOL r; if (hthd->fIsCallDone) { *lpaddr = hthd->addrIsCall; *lpf = hthd->iInstrIsCall; return; } /* * Assume that this is not a call instruction */ *lpf = FALSE; /* * Read in the dword which contains the instruction under * inspection. */ r = AddrReadMemory(hthd->hprc, hthd, &iaraddr, &disinstr.Long, sizeof(DWORD), &length); if (!r || length != sizeof(DWORD)) { goto done; } opcode = disinstr.Primary_Op; /* Do we have a branch or is this a breakpoint ? If it is a breakpoint, is it set by the user or was it set by the debugger ? If all else fails return FALSE */ switch (opcode) { default: DPRINT(5,("IsCall opcode == DEFAULT")); break; // leaving *lpf = FALSE case BC_OP: DPRINT(5,("IsCall opcode == BC_OP")); // branch conditional NEVER a call break; // leaving *lpf == FALSE case B_OP: DPRINT(5,("IsCall opcode == B_OP")); // unconditional branch, could be a call // THIS is the real call operation if LK == 1 if ((disinstr.Long & 1) == 1) { // LK is on, we have a call *lpf = INSTR_IS_CALL; } break; // leaving *lpf = FALSE if not call... case X19_OP: DPRINT(5,("IsCall opcode == X19_OP")); // branch conditional on register (various extended opcodes) // This could be a function call if it is a // BCCTRL if ((disinstr.XLform_XO == BCCTR_OP) && ((disinstr.Long & 1) == 1)) *lpf = INSTR_IS_CALL; if (disinstr.XLform_XO == BCLR_OP) { *lpf = INSTR_IS_CALL; } break; // leaving *lpf = FALSE if not BCCTRL case TWI_OP: DPRINT(5,("IsCall opcode == TWI_OP")); // Is this TWI instruction installed by the debugger or // was it a user installed one ? // First make sure this is a BREAK if (disinstr.Dform_TO == 0x1f) // All 1's in the TO field { switch(disinstr.Dform_SI) { case DEBUG_PRINT_BREAKPOINT: case DEBUG_PROMPT_BREAKPOINT: case DEBUG_STOP_BREAKPOINT: case DEBUG_LOAD_SYMBOLS_BREAKPOINT: case DEBUG_UNLOAD_SYMBOLS_BREAKPOINT: *lpf = INSTR_BREAKPOINT; DPRINT(5,("IsCall opcode was an INSTR_BREAKPOINT")); break; default: *lpf = INSTR_SOFT_INTERRUPT; DPRINT(5,("IsCall opcode was a INSTR_SOFT_INTERRUPT")); break; } } } DPRINT(1, ("(IsCall?) FIR=%08x Type=%s\n", iaraddr.addr.off, *lpf==INSTR_IS_CALL ?"CALL": (*lpf==INSTR_BREAKPOINT?"BREAKPOINT": (*lpf==INSTR_SOFT_INTERRUPT ?"INTERRUPT": "NORMAL")))); done: if (*lpf==INSTR_IS_CALL) { lpaddr->addr.off += BP_SIZE; hthd->addrIsCall = *lpaddr; } else if ( *lpf==INSTR_SOFT_INTERRUPT ) { lpaddr->addr.off += BP_SIZE; } hthd->iInstrIsCall = *lpf; return; } /* IsCall() */ #ifndef KERNEL void ProcessGetDRegsCmd( HPRCX hprc, HTHDX hthd, LPDBB lpdbb ) { LPDWORD lpdw = (LPDWORD)LpDmMsg->rgb; CONTEXT cxt; int rs = 0; DEBUG_PRINT( "ProcessGetDRegsCmd :\n"); if (hthd == 0) { rs = 0; } else { cxt.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(hthd->rwHand, &cxt)) { LpDmMsg->xosdRet = xosdUnknown; rs = 0; } else { lpdw[0] = hthd->context.Dr0; lpdw[1] = hthd->context.Dr1; lpdw[2] = hthd->context.Dr2; lpdw[3] = hthd->context.Dr3; lpdw[4] = hthd->context.Dr6; lpdw[5] = hthd->context.Dr7; LpDmMsg->xosdRet = xosdNone; rs = sizeof(CONTEXT); } } Reply( rs, LpDmMsg, lpdbb->hpid ); return; } /* ProcessGetDRegsCmd() */ void ProcessSetDRegsCmd( HPRCX hprc, HTHDX hthd, LPDBB lpdbb ) { LPDWORD lpdw = (LPDWORD)(lpdbb->rgbVar); XOSD_ xosd = xosdNone; Unreferenced(hprc); DPRINT(5, ("ProcessSetDRegsCmd : ")); hthd->context.ContextFlags = CONTEXT_DEBUG_REGISTERS; hthd->context.Dr0 = lpdw[0]; hthd->context.Dr1 = lpdw[1]; hthd->context.Dr2 = lpdw[2]; hthd->context.Dr3 = lpdw[3]; hthd->context.Dr6 = lpdw[4]; hthd->context.Dr7 = lpdw[5]; if (hthd->fWowEvent) { WOWSetThreadContext(hthd, &hthd->context); } else { SetThreadContext(hthd->rwHand, &hthd->context); } Reply(0, &xosd, lpdbb->hpid); return; } /* ProcessSetDRegsCmd() */ VOID MakeThreadSuspendItselfHelper( HTHDX hthd, FARPROC lpSuspendThread ) { // // set up the args to SuspendThread // // GetCurrentThread always returns a magic cookie, safe for any thread. hthd->context.Gpr3 = (DWORD)GetCurrentThread(); hthd->context.Lr = PC(hthd); PC(hthd) = (DWORD)lpSuspendThread; hthd->fContextDirty = TRUE; } #endif // !KERNEL ULONG GetNextOffset ( HTHDX hthd, BOOL fStep ) /*++ Routine Description: From a limited disassembly of the instruction pointed by the IAR register, compute the offset of the next instruction for either a trace or step operation. Arguments: hthd - Supplies the handle to the thread to get the next offset for fStep - Supplies TRUE for STEP offset and FALSE for trace offset Return Value: Offset to place breakpoint at for doing a STEP or TRACE --*/ { ULONG returnvalue; ULONG opcode; ADDR iaraddr; DWORD length; ULONG *regArray = &hthd->context.Gpr0; PPC_INSTRUCTION disinstr; ULONG absolute; ULONG cr,ctr,lr,cond_ok=0,ctr_ok=0; BOOL r; AddrFromHthdx(&iaraddr, hthd); r = AddrReadMemory(hthd->hprc, hthd, &iaraddr, &disinstr.Long, sizeof(DWORD), &length); opcode = disinstr.Primary_Op; DPRINT(5,("Entered GetNextOffset routine, the address we start with is " "0x%x\n\tThe instruction is 0x%x",iaraddr.addr.off, disinstr.Long)); // setup default return value returnvalue = iaraddr.addr.off + sizeof(ULONG); // setup the absolute flag in case of a branch absolute = (int) ((disinstr.Long >> 1) & 1); // Before going into the switch, let us do some up front // calculations /* Let us use the algorithm described in pp 10-22 of the MPC/601 users manual */ ctr = hthd->context.Ctr; cr = hthd->context.Cr; /* First find out whether the CTR has to be decremented */ if (NTH_BIT(disinstr.Bform_BO,2,5) == 0) // i.e if ~B0[2] then ctr = ctr - 1 ctr = ctr - 1; // next we do the following operation: // ctr_ok = BO[2] OR ((ctr NEQ 0) XOR BO[3])) ctr_ok = (NTH_BIT(disinstr.Bform_BO,2,5) || ((ctr != 0) ^ (NTH_BIT(disinstr.Bform_BO,3,5)))); // now for // cond_ok= BO[0] OR ( (CR[BI] LEQIV BO[1])) cond_ok = ((NTH_BIT(disinstr.Bform_BO,0,5)) || ((NTH_BIT(cr,(disinstr.Bform_BI),32)) == (NTH_BIT(disinstr.Bform_BO,1,5)))); switch (opcode) { case SC_OP: DPRINT(5,("We have an SC_OP")); // stepping over a syscall instruction must set the breakpoint // at the inst after the syscall (default) break; case B_OP: DPRINT(5,("We have an B_OP")); // unconditional branch found // no need to chase down branch targets unless you are // tracing (i.e. NOT stepping over functions). // Of course the whole test about stepping etc. only // makes sense if you are stepping over a FUNCTION CALL // i.e. a branch and link operation if (!fStep) { if (absolute) { /* LI can only address words not bytes so << 2 */ returnvalue = disinstr.Iform_LI << 2; } else { returnvalue = (disinstr.Iform_LI << 2) + iaraddr.addr.off; } } break; case BC_OP: DPRINT(5,("We have a BC_OP")); /* We got a branch conditional, if it evaluates to true, let us set return to the target. Otherwise let us leave the default returnvalue in place */ /* << 2 bits since we address words */ if (ctr_ok && cond_ok) { if (absolute) { returnvalue = disinstr.Bform_BD << 2; } else { returnvalue = (disinstr.Bform_BD << 2)+iaraddr.addr.off; } } break; case X19_OP: DPRINT(5,("We have an X19_OP")); if (disinstr.XLform_XO == BCLR_OP) { lr = hthd->context.Lr; if (ctr_ok && cond_ok) { returnvalue = lr & ~3; // remember, we address words // not bytes thus ~3 } } else if (disinstr.XLform_XO == BCCTR_OP) { if (cond_ok) { returnvalue = ctr & ~3; } } break; default: DPRINT(5,("We have an unhandled DEFAULT op")); break; } return returnvalue; } /* GetNextOffset() */ XOSD SetupFunctionCall( LPEXECUTE_OBJECT_DM lpeo, LPEXECUTE_STRUCT lpes ) { /* * Can only execute functions on the current stopped thread. Therefore * assert that the current thread is stopped. */ assert(lpeo->hthd->tstate & ts_stopped); if (!(lpeo->hthd->tstate & ts_stopped)) { return xosdInvalidThread; } /* * Now get the current stack offset. */ lpeo->addrStack.addr.off = lpeo->hthd->context.Gpr1; /* * Now place the return address correctly */ lpeo->hthd->context.Iar = lpeo->hthd->context.Lr = lpeo->addrStart.addr.off; /* * Set the instruction pointer to the starting addresses * and write the context back out */ lpeo->hthd->context.Iar = lpeo->addrStart.addr.off; lpeo->hthd->fContextDirty = TRUE; return xosdNone; } BOOL CompareStacks( LPEXECUTE_OBJECT_DM lpeo ) /*++ Routine Description: This routine is used to determine if the stack pointers are currect for terminating function evaluation. Arguments: lpeo - Supplies the pointer to the DM Execute Object description Return Value: TRUE if the evaluation is to be terminated and FALSE otherwise --*/ { if (lpeo->addrStack.addr.off <= lpeo->hthd->context.Gpr1) { return TRUE; } return FALSE; } /* CompareStacks() */ BOOL ProcessFrameStackWalkNextCmd( HPRCX hprc, HTHDX hthd, PCONTEXT context, LPVOID pctxPtrs ) { return FALSE; } DWORD BranchUnassemble( void *Memory, ADDR *Addr, BOOL *IsBranch, BOOL *TargetKnown, BOOL *IsCall, BOOL *IsTable, ADDR *Target ) { ULONG opcode, absolute=FALSE, linkbit_on=FALSE; PPC_INSTRUCTION disinstr; UOFF32 Offset; UOFF32 TargetOffset; assert( Memory ); assert( IsBranch ); assert( TargetKnown ); assert( IsCall ); assert( Target ); Offset = GetAddrOff(*Addr); TargetOffset = 0; *IsBranch = FALSE; *IsTable = FALSE; disinstr.Long = * (PULONG ) Memory; // Is the absolute bit on ? absolute = (int) ((disinstr.Long >> 1) & 1); // Is the link bit on ? linkbit_on = (int) ((disinstr.Long & 1)); opcode = disinstr.Primary_Op; switch (opcode) { default: break; case BC_OP: *IsCall= linkbit_on; *IsBranch = TRUE; *TargetKnown = TRUE; if (absolute) TargetOffset = disinstr.Bform_BD << 2; else TargetOffset = (disinstr.Bform_BD << 2)+ Offset; break; case B_OP: *IsCall= linkbit_on; *IsBranch = TRUE; *TargetKnown = TRUE; if (absolute) TargetOffset = disinstr.Iform_LI << 2; else TargetOffset = (disinstr.Iform_LI << 2) + Offset; break; case X19_OP: // branch conditional on register (various extended opcodes) *IsCall = linkbit_on; *IsBranch = TRUE; *TargetKnown = FALSE; TargetOffset = 0; break; } AddrInit(Target, 0, 0, TargetOffset, TRUE, TRUE, FALSE, FALSE); return(sizeof(DWORD)); } BOOL SetupDebugRegister( HTHDX hthd, int Register, int DataSize, DWORD DataAddr, DWORD BpType ) { DWORD Len; DWORD rwMask; #ifdef KERNEL KSPECIAL_REGISTERS ksr; PDWORD Dr0 = &ksr.KernelDr0; PDWORD Dr1 = &ksr.KernelDr1; PDWORD Dr2 = &ksr.KernelDr2; PDWORD Dr3 = &ksr.KernelDr3; PDR7 Dr7 = (PDR7)&(ksr.KernelDr7); #else CONTEXT Context; PDWORD Dr0 = &Context.Dr0; PDWORD Dr1 = &Context.Dr1; PDWORD Dr2 = &Context.Dr2; PDWORD Dr3 = &Context.Dr3; PDR7 Dr7 = (PDR7)&(Context.Dr7); #endif // ppc currently only supports 1 assert(Register == 1); #ifdef KERNEL if (!GetExtendedContext(hthd, &ksr)) #else Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (!GetThreadContext(hthd->rwHand, &Context)) #endif { return FALSE; } Len = LenMask[ DataSize ]; switch ( BpType ) { case bptpDataR: rwMask = RWE_READWRITE; break; case bptpDataW: case bptpDataC: rwMask = RWE_WRITE; break; case bptpDataExec: rwMask = RWE_EXEC; // // length must be 0 for exec bp // Len = 0; break; default: assert(!"Invalid BpType!!"); break; } switch( Register ) { case 0: *Dr0 = DataAddr; Dr7->Len0 = Len; Dr7->Rwe0 = rwMask; Dr7->L0 = 0x01; break; case 1: *Dr1 = DataAddr; Dr7->Len1 = Len; Dr7->Rwe1 = rwMask; Dr7->L1 = 0x01; break; case 2: *Dr2 = DataAddr; Dr7->Len2 = Len; Dr7->Rwe2 = rwMask; Dr7->L2 = 0x01; break; case 3: *Dr3 = DataAddr; Dr7->Len3 = Len; Dr7->Rwe3 = rwMask; Dr7->L3 = 0x01; break; } #ifdef KERNEL ksr.KernelDr6 = 0; return SetExtendedContext(hthd, &ksr); #else return SetThreadContext(hthd->rwHand, &Context); #endif } VOID ClearDebugRegister( HTHDX hthd, int Register ) { #ifdef KERNEL KSPECIAL_REGISTERS ksr; PDWORD Dr0 = &ksr.KernelDr0; PDWORD Dr1 = &ksr.KernelDr1; PDWORD Dr2 = &ksr.KernelDr2; PDWORD Dr3 = &ksr.KernelDr3; PDR7 Dr7 = (PDR7)&(ksr.KernelDr7); #else CONTEXT Context; PDWORD Dr0 = &Context.Dr0; PDWORD Dr1 = &Context.Dr1; PDWORD Dr2 = &Context.Dr2; PDWORD Dr3 = &Context.Dr3; PDR7 Dr7 = (PDR7)&(Context.Dr7); #endif // ppc currently only supports 1 assert(Register == 1); #ifdef KERNEL if (GetExtendedContext(hthd, &ksr)) #else Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; if (GetThreadContext(hthd->rwHand, &Context)) #endif { switch( Register ) { case 0: *Dr0 = 0; Dr7->Len0 = 0; Dr7->Rwe0 = 0; Dr7->L0 = 0; break; case 1: *Dr1 = 0; Dr7->Len1 = 0; Dr7->Rwe1 = 0; Dr7->L1 = 0; break; case 2: *Dr2 = 0; Dr7->Len2 = 0; Dr7->Rwe2 = 0; Dr7->L2 = 0; break; case 3: *Dr3 = 0; Dr7->Len3 = 0; Dr7->Rwe3 = 0; Dr7->L3 = 0; break; } #ifdef KERNEL ksr.KernelDr6 = 0; SetExtendedContext(hthd, &ksr); #else SetThreadContext( hthd->rwHand, &Context ); #endif } } BOOL DecodeSingleStepEvent( HTHDX hthd, DEBUG_EVENT *de, PDWORD eventCode, PDWORD subClass ) /*++ Routine Description: Arguments: hthd - Supplies thread that has a single step exception pending de - Supplies the DEBUG_EVENT structure for the exception eventCode - Returns the remapped debug event id subClass - Returns the remapped subClass id Return Value: TRUE if event was a real single step or was successfully mapped to a breakpoint. FALSE if a register breakpoint occurred which was not expected. --*/ { return FALSE; }