windows-nt/Source/XPSP1/NT/base/ntos/mm/debugsup.c
2020-09-26 16:20:57 +08:00

948 lines
25 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
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;
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(NT_UP) || defined(_IA64_)
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);
*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 ((PHARDWARE_PTE)PointerPte, VirtualAddress, TRUE);
}
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;
*PointerPte = TempPte;
KeFillEntryTb ((PHARDWARE_PTE)PointerPte, VirtualAddress, TRUE);
}
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.
--*/
{
ULONG Hint;
MMPTE TempPte;
PVOID BaseAddress;
PFN_NUMBER PageFrameIndex;
PMMPFN Pfn1;
MMPTE OriginalPte;
//
// 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;
}
Hint = 0;
BaseAddress = MiGetVirtualAddressMappedByPte (MmDebugPte);
TempPte = ValidKernelPte;
PageFrameIndex = (PFN_NUMBER)(PhysicalAddress.QuadPart >> PAGE_SHIFT);
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
if (MiIsPhysicalMemoryAddress (PageFrameIndex, &Hint, FALSE) == TRUE) {
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 {
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.
//
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 (TRUE, 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));
#if defined (_WIN64)
InterlockedExchange64 ((PLONG64)MmDebugPte, ZeroPte.u.Long);
#elif defined(_X86PAE_)
KeInterlockedSwapPte ((PHARDWARE_PTE)MmDebugPte,
(PHARDWARE_PTE)&ZeroPte.u.Long);
#else
InterlockedExchange ((PLONG)MmDebugPte, ZeroPte.u.Long);
#endif
KiFlushSingleTb (TRUE, 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;
ULONG i;
KIRQL PfnIrql;
KIRQL OldIrql;
PVOID VirtualAddress;
HARDWARE_PTE Opaque;
CHAR TempBuffer[8];
PCHAR SourceBuffer;
PCHAR TargetBuffer;
PHYSICAL_ADDRESS PhysicalAddress;
PETHREAD Thread;
LOGICAL PfnHeld;
ULONG WsHeld;
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;
WsHeld = 0;
//
// Initializing OldIrql and PhysicalAddress 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;
PhysicalAddress.LowPart = 0;
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)) {
if (MmGetSessionId (PsGetCurrentProcess ()) == 0) {
return STATUS_INVALID_PARAMETER_1;
}
WsHeld = 1;
LOCK_SESSION_SPACE_WS (OldIrql, Thread);
}
else {
WsHeld = 2;
LOCK_SYSTEM_WS (OldIrql, Thread);
}
PfnHeld = TRUE;
LOCK_PFN (PfnIrql);
}
else {
//
// The caller specified a user address. 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 (WsHeld == 1) {
UNLOCK_SESSION_SPACE_WS (OldIrql);
}
else if (WsHeld == 2) {
UNLOCK_SYSTEM_WS (OldIrql);
}
return STATUS_INVALID_PARAMETER_1;
}
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;
}
PfnHeld = TRUE;
LOCK_PFN (PfnIrql);
}
VirtualAddress = (PVOID) (ULONG_PTR) MiDbgTranslatePhysicalAddress (PhysicalAddress, Flags);
if (VirtualAddress == NULL) {
if (PfnHeld == TRUE) {
UNLOCK_PFN (PfnIrql);
}
return STATUS_UNSUCCESSFUL;
}
}
if (Flags & MMDBG_COPY_WRITE) {
VirtualAddress = MiDbgWriteCheck (VirtualAddress, &Opaque, ForceWritableIfPossible);
if (VirtualAddress == NULL) {
if (PfnHeld == TRUE) {
UNLOCK_PFN (PfnIrql);
}
if (WsHeld == 1) {
UNLOCK_SESSION_SPACE_WS (OldIrql);
}
else if (WsHeld == 2) {
UNLOCK_SYSTEM_WS (OldIrql);
}
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 (WsHeld == 0);
ASSERT (PfnHeld == 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 (WsHeld == 0);
ASSERT (PfnHeld == 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 (WsHeld == 1) {
UNLOCK_SESSION_SPACE_WS (OldIrql);
}
else if (WsHeld == 2) {
UNLOCK_SYSTEM_WS (OldIrql);
}
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;
}