[NTOSKRNL] Implement !poolfind command in KDBG
[reactos.git] / ntoskrnl / mm / ARM3 / expool.c
index 4558bfa..b321b52 100644 (file)
@@ -13,7 +13,7 @@
 #include <debug.h>
 
 #define MODULE_INVOLVED_IN_ARM3
-#include "../ARM3/miarm.h"
+#include <mm/ARM3/miarm.h>
 
 #undef ExAllocatePoolWithQuota
 #undef ExAllocatePoolWithQuotaTag
@@ -460,11 +460,177 @@ ExpComputePartialHashForAddress(IN PVOID BaseAddress)
     return (Result >> 24) ^ (Result >> 16) ^ (Result >> 8) ^ Result;
 }
 
+#if DBG
+FORCEINLINE
+BOOLEAN
+ExpTagAllowPrint(CHAR Tag)
+{
+    if ((Tag >= 'a' && Tag <= 'z') ||
+        (Tag >= 'A' && Tag <= 'Z') ||
+        (Tag >= '0' && Tag <= '9') ||
+        Tag == ' ' || Tag == '=' ||
+        Tag == '?' || Tag == '@')
+    {
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+#ifdef KDBG
+#define MiDumperPrint(dbg, fmt, ...)        \
+    if (dbg) KdbpPrint(fmt, ##__VA_ARGS__); \
+    else DPRINT1(fmt, ##__VA_ARGS__)
+#else
+#define MiDumperPrint(dbg, fmt, ...)        \
+    DPRINT1(fmt, ##__VA_ARGS__)
+#endif
+
+VOID
+MiDumpPoolConsumers(BOOLEAN CalledFromDbg, ULONG Tag, ULONG Mask, ULONG Flags)
+{
+    SIZE_T i;
+    BOOLEAN Verbose;
+
+    //
+    // Only print header if called from OOM situation
+    //
+    if (!CalledFromDbg)
+    {
+        DPRINT1("---------------------\n");
+        DPRINT1("Out of memory dumper!\n");
+    }
+#ifdef KDBG
+    else
+    {
+        KdbpPrint("Pool Used:\n");
+    }
+#endif
+
+    //
+    // Remember whether we'll have to be verbose
+    // This is the only supported flag!
+    //
+    Verbose = BooleanFlagOn(Flags, 1);
+
+    //
+    // Print table header
+    //
+    if (Verbose)
+    {
+        MiDumperPrint(CalledFromDbg, "\t\t\t\tNonPaged\t\t\t\t\t\t\tPaged\n");
+        MiDumperPrint(CalledFromDbg, "Tag\t\tAllocs\t\tFrees\t\tDiff\t\tUsed\t\tAllocs\t\tFrees\t\tDiff\t\tUsed\n");
+    }
+    else
+    {
+        MiDumperPrint(CalledFromDbg, "\t\tNonPaged\t\t\tPaged\n");
+        MiDumperPrint(CalledFromDbg, "Tag\t\tAllocs\t\tUsed\t\tAllocs\t\tUsed\n");
+    }
+
+    //
+    // We'll extract allocations for all the tracked pools
+    //
+    for (i = 0; i < PoolTrackTableSize; ++i)
+    {
+        PPOOL_TRACKER_TABLE TableEntry;
+
+        TableEntry = &PoolTrackTable[i];
+
+        //
+        // We only care about tags which have allocated memory
+        //
+        if (TableEntry->NonPagedBytes != 0 || TableEntry->PagedBytes != 0)
+        {
+            //
+            // If there's a tag, attempt to do a pretty print
+            // only if it matches the caller's tag, or if
+            // any tag is allowed
+            // For checking whether it matches caller's tag,
+            // use the mask to make sure not to mess with the wildcards
+            //
+            if (TableEntry->Key != 0 && TableEntry->Key != TAG_NONE &&
+                (Tag == 0 || (TableEntry->Key & Mask) == (Tag & Mask)))
+            {
+                CHAR Tag[4];
+
+                //
+                // Extract each 'component' and check whether they are printable
+                //
+                Tag[0] = TableEntry->Key & 0xFF;
+                Tag[1] = TableEntry->Key >> 8 & 0xFF;
+                Tag[2] = TableEntry->Key >> 16 & 0xFF;
+                Tag[3] = TableEntry->Key >> 24 & 0xFF;
+
+                if (ExpTagAllowPrint(Tag[0]) && ExpTagAllowPrint(Tag[1]) && ExpTagAllowPrint(Tag[2]) && ExpTagAllowPrint(Tag[3]))
+                {
+                    //
+                    // Print in direct order to make !poolused TAG usage easier
+                    //
+                    if (Verbose)
+                    {
+                        MiDumperPrint(CalledFromDbg, "'%c%c%c%c'\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\n", Tag[0], Tag[1], Tag[2], Tag[3],
+                                      TableEntry->NonPagedAllocs, TableEntry->NonPagedFrees,
+                                      (TableEntry->NonPagedAllocs - TableEntry->NonPagedFrees), TableEntry->NonPagedBytes,
+                                      TableEntry->PagedAllocs, TableEntry->PagedFrees,
+                                      (TableEntry->PagedAllocs - TableEntry->PagedFrees), TableEntry->PagedBytes);
+                    }
+                    else
+                    {
+                        MiDumperPrint(CalledFromDbg, "'%c%c%c%c'\t\t%ld\t\t%ld\t\t%ld\t\t%ld\n", Tag[0], Tag[1], Tag[2], Tag[3],
+                                      TableEntry->NonPagedAllocs, TableEntry->NonPagedBytes,
+                                      TableEntry->PagedAllocs, TableEntry->PagedBytes);
+                    }
+                }
+                else
+                {
+                    if (Verbose)
+                    {
+                        MiDumperPrint(CalledFromDbg, "%x\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\n", TableEntry->Key,
+                                      TableEntry->NonPagedAllocs, TableEntry->NonPagedFrees,
+                                      (TableEntry->NonPagedAllocs - TableEntry->NonPagedFrees), TableEntry->NonPagedBytes,
+                                      TableEntry->PagedAllocs, TableEntry->PagedFrees,
+                                      (TableEntry->PagedAllocs - TableEntry->PagedFrees), TableEntry->PagedBytes);
+                    }
+                    else
+                    {
+                        MiDumperPrint(CalledFromDbg, "%x\t%ld\t\t%ld\t\t%ld\t\t%ld\n", TableEntry->Key,
+                                      TableEntry->NonPagedAllocs, TableEntry->NonPagedBytes,
+                                      TableEntry->PagedAllocs, TableEntry->PagedBytes);
+                    }
+                }
+            }
+            else if (Tag == 0 || (Tag & Mask) == (TAG_NONE & Mask))
+            {
+                if (Verbose)
+                {
+                    MiDumperPrint(CalledFromDbg, "Anon\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\t\t%ld\n",
+                                  TableEntry->NonPagedAllocs, TableEntry->NonPagedFrees,
+                                  (TableEntry->NonPagedAllocs - TableEntry->NonPagedFrees), TableEntry->NonPagedBytes,
+                                  TableEntry->PagedAllocs, TableEntry->PagedFrees,
+                                  (TableEntry->PagedAllocs - TableEntry->PagedFrees), TableEntry->PagedBytes);
+                }
+                else
+                {
+                    MiDumperPrint(CalledFromDbg, "Anon\t\t%ld\t\t%ld\t\t%ld\t\t%ld\n",
+                                  TableEntry->NonPagedAllocs, TableEntry->NonPagedBytes,
+                                  TableEntry->PagedAllocs, TableEntry->PagedBytes);
+                }
+            }
+        }
+    }
+
+    if (!CalledFromDbg)
+    {
+        DPRINT1("---------------------\n");
+    }
+}
+#endif
+
 /* PRIVATE FUNCTIONS **********************************************************/
 
+INIT_FUNCTION
 VOID
 NTAPI
-INIT_FUNCTION
 ExpSeedHotTags(VOID)
 {
     ULONG i, Key, Hash, Index;
@@ -792,9 +958,9 @@ ExpInsertPoolTracker(IN ULONG Key,
     DPRINT1("Out of pool tag space, ignoring...\n");
 }
 
+INIT_FUNCTION
 VOID
 NTAPI
-INIT_FUNCTION
 ExInitializePoolDescriptor(IN PPOOL_DESCRIPTOR PoolDescriptor,
                            IN POOL_TYPE PoolType,
                            IN ULONG PoolIndex,
@@ -843,9 +1009,9 @@ ExInitializePoolDescriptor(IN PPOOL_DESCRIPTOR PoolDescriptor,
     ASSERT(PoolType != PagedPoolSession);
 }
 
+INIT_FUNCTION
 VOID
 NTAPI
-INIT_FUNCTION
 InitializePool(IN POOL_TYPE PoolType,
                IN ULONG Threshold)
 {
@@ -932,7 +1098,7 @@ InitializePool(IN POOL_TYPE PoolType,
         }
 
         //
-        // Finally, add one entry, compute the hash, and zero the table
+        // Add one entry, compute the hash, and zero the table
         //
         PoolTrackTableSize++;
         PoolTrackTableMask = PoolTrackTableSize - 2;
@@ -940,6 +1106,11 @@ InitializePool(IN POOL_TYPE PoolType,
         RtlZeroMemory(PoolTrackTable,
                       PoolTrackTableSize * sizeof(POOL_TRACKER_TABLE));
 
+        //
+        // Finally, add the most used tags to speed up those allocations
+        //
+        ExpSeedHotTags();
+
         //
         // We now do the exact same thing with the tracker table for big pages
         //
@@ -1498,10 +1669,77 @@ ExQueryPoolUsage(OUT PULONG PagedPoolPages,
 #endif
 
     //
-    // FIXME: Not yet supported
+    // Get the amount of hits in the system lookaside lists
     //
-    *NonPagedPoolLookasideHits += 0;
-    *PagedPoolLookasideHits += 0;
+    if (!IsListEmpty(&ExPoolLookasideListHead))
+    {
+        PLIST_ENTRY ListEntry;
+
+        for (ListEntry = ExPoolLookasideListHead.Flink;
+             ListEntry != &ExPoolLookasideListHead;
+             ListEntry = ListEntry->Flink)
+        {
+            PGENERAL_LOOKASIDE Lookaside;
+
+            Lookaside = CONTAINING_RECORD(ListEntry, GENERAL_LOOKASIDE, ListEntry);
+
+            if (Lookaside->Type == NonPagedPool)
+            {
+                *NonPagedPoolLookasideHits += Lookaside->AllocateHits;
+            }
+            else
+            {
+                *PagedPoolLookasideHits += Lookaside->AllocateHits;
+            }
+        }
+    }
+}
+
+VOID
+NTAPI
+ExReturnPoolQuota(IN PVOID P)
+{
+    PPOOL_HEADER Entry;
+    POOL_TYPE PoolType;
+    USHORT BlockSize;
+    PEPROCESS Process;
+
+    if ((ExpPoolFlags & POOL_FLAG_SPECIAL_POOL) &&
+        (MmIsSpecialPoolAddress(P)))
+    {
+        return;
+    }
+
+    Entry = P;
+    Entry--;
+    ASSERT((ULONG_PTR)Entry % POOL_BLOCK_SIZE == 0);
+
+    PoolType = Entry->PoolType - 1;
+    BlockSize = Entry->BlockSize;
+
+    if (PoolType & QUOTA_POOL_MASK)
+    {
+        Process = ((PVOID *)POOL_NEXT_BLOCK(Entry))[-1];
+        ASSERT(Process != NULL);
+        if (Process)
+        {
+            if (Process->Pcb.Header.Type != ProcessObject)
+            {
+                DPRINT1("Object %p is not a process. Type %u, pool type 0x%x, block size %u\n",
+                        Process, Process->Pcb.Header.Type, Entry->PoolType, BlockSize);
+                KeBugCheckEx(BAD_POOL_CALLER,
+                             0x0D,
+                             (ULONG_PTR)P,
+                             Entry->PoolTag,
+                             (ULONG_PTR)Process);
+            }
+            ((PVOID *)POOL_NEXT_BLOCK(Entry))[-1] = NULL;
+            PsReturnPoolQuota(Process,
+                              PoolType & BASE_POOL_TYPE_MASK,
+                              BlockSize * POOL_BLOCK_SIZE);
+            ObDereferenceObject(Process);
+        }
+    }
 }
 
 /* PUBLIC FUNCTIONS ***********************************************************/
@@ -1589,6 +1827,19 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
         Entry = MiAllocatePoolPages(OriginalType, NumberOfBytes);
         if (!Entry)
         {
+#if DBG
+            //
+            // Out of memory, display current consumption
+            // Let's consider that if the caller wanted more
+            // than a hundred pages, that's a bogus caller
+            // and we are not out of memory
+            //
+            if (NumberOfBytes < 100 * PAGE_SIZE)
+            {
+                MiDumpPoolConsumers(FALSE, 0, 0, 0);
+            }
+#endif
+
             //
             // Must succeed pool is deprecated, but still supported. These allocation
             // failures must cause an immediate bugcheck
@@ -1627,6 +1878,8 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
             {
                 ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
             }
+
+            return NULL;
         }
 
         //
@@ -1913,6 +2166,19 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
     Entry = MiAllocatePoolPages(OriginalType, PAGE_SIZE);
     if (!Entry)
     {
+#if DBG
+        //
+        // Out of memory, display current consumption
+        // Let's consider that if the caller wanted more
+        // than a hundred pages, that's a bogus caller
+        // and we are not out of memory
+        //
+        if (NumberOfBytes < 100 * PAGE_SIZE)
+        {
+            MiDumpPoolConsumers(FALSE, 0, 0, 0);
+        }
+#endif
+
         //
         // Must succeed pool is deprecated, but still supported. These allocation
         // failures must cause an immediate bugcheck
@@ -2041,10 +2307,25 @@ NTAPI
 ExAllocatePool(POOL_TYPE PoolType,
                SIZE_T NumberOfBytes)
 {
-    //
-    // Use a default tag of "None"
-    //
-    return ExAllocatePoolWithTag(PoolType, NumberOfBytes, TAG_NONE);
+    ULONG Tag = TAG_NONE;
+#if 0 && DBG
+    PLDR_DATA_TABLE_ENTRY LdrEntry;
+
+    /* Use the first four letters of the driver name, or "None" if unavailable */
+    LdrEntry = KeGetCurrentIrql() <= APC_LEVEL
+                ? MiLookupDataTableEntry(_ReturnAddress())
+                : NULL;
+    if (LdrEntry)
+    {
+        ULONG i;
+        Tag = 0;
+        for (i = 0; i < min(4, LdrEntry->BaseDllName.Length / sizeof(WCHAR)); i++)
+            Tag = Tag >> 8 | (LdrEntry->BaseDllName.Buffer[i] & 0xff) << 24;
+        for (; i < 4; i++)
+            Tag = Tag >> 8 | ' ' << 24;
+    }
+#endif
+    return ExAllocatePoolWithTag(PoolType, NumberOfBytes, Tag);
 }
 
 /*
@@ -2220,6 +2501,7 @@ ExFreePoolWithTag(IN PVOID P,
     //
     Entry = P;
     Entry--;
+    ASSERT((ULONG_PTR)Entry % POOL_BLOCK_SIZE == 0);
 
     //
     // Get the size of the entry, and it's pool type, then load the descriptor
@@ -2262,7 +2544,6 @@ ExFreePoolWithTag(IN PVOID P,
     if ((Entry->PoolType - 1) & QUOTA_POOL_MASK)
     {
         Process = ((PVOID *)POOL_NEXT_BLOCK(Entry))[-1];
-        ASSERT(Process != NULL);
         if (Process)
         {
             if (Process->Pcb.Header.Type != ProcessObject)
@@ -2507,7 +2788,7 @@ ExAllocatePoolWithQuota(IN POOL_TYPE PoolType,
     //
     // Allocate the pool
     //
-    return ExAllocatePoolWithQuotaTag(PoolType, NumberOfBytes, 'enoN');
+    return ExAllocatePoolWithQuotaTag(PoolType, NumberOfBytes, TAG_NONE);
 }
 
 /*
@@ -2520,11 +2801,18 @@ ExAllocatePoolWithTagPriority(IN POOL_TYPE PoolType,
                               IN ULONG Tag,
                               IN EX_POOL_PRIORITY Priority)
 {
+    PVOID Buffer;
+
     //
     // Allocate the pool
     //
-    UNIMPLEMENTED;
-    return ExAllocatePoolWithTag(PoolType, NumberOfBytes, Tag);
+    Buffer = ExAllocatePoolWithTag(PoolType, NumberOfBytes, Tag);
+    if (Buffer == NULL)
+    {
+        UNIMPLEMENTED;
+    }
+
+    return Buffer;
 }
 
 /*
@@ -2646,7 +2934,7 @@ ExAllocatePoolWithQuotaTag(IN POOL_TYPE PoolType,
     return Buffer;
 }
 
-#if DBG && KDBG
+#if DBG && defined(KDBG)
 
 BOOLEAN
 ExpKdbgExtPool(
@@ -2743,6 +3031,303 @@ ExpKdbgExtPool(
     return TRUE;
 }
 
+static
+VOID
+ExpKdbgExtPoolUsedGetTag(PCHAR Arg, PULONG Tag, PULONG Mask)
+{
+    CHAR Tmp[4];
+    ULONG Len;
+    USHORT i;
+
+    /* Get the tag */
+    Len = strlen(Arg);
+    if (Len > 4)
+    {
+        Len = 4;
+    }
+
+    /* Generate the mask to have wildcards support */
+    for (i = 0; i < Len; ++i)
+    {
+        Tmp[i] = Arg[i];
+        if (Tmp[i] != '?')
+        {
+            *Mask |= (0xFF << i * 8);
+        }
+    }
+
+    /* Get the tag in the ulong form */
+    *Tag = *((PULONG)Tmp);
+}
+
+BOOLEAN
+ExpKdbgExtPoolUsed(
+    ULONG Argc,
+    PCHAR Argv[])
+{
+    ULONG Tag = 0;
+    ULONG Mask = 0;
+    ULONG Flags = 0;
+
+    if (Argc > 1)
+    {
+        /* If we have 2+ args, easy: flags then tag */
+        if (Argc > 2)
+        {
+            ExpKdbgExtPoolUsedGetTag(Argv[2], &Tag, &Mask);
+            if (!KdbpGetHexNumber(Argv[1], &Flags))
+            {
+                KdbpPrint("Invalid parameter: %s\n", Argv[0]);
+            }
+        }
+        else
+        {
+            /* Otherwise, try to find out whether that's flags */
+            if (strlen(Argv[1]) == 1 ||
+                (strlen(Argv[1]) == 3 && Argv[1][0] == '0' && Argv[1][1] == 'x'))
+            {
+                /* Fallback: if reading flags failed, assume it's a tag */
+                if (!KdbpGetHexNumber(Argv[1], &Flags))
+                {
+                    ExpKdbgExtPoolUsedGetTag(Argv[1], &Tag, &Mask);
+                }
+            }
+            /* Or tag */
+            else
+            {
+                ExpKdbgExtPoolUsedGetTag(Argv[1], &Tag, &Mask);
+            }
+        }
+    }
+
+    /* Call the dumper */
+    MiDumpPoolConsumers(TRUE, Tag, Mask, Flags);
+
+    return TRUE;
+}
+
+static
+BOOLEAN
+ExpKdbgExtValidatePoolHeader(
+    PVOID BaseVa,
+    PPOOL_HEADER Entry,
+    POOL_TYPE BasePoolTye)
+{
+    /* Block size cannot be NULL or negative and it must cover the page */
+    if (Entry->BlockSize <= 0)
+    {
+        return FALSE;
+    }
+    if (Entry->BlockSize * 8 + (ULONG_PTR)Entry - (ULONG_PTR)BaseVa > PAGE_SIZE)
+    {
+        return FALSE;
+    }
+
+    /*
+     * PreviousSize cannot be 0 unless on page begin
+     * And it cannot be bigger that our current
+     * position in page
+     */
+    if (Entry->PreviousSize == 0 && BaseVa != Entry)
+    {
+        return FALSE;
+    }
+    if (Entry->PreviousSize * 8 > (ULONG_PTR)Entry - (ULONG_PTR)BaseVa)
+    {
+        return FALSE;
+    }
+
+    /* Must be paged pool */
+    if (((Entry->PoolType - 1) & BASE_POOL_TYPE_MASK) != BasePoolTye)
+    {
+        return FALSE;
+    }
+
+    /* Match tag mask */
+    if ((Entry->PoolTag & 0x00808080) != 0)
+    {
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static
+VOID
+ExpKdbgExtPoolFindPagedPool(
+    ULONG Tag,
+    ULONG Mask)
+{
+    ULONG i = 0;
+    PPOOL_HEADER Entry;
+    PVOID BaseVa;
+    PMMPTE PointerPte;
+    PMMPDE PointerPde;
+
+    KdbpPrint("Searching Paged pool (%p : %p) for Tag: %.4s\n", MmPagedPoolStart, MmPagedPoolEnd, (PCHAR)&Tag);
+
+    /*
+     * To speed up paged pool search, we will use the allocation bipmap.
+     * This is possible because we live directly in the kernel :-)
+     */
+    i = RtlFindSetBits(MmPagedPoolInfo.PagedPoolAllocationMap, 1, 0);
+    while (i != 0xFFFFFFFF)
+    {
+        BaseVa = (PVOID)((ULONG_PTR)MmPagedPoolStart + (i << PAGE_SHIFT));
+        Entry = BaseVa;
+
+        /* Validate our address */
+        if ((ULONG_PTR)BaseVa > (ULONG_PTR)MmPagedPoolEnd || (ULONG_PTR)BaseVa + PAGE_SIZE > (ULONG_PTR)MmPagedPoolEnd)
+        {
+            break;
+        }
+
+        /* Check whether we are beyond expansion */
+        PointerPde = MiAddressToPde(BaseVa);
+        if (PointerPde >= MmPagedPoolInfo.NextPdeForPagedPoolExpansion)
+        {
+            break;
+        }
+
+        /* Check if allocation is valid */
+        PointerPte = MiAddressToPte(BaseVa);
+        if ((ULONG_PTR)PointerPte > PTE_TOP)
+        {
+            break;
+        }
+
+        if (PointerPte->u.Hard.Valid)
+        {
+            for (Entry = BaseVa;
+                 (ULONG_PTR)Entry + sizeof(POOL_HEADER) < (ULONG_PTR)BaseVa + PAGE_SIZE;
+                 Entry = (PVOID)((ULONG_PTR)Entry + 8))
+            {
+                /* Try to find whether we have a pool entry */
+                if (!ExpKdbgExtValidatePoolHeader(BaseVa, Entry, PagedPool))
+                {
+                    continue;
+                }
+
+                if ((Entry->PoolTag & Mask) == (Tag & Mask))
+                {
+                    /* Print the line */
+                    KdbpPrint("%p size: %4d previous size: %4d  %s  %.4s\n",
+                              Entry, Entry->BlockSize, Entry->PreviousSize,
+                              Entry->PoolType ? "(Allocated)" : "(Free)     ",
+                              (PCHAR)&Entry->PoolTag);
+                }
+            }
+        }
+
+        i = RtlFindSetBits(MmPagedPoolInfo.PagedPoolAllocationMap, 1, i + 1);
+    }
+}
+
+extern PVOID MmNonPagedPoolEnd0;
+static
+VOID
+ExpKdbgExtPoolFindNonPagedPool(
+    ULONG Tag,
+    ULONG Mask)
+{
+    PPOOL_HEADER Entry;
+    PVOID BaseVa;
+    PMMPTE PointerPte;
+
+    KdbpPrint("Searching NonPaged pool (%p : %p) for Tag: %.4s\n", MmNonPagedPoolStart, MmNonPagedPoolEnd0, (PCHAR)&Tag);
+
+    /* Brute force search: start browsing the whole non paged pool */
+    for (BaseVa = MmNonPagedPoolStart;
+         (ULONG_PTR)BaseVa + PAGE_SIZE <= (ULONG_PTR)MmNonPagedPoolEnd0;
+         BaseVa = (PVOID)((ULONG_PTR)BaseVa + PAGE_SIZE))
+    {
+        Entry = BaseVa;
+
+        /* Check whether we are beyond expansion */
+        if (BaseVa >= MmNonPagedPoolExpansionStart)
+        {
+            break;
+        }
+
+        /* Check if allocation is valid */
+        PointerPte = MiAddressToPte(BaseVa);
+        if ((ULONG_PTR)PointerPte > PTE_TOP)
+        {
+            break;
+        }
+
+        if (PointerPte->u.Hard.Valid)
+        {
+            for (Entry = BaseVa;
+                 (ULONG_PTR)Entry + sizeof(POOL_HEADER) < (ULONG_PTR)BaseVa + PAGE_SIZE;
+                 Entry = (PVOID)((ULONG_PTR)Entry + 8))
+            {
+                /* Try to find whether we have a pool entry */
+                if (!ExpKdbgExtValidatePoolHeader(BaseVa, Entry, NonPagedPool))
+                {
+                    continue;
+                }
+
+                if ((Entry->PoolTag & Mask) == (Tag & Mask))
+                {
+                    /* Print the line */
+                    KdbpPrint("%p size: %4d previous size: %4d  %s  %.4s\n",
+                              Entry, Entry->BlockSize, Entry->PreviousSize,
+                              Entry->PoolType ? "(Allocated)" : "(Free)     ",
+                              (PCHAR)&Entry->PoolTag);
+                }
+            }
+        }
+    }
+}
+
+BOOLEAN
+ExpKdbgExtPoolFind(
+    ULONG Argc,
+    PCHAR Argv[])
+{
+    ULONG Tag = 0;
+    ULONG Mask = 0;
+    ULONG PoolType = NonPagedPool;
+
+    if (Argc == 1)
+    {
+        KdbpPrint("Specify a tag string\n");
+        return TRUE;
+    }
+
+    /* First arg is tag */
+    if (strlen(Argv[1]) != 1 || Argv[1][0] != '*')
+    {
+        ExpKdbgExtPoolUsedGetTag(Argv[1], &Tag, &Mask);
+    }
+
+    /* Second arg might be pool to search */
+    if (Argc > 2)
+    {
+        PoolType = strtoul(Argv[2], NULL, 0);
+
+        if (PoolType > 1)
+        {
+            KdbpPrint("Only (non) paged pool are supported\n");
+            return TRUE;
+        }
+    }
+
+    /* FIXME: What about large pool? */
+
+    if (PoolType == NonPagedPool)
+    {
+        ExpKdbgExtPoolFindNonPagedPool(Tag, Mask);
+    }
+    else if (PoolType == PagedPool)
+    {
+        ExpKdbgExtPoolFindPagedPool(Tag, Mask);
+    }
+
+    return TRUE;
+}
+
 #endif // DBG && KDBG
 
 /* EOF */