/*++ Copyright (c) 1992 Microsoft Corporation Module Name: mach.c Abstract: This file contains the x86 specific code for dealing with machine dependent issues that invlove registers, instruction disassembly, function calling and other interesting things. Author: Jim Schaad (jimsch) Environment: Win32 - User Notes: --*/ #include "precomp.h" #pragma hdrstop #include "i386excp.h" extern CRITICAL_SECTION csContinueQueue; extern LPDM_MSG LpDmMsg; #if DBG static char * rgszTrace[] = { "Trace", "BreakPoint", "Cannot Trace", "Soft Int", "Call" }; #endif // DBG #define MAXL 20L #define BIT20(b) (b & 0x07) #define BIT53(b) (b >> 3 & 0x07) #define BIT76(b) (b >> 6 & 0x03) static int mod; /* mod of mod/rm byte */ // Stuff for debug registers 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; BOOL IsRet( HTHDX hthd, LPADDR addr ) { BYTE instr; DWORD cBytes; if (!AddrReadMemory( hthd->hprc, hthd, addr, &instr, 1, &cBytes )) { return FALSE; } return ((instr == 0xc2) || (instr == 0xc3) || (instr == 0xca) || (instr == 0xcb)) ? RETURN_USER : RETURN_NONE; } void IsCall( HTHDX hthd, LPADDR lpaddr, LPINT lpf, BOOL fStepOver ) /*++ Routine Description: This function checks to see if the specified instruction is a call instruction. Arguments: hthd - Supplies the handle to the current thread lpaddr - Supplies the address to check for the call instruction at lpf - Returns TRUE if is a call instruction fStepOver - Supplies TRUE if doing a step over Return Value: None. --*/ { int mode_32; int opsize_32; DWORD cBytes; DWORD cb; char membuf [ MAXL ]; char *pMem; UCHAR opcode, savedOpcode; int fPrefix; int fRepPrefix; int ttt; int mode; int rm; BOOL fAddrSet; ULONG rgul[2]; USHORT rgus[2]; ADDR addrSp; UOFFSET uNextEip = GetAddrOff(*lpaddr); /* * If we have already done this work and cached the answer then * pick up the answer from the cache. The cache marker is cleared * at the start of ProcessDebugEvent. */ if (hthd->fIsCallDone) { *lpaddr = hthd->addrIsCall; *lpf = hthd->iInstrIsCall; return; } /* * local addressing mode */ mode_32 = opsize_32 = hthd->fAddrOff32; /* * Read enough bytes to get the longest possible instruction */ if (!AddrReadMemory( hthd->hprc, hthd, lpaddr, membuf, MAXL, &cBytes) || (cBytes == 0)) { goto done; } DPRINT(1, ("(IsCall?) EIP=%08x Type=", *lpaddr)); /* * point to begin of instruction */ pMem = membuf; /* * read and process any prefixes first */ fPrefix = TRUE; fRepPrefix = FALSE; do { opcode = (UCHAR) *pMem++; /* get opcode */ /* * Operand size prefix */ if (opcode == 0x66) { opsize_32 = !opsize_32; } /* * Address size prefix */ else if (opcode == 0x67) { mode_32 = !mode_32; } /* * REP and REPNE prefix */ else if ((opcode & ~1) == 0xf2) { fRepPrefix = TRUE; } /* * LOCK prefix (0xf0) * Segment Override (0x26, 0x36, 0x2e, 0x3e, 0x64, 0x65) */ else if ( opcode != 0xf0 && (opcode & ~0x18) != 0x26 && (opcode & ~1) != 0x64 ) { fPrefix = FALSE; } } while ( fPrefix ); /* * Now start checking for the instructions which must be treated * in a special manner. Any instruction which does not respect * the trace bit (either due to flag munging or faulting) needs * to be treated specially. Also all call ops need to be treated * specially (step in vs step over). Finally interupts need to * be treated specially since they could cause faults in 16-bit mode */ fAddrSet = FALSE; savedOpcode = opcode; /* * Break point instruction */ if (opcode == 0xcc) { *lpf = INSTR_BREAKPOINT; } // NOTENOTE -- missing the INTO instruction /* * all other interrrupt instructions */ else if (opcode == 0xcd) { opcode = (UCHAR) *pMem++; /* * Is this really a 2-byte version of INT 3 ? */ if (opcode == 0x3) { *lpf = INSTR_BREAKPOINT; } /* * Is this a funky 16-bit floating point instruction? if so then * we need to make sure and step over it */ else if (!ADDR_IS_FLAT(*lpaddr) && (0x34 <= opcode) && (opcode <= 0x3c)) { if (opcode == 0x3C) { pMem++; } opcode = *pMem++; mode = opcode & 0xc0; rm = opcode & 0x03; switch ( mode) { case 0: if (rm == 0x6) { pMem += 2; } break; case 1: pMem += 1; break; case 2: pMem += 2; break; } *lpf = INSTR_CANNOT_TRACE; GetAddrOff(*lpaddr) += pMem - membuf; fAddrSet = TRUE; } /* * This is an FWAIT instr -- 2 bytes long */ else if (!ADDR_IS_FLAT(*lpaddr) && opcode == 0x3d) { *lpf = INSTR_CANNOT_TRACE; GetAddrOff(*lpaddr) += 2; fAddrSet = TRUE; } /* * This is a 0x3f interrupt -- I think this is for * overlays in dos */ else if (!ADDR_IS_FLAT(*lpaddr) && (opcode == 0x3f)) { if (fStepOver) { *lpf = INSTR_CANNOT_TRACE; AddrInit(&addrSp, 0, SsSegOfHthdx(hthd), STACK_POINTER(hthd), FALSE, FALSE, FALSE, hthd->fAddrIsReal ); if (!AddrReadMemory(hthd->hprc, hthd, &addrSp, rgus, 4, &cb) || (cb != 4) ) { goto done; } AddrInit(lpaddr, 0, rgus[1], SE32To64( (UOFF32) rgus[0] ), FALSE, FALSE, FALSE, hthd->fAddrIsReal ); fAddrSet = TRUE; } } /* * OK its really an interrupt --- deal with it */ else { if (!fStepOver && hthd->fAddrIsReal) { *lpf = INSTR_CANNOT_TRACE; AddrInit(&addrSp, 0, 0, SE32To64(opcode*4), FALSE, FALSE, FALSE, TRUE); if (!AddrReadMemory(hthd->hprc, hthd, &addrSp, rgus, 4, &cb) || (cb != 4) ) { goto done; } AddrInit(lpaddr, 0, rgus[1], SE32To64( (UOFF32) rgus[0] ), FALSE, FALSE, FALSE, TRUE ); fAddrSet = TRUE; } } } /* * Now check for various call instructions */ else if (opcode == 0xe8) { /* near direct call */ *lpf = INSTR_IS_CALL; // If we are going to follow the call, keep track of the // address we are calling, since we can't step Win95 system // if (!fStepOver) { // uNextEip = *(DWORD *)(pMem+1); // } pMem += (1 + opsize_32)*2; } else if (opcode == 0x9a) { /* far direct call */ *lpf = INSTR_IS_CALL; pMem += (2 + opsize_32)*2; } else if (opcode == 0xff) { opcode = *pMem++; /* compute the modRM bits for instruction */ ttt = BIT53(opcode); if ((ttt & ~1) == 2) { /* indirect call */ *lpf = INSTR_IS_CALL; mod = BIT76(opcode); if (mod != 3) { /* non-register operand */ rm = BIT20( opcode ); if (mode_32) { if (rm == 4) { rm = BIT20(*pMem++); /* get base from SIB */ } if (mod == 0) { if (rm == 5) { pMem += 4; /* long direct address */ } } else if (mod == 1) { pMem++; /* register with byte offset */ } else { pMem += 4; /* register with long offset */ } } else { /* 16-bit mode */ if (mod == 0) { if (rm == 6) { pMem += 2; /* short direct address */ } } else { pMem += mod; /* reg, byte, word offset */ } } } } } /* * Now catch all of the repeated instructions * INSB (0x6c) INSW (0x6d) OUTSB (0x6e) OUTSW (0x6f) * MOVSB (0xa4) MOVSW (0xa5) CMPSB (0xa6) CMPSW (0xa7) * STOSB (0xaa) STOSW (0xab) * LODSB (0xac) LODSW (0xad) SCASB (0xae) SCASW (0xaf) */ else if (fRepPrefix && (((opcode & ~3) == 0x6c) || ((opcode & ~3) == 0xa4) || ((opcode & ~1) == 0xaa) || ((opcode & ~3) == 0xac))) { if (fStepOver) { *lpf = INSTR_CANNOT_TRACE; } else { /* * Cannot trace the ins/outs instructions */ if ((opcode & ~3) == 0x6c) { *lpf = INSTR_CANNOT_TRACE; } } } /* * Now catch IO instructions -- these will generally fault and * be interpreted. */ else if ((opcode & ~3) == 0x6c) { *lpf = INSTR_CANNOT_TRACE; } /* * Now catch other instructions which change registers */ else if ((opcode == 0xfa) || (opcode == 0xfb) || (opcode == 0x9d) || (opcode == 0x9c)) { *lpf = INSTR_CANNOT_TRACE; } /* * Now catch irets */ else if (opcode == 0xcf) { *lpf = INSTR_CANNOT_TRACE; AddrInit(&addrSp, 0, SsSegOfHthdx(hthd), STACK_POINTER(hthd), hthd->fAddrIsFlat, hthd->fAddrOff32, FALSE, hthd->fAddrIsReal ); if (opsize_32) { if (!AddrReadMemory(hthd->hprc, hthd, &addrSp, rgul, 8, &cb) || (cb != 8) ) { goto done; } AddrInit(lpaddr, 0, (SEGMENT) rgul[1], SE32To64( rgul[0] ), hthd->fAddrIsFlat, TRUE, FALSE, FALSE ); } else { if (!AddrReadMemory(hthd->hprc, hthd, &addrSp, rgus, 4, &cb) || (cb != 4) ) { goto done; } AddrInit(lpaddr, 0, rgus[1], SE32To64( (UOFF32) rgus[0] ), FALSE, FALSE, FALSE, hthd->fAddrIsReal ); } fAddrSet = TRUE; } /* * Assume that we want to just trace the instruction */ else { *lpf = INSTR_TRACE_BIT; goto done; } /* */ DPRINT(1, ("%s", rgszTrace[*lpf])); /* * Have read enough bytes? no -- expect somebody else to blow up */ if (cBytes < (DWORD)(pMem - membuf)) { *lpf = INSTR_TRACE_BIT; goto done; } if (!fAddrSet) { GetAddrOff(*lpaddr) += pMem - membuf; } /* * Dump out the bytes for later checking */ #if DBG { DWORD i; DPRINT(1, ("length = %d bytes=", cBytes & 0xff)); for (i=0; icontext.Esp; AddrReadMemory(hthd->hprc, hthd, &addr, &uNextEip, sizeof(uNextEip), &cBytes); } if (IsInSystemDll(uNextEip)) { *lpf = INSTR_CANNOT_STEP; } } hthd->fIsCallDone = TRUE; hthd->addrIsCall = *lpaddr; hthd->iInstrIsCall = *lpf; return; } /* IsCall() */ XOSD SetupFunctionCall( LPEXECUTE_OBJECT_DM lpeo, LPEXECUTE_STRUCT lpes ) /*++ Routine Description: This function contains the machine dependent code for initializing the function call system. Arguments: lpeo - Supplies a pointer to the Execute Object for the Function call lpes - Supplies a pointer to the Execute Struct from the DM Return Value: XOSD error code --*/ { CONTEXT context; OFFSET off; int cb; ULONG ul; HPRCX hprc = lpeo->hthd->hprc; ADDR addr; /* * 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; } /* * Can copy the context from the cached context in the thread structure */ context = lpeo->hthd->context; /* * Now get the current stack offset */ lpeo->addrStack.addr.off = context.Esp; lpeo->addrStack.addr.seg = (SEGMENT) context.SegSs; if (!lpeo->hthd->fAddrOff32) { lpeo->addrStack.addr.off &= 0xffff; } /* * Put the return address onto the stack. If this is a far address * then it needs to be a far return address. Else it must be a * near return address. */ if (lpeo->hthd->fAddrOff32) { if (lpes->fFar) { assert(FALSE); /* Not used for Win32 */ } off = context.Esp - 4; if (DbgWriteMemory(hprc, off, &lpeo->addrStart.addr.off, 4, &cb) == 0 || (cb != 4)) { return xosdUnknown; } } else { if (lpes->fFar) { off = context.Esp - 4; ul = (lpeo->addrStart.addr.seg << 16) | (DWORD)lpeo->addrStart.addr.off; addr = lpeo->addrStack; GetAddrOff(addr) -= 4; TranslateAddress(hprc, lpeo->hthd, &addr, TRUE); if ((DbgWriteMemory(hprc, GetAddrOff(addr), &ul, 4, &cb) == 0) || (cb != 4)) { return xosdUnknown; } } else { off = context.Esp & 0xffff - 2; addr = lpeo->addrStack; GetAddrOff(addr) -= 2; TranslateAddress(hprc, lpeo->hthd, &addr, TRUE); if ((DbgWriteMemory(hprc, GetAddrOff(addr), &lpeo->addrStart.addr.off, 2, &cb) == 0) || (cb != 2)) { return xosdUnknown; } } } /* * Set the new stack pointer and starting address in the context and * write them back to the thread. */ lpeo->hthd->context.Esp = (DWORD)off; lpeo->hthd->context.Eip = (DWORD)lpeo->addrStart.addr.off; lpeo->hthd->fContextDirty = TRUE; return xosdNone; } /* SetupFunctionCall() */ 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->hthd->fAddrOff32) { if (lpeo->addrStack.addr.off <= lpeo->hthd->context.Esp) { return TRUE; } } else if ((lpeo->addrStack.addr.off <= (lpeo->hthd->context.Esp & 0xffff)) && (lpeo->addrStack.addr.seg == (SEGMENT) lpeo->hthd->context.SegSs)) { return TRUE; } return FALSE; } /* CompareStacks() */ #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() */ DWORDLONG GetFunctionResult( PCALLSTRUCT pcs ) { return pcs->context.Eax; } VOID vCallFunctionHelper( HTHDX hthd, ULONG64 Function, int cArgs, va_list vargs ) { int i; DWORD dw; IP_TYPE TmpPC; assert(Is64PtrSE(Function)); // set up the args to SuspendThread for (i = 0; i < cArgs; i++) { DWORD dw; DWORD arg = va_arg(vargs, DWORD); hthd->context.Esp -= 4; WriteProcessMemory(hthd->hprc->rwHand, (PVOID)hthd->context.Esp, &arg, sizeof(arg), &dw); } TmpPC = (IP_TYPE) PC(hthd); hthd->context.Esp -= 4; WriteProcessMemory(hthd->hprc->rwHand, (PVOID)hthd->context.Esp, &TmpPC, sizeof(TmpPC), &dw); Set_PC(hthd, Function); hthd->fContextDirty = TRUE; } #endif // !KERNEL BOOL ProcessFrameStackWalkNextCmd(HPRCX hprc, HTHDX hthd, PCONTEXT context, LPVOID pctxPtrs) { return FALSE; } #if 0 XOSD disasm ( LPSDI lpsdi, void*Memory, int Size ); BOOL ParseAddr ( char*, ADDR* ); BOOL ParseNumber( char*, DWORD*, int ); typedef struct _BTNODE { char *Name; BOOL IsCall; BOOL TargetAvail; } BTNODE; BTNODE BranchTable[] = { { "call" , TRUE , TRUE }, { "ja" , FALSE , TRUE }, { "jae" , FALSE , TRUE }, { "jb" , FALSE , TRUE }, { "jbe" , FALSE , TRUE }, { "jcxz" , FALSE , TRUE }, { "je" , FALSE , TRUE }, { "jecxz" , FALSE , TRUE }, { "jg" , FALSE , TRUE }, { "jge" , FALSE , TRUE }, { "jl" , FALSE , TRUE }, { "jle" , FALSE , TRUE }, { "jmp" , FALSE , TRUE }, { "jne" , FALSE , TRUE }, { "jno" , FALSE , TRUE }, { "jnp" , FALSE , TRUE }, { "jns" , FALSE , TRUE }, { "jo" , FALSE , TRUE }, { "jp" , FALSE , TRUE }, { "js" , FALSE , TRUE }, { "loop" , FALSE , FALSE }, { "loope" , FALSE , FALSE }, { "loopne" , FALSE , FALSE }, { "loopnz" , FALSE , FALSE }, { "loopz" , FALSE , FALSE }, { "ret" , FALSE , FALSE }, { "retf" , FALSE , FALSE }, { "retn" , FALSE , FALSE }, { NULL , FALSE , FALSE } }; DWORD BranchUnassemble( void *Memory, ADDR *Addr, BOOL *IsBranch, BOOL *TargetKnown, BOOL *IsCall, BOOL *IsTable, ADDR *Target ) { XOSD xosd; DWORD Consumed = 0; DWORD i; int s; char *p; ADDR Trgt; AddrInit( &Trgt, 0, 0, 0, TRUE, TRUE, FALSE, FALSE ); *IsBranch = FALSE; *IsTable = FALSE; Sdi.dop = dopOpcode| dopOperands | dopEA; Sdi.addr = *Addr; xosd = disasm( &Sdi, Memory, 16 ); if ( xosd == xosdNone ) { *IsTable = Sdi.fJumpTable; for ( i=0; BranchTable[i].Name != NULL; i++ ) { s = strcmp( Sdi.lpch, BranchTable[i].Name ); if ( s == 0 ) { *IsBranch = TRUE; *IsCall = BranchTable[i].IsCall; if (*IsTable) { *Target = Sdi.addrEA0; // We might know the target, but for this // purpose, we don't want to deal with it. *TargetKnown = FALSE; } else if (BranchTable[i].TargetAvail && (p = Sdi.lpch) && *(p += (_tcslen(p)+1)) ) { Trgt = *Addr; if ( ParseAddr( p, &Trgt ) ) { *TargetKnown = TRUE; } else { AddrInit( &Trgt, 0, 0, 0, TRUE, TRUE, FALSE, FALSE ); *TargetKnown = FALSE; } *Target = Trgt; } else { *Target = Trgt; *TargetKnown = FALSE; } break; } else if ( s < 0 ) { break; } } Consumed = GetAddrOff( Sdi.addr ) - GetAddrOff(*Addr); } return Consumed; } BOOL ParseAddr ( char *szAddr, ADDR *Addr ) { char *p; BOOL fParsed; SEGMENT Segment; UOFF16 Off16; UOFF32 Off32; DWORD Dword; assert( szAddr ); assert( Addr ); fParsed = FALSE; p = _tcschr( szAddr, ':' ); if ( p ) { *p = '\0'; p++; if ( ParseNumber( szAddr, &Dword, 16 ) ) { Segment = (SEGMENT)Dword; if ( ParseNumber( p, &Dword, 16 ) ) { Off16 = (UOFF16)Dword; fParsed = TRUE; GetAddrSeg(*Addr) = Segment; GetAddrOff(*Addr) = Off16; } } } else { if ( ParseNumber( szAddr, &Dword, 16 ) ) { Off32 = (UOFF32)Dword; fParsed = TRUE; GetAddrOff(*Addr) = Off32; } } return fParsed; } BOOL ParseNumber ( char *szNumber, DWORD *Number, int Radix ) { BOOL fParsed = FALSE; char *p = szNumber; char *q; assert( szNumber ); assert( Number ); if ( _tcslen(p) > 2 && p[0]=='0' && (p[1]=='x' || p[1]=='X') ) { p+=2; assert( Radix == 16 ); } q = p; while ( *q && isxdigit(*q) ) { q++; } if ( !*q ) { *Number = _tcstoul( p, NULL, Radix ); fParsed = TRUE; } return fParsed; } #endif // 0 BOOL SetupDebugRegister( HTHDX hthd, int Register, int DataSize, ULONG64 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 #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 = (DWORD)DataAddr; Dr7->Len0 = Len; Dr7->Rwe0 = rwMask; Dr7->L0 = 0x01; break; case 1: *Dr1 = (DWORD)DataAddr; Dr7->Len1 = Len; Dr7->Rwe1 = rwMask; Dr7->L1 = 0x01; break; case 2: *Dr2 = (DWORD)DataAddr; Dr7->Len2 = Len; Dr7->Rwe2 = rwMask; Dr7->L2 = 0x01; break; case 3: *Dr3 = (DWORD)DataAddr; Dr7->Len3 = Len; Dr7->Rwe3 = rwMask; Dr7->L3 = 0x01; break; } Dr7->LE = 0x01; #ifdef KERNEL ksr.KernelDr6 = 0; return SetExtendedContext(hthd, &ksr); #else Context.Dr6 = 0; return SetThreadContext(hthd->rwHand, &Context); #endif } #ifndef KERNEL VOID ClearAllDebugRegisters ( HPRCX hprc ) { HTHDX hthd; CONTEXT Context; Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; /* ** OSDebug only supports hardware breakpoints across all threads; ** NT supports them on a per-thread basis. So we just set the ** specified breakpoint on all currently existing threads. */ for (hthd = hprc->hthdChild; hthd; hthd = hthd->nextSibling) { if (!(hthd->tstate & ts_dead)) { if (!GetThreadContext(hthd->rwHand, &Context)) continue; Context.Dr7 = 0; SetThreadContext(hthd->rwHand, &Context); } } } /* ClearAllDebugRegisters */ #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 #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 Context.Dr6 = 0; SetThreadContext( hthd->rwHand, &Context ); #endif } } BOOL DecodeSingleStepEvent( HTHDX hthd, DEBUG_EVENT64 *de, PDWORD eventCode, PDWORD_PTR subClass ) /*++ Routine Description: 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. --*/ { DWORD dr6; PBREAKPOINT bp; #ifdef KERNEL KSPECIAL_REGISTERS ksr; GetExtendedContext( hthd, &ksr); dr6 = ksr.KernelDr6; #else CONTEXT Context; Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; DbgGetThreadContext( hthd, &Context); dr6 = Context.Dr6; Context.Dr6 = 0; DbgSetThreadContext( hthd, &Context ); #endif // if it was a single step, look no further: if ((dr6 & 0x4000) != 0) { #ifndef KERNEL if (IsChicago() && ! PeeIsEventExpected(hthd, *eventCode, *subClass, FALSE)) { // Win95 will sometimes give us totally bogus single step events out of the // blue. Map them to a new internal event code, so we can ignore them // later. de->dwDebugEventCode = *eventCode = BOGUS_WIN95_SINGLESTEP_EVENT; de->u.Exception.ExceptionRecord.ExceptionCode = *subClass = 0; } #endif return TRUE; } // Search for a matching walk... bp = GetWalkBPFromBits(hthd, (dr6 & 0xf)); if (bp) { de->dwDebugEventCode = *eventCode = BREAKPOINT_DEBUG_EVENT; de->u.Exception.ExceptionRecord.ExceptionCode = 0; *subClass = (DWORD_PTR)bp; return TRUE; } #ifndef KERNEL else if (IsChicago() && !PeeIsEventExpected(hthd, *eventCode, *subClass, FALSE)) { // Win95 will sometimes give us bogus single step events out of the // blue. Map them to a new internal event code, so we can ignore them // later. de->dwDebugEventCode = *eventCode = BOGUS_WIN95_SINGLESTEP_EVENT; de->u.Exception.ExceptionRecord.ExceptionCode = *subClass = 0; return TRUE; } #endif else { return FALSE; } } ULONGLONG GetRegValue( PCONTEXT regs, int cvindex ) { switch (cvindex) { case CV_REG_EAX: return regs->Eax; case CV_REG_EBX: return regs->Ebx; case CV_REG_ECX: return regs->Ecx; case CV_REG_EDX: return regs->Edx; case CV_REG_ESP: return regs->Esp; case CV_REG_EBP: return regs->Ebp; case CV_REG_ESI: return regs->Esi; case CV_REG_EDI: return regs->Edi; case CV_REG_EIP: return regs->Eip; default: assert(!"GetRegValue called with unrecognized index"); return (ULONGLONG)0 - 1; } } // Code related to exception handling below. //#ifndef KERNEL #if 1 /* * PrnTopOfExceptionStack() * Returns the address of the Registration Node currently at the top of the NT * exception stack */ RN * PrnTopOfExceptionStack( HTHDX hthd ) { RN * prn; DWORD cb; // read registration node from FS:0 if ((DbgReadMemory(hthd->hprc, hthd->offTeb + 0 /*except_list*/, &prn, sizeof(prn), &cb) == 0) || (cb != sizeof(prn)) ) { // 0xFFFFFFFF is the value NT uses to indicate an empty stack, so // that's what we'll use if the read failed prn = (RN *) -1; } return prn; } // Helper function to add an entry to the exception handler list. // Adds the lpAddr passed in to the EXHDLR allocating new memory if // neccessary. Returns back the count of the number of elements // allocated in EXHDLR BOOL AddToExHdlr( EXHDLR *pExHdlr, ADDR *lpAddr, DWORD *lpdwCountAlloc ) { assert(pExHdlr); assert(lpAddr); assert(lpdwCountAlloc); if (pExHdlr->count == *lpdwCountAlloc) { (*lpdwCountAlloc) *= 2; MHRealloc(pExHdlr, sizeof(EXHDLR) + (*lpdwCountAlloc) * sizeof(ADDR)); if (pExHdlr == NULL) { return FALSE; } } else { assert(pExHdlr->count < *lpdwCountAlloc); if (pExHdlr->count > *lpdwCountAlloc) { return FALSE; } } pExHdlr->addr[pExHdlr->count] = (*lpAddr); pExHdlr->count++; return TRUE; } UOFFSET GetSPFromNLGDest( HTHDX hthd, LPNLG_DESTINATION pNlgDest ) { Unreferenced(hthd); return (pNlgDest->uoffFramePointer); } void * InfoExceptionDuringStep( HTHDX 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. return PrnTopOfExceptionStack(hthd); } EXHDLR * GetExceptionCatchLocations( HTHDX hthd, LPVOID lpv ) { BOOL fFoundFinally = FALSE; // have we found any try/finally's yet // which are not nested inside the active // function call. BOOL fFoundExcept = FALSE; // have we found any try/except's STE ste; DWORD cb; SCOPE scope; CHAR rgbSig[4]; // signature, "XC00" or 0x19930520 BYTE rgbLH[10]; // Lang Hdlr: "mov eax, ...; jmp ..." FUNCINFO funcinfo; // C++ EH function info TBME tbme; HT ht; DWORD itry; DWORD icatch; INT thunk; UOFFSET uoffThunk; int countAllocated = 32; // Initial no of entries in ExHdlr. RN rn; RN * prn; RN * prnOldTopOfStack = (RN *)lpv; BOOL fInsideCall; ADDR addr = {0}; HPRCX hprc = hthd->hprc; EXHDLR * pExHdlr; pExHdlr = (EXHDLR *)MHAlloc(sizeof(EXHDLR) + countAllocated * sizeof(ADDR)); pExHdlr->count = 0; // None allocated yet. if (pExHdlr == NULL) { return NULL; } // initialize pieces of the addr correctly. AddrFromHthdx(&addr, hthd); prn = PrnTopOfExceptionStack(hthd); if (prnOldTopOfStack) { fInsideCall = TRUE; } else { fInsideCall = FALSE; } // Walk through all active registration nodes while ( ( prn != NULL ) && ( prn != (RN FAR *)0xFFFFFFFF ) ) { // Remember if the registration node we've just reached is equal to the old top of stack. if (prn == prnOldTopOfStack) { fInsideCall = FALSE; } // Read the registration node if (!ReadProcessMemory(hprc->rwHand, prn, &rn, sizeof(rn), &cb)) { assert(FALSE); // this can only happen if exception list is bogus! break; } // If lpfnLanguageHandler actually points to a thunk, figure out // where the thunk points IsThunk(hthd, (ULONG_PTR) rn.lpfnLanguageHandler, &thunk, &uoffThunk, NULL); if (thunk != THUNK_NONE) { rn.lpfnLanguageHandler = (LPFN) uoffThunk; } // The code below assumes that the implementation-dependent part of // the Registration Node matches the Microsoft C format. To make // sure this is the case, we check for a special signature. There // are two formats supported: // (1) SEH format: check for signature in front of the // lpfnLanguageHandler. In the C runtime, // _except_handler2() is immediately preceeded by the // bytes "XC00". // (2) C++ EH format: the lpfnLanguageHandler points to: // mov eax, offset FuncInfo // jmp __CxxFrameHandler // The FuncInfo structure begins with a magic number // which consists of the number 0x19930520. // If neither of these signatures is present, the node must have // been created either by assembly code or by some language product // other than Microsoft C, so we will skip over this registration // node since we don't know its format. if ( // lpfnLanguageHandler points to SEH handler? ReadProcessMemory(hprc->rwHand, (LPVOID) ((ULONG_PTR)rn.lpfnLanguageHandler - sizeof(rgbSig)), rgbSig, sizeof(rgbSig), &cb) && (memcmp(rgbSig, "XC00", sizeof(rgbSig)) == 0) ) { // Walk through all active scope table entries for (scope=rn.scopeCur; scope != scopeNil; scope=ste.scopeEnclosing) { // Read the scopetable entry (UNDONE: we're reading lots of // tiny chunks which are also close to each other -- we could optimize this!) if (!ReadProcessMemory(hprc->rwHand, (LPVOID)(rn.rgste + scope*sizeof(STE)), &ste, sizeof(ste), &cb)) { // this can only happen if scope list is bogus! assert(FALSE); break; } // Set a breakpoint if this is a try/except or the first // try/finally which is not nested inside the function // call that we're stepping over if (ste.lpfnFilter || (!fFoundFinally && !fInsideCall)) { // Set a breakpoint at the address of the handler GetAddrOff( addr ) = (UOFFSET) ste.lpfnHandler; // We are not interested in stopping if this is a nested // handler if (!fInsideCall) { if (!AddToExHdlr(pExHdlr, &addr, &countAllocated)) { break; } } } if (ste.lpfnFilter == NULL) { fFoundFinally = TRUE; } else { fFoundExcept = TRUE; } } } else if ( // lpfnLanguageHandler points to C++ EH handler? ReadProcessMemory(hprc->rwHand, rn.lpfnLanguageHandler, rgbLH, sizeof(rgbLH), &cb) && rgbLH[0] == 0xB8 && // "mov eax, ..." rgbLH[5] == 0xE9 && // "jmp ..." ReadProcessMemory(hprc->rwHand, *(LPVOID*)(rgbLH+1), &funcinfo, sizeof(funcinfo), &cb) && funcinfo.dwMagic == 0x19930520 ) { for (itry = 0; itry < funcinfo.dwTryBlocks; ++itry) { if (ReadProcessMemory(hprc->rwHand, funcinfo.ptbme + itry, &tbme, sizeof(tbme), &cb)) { for (icatch = 0; icatch < tbme.dwCatches; ++icatch) { if (ReadProcessMemory(hprc->rwHand, tbme.pht + icatch, &ht, sizeof(ht), &cb)) { // Set a breakpoint GetAddrOff( addr ) = (UOFFSET) ht.lpfnCatch; // We are not interested in stopping if this is a nested // handler if (!fInsideCall) { if (!AddToExHdlr(pExHdlr, &addr, &countAllocated)) { break; } } fFoundExcept = TRUE; } } } } } // Move to next registration node in chain prn = rn.prnNext; } // Now that we have the whole list, we should return pExHdlr; } BOOL GetWndProcMessage( HTHDX hthd, UINT* pmsg ) /*++ Routine Description: This function is used to get the current Windows message (WM_CREATE, etc) from the stack. You must be at the first instruction of the WndProc and that instruction must be unexecuted. Return Value: False on failure; True otherwise. --*/ { ADDR addr; BOOL succ; ULONG cbRead; AddrInit(&addr, 0, PcSegOfHthdx (hthd), STACK_POINTER (hthd) + 8, hthd->fAddrIsFlat, hthd->fAddrOff32, FALSE, hthd->fAddrIsReal ); succ = AddrReadMemory (hthd->hprc, hthd, &addr, pmsg, 4, &cbRead); if (succ && cbRead == 4) { return TRUE; } return FALSE; } #endif // !KERNEL