| /* |
| * 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->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("--- after pop, top=%d holes=%d\n", |
| 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(obj, idx, pRef->kind); |
| if (checkRef != iref) { |
| LOGW("iref mismatch: %p vs %p\n", iref, checkRef); |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * 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("ReferenceTable 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 entries)\n", |
| pRef->allocEntries, newSize); |
| return false; |
| } |
| LOGI("Growing %p from %d to %d\n", pRef, pRef->allocEntries, newSize); |
| |
| /* 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); |
| } |
| result = dvmObjectToIndirectRef(obj, pScan - pRef->table, pRef->kind); |
| *pScan = obj; |
| pRef->segmentState.parts.numHoles--; |
| } else { |
| /* add to the end */ |
| result = dvmObjectToIndirectRef(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. |
| * |
| * 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; |
| |
| #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; |
| |
| 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 |
| |
| { |
| 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); |
| } |
| |