| /* |
| * 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. |
| */ |
| |
| #include "Hprof.h" |
| #include "HprofStack.h" |
| #include "alloc/HeapInternal.h" |
| |
| static HashTable *gStackTraceHashTable = NULL; |
| static int gSerialNumber = 0; |
| |
| /* Number of stack frames to cache */ |
| #define STACK_DEPTH 8 |
| |
| typedef struct { |
| int serialNumber; |
| int threadSerialNumber; |
| int frameIds[STACK_DEPTH]; |
| } StackTrace; |
| |
| typedef struct { |
| StackTrace trace; |
| u1 live; |
| } StackTraceEntry; |
| |
| static u4 computeStackTraceHash(const StackTraceEntry *stackTraceEntry); |
| |
| int |
| hprofStartup_Stack() |
| { |
| HashIter iter; |
| |
| /* This will be called when a GC begins. */ |
| for (dvmHashIterBegin(gStackTraceHashTable, &iter); |
| !dvmHashIterDone(&iter); |
| dvmHashIterNext(&iter)) { |
| StackTraceEntry *stackTraceEntry; |
| |
| /* Clear the 'live' bit at the start of the GC pass. */ |
| stackTraceEntry = (StackTraceEntry *) dvmHashIterData(&iter); |
| stackTraceEntry->live = 0; |
| } |
| |
| return 0; |
| } |
| |
| int |
| hprofShutdown_Stack() |
| { |
| HashIter iter; |
| |
| /* This will be called when a GC has completed. */ |
| for (dvmHashIterBegin(gStackTraceHashTable, &iter); |
| !dvmHashIterDone(&iter); |
| dvmHashIterNext(&iter)) { |
| StackTraceEntry *stackTraceEntry; |
| |
| /* |
| * If the 'live' bit is 0, the trace is not in use by any current |
| * heap object and may be destroyed. |
| */ |
| stackTraceEntry = (StackTraceEntry *) dvmHashIterData(&iter); |
| if (!stackTraceEntry->live) { |
| dvmHashTableRemove(gStackTraceHashTable, |
| computeStackTraceHash(stackTraceEntry), stackTraceEntry); |
| free(stackTraceEntry); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static u4 |
| computeStackTraceHash(const StackTraceEntry *stackTraceEntry) |
| { |
| u4 hash = 0; |
| const char *cp = (const char *) &stackTraceEntry->trace; |
| int i; |
| |
| for (i = 0; i < (int) sizeof(StackTrace); i++) { |
| hash = hash * 31 + cp[i]; |
| } |
| |
| return hash; |
| } |
| |
| /* Only compare the 'trace' portion of the StackTraceEntry. */ |
| static int |
| stackCmp(const void *tableItem, const void *looseItem) |
| { |
| return memcmp(&((StackTraceEntry *) tableItem)->trace, |
| &((StackTraceEntry *) looseItem)->trace, sizeof(StackTrace)); |
| } |
| |
| static StackTraceEntry * |
| stackDup(const StackTraceEntry *stackTrace) |
| { |
| StackTraceEntry *newStackTrace = malloc(sizeof(StackTraceEntry)); |
| memcpy(newStackTrace, stackTrace, sizeof(StackTraceEntry)); |
| return newStackTrace; |
| } |
| |
| static u4 |
| hprofLookupStackSerialNumber(const StackTraceEntry *stackTrace) |
| { |
| StackTraceEntry *val; |
| u4 hashValue; |
| int serial; |
| |
| /* |
| * Create the hash table on first contact. We can't do this in |
| * hprofStartupStack, because we have to compute stack trace |
| * serial numbers and place them into object headers before the |
| * rest of hprof is triggered by a GC event. |
| */ |
| if (gStackTraceHashTable == NULL) { |
| gStackTraceHashTable = dvmHashTableCreate(512, free); |
| } |
| dvmHashTableLock(gStackTraceHashTable); |
| |
| hashValue = computeStackTraceHash(stackTrace); |
| val = dvmHashTableLookup(gStackTraceHashTable, hashValue, (void *)stackTrace, |
| (HashCompareFunc)stackCmp, false); |
| if (val == NULL) { |
| StackTraceEntry *newStackTrace; |
| |
| newStackTrace = stackDup(stackTrace); |
| newStackTrace->trace.serialNumber = ++gSerialNumber; |
| val = dvmHashTableLookup(gStackTraceHashTable, hashValue, |
| (void *)newStackTrace, (HashCompareFunc)stackCmp, true); |
| assert(val != NULL); |
| } |
| |
| /* Mark the trace as live (in use by an object in the current heap). */ |
| val->live = 1; |
| |
| /* Grab the serial number before unlocking the table. */ |
| serial = val->trace.serialNumber; |
| |
| dvmHashTableUnlock(gStackTraceHashTable); |
| |
| return serial; |
| } |
| |
| int |
| hprofDumpStacks(hprof_context_t *ctx) |
| { |
| HashIter iter; |
| hprof_record_t *rec = &ctx->curRec; |
| |
| dvmHashTableLock(gStackTraceHashTable); |
| |
| for (dvmHashIterBegin(gStackTraceHashTable, &iter); |
| !dvmHashIterDone(&iter); |
| dvmHashIterNext(&iter)) |
| { |
| const StackTraceEntry *stackTraceEntry; |
| int count; |
| int i; |
| |
| hprofStartNewRecord(ctx, HPROF_TAG_STACK_TRACE, HPROF_TIME); |
| |
| stackTraceEntry = (const StackTraceEntry *) dvmHashIterData(&iter); |
| assert(stackTraceEntry != NULL); |
| |
| /* STACK TRACE format: |
| * |
| * u4: serial number for this stack |
| * u4: serial number for the running thread |
| * u4: number of frames |
| * [ID]*: ID for the stack frame |
| */ |
| hprofAddU4ToRecord(rec, stackTraceEntry->trace.serialNumber); |
| hprofAddU4ToRecord(rec, stackTraceEntry->trace.threadSerialNumber); |
| |
| count = 0; |
| while ((count < STACK_DEPTH) && |
| (stackTraceEntry->trace.frameIds[count] != 0)) { |
| count++; |
| } |
| hprofAddU4ToRecord(rec, count); |
| for (i = 0; i < count; i++) { |
| hprofAddU4ToRecord(rec, stackTraceEntry->trace.frameIds[i]); |
| } |
| } |
| |
| dvmHashTableUnlock(gStackTraceHashTable); |
| |
| return 0; |
| } |
| |
| void |
| hprofFillInStackTrace(void *objectPtr) |
| |
| { |
| DvmHeapChunk *chunk; |
| StackTraceEntry stackTraceEntry; |
| Thread* self; |
| void* fp; |
| int i; |
| |
| if (objectPtr == NULL) { |
| return; |
| } |
| self = dvmThreadSelf(); |
| if (self == NULL) { |
| return; |
| } |
| fp = self->curFrame; |
| |
| /* Serial number to be filled in later. */ |
| stackTraceEntry.trace.serialNumber = -1; |
| |
| /* |
| * TODO - The HAT tool doesn't care about thread data, so we can defer |
| * actually emitting thread records and assigning thread serial numbers. |
| */ |
| stackTraceEntry.trace.threadSerialNumber = (int) self; |
| |
| memset(&stackTraceEntry.trace.frameIds, 0, |
| sizeof(stackTraceEntry.trace.frameIds)); |
| |
| i = 0; |
| while ((fp != NULL) && (i < STACK_DEPTH)) { |
| const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp); |
| const Method* method = saveArea->method; |
| StackFrameEntry frame; |
| |
| if (!dvmIsBreakFrame(fp)) { |
| frame.frame.method = method; |
| if (dvmIsNativeMethod(method)) { |
| frame.frame.pc = 0; /* no saved PC for native methods */ |
| } else { |
| assert(saveArea->xtra.currentPc >= method->insns && |
| saveArea->xtra.currentPc < |
| method->insns + dvmGetMethodInsnsSize(method)); |
| frame.frame.pc = (int) (saveArea->xtra.currentPc - |
| method->insns); |
| } |
| |
| // Canonicalize the frame and cache it in the hprof context |
| stackTraceEntry.trace.frameIds[i++] = |
| hprofLookupStackFrameId(&frame); |
| } |
| |
| assert(fp != saveArea->prevFrame); |
| fp = saveArea->prevFrame; |
| } |
| |
| /* Store the stack trace serial number in the object header */ |
| chunk = ptr2chunk(objectPtr); |
| chunk->stackTraceSerialNumber = |
| hprofLookupStackSerialNumber(&stackTraceEntry); |
| } |