You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1055 lines
29 KiB
1055 lines
29 KiB
/*++
|
|
|
|
Copyright (c) 1989 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
debugsup.c
|
|
|
|
Abstract:
|
|
|
|
This module contains routines which provide support for the
|
|
kernel debugger.
|
|
|
|
Author:
|
|
|
|
Lou Perazzoli (loup) 02-Aug-1990
|
|
Landy Wang (landyw) 02-June-1997
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "mi.h"
|
|
|
|
#include <kdp.h>
|
|
|
|
ULONG MmPoisonedTb;
|
|
LONG MiInDebugger;
|
|
|
|
|
|
PVOID
|
|
MiDbgWriteCheck (
|
|
IN PVOID VirtualAddress,
|
|
IN PHARDWARE_PTE Opaque,
|
|
IN LOGICAL ForceWritableIfPossible
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine checks the specified virtual address and if it is
|
|
valid and writable, it returns that virtual address, otherwise
|
|
it returns NULL.
|
|
|
|
Arguments:
|
|
|
|
VirtualAddress - Supplies the virtual address to check.
|
|
|
|
Opaque - Supplies an opaque pointer.
|
|
|
|
Return Value:
|
|
|
|
Returns NULL if the address is not valid or writable, otherwise
|
|
returns the virtual address.
|
|
|
|
Environment:
|
|
|
|
Kernel mode IRQL at DISPATCH_LEVEL or greater.
|
|
|
|
--*/
|
|
|
|
{
|
|
MMPTE PteContents;
|
|
PMMPTE InputPte;
|
|
PMMPTE PointerPte;
|
|
ULONG_PTR IsPhysical;
|
|
|
|
InputPte = (PMMPTE)Opaque;
|
|
|
|
InputPte->u.Long = 0;
|
|
|
|
if (!MmIsAddressValid (VirtualAddress)) {
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(_IA64_)
|
|
|
|
//
|
|
// There are regions mapped by TRs (PALcode, PCR, etc) that are
|
|
// not part of the MI_IS_PHYSICAL_ADDRESS macro.
|
|
//
|
|
|
|
IsPhysical = MiIsVirtualAddressMappedByTr (VirtualAddress);
|
|
if (IsPhysical == FALSE) {
|
|
IsPhysical = MI_IS_PHYSICAL_ADDRESS (VirtualAddress);
|
|
}
|
|
#else
|
|
IsPhysical = MI_IS_PHYSICAL_ADDRESS (VirtualAddress);
|
|
#endif
|
|
|
|
if (IsPhysical) {
|
|
|
|
//
|
|
// All superpage mappings must be read-write and never generate
|
|
// faults so nothing needs to be done for this case.
|
|
//
|
|
|
|
return VirtualAddress;
|
|
}
|
|
|
|
PointerPte = MiGetPteAddress (VirtualAddress);
|
|
|
|
PteContents = *PointerPte;
|
|
|
|
#if defined(_IA64_)
|
|
|
|
//
|
|
// IA64 does not set the dirty bit in the processor microcode so
|
|
// check for it here. Note the access bit was already set by the
|
|
// caller if it wasn't initially on.
|
|
//
|
|
|
|
if ((PteContents.u.Hard.Write == 0) || (PteContents.u.Hard.Dirty == 0))
|
|
|
|
#elif defined(NT_UP)
|
|
if (PteContents.u.Hard.Write == 0)
|
|
#else
|
|
if (PteContents.u.Hard.Writable == 0)
|
|
#endif
|
|
{
|
|
if (ForceWritableIfPossible == FALSE) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// PTE is not writable, make it so.
|
|
//
|
|
|
|
*InputPte = PteContents;
|
|
|
|
//
|
|
// Carefully modify the PTE to ensure write permissions,
|
|
// preserving the page's cache attributes to keep the TB
|
|
// coherent.
|
|
//
|
|
|
|
#if defined(NT_UP) || defined(_IA64_)
|
|
PteContents.u.Hard.Write = 1;
|
|
#else
|
|
PteContents.u.Hard.Writable = 1;
|
|
#endif
|
|
MI_SET_PTE_DIRTY (PteContents);
|
|
MI_SET_ACCESSED_IN_PTE (&PteContents, 1);
|
|
|
|
MI_DEBUGGER_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, PteContents);
|
|
|
|
//
|
|
// Note KeFillEntryTb does not IPI the other processors. This is
|
|
// required as the other processors are frozen in the debugger
|
|
// and we will deadlock if we try and IPI them.
|
|
// Just flush the current processor instead.
|
|
//
|
|
|
|
KeFillEntryTb (VirtualAddress);
|
|
}
|
|
|
|
return VirtualAddress;
|
|
}
|
|
|
|
VOID
|
|
MiDbgReleaseAddress (
|
|
IN PVOID VirtualAddress,
|
|
IN PHARDWARE_PTE Opaque
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine resets the specified virtual address access permissions
|
|
to its original state.
|
|
|
|
Arguments:
|
|
|
|
VirtualAddress - Supplies the virtual address to check.
|
|
|
|
Opaque - Supplies an opaque pointer.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode IRQL at DISPATCH_LEVEL or greater.
|
|
|
|
--*/
|
|
|
|
{
|
|
MMPTE TempPte;
|
|
PMMPTE PointerPte;
|
|
PMMPTE InputPte;
|
|
|
|
InputPte = (PMMPTE)Opaque;
|
|
|
|
ASSERT (MmIsAddressValid (VirtualAddress));
|
|
|
|
if (InputPte->u.Long != 0) {
|
|
|
|
PointerPte = MiGetPteAddress (VirtualAddress);
|
|
|
|
TempPte = *InputPte;
|
|
TempPte.u.Hard.Dirty = 1;
|
|
|
|
MI_DEBUGGER_WRITE_VALID_PTE_NEW_PROTECTION (PointerPte, TempPte);
|
|
|
|
KeFillEntryTb (VirtualAddress);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
PVOID64
|
|
MiDbgTranslatePhysicalAddress (
|
|
IN PHYSICAL_ADDRESS PhysicalAddress,
|
|
IN ULONG Flags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine maps the specified physical address and returns
|
|
the virtual address which maps the physical address.
|
|
|
|
The next call to MiDbgTranslatePhysicalAddress removes the
|
|
previous physical address translation, hence only a single
|
|
physical address can be examined at a time (can't cross page
|
|
boundaries).
|
|
|
|
Arguments:
|
|
|
|
PhysicalAddress - Supplies the physical address to map and translate.
|
|
|
|
Flags -
|
|
|
|
MMDBG_COPY_WRITE - Ignored.
|
|
|
|
MMDBG_COPY_PHYSICAL - Ignored.
|
|
|
|
MMDBG_COPY_UNSAFE - Ignored.
|
|
|
|
MMDBG_COPY_CACHED - Use a PTE with the cached attribute for the
|
|
mapping to ensure TB coherence.
|
|
|
|
MMDBG_COPY_UNCACHED - Use a PTE with the uncached attribute for the
|
|
mapping to ensure TB coherence.
|
|
|
|
MMDBG_COPY_WRITE_COMBINED - Use a PTE with the writecombined attribute
|
|
for the mapping to ensure TB coherence.
|
|
|
|
Note the cached/uncached/write combined attribute requested by the
|
|
caller is ignored if Mm can internally determine the proper attribute.
|
|
|
|
Return Value:
|
|
|
|
The virtual address which corresponds to the physical address.
|
|
|
|
Environment:
|
|
|
|
Kernel mode IRQL at DISPATCH_LEVEL or greater.
|
|
|
|
--*/
|
|
|
|
{
|
|
MMPTE TempPte;
|
|
PVOID BaseAddress;
|
|
PFN_NUMBER PageFrameIndex;
|
|
PMMPFN Pfn1;
|
|
MMPTE OriginalPte;
|
|
PMMIO_TRACKER Tracker;
|
|
PLIST_ENTRY NextEntry;
|
|
LOGICAL AttributeConfirmed;
|
|
|
|
//
|
|
// The debugger can call this before Mm has even initialized in Phase 0 !
|
|
// MmDebugPte cannot be referenced before Mm has initialized without
|
|
// causing an infinite loop wedging the machine.
|
|
//
|
|
|
|
if (MmPhysicalMemoryBlock == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
BaseAddress = MiGetVirtualAddressMappedByPte (MmDebugPte);
|
|
|
|
TempPte = ValidKernelPte;
|
|
|
|
PageFrameIndex = (PFN_NUMBER)(PhysicalAddress.QuadPart >> PAGE_SHIFT);
|
|
|
|
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
|
|
|
|
if (MI_IS_PFN (PageFrameIndex)) {
|
|
|
|
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
|
|
|
|
switch (Pfn1->u3.e1.CacheAttribute) {
|
|
|
|
case MiCached:
|
|
case MiNotMapped:
|
|
default:
|
|
break;
|
|
case MiNonCached:
|
|
MI_DISABLE_CACHING (TempPte);
|
|
break;
|
|
case MiWriteCombined:
|
|
MI_SET_PTE_WRITE_COMBINE (TempPte);
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
|
|
AttributeConfirmed = FALSE;
|
|
|
|
NextEntry = MmIoHeader.Flink;
|
|
|
|
while (NextEntry != &MmIoHeader) {
|
|
|
|
Tracker = (PMMIO_TRACKER) CONTAINING_RECORD (NextEntry,
|
|
MMIO_TRACKER,
|
|
ListEntry.Flink);
|
|
|
|
if ((PageFrameIndex >= Tracker->PageFrameIndex) &&
|
|
(PageFrameIndex < Tracker->PageFrameIndex + Tracker->NumberOfPages)) {
|
|
Flags &= ~(MMDBG_COPY_CACHED | MMDBG_COPY_UNCACHED | MMDBG_COPY_WRITE_COMBINED);
|
|
switch (Tracker->CacheAttribute) {
|
|
case MiNonCached :
|
|
Flags |= MMDBG_COPY_UNCACHED;
|
|
break;
|
|
case MiWriteCombined :
|
|
Flags |= MMDBG_COPY_WRITE_COMBINED;
|
|
break;
|
|
case MiCached :
|
|
default:
|
|
Flags |= MMDBG_COPY_CACHED;
|
|
break;
|
|
}
|
|
AttributeConfirmed = TRUE;
|
|
break;
|
|
}
|
|
|
|
NextEntry = Tracker->ListEntry.Flink;
|
|
}
|
|
|
|
if (Flags & MMDBG_COPY_CACHED) {
|
|
NOTHING;
|
|
}
|
|
else if (Flags & MMDBG_COPY_UNCACHED) {
|
|
|
|
//
|
|
// Just flush the entire TB on this processor but not the others
|
|
// as an IPI may not be safe depending on when/why we broke into
|
|
// the debugger.
|
|
//
|
|
// If IPIs were safe, we would have used
|
|
// MI_PREPARE_FOR_NONCACHED (MiNonCached) instead.
|
|
//
|
|
|
|
KeFlushCurrentTb ();
|
|
|
|
MI_DISABLE_CACHING (TempPte);
|
|
}
|
|
else if (Flags & MMDBG_COPY_WRITE_COMBINED) {
|
|
|
|
//
|
|
// Just flush the entire TB on this processor but not the others
|
|
// as an IPI may not be safe depending on when/why we broke into
|
|
// the debugger.
|
|
//
|
|
// If IPIs were safe, we would have used
|
|
// MI_PREPARE_FOR_NONCACHED (MiWriteCombined) instead.
|
|
//
|
|
|
|
KeFlushCurrentTb ();
|
|
|
|
MI_SET_PTE_WRITE_COMBINE (TempPte);
|
|
}
|
|
else {
|
|
|
|
//
|
|
// This is an access to I/O space and we don't know the correct
|
|
// attribute type. Only proceed if the caller explicitly specified
|
|
// an attribute and hope he didn't get it wrong. If no attribute
|
|
// is specified then just return failure.
|
|
//
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Since we really don't know if the caller got the attribute right,
|
|
// set the flag below so (assuming the machine doesn't hard hang) we
|
|
// can at least tell in the crash that he may have whacked the TB.
|
|
//
|
|
|
|
if (AttributeConfirmed == FALSE) {
|
|
MmPoisonedTb += 1;
|
|
}
|
|
}
|
|
|
|
MI_SET_ACCESSED_IN_PTE (&TempPte, 1);
|
|
|
|
OriginalPte.u.Long = 0;
|
|
|
|
OriginalPte.u.Long = InterlockedCompareExchangePte (MmDebugPte,
|
|
TempPte.u.Long,
|
|
OriginalPte.u.Long);
|
|
|
|
if (OriginalPte.u.Long != 0) {
|
|
|
|
//
|
|
// Someone else is using the debug PTE. Inform our caller it is not
|
|
// available.
|
|
//
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Just flush (no sweep) the TB entry on this processor as an IPI
|
|
// may not be safe depending on when/why we broke into the debugger.
|
|
// Note that if we are in kd, then all the processors are frozen and
|
|
// this thread can't migrate so the local TB flush is enough. For
|
|
// the localkd case, our caller has raised to DISPATCH_LEVEL thereby
|
|
// ensuring this thread can't migrate even though the other processors
|
|
// are not frozen.
|
|
//
|
|
|
|
KiFlushSingleTb (BaseAddress);
|
|
|
|
return (PVOID64)((ULONG_PTR)BaseAddress + BYTE_OFFSET(PhysicalAddress.LowPart));
|
|
}
|
|
|
|
VOID
|
|
MiDbgUnTranslatePhysicalAddress (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine unmaps the virtual address currently mapped by the debug PTE.
|
|
|
|
This is needed so that stale PTE mappings are not left in the debug PTE
|
|
as if the page attribute subsequently changes, a stale mapping would
|
|
cause TB incoherency.
|
|
|
|
This can only be called if the previous MiDbgTranslatePhysicalAddress
|
|
succeeded.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Environment:
|
|
|
|
Kernel mode IRQL at DISPATCH_LEVEL or greater.
|
|
|
|
--*/
|
|
|
|
{
|
|
PVOID BaseAddress;
|
|
|
|
BaseAddress = MiGetVirtualAddressMappedByPte (MmDebugPte);
|
|
|
|
ASSERT (MmIsAddressValid (BaseAddress));
|
|
|
|
InterlockedExchangePte (MmDebugPte, ZeroPte.u.Long);
|
|
|
|
KiFlushSingleTb (BaseAddress);
|
|
|
|
return;
|
|
}
|
|
|
|
NTSTATUS
|
|
MmDbgCopyMemory (
|
|
IN ULONG64 UntrustedAddress,
|
|
IN PVOID Buffer,
|
|
IN ULONG Size,
|
|
IN ULONG Flags
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Transfers a single chunk of memory between a buffer and a system
|
|
address. The transfer can be a read or write with a virtual or
|
|
physical address.
|
|
|
|
The chunk size must be 1, 2, 4 or 8 bytes and the address
|
|
must be appropriately aligned for the size.
|
|
|
|
Arguments:
|
|
|
|
UntrustedAddress - Supplies the system address being read from or written
|
|
into. The address is translated appropriately and
|
|
validated before being used. This address must not
|
|
cross a page boundary.
|
|
|
|
Buffer - Supplies the buffer to read into or write from. It is the caller's
|
|
responsibility to ensure this buffer address is nonpaged and valid
|
|
(ie: will not generate any faults including access bit faults)
|
|
throughout the duration of this call. This routine (not the
|
|
caller) will handle copying into this buffer as the buffer
|
|
address may not be aligned properly for the requested transfer.
|
|
|
|
Typically this buffer points to a kd circular buffer or an
|
|
ExLockUserBuffer'd address. Note this buffer can cross page
|
|
boundaries.
|
|
|
|
Size - Supplies the size of the transfer. This may be 1, 2, 4 or 8 bytes.
|
|
|
|
Flags -
|
|
|
|
MMDBG_COPY_WRITE - Write from the buffer to the address.
|
|
If this is not set a read is done.
|
|
|
|
MMDBG_COPY_PHYSICAL - The address is a physical address and by default
|
|
a PTE with a cached attribute will be used to
|
|
map it to retrieve (or set) the specified data.
|
|
If this is not set the address is virtual.
|
|
|
|
MMDBG_COPY_UNSAFE - No locks are taken during operation. It
|
|
is the caller's responsibility to ensure
|
|
stability of the system during the call.
|
|
|
|
MMDBG_COPY_CACHED - If MMDBG_COPY_PHYSICAL is specified, then use
|
|
a PTE with the cached attribute for the mapping
|
|
to ensure TB coherence.
|
|
|
|
MMDBG_COPY_UNCACHED - If MMDBG_COPY_PHYSICAL is specified, then use
|
|
a PTE with the uncached attribute for the mapping
|
|
to ensure TB coherence.
|
|
|
|
MMDBG_COPY_WRITE_COMBINED - If MMDBG_COPY_PHYSICAL is specified, then
|
|
use a PTE with the writecombined attribute
|
|
for the mapping to ensure TB coherence.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS.
|
|
|
|
--*/
|
|
|
|
{
|
|
LOGICAL ForceWritableIfPossible;
|
|
PMMSUPPORT Ws;
|
|
ULONG i;
|
|
KIRQL OldIrql;
|
|
KIRQL PfnIrql;
|
|
PVOID VirtualAddress;
|
|
HARDWARE_PTE Opaque;
|
|
CHAR TempBuffer[8];
|
|
PCHAR SourceBuffer;
|
|
PCHAR TargetBuffer;
|
|
PHYSICAL_ADDRESS PhysicalAddress;
|
|
PETHREAD Thread;
|
|
LOGICAL PfnHeld;
|
|
LOGICAL IoHeld;
|
|
|
|
switch (Size) {
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
break;
|
|
case 4:
|
|
break;
|
|
case 8:
|
|
break;
|
|
default:
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
}
|
|
|
|
if (UntrustedAddress & (Size - 1)) {
|
|
|
|
//
|
|
// The untrusted address is not properly aligned with the requested
|
|
// transfer size. This is a caller error.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
}
|
|
|
|
if (((ULONG)UntrustedAddress & ~(Size - 1)) !=
|
|
(((ULONG)UntrustedAddress + Size - 1) & ~(Size - 1))) {
|
|
|
|
//
|
|
// The range spanned by the untrusted address crosses a page boundary.
|
|
// Straddling pages is not allowed. This is a caller error.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER_3;
|
|
}
|
|
|
|
PfnHeld = FALSE;
|
|
IoHeld = FALSE;
|
|
Ws = NULL;
|
|
|
|
//
|
|
// Initializing PfnIrql and OldIrql are not needed for
|
|
// correctness but without it the compiler cannot compile this code
|
|
// W4 to check for use of uninitialized variables.
|
|
//
|
|
|
|
PfnIrql = PASSIVE_LEVEL;
|
|
OldIrql = PASSIVE_LEVEL;
|
|
ForceWritableIfPossible = TRUE;
|
|
|
|
if ((Flags & MMDBG_COPY_PHYSICAL) == 0) {
|
|
|
|
//
|
|
// If the caller has not frozen the machine (ie: this is localkd or the
|
|
// equivalent), then acquire the PFN lock. This keeps the address
|
|
// valid even after the return from the MmIsAddressValid call. Note
|
|
// that for system (or session) addresses, the relevant working set
|
|
// mutex is acquired to prevent the page from getting trimmed or the
|
|
// PTE access bit from getting cleared. For user space addresses,
|
|
// no mutex is needed because the access is performed using the user
|
|
// virtual address inside an exception handler.
|
|
//
|
|
|
|
if ((Flags & MMDBG_COPY_UNSAFE) == 0) {
|
|
|
|
if (KeGetCurrentIrql () > APC_LEVEL) {
|
|
return STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
|
|
//
|
|
// Note that for safe copy mode (ie: the system is live), the
|
|
// address must not be made writable if it is not already because
|
|
// other threads might concurrently access it this way and losing
|
|
// copy-on-write semantics, etc would be very bad.
|
|
//
|
|
|
|
ForceWritableIfPossible = FALSE;
|
|
|
|
if ((PVOID) (ULONG_PTR) UntrustedAddress >= MmSystemRangeStart) {
|
|
|
|
Thread = PsGetCurrentThread ();
|
|
|
|
if (MmIsSessionAddress ((PVOID)(ULONG_PTR)UntrustedAddress)) {
|
|
|
|
PEPROCESS Process;
|
|
|
|
Process = PsGetCurrentProcessByThread (Thread);
|
|
|
|
if ((Process->Vm.Flags.SessionLeader == 1) ||
|
|
(Process->Session == NULL)) {
|
|
|
|
//
|
|
// smss may transiently have a session space but
|
|
// that's of no interest to our caller. The system
|
|
// and idle process never have a session at all.
|
|
//
|
|
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
Ws = &MmSessionSpace->GlobalVirtualAddress->Vm;
|
|
}
|
|
else {
|
|
Ws = &MmSystemCacheWs;
|
|
}
|
|
|
|
if (KeGetOwnerGuardedMutex (&Ws->WorkingSetMutex) == KeGetCurrentThread ()) {
|
|
return STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
|
|
PfnHeld = TRUE;
|
|
|
|
LOCK_WORKING_SET (Ws);
|
|
|
|
LOCK_PFN (PfnIrql);
|
|
}
|
|
else {
|
|
|
|
//
|
|
// The caller specified a user address.
|
|
//
|
|
|
|
if (MI_WS_OWNER (PsGetCurrentProcess ())) {
|
|
return STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
|
|
//
|
|
// Probe and access the address carefully inside an
|
|
// exception handler.
|
|
//
|
|
|
|
try {
|
|
if (Flags & MMDBG_COPY_WRITE) {
|
|
ProbeForWrite ((PVOID)(ULONG_PTR)UntrustedAddress, Size, Size);
|
|
}
|
|
else {
|
|
ProbeForRead ((PVOID)(ULONG_PTR)UntrustedAddress, Size, Size);
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
return GetExceptionCode ();
|
|
}
|
|
|
|
VirtualAddress = (PVOID) (ULONG_PTR) UntrustedAddress;
|
|
|
|
if (Flags & MMDBG_COPY_WRITE) {
|
|
goto WriteData;
|
|
}
|
|
else {
|
|
goto ReadData;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MmIsAddressValid ((PVOID) (ULONG_PTR) UntrustedAddress) == FALSE) {
|
|
|
|
if (PfnHeld == TRUE) {
|
|
UNLOCK_PFN (PfnIrql);
|
|
}
|
|
if (Ws != NULL) {
|
|
UNLOCK_WORKING_SET (Ws);
|
|
}
|
|
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
#if defined(_IA64_) && defined (_MIALT4K_)
|
|
|
|
//
|
|
// Split PTEs (for emulated processes) may generate a fault,
|
|
// depending on their access bits, the ALTPTEs, etc.
|
|
//
|
|
// They share the same encoding as large pages so make sure it's
|
|
// really a PTE before checking the bit.
|
|
//
|
|
// Also make sure that it even has a PTE (large pages don't) !
|
|
//
|
|
|
|
if (((PVOID) (ULONG_PTR) UntrustedAddress < MmSystemRangeStart) &&
|
|
(Flags & MMDBG_COPY_UNSAFE) &&
|
|
(PsGetCurrentProcess()->Wow64Process != NULL) &&
|
|
(!MI_PDE_MAPS_LARGE_PAGE (MiGetPdeAddress ((PVOID)UntrustedAddress))) &&
|
|
(MI_PDE_MAPS_LARGE_PAGE (MiGetPteAddress ((PVOID)UntrustedAddress))) &&
|
|
(MiGetPteAddress ((PVOID)UntrustedAddress)->u.Hard.Cache == MM_PTE_CACHE_RESERVED) &&
|
|
((KeGetCurrentIrql () > APC_LEVEL) ||
|
|
(KeGetCurrentThread () == KeGetOwnerGuardedMutex (&PsGetCurrentProcess()->Wow64Process->AlternateTableLock)) ||
|
|
(MI_WS_OWNER (PsGetCurrentProcess ())))) {
|
|
|
|
if (PfnHeld == TRUE) {
|
|
UNLOCK_PFN (PfnIrql);
|
|
}
|
|
if (Ws != NULL) {
|
|
UNLOCK_WORKING_SET (Ws);
|
|
}
|
|
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
#endif
|
|
|
|
VirtualAddress = (PVOID) (ULONG_PTR) UntrustedAddress;
|
|
}
|
|
else {
|
|
|
|
PhysicalAddress.QuadPart = UntrustedAddress;
|
|
|
|
//
|
|
// If the caller has not frozen the machine (ie: this is localkd or the
|
|
// equivalent), then acquire the PFN lock. This prevents
|
|
// MmPhysicalMemoryBlock from changing inside the debug PTE routines
|
|
// and also blocks APCs so malicious callers cannot suspend us
|
|
// while we hold the debug PTE.
|
|
//
|
|
|
|
if ((Flags & MMDBG_COPY_UNSAFE) == 0) {
|
|
|
|
if (KeGetCurrentIrql () > APC_LEVEL) {
|
|
return STATUS_INVALID_PARAMETER_4;
|
|
}
|
|
|
|
IoHeld = TRUE;
|
|
PfnHeld = TRUE;
|
|
|
|
ExAcquireSpinLock (&MmIoTrackerLock, &OldIrql);
|
|
|
|
LOCK_PFN (PfnIrql);
|
|
}
|
|
|
|
VirtualAddress = (PVOID) (ULONG_PTR) MiDbgTranslatePhysicalAddress (PhysicalAddress, Flags);
|
|
|
|
if (VirtualAddress == NULL) {
|
|
if (PfnHeld == TRUE) {
|
|
UNLOCK_PFN (PfnIrql);
|
|
}
|
|
if (IoHeld == TRUE) {
|
|
ExReleaseSpinLock (&MmIoTrackerLock, OldIrql);
|
|
}
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
if (Flags & MMDBG_COPY_WRITE) {
|
|
VirtualAddress = MiDbgWriteCheck (VirtualAddress, &Opaque, ForceWritableIfPossible);
|
|
|
|
if (VirtualAddress == NULL) {
|
|
if (PfnHeld == TRUE) {
|
|
UNLOCK_PFN (PfnIrql);
|
|
}
|
|
if (IoHeld == TRUE) {
|
|
ExReleaseSpinLock (&MmIoTrackerLock, OldIrql);
|
|
}
|
|
if (Ws != NULL) {
|
|
UNLOCK_WORKING_SET (Ws);
|
|
}
|
|
return STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
WriteData:
|
|
|
|
//
|
|
// Carefully capture the source buffer into a local *aligned* buffer
|
|
// as the write to the target must be done using the desired operation
|
|
// size specified by the caller. This is because the target may be
|
|
// a memory mapped device which requires specific transfer sizes.
|
|
//
|
|
|
|
SourceBuffer = (PCHAR) Buffer;
|
|
|
|
try {
|
|
for (i = 0; i < Size; i += 1) {
|
|
TempBuffer[i] = *SourceBuffer;
|
|
SourceBuffer += 1;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
ASSERT (Ws == NULL);
|
|
ASSERT (PfnHeld == FALSE);
|
|
ASSERT (IoHeld == FALSE);
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
switch (Size) {
|
|
case 1:
|
|
*(PCHAR) VirtualAddress = *(PCHAR) TempBuffer;
|
|
break;
|
|
case 2:
|
|
*(PSHORT) VirtualAddress = *(PSHORT) TempBuffer;
|
|
break;
|
|
case 4:
|
|
*(PULONG) VirtualAddress = *(PULONG) TempBuffer;
|
|
break;
|
|
case 8:
|
|
*(PULONGLONG) VirtualAddress = *(PULONGLONG) TempBuffer;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((PVOID) (ULONG_PTR) UntrustedAddress >= MmSystemRangeStart) {
|
|
MiDbgReleaseAddress (VirtualAddress, &Opaque);
|
|
}
|
|
}
|
|
else {
|
|
|
|
ReadData:
|
|
|
|
try {
|
|
switch (Size) {
|
|
case 1:
|
|
*(PCHAR) TempBuffer = *(PCHAR) VirtualAddress;
|
|
break;
|
|
case 2:
|
|
*(PSHORT) TempBuffer = *(PSHORT) VirtualAddress;
|
|
break;
|
|
case 4:
|
|
*(PULONG) TempBuffer = *(PULONG) VirtualAddress;
|
|
break;
|
|
case 8:
|
|
*(PULONGLONG) TempBuffer = *(PULONGLONG) VirtualAddress;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} except(EXCEPTION_EXECUTE_HANDLER) {
|
|
ASSERT (Ws == NULL);
|
|
ASSERT (PfnHeld == FALSE);
|
|
ASSERT (IoHeld == FALSE);
|
|
return GetExceptionCode();
|
|
}
|
|
|
|
//
|
|
// The buffer to fill may not be aligned so do it one character at
|
|
// a time.
|
|
//
|
|
|
|
TargetBuffer = (PCHAR) Buffer;
|
|
|
|
for (i = 0; i < Size; i += 1) {
|
|
*TargetBuffer = TempBuffer[i];
|
|
TargetBuffer += 1;
|
|
}
|
|
}
|
|
|
|
if (Flags & MMDBG_COPY_PHYSICAL) {
|
|
MiDbgUnTranslatePhysicalAddress ();
|
|
}
|
|
|
|
if (PfnHeld == TRUE) {
|
|
UNLOCK_PFN (PfnIrql);
|
|
}
|
|
|
|
if (IoHeld == TRUE) {
|
|
ExReleaseSpinLock (&MmIoTrackerLock, OldIrql);
|
|
}
|
|
|
|
if (Ws != NULL) {
|
|
UNLOCK_WORKING_SET (Ws);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
LOGICAL
|
|
MmDbgIsLowMemOk (
|
|
IN PFN_NUMBER PageFrameIndex,
|
|
OUT PPFN_NUMBER NextPageFrameIndex,
|
|
IN OUT PULONG CorruptionOffset
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is a special function called only from the kernel debugger
|
|
to check that the physical memory below 4Gb removed with /NOLOWMEM
|
|
contains the expected fill patterns. If not, there is a high
|
|
probability that a driver which cannot handle physical addresses greater
|
|
than 32 bits corrupted the memory.
|
|
|
|
Arguments:
|
|
|
|
PageFrameIndex - Supplies the physical page number to check.
|
|
|
|
NextPageFrameIndex - Supplies the next physical page number the caller
|
|
should check or 0 if the search is complete.
|
|
|
|
CorruptionOffset - If corruption is found, the byte offset
|
|
of the corruption start is returned here.
|
|
|
|
Return Value:
|
|
|
|
TRUE if the page was removed and the fill pattern is correct, or
|
|
if the page was never removed. FALSE if corruption was detected
|
|
in the page.
|
|
|
|
Environment:
|
|
|
|
This routine is for use of the kernel debugger ONLY, specifically
|
|
the !chklowmem command.
|
|
|
|
The debugger's PTE will be repointed.
|
|
|
|
--*/
|
|
|
|
{
|
|
#if defined (_MI_MORE_THAN_4GB_)
|
|
|
|
PULONG Va;
|
|
ULONG Index;
|
|
PHYSICAL_ADDRESS Pa;
|
|
#if DBG
|
|
PMMPFN Pfn;
|
|
#endif
|
|
|
|
if (MiNoLowMemory == 0) {
|
|
*NextPageFrameIndex = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
if (MiLowMemoryBitMap == NULL) {
|
|
*NextPageFrameIndex = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
if (PageFrameIndex >= MiNoLowMemory - 1) {
|
|
*NextPageFrameIndex = 0;
|
|
}
|
|
else {
|
|
*NextPageFrameIndex = PageFrameIndex + 1;
|
|
}
|
|
|
|
//
|
|
// Verify that the page to be verified is one of the reclaimed
|
|
// pages.
|
|
//
|
|
|
|
if ((PageFrameIndex >= MiLowMemoryBitMap->SizeOfBitMap) ||
|
|
(RtlCheckBit (MiLowMemoryBitMap, PageFrameIndex) == 0)) {
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// At this point we have a low page that is not in active use.
|
|
// The fill pattern must match.
|
|
//
|
|
|
|
#if DBG
|
|
Pfn = MI_PFN_ELEMENT (PageFrameIndex);
|
|
ASSERT (Pfn->u4.PteFrame == MI_MAGIC_4GB_RECLAIM);
|
|
ASSERT (Pfn->u3.e1.PageLocation == ActiveAndValid);
|
|
#endif
|
|
|
|
//
|
|
// Map the physical page using the debug PTE so the
|
|
// fill pattern can be validated.
|
|
//
|
|
// The debugger cannot be using this virtual address on entry or exit.
|
|
//
|
|
|
|
Pa.QuadPart = ((ULONGLONG)PageFrameIndex) << PAGE_SHIFT;
|
|
|
|
Va = (PULONG) MiDbgTranslatePhysicalAddress (Pa, 0);
|
|
|
|
if (Va == NULL) {
|
|
return TRUE;
|
|
}
|
|
|
|
for (Index = 0; Index < PAGE_SIZE / sizeof(ULONG); Index += 1) {
|
|
|
|
if (*Va != (PageFrameIndex | MI_LOWMEM_MAGIC_BIT)) {
|
|
|
|
if (CorruptionOffset != NULL) {
|
|
*CorruptionOffset = Index * sizeof(ULONG);
|
|
}
|
|
|
|
MiDbgUnTranslatePhysicalAddress ();
|
|
return FALSE;
|
|
}
|
|
|
|
Va += 1;
|
|
}
|
|
MiDbgUnTranslatePhysicalAddress ();
|
|
#else
|
|
UNREFERENCED_PARAMETER (PageFrameIndex);
|
|
UNREFERENCED_PARAMETER (CorruptionOffset);
|
|
|
|
*NextPageFrameIndex = 0;
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|