mirror of https://github.com/tongzx/nt5src
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.
1410 lines
35 KiB
1410 lines
35 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
physical.c
|
|
|
|
Abstract:
|
|
|
|
WinDbg Extension Api
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reverse sign extension of the value returned by GetExpression()
|
|
based on the assumption that no physical address may be bigger
|
|
than 0xfffffff00000000.
|
|
|
|
Arguments:
|
|
|
|
Val - points to the value to reverse sign extension
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
void
|
|
ReverseSignExtension(ULONG64* Val)
|
|
{
|
|
if ((*Val & 0xffffffff00000000) == 0xffffffff00000000)
|
|
{
|
|
*Val &= 0x00000000ffffffff;
|
|
}
|
|
}
|
|
|
|
|
|
DECLARE_API( chklowmem )
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Calls an Mm function that checks if the physical pages
|
|
below 4Gb have a required fill pattern for PAE systems
|
|
booted with /LOWMEM switch.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
UNREFERENCED_PARAMETER (args);
|
|
UNREFERENCED_PARAMETER (Client);
|
|
|
|
dprintf ("Checking the low 4GB of RAM for required fill pattern. \n");
|
|
dprintf ("Please wait (verification takes approx. 20s) ...\n");
|
|
|
|
Ioctl (IG_LOWMEM_CHECK, NULL, 0);
|
|
|
|
dprintf ("Lowmem check done.\n");
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////// !search
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Kernel variable modification functions.
|
|
//
|
|
|
|
ULONG
|
|
READ_ULONG (
|
|
ULONG64 Address
|
|
);
|
|
|
|
VOID
|
|
WRITE_ULONG (
|
|
ULONG64 Address,
|
|
ULONG Value
|
|
);
|
|
|
|
ULONG64
|
|
READ_PVOID (
|
|
ULONG64 Address
|
|
);
|
|
|
|
|
|
ULONG
|
|
READ_PHYSICAL_ULONG (
|
|
ULONG64 Address
|
|
);
|
|
|
|
ULONG64
|
|
READ_PHYSICAL_ULONG64 (
|
|
ULONG64 Address
|
|
);
|
|
|
|
|
|
ULONG64
|
|
SearchGetSystemMemoryDescriptor (
|
|
);
|
|
|
|
ULONG64
|
|
SearchConvertPageFrameToVa (
|
|
ULONG64 PageFrameIndex,
|
|
PULONG Flags,
|
|
PULONG64 PteAddress
|
|
);
|
|
|
|
#define SEARCH_VA_PROTOTYPE_ADDRESS 0x0001
|
|
#define SEARCH_VA_NORMAL_ADDRESS 0x0002
|
|
#define SEARCH_VA_LARGE_PAGE_ADDRESS 0x0004
|
|
#define SEARCH_VA_UNKNOWN_TYPE_ADDRESS 0x0008
|
|
|
|
//
|
|
// PAE independent functions from p_i386\pte.c
|
|
//
|
|
|
|
ULONG64
|
|
DbgGetPdeAddress(
|
|
IN ULONG64 VirtualAddress
|
|
);
|
|
|
|
ULONG64
|
|
DbgGetPteAddress(
|
|
IN ULONG64 VirtualAddress
|
|
);
|
|
|
|
#define BANG_SEARCH_HELP \
|
|
"\n\
|
|
!search ADDRESS [DELTA [START_PFN END_PFN]] \n\
|
|
\n\
|
|
Search the physical pages in range [START_PFN..END_PFN] \n\
|
|
for ULONG_PTRs with values in range ADDRESS+/-DELTA or values \n\
|
|
that differ in only one bit position from ADDRESS. \n\
|
|
\n\
|
|
The default value for DELTA is 0. For START/END_PFN the default \n\
|
|
values are lowest physical page and highest physical page. \n\
|
|
\n\
|
|
Examples: \n\
|
|
\n\
|
|
!search AABBCCDD 0A \n\
|
|
\n\
|
|
Search all physical memory for values in range AABBCCD3 - \n\
|
|
AABBCCE8 or with only one bit different than AABBCCDD. \n\
|
|
\n\
|
|
!search AABBCCDD 0A 13F 240 \n\
|
|
\n\
|
|
Search page frames in range 13F - 240 for values in range \n\
|
|
AABBCCD3 - AABBCCE8 or with only one bit different \n\
|
|
than AABBCCDD. \n\
|
|
\n\
|
|
By default only the first hit in the page is detected. If all \n\
|
|
hits within the page are needed the START_PFN and END_PFN \n\
|
|
must have the same value. \n\
|
|
\n\
|
|
Note that a search through the entire physical memory will find \n\
|
|
hits in the search engine structures. By doing a search with a \n\
|
|
completely different value it can be deduced what hits can be \n\
|
|
ignored. \n\n"
|
|
|
|
|
|
//
|
|
// Comment this to get verbose output.
|
|
//
|
|
// #define _INTERNAL_DEBUG_
|
|
//
|
|
|
|
|
|
DECLARE_API( search )
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine triggers a search within a given physical
|
|
memory range for a pointer. The hits are defined by
|
|
an interval (below and above the pointer value) and also
|
|
by a Hamming distance equal to one (only one bit different).
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
const ULONG SEARCH_SYMBOL_CHECK = 0xABCDDCBA;
|
|
|
|
ULONG64 ParamAddress;
|
|
ULONG64 ParamDelta;
|
|
ULONG64 ParamStart;
|
|
ULONG64 ParamEnd;
|
|
|
|
ULONG64 KdpSearchPageHits;
|
|
ULONG64 KdpSearchPageHitOffsets;
|
|
ULONG64 KdpSearchPageHitIndex;
|
|
|
|
ULONG64 KdpSearchCheckPoint;
|
|
ULONG64 KdpSearchInProgress;
|
|
|
|
ULONG64 KdpSearchStartPageFrame;
|
|
ULONG64 KdpSearchEndPageFrame;
|
|
|
|
ULONG64 KdpSearchAddressRangeStart;
|
|
ULONG64 KdpSearchAddressRangeEnd;
|
|
|
|
ULONG64 MmLowestPhysicalPage;
|
|
ULONG64 MmHighestPhysicalPage;
|
|
|
|
ULONG64 PageFrame;
|
|
ULONG64 StartPage;
|
|
ULONG64 EndPage;
|
|
ULONG64 RunStartPage;
|
|
ULONG64 RunEndPage;
|
|
ULONG RunIndex;
|
|
|
|
BOOLEAN RequestForInterrupt;
|
|
BOOLEAN RequestAllOffsets;
|
|
ULONG Hits;
|
|
ULONG Index;
|
|
ULONG64 PfnHit;
|
|
ULONG64 VaHit;
|
|
ULONG VaFlags;
|
|
ULONG PfnOffset;
|
|
ULONG64 AddressStart;
|
|
ULONG64 AddressEnd;
|
|
ULONG DefaultRange;
|
|
ULONG64 MemoryDescriptor;
|
|
ULONG64 PageCount, BasePage, NumberOfPages;
|
|
ULONG NumberOfRuns;
|
|
|
|
ULONG SizeOfPfnNumber = 0;
|
|
ULONG64 PteAddress;
|
|
BOOLEAN On64Bits;
|
|
|
|
UNREFERENCED_PARAMETER (Client);
|
|
|
|
switch (TargetMachine) {
|
|
case IMAGE_FILE_MACHINE_IA64:
|
|
case IMAGE_FILE_MACHINE_AMD64:
|
|
On64Bits = TRUE;
|
|
break;
|
|
default:
|
|
On64Bits = FALSE;
|
|
break;
|
|
}
|
|
|
|
SizeOfPfnNumber = GetTypeSize("nt!PFN_NUMBER");
|
|
|
|
if (SizeOfPfnNumber == 0) {
|
|
dprintf ("Search: cannot get size of PFN_NUMBER \n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
RequestForInterrupt = FALSE;
|
|
RequestAllOffsets = FALSE;
|
|
DefaultRange = 128;
|
|
|
|
ParamAddress = 0;
|
|
ParamDelta = 0;
|
|
ParamStart = 0;
|
|
ParamEnd = 0;
|
|
|
|
//
|
|
// Help requested ?
|
|
//
|
|
|
|
if (strstr (args, "?") != 0) {
|
|
|
|
dprintf (BANG_SEARCH_HELP);
|
|
return S_OK;
|
|
|
|
}
|
|
|
|
//
|
|
// Get command line arguments.
|
|
//
|
|
|
|
{
|
|
PCHAR Current = (PCHAR)args;
|
|
CHAR Buffer [64];
|
|
ULONG Index;
|
|
ULONG BufferIndex;
|
|
|
|
//
|
|
// Get the 4 numeric arguments.
|
|
//
|
|
|
|
for (Index = 0; Index < 4; Index++) {
|
|
|
|
//
|
|
// Get rid of any leading spaces.
|
|
//
|
|
|
|
while (*Current == ' ' || *Current == '\t') {
|
|
Current++;
|
|
}
|
|
|
|
if (*Current == 0) {
|
|
|
|
if (Index == 0) {
|
|
|
|
dprintf (BANG_SEARCH_HELP);
|
|
return E_INVALIDARG;
|
|
}
|
|
else {
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Get the digits from the Index-th parameter.
|
|
//
|
|
|
|
Buffer [0] = '0';
|
|
Buffer [1] = 'x';
|
|
BufferIndex = 2;
|
|
|
|
while ((*Current >= '0' && *Current <= '9')
|
|
|| (*Current >= 'a' && *Current <= 'f')
|
|
|| (*Current >= 'A' && *Current <= 'F')) {
|
|
|
|
Buffer[BufferIndex] = *Current;
|
|
Buffer[BufferIndex + 1] = 0;
|
|
|
|
Current += 1;
|
|
BufferIndex += 1;
|
|
}
|
|
|
|
switch (Index) {
|
|
|
|
case 0: ParamAddress = GetExpression(Buffer); break;
|
|
case 1: ParamDelta = GetExpression(Buffer); break;
|
|
case 2: ParamStart = GetExpression(Buffer); break;
|
|
case 3: ParamEnd = GetExpression(Buffer); break;
|
|
|
|
default:
|
|
dprintf (BANG_SEARCH_HELP);
|
|
return E_INVALIDARG;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Verify that we have the right symbols.
|
|
//
|
|
|
|
KdpSearchCheckPoint = GetExpression ("nt!KdpSearchCheckPoint");
|
|
|
|
if (KdpSearchCheckPoint == 0
|
|
|| READ_ULONG (KdpSearchCheckPoint) != SEARCH_SYMBOL_CHECK) {
|
|
|
|
dprintf ("Search error: Incorrect symbols for kernel\n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
//
|
|
// Get all symbol values so that we can manipulate only addresses
|
|
// from now on.
|
|
//
|
|
|
|
KdpSearchPageHits = GetExpression ("nt!KdpSearchPageHits");
|
|
KdpSearchPageHitOffsets = GetExpression ("nt!KdpSearchPageHitOffsets");
|
|
KdpSearchPageHitIndex = GetExpression ("nt!KdpSearchPageHitIndex");
|
|
KdpSearchInProgress = GetExpression ("nt!KdpSearchInProgress");
|
|
KdpSearchStartPageFrame = GetExpression ("nt!KdpSearchStartPageFrame");
|
|
KdpSearchEndPageFrame = GetExpression ("nt!KdpSearchEndPageFrame");
|
|
KdpSearchAddressRangeStart = GetExpression ("nt!KdpSearchAddressRangeStart");
|
|
KdpSearchAddressRangeEnd = GetExpression ("nt!KdpSearchAddressRangeEnd");
|
|
|
|
//
|
|
// Perform some sanity checks on the values.
|
|
//
|
|
|
|
if (READ_ULONG (KdpSearchInProgress) != 0) {
|
|
dprintf ("Search error: Inconsistent value for nt!KdpSearchInProgress \n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
//
|
|
// Reset the search engine
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
WRITE_ULONG (KdpSearchInProgress, 1);
|
|
|
|
//
|
|
// Read physical memory limits.
|
|
//
|
|
|
|
MmLowestPhysicalPage = GetExpression ("nt!MmLowestPhysicalPage");
|
|
MmHighestPhysicalPage = GetExpression ("nt!MmHighestPhysicalPage");
|
|
|
|
#ifdef _INTERNAL_DEBUG_
|
|
|
|
dprintf ("Low: %I64X, High: %I64X \n",
|
|
READ_PVOID (MmLowestPhysicalPage),
|
|
READ_PVOID (MmHighestPhysicalPage));
|
|
|
|
|
|
#endif // #ifdef _INTERNAL_DEBUG_
|
|
|
|
//
|
|
// Figure out proper search parameters.
|
|
//
|
|
|
|
if (ParamStart == 0) {
|
|
StartPage = READ_PVOID (MmLowestPhysicalPage);
|
|
ParamStart = StartPage;
|
|
}
|
|
else {
|
|
StartPage = ParamStart;
|
|
}
|
|
|
|
if (ParamEnd == 0) {
|
|
EndPage = READ_PVOID (MmHighestPhysicalPage);
|
|
ParamEnd = EndPage;
|
|
}
|
|
else {
|
|
EndPage = ParamEnd;
|
|
}
|
|
|
|
//
|
|
// Set range of addresses that we want to be searched.
|
|
//
|
|
|
|
AddressStart = ParamAddress - ParamDelta;
|
|
AddressEnd = ParamAddress + ParamDelta;
|
|
|
|
WritePointer (KdpSearchAddressRangeStart, AddressStart);
|
|
WritePointer (KdpSearchAddressRangeEnd, AddressEnd);
|
|
|
|
if (SizeOfPfnNumber == 8) {
|
|
|
|
dprintf ("Searching PFNs in range %016I64X - %016I64X for [%016I64X - %016I64X]\n\n",
|
|
StartPage, EndPage, AddressStart, AddressEnd);
|
|
dprintf ("%-16s %-8s %-16s %-16s %-16s \n", "Pfn","Offset", "Hit", "Va", "Pte");
|
|
dprintf ("- - - - - - - - - - - - - - - - - - - - - - ");
|
|
dprintf ("- - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
|
|
}
|
|
else {
|
|
|
|
dprintf ("Searching PFNs in range %08I64X - %08I64X for [%08I64X - %08I64X]\n\n",
|
|
StartPage, EndPage, AddressStart, AddressEnd);
|
|
dprintf ("%-8s %-8s %-8s %-8s %-8s \n", "Pfn","Offset", "Hit", "Va", "Pte");
|
|
dprintf ("- - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
|
|
}
|
|
|
|
//
|
|
// Get system memory description to figure out what ranges
|
|
// should we skip. This is important for sparse PFN database
|
|
// and for pages managed by drivers.
|
|
//
|
|
|
|
MemoryDescriptor = SearchGetSystemMemoryDescriptor ();
|
|
|
|
if (MemoryDescriptor == 0) {
|
|
dprintf ("Search error: cannot allocate system memory descriptor \n");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
//
|
|
// Search all physical memory in the specified range.
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
|
|
if (StartPage == EndPage) {
|
|
|
|
EndPage += 1;
|
|
RequestAllOffsets = TRUE;
|
|
}
|
|
|
|
//
|
|
// Find out what pages are physically available create
|
|
// page search ranges based on that.
|
|
//
|
|
// SilviuC: I should use ReadField to read all these structures
|
|
// so that I do not have to take into account padding myself.
|
|
//
|
|
|
|
NumberOfRuns = READ_ULONG (MemoryDescriptor);
|
|
NumberOfPages = READ_PVOID (MemoryDescriptor + SizeOfPfnNumber);
|
|
|
|
#ifdef _INTERNAL_DEBUG_
|
|
|
|
dprintf ("Runs: %x, Pages: %I64X \n", NumberOfRuns, NumberOfPages);
|
|
|
|
for (RunIndex = 0; RunIndex < NumberOfRuns; RunIndex += 1) {
|
|
|
|
ULONG64 RunAddress;
|
|
|
|
RunAddress = MemoryDescriptor + 2 * SizeOfPfnNumber
|
|
+ RunIndex * GetTypeSize("nt!_PHYSICAL_MEMORY_RUN");
|
|
|
|
BasePage = READ_PVOID (RunAddress);
|
|
PageCount = READ_PVOID (RunAddress + SizeOfPfnNumber);
|
|
|
|
dprintf ("Run[%d]: Base: %I64X, Count: %I64X \n",
|
|
RunIndex, BasePage, PageCount);
|
|
}
|
|
#endif // #if _INTERNAL_DEBUG_
|
|
|
|
#ifdef _INTERNAL_DEBUG_
|
|
dprintf ("StartPage: %I64X, EndPage: %I64X \n", StartPage, EndPage);
|
|
#endif // #ifdef _INTERNAL_DEBUG_
|
|
|
|
for (PageFrame = StartPage; PageFrame < EndPage; PageFrame += DefaultRange) {
|
|
|
|
for (RunIndex = 0; RunIndex < NumberOfRuns; RunIndex += 1) {
|
|
|
|
//
|
|
// BaseAddress and PageCount for current memory run.
|
|
//
|
|
|
|
ULONG64 RunAddress;
|
|
|
|
#ifdef _INTERNAL_DEBUG_
|
|
// dprintf ("Finding a good range ... \n");
|
|
#endif // #ifdef _INTERNAL_DEBUG_
|
|
|
|
RunAddress = MemoryDescriptor + 2 * SizeOfPfnNumber
|
|
+ RunIndex * GetTypeSize("nt!_PHYSICAL_MEMORY_RUN");
|
|
|
|
BasePage = READ_PVOID (RunAddress);
|
|
PageCount = READ_PVOID (RunAddress + SizeOfPfnNumber);
|
|
|
|
//
|
|
// Figure out real start and end page.
|
|
//
|
|
|
|
RunStartPage = PageFrame;
|
|
RunEndPage = PageFrame + DefaultRange;
|
|
|
|
if (RunEndPage < BasePage) {
|
|
continue;
|
|
}
|
|
|
|
if (RunStartPage >= BasePage + PageCount) {
|
|
continue;
|
|
}
|
|
|
|
if (RunStartPage < BasePage) {
|
|
RunStartPage = BasePage;
|
|
}
|
|
|
|
if (RunEndPage > BasePage + PageCount) {
|
|
RunEndPage = BasePage + PageCount;
|
|
}
|
|
|
|
WritePointer (KdpSearchStartPageFrame, RunStartPage);
|
|
|
|
if (RequestAllOffsets) {
|
|
|
|
//
|
|
// If the search is in only one page then we
|
|
// will try to get all offsets with a hit.
|
|
//
|
|
|
|
WritePointer (KdpSearchEndPageFrame, RunStartPage);
|
|
}
|
|
else {
|
|
|
|
WritePointer (KdpSearchEndPageFrame, RunEndPage);
|
|
}
|
|
|
|
#ifdef _INTERNAL_DEBUG_
|
|
dprintf ("Start: %I64X, End: %I64X \n",
|
|
READ_PVOID(KdpSearchStartPageFrame),
|
|
READ_PVOID(KdpSearchEndPageFrame));
|
|
#endif // #if _INTERNAL_DEBUG_
|
|
|
|
//
|
|
// Reset search index
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
|
|
//
|
|
// Invalidate kd cache
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHits, 0);
|
|
WRITE_ULONG (KdpSearchPageHitOffsets, 0);
|
|
|
|
//
|
|
// This is the trigger for memory search. We piggy back on the same
|
|
// code as for !chklowmem and the logic in kernel detects what
|
|
// we really want to do.
|
|
//
|
|
|
|
Ioctl (IG_LOWMEM_CHECK, NULL, 0);
|
|
|
|
//
|
|
// Display results
|
|
//
|
|
|
|
Hits = READ_ULONG (KdpSearchPageHitIndex);
|
|
|
|
for (Index = 0; Index < Hits; Index++) {
|
|
|
|
PCHAR VaString = "";
|
|
|
|
VaFlags = 0;
|
|
|
|
PfnHit = READ_PVOID (KdpSearchPageHits + Index * SizeOfPfnNumber);
|
|
PfnOffset = READ_ULONG (KdpSearchPageHitOffsets + Index * sizeof (ULONG));
|
|
VaHit = SearchConvertPageFrameToVa (PfnHit, &VaFlags, &PteAddress);
|
|
|
|
// dprintf ("Hits: %u, Index: %u, Va: %I64X \n", Hits, Index, VaHit);
|
|
|
|
PfnOffset &= (ULONG)0xFFFF;
|
|
|
|
#if DBG
|
|
if ((VaFlags & SEARCH_VA_NORMAL_ADDRESS)) {
|
|
VaString = ""; // "normal";
|
|
}
|
|
else if ((VaFlags & SEARCH_VA_LARGE_PAGE_ADDRESS)) {
|
|
VaString = "large page";
|
|
}
|
|
else if ((VaFlags & SEARCH_VA_PROTOTYPE_ADDRESS)) {
|
|
VaString = "prototype";
|
|
}
|
|
else if ((VaFlags & SEARCH_VA_UNKNOWN_TYPE_ADDRESS)) {
|
|
VaString = "unknown";
|
|
}
|
|
#endif // #if DBG
|
|
|
|
if (SizeOfPfnNumber == 8) {
|
|
|
|
dprintf ("%016I64X %08X %016I64X %016I64X %016I64X %s\n",
|
|
PfnHit,
|
|
PfnOffset,
|
|
READ_PHYSICAL_ULONG64 (PfnHit * PageSize + PfnOffset),
|
|
(VaHit == 0 ? 0 : VaHit + PfnOffset),
|
|
PteAddress,
|
|
VaString);
|
|
}
|
|
else {
|
|
|
|
VaHit &= (ULONG64)0xFFFFFFFF;
|
|
PteAddress &= (ULONG64)0xFFFFFFFF;
|
|
|
|
dprintf ("%08I64X %08X %08X %08I64X %08I64X %s\n",
|
|
PfnHit,
|
|
PfnOffset,
|
|
READ_PHYSICAL_ULONG (PfnHit * PageSize + PfnOffset),
|
|
(VaHit == 0 ? 0 : VaHit + PfnOffset),
|
|
PteAddress,
|
|
VaString);
|
|
}
|
|
}
|
|
|
|
//
|
|
// check for ctrl-c
|
|
//
|
|
|
|
if (CheckControlC()) {
|
|
|
|
dprintf ("Search interrupted. \n");
|
|
RequestForInterrupt = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RequestForInterrupt) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Reset the search engine state
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchInProgress, 0);
|
|
|
|
if (RequestForInterrupt) {
|
|
|
|
return E_INVALIDARG;
|
|
}
|
|
else {
|
|
|
|
dprintf ("Search done.\n");
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
ULONG64
|
|
SearchGetSystemMemoryDescriptor (
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
A malloc'd PHYSICAL_MEMORY_DESCRIPTOR structure.
|
|
Caller is responsible of freeing.
|
|
|
|
Environment:
|
|
|
|
Call triggered only from !search Kd extension.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG64 MemoryDescriptorAddress;
|
|
ULONG NumberOfRuns;
|
|
|
|
MemoryDescriptorAddress = READ_PVOID (GetExpression ("nt!MmPhysicalMemoryBlock"));
|
|
NumberOfRuns = READ_ULONG (MemoryDescriptorAddress);
|
|
|
|
if (NumberOfRuns == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return MemoryDescriptorAddress;
|
|
}
|
|
|
|
|
|
//
|
|
// SilviuC: this is copied from \ntos\mm headers.
|
|
// We only need it to figure out if a PFN has
|
|
// prototype ptes.
|
|
//
|
|
|
|
typedef struct _MMPFNENTRY {
|
|
ULONG Modified : 1;
|
|
ULONG ReadInProgress : 1;
|
|
ULONG WriteInProgress : 1;
|
|
ULONG PrototypePte: 1;
|
|
ULONG PageColor : 3;
|
|
ULONG ParityError : 1;
|
|
ULONG PageLocation : 3;
|
|
ULONG InPageError : 1;
|
|
ULONG VerifierAllocation : 1;
|
|
ULONG RemovalRequested : 1;
|
|
#if PFN_CONSISTENCY
|
|
ULONG PageTablePage : 1;
|
|
ULONG Reserved : 1;
|
|
#else
|
|
ULONG Reserved : 2;
|
|
#endif
|
|
ULONG DontUse : 16; //overlays USHORT for reference count field.
|
|
} MMPFNENTRY;
|
|
|
|
|
|
ULONG64
|
|
SearchConvertPageFrameToVa (
|
|
ULONG64 PageFrameIndex,
|
|
PULONG Flags,
|
|
PULONG64 PteAddress
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returnes the virtual address corresponding to a
|
|
PFN index if the reverse mapping is easy to figure out. For all
|
|
other cases (e.g. prototype PTE) the result is null.
|
|
|
|
Arguments:
|
|
|
|
PageFrameIndex - PFN index to convert.
|
|
|
|
Return Value:
|
|
|
|
The corresponding virtual address or null in case the PFN index
|
|
cannot be easily converted to a virtual address.
|
|
|
|
Environment:
|
|
|
|
Call triggered only from Kd extension.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG64 Va;
|
|
ULONG64 PfnAddress;
|
|
ULONG BytesRead;
|
|
MMPFNENTRY u3_e1;
|
|
|
|
//
|
|
// Get address of PFN structure
|
|
//
|
|
|
|
PfnAddress = READ_PVOID (GetExpression("nt!MmPfnDatabase"))
|
|
+ PageFrameIndex * GetTypeSize("nt!_MMPFN");
|
|
|
|
BytesRead = 0;
|
|
*Flags = 0;
|
|
|
|
InitTypeRead(PfnAddress, nt!_MMPFN);
|
|
|
|
//
|
|
// (SilviuC): should check if MI_IS_PFN_DELETED(Pfn) is on.
|
|
//
|
|
|
|
//
|
|
// Try to figure out Va if possible.
|
|
//
|
|
|
|
*PteAddress = ((ULONG64)ReadField (PteAddress));
|
|
GetFieldValue(PfnAddress, "nt!_MMPFN", "u3.e1", u3_e1);
|
|
|
|
if (u3_e1.PrototypePte) {
|
|
|
|
*Flags |= SEARCH_VA_PROTOTYPE_ADDRESS;
|
|
return 0;
|
|
}
|
|
|
|
Va = DbgGetVirtualAddressMappedByPte (*PteAddress);
|
|
|
|
*Flags |= SEARCH_VA_NORMAL_ADDRESS;
|
|
return Va;
|
|
}
|
|
|
|
//
|
|
// Read/write functions
|
|
//
|
|
|
|
ULONG
|
|
READ_ULONG (
|
|
ULONG64 Address
|
|
)
|
|
{
|
|
ULONG Value = 0;
|
|
ULONG BytesRead;
|
|
|
|
if (! ReadMemory (Address, &Value, sizeof Value, &BytesRead)) {
|
|
dprintf ("Search: READ_ULONG error \n");
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
|
|
VOID
|
|
WRITE_ULONG (
|
|
ULONG64 Address,
|
|
ULONG Value
|
|
)
|
|
{
|
|
ULONG BytesWritten;
|
|
|
|
if (! WriteMemory (Address, &Value, sizeof Value, &BytesWritten)) {
|
|
dprintf ("Search: WRITE_ULONG error \n");
|
|
}
|
|
}
|
|
|
|
ULONG64
|
|
READ_PVOID (
|
|
ULONG64 Address
|
|
)
|
|
{
|
|
ULONG64 Value64 = 0;
|
|
|
|
if (!ReadPointer(Address, &Value64)) {
|
|
dprintf ("Search: READ_PVOID error \n");
|
|
}
|
|
return Value64;
|
|
}
|
|
|
|
ULONG
|
|
READ_PHYSICAL_ULONG (
|
|
ULONG64 Address
|
|
)
|
|
{
|
|
ULONG Value = 0;
|
|
ULONG Bytes = 0;
|
|
|
|
ReadPhysical (Address, &Value, sizeof Value, &Bytes);
|
|
|
|
if (Bytes != sizeof Value) {
|
|
dprintf ("Search: READ_PHYSICAL_ULONG error \n");
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
|
|
|
|
ULONG64
|
|
READ_PHYSICAL_ULONG64 (
|
|
ULONG64 Address
|
|
)
|
|
{
|
|
ULONG64 Value = 0;
|
|
ULONG Bytes = 0;
|
|
|
|
ReadPhysical (Address, &Value, sizeof Value, &Bytes);
|
|
|
|
if (Bytes != sizeof Value) {
|
|
dprintf ("Search: READ_PHYSICAL_ULONG64 error \n");
|
|
}
|
|
|
|
return Value;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////// !searchpte
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
DECLARE_API( searchpte )
|
|
{
|
|
const ULONG SEARCH_SYMBOL_CHECK = 0xABCDDCBA;
|
|
|
|
ULONG64 ParamAddress;
|
|
ULONG64 ParamDelta;
|
|
ULONG64 ParamStart;
|
|
ULONG64 ParamEnd;
|
|
|
|
ULONG64 KdpSearchPageHits;
|
|
ULONG64 KdpSearchPageHitOffsets;
|
|
ULONG64 KdpSearchPageHitIndex;
|
|
ULONG64 KdpSearchInProgress;
|
|
ULONG64 KdpSearchStartPageFrame;
|
|
ULONG64 KdpSearchEndPageFrame;
|
|
|
|
ULONG64 KdpSearchAddressRangeStart;
|
|
ULONG64 KdpSearchAddressRangeEnd;
|
|
ULONG64 KdpSearchPfnValueAddress;
|
|
ULONG64 KdpSearchCheckPoint;
|
|
|
|
ULONG64 MmLowestPhysicalPage;
|
|
ULONG64 MmHighestPhysicalPage;
|
|
|
|
ULONG64 PageFrame;
|
|
ULONG64 StartPage;
|
|
ULONG64 EndPage;
|
|
ULONG64 RunStartPage;
|
|
ULONG64 RunEndPage;
|
|
ULONG RunIndex;
|
|
|
|
BOOLEAN RequestForInterrupt = FALSE;
|
|
ULONG Hits;
|
|
ULONG LastHits;
|
|
ULONG Index;
|
|
ULONG64 PfnHit;
|
|
ULONG64 VaHit;
|
|
ULONG VaFlags;
|
|
ULONG PfnOffset;
|
|
ULONG PfnValue;
|
|
ULONG64 AddressStart;
|
|
ULONG64 AddressEnd;
|
|
ULONG DefaultRange = 128;
|
|
ULONG64 MemoryDescriptor;
|
|
ULONG64 PageCount, BasePage, NumberOfPages;
|
|
ULONG NumberOfRuns;
|
|
|
|
ULONG SizeOfPfnNumber = 0;
|
|
ULONG64 PteAddress;
|
|
BOOLEAN On64Bits;
|
|
|
|
ULONG64 PfnSearchValue;
|
|
ULONG NumberOfHits = 0;
|
|
|
|
PULONG64 PfnHitsBuffer = NULL;
|
|
ULONG PfnHitsBufferIndex = 0;
|
|
ULONG PfnHitsBufferSize = 1024;
|
|
ULONG PfnIndex;
|
|
HRESULT Result;
|
|
|
|
switch (TargetMachine) {
|
|
case IMAGE_FILE_MACHINE_IA64:
|
|
case IMAGE_FILE_MACHINE_AMD64:
|
|
On64Bits = TRUE;
|
|
break;
|
|
default:
|
|
On64Bits = FALSE;
|
|
break;
|
|
}
|
|
|
|
SizeOfPfnNumber = GetTypeSize("nt!PFN_NUMBER");
|
|
|
|
if (SizeOfPfnNumber == 0) {
|
|
dprintf ("Search: cannot get size of PFN_NUMBER \n");
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
|
|
ParamAddress = 0;
|
|
|
|
//
|
|
// Help requested ?
|
|
//
|
|
|
|
if (strstr (args, "?") != 0) {
|
|
|
|
dprintf ("!searchpte FRAME(in hex) \n");
|
|
dprintf (" \n");
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// Get command line arguments.
|
|
//
|
|
|
|
sscanf (args, "%I64X", &ParamAddress);
|
|
|
|
//
|
|
// Verify that we have the right symbols.
|
|
//
|
|
|
|
KdpSearchCheckPoint = GetExpression ("nt!KdpSearchCheckPoint");
|
|
|
|
if (KdpSearchCheckPoint == 0
|
|
|| READ_ULONG (KdpSearchCheckPoint) != SEARCH_SYMBOL_CHECK) {
|
|
|
|
dprintf ("Search error: Incorrect symbols for kernel\n");
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Get all symbol values so that we can manipulate only addresses
|
|
// from now on.
|
|
//
|
|
|
|
KdpSearchPageHits = GetExpression ("nt!KdpSearchPageHits");
|
|
KdpSearchPageHitOffsets = GetExpression ("nt!KdpSearchPageHitOffsets");
|
|
KdpSearchPageHitIndex = GetExpression ("nt!KdpSearchPageHitIndex");
|
|
KdpSearchInProgress = GetExpression ("nt!KdpSearchInProgress");
|
|
KdpSearchStartPageFrame = GetExpression ("nt!KdpSearchStartPageFrame");
|
|
KdpSearchEndPageFrame = GetExpression ("nt!KdpSearchEndPageFrame");
|
|
KdpSearchAddressRangeStart = GetExpression ("nt!KdpSearchAddressRangeStart");
|
|
KdpSearchAddressRangeEnd = GetExpression ("nt!KdpSearchAddressRangeEnd");
|
|
KdpSearchPfnValueAddress = GetExpression ("nt!KdpSearchPfnValue");
|
|
|
|
//
|
|
// Perform some sanity checks on the values.
|
|
//
|
|
|
|
if (READ_ULONG (KdpSearchInProgress) != 0) {
|
|
dprintf ("Search error: Inconsistent value for nt!KdpSearchInProgress \n");
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Reset the search engine
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
WRITE_ULONG (KdpSearchInProgress, 1);
|
|
|
|
PfnSearchValue = ParamAddress;
|
|
|
|
{
|
|
ULONG BytesWritten = 0;
|
|
|
|
WriteMemory (KdpSearchPfnValueAddress,
|
|
&PfnSearchValue,
|
|
SizeOfPfnNumber,
|
|
&BytesWritten);
|
|
|
|
if (BytesWritten != SizeOfPfnNumber) {
|
|
dprintf ("Search error: failed to write nt!KdpSearchPfnValue \n");
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
dprintf ("Searching for PTEs containing PFN value %I64X ...\n", PfnSearchValue);
|
|
|
|
//
|
|
// Read physical memory limits.
|
|
//
|
|
|
|
MmLowestPhysicalPage = GetExpression ("nt!MmLowestPhysicalPage");
|
|
MmHighestPhysicalPage = GetExpression ("nt!MmHighestPhysicalPage");
|
|
|
|
//
|
|
// Figure out proper search parameters.
|
|
//
|
|
|
|
StartPage = READ_PVOID (MmLowestPhysicalPage);
|
|
ParamStart = StartPage;
|
|
|
|
EndPage = READ_PVOID (MmHighestPhysicalPage);
|
|
ParamEnd = EndPage;
|
|
|
|
//
|
|
// Set the range of addresses that we want searched.
|
|
//
|
|
|
|
AddressStart = PfnSearchValue;
|
|
AddressEnd = PfnSearchValue;
|
|
|
|
WritePointer (KdpSearchAddressRangeStart, PfnSearchValue);
|
|
WritePointer (KdpSearchAddressRangeEnd, PfnSearchValue);
|
|
|
|
if (SizeOfPfnNumber == 8) {
|
|
|
|
dprintf ("Searching PFNs in range %016I64X - %016I64X \n\n",
|
|
StartPage, EndPage);
|
|
dprintf ("%-16s %-8s %-16s %-16s %-16s \n", "Pfn","Offset", "Hit", "Va", "Pte");
|
|
dprintf ("- - - - - - - - - - - - - - - - - - - - - - ");
|
|
dprintf ("- - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
|
|
}
|
|
else {
|
|
|
|
dprintf ("Searching PFNs in range %08I64X - %08I64X \n\n",
|
|
StartPage, EndPage);
|
|
dprintf ("%-8s %-8s %-8s %-8s %-8s \n", "Pfn","Offset", "Hit", "Va", "Pte");
|
|
dprintf ("- - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
|
|
}
|
|
|
|
//
|
|
// Get system memory description to figure out what ranges
|
|
// should we skip. This is important for sparse PFN database
|
|
// and for pages managed by drivers.
|
|
//
|
|
|
|
MemoryDescriptor = SearchGetSystemMemoryDescriptor ();
|
|
|
|
if (MemoryDescriptor == 0) {
|
|
dprintf ("Search error: cannot allocate system memory descriptor \n");
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Search all physical memory in the specified range.
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
|
|
//
|
|
// Allocate hits buffer.
|
|
//
|
|
|
|
PfnHitsBuffer = (PULONG64) malloc (PfnHitsBufferSize * sizeof(ULONG64));
|
|
|
|
if (PfnHitsBuffer == NULL) {
|
|
dprintf ("Search error: cannot allocate hits buffer. \n");
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Find out what pages are physically available create
|
|
// page search ranges based on that.
|
|
//
|
|
// SilviuC: I should use ReadField to read all these structures
|
|
// so that I do not have to take into account padding myself.
|
|
//
|
|
|
|
NumberOfRuns = READ_ULONG (MemoryDescriptor);
|
|
NumberOfPages = READ_PVOID (MemoryDescriptor + SizeOfPfnNumber);
|
|
|
|
for (PageFrame = StartPage; PageFrame < EndPage; PageFrame += DefaultRange) {
|
|
|
|
for (RunIndex = 0; RunIndex < NumberOfRuns; RunIndex += 1) {
|
|
|
|
//
|
|
// BaseAddress and PageCount for current memory run.
|
|
//
|
|
|
|
ULONG64 RunAddress;
|
|
|
|
RunAddress = MemoryDescriptor + 2 * SizeOfPfnNumber
|
|
+ RunIndex * GetTypeSize("nt!_PHYSICAL_MEMORY_RUN");
|
|
|
|
BasePage = READ_PVOID (RunAddress);
|
|
PageCount = READ_PVOID (RunAddress + SizeOfPfnNumber);
|
|
|
|
//
|
|
// Figure out real start and end page.
|
|
//
|
|
|
|
RunStartPage = PageFrame;
|
|
RunEndPage = PageFrame + DefaultRange;
|
|
|
|
if (RunEndPage < BasePage) {
|
|
continue;
|
|
}
|
|
|
|
if (RunStartPage >= BasePage + PageCount) {
|
|
continue;
|
|
}
|
|
|
|
if (RunStartPage < BasePage) {
|
|
RunStartPage = BasePage;
|
|
}
|
|
|
|
if (RunEndPage > BasePage + PageCount) {
|
|
RunEndPage = BasePage + PageCount;
|
|
}
|
|
|
|
WritePointer (KdpSearchStartPageFrame, RunStartPage);
|
|
WritePointer (KdpSearchEndPageFrame, RunEndPage);
|
|
|
|
//
|
|
// Reset search index
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
|
|
//
|
|
// Invalidate kd cache
|
|
//
|
|
|
|
WRITE_ULONG (KdpSearchPageHits, 0);
|
|
WRITE_ULONG (KdpSearchPageHitOffsets, 0);
|
|
|
|
//
|
|
// This is the trigger for memory search. We piggy back on the same
|
|
// code as for !chklowmem and the logic in kernel detects what
|
|
// we really want to do.
|
|
//
|
|
|
|
Ioctl (IG_LOWMEM_CHECK, NULL, 0);
|
|
|
|
//
|
|
// Display results
|
|
//
|
|
|
|
Hits = READ_ULONG (KdpSearchPageHitIndex);
|
|
|
|
for (Index = 0; Index < Hits; Index++) {
|
|
|
|
NumberOfHits += 1;
|
|
|
|
dprintf (".");
|
|
|
|
//
|
|
// Add to hits buffer
|
|
//
|
|
|
|
PfnHit = READ_PVOID (KdpSearchPageHits + Index * SizeOfPfnNumber);
|
|
PfnHitsBuffer [PfnHitsBufferIndex] = PfnHit;
|
|
PfnHitsBufferIndex += 1;
|
|
|
|
if (PfnHitsBufferIndex >= PfnHitsBufferSize) {
|
|
PVOID NewBuffer;
|
|
|
|
PfnHitsBufferSize *= 2;
|
|
|
|
NewBuffer = realloc (PfnHitsBuffer,
|
|
PfnHitsBufferSize * sizeof(ULONG64));
|
|
|
|
if (NewBuffer == NULL) {
|
|
dprintf ("Search error: cannot reallocate hits buffer with size %u. \n",
|
|
PfnHitsBufferSize);
|
|
Result = E_INVALIDARG;
|
|
goto Exit;
|
|
}
|
|
|
|
PfnHitsBuffer = NewBuffer;
|
|
}
|
|
}
|
|
|
|
//
|
|
// check for ctrl-c
|
|
//
|
|
|
|
if (CheckControlC()) {
|
|
|
|
RequestForInterrupt = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (RequestForInterrupt) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now find all hits in all pages.
|
|
//
|
|
|
|
dprintf ("\n");
|
|
dprintf ("Found %u pages with hits. \n", PfnHitsBufferIndex);
|
|
dprintf ("Searching now for all hits in relevant pages ... \n");
|
|
|
|
NumberOfHits = 0;
|
|
|
|
for (PfnIndex = 0;
|
|
!RequestForInterrupt && PfnIndex < PfnHitsBufferIndex;
|
|
PfnIndex += 1) {
|
|
|
|
WRITE_ULONG (KdpSearchPageHitIndex, 0);
|
|
WRITE_ULONG (KdpSearchInProgress, 1);
|
|
|
|
WritePointer (KdpSearchAddressRangeStart, PfnSearchValue);
|
|
WritePointer (KdpSearchAddressRangeEnd, PfnSearchValue);
|
|
|
|
WritePointer (KdpSearchStartPageFrame, PfnHitsBuffer[PfnIndex]);
|
|
WritePointer (KdpSearchEndPageFrame, PfnHitsBuffer[PfnIndex]);
|
|
|
|
WRITE_ULONG (KdpSearchPageHits, 0);
|
|
WRITE_ULONG (KdpSearchPageHitOffsets, 0);
|
|
|
|
Ioctl (IG_LOWMEM_CHECK, NULL, 0);
|
|
|
|
Hits = READ_ULONG (KdpSearchPageHitIndex);
|
|
|
|
for (Index = 0; Index < Hits; Index++) {
|
|
|
|
NumberOfHits += 1;
|
|
|
|
PfnHit = READ_PVOID (KdpSearchPageHits + Index * SizeOfPfnNumber);
|
|
PfnOffset = READ_ULONG (KdpSearchPageHitOffsets + Index * sizeof (ULONG));
|
|
VaHit = SearchConvertPageFrameToVa (PfnHit, &VaFlags, &PteAddress);
|
|
|
|
PfnOffset &= (ULONG)0xFFFF;
|
|
|
|
if (SizeOfPfnNumber == 8) {
|
|
|
|
dprintf ("%016I64X %08X %016I64X %016I64X %016I64X \n",
|
|
PfnHit,
|
|
PfnOffset,
|
|
READ_PHYSICAL_ULONG64 (PfnHit * PageSize + PfnOffset),
|
|
(VaHit == 0 ? 0 : VaHit + PfnOffset),
|
|
PteAddress);
|
|
}
|
|
else {
|
|
|
|
VaHit &= (ULONG64)0xFFFFFFFF;
|
|
PteAddress &= (ULONG64)0xFFFFFFFF;
|
|
|
|
dprintf ("%08I64X %08X %08X %08I64X %08I64X \n",
|
|
PfnHit,
|
|
PfnOffset,
|
|
READ_PHYSICAL_ULONG (PfnHit * PageSize + PfnOffset),
|
|
(VaHit == 0 ? 0 : VaHit + PfnOffset),
|
|
PteAddress);
|
|
}
|
|
|
|
if (CheckControlC()) {
|
|
RequestForInterrupt = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
dprintf ("\n");
|
|
|
|
Result = S_OK;
|
|
|
|
//
|
|
// Exit point
|
|
//
|
|
|
|
Exit:
|
|
|
|
WRITE_ULONG (KdpSearchInProgress, 0);
|
|
PfnSearchValue = 0;
|
|
|
|
{
|
|
ULONG BytesWritten = 0;
|
|
|
|
WriteMemory (KdpSearchPfnValueAddress,
|
|
&PfnSearchValue,
|
|
SizeOfPfnNumber,
|
|
&BytesWritten);
|
|
|
|
if (BytesWritten != SizeOfPfnNumber) {
|
|
dprintf ("Search error: failed to reset nt!KdpSearchPfnValue \n");
|
|
}
|
|
}
|
|
|
|
if (PfnHitsBuffer) {
|
|
free (PfnHitsBuffer);
|
|
}
|
|
|
|
if (! RequestForInterrupt) {
|
|
|
|
dprintf ("Search done (%u hits in %u pages).\n",
|
|
NumberOfHits,
|
|
PfnHitsBufferIndex);
|
|
}
|
|
else {
|
|
|
|
dprintf ("Search interrupted. \n");
|
|
}
|
|
|
|
return Result;
|
|
}
|