/*++ Copyright (c) 1992 Microsoft Corporation Module Name: Walk.c Abstract: This module contains the support for "Walking". Author: Kent Forschmiedt (kentf) January 1, 1996 Environment: Win32, User Mode --*/ #include "precomp.h" #pragma hdrstop // Externals extern DMTLFUNCTYPE DmTlFunc; extern char abEMReplyBuf[]; extern DEBUG_EVENT64 falseBPEvent; extern CRITICAL_SECTION csWalk; extern CRITICAL_SECTION csThreadProcList; #ifdef HAS_DEBUG_REGS // Initializer defined in dm.h DWORD DebugRegDataSizes[] = DEBUG_REG_DATA_SIZES; #define NDEBUG_REG_DATA_SIZES (sizeof(DebugRegDataSizes) / sizeof(*DebugRegDataSizes)) #endif // Walk Structure. // Contains information to perform a walk on a thread. typedef struct _WALK { LIST_ENTRY AllWalkList; // List of all walks LIST_ENTRY WalkList; // List of all walks in this thread LIST_ENTRY GroupEntryList; // Binding to breakpoints HTHDX hthd; // thread BPTP BpType; // Breakpoint type BOOL Active; // Active flag DWORD GlobalCount; // All thread ref count DWORD LocalCount; // per-thread ref count UOFFSET AddrStart; // Range Begin UOFFSET AddrEnd; // Range End PBREAKPOINT StartBP; // BP on range entry BOOL HasAddrEnd; UOFFSET DataAddr; // Data Address DWORD DataSize; // Data Size PVOID DataContents; // for change detection BREAKPOINT *SafetyBP; // Safety breakpoint for calls METHOD Method; // Walk method #ifdef HAS_DEBUG_REGS int Register; BOOL SingleStep; // In single-step mode #endif } WALK; typedef struct _WALK *PWALK; // This node binds a walk structure to one or more breakpoints. // One of these nodes will be allocated for each binding of // a WALK to a breakpoint. typedef struct _WALK_GROUP_ENTRY { // Here is the list of walks that belongs to a breakpoint. // This is a list of WALK_GROUP_ENTRY nodes; all nodes on // this list represent the same breakpoint, each represents // a different WALK. LIST_ENTRY WalksInGroup; // Here is the list of breakpoints which are using this walk. // All WALK_GROUP_ENTRY nodes in this list refer to the same // WALK, while each refers to a different breakpoint. LIST_ENTRY GroupsUsingWalk; // Here is the walk associated with this node: PWALK Walk; } WALK_GROUP_ENTRY, *PWALK_GROUP_ENTRY; // Local variables LIST_ENTRY AllWalkListHead; // List of all walks PWALK SetWalkThread ( HPRCX hprc, HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType, BOOL Global ); BOOL RemoveWalkThread ( HPRCX hprc, HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType, BOOL ); BOOL StartWalk( PWALK Walk, BOOL Continuing ); PWALK AllocateWalk( HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType ); BOOL DeallocateWalk( PWALK ); BOOL RemoveWalkEntry( PWALK Walk, BOOL Global ); PWALK FindWalk ( HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType ); PWALK FindWalkForHthd( HANDLE hWalk, HTHDX hthd ); PBREAKPOINT FindBpForWalk( PVOID pWalk ); int MethodWalk( DEBUG_EVENT64*, HTHDX, DWORDLONG, DWORDLONG ); VOID AddWalkToGroupList( PLIST_ENTRY GroupList, PWALK Walk ); VOID RemoveWalkBindings( PWALK Walk ); VOID DuplicateWalkBindings( PWALK OldWalk, PWALK NewWalk ); // Exported Functions VOID ExprBPInitialize( VOID ) { InitializeListHead(&AllWalkListHead); } VOID ExprBPCreateThread( HPRCX hprc, HTHDX hthd ) /*++ Routine Description: If global walking, adds walk to new thread. Called when a new thread is created. Arguments: hprc - Supplies process hthd - Supplies thread Return Value: None --*/ { PWALK Walk; PLIST_ENTRY List; HTHDX hthdT; // If there are global walks, set them in this thread // Get a walk list from any thread in this process and // traverse it, copying any global walks. Note that we // can use any walk list because global walks are common // to all threads. EnterCriticalSection(&csWalk); EnterCriticalSection(&csThreadProcList); hthdT = hprc->hthdChild; while (hthdT && hthdT == hthd) { hthdT = hthdT->nextSibling; } LeaveCriticalSection(&csThreadProcList); if (hthdT) { List = hthdT->WalkList.Flink; while (List != &hthdT->WalkList) { Walk = CONTAINING_RECORD(List, WALK, WalkList); List = List->Flink; if ( Walk->GlobalCount > 0 ) { PWALK twalk = SetWalkThread( hprc, hthd, Walk->DataAddr, Walk->DataSize, Walk->BpType, TRUE ); // bind the new walk record the same as the old: DuplicateWalkBindings(Walk, twalk); } } } LeaveCriticalSection(&csWalk); } VOID ExprBPExitThread ( HPRCX hprc, HTHDX hthd ) /*++ Routine Description: Removes walk in a thread, called when the thread is gone. Arguments: hprc - Supplies process hthd - Supplies thread Return Value: None --*/ { PLIST_ENTRY List; PWALK Walk; DWORD GlobalCount; DWORD LocalCount; EnterCriticalSection(&csWalk); List = hthd->WalkList.Flink; while ( List != &hthd->WalkList ) { Walk = CONTAINING_RECORD(List, WALK, WalkList); List = List->Flink; GlobalCount = Walk->GlobalCount; LocalCount = Walk->LocalCount; while ( GlobalCount-- ) { RemoveWalkEntry( Walk, TRUE ); } while ( LocalCount-- ) { RemoveWalkEntry( Walk, FALSE ); } } LeaveCriticalSection(&csWalk); } VOID ExprBPContinue ( HPRCX hprc, HTHDX hthd ) /*++ Routine Description: Continues walking. Called as a result of a continue command. Arguments: hprc - Supplies process hthd - Supplies thread Return Value: None --*/ { PWALK Walk; PLIST_ENTRY List; if ( !hthd ) { return; } // See if we have a walk on the thread EnterCriticalSection(&csWalk); List = hthd->WalkList.Flink; while (List != &hthd->WalkList) { Walk = CONTAINING_RECORD(List, WALK, WalkList); List = List->Flink; #ifdef HAS_DEBUG_REGS if ( Walk->Register >= 0 && !Walk->SingleStep ) { StartWalk( Walk, TRUE ); } else #endif if ( !Walk->Active ) { if (Walk->BpType != bptpRange) { // Get the current address for the thread. Walk->AddrStart = PC( hthd ); // Get the end of the range Walk->AddrEnd = Walk->AddrStart; Walk->HasAddrEnd = FALSE; } // Start walking StartWalk( Walk, TRUE ); } } LeaveCriticalSection(&csWalk); } /* ExprBPContinue() */ VOID ExprBPResetBP( HTHDX hthd, PBREAKPOINT bp ) /*++ Routine Description: After stepping off of a hardware BP, reset debug register(s) before continuing. Arguments: hthd - Supplies the thread which has been stepped. bp - Supplies the BREAKPOINT Return Value: none --*/ { #ifndef HAS_DEBUG_REGS Unreferenced(hthd); #else PWALK Walk; PDEBUGREG Dr; assert(bp); assert(bp->hWalk); Walk = FindWalkForHthd(bp->hWalk, hthd); assert(Walk->Register >= 0 && hthd->DebugRegs[Walk->Register].InUse); Dr = &hthd->DebugRegs[Walk->Register]; SetupDebugRegister( hthd, Walk->Register, Dr->DataSize, Dr->DataAddr, Dr->BpType ); #endif // HAS_DEBUG_REGS } VOID ExprBPClearBPForStep( HTHDX hthd ) /*++ Routine Description: Turn off a hardware breakpoint to allow a single step to occur. This is necessary for x86 exec breakpoints, but not for data read/write. Arguments: hthd - Supplies the thread which is going to be stepped. Return Value: none --*/ { #ifndef HAS_DEBUG_REGS Unreferenced(hthd); #else BREAKPOINT *bp; PWALK Walk; bp = AtBP(hthd); assert(bp); assert(bp->hWalk); Walk = FindWalkForHthd(bp->hWalk, hthd); assert(Walk && Walk->Register >= 0 && hthd->DebugRegs[Walk->Register].InUse); ClearDebugRegister(hthd, Walk->Register); #endif } void ExprBPRestoreDebugRegs( HTHDX hthd ) /*++ Routine Description: Restore the CPU debug registers to the state that we last put them in. This routine is needed because the system trashes the debug registers after initializing the DLLs and before the app entry point is executed. Arguments: hthd - Supplies descriptor for thread whose registers need fixing. Return Value: None --*/ { #ifndef HAS_DEBUG_REGS Unreferenced(hthd); #else PWALK Walk; PLIST_ENTRY List; PDEBUGREG Dr; EnterCriticalSection(&csWalk); List = hthd->WalkList.Flink; while (List != &hthd->WalkList) { Walk = CONTAINING_RECORD(List, WALK, WalkList); List = List->Flink; if ( Walk->Active && Walk->Register >= 0 && hthd->DebugRegs[Walk->Register].InUse) { Dr = &hthd->DebugRegs[Walk->Register]; SetupDebugRegister( hthd, Walk->Register, Dr->DataSize, Dr->DataAddr, Dr->BpType ); } } LeaveCriticalSection(&csWalk); #endif } HANDLE SetWalk ( HPRCX hprc, HTHDX hthd, UOFFSET Addr, DWORD Size, DWORD BpType ) /*++ Routine Description: Sets up a walk. Returns a handle which may be used to associate this walk with a breakpoint structure. Arguments: hprc - Supplies process hthd - Supplies thread Addr - Supplies address Size - Supplies size of memory to watch BpType - Supplies type of breakpoint Return Value: A handle to the new list of walks --*/ { PWALK Walk; PLIST_ENTRY GroupList = NULL; if ( hprc ) { // If a thread is specified, we use that specific thread, // otherwise we must set the walk in all existing threads, // plus we must set things up so that we walk all future // threads too (while this walk is active). if ( hthd ) { Walk = SetWalkThread( hprc, hthd, Addr, Size, BpType, FALSE ); if (Walk) { GroupList = MHAlloc(sizeof(LIST_ENTRY)); InitializeListHead(GroupList); AddWalkToGroupList(GroupList, Walk); } } else { GroupList = MHAlloc(sizeof(LIST_ENTRY)); InitializeListHead(GroupList); EnterCriticalSection(&csThreadProcList); for ( hthd = (HTHDX)hprc->hthdChild; hthd; hthd = hthd->nextSibling ) { Walk = SetWalkThread( hprc, hthd, Addr, Size, BpType, TRUE ); if (Walk) { AddWalkToGroupList(GroupList, Walk); } } LeaveCriticalSection(&csThreadProcList); if (IsListEmpty(GroupList)) { MHFree(GroupList); GroupList = NULL; } } } return (HANDLE)GroupList; } BOOL RemoveWalk( HANDLE hWalk, BOOL Global ) /*++ Routine Description: Remove a group of walks. Arguments: Return Value: --*/ { PLIST_ENTRY GroupListHead = (PLIST_ENTRY)hWalk; PLIST_ENTRY List; PWALK_GROUP_ENTRY Entry; PWALK Walk; List = GroupListHead->Flink; while (List != GroupListHead) { Entry = CONTAINING_RECORD(List, WALK_GROUP_ENTRY, WalksInGroup); List = List->Flink; Walk = Entry->Walk; RemoveWalkEntry(Walk, Global); } // The list head pointed to by GroupList will be freed // when the last entry is deleted by DeallocateWalk. return TRUE; } BOOL RemoveWalkEntry( PWALK Walk, BOOL Global ) { HTHDX hthd = Walk->hthd; #ifndef KERNEL BOOL Froze = FALSE; // freeze the thread if ( hthd->tstate & ts_running ) { if ( SuspendThread( hthd->rwHand ) != -1L) { hthd->tstate |= ts_frozen; Froze = TRUE; } } #endif // Remove the walk Global ? Walk->GlobalCount-- : Walk->LocalCount--; if ( Walk->GlobalCount == 0 && Walk->LocalCount == 0 ) { #ifdef HAS_DEBUG_REGS if ( Walk->Register >= 0 ) { Walk->Active = FALSE; DeallocateWalk( Walk ); } else #endif { // If the walk is active, the method will eventually // be called. Otherwise we must call the method // ourselves. if ( !Walk->Active ) { MethodWalk( NULL, hthd, 0, (DWORDLONG)Walk ); } } } #ifndef KERNEL // Resume the thread if we froze it. if ( Froze ) { if (ResumeThread(hthd->rwHand) != -1L ) { hthd->tstate &= ~ts_frozen; } } #endif return TRUE; } #ifdef HAS_DEBUG_REGS PBREAKPOINT GetWalkBPFromBits( HTHDX hthd, DWORD bits ) { PWALK Walk; PLIST_ENTRY List; PBREAKPOINT bp = NULL; EnterCriticalSection(&csWalk); // This only finds the first match. If more than one BP was // matched by the CPU, we won't notice. List = hthd->WalkList.Flink; while (List != &hthd->WalkList) { Walk = CONTAINING_RECORD(List, WALK, WalkList); List = List->Flink; if ( Walk->Register >= 0 && hthd->DebugRegs[Walk->Register].InUse ) { if (bits & (1 << Walk->Register)) { // hit! bp = FindBpForWalk(Walk); break; } } } LeaveCriticalSection(&csWalk); return bp; } #endif // HAS_DEBUG_REGS BOOL CheckWalk( PWALK Walk ) /*++ Routine Description: This decides whether a data or range breakpoint should fire. Current implementation handles: Data change, emulated or implemented on hardware write BP Range BP Not handled: Emulated data read/write BP Arguments: Walk - Supplies the thread and breakpoint info Return Value: TRUE if the breakpoint should fire, FALSE if it should be ignored. This implementation is conservative; unhandled cases always return TRUE. --*/ { PVOID Data; DWORD dwSize; BOOL ret = TRUE; if (Walk->BpType == bptpDataC) { if (Walk->DataContents && (Data = MHAlloc(Walk->DataSize))) { if (DbgReadMemory(Walk->hthd->hprc, Walk->DataAddr, Data, Walk->DataSize, &dwSize)) { ret = (memcmp(Data, Walk->DataContents, Walk->DataSize) != 0); } MHFree(Data); } } else if (Walk->BpType == bptpRange) { ret = Walk->AddrStart <= PC(Walk->hthd) && PC(Walk->hthd) <= Walk->AddrEnd; } return ret; } BOOL CheckDataBP( HTHDX hthd, PBREAKPOINT Bp ) /*++ Routine Description: This decides whether a breakpoint should fire. If it is not a data breakpoint, it should fire. If it is a data breakpoint, decide whether it has been satisfied. Arguments: hthd - Supplies the thread that stopped Bp - Supplies the breakpoint that was hit Return Value: TRUE if the breakpoint has really fired, FALSE if it should be ignored. --*/ { PWALK Walk; assert(hthd); assert(Bp); if (!Bp->hWalk) { return TRUE; } Walk = FindWalkForHthd(Bp->hWalk, hthd); assert(Walk); if (!Walk) { return TRUE; } return CheckWalk(Walk); } // Local Functions PWALK SetWalkThread ( HPRCX hprc, HTHDX hthd, UOFFSET Addr, DWORD Size, DWORD BpType, BOOL Global ) /*++ Routine Description: Sets up a walk in a specific thread Arguments: hprc - Supplies process hthd - Supplies thread Addr - Supplies address Size - Supplies Size BpType - Supplies type (read, read/write, change, exec, range) Global - Supplies global flag Return Value: BOOL - TRUE if Walk set --*/ { PWALK Walk; BOOL AllocatedWalk = FALSE; BOOL Ok = FALSE; BOOL Froze = FALSE; if ( Walk = FindWalk( hthd, Addr, Size, BpType ) ) { // If the walk is already active, just increment the // reference count and we're done. // if it isn't active, fall through and activate it if ( Walk->Active ) { Global ? Walk->GlobalCount++ : Walk->LocalCount++; Ok = TRUE; goto Done; } } else { // Allocate a walk for this thread. if ( Walk = AllocateWalk( hthd, Addr, Size, BpType ) ) { AllocatedWalk = TRUE; } else { goto Done; } } #ifdef KERNEL Ok = TRUE; #else // We have to freeze the specified thread in order to get // the current address. if ( !(hthd->tstate & ts_running) || (hthd->tstate & ts_frozen)) { Ok = TRUE; } else if ( SuspendThread( hthd->rwHand ) != -1L) { Froze = TRUE; Ok = TRUE; hthd->context.ContextFlags = CONTEXT_CONTROL; DbgGetThreadContext( hthd, &hthd->context ); } #endif if ( Ok ) { // Increment reference count Global ? Walk->GlobalCount++ : Walk->LocalCount++; if (Walk->BpType != bptpRange) { // Get the current address for the thread. Walk->AddrStart = PC( hthd ); // Get the end of the range Walk->AddrEnd = Walk->AddrStart; Walk->HasAddrEnd = FALSE; } Ok = StartWalk( Walk, FALSE ); #ifndef KERNEL // Resume the thread if we froze it. if ( Froze ) { if (ResumeThread(hthd->rwHand) == (ULONG)-1) { assert(!"ResumeThread failed in SetWalkThread"); hthd->tstate |= ts_frozen; } } #endif } Done: // Clean up if ( !Ok ) { if ( Walk && AllocatedWalk ) { DeallocateWalk( Walk ); } Walk = NULL; } return Walk; } BOOL RemoveWalkThread ( HPRCX hprc, HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType, BOOL Global ) /*++ Routine Description: Removes a walk in a specific thread Arguments: hprc - Supplies process hthd - Supplies thread Addr - Supplies address Size - Supplies Size BpType - Supplies breakpoint type Global - Supplies global flag Return Value: BOOL - TRUE if Walk removed --*/ { PWALK Walk; if ( Walk = FindWalk( hthd, Addr, Size, BpType ) ) { return RemoveWalkEntry(Walk, Global); } else { return FALSE; } } BOOL StartWalk( PWALK Walk, BOOL Continuing ) /*++ Routine Description: Starts walking. Arguments: Walk - Supplies the walk sructure Continuing - Supplies a flag saying that the thread is being continued Return Value: BOOL - TRUE if done --*/ { BREAKPOINT* bp; ACVECTOR action = NO_ACTION; HTHDX hthd = Walk->hthd; DWORD dwSize; if (!(hthd->tstate & ts_stopped) || Continuing) { if (Walk->BpType == bptpDataC) { // remember contents for change detection if (Walk->DataContents) { MHFree(Walk->DataContents); } Walk->DataContents = MHAlloc(Walk->DataSize); // if we can't read the data, just fire on any write if (!DbgReadMemory(hthd->hprc, Walk->DataAddr, Walk->DataContents, Walk->DataSize, &dwSize)) { MHFree(Walk->DataContents); Walk->DataContents = NULL; } } #ifdef HAS_DEBUG_REGS if ( Walk->Register >= 0 ) { PDEBUGREG Dr = &hthd->DebugRegs[Walk->Register]; if ( !Dr->InUse ) { if (!SetupDebugRegister( hthd, Walk->Register, Dr->DataSize, Dr->DataAddr, Dr->BpType) ) { return FALSE; } Walk->Active = TRUE; Walk->SingleStep = FALSE; Dr->InUse = TRUE; } } else #endif // HAS_DEBUG_REGS if (Walk->BpType != bptpRange || ((Walk->AddrStart <= PC(hthd) && PC(hthd) <= Walk->AddrEnd)) ) { bp = AtBP( hthd ); // if the thread is sitting on a BP, the step off of BP code // does its thing first, then the walk method gets control. if ( !bp ) { Walk->Active = TRUE; // Setup a single step if (!SetupSingleStep(hthd, FALSE )) { return FALSE; } // Place a single step on our list of expected events. if (!(hthd->tstate & ts_stopped) || Continuing) { RegisterExpectedEvent( hthd->hprc, hthd, EXCEPTION_DEBUG_EVENT, (DWORD_PTR)EXCEPTION_SINGLE_STEP, &(Walk->Method), action, FALSE, 0); } } } else { // Range BP, current IP is out of range. // This implementation only works reliably for function // scoped range BPs. If a range has an entry point other // than its lowest address, it will fail to activate the // range stepper on entry. ADDR bpAddr; BREAKPOINT *pbp; AddrFromHthdx(&bpAddr, hthd); bpAddr.addr.off = Walk->AddrStart; pbp = FindBP(Walk->hthd->hprc, Walk->hthd, bptpExec, bpnsStop, &bpAddr, TRUE); if (!pbp) { METHOD *method = (METHOD*)MHAlloc(sizeof(METHOD)); *method = Walk->Method; Walk->StartBP = SetBP(Walk->hthd->hprc, Walk->hthd, bptpExec, bpnsStop, &bpAddr, (HPID)INVALID); method->lparam2 = (LPVOID) Walk->StartBP; RegisterExpectedEvent(Walk->hthd->hprc, Walk->hthd, BREAKPOINT_DEBUG_EVENT, (DWORD_PTR) Walk->StartBP, DONT_NOTIFY, SSActionRemoveBP, FALSE, (UINT_PTR)method); } } } return TRUE; } /* StartWalk() */ // WALK Stuff PWALK AllocateWalk ( HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType ) /*++ Routine Description: Allocates new Walk structure and adds it to the list Arguments: hthd - Supplies thread Addr - Supplies address Size - Supplies Size BpType - Read, write, change, exec Return Value: PWALK - Walk created --*/ { PWALK Walk; DWORD i; EnterCriticalSection(&csWalk); if ( Walk = (PWALK)MHAlloc( sizeof( WALK ) ) ) { Walk->hthd = hthd; Walk->GlobalCount = 0; Walk->LocalCount = 0; Walk->Active = FALSE; Walk->SafetyBP = NULL; Walk->BpType = BpType; Walk->DataContents = NULL; Walk->StartBP = NULL; Walk->Method.notifyFunction = MethodWalk; Walk->Method.lparam = (UINT_PTR)Walk; InitializeListHead(&Walk->GroupEntryList); InsertTailList(&hthd->WalkList, &Walk->WalkList); InsertTailList(&AllWalkListHead, &Walk->AllWalkList); #ifdef HAS_DEBUG_REGS // If we can use (or re-use) a REG_WALK structure, do so. if (BpType == bptpRange) { Walk->DataAddr = 0; Walk->DataSize = 0; Walk->Register = -1; Walk->AddrStart = Addr; Walk->AddrEnd = Addr + Size; Walk->HasAddrEnd = TRUE; } else if (Addr == 0) { Walk->DataAddr = 0; Walk->DataSize = 0; Walk->Register = -1; Walk->AddrStart = 0; Walk->AddrEnd = 0; Walk->HasAddrEnd = FALSE; } else { Walk->DataAddr = Addr; Walk->DataSize = Size; Walk->AddrStart = 0; Walk->AddrEnd = 0; Walk->HasAddrEnd = FALSE; for (i = 0; i < NDEBUG_REG_DATA_SIZES; i++) { if (Size == DebugRegDataSizes[i]) { break; } } if (i == NDEBUG_REG_DATA_SIZES) { Walk->Register = -1; } else { int Register = -1; for ( i=0; i < NUMBER_OF_DEBUG_REGISTERS; i++ ) { if ( hthd->DebugRegs[i].ReferenceCount == 0 ) { Register = i; } else if ( (hthd->DebugRegs[i].DataAddr == Addr) && (hthd->DebugRegs[i].DataSize >= Size) && (hthd->DebugRegs[i].BpType == BpType) ) { Register = i; break; } } Walk->Register = Register; if ( Register >= 0 ) { if ( hthd->DebugRegs[Register].ReferenceCount == 0 ) { hthd->DebugRegs[Register].DataAddr = Addr; hthd->DebugRegs[Register].DataSize = Size; hthd->DebugRegs[Register].BpType = BpType; hthd->DebugRegs[Register].InUse = FALSE; } hthd->DebugRegs[Register].ReferenceCount++; } } } #endif } LeaveCriticalSection(&csWalk); return Walk; } BOOL DeallocateWalk ( PWALK Walk ) /*++ Routine Description: Takes a walk out of the list and frees its memory. Arguments: Walk - Supplies Walk to deallocate Return Value: BOOLEAN - TRUE if deallocated --*/ { EnterCriticalSection(&csWalk); RemoveEntryList(&Walk->AllWalkList); RemoveEntryList(&Walk->WalkList); RemoveWalkBindings(Walk); #ifdef HAS_DEBUG_REGS if ( Walk->Register >= 0 ) { PDEBUGREG Dr = &Walk->hthd->DebugRegs[Walk->Register]; if (--Dr->ReferenceCount <= 0) { Dr->InUse = FALSE; ClearDebugRegister(Walk->hthd, Walk->Register); } } #endif if (Walk->DataContents) { MHFree(Walk->DataContents); } MHFree( Walk ); LeaveCriticalSection(&csWalk); return TRUE; } PWALK FindWalk ( HTHDX hthd, UOFFSET Addr, DWORD Size, BPTP BpType ) /*++ Routine Description: Finds a walk Arguments: hthd - Supplies thread Addr - Supplies Address Size - Supplies Size BpType - Supplies type of BP Return Value: PWALK - Found Walk --*/ { PWALK Walk; PWALK FoundWalk = NULL; PLIST_ENTRY List; EnterCriticalSection(&csWalk); List = hthd->WalkList.Flink; while ( List != &hthd->WalkList ) { Walk = CONTAINING_RECORD(List, WALK, WalkList); List = List->Flink; if ( Walk->BpType == BpType ) { if (BpType == bptpRange) { if (Walk->DataAddr == Addr && Walk->DataSize == Size) { FoundWalk = Walk; break; } } else { if ((Walk->DataAddr == 0) || (Walk->DataAddr == Addr) ) { #ifdef HAS_DEBUG_REGS if ( Walk->Register == -1 ) { FoundWalk = Walk; break; } else if ( Size <= hthd->DebugRegs[Walk->Register].DataSize ) #endif { FoundWalk = Walk; break; } } } } } LeaveCriticalSection(&csWalk); return FoundWalk; } PWALK FindWalkForHthd( HANDLE hWalk, HTHDX hthd ) /*++ Routine Description: Arguments: Return Value: --*/ { PLIST_ENTRY GroupList = (PLIST_ENTRY)hWalk; PWALK_GROUP_ENTRY Entry; PLIST_ENTRY List; PWALK Walk; List = GroupList->Flink; while (List != GroupList) { Entry = CONTAINING_RECORD(List, WALK_GROUP_ENTRY, WalksInGroup); List = List->Flink; Walk = Entry->Walk; if (Walk->hthd == hthd) { return Walk; } } return NULL; } BOOL IsWalkInGroup( HANDLE hWalk, PVOID pWalk ) { PLIST_ENTRY GroupList = (PLIST_ENTRY)hWalk; PLIST_ENTRY List; PWALK Walk; List = GroupList->Flink; while (List != GroupList) { if ( (PWALK)pWalk == CONTAINING_RECORD(List, WALK_GROUP_ENTRY, WalksInGroup)->Walk ) { return TRUE; } List = List->Flink; } return FALSE; } PBREAKPOINT FindBpForWalk( PVOID pWalk ) { PBREAKPOINT pbp; EnterCriticalSection(&csThreadProcList); pbp = bpList; while (pbp) { if (pbp->hWalk && IsWalkInGroup(pbp->hWalk, pWalk)) { break; } pbp = pbp->next; } LeaveCriticalSection(&csThreadProcList); return pbp; } MethodWalk( DEBUG_EVENT64* de, HTHDX hthd, DWORDLONG unused, DWORDLONG lparam ) /*++ Routine Description: Walk method. Arguments: de - Supplies debug event hthd - Supplies thread unused - Walk - Supplies Walk Return Value: Nothing meaningful. --*/ { PWALK Walk = (PWALK)lparam; HPRCX hprc = hthd->hprc; Unreferenced( unused ); // however we got here, we don't need this BP anymore. if (Walk->SafetyBP) { RemoveBP( Walk->SafetyBP ); Walk->SafetyBP = NULL; } if (Walk->GlobalCount == 0 && Walk->LocalCount == 0) { // Walk has been removed, now discard it. if ( Walk->Active ) { ContinueThread(hthd); } DeallocateWalk( Walk ); } else { // is this useful? This is for emulating one source line // at a time. if (Walk->BpType == bptpRange && !Walk->HasAddrEnd ) { Walk->AddrEnd = GetEndOfRange( hprc, hthd, Walk->AddrStart ); Walk->HasAddrEnd = TRUE; } // First check to see if it should fire; if it should, // then ask the EM if it should stop. if (CheckWalk(Walk) && CheckBpt(hthd, FindBpForWalk(Walk))) { // tell the EM it stopped. Walk->Active = FALSE; ConsumeAllThreadEvents(hthd, FALSE); NotifyEM(&falseBPEvent, hthd, 0,(UINT_PTR)FindBpForWalk(Walk)); } else if (Walk->BpType == bptpRange) { // We still are in the range, continue stepping. ADDR currAddr; int lpf; AddrFromHthdx(&currAddr, hthd); IsCall(hthd, &currAddr, &lpf, FALSE); if (lpf == INSTR_IS_CALL) { // Set a safety breakpoint on the return site to prevent running // free over system calls and other mysterious cases. Walk->SafetyBP = SetBP(hprc, hthd, bptpExec, bpnsStop, &currAddr,(HPID)INVALID); } SingleStep(hthd, &(Walk->Method), TRUE, FALSE); } else { // Have the Expression BP manager know that we are continuing //ExprBPContinue( hprc, hthd ); // This just calls StartWalk instead of ExprBPContinue, to shorten // the code path at the expense of generality. // It could be shortened rather more by simply copying the // relevant fragments from StartWalk to here. StartWalk( Walk, TRUE ); ContinueThread(hthd); } } return TRUE; } VOID DuplicateWalkBindings( PWALK OldWalk, PWALK NewWalk ) /*++ Routine Description: Copy the breakpoint bindings for a walk into another walk. This is used for duplicationg global watchpoints. Arguments: OldWalk - Supplies a WALK which is bound to one or more breakpoints. NewWalk - Supplies a WALK which is to be bound to the same breakpoints as OldWalk. Return Value: None --*/ { PLIST_ENTRY OldList; PWALK_GROUP_ENTRY OldEntry; PWALK_GROUP_ENTRY NewEntry; // Run down the list of breakpoints that OldWalk is bound to, // and create equivalent bindings for NewWalk. OldList = OldWalk->GroupEntryList.Flink; while (OldList != &OldWalk->GroupEntryList) { // Find each breakpoint OldEntry = CONTAINING_RECORD(OldList, WALK_GROUP_ENTRY, GroupsUsingWalk); NewEntry = MHAlloc(sizeof(WALK_GROUP_ENTRY)); NewEntry->Walk = NewWalk; // add this to the Walk's list of BP bindings: InsertTailList(&NewWalk->GroupEntryList, &NewEntry->GroupsUsingWalk); // And add it to the BP's list of walks: InsertTailList(&OldEntry->WalksInGroup, &NewEntry->WalksInGroup); OldList = OldList->Flink; } } VOID RemoveWalkBindings( PWALK Walk ) /*++ Routine Description: Delete all of the bindings from a WALK to breakpoints (Group lists). The GROUP_LIST_ENTRY binding nodes will be freed. Arguments: Walk - Supplies the WALK which is to be unbound. Return Value: None --*/ { // remove all GROUP_LIST_ENTRY bindings for a walk PLIST_ENTRY List; PLIST_ENTRY PossibleGroupListHead; PWALK_GROUP_ENTRY Entry; List = Walk->GroupEntryList.Flink; while (List != &Walk->GroupEntryList) { Entry = CONTAINING_RECORD(List, WALK_GROUP_ENTRY, GroupsUsingWalk); List = List->Flink; // clean this entry out of the lists... PossibleGroupListHead = Entry->WalksInGroup.Flink; RemoveEntryList(&Entry->WalksInGroup); if (IsListEmpty(PossibleGroupListHead)) { MHFree(PossibleGroupListHead); } RemoveEntryList(&Entry->GroupsUsingWalk); MHFree(Entry); } } VOID AddWalkToGroupList( PLIST_ENTRY GroupList, PWALK Walk ) /*++ Routine Description: Bind a WALK to a breakpoint's walk group list. Arguments: GroupList - Supplis the list header for a breakpoint's group list Walk - Supplies a WALK structure which is to be added to the group list. Return Value: None --*/ { PWALK_GROUP_ENTRY Entry; Entry = MHAlloc(sizeof(WALK_GROUP_ENTRY)); Entry->Walk = Walk; InsertTailList(GroupList, &Entry->WalksInGroup); InsertTailList(&Walk->GroupEntryList, &Entry->GroupsUsingWalk); }