/* * Copyright (C) 2008 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. */ /* * Heap object dump */ #include "Hprof.h" #include "alloc/HeapInternal.h" #include "alloc/HeapSource.h" /* Set DUMP_PRIM_DATA to 1 if you want to include the contents * of primitive arrays (byte arrays, character arrays, etc.) * in heap dumps. This can be a large amount of data. */ #define DUMP_PRIM_DATA 1 #define OBJECTS_PER_SEGMENT ((size_t)128) #define BYTES_PER_SEGMENT ((size_t)4096) int hprofStartHeapDump(hprof_context_t *ctx) { UNUSED_PARAMETER(ctx); ctx->objectsInSegment = OBJECTS_PER_SEGMENT; ctx->currentHeap = HPROF_HEAP_DEFAULT; return 0; } int hprofFinishHeapDump(hprof_context_t *ctx) { return hprofStartNewRecord(ctx, HPROF_TAG_HEAP_DUMP_END, HPROF_TIME); } int hprofSetGcScanState(hprof_context_t *ctx, hprof_heap_tag_t state, u4 threadSerialNumber) { /* Used by hprofMarkRootObject() */ ctx->gcScanState = state; ctx->gcThreadSerialNumber = threadSerialNumber; return 0; } static hprof_basic_type signatureToBasicTypeAndSize(const char *sig, size_t *sizeOut) { char c = sig[0]; hprof_basic_type ret; size_t size; switch (c) { case '[': case 'L': ret = hprof_basic_object; size = 4; break; case 'Z': ret = hprof_basic_boolean; size = 1; break; case 'C': ret = hprof_basic_char; size = 2; break; case 'F': ret = hprof_basic_float; size = 4; break; case 'D': ret = hprof_basic_double; size = 8; break; case 'B': ret = hprof_basic_byte; size = 1; break; case 'S': ret = hprof_basic_short; size = 2; break; default: assert(false); case 'I': ret = hprof_basic_int; size = 4; break; case 'J': ret = hprof_basic_long; size = 8; break; } if (sizeOut != NULL) { *sizeOut = size; } return ret; } static hprof_basic_type primitiveToBasicTypeAndSize(PrimitiveType prim, size_t *sizeOut) { hprof_basic_type ret; size_t size; switch (prim) { case PRIM_BOOLEAN: ret = hprof_basic_boolean; size = 1; break; case PRIM_CHAR: ret = hprof_basic_char; size = 2; break; case PRIM_FLOAT: ret = hprof_basic_float; size = 4; break; case PRIM_DOUBLE: ret = hprof_basic_double; size = 8; break; case PRIM_BYTE: ret = hprof_basic_byte; size = 1; break; case PRIM_SHORT: ret = hprof_basic_short; size = 2; break; default: assert(false); case PRIM_INT: ret = hprof_basic_int; size = 4; break; case PRIM_LONG: ret = hprof_basic_long; size = 8; break; } if (sizeOut != NULL) { *sizeOut = size; } return ret; } /* Always called when marking objects, but only does * something when ctx->gcScanState is non-zero, which is usually * only true when marking the root set or unreachable * objects. Used to add rootset references to obj. */ int hprofMarkRootObject(hprof_context_t *ctx, const Object *obj, jobject jniObj) { hprof_record_t *rec = &ctx->curRec; int err; hprof_heap_tag_t heapTag = ctx->gcScanState; if (heapTag == 0) { return 0; } if (ctx->objectsInSegment >= OBJECTS_PER_SEGMENT || rec->length >= BYTES_PER_SEGMENT) { /* This flushes the old segment and starts a new one. */ hprofStartNewRecord(ctx, HPROF_TAG_HEAP_DUMP_SEGMENT, HPROF_TIME); ctx->objectsInSegment = 0; } switch (heapTag) { /* ID: object ID */ case HPROF_ROOT_UNKNOWN: case HPROF_ROOT_STICKY_CLASS: case HPROF_ROOT_MONITOR_USED: case HPROF_ROOT_INTERNED_STRING: case HPROF_ROOT_FINALIZING: case HPROF_ROOT_DEBUGGER: case HPROF_ROOT_REFERENCE_CLEANUP: case HPROF_ROOT_VM_INTERNAL: case HPROF_UNREACHABLE: hprofAddU1ToRecord(rec, heapTag); hprofAddIdToRecord(rec, (hprof_object_id)obj); break; /* ID: object ID * ID: JNI global ref ID */ case HPROF_ROOT_JNI_GLOBAL: hprofAddU1ToRecord(rec, heapTag); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddIdToRecord(rec, (hprof_id)jniObj); break; /* ID: object ID * u4: thread serial number * u4: frame number in stack trace (-1 for empty) */ case HPROF_ROOT_JNI_LOCAL: case HPROF_ROOT_JNI_MONITOR: case HPROF_ROOT_JAVA_FRAME: hprofAddU1ToRecord(rec, heapTag); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber); hprofAddU4ToRecord(rec, (u4)-1); break; /* ID: object ID * u4: thread serial number */ case HPROF_ROOT_NATIVE_STACK: case HPROF_ROOT_THREAD_BLOCK: hprofAddU1ToRecord(rec, heapTag); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber); break; /* ID: thread object ID * u4: thread serial number * u4: stack trace serial number */ case HPROF_ROOT_THREAD_OBJECT: hprofAddU1ToRecord(rec, heapTag); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber); hprofAddU4ToRecord(rec, (u4)-1); //xxx break; default: err = 0; break; } ctx->objectsInSegment++; return err; } static int stackTraceSerialNumber(const void *obj) { #if WITH_HPROF_STACK DvmHeapChunk *chunk = ptr2chunk(obj); return chunk->stackTraceSerialNumber; #else return HPROF_NULL_STACK_TRACE; #endif } int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj) { const ClassObject *clazz; hprof_record_t *rec = &ctx->curRec; HprofHeapId desiredHeap; desiredHeap = dvmHeapSourceGetPtrFlag(ptr2chunk(obj), HS_ALLOCATED_IN_ZYGOTE) ? HPROF_HEAP_ZYGOTE : HPROF_HEAP_APP; if (ctx->objectsInSegment >= OBJECTS_PER_SEGMENT || rec->length >= BYTES_PER_SEGMENT) { /* This flushes the old segment and starts a new one. */ hprofStartNewRecord(ctx, HPROF_TAG_HEAP_DUMP_SEGMENT, HPROF_TIME); ctx->objectsInSegment = 0; /* Starting a new HEAP_DUMP resets the heap to default. */ ctx->currentHeap = HPROF_HEAP_DEFAULT; } if (desiredHeap != ctx->currentHeap) { hprof_string_id nameId; /* This object is in a different heap than the current one. * Emit a HEAP_DUMP_INFO tag to change heaps. */ hprofAddU1ToRecord(rec, HPROF_HEAP_DUMP_INFO); hprofAddU4ToRecord(rec, (u4)desiredHeap); // u4: heap id switch (desiredHeap) { case HPROF_HEAP_APP: nameId = hprofLookupStringId("app"); break; case HPROF_HEAP_ZYGOTE: nameId = hprofLookupStringId("zygote"); break; default: /* Internal error. */ assert(!"Unexpected desiredHeap"); nameId = hprofLookupStringId("<ILLEGAL>"); break; } hprofAddIdToRecord(rec, nameId); ctx->currentHeap = desiredHeap; } clazz = obj->clazz; if (clazz == NULL) { /* This object was probably just allocated and hasn't been * initialized yet. Add an instance entry to make a note of * it; there's not much else that we can do. */ hprofAddU1ToRecord(rec, HPROF_INSTANCE_DUMP); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj)); hprofAddIdToRecord(rec, (hprof_class_object_id)clazz); // NULL hprofAddIdToRecord(rec, 0); // no instance data } else if (clazz == gDvm.unlinkedJavaLangClass) { /* obj is a ClassObject that hasn't been linked yet. */ hprofAddU1ToRecord(rec, HPROF_CLASS_DUMP); //TODO: use hprofLookupClassId() for this: hprofAddIdToRecord(rec, (hprof_class_object_id)obj); hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj)); hprofAddIdToRecord(rec, (hprof_class_object_id)0); // no super class hprofAddIdToRecord(rec, (hprof_object_id)0); // no class loader hprofAddIdToRecord(rec, (hprof_object_id)0); // no signer hprofAddIdToRecord(rec, (hprof_object_id)0); // no prot domain hprofAddIdToRecord(rec, (hprof_id)0); // reserved hprofAddIdToRecord(rec, (hprof_id)0); // reserved hprofAddU4ToRecord(rec, 0); // zero instance size hprofAddU2ToRecord(rec, 0); // empty const pool hprofAddU2ToRecord(rec, 0); // no statics hprofAddU2ToRecord(rec, 0); // no instance fields } else { hprof_class_object_id clazzId; clazzId = hprofLookupClassId(clazz); if (clazz == gDvm.classJavaLangClass) { const ClassObject *thisClass = (const ClassObject *)obj; int i, n; /* obj is a ClassObject. */ hprofAddU1ToRecord(rec, HPROF_CLASS_DUMP); hprofAddIdToRecord(rec, hprofLookupClassId(thisClass)); hprofAddU4ToRecord(rec, stackTraceSerialNumber(thisClass)); hprofAddIdToRecord(rec, hprofLookupClassId(thisClass->super)); hprofAddIdToRecord(rec, (hprof_object_id)thisClass->classLoader); hprofAddIdToRecord(rec, (hprof_object_id)0); // no signer hprofAddIdToRecord(rec, (hprof_object_id)0); // no prot domain hprofAddIdToRecord(rec, (hprof_id)0); // reserved hprofAddIdToRecord(rec, (hprof_id)0); // reserved if (obj == (Object *)gDvm.classJavaLangClass) { hprofAddU4ToRecord(rec, sizeof(ClassObject)); // instance size } else { hprofAddU4ToRecord(rec, thisClass->objectSize); // instance size } hprofAddU2ToRecord(rec, 0); // empty const pool /* Static fields */ n = thisClass->sfieldCount; hprofAddU2ToRecord(rec, (u2)n); for (i = 0; i < n; i++) { const StaticField *f = &thisClass->sfields[i]; hprof_basic_type t; size_t size; t = signatureToBasicTypeAndSize(f->field.signature, &size); hprofAddIdToRecord(rec, hprofLookupStringId(f->field.name)); hprofAddU1ToRecord(rec, t); if (size == 1) { hprofAddU1ToRecord(rec, (u1)f->value.b); } else if (size == 2) { hprofAddU2ToRecord(rec, (u2)f->value.c); } else if (size == 4) { hprofAddU4ToRecord(rec, (u4)f->value.i); } else if (size == 8) { hprofAddU8ToRecord(rec, (u8)f->value.j); } else { assert(false); } } /* Instance fields for this class (no superclass fields) */ n = thisClass->ifieldCount; hprofAddU2ToRecord(rec, (u2)n); for (i = 0; i < n; i++) { const InstField *f = &thisClass->ifields[i]; hprof_basic_type t; t = signatureToBasicTypeAndSize(f->field.signature, NULL); hprofAddIdToRecord(rec, hprofLookupStringId(f->field.name)); hprofAddU1ToRecord(rec, t); } } else if (IS_CLASS_FLAG_SET(clazz, CLASS_ISARRAY)) { const ArrayObject *aobj = (const ArrayObject *)obj; u4 length = aobj->length; if (IS_CLASS_FLAG_SET(clazz, CLASS_ISOBJECTARRAY)) { /* obj is an object array. */ hprofAddU1ToRecord(rec, HPROF_OBJECT_ARRAY_DUMP); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj)); hprofAddU4ToRecord(rec, length); hprofAddIdToRecord(rec, hprofLookupClassId(clazz)); /* Dump the elements, which are always objects or NULL. */ hprofAddIdListToRecord(rec, (const hprof_object_id *)aobj->contents, length); } else { hprof_basic_type t; size_t size; t = primitiveToBasicTypeAndSize(clazz->elementClass-> primitiveType, &size); /* obj is a primitive array. */ #if DUMP_PRIM_DATA hprofAddU1ToRecord(rec, HPROF_PRIMITIVE_ARRAY_DUMP); #else hprofAddU1ToRecord(rec, HPROF_PRIMITIVE_ARRAY_NODATA_DUMP); #endif hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj)); hprofAddU4ToRecord(rec, length); hprofAddU1ToRecord(rec, t); #if DUMP_PRIM_DATA /* Dump the raw, packed element values. */ if (size == 1) { hprofAddU1ListToRecord(rec, (const u1 *)aobj->contents, length); } else if (size == 2) { hprofAddU2ListToRecord(rec, (const u2 *)aobj->contents, length); } else if (size == 4) { hprofAddU4ListToRecord(rec, (const u4 *)aobj->contents, length); } else if (size == 8) { hprofAddU8ListToRecord(rec, (const u8 *)aobj->contents, length); } #endif } } else { const ClassObject *sclass; size_t sizePatchOffset, savedLen; /* obj is an instance object. */ hprofAddU1ToRecord(rec, HPROF_INSTANCE_DUMP); hprofAddIdToRecord(rec, (hprof_object_id)obj); hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj)); hprofAddIdToRecord(rec, hprofLookupClassId(clazz)); /* Reserve some space for the length of the instance * data, which we won't know until we're done writing * it. */ sizePatchOffset = rec->length; hprofAddU4ToRecord(rec, 0x77777777); /* Write the instance data; fields for this * class, followed by super class fields, and so on. */ sclass = clazz; while (sclass != NULL) { int i, ifieldCount; ifieldCount = sclass->ifieldCount; for (i = 0; i < ifieldCount; i++) { const InstField *f = &sclass->ifields[i]; hprof_basic_type t; size_t size; t = signatureToBasicTypeAndSize(f->field.signature, &size); if (size == 1) { hprofAddU1ToRecord(rec, (u1)dvmGetFieldByte(obj, f->byteOffset)); } else if (size == 2) { hprofAddU2ToRecord(rec, (u2)dvmGetFieldChar(obj, f->byteOffset)); } else if (size == 4) { hprofAddU4ToRecord(rec, (u4)dvmGetFieldInt(obj, f->byteOffset)); } else if (size == 8) { hprofAddU8ToRecord(rec, (u8)dvmGetFieldLong(obj, f->byteOffset)); } else { assert(false); } } sclass = sclass->super; } /* Patch the instance field length. */ savedLen = rec->length; rec->length = sizePatchOffset; hprofAddU4ToRecord(rec, savedLen - (sizePatchOffset + 4)); rec->length = savedLen; } } ctx->objectsInSegment++; return 0; }