1390 lines
29 KiB
C
1390 lines
29 KiB
C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <Types.h>
|
|
#include <Files.h>
|
|
#include <Gestalt.h>
|
|
#include <Processes.h>
|
|
|
|
#include "TypesAndSwitches.h"
|
|
#include "MemoryTracker.h"
|
|
#include "prthread.h"
|
|
|
|
|
|
/*
|
|
* These should all be runtime options.
|
|
*/
|
|
#define LOG_ALL_OPEN_SETS 1
|
|
//#define LOG_SITE_LEAKS_ONLY 1
|
|
|
|
/*
|
|
* Considering that this is meant to help us reduce memory consumption, my use of memory
|
|
* here is pretty appalling. However, I think optimizations here are only worth the time
|
|
* if they're needed. The most important thing is to get this working quickly and start
|
|
* using it. Then I'll come back and fix this other stuff up.
|
|
*/
|
|
|
|
#define kNumBlocksTracked 100000
|
|
#define kBlockAllocationLongs (( kNumBlocksTracked + 31 ) >> 5 )
|
|
#define kHashMod 1091
|
|
#define kNumAllocationSites 30000
|
|
#define kNumAllocationSets 20
|
|
#define kMaxInstructionScanDistance 4096
|
|
#define kInvalidIndex 0xFFFFFFFF
|
|
|
|
|
|
/*
|
|
* Hash Table crap
|
|
*/
|
|
typedef struct HashBlock HashBlock;
|
|
typedef struct HashTable HashTable;
|
|
typedef struct AllocationSite AllocationSite;
|
|
|
|
typedef unsigned long (*HashFunction)( void * key );
|
|
typedef Boolean (*HashCompare) ( void * key1, void *key2 );
|
|
|
|
|
|
struct HashBlock {
|
|
struct HashBlock * next;
|
|
char key[];
|
|
};
|
|
|
|
struct HashTable {
|
|
HashFunction hashFunction;
|
|
HashCompare compareFunction;
|
|
HashBlock * index[ kHashMod ];
|
|
};
|
|
|
|
|
|
static void HashInitialize ( HashTable * table, HashFunction hash, HashCompare compare );
|
|
static HashBlock * HashFind ( HashTable * table, void * key );
|
|
static void HashInsert ( HashTable * table, HashBlock * block );
|
|
static void HashRemove ( HashTable * table, HashBlock * block );
|
|
|
|
|
|
|
|
typedef struct Block {
|
|
unsigned long blockNum;
|
|
unsigned long blockID;
|
|
unsigned long blockSize;
|
|
void * blockAddress;
|
|
AllocationSite * site;
|
|
unsigned char allocType;
|
|
unsigned char blockState;
|
|
unsigned char refCount;
|
|
unsigned char overhead;
|
|
struct Block * next;
|
|
} Block;
|
|
|
|
struct AllocationSet {
|
|
unsigned long numBlocks;
|
|
unsigned long totalAllocation;
|
|
unsigned long currentAllocation;
|
|
unsigned long maxAllocation;
|
|
unsigned long blockSet[ kBlockAllocationLongs ];
|
|
unsigned char inUse;
|
|
unsigned char enabledState;
|
|
char name[ 256 ];
|
|
};
|
|
|
|
typedef Block * Bucket;
|
|
|
|
struct AllocationSite {
|
|
AllocationSite * next;
|
|
void * stackCrawl[ kStackDepth ];
|
|
unsigned long siteIndex;
|
|
unsigned long currentBlocks;
|
|
unsigned long currentMemUsed;
|
|
unsigned long maxMemUsed;
|
|
unsigned long totalBlocks;
|
|
unsigned long totalMemUsed;
|
|
};
|
|
|
|
|
|
pascal void TrackerExitToShellPatch(void);
|
|
asm void * GetCurrentStackPointer();
|
|
|
|
#if DEBUG_MAC_MEMORY
|
|
Block gBlockPool[ kNumBlocksTracked ];
|
|
Block * gFreeBlocks;
|
|
Block * gBlockIndex[ kHashMod ];
|
|
unsigned long gIndexSize;
|
|
unsigned long gBlockNumber;
|
|
unsigned long gNumActiveTrackerBlocks;
|
|
unsigned long gNumHeapBlocks;
|
|
unsigned long gMaxNumHeapBlocks;
|
|
unsigned long gNumAllocations;
|
|
AllocationSite gAllocationSites[ kNumAllocationSites ];
|
|
unsigned long gAllocationSitesUsed;
|
|
HashTable gSiteTable;
|
|
AllocationSet gAllocationSetPool[ kNumAllocationSets ];
|
|
unsigned char gTrackerInitialized = false;
|
|
Boolean gNeverInitialized = true;
|
|
unsigned long gTrackerEnableState = 0;
|
|
|
|
static void * gMemoryTop;
|
|
static short gLogFile = 0;
|
|
|
|
|
|
UniversalProcPtr gExitToShellPatchCallThru = NULL;
|
|
#if GENERATINGCFM
|
|
enum {
|
|
uppExitToShellProcInfo = kPascalStackBased
|
|
};
|
|
|
|
RoutineDescriptor gExitToShellPatchRD = BUILD_ROUTINE_DESCRIPTOR(uppExitToShellProcInfo, &TrackerExitToShellPatch);
|
|
#else
|
|
#define gExitToShellPatchRD ExitToShellPatch
|
|
#endif
|
|
#endif
|
|
|
|
void * gOurApplicationHeapBase;
|
|
void * gOurApplicationHeapMax;
|
|
|
|
|
|
static Block * AllocateBlockFromPool ( void );
|
|
static void FreeBlockFromPool ( Block * block );
|
|
static Block * FindBlock ( void * address );
|
|
static void InsertBlock ( Block * block );
|
|
static void RemoveBlock ( Block * block );
|
|
static Block * FindBlockBucket ( void * address, Bucket ** bucket );
|
|
static void AddBlockReference ( Block * block );
|
|
static void RemoveBlockReference ( Block * block );
|
|
static void CatenateRoutineNameFromPC( char * string, void *currentPC );
|
|
static void PrintHexNumber( char * string, UInt32 hexNumber );
|
|
static void PrintStackCrawl ( char * name, void ** stackCrawl );
|
|
|
|
static void AddNewBlockToAllocationSet ( AllocationSet * set, Block * block );
|
|
static void MarkBlockFreeFromSet ( AllocationSet * set, Block * block );
|
|
static void BlockFreedFromSite ( AllocationSite * site, unsigned long blockSize );
|
|
|
|
|
|
static unsigned long AllocationSiteHash( void ** stackCrawl );
|
|
static Boolean AllocationSiteHashCompare ( void ** site1, void ** site2 );
|
|
|
|
void InitializeMemoryTracker ( void )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long count;
|
|
unsigned long blockCount;
|
|
AllocationSet * set;
|
|
Block * block;
|
|
Block ** blockIndex;
|
|
OSErr err;
|
|
ProcessSerialNumber thisProcess = { 0, kCurrentProcess };
|
|
ProcessInfoRec processInfo;
|
|
|
|
processInfo.processInfoLength = sizeof(processInfo);
|
|
processInfo.processName = NULL;
|
|
processInfo.processAppSpec = NULL;
|
|
|
|
GetProcessInformation(&thisProcess, &processInfo);
|
|
|
|
gOurApplicationHeapBase = processInfo.processLocation;
|
|
gOurApplicationHeapMax = (Ptr)gOurApplicationHeapBase + processInfo.processSize;
|
|
|
|
err = Gestalt ( gestaltLogicalRAMSize, (long *) &gMemoryTop );
|
|
if ( err != noErr )
|
|
{
|
|
DebugStr ( "\pThis machine has no logical ram?" );
|
|
ExitToShell();
|
|
}
|
|
|
|
/* do any one time init */
|
|
if ( gNeverInitialized )
|
|
{
|
|
gNeverInitialized = false;
|
|
gExitToShellPatchCallThru = GetToolboxTrapAddress(0x01F4);
|
|
SetToolboxTrapAddress((UniversalProcPtr)&gExitToShellPatchRD, 0x01F4);
|
|
}
|
|
|
|
/* Be sure to dispose of ourselves if already allocated */
|
|
if ( gTrackerInitialized != false )
|
|
{
|
|
ExitMemoryTracker();
|
|
}
|
|
|
|
gBlockNumber = 0;
|
|
gNumActiveTrackerBlocks = 0;
|
|
gNumHeapBlocks = 0;
|
|
gMaxNumHeapBlocks = 0;
|
|
gNumAllocations = 0;
|
|
gAllocationSitesUsed = 0;
|
|
gIndexSize = 0;
|
|
|
|
FSDelete ( "\pMemoryLog.bin", 0 );
|
|
Create ( "\pMemoryLog.bin", 0, 'shan', 'binl' );
|
|
err = FSOpen ( "\pMemoryLog.bin", 0, &gLogFile );
|
|
if ( err != noErr )
|
|
{
|
|
gLogFile = 0;
|
|
}
|
|
|
|
block = gBlockPool;
|
|
gFreeBlocks = NULL;
|
|
|
|
for ( count = 0; count < kNumBlocksTracked; ++count )
|
|
{
|
|
block->refCount = 0;
|
|
block->blockAddress = NULL;
|
|
block->blockID = count;
|
|
block->next = gFreeBlocks;
|
|
gFreeBlocks = block;
|
|
|
|
++block;
|
|
}
|
|
|
|
blockIndex = gBlockIndex;
|
|
|
|
for ( count = 0; count < kHashMod; ++count )
|
|
{
|
|
*blockIndex++ = NULL;
|
|
}
|
|
|
|
set = gAllocationSetPool;
|
|
|
|
for ( count = 0; count < kNumAllocationSets; ++count )
|
|
{
|
|
set->inUse = false;
|
|
|
|
for ( blockCount = 0; blockCount < kBlockAllocationLongs; ++blockCount )
|
|
{
|
|
set->blockSet[ blockCount ] = 0;
|
|
}
|
|
|
|
++set;
|
|
}
|
|
|
|
HashInitialize ( &gSiteTable, (HashFunction) &AllocationSiteHash,
|
|
(HashCompare) AllocationSiteHashCompare );
|
|
|
|
gTrackerInitialized = true;
|
|
gTrackerEnableState = 0;
|
|
|
|
NewAllocationSet ( 0, "InitializeMemoryTracker" );
|
|
#endif
|
|
}
|
|
|
|
|
|
void ExitMemoryTracker ( void )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
gTrackerInitialized = false;
|
|
|
|
if ( gLogFile != NULL )
|
|
{
|
|
FSClose ( gLogFile );
|
|
gLogFile = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void DisableMemoryTracker ( void )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
++gTrackerEnableState;
|
|
#endif
|
|
}
|
|
|
|
|
|
void EnableMemoryTracker ( void )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
if ( gTrackerEnableState > 0 )
|
|
{
|
|
--gTrackerEnableState;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void DumpMemoryTrackerState ( void )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long count;
|
|
|
|
printf ( "Number of tracker blocks in use: %ld\n", gNumActiveTrackerBlocks );
|
|
printf ( "Number of heap blocks in use: %ld\n", gNumHeapBlocks );
|
|
printf ( "Max Number of heap blocks in use: %ld\n", gMaxNumHeapBlocks );
|
|
printf ( "Total number of allcations %ld\n", gNumAllocations );
|
|
|
|
#if LOG_ALL_OPEN_SETS
|
|
/* Log all active allocation sets */
|
|
for ( count = 0; count < kNumAllocationSets; ++count )
|
|
{
|
|
if ( gAllocationSetPool[ count ].inUse == true )
|
|
{
|
|
LogAllocationSetState ( &gAllocationSetPool[ count ] );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
DumpAllocationSites();
|
|
#endif
|
|
}
|
|
|
|
|
|
void DumpAllocationSites ( void )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long count;
|
|
AllocationSite * site;
|
|
AllocationSiteLogEntry logEntry;
|
|
AllocationSiteLog logHeader;
|
|
long size;
|
|
OSErr err;
|
|
|
|
if ( gLogFile != NULL )
|
|
{
|
|
logHeader.header.logTag = kALLOCATION_SITE_LIST;
|
|
logHeader.header.logSize = sizeof(AllocationSiteLog) +
|
|
( gAllocationSitesUsed * sizeof(AllocationSiteLogEntry) );
|
|
logHeader.numEntries = gAllocationSitesUsed;
|
|
|
|
size = sizeof(logHeader);
|
|
err = FSWrite ( gLogFile, &size, &logHeader );
|
|
if ( err != noErr )
|
|
{
|
|
DebugStr ( "\pWrite failed" );
|
|
return;
|
|
}
|
|
|
|
for ( count = 0; count < gAllocationSitesUsed; ++count )
|
|
{
|
|
site = &gAllocationSites[ count ];
|
|
|
|
memset( &logEntry, 0, sizeof(logEntry) );
|
|
|
|
PrintStackCrawl ( logEntry.stackNames, site->stackCrawl );
|
|
|
|
logEntry.currentBlocks = site->currentBlocks;
|
|
logEntry.currentMemUsed = site->currentMemUsed;
|
|
logEntry.maxMemUsed = site->maxMemUsed;
|
|
logEntry.totalBlocks = site->totalBlocks;
|
|
logEntry.totalMemUsed = site->totalMemUsed;
|
|
|
|
size = sizeof(logEntry);
|
|
err = FSWrite ( gLogFile, &size, &logEntry );
|
|
if ( err != noErr )
|
|
{
|
|
DebugStr ( "\pWrite failed" );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
void AddNewBlock ( void * address, size_t blockSize, size_t overhead, AllocatorType allocType,
|
|
void * stackCrawl )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
AllocationSite * site;
|
|
Block * block;
|
|
unsigned long setCount;
|
|
|
|
++gNumHeapBlocks;
|
|
++gNumAllocations;
|
|
|
|
if ( gNumHeapBlocks > gMaxNumHeapBlocks )
|
|
{
|
|
gMaxNumHeapBlocks = gNumHeapBlocks;
|
|
}
|
|
|
|
/* tracking a null block will hose us big time */
|
|
if ( ( address == NULL ) || ( gTrackerInitialized == false ) || ( gTrackerEnableState > 0 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Find a free block in our block pool
|
|
*/
|
|
block = AllocateBlockFromPool();
|
|
|
|
/* if we found a block, allocate it */
|
|
if ( block != NULL )
|
|
{
|
|
/* Find the allocation site for this block */
|
|
site = NewAllocationSite ( stackCrawl, blockSize );
|
|
|
|
block->blockNum = gBlockNumber++;
|
|
block->blockSize = blockSize;
|
|
block->blockAddress = address;
|
|
block->allocType = allocType;
|
|
block->refCount = 0;
|
|
block->overhead = overhead;
|
|
block->blockState = kBlockAllocated;
|
|
block->site = site;
|
|
|
|
/* insert this block into the block index */
|
|
InsertBlock ( block );
|
|
|
|
/* add our own reference to this block */
|
|
AddBlockReference ( block );
|
|
|
|
/* and then add it to all relevant allocation sets */
|
|
for ( setCount = 0; setCount < kNumAllocationSets; ++setCount )
|
|
{
|
|
if ( gAllocationSetPool[ setCount ].inUse == true )
|
|
{
|
|
AddNewBlockToAllocationSet ( &gAllocationSetPool[ setCount ], block );
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
#pragma unused(address, blockSize, overhead, allocType, stackCrawl)
|
|
#endif
|
|
}
|
|
|
|
|
|
void FreeBlock ( void * address )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long count;
|
|
Block * block;
|
|
|
|
--gNumHeapBlocks;
|
|
|
|
if ( ( gTrackerInitialized == false ) || ( gTrackerEnableState > 0 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
block = NULL;
|
|
|
|
block = FindBlockBucket ( address, NULL );
|
|
|
|
if ( block != NULL )
|
|
{
|
|
block->blockState = kBlockFree;
|
|
|
|
BlockFreedFromSite ( block->site, block->blockSize );
|
|
|
|
/* remove our own reference from this block */
|
|
RemoveBlockReference ( block );
|
|
|
|
/* remove block from all sets */
|
|
for ( count = 0; count < kNumAllocationSets; ++count )
|
|
{
|
|
if ( gAllocationSetPool[ count ].inUse == true )
|
|
{
|
|
MarkBlockFreeFromSet ( &gAllocationSetPool[ count ], block );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// DebugStr ( "\pCan't find block?" );
|
|
}
|
|
#else
|
|
#pragma unused(address)
|
|
#endif
|
|
}
|
|
|
|
AllocationSite * NewAllocationSite ( void ** stackCrawl, unsigned long blockSize )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
AllocationSite * site;
|
|
unsigned long stackCount;
|
|
|
|
site = (AllocationSite *) HashFind ( &gSiteTable, stackCrawl );
|
|
if ( site == NULL )
|
|
{
|
|
if ( gAllocationSitesUsed < kNumAllocationSites )
|
|
{
|
|
site = &gAllocationSites[ gAllocationSitesUsed++ ];
|
|
for ( stackCount = 0; stackCount < kStackDepth; ++stackCount )
|
|
{
|
|
site->stackCrawl[ stackCount ] = stackCrawl[ stackCount ];
|
|
}
|
|
|
|
site->siteIndex = gAllocationSitesUsed - 1;
|
|
site->currentBlocks = 0;
|
|
site->currentMemUsed = 0;
|
|
site->maxMemUsed = 0;
|
|
site->totalBlocks = 0;
|
|
site->totalMemUsed = 0;
|
|
}
|
|
else
|
|
{
|
|
// DebugStr ( "\pOut of allocation sites..." );
|
|
}
|
|
|
|
HashInsert ( &gSiteTable, (HashBlock *) site );
|
|
}
|
|
|
|
if ( site != NULL )
|
|
{
|
|
++site->currentBlocks;
|
|
site->currentMemUsed += blockSize;
|
|
if ( site->currentMemUsed > site->maxMemUsed )
|
|
{
|
|
site->maxMemUsed = site->currentMemUsed;
|
|
}
|
|
|
|
++site->totalBlocks;
|
|
site->totalMemUsed += blockSize;
|
|
}
|
|
|
|
return site;
|
|
#else
|
|
#pragma unused(stackCrawl, blockSize)
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
void BlockFreedFromSite ( AllocationSite * site, unsigned long blockSize )
|
|
{
|
|
if ( site != NULL )
|
|
{
|
|
--site->currentBlocks;
|
|
site->currentMemUsed -= blockSize;
|
|
}
|
|
}
|
|
|
|
|
|
static unsigned long AllocationSiteHash( void ** stackCrawl )
|
|
{
|
|
unsigned long hash;
|
|
unsigned long count;
|
|
|
|
hash = 0;
|
|
|
|
for ( count = 0; count < kStackDepth; ++count )
|
|
{
|
|
hash += (unsigned long) stackCrawl[ count ];
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
static Boolean AllocationSiteHashCompare ( void ** site1, void ** site2 )
|
|
{
|
|
Boolean matched;
|
|
unsigned long count;
|
|
|
|
matched = true;
|
|
for ( count = 0; count < kStackDepth; ++count )
|
|
{
|
|
if ( site1[ count ] != site2[ count ] )
|
|
{
|
|
matched = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return matched;
|
|
}
|
|
|
|
|
|
AllocationSet * NewAllocationSet ( unsigned long trackingOptions, char * name )
|
|
{
|
|
#pragma unused(trackingOptions)
|
|
#if DEBUG_MAC_MEMORY
|
|
AllocationSet * set;
|
|
unsigned long count;
|
|
|
|
set = NULL;
|
|
|
|
/* find a free set */
|
|
for ( count = 0; count < kNumAllocationSets; ++count )
|
|
{
|
|
if ( gAllocationSetPool[ count ].inUse == false )
|
|
{
|
|
set = &gAllocationSetPool[ count ];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( set != NULL )
|
|
{
|
|
set->inUse = true;
|
|
set->numBlocks = 0;
|
|
set->totalAllocation = 0;
|
|
set->currentAllocation = 0;
|
|
set->maxAllocation = 0;
|
|
set->enabledState = 0;
|
|
strcpy ( set->name, name );
|
|
|
|
/* clear all blocks from this set */
|
|
for ( count = 0; count < kBlockAllocationLongs; ++count )
|
|
{
|
|
set->blockSet[ count ] = 0;
|
|
}
|
|
}
|
|
|
|
return set;
|
|
#else
|
|
#pragma unused(name)
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
|
|
void EnableAllocationSet ( AllocationSet * set )
|
|
{
|
|
if ( set->enabledState > 0 )
|
|
{
|
|
--set->enabledState;
|
|
}
|
|
}
|
|
|
|
|
|
void DisableAllocationSet ( AllocationSet * set )
|
|
{
|
|
if ( set->enabledState == 255 )
|
|
{
|
|
// DebugStr ( "\pAllocationSet's enabledState overflowed!" );
|
|
}
|
|
|
|
++set->enabledState;
|
|
}
|
|
|
|
|
|
void AddNewBlockToAllocationSet ( AllocationSet * set, Block * block )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long blockID;
|
|
unsigned long blockMask;
|
|
unsigned long * blockSetLong;
|
|
|
|
/* if we're not enabled, then bail */
|
|
if ( set->enabledState != 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
blockID = block->blockID;
|
|
blockSetLong = &set->blockSet[ blockID >> 5 ];
|
|
|
|
blockMask = 1L << ( 31 - ( blockID & 0x1F ) );
|
|
|
|
if ( *blockSetLong & blockMask )
|
|
{
|
|
// DebugStr ( "\pWe suck, this block is already part of the set" );
|
|
return;
|
|
}
|
|
|
|
set->numBlocks++;
|
|
set->totalAllocation += block->blockSize;
|
|
set->currentAllocation += block->blockSize;
|
|
|
|
if ( set->currentAllocation > set->maxAllocation )
|
|
{
|
|
set->maxAllocation = set->currentAllocation;
|
|
}
|
|
|
|
*blockSetLong |= blockMask;
|
|
AddBlockReference ( block );
|
|
#else
|
|
#pragma unused(set, block)
|
|
#endif
|
|
}
|
|
|
|
|
|
void MarkBlockFreeFromSet ( AllocationSet * set, Block * block )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long blockID;
|
|
unsigned long blockMask;
|
|
unsigned long * blockSetLong;
|
|
|
|
blockID = block->blockID;
|
|
blockSetLong = &set->blockSet[ blockID >> 5 ];
|
|
|
|
blockMask = 1L << ( 31 - ( blockID & 0x1F ) );
|
|
|
|
if ( ( *blockSetLong & blockMask ) == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
*blockSetLong &= ~blockMask;
|
|
|
|
set->numBlocks--;
|
|
set->currentAllocation -= block->blockSize;
|
|
|
|
RemoveBlockReference ( block );
|
|
#else
|
|
#pragma unused(set, block)
|
|
#endif
|
|
}
|
|
|
|
|
|
void LogAllocationSetState ( AllocationSet * set )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long blockCount;
|
|
unsigned long blockMask;
|
|
unsigned long * setLongPtr;
|
|
unsigned long setLong;
|
|
Block * block;
|
|
AllocationSetLogEntry logEntry;
|
|
AllocationSetLog logHeader;
|
|
long size;
|
|
OSErr err;
|
|
|
|
if ( set == NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( gLogFile != NULL )
|
|
{
|
|
memset( &logHeader, 0, sizeof(logHeader) );
|
|
logHeader.header.logTag = kSET_BLOCK_LIST;
|
|
logHeader.header.logSize = sizeof(AllocationSetLog) +
|
|
( set->numBlocks * sizeof(AllocationSetLogEntry) );
|
|
|
|
logHeader.numEntries = set->numBlocks;
|
|
logHeader.totalAllocation = set->totalAllocation;
|
|
logHeader.currentAllocation = set->currentAllocation;
|
|
logHeader.maxAllocation = set->maxAllocation;
|
|
strcpy ( logHeader.name, set->name );
|
|
|
|
size = sizeof(logHeader);
|
|
err = FSWrite ( gLogFile, &size, &logHeader );
|
|
if ( err != noErr )
|
|
{
|
|
DebugStr ( "\pWrite failed" );
|
|
return;
|
|
}
|
|
|
|
blockMask = 0;
|
|
setLongPtr = set->blockSet;
|
|
|
|
for ( blockCount = 0; blockCount < kNumBlocksTracked; ++blockCount )
|
|
{
|
|
if ( blockMask == 0 )
|
|
{
|
|
blockMask = 0x80000000;
|
|
setLong = *setLongPtr++;
|
|
}
|
|
|
|
if ( setLong & blockMask )
|
|
{
|
|
block = &gBlockPool[ blockCount ];
|
|
|
|
memset( &logEntry, 0, sizeof(logEntry) );
|
|
logEntry.address = (unsigned long) block->blockAddress;
|
|
logEntry.blockNumber = block->blockNum;
|
|
logEntry.size = block->blockSize;
|
|
if ( block->site != NULL )
|
|
{
|
|
logEntry.siteIndex = block->site->siteIndex;
|
|
}
|
|
else
|
|
{
|
|
logEntry.siteIndex = -1;
|
|
}
|
|
|
|
logEntry.allocType = block->allocType;
|
|
logEntry.blockState = block->blockState;
|
|
logEntry.overhead = block->overhead;
|
|
|
|
size = sizeof(logEntry);
|
|
err = FSWrite ( gLogFile, &size, &logEntry );
|
|
if ( err != noErr )
|
|
{
|
|
DebugStr ( "\pWrite failed" );
|
|
return;
|
|
}
|
|
}
|
|
|
|
blockMask >>= 1;
|
|
}
|
|
}
|
|
#else
|
|
#pragma unused(set)
|
|
#endif
|
|
}
|
|
|
|
|
|
void DisposeAllocationSet ( AllocationSet * set )
|
|
{
|
|
#if DEBUG_MAC_MEMORY
|
|
unsigned long blockIndex;
|
|
unsigned long * setBlocksPtr;
|
|
unsigned long setBlocksLong;
|
|
unsigned long blockMask;
|
|
|
|
/* release all the blocks we own */
|
|
setBlocksPtr = set->blockSet;
|
|
blockMask = 0;
|
|
|
|
for ( blockIndex = 0; blockIndex < kNumBlocksTracked; ++blockIndex )
|
|
{
|
|
if ( blockMask == 0 )
|
|
{
|
|
blockMask = 0x80000000;
|
|
setBlocksLong = *setBlocksPtr++;
|
|
}
|
|
|
|
if ( blockMask & setBlocksLong )
|
|
{
|
|
RemoveBlockReference ( &gBlockPool[ blockIndex ] );
|
|
}
|
|
|
|
blockMask >>= 1;
|
|
}
|
|
|
|
set->inUse = false;
|
|
#else
|
|
#pragma unused(set)
|
|
#endif
|
|
}
|
|
|
|
|
|
/* These utility routines can all go if we're not on */
|
|
#if DEBUG_MAC_MEMORY
|
|
static Block * AllocateBlockFromPool ( void )
|
|
{
|
|
Block * block;
|
|
|
|
block = gFreeBlocks;
|
|
if ( block != NULL )
|
|
{
|
|
++gNumActiveTrackerBlocks;
|
|
gFreeBlocks = block->next;
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
|
|
static void FreeBlockFromPool ( Block * block )
|
|
{
|
|
/* this sucks to research the hash table, but I don't want to eat more */
|
|
/* memory */
|
|
RemoveBlock ( block );
|
|
|
|
--gNumActiveTrackerBlocks;
|
|
|
|
block->next = gFreeBlocks;
|
|
gFreeBlocks = block;
|
|
}
|
|
|
|
|
|
static void InsertBlock ( Block * block )
|
|
{
|
|
Bucket * bucket;
|
|
|
|
bucket = &gBlockIndex[ (unsigned long) block->blockAddress % kHashMod ];
|
|
block->next = *bucket;
|
|
*bucket = block;
|
|
}
|
|
|
|
|
|
static void RemoveBlock ( Block * block )
|
|
{
|
|
Bucket * bucket;
|
|
Block * prev;
|
|
Block * next;
|
|
Block * walker;
|
|
|
|
bucket = &gBlockIndex[ (unsigned long) block->blockAddress % kHashMod ];
|
|
|
|
/* walk the list, find our guy and remove it */
|
|
prev = NULL;
|
|
walker = *bucket;
|
|
|
|
while ( walker != NULL )
|
|
{
|
|
next = walker->next;
|
|
|
|
if ( walker == block )
|
|
{
|
|
if ( prev == NULL )
|
|
{
|
|
/* first block in the list */
|
|
*bucket = next;
|
|
}
|
|
else
|
|
{
|
|
prev->next = next;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
prev = walker;
|
|
walker = next;
|
|
}
|
|
}
|
|
|
|
|
|
static Block * FindBlockBucket ( void * address, Bucket ** bucket )
|
|
{
|
|
unsigned long hashIndex;
|
|
Bucket * hashBucket;
|
|
Block * blockWalker;
|
|
Block * block;
|
|
|
|
block = NULL;
|
|
|
|
hashIndex = (unsigned long) address % kHashMod;
|
|
hashBucket = &gBlockIndex[ hashIndex ];
|
|
if ( bucket != NULL )
|
|
{
|
|
*bucket = hashBucket;
|
|
}
|
|
|
|
blockWalker = *hashBucket;
|
|
while ( blockWalker != NULL )
|
|
{
|
|
if ( blockWalker->blockAddress == address )
|
|
{
|
|
block = blockWalker;
|
|
break;
|
|
}
|
|
|
|
blockWalker = blockWalker->next;
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
|
|
static void AddBlockReference ( Block * block )
|
|
{
|
|
++block->refCount;
|
|
|
|
/* make sure we didn't wrap the refCount */
|
|
assert(block->refCount != 0);
|
|
}
|
|
|
|
static void RemoveBlockReference ( Block * block )
|
|
{
|
|
if ( --block->refCount == 0 )
|
|
{
|
|
FreeBlockFromPool ( block );
|
|
}
|
|
}
|
|
|
|
|
|
static void PrintStackCrawl ( char * name, void ** stackCrawl )
|
|
{
|
|
unsigned long currentStackLevel;
|
|
|
|
*name = 0;
|
|
|
|
for (currentStackLevel = 0; currentStackLevel < kStackDepth; currentStackLevel++)
|
|
{
|
|
CatenateRoutineNameFromPC ( name, stackCrawl[ currentStackLevel ] );
|
|
|
|
if (currentStackLevel < kStackDepth - 1)
|
|
strcat( name, "," );
|
|
}
|
|
}
|
|
|
|
|
|
static void CatenateRoutineNameFromPC( char * string, void *currentPC )
|
|
{
|
|
UInt32 instructionsToLook = kMaxInstructionScanDistance;
|
|
UInt32 *currentPCAsLong = (UInt32 *)currentPC;
|
|
UInt32 offset = 0;
|
|
char labelString[ 512 ];
|
|
char lameString[ 512 ];
|
|
|
|
if (currentPC == NULL)
|
|
return;
|
|
|
|
/* make sure we're not out in the weeds */
|
|
if ( (unsigned long) currentPC > (unsigned long) gMemoryTop )
|
|
return;
|
|
|
|
if ( (unsigned long) currentPC & 0x3 )
|
|
return;
|
|
|
|
*labelString = 0;
|
|
*lameString = 0;
|
|
|
|
while (instructionsToLook--) {
|
|
|
|
if (*currentPCAsLong == 0x4E800020) {
|
|
char stringLength = *((char *)currentPCAsLong + 21);
|
|
memset( labelString, 0, 512 );
|
|
BlockMoveData( ((char *)currentPCAsLong + 22), labelString, stringLength );
|
|
|
|
// if there was no routine name, then just put down the address.
|
|
if ( *labelString == 0 )
|
|
{
|
|
PrintHexNumber( lameString, (unsigned long) currentPC );
|
|
goto bail;
|
|
}
|
|
else
|
|
{
|
|
strcat ( lameString, labelString );
|
|
}
|
|
break;
|
|
}
|
|
|
|
currentPCAsLong++;
|
|
}
|
|
|
|
instructionsToLook = kMaxInstructionScanDistance;
|
|
currentPCAsLong = (UInt32 *)currentPC;
|
|
|
|
*labelString = 0;
|
|
|
|
while (instructionsToLook--) {
|
|
if (*currentPCAsLong == 0x7C0802A6) {
|
|
strcat ( lameString, "+" );
|
|
|
|
PrintHexNumber( labelString, offset - 4 );
|
|
strcat ( lameString, labelString );
|
|
break;
|
|
}
|
|
currentPCAsLong--;
|
|
offset += 4;
|
|
}
|
|
|
|
bail:
|
|
/* make sure we don't end up being too big */
|
|
lameString[ kNameMaxLength ] = 0;
|
|
strcat ( string, lameString );
|
|
}
|
|
|
|
|
|
static void PrintHexNumber( char * string, UInt32 hexNumber )
|
|
{
|
|
char hexString[11];
|
|
char hexMap[] = "0123456789ABCDEF";
|
|
long digits = 8;
|
|
|
|
|
|
hexString[0] = '0';
|
|
hexString[1] = 'x';
|
|
|
|
while (digits) {
|
|
hexString[digits + 1] = *((hexNumber & 0x0F) + hexMap);
|
|
hexNumber = hexNumber >> 4;
|
|
digits--;
|
|
}
|
|
hexString[10] = 0;
|
|
|
|
strcat ( string, hexString );
|
|
}
|
|
|
|
|
|
static void HashInitialize ( HashTable * table, HashFunction hash, HashCompare compare )
|
|
{
|
|
unsigned long count;
|
|
|
|
table->hashFunction = hash;
|
|
table->compareFunction = compare;
|
|
|
|
for ( count = 0; count < kHashMod; ++count )
|
|
{
|
|
table->index[ count ] = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static void HashInsert ( HashTable * table, HashBlock * block )
|
|
{
|
|
HashBlock ** bucket;
|
|
unsigned long hash;
|
|
|
|
hash = (*table->hashFunction) ( block->key ) % kHashMod;
|
|
|
|
bucket = &table->index[ hash ];
|
|
block->next = *bucket;
|
|
*bucket = block;
|
|
}
|
|
|
|
|
|
static void HashRemove ( HashTable * table, HashBlock * block )
|
|
{
|
|
HashBlock ** bucket;
|
|
HashBlock * prev;
|
|
HashBlock * next;
|
|
HashBlock * walker;
|
|
unsigned long hash;
|
|
|
|
hash = (*table->hashFunction) ( block->key ) % kHashMod;
|
|
|
|
bucket = &table->index[ hash ];
|
|
|
|
/* walk the list, find our guy and remove it */
|
|
prev = NULL;
|
|
walker = *bucket;
|
|
|
|
while ( walker != NULL )
|
|
{
|
|
next = walker->next;
|
|
|
|
if ( (*table->compareFunction)( walker->key, block->key ) )
|
|
{
|
|
if ( prev == NULL )
|
|
{
|
|
/* first block in the list */
|
|
*bucket = next;
|
|
}
|
|
else
|
|
{
|
|
prev->next = next;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
prev = walker;
|
|
walker = next;
|
|
}
|
|
}
|
|
|
|
|
|
static HashBlock * HashFind ( HashTable * table, void * key )
|
|
{
|
|
HashBlock ** bucket;
|
|
HashBlock * blockWalker;
|
|
HashBlock * block;
|
|
unsigned long hash;
|
|
|
|
hash = (*table->hashFunction) ( key ) % kHashMod;
|
|
|
|
bucket = &table->index[ hash ];
|
|
|
|
block = NULL;
|
|
blockWalker = *bucket;
|
|
|
|
while ( blockWalker != NULL )
|
|
{
|
|
if ( (*table->compareFunction) ( blockWalker->key, key ) )
|
|
{
|
|
block = blockWalker;
|
|
break;
|
|
}
|
|
|
|
blockWalker = blockWalker->next;
|
|
}
|
|
|
|
return block;
|
|
}
|
|
|
|
pascal void TrackerExitToShellPatch(void)
|
|
{
|
|
static Boolean gBeenHere = false;
|
|
|
|
/* if we haven't been here, then dump our memory state, otherwise don't */
|
|
/* this way we don't hose ourselves if we crash in here */
|
|
if ( !gBeenHere )
|
|
{
|
|
gBeenHere = true;
|
|
DumpMemoryTrackerState();
|
|
}
|
|
|
|
ExitMemoryTracker();
|
|
|
|
#if GENERATINGCFM
|
|
CallUniversalProc(gExitToShellPatchCallThru, uppExitToShellProcInfo);
|
|
#else
|
|
{
|
|
ExitToShellProc *exitProc = (ExitToShellProc *)&gExitToShellPatchCallThru;
|
|
(*exitProc)();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
asm void *GetCurrentStackPointer()
|
|
{
|
|
mr r3, sp
|
|
lwz r3, 0(r3)
|
|
blr
|
|
}
|
|
|
|
|
|
void GetCurrentStackTrace(void **stackCrawl)
|
|
{
|
|
void * currentSP;
|
|
void * interfaceLibSP;
|
|
void * callerFirstStackFrame;
|
|
void * callerSP;
|
|
UInt32 stackFrameLevel;
|
|
UInt8 isPowerPC = true;
|
|
void * nextFrame;
|
|
long nativeFrames;
|
|
PRThread * curThread;
|
|
void * stackMin;
|
|
void * stackMax;
|
|
|
|
memset(stackCrawl, 0, sizeof(void *) * kStackDepth);
|
|
|
|
#if !GENERATINGPOWERPC
|
|
return;
|
|
#endif
|
|
|
|
curThread = PR_CurrentThread();
|
|
if ( curThread != NULL && curThread->stack->stackBottom != 0 )
|
|
{
|
|
stackMin = curThread->stack->stackBottom;
|
|
stackMax = curThread->stack->stackTop;
|
|
}
|
|
else
|
|
{
|
|
stackMin = gOurApplicationHeapBase;
|
|
stackMax = gOurApplicationHeapMax;
|
|
}
|
|
|
|
// If the current SP is not in our heap, then bail .
|
|
|
|
#if GENERATINGPOWERPC
|
|
currentSP = GetCurrentStackPointer();
|
|
#endif
|
|
|
|
if ((currentSP > gOurApplicationHeapMax) || (currentSP < gOurApplicationHeapBase))
|
|
return;
|
|
|
|
interfaceLibSP = *((void **)currentSP);
|
|
|
|
callerFirstStackFrame = interfaceLibSP;
|
|
nextFrame = callerFirstStackFrame;
|
|
|
|
// What we really want to do here is walk up the stack until we get to a native frame that is in
|
|
// one of our Shared libraries... Since they can live in temp mem, this gets whacky.
|
|
|
|
// So, for now, I just walk up until I hit two native frames in a row...
|
|
nativeFrames = 0;
|
|
|
|
while (1) {
|
|
// if this frame is outside of our thread's stack, then bail
|
|
if ( ( nextFrame > stackMax ) || ( nextFrame < stackMin ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Walk the stack differently whether we are at a
|
|
// PowerPC or 68K frame...
|
|
|
|
if (isPowerPC) {
|
|
|
|
#if 0
|
|
void *framePC;
|
|
|
|
// If we are PowerPC, check to see if the PC
|
|
// corresponding to the current frame is in the
|
|
// the app's code space. If so, then we are
|
|
// done and we break out.
|
|
|
|
framePC = *((void **)callerFirstStackFrame + 2);
|
|
if ((framePC < gOurApplicationHeapMax) && (framePC > gOurApplicationHeapBase))
|
|
break;
|
|
#endif
|
|
|
|
// Otherwise, check the pointer to the next
|
|
// stack frame. If its low bit is set, then
|
|
// it is a mixed-mode switch frame, and
|
|
// we need to switch to 68K frames.
|
|
|
|
nextFrame = *((void **)callerFirstStackFrame);
|
|
|
|
isPowerPC = ((UInt32)nextFrame & 0x00000001) == 0;
|
|
callerFirstStackFrame = (void *)((UInt32)nextFrame & 0xFFFFFFFE);
|
|
if ( isPowerPC != false )
|
|
{
|
|
if ( ++nativeFrames >= 1 )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
|
|
nativeFrames = 0;
|
|
|
|
// 68K case: If the location immediately above the stack
|
|
// frame pointer is -1, then we are at a switch frame,
|
|
// move back to PowerPC.
|
|
|
|
if (*((UInt32 *)callerFirstStackFrame - 1) == 0xFFFFFFFF)
|
|
isPowerPC = true;
|
|
callerFirstStackFrame = *((void **)callerFirstStackFrame);
|
|
nextFrame = callerFirstStackFrame;
|
|
}
|
|
|
|
}
|
|
|
|
callerSP = callerFirstStackFrame;
|
|
|
|
for (stackFrameLevel = 0; stackFrameLevel < kStackDepth; stackFrameLevel++) {
|
|
|
|
if ( ( callerSP > stackMax ) || ( callerSP < stackMin ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
stackCrawl[stackFrameLevel] = *(((void **)callerSP) + 2);
|
|
|
|
callerSP = *((void **)callerSP);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void GetCurrentNativeStackTrace ( void ** stackCrawl )
|
|
{
|
|
void * currentSP;
|
|
UInt32 stackFrameLevel;
|
|
PRThread * curThread;
|
|
void * stackMin;
|
|
void * stackMax;
|
|
|
|
memset(stackCrawl, 0, sizeof(void *) * kStackDepth);
|
|
|
|
#if GENERATINGPOWERPC
|
|
currentSP = GetCurrentStackPointer();
|
|
#else
|
|
return;
|
|
#endif
|
|
|
|
curThread = PR_CurrentThread();
|
|
if ( curThread != NULL && curThread->stack->stackBottom != 0 )
|
|
{
|
|
stackMin = curThread->stack->stackBottom;
|
|
stackMax = curThread->stack->stackTop;
|
|
}
|
|
else
|
|
{
|
|
stackMin = gOurApplicationHeapBase;
|
|
stackMax = gOurApplicationHeapMax;
|
|
}
|
|
|
|
for (stackFrameLevel = 0; stackFrameLevel < kStackDepth; stackFrameLevel++) {
|
|
if ( ( currentSP > stackMax ) || ( currentSP < stackMin ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
stackCrawl[stackFrameLevel] = *((void **)((char *)currentSP + 8));
|
|
currentSP = *((void **)currentSP);
|
|
}
|
|
}
|
|
|