/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Indirect reference table management. */ #include "Dalvik.h" /* * Initialize an IndirectRefTable structure. */ bool dvmInitIndirectRefTable(IndirectRefTable* pRef, int initialCount, int maxCount, IndirectRefKind kind) { assert(initialCount > 0); assert(initialCount <= maxCount); assert(kind == kIndirectKindLocal || kind == kIndirectKindGlobal); pRef->table = (Object**) malloc(initialCount * sizeof(Object*)); if (pRef->table == NULL) return false; #ifndef NDEBUG memset(pRef->table, 0xd1, initialCount * sizeof(Object*)); #endif pRef->slotData = (IndirectRefSlot*) calloc(maxCount, sizeof(IndirectRefSlot)); if (pRef->slotData == NULL) return false; pRef->segmentState.all = IRT_FIRST_SEGMENT; pRef->allocEntries = initialCount; pRef->maxEntries = maxCount; pRef->kind = kind; return true; } /* * Clears out the contents of a IndirectRefTable, freeing allocated storage. */ void dvmClearIndirectRefTable(IndirectRefTable* pRef) { free(pRef->table); pRef->table = NULL; pRef->allocEntries = pRef->maxEntries = -1; } /* * Remove one or more segments from the top. The table entry identified * by "cookie" becomes the new top-most entry. * * Returns false if "cookie" is invalid or the table has only one segment. */ bool dvmPopIndirectRefTableSegmentCheck(IndirectRefTable* pRef, u4 cookie) { IRTSegmentState sst; /* * The new value for "top" must be <= the current value. Otherwise * this would represent an expansion of the table. */ sst.all = cookie; if (sst.parts.topIndex > pRef->segmentState.parts.topIndex) { LOGE("Attempt to expand table with segment pop (%d to %d)\n", pRef->segmentState.parts.topIndex, sst.parts.topIndex); return false; } if (sst.parts.numHoles >= sst.parts.topIndex) { LOGE("Absurd numHoles in cookie (%d bi=%d)\n", sst.parts.numHoles, sst.parts.topIndex); return false; } LOGV("IRT %p[%d]: pop, top=%d holes=%d\n", pRef, pRef->kind, sst.parts.topIndex, sst.parts.numHoles); return true; } /* * Make sure that the entry at "idx" is correctly paired with "iref". */ static bool checkEntry(IndirectRefTable* pRef, IndirectRef iref, int idx) { Object* obj = pRef->table[idx]; IndirectRef checkRef = dvmObjectToIndirectRef(pRef, obj, idx, pRef->kind); if (checkRef != iref) { LOGW("IRT %p[%d]: iref mismatch (req=%p vs cur=%p)\n", pRef, pRef->kind, iref, checkRef); return false; } return true; } /* * Update extended debug info when an entry is added. * * We advance the serial number, invalidating any outstanding references to * this slot. */ static inline void updateSlotAdd(IndirectRefTable* pRef, Object* obj, int slot) { if (pRef->slotData != NULL) { IndirectRefSlot* pSlot = &pRef->slotData[slot]; pSlot->serial++; //LOGI("+++ add [%d] slot %d (%p->%p), serial=%d\n", // pRef->kind, slot, obj, iref, pSlot->serial); pSlot->previous[pSlot->serial % kIRTPrevCount] = obj; } } /* * Update extended debug info when an entry is removed. */ static inline void updateSlotRemove(IndirectRefTable* pRef, int slot) { if (pRef->slotData != NULL) { IndirectRefSlot* pSlot = &pRef->slotData[slot]; //LOGI("+++ remove [%d] slot %d, serial now %d\n", // pRef->kind, slot, pSlot->serial); } } /* * Add "obj" to "pRef". */ IndirectRef dvmAddToIndirectRefTable(IndirectRefTable* pRef, u4 cookie, Object* obj) { IRTSegmentState prevState; prevState.all = cookie; int topIndex = pRef->segmentState.parts.topIndex; int bottomIndex = prevState.parts.topIndex; assert(obj != NULL); assert(dvmIsValidObject(obj)); assert(pRef->table != NULL); assert(pRef->allocEntries <= pRef->maxEntries); assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles); if (topIndex == pRef->allocEntries) { /* reached end of allocated space; did we hit buffer max? */ if (topIndex == pRef->maxEntries) { LOGW("IndirectRefTable overflow (max=%d)\n", pRef->maxEntries); return NULL; } Object** newTable; int newSize; newSize = pRef->allocEntries * 2; if (newSize > pRef->maxEntries) newSize = pRef->maxEntries; assert(newSize > pRef->allocEntries); newTable = (Object**) realloc(pRef->table, newSize * sizeof(Object*)); if (newTable == NULL) { LOGE("Unable to expand iref table (from %d to %d, max=%d)\n", pRef->allocEntries, newSize, pRef->maxEntries); return false; } LOGI("Growing ireftab %p from %d to %d (max=%d)\n", pRef, pRef->allocEntries, newSize, pRef->maxEntries); /* update entries; adjust "nextEntry" in case memory moved */ pRef->table = newTable; pRef->allocEntries = newSize; } IndirectRef result; /* * We know there's enough room in the table. Now we just need to find * the right spot. If there's a hole, find it and fill it; otherwise, * add to the end of the list. */ int numHoles = pRef->segmentState.parts.numHoles - prevState.parts.numHoles; if (numHoles > 0) { assert(topIndex > 1); /* find the first hole; likely to be near the end of the list */ Object** pScan = &pRef->table[topIndex - 1]; assert(*pScan != NULL); while (*--pScan != NULL) { assert(pScan >= pRef->table + bottomIndex); } updateSlotAdd(pRef, obj, pScan - pRef->table); result = dvmObjectToIndirectRef(pRef, obj, pScan - pRef->table, pRef->kind); *pScan = obj; pRef->segmentState.parts.numHoles--; } else { /* add to the end */ updateSlotAdd(pRef, obj, topIndex); result = dvmObjectToIndirectRef(pRef, obj, topIndex, pRef->kind); pRef->table[topIndex++] = obj; pRef->segmentState.parts.topIndex = topIndex; } assert(result != NULL); return result; } /* * Verify that the indirect table lookup is valid. * * Returns "false" if something looks bad. */ bool dvmGetFromIndirectRefTableCheck(IndirectRefTable* pRef, IndirectRef iref) { if (dvmGetIndirectRefType(iref) == kIndirectKindInvalid) { LOGW("Invalid indirect reference 0x%08x\n", (u4) iref); return false; } int topIndex = pRef->segmentState.parts.topIndex; int idx = dvmIndirectRefToIndex(iref); if (iref == NULL) { LOGI("--- lookup on NULL iref\n"); return false; } if (idx >= topIndex) { /* bad -- stale reference? */ LOGI("Attempt to access invalid index %d (top=%d)\n", idx, topIndex); return false; } Object* obj = pRef->table[idx]; if (obj == NULL) { LOGI("Attempt to read from hole, iref=%p\n", iref); return false; } if (!checkEntry(pRef, iref, idx)) return false; return true; } /* * Remove "obj" from "pRef". We extract the table offset bits from "iref" * and zap the corresponding entry, leaving a hole if it's not at the top. * * If the entry is not between the current top index and the bottom index * specified by the cookie, we don't remove anything. This is the behavior * required by JNI's DeleteLocalRef function. * * Note this is NOT called when a local frame is popped. This is only used * for explict single removals. * * Returns "false" if nothing was removed. */ bool dvmRemoveFromIndirectRefTable(IndirectRefTable* pRef, u4 cookie, IndirectRef iref) { IRTSegmentState prevState; prevState.all = cookie; int topIndex = pRef->segmentState.parts.topIndex; int bottomIndex = prevState.parts.topIndex; assert(pRef->table != NULL); assert(pRef->allocEntries <= pRef->maxEntries); assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles); int idx = dvmIndirectRefToIndex(iref); if (idx < bottomIndex) { /* wrong segment */ LOGV("Attempt to remove index outside index area (%d vs %d-%d)\n", idx, bottomIndex, topIndex); return false; } if (idx >= topIndex) { /* bad -- stale reference? */ LOGI("Attempt to remove invalid index %d (bottom=%d top=%d)\n", idx, bottomIndex, topIndex); return false; } if (idx == topIndex-1) { /* * Top-most entry. Scan up and consume holes. No need to NULL * out the entry, since the test vs. topIndex will catch it. */ if (!checkEntry(pRef, iref, idx)) return false; updateSlotRemove(pRef, idx); #ifndef NDEBUG pRef->table[idx] = (IndirectRef) 0xd3d3d3d3; #endif int numHoles = pRef->segmentState.parts.numHoles - prevState.parts.numHoles; if (numHoles != 0) { while (--topIndex > bottomIndex && numHoles != 0) { LOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p\n", topIndex-1, cookie, pRef->table[topIndex-1]); if (pRef->table[topIndex-1] != NULL) break; LOGV("+++ ate hole at %d\n", topIndex-1); numHoles--; } pRef->segmentState.parts.numHoles = numHoles + prevState.parts.numHoles; pRef->segmentState.parts.topIndex = topIndex; } else { pRef->segmentState.parts.topIndex = topIndex-1; LOGV("+++ ate last entry %d\n", topIndex-1); } } else { /* * Not the top-most entry. This creates a hole. We NULL out the * entry to prevent somebody from deleting it twice and screwing up * the hole count. */ if (pRef->table[idx] == NULL) { LOGV("--- WEIRD: removing null entry %d\n", idx); return false; } if (!checkEntry(pRef, iref, idx)) return false; updateSlotRemove(pRef, idx); pRef->table[idx] = NULL; pRef->segmentState.parts.numHoles++; LOGV("+++ left hole at %d, holes=%d\n", idx, pRef->segmentState.parts.numHoles); } return true; } /* * This is a qsort() callback. We sort Object* by class, allocation size, * and then by the Object* itself. */ static int compareObject(const void* vobj1, const void* vobj2) { Object* obj1 = *((Object**) vobj1); Object* obj2 = *((Object**) vobj2); /* ensure null references appear at the end */ if (obj1 == NULL) { if (obj2 == NULL) { return 0; } else { return 1; } } else if (obj2 == NULL) { return -1; } if (obj1->clazz != obj2->clazz) { return (u1*)obj1->clazz - (u1*)obj2->clazz; } else { int size1 = dvmObjectSizeInHeap(obj1); int size2 = dvmObjectSizeInHeap(obj2); if (size1 != size2) { return size1 - size2; } else { return (u1*)obj1 - (u1*)obj2; } } } /* * Log an object with some additional info. * * Pass in the number of additional elements that are identical to or * equivalent to the original. */ static void logObject(Object* obj, int size, int identical, int equiv) { if (obj == NULL) { LOGW(" NULL reference (count=%d)\n", equiv); return; } if (identical + equiv != 0) { LOGW("%5d of %s %dB (%d unique)\n", identical + equiv +1, obj->clazz->descriptor, size, equiv +1); } else { LOGW("%5d of %s %dB\n", identical + equiv +1, obj->clazz->descriptor, size); } } /* * Dump the contents of a IndirectRefTable to the log. */ void dvmDumpIndirectRefTable(const IndirectRefTable* pRef, const char* descr) { const int kLast = 10; int count = dvmIndirectRefTableEntries(pRef); Object** refs; int i; if (count == 0) { LOGW("Reference table has no entries\n"); return; } assert(count > 0); /* * Dump the most recent N entries. If there are holes, we will show * fewer than N. */ LOGW("Last %d entries in %s reference table:\n", kLast, descr); refs = pRef->table; // use unsorted list int size; int start = count - kLast; if (start < 0) start = 0; for (i = start; i < count; i++) { if (refs[i] == NULL) continue; size = dvmObjectSizeInHeap(refs[i]); Object* ref = refs[i]; if (ref->clazz == gDvm.classJavaLangClass) { ClassObject* clazz = (ClassObject*) ref; LOGW("%5d: %p cls=%s '%s' (%d bytes)\n", i, ref, (refs[i] == NULL) ? "-" : ref->clazz->descriptor, clazz->descriptor, size); } else { LOGW("%5d: %p cls=%s (%d bytes)\n", i, ref, (refs[i] == NULL) ? "-" : ref->clazz->descriptor, size); } } /* * Make a copy of the table, and sort it. * * The NULL "holes" wind up at the end, so we can strip them off easily. */ Object** tableCopy = (Object**)malloc(sizeof(Object*) * count); memcpy(tableCopy, pRef->table, sizeof(Object*) * count); qsort(tableCopy, count, sizeof(Object*), compareObject); refs = tableCopy; // use sorted list if (false) { int q; for (q = 0; q < count; q++) LOGI("%d %p\n", q, refs[q]); } int holes = 0; while (refs[count-1] == NULL) { count--; holes++; } /* * Dump uniquified table summary. While we're at it, generate a * cumulative total amount of pinned memory based on the unique entries. */ LOGW("%s reference table summary (%d entries / %d holes):\n", descr, count, holes); int equiv, identical, total; total = equiv = identical = 0; for (i = 1; i < count; i++) { size = dvmObjectSizeInHeap(refs[i-1]); if (refs[i] == refs[i-1]) { /* same reference, added more than once */ identical++; } else if (refs[i]->clazz == refs[i-1]->clazz && (int) dvmObjectSizeInHeap(refs[i]) == size) { /* same class / size, different object */ total += size; equiv++; } else { /* different class */ total += size; logObject(refs[i-1], size, identical, equiv); equiv = identical = 0; } } /* handle the last entry (everything above outputs refs[i-1]) */ size = (refs[count-1] == NULL) ? 0 : dvmObjectSizeInHeap(refs[count-1]); total += size; logObject(refs[count-1], size, identical, equiv); LOGW("Memory held directly by native code is %d bytes\n", total); free(tableCopy); }