// Determine whether an address is part of a live packet, or a live packet
// header. Intended for interactive use in the debugger, outputs to debugger
// console.
DbgAllocHeader *DbgCheckAddress(unsigned ptr)
{
    STATIC_CONTRACT_NOTHROW;

    DbgAllocHeader *h = g_AllocListFirst;
    WCHAR           output[1024];
    void           *p = (void *)(UINT_PTR)ptr;

    while (h) {
        void *head = (void *)h;
        void *start = (void *)CDA_HEADER_TO_DATA(h);
        void *end = (void *)&CDA_DATA(h, h->m_Length);
        void *tail = (void *)&CDA_DATA(h, h->m_Length + CDA_OPT_GUARD_BYTES);
        if ((p >= head) && (p < start)) {
            wsprintfW(output, L"0x%08X is in packet header at 0x%08X\n", p, h);
            WszOutputDebugString(output);
            return h;
        } else if ((p >= start) && (p < end)) {
            wsprintfW(output, L"0x%08X is in data portion of packet at 0x%08X\n", p, h);
            WszOutputDebugString(output);
            return h;
        } else if ((p >= end) && (p < tail)) {
            wsprintfW(output, L"0x%08X is in guard portion of packet at 0x%08X\n", p, h);
            WszOutputDebugString(output);
            return h;
        }
        h = h->m_Next;
    }

    wsprintfW(output, L"%08X not located in any live packet\n", p);
    WszOutputDebugString(output);

    return NULL;
}
Beispiel #2
0
// Routine to check that an allocation header looks valid. Asserts on failure.
void DbgValidateHeader(DbgAllocHeader *h)
{
    _ASSERTE((h->m_Magic1 == CDA_MAGIC_1) &&
             (*CDA_MAGIC2(h) == CDA_MAGIC_2) &&
             ((unsigned)h->m_Next != CDA_INV_PATTERN) &&
             ((unsigned)h->m_Prev != CDA_INV_PATTERN));
    if (g_AllocGuard)
        for (unsigned i = 0; i < CDA_GUARD_BYTES; i++)
            _ASSERTE(CDA_DATA(h, h->m_Length + i) == CDA_GUARD_PATTERN);
}
Beispiel #3
0
// Routine to check all active packets to see if they still look valid.
// Optionally, also check that the non-NULL address passed does not lie within
// any of the currently allocated packets.
void DbgValidateActivePackets(void *Start, void *End)
{
    DbgAllocHeader *h = g_AllocListFirst;

    while (h) {
        DbgValidateHeader(h);
        if (Start) {
            void *head = (void *)h;
            void *tail = (void *)&CDA_DATA(h, h->m_Length + CDA_OPT_GUARD_BYTES);
            _ASSERTE((End <= head) || (Start >= tail));
        }
        h = h->m_Next;
    }

}
// Allocate a block of memory at least n bytes big.
void * __stdcall DbgAlloc(size_t n, void **ppvCallstack, BOOL isArray)
{
    STATIC_CONTRACT_NOTHROW;

    // Initialize if necessary (DbgAllocInit takes care of the synchronization).
    if (!g_HeapInitialized)
        DbgAllocInit();

    if (!g_DbgEnabled)
        return ClrAllocInProcessHeap(0, n);

    CDA_LOCK();

    // Count calls to this routine and the number that specify 0 bytes of
    // allocation. This needs to be done under the lock since the counters
    // themselves aren't synchronized.
    CDA_STATS_INC(Allocs);
    if (n == 0)
        CDA_STATS_INC(ZeroAllocs);

    CDA_UNLOCK();

    // Allocate enough memory for the caller, our debugging header and possibly
    // some guard bytes.
    unsigned        length = CDA_SIZEOF_HEADER() + (unsigned)n + CDA_OPT_GUARD_BYTES;
    DbgAllocHeader *h;

    if (g_PagePerAlloc) {
        // In page per alloc mode we allocate a number of whole pages. The
        // actual packet is placed at the end of the second to last page and the
        // last page is reserved but never commited (so will cause an access
        // violation if touched). This will catch heap crawl real quick.
        unsigned pages = ((length + (g_PageSize - 1)) / g_PageSize) + 1;
        h = (DbgAllocHeader *)ClrVirtualAlloc(NULL, pages * g_PageSize, MEM_RESERVE, PAGE_NOACCESS);
        if (h) {
            ClrVirtualAlloc(h, (pages - 1) * g_PageSize, MEM_COMMIT, PAGE_READWRITE);
            h = (DbgAllocHeader *)((BYTE *)h + (g_PageSize - (length % g_PageSize)));
        }
    } else
        h = (DbgAllocHeader *)ClrHeapAlloc(g_HeapHandle, 0, length);

    CDA_LOCK();
    if (h == NULL) {

        // Whoops, allocation failure. Record it.
        CDA_STATS_INC(AllocFailures);
        LOG((LF_DBGALLOC, LL_ALWAYS, "DbgAlloc: alloc fail for %u bytes\n", n));

    } else {

        // Check all active packets still look OK.
        if (g_ConstantRecheck)
            DbgValidateActivePackets(h, &CDA_DATA(h, n + CDA_OPT_GUARD_BYTES));

        // Count the total number of bytes we've allocated so far.
        CDA_STATS_ADD(AllocBytes, n);

        // Record the largest amount of concurrent allocations we ever see
        // during the life of the process.
        if((g_AllocStats.m_AllocBytes - g_AllocStats.m_FreeBytes) > g_AllocStats.m_MaxAlloc)
            g_AllocStats.m_MaxAlloc = g_AllocStats.m_AllocBytes - g_AllocStats.m_FreeBytes;

        // Fill in the packet debugging header.
        for (unsigned i = 0; i < g_CallStackDepth; i++) {
            CDA_ALLOC_STACK(h, i) = ppvCallstack[i];
            CDA_DEALLOC_STACK(h, i) = NULL;
        }
        h->m_hmod = NULL;
        h->m_SN = g_NextSN++;
        h->m_Length = (unsigned)n;
        h->m_IsArray = isArray;
        h->m_Prev = g_AllocListLast;
        h->m_Next = NULL;
        h->m_Magic1 = CDA_MAGIC_1;
        *CDA_MAGIC2(h) = CDA_MAGIC_2;

        // If the user wants to breakpoint on the allocation of a specific
        // packet, do it now.
        if (g_BreakOnAlloc && (h->m_SN == g_BreakOnAllocNumber))
            _ASSERTE(!"Hit memory allocation # for breakpoint");

        // Link the packet into the queue of live packets.
        if (g_AllocListLast != NULL) {
            g_AllocListLast->m_Next = h;
            g_AllocListLast = h;
        }
        if (g_AllocListFirst == NULL) {
            _ASSERTE(g_AllocListLast == NULL);
            g_AllocListFirst = h;
            g_AllocListLast = h;
        }

        if (g_PoisonPackets)
            memset(CDA_HEADER_TO_DATA(h), CDA_ALLOC_PATTERN, n);

        // Write a guard pattern after the user data to trap overwrites.
        if (g_AllocGuard)
            memset(&CDA_DATA(h, n), CDA_GUARD_PATTERN, CDA_GUARD_BYTES);

        // See if our allocator makes the list of most frequent allocators.
        if (g_UsageByAllocator) {
            // Look for an existing entry in the table for our EIP, or for the
            // first empty slot (the table is kept in sorted order, so the first
            // empty slot marks the end of the table).
            unsigned i;
            for (i = 0; i < g_TopAllocatorsSlots; i++) {

                if (g_TopAllocators[i].m_EIP == ppvCallstack[0]) {
                    // We already have an entry for this allocator. Incrementing
                    // the count may allow us to move the allocator up the
                    // table.
                    g_TopAllocators[i].m_Count++;
                    g_TopAllocators[i].m_TotalBytes += n;
                    if ((i > 0) &&
                        (g_TopAllocators[i].m_Count > g_TopAllocators[i - 1].m_Count)) {
                        DbgAllocTop tmp = g_TopAllocators[i - 1];
                        g_TopAllocators[i - 1] = g_TopAllocators[i];
                        g_TopAllocators[i] = tmp;
                    }
                    break;
                }

                if (g_TopAllocators[i].m_EIP == NULL) {
                    // We've found an empty slot, we weren't in the table. This
                    // is the right place to put the entry though, since we've
                    // only done a single allocation.
                    g_TopAllocators[i].m_EIP = ppvCallstack[0];
                    g_TopAllocators[i].m_Count = 1;
                    g_TopAllocators[i].m_TotalBytes = n;
                    break;
                }

            }

            if (i == g_TopAllocatorsSlots) {
                // Ran out of space in the table, need to expand it.
                unsigned slots = g_TopAllocatorsSlots ?
                    g_TopAllocatorsSlots * 2 :
                    CDA_TOP_ALLOCATORS;
                DbgAllocTop *newtab = (DbgAllocTop*)ClrAllocInProcessHeap(0, slots*sizeof(DbgAllocTop));
                if (newtab) {

                    // Copy old contents over.
                    if (g_TopAllocatorsSlots) {
                        memcpy(newtab, g_TopAllocators, sizeof(DbgAllocTop) * g_TopAllocatorsSlots);
                        delete [] g_TopAllocators;
                    }

                    // Install new table.
                    g_TopAllocators = newtab;
                    g_TopAllocatorsSlots = slots;

                    // Add new entry to tail.
                    g_TopAllocators[i].m_EIP = ppvCallstack[0];
                    g_TopAllocators[i].m_Count = 1;
                    g_TopAllocators[i].m_TotalBytes = n;

                    // And initialize the rest of the entries to empty.
                    memset(&g_TopAllocators[i + 1],
                           0,
                           sizeof(DbgAllocTop) * (slots - (i + 1)));

                }
            }
        }

        // Count how many allocations of each size range we get. Allocations
        // above a certain size are all dumped into one bucket.
        if (g_LogDist) {
            if (n > CDA_MAX_DIST_SIZE)
                g_LargeAllocs++;
            else {
                for (unsigned i = CDA_DIST_BUCKET_SIZE - 1; i <= CDA_MAX_DIST_SIZE; i += CDA_DIST_BUCKET_SIZE)
                    if (n <= i) {
                        g_AllocBuckets[i/CDA_DIST_BUCKET_SIZE]++;
                        break;
                    }
            }
        }

    }
    CDA_UNLOCK();

    return h ? CDA_HEADER_TO_DATA(h) : NULL;
}
// Called just before process exit to report stats and check for memory
// leakages etc.
void __stdcall DbgAllocReport(__in_z __in_opt char * pString,
                              BOOL fDone,
                              BOOL fDoPrintf,
                              unsigned snapShot)
{
    STATIC_CONTRACT_NOTHROW;

    if (!g_HeapInitialized)
        return;

    CDA_LOCK();

    if (g_LogStats || g_LogDist || g_DetectLeaks || g_UsageByAllocator)
        LOG((LF_DBGALLOC, LL_ALWAYS, "------ Allocation Stats ------\n"));

    // Print out basic statistics.
    if (g_LogStats) {
        LOG((LF_DBGALLOC, LL_ALWAYS, "\n"));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc calls    : %u\n", (int)g_AllocStats.m_Allocs));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc failures : %u\n", (int)g_AllocStats.m_AllocFailures));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc 0s       : %u\n", (int)g_AllocStats.m_ZeroAllocs));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc bytes    : %u\n", (int)g_AllocStats.m_AllocBytes));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Free calls     : %u\n", (int)g_AllocStats.m_Frees));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Free NULLs     : %u\n", (int)g_AllocStats.m_NullFrees));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Free bytes     : %u\n", (int)g_AllocStats.m_FreeBytes));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Leaked allocs  : %u\n", (int)(g_AllocStats.m_Allocs - g_AllocStats.m_AllocFailures) -
             (g_AllocStats.m_Frees - g_AllocStats.m_NullFrees)));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Leaked bytes   : %u\n", (int)g_AllocStats.m_AllocBytes - g_AllocStats.m_FreeBytes));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Max allocation : %u\n", (int)g_AllocStats.m_MaxAlloc));
    }

    // Print out allocation size distribution statistics.
    if (g_LogDist) {
        LOG((LF_DBGALLOC, LL_ALWAYS, "\n"));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Alloc distrib  :\n"));
        for (unsigned i = 0; i < CDA_DIST_BUCKETS; i++)
            LOG((LF_DBGALLOC, LL_ALWAYS, "  [%3u,%3u] : %u\n", i * CDA_DIST_BUCKET_SIZE,
                 (i * CDA_DIST_BUCKET_SIZE) + (CDA_DIST_BUCKET_SIZE - 1),
                 (int)g_AllocBuckets[i]));
        LOG((LF_DBGALLOC, LL_ALWAYS, "  [%3u,---] : %u\n", CDA_MAX_DIST_SIZE + 1, (int)g_LargeAllocs));
    }

    // Print out the table of top allocators. Table is pre-sorted, the first
    // NULL entry indicates the end of the valid list.
    if (g_UsageByAllocator && g_TopAllocators) {
        LOG((LF_DBGALLOC, LL_ALWAYS, "\n"));
        LOG((LF_DBGALLOC, LL_ALWAYS, "Top allocators :\n"));
        for (unsigned i = 0; i < min(CDA_TOP_ALLOCATORS, g_TopAllocatorsSlots); i++) {
            if (g_TopAllocators[i].m_EIP == NULL)
                break;
            LOG((LF_DBGALLOC, LL_ALWAYS, "  %2u: %08X %s\n",
                 i + 1,
                 g_TopAllocators[i].m_EIP,
                 DbgSymbolize(g_TopAllocators[i].m_EIP)));
            LOG((LF_DBGALLOC, LL_ALWAYS, "       %u allocations, %u bytes total, %u bytes average size\n",
                 g_TopAllocators[i].m_Count,
                 (unsigned)g_TopAllocators[i].m_TotalBytes,
                 (unsigned)(g_TopAllocators[i].m_TotalBytes / g_TopAllocators[i].m_Count)));
        }
    }

    // Print out info for all leaked packets.
    if (g_DetectLeaks) {

        DbgAllocHeader *h = g_AllocListFirst;

        // Find first leak after snapshot point
        while (h)
        {
            if (h->m_SN > snapShot)
            {
                break;
            }
            else
            {
                h = h->m_Next;
            }
        }

        int fHaveLeaks = (h!=NULL);

        if (h) {

            // Tell the Log we had memory leaks
            LOG((LF_DBGALLOC, LL_ALWAYS, "\n"));
            LOG((LF_DBGALLOC, LL_ALWAYS, "Detected memory leaks!\n"));
            LOG((LF_DBGALLOC, LL_ALWAYS, "Leaked packets :\n"));

            // Tell the console we had memory leaks
            if (fDoPrintf)
            {
                printf("Detected memory leaks!\n");
                if (pString != NULL)
                    printf("%s\n", pString);

                printf("Leaked packets :\n");
            }
        }

        while (h) {
            if (h->m_SN > snapShot) {
                char buffer1[132];
                char buffer2[32];
                sprintf(buffer1, "#%u %08p:%u ", h->m_SN, CDA_HEADER_TO_DATA(h), (unsigned int) h->m_Length);
                unsigned i;
                for (i = 0; i < 16; i++) {
                    if (i < h->m_Length)
                        sprintf(buffer2, "%02X", (BYTE)CDA_DATA(h, i));
                    else
                        strcpy(buffer2, "  ");
                    if ((i % 4) == 3)
                        strcat_s(buffer2, _countof(buffer2), " ");
                    strcat_s(buffer1, _countof(buffer1), buffer2);
                }
                for (i = 0; i < min(16, h->m_Length); i++) {
                    sprintf(buffer2, "%c", (CDA_DATA(h, i) < 32) || (CDA_DATA(h, i) > 127) ? '.' : CDA_DATA(h, i));
                    strcat_s(buffer1, _countof(buffer1), buffer2);
                }
                LOG((LF_DBGALLOC, LL_ALWAYS, "%s\n", buffer1));
                if (fDoPrintf)
                    printf("%s\n", buffer1);

                if (g_CallStackDepth == 1) {
                    LOG((LF_DBGALLOC, LL_ALWAYS, " Allocated at %08X %s\n",
                         CDA_ALLOC_STACK(h, 0), DbgSymbolize(CDA_ALLOC_STACK(h, 0))));

                if (fDoPrintf)
                    printf(" Allocated at %p %s\n",
                         CDA_ALLOC_STACK(h, 0), DbgSymbolize(CDA_ALLOC_STACK(h, 0)));
                } else {
                    LOG((LF_DBGALLOC, LL_ALWAYS, " Allocation call stack:\n"));
                    if (fDoPrintf)
                        printf(" Allocation call stack:\n");
                    for (unsigned i = 0; i < g_CallStackDepth; i++) {
                        if (CDA_ALLOC_STACK(h, i) == NULL)
                            break;
                        LOG((LF_DBGALLOC, LL_ALWAYS, "  %08X %s\n",
                             CDA_ALLOC_STACK(h, i), DbgSymbolize(CDA_ALLOC_STACK(h, i))));
                        if (fDoPrintf)
                            printf("  %p %s\n",
                                 CDA_ALLOC_STACK(h, i), DbgSymbolize(CDA_ALLOC_STACK(h, i)));
                    }
                }
                wchar_t buf[256];
                GetModuleFileNameW(h->m_hmod, buf, 256);
                LOG((LF_DBGALLOC, LL_ALWAYS, " Base, name: %08X %S\n\n", h->m_hmod, buf));
                if (fDoPrintf)
                    printf(" Base, name: %p %S\n\n", h->m_hmod, buf);
            }

            h = h->m_Next;
        }

        if (fDoPrintf)
            fflush(stdout);

        if (fHaveLeaks && g_AssertOnLeaks)
            _ASSERTE(!"Detected memory leaks!");

    }

    if (g_LogStats || g_LogDist || g_DetectLeaks || g_UsageByAllocator) {
        LOG((LF_DBGALLOC, LL_ALWAYS, "\n"));
        LOG((LF_DBGALLOC, LL_ALWAYS, "------------------------------\n"));
    }

    CDA_UNLOCK();

    if (fDone)
    {
        DbgUnloadSymbols();
        UnsafeDeleteCriticalSection(&g_AllocMutex);
        // We won't be doing any more of our debug allocation stuff
        g_DbgEnabled=0;
    }
}