1 /*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /*
18 * Indirect reference table management.
19 */
20 #include "Dalvik.h"
21
22 /*
23 * Initialize an IndirectRefTable structure.
24 */
dvmInitIndirectRefTable(IndirectRefTable * pRef,int initialCount,int maxCount,IndirectRefKind kind)25 bool dvmInitIndirectRefTable(IndirectRefTable* pRef, int initialCount,
26 int maxCount, IndirectRefKind kind)
27 {
28 assert(initialCount > 0);
29 assert(initialCount <= maxCount);
30 assert(kind == kIndirectKindLocal || kind == kIndirectKindGlobal);
31
32 pRef->table = (Object**) malloc(initialCount * sizeof(Object*));
33 if (pRef->table == NULL)
34 return false;
35 #ifndef NDEBUG
36 memset(pRef->table, 0xd1, initialCount * sizeof(Object*));
37 #endif
38
39 pRef->slotData =
40 (IndirectRefSlot*) calloc(maxCount, sizeof(IndirectRefSlot));
41 if (pRef->slotData == NULL)
42 return false;
43
44 pRef->segmentState.all = IRT_FIRST_SEGMENT;
45 pRef->allocEntries = initialCount;
46 pRef->maxEntries = maxCount;
47 pRef->kind = kind;
48
49 return true;
50 }
51
52 /*
53 * Clears out the contents of a IndirectRefTable, freeing allocated storage.
54 */
dvmClearIndirectRefTable(IndirectRefTable * pRef)55 void dvmClearIndirectRefTable(IndirectRefTable* pRef)
56 {
57 free(pRef->table);
58 pRef->table = NULL;
59 pRef->allocEntries = pRef->maxEntries = -1;
60 }
61
62 /*
63 * Remove one or more segments from the top. The table entry identified
64 * by "cookie" becomes the new top-most entry.
65 *
66 * Returns false if "cookie" is invalid or the table has only one segment.
67 */
dvmPopIndirectRefTableSegmentCheck(IndirectRefTable * pRef,u4 cookie)68 bool dvmPopIndirectRefTableSegmentCheck(IndirectRefTable* pRef, u4 cookie)
69 {
70 IRTSegmentState sst;
71
72 /*
73 * The new value for "top" must be <= the current value. Otherwise
74 * this would represent an expansion of the table.
75 */
76 sst.all = cookie;
77 if (sst.parts.topIndex > pRef->segmentState.parts.topIndex) {
78 LOGE("Attempt to expand table with segment pop (%d to %d)\n",
79 pRef->segmentState.parts.topIndex, sst.parts.topIndex);
80 return false;
81 }
82 if (sst.parts.numHoles >= sst.parts.topIndex) {
83 LOGE("Absurd numHoles in cookie (%d bi=%d)\n",
84 sst.parts.numHoles, sst.parts.topIndex);
85 return false;
86 }
87
88 LOGV("IRT %p[%d]: pop, top=%d holes=%d\n",
89 pRef, pRef->kind, sst.parts.topIndex, sst.parts.numHoles);
90
91 return true;
92 }
93
94 /*
95 * Make sure that the entry at "idx" is correctly paired with "iref".
96 */
checkEntry(IndirectRefTable * pRef,IndirectRef iref,int idx)97 static bool checkEntry(IndirectRefTable* pRef, IndirectRef iref, int idx)
98 {
99 Object* obj = pRef->table[idx];
100 IndirectRef checkRef = dvmObjectToIndirectRef(pRef, obj, idx, pRef->kind);
101 if (checkRef != iref) {
102 LOGW("IRT %p[%d]: iref mismatch (req=%p vs cur=%p)\n",
103 pRef, pRef->kind, iref, checkRef);
104 return false;
105 }
106 return true;
107 }
108
109 /*
110 * Update extended debug info when an entry is added.
111 *
112 * We advance the serial number, invalidating any outstanding references to
113 * this slot.
114 */
updateSlotAdd(IndirectRefTable * pRef,Object * obj,int slot)115 static inline void updateSlotAdd(IndirectRefTable* pRef, Object* obj, int slot)
116 {
117 if (pRef->slotData != NULL) {
118 IndirectRefSlot* pSlot = &pRef->slotData[slot];
119 pSlot->serial++;
120 //LOGI("+++ add [%d] slot %d (%p->%p), serial=%d\n",
121 // pRef->kind, slot, obj, iref, pSlot->serial);
122 pSlot->previous[pSlot->serial % kIRTPrevCount] = obj;
123 }
124 }
125
126 /*
127 * Update extended debug info when an entry is removed.
128 */
updateSlotRemove(IndirectRefTable * pRef,int slot)129 static inline void updateSlotRemove(IndirectRefTable* pRef, int slot)
130 {
131 if (pRef->slotData != NULL) {
132 //IndirectRefSlot* pSlot = &pRef->slotData[slot];
133 //LOGI("+++ remove [%d] slot %d, serial now %d\n",
134 // pRef->kind, slot, pSlot->serial);
135 }
136 }
137
138 /*
139 * Add "obj" to "pRef".
140 */
dvmAddToIndirectRefTable(IndirectRefTable * pRef,u4 cookie,Object * obj)141 IndirectRef dvmAddToIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
142 Object* obj)
143 {
144 IRTSegmentState prevState;
145 prevState.all = cookie;
146 int topIndex = pRef->segmentState.parts.topIndex;
147
148 assert(obj != NULL);
149 assert(dvmIsValidObject(obj));
150 assert(pRef->table != NULL);
151 assert(pRef->allocEntries <= pRef->maxEntries);
152 assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles);
153
154 if (topIndex == pRef->allocEntries) {
155 /* reached end of allocated space; did we hit buffer max? */
156 if (topIndex == pRef->maxEntries) {
157 LOGW("IndirectRefTable overflow (max=%d)\n", pRef->maxEntries);
158 return NULL;
159 }
160
161 Object** newTable;
162 int newSize;
163
164 newSize = pRef->allocEntries * 2;
165 if (newSize > pRef->maxEntries)
166 newSize = pRef->maxEntries;
167 assert(newSize > pRef->allocEntries);
168
169 newTable = (Object**) realloc(pRef->table, newSize * sizeof(Object*));
170 if (newTable == NULL) {
171 LOGE("Unable to expand iref table (from %d to %d, max=%d)\n",
172 pRef->allocEntries, newSize, pRef->maxEntries);
173 return false;
174 }
175 LOGI("Growing ireftab %p from %d to %d (max=%d)\n",
176 pRef, pRef->allocEntries, newSize, pRef->maxEntries);
177
178 /* update entries; adjust "nextEntry" in case memory moved */
179 pRef->table = newTable;
180 pRef->allocEntries = newSize;
181 }
182
183 IndirectRef result;
184
185 /*
186 * We know there's enough room in the table. Now we just need to find
187 * the right spot. If there's a hole, find it and fill it; otherwise,
188 * add to the end of the list.
189 */
190 int numHoles = pRef->segmentState.parts.numHoles - prevState.parts.numHoles;
191 if (numHoles > 0) {
192 assert(topIndex > 1);
193 /* find the first hole; likely to be near the end of the list */
194 Object** pScan = &pRef->table[topIndex - 1];
195 assert(*pScan != NULL);
196 while (*--pScan != NULL) {
197 assert(pScan >= pRef->table + prevState.parts.topIndex);
198 }
199 updateSlotAdd(pRef, obj, pScan - pRef->table);
200 result = dvmObjectToIndirectRef(pRef, obj, pScan - pRef->table,
201 pRef->kind);
202 *pScan = obj;
203 pRef->segmentState.parts.numHoles--;
204 } else {
205 /* add to the end */
206 updateSlotAdd(pRef, obj, topIndex);
207 result = dvmObjectToIndirectRef(pRef, obj, topIndex, pRef->kind);
208 pRef->table[topIndex++] = obj;
209 pRef->segmentState.parts.topIndex = topIndex;
210 }
211
212 assert(result != NULL);
213 return result;
214 }
215
216 /*
217 * Verify that the indirect table lookup is valid.
218 *
219 * Returns "false" if something looks bad.
220 */
dvmGetFromIndirectRefTableCheck(IndirectRefTable * pRef,IndirectRef iref)221 bool dvmGetFromIndirectRefTableCheck(IndirectRefTable* pRef, IndirectRef iref)
222 {
223 if (dvmGetIndirectRefType(iref) == kIndirectKindInvalid) {
224 LOGW("Invalid indirect reference 0x%08x\n", (u4) iref);
225 return false;
226 }
227
228 int topIndex = pRef->segmentState.parts.topIndex;
229 int idx = dvmIndirectRefToIndex(iref);
230
231 if (iref == NULL) {
232 LOGI("--- lookup on NULL iref\n");
233 return false;
234 }
235 if (idx >= topIndex) {
236 /* bad -- stale reference? */
237 LOGI("Attempt to access invalid index %d (top=%d)\n",
238 idx, topIndex);
239 return false;
240 }
241
242 Object* obj = pRef->table[idx];
243 if (obj == NULL) {
244 LOGI("Attempt to read from hole, iref=%p\n", iref);
245 return false;
246 }
247 if (!checkEntry(pRef, iref, idx))
248 return false;
249
250 return true;
251 }
252
253 /*
254 * Remove "obj" from "pRef". We extract the table offset bits from "iref"
255 * and zap the corresponding entry, leaving a hole if it's not at the top.
256 *
257 * If the entry is not between the current top index and the bottom index
258 * specified by the cookie, we don't remove anything. This is the behavior
259 * required by JNI's DeleteLocalRef function.
260 *
261 * Note this is NOT called when a local frame is popped. This is only used
262 * for explict single removals.
263 *
264 * Returns "false" if nothing was removed.
265 */
dvmRemoveFromIndirectRefTable(IndirectRefTable * pRef,u4 cookie,IndirectRef iref)266 bool dvmRemoveFromIndirectRefTable(IndirectRefTable* pRef, u4 cookie,
267 IndirectRef iref)
268 {
269 IRTSegmentState prevState;
270 prevState.all = cookie;
271 int topIndex = pRef->segmentState.parts.topIndex;
272 int bottomIndex = prevState.parts.topIndex;
273
274 assert(pRef->table != NULL);
275 assert(pRef->allocEntries <= pRef->maxEntries);
276 assert(pRef->segmentState.parts.numHoles >= prevState.parts.numHoles);
277
278 int idx = dvmIndirectRefToIndex(iref);
279 if (idx < bottomIndex) {
280 /* wrong segment */
281 LOGV("Attempt to remove index outside index area (%d vs %d-%d)\n",
282 idx, bottomIndex, topIndex);
283 return false;
284 }
285 if (idx >= topIndex) {
286 /* bad -- stale reference? */
287 LOGI("Attempt to remove invalid index %d (bottom=%d top=%d)\n",
288 idx, bottomIndex, topIndex);
289 return false;
290 }
291
292 if (idx == topIndex-1) {
293 /*
294 * Top-most entry. Scan up and consume holes. No need to NULL
295 * out the entry, since the test vs. topIndex will catch it.
296 */
297 if (!checkEntry(pRef, iref, idx))
298 return false;
299 updateSlotRemove(pRef, idx);
300
301 #ifndef NDEBUG
302 pRef->table[idx] = (IndirectRef) 0xd3d3d3d3;
303 #endif
304
305 int numHoles =
306 pRef->segmentState.parts.numHoles - prevState.parts.numHoles;
307 if (numHoles != 0) {
308 while (--topIndex > bottomIndex && numHoles != 0) {
309 LOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p\n",
310 topIndex-1, cookie, pRef->table[topIndex-1]);
311 if (pRef->table[topIndex-1] != NULL)
312 break;
313 LOGV("+++ ate hole at %d\n", topIndex-1);
314 numHoles--;
315 }
316 pRef->segmentState.parts.numHoles =
317 numHoles + prevState.parts.numHoles;
318 pRef->segmentState.parts.topIndex = topIndex;
319 } else {
320 pRef->segmentState.parts.topIndex = topIndex-1;
321 LOGV("+++ ate last entry %d\n", topIndex-1);
322 }
323 } else {
324 /*
325 * Not the top-most entry. This creates a hole. We NULL out the
326 * entry to prevent somebody from deleting it twice and screwing up
327 * the hole count.
328 */
329 if (pRef->table[idx] == NULL) {
330 LOGV("--- WEIRD: removing null entry %d\n", idx);
331 return false;
332 }
333 if (!checkEntry(pRef, iref, idx))
334 return false;
335 updateSlotRemove(pRef, idx);
336
337 pRef->table[idx] = NULL;
338 pRef->segmentState.parts.numHoles++;
339 LOGV("+++ left hole at %d, holes=%d\n",
340 idx, pRef->segmentState.parts.numHoles);
341 }
342
343 return true;
344 }
345
346 /*
347 * This is a qsort() callback. We sort Object* by class, allocation size,
348 * and then by the Object* itself.
349 */
compareObject(const void * vobj1,const void * vobj2)350 static int compareObject(const void* vobj1, const void* vobj2)
351 {
352 Object* obj1 = *((Object**) vobj1);
353 Object* obj2 = *((Object**) vobj2);
354
355 /* ensure null references appear at the end */
356 if (obj1 == NULL) {
357 if (obj2 == NULL) {
358 return 0;
359 } else {
360 return 1;
361 }
362 } else if (obj2 == NULL) {
363 return -1;
364 }
365
366 if (obj1->clazz != obj2->clazz) {
367 return (u1*)obj1->clazz - (u1*)obj2->clazz;
368 } else {
369 int size1 = dvmObjectSizeInHeap(obj1);
370 int size2 = dvmObjectSizeInHeap(obj2);
371 if (size1 != size2) {
372 return size1 - size2;
373 } else {
374 return (u1*)obj1 - (u1*)obj2;
375 }
376 }
377 }
378
379 /*
380 * Log an object with some additional info.
381 *
382 * Pass in the number of additional elements that are identical to or
383 * equivalent to the original.
384 */
logObject(Object * obj,int size,int identical,int equiv)385 static void logObject(Object* obj, int size, int identical, int equiv)
386 {
387 if (obj == NULL) {
388 LOGW(" NULL reference (count=%d)\n", equiv);
389 return;
390 }
391
392 if (identical + equiv != 0) {
393 LOGW("%5d of %s %dB (%d unique)\n", identical + equiv +1,
394 obj->clazz->descriptor, size, equiv +1);
395 } else {
396 LOGW("%5d of %s %dB\n", identical + equiv +1,
397 obj->clazz->descriptor, size);
398 }
399 }
400
401 /*
402 * Dump the contents of a IndirectRefTable to the log.
403 */
dvmDumpIndirectRefTable(const IndirectRefTable * pRef,const char * descr)404 void dvmDumpIndirectRefTable(const IndirectRefTable* pRef, const char* descr)
405 {
406 const int kLast = 10;
407 int count = dvmIndirectRefTableEntries(pRef);
408 Object** refs;
409 int i;
410
411 if (count == 0) {
412 LOGW("Reference table has no entries\n");
413 return;
414 }
415 assert(count > 0);
416
417 /*
418 * Dump the most recent N entries. If there are holes, we will show
419 * fewer than N.
420 */
421 LOGW("Last %d entries in %s reference table:\n", kLast, descr);
422 refs = pRef->table; // use unsorted list
423 int size;
424 int start = count - kLast;
425 if (start < 0)
426 start = 0;
427
428 for (i = start; i < count; i++) {
429 if (refs[i] == NULL)
430 continue;
431 size = dvmObjectSizeInHeap(refs[i]);
432 Object* ref = refs[i];
433 if (ref->clazz == gDvm.classJavaLangClass) {
434 ClassObject* clazz = (ClassObject*) ref;
435 LOGW("%5d: %p cls=%s '%s' (%d bytes)\n", i, ref,
436 (refs[i] == NULL) ? "-" : ref->clazz->descriptor,
437 clazz->descriptor, size);
438 } else {
439 LOGW("%5d: %p cls=%s (%d bytes)\n", i, ref,
440 (refs[i] == NULL) ? "-" : ref->clazz->descriptor, size);
441 }
442 }
443
444 /*
445 * Make a copy of the table, and sort it.
446 *
447 * The NULL "holes" wind up at the end, so we can strip them off easily.
448 */
449 Object** tableCopy = (Object**)malloc(sizeof(Object*) * count);
450 memcpy(tableCopy, pRef->table, sizeof(Object*) * count);
451 qsort(tableCopy, count, sizeof(Object*), compareObject);
452 refs = tableCopy; // use sorted list
453
454 if (false) {
455 int q;
456 for (q = 0; q < count; q++)
457 LOGI("%d %p\n", q, refs[q]);
458 }
459
460 int holes = 0;
461 while (refs[count-1] == NULL) {
462 count--;
463 holes++;
464 }
465
466 /*
467 * Dump uniquified table summary. While we're at it, generate a
468 * cumulative total amount of pinned memory based on the unique entries.
469 */
470 LOGW("%s reference table summary (%d entries / %d holes):\n",
471 descr, count, holes);
472 int equiv, identical, total;
473 total = equiv = identical = 0;
474 for (i = 1; i < count; i++) {
475 size = dvmObjectSizeInHeap(refs[i-1]);
476
477 if (refs[i] == refs[i-1]) {
478 /* same reference, added more than once */
479 identical++;
480 } else if (refs[i]->clazz == refs[i-1]->clazz &&
481 (int) dvmObjectSizeInHeap(refs[i]) == size)
482 {
483 /* same class / size, different object */
484 total += size;
485 equiv++;
486 } else {
487 /* different class */
488 total += size;
489 logObject(refs[i-1], size, identical, equiv);
490 equiv = identical = 0;
491 }
492 }
493
494 /* handle the last entry (everything above outputs refs[i-1]) */
495 size = (refs[count-1] == NULL) ? 0 : dvmObjectSizeInHeap(refs[count-1]);
496 total += size;
497 logObject(refs[count-1], size, identical, equiv);
498
499 LOGW("Memory held directly by native code is %d bytes\n", total);
500 free(tableCopy);
501 }
502