/*++ Copyright (c) 1992 Microsoft Corporation Module Name: ppcmach.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 --*/ #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 --*/ { 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; } // don't call BLR a call instruction if (disinstr.XLform_XO == BCLR_OP && disinstr.Long != 0x4e800020) { *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 ); }/* 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); }/* ProcessSetDRegsCmd() */ DWORDLONG GetFunctionResult( PCALLSTRUCT pcs ) { return pcs->context.Gpr3; } VOID vCallFunctionHelper( HTHDX hthd, FARPROC lpFunction, int cArgs, va_list vargs ) { int i; assert(Is64PtrSE(lpFunction)); for (i = 0; i < cArgs; i++) { (&hthd->context.Gpr3)[i] = va_arg(vargs, DWORD); } hthd->context.Lr = PC(hthd); Set_PC(hthd, lpFunction); hthd->fContextDirty = TRUE; } BOOL GetWndProcMessage( HTHDX hthd, UINT* pmsg ) /*++ Routine Description: This function is used to get the current Windows message (WM_CREATE, etc) when execution has been stopped at a wndproc. Return Value: False on failure; True otherwise. --*/ { *pmsg = (UINT)hthd->context.Gpr4; return TRUE; } #endif // !KERNEL ULONG PpcGetNextOffset (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%I64x\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 xosdBadThread; } /* * 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; } #if 0 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)); } #endif 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_EVENT64 *de, PDWORD eventCode, PDWORD subClass ) /*++ Arguments: hthd - Supplies thread that has a single step exception pending de - Supplies the DEBUG_EVENT64 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; } ULONGLONG GetRegValue(PCONTEXT regs, int cvindex) { if (cvindex < CV_PPC_GPR0+32) { return (®s->Gpr0)[cvindex - CV_PPC_GPR0]; } switch (cvindex) { case CV_PPC_PC: return regs->Iar; case CV_PPC_LR: return regs->Lr; default: assert(!"GetRegValue called with unrecognized index"); return (ULONGLONG)0 - 1; } } #ifndef KERNEL UOFFSET GetSPFromNLGDest(HTHDX hthd, LPNLG_DESTINATION pNlgDest) { UOFFSET dwRet; switch (pNlgDest->dwCode) { case NLG_CATCH_ENTER: // Catch handler case NLG_EXCEPT_ENTER: // Exception handler case NLG_FILTER_ENTER: // Exception filter case NLG_CATCH_LEAVE: // Return from Catch case NLG_FINALLY_ENTER: // Termination handlers case NLG_DESTRUCTOR_ENTER: // -GX handler case NLG_LONGJMPEX: // Exception safe longjmp dwRet = pNlgDest->uoffFramePointer; break; case NLG_LONGJMP: dwRet = pNlgDest->uoffFramePointer+1; break; default: dwRet = STACK_POINTER(hthd)+1; // emulate Virtual FP? break; } return dwRet; } PVOID InfoExceptionDuringStep(HTHDX hthd) { Unreferenced(hthd); // Information that needs to be propagated from the step location // to the action handler when an exception occurs is passed in the // void * returned by this function. In case of PPC no information // needs to be passed currently. return NULL; } #endif