1897 lines
36 KiB
C
1897 lines
36 KiB
C
/*++
|
|
|
|
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);
|
|
}
|