/* * 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. */ /* * Strip Android-specific records out of hprof data, back-converting from * 1.0.3 to 1.0.2. This removes some useful information, but allows * Android hprof data to be handled by widely-available tools (like "jhat"). */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdint.h> #include <errno.h> #include <assert.h> //#define VERBOSE_DEBUG #ifdef VERBOSE_DEBUG # define DBUG(...) fprintf(stderr, __VA_ARGS__) #else # define DBUG(...) #endif #ifndef FALSE # define FALSE 0 # define TRUE (!FALSE) #endif typedef enum HprofBasicType { HPROF_BASIC_OBJECT = 2, HPROF_BASIC_BOOLEAN = 4, HPROF_BASIC_CHAR = 5, HPROF_BASIC_FLOAT = 6, HPROF_BASIC_DOUBLE = 7, HPROF_BASIC_BYTE = 8, HPROF_BASIC_SHORT = 9, HPROF_BASIC_INT = 10, HPROF_BASIC_LONG = 11, } HprofBasicType; typedef enum HprofTag { /* tags we must handle specially */ HPROF_TAG_HEAP_DUMP = 0x0c, HPROF_TAG_HEAP_DUMP_SEGMENT = 0x1c, } HprofTag; typedef enum HprofHeapTag { /* 1.0.2 tags */ HPROF_ROOT_UNKNOWN = 0xff, HPROF_ROOT_JNI_GLOBAL = 0x01, HPROF_ROOT_JNI_LOCAL = 0x02, HPROF_ROOT_JAVA_FRAME = 0x03, HPROF_ROOT_NATIVE_STACK = 0x04, HPROF_ROOT_STICKY_CLASS = 0x05, HPROF_ROOT_THREAD_BLOCK = 0x06, HPROF_ROOT_MONITOR_USED = 0x07, HPROF_ROOT_THREAD_OBJECT = 0x08, HPROF_CLASS_DUMP = 0x20, HPROF_INSTANCE_DUMP = 0x21, HPROF_OBJECT_ARRAY_DUMP = 0x22, HPROF_PRIMITIVE_ARRAY_DUMP = 0x23, /* Android 1.0.3 tags */ HPROF_HEAP_DUMP_INFO = 0xfe, HPROF_ROOT_INTERNED_STRING = 0x89, HPROF_ROOT_FINALIZING = 0x8a, HPROF_ROOT_DEBUGGER = 0x8b, HPROF_ROOT_REFERENCE_CLEANUP = 0x8c, HPROF_ROOT_VM_INTERNAL = 0x8d, HPROF_ROOT_JNI_MONITOR = 0x8e, HPROF_UNREACHABLE = 0x90, /* deprecated */ HPROF_PRIMITIVE_ARRAY_NODATA_DUMP = 0xc3, } HprofHeapTag; #define kIdentSize 4 #define kRecHdrLen 9 /* * =========================================================================== * Expanding buffer * =========================================================================== */ /* simple struct */ typedef struct { unsigned char* storage; size_t curLen; size_t maxLen; } ExpandBuf; /* * Create an ExpandBuf. */ static ExpandBuf* ebAlloc(void) { static const int kInitialSize = 64; ExpandBuf* newBuf = (ExpandBuf*) malloc(sizeof(ExpandBuf)); if (newBuf == NULL) return NULL; newBuf->storage = (unsigned char*) malloc(kInitialSize); newBuf->curLen = 0; newBuf->maxLen = kInitialSize; return newBuf; } /* * Release the storage associated with an ExpandBuf. */ static void ebFree(ExpandBuf* pBuf) { if (pBuf != NULL) { free(pBuf->storage); free(pBuf); } } /* * Return a pointer to the data buffer. * * The pointer may change as data is added to the buffer, so this value * should not be cached. */ static inline unsigned char* ebGetBuffer(ExpandBuf* pBuf) { return pBuf->storage; } /* * Get the amount of data currently in the buffer. */ static inline size_t ebGetLength(ExpandBuf* pBuf) { return pBuf->curLen; } /* * Empty the buffer. */ static void ebClear(ExpandBuf* pBuf) { pBuf->curLen = 0; } /* * Ensure that the buffer can hold at least "size" additional bytes. */ static int ebEnsureCapacity(ExpandBuf* pBuf, int size) { assert(size > 0); if (pBuf->curLen + size > pBuf->maxLen) { int newSize = pBuf->curLen + size + 128; /* oversize slightly */ unsigned char* newStorage = realloc(pBuf->storage, newSize); if (newStorage == NULL) { fprintf(stderr, "ERROR: realloc failed on size=%d\n", newSize); return -1; } pBuf->storage = newStorage; pBuf->maxLen = newSize; } assert(pBuf->curLen + size <= pBuf->maxLen); return 0; } /* * Add data to the buffer after ensuring it can hold it. */ static int ebAddData(ExpandBuf* pBuf, const void* data, size_t count) { ebEnsureCapacity(pBuf, count); memcpy(pBuf->storage + pBuf->curLen, data, count); pBuf->curLen += count; return 0; } /* * Read a NULL-terminated string from the input. */ static int ebReadString(ExpandBuf* pBuf, FILE* in) { int ic; do { ebEnsureCapacity(pBuf, 1); ic = getc(in); if (feof(in) || ferror(in)) { fprintf(stderr, "ERROR: failed reading input\n"); return -1; } pBuf->storage[pBuf->curLen++] = (unsigned char) ic; } while (ic != 0); return 0; } /* * Read some data, adding it to the expanding buffer. * * This will ensure that the buffer has enough space to hold the new data * (plus the previous contents). */ static int ebReadData(ExpandBuf* pBuf, FILE* in, size_t count, int eofExpected) { size_t actual; assert(count > 0); ebEnsureCapacity(pBuf, count); actual = fread(pBuf->storage + pBuf->curLen, 1, count, in); if (actual != count) { if (eofExpected && feof(in) && !ferror(in)) { /* return without reporting an error */ } else { fprintf(stderr, "ERROR: read %d of %d bytes\n", actual, count); return -1; } } pBuf->curLen += count; assert(pBuf->curLen <= pBuf->maxLen); return 0; } /* * Write the data from the buffer. Resets the data count to zero. */ static int ebWriteData(ExpandBuf* pBuf, FILE* out) { size_t actual; assert(pBuf->curLen > 0); assert(pBuf->curLen <= pBuf->maxLen); actual = fwrite(pBuf->storage, 1, pBuf->curLen, out); if (actual != pBuf->curLen) { fprintf(stderr, "ERROR: write %d of %d bytes\n", actual, pBuf->curLen); return -1; } pBuf->curLen = 0; return 0; } /* * =========================================================================== * Hprof stuff * =========================================================================== */ /* * Get a 2-byte value, in big-endian order, from memory. */ static uint16_t get2BE(const unsigned char* buf) { uint16_t val; val = (buf[0] << 8) | buf[1]; return val; } /* * Get a 4-byte value, in big-endian order, from memory. */ static uint32_t get4BE(const unsigned char* buf) { uint32_t val; val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; return val; } /* * Set a 4-byte value, in big-endian order. */ static void set4BE(unsigned char* buf, uint32_t val) { buf[0] = val >> 24; buf[1] = val >> 16; buf[2] = val >> 8; buf[3] = val; } /* * Get the size, in bytes, of one of the "basic types". */ static int computeBasicLen(HprofBasicType basicType) { static const int sizes[] = { -1, -1, 4, -1, 1, 2, 4, 8, 1, 2, 4, 8 }; static const size_t maxSize = sizeof(sizes) / sizeof(sizes[0]); assert(basicType >= 0); if (basicType >= maxSize) return -1; return sizes[basicType]; } /* * Compute the length of a HPROF_CLASS_DUMP block. */ static int computeClassDumpLen(const unsigned char* origBuf, int len) { const unsigned char* buf = origBuf; int blockLen = 0; int i, count; blockLen += kIdentSize * 7 + 8; buf += blockLen; len -= blockLen; if (len < 0) return -1; count = get2BE(buf); buf += 2; len -= 2; DBUG("CDL: 1st count is %d\n", count); for (i = 0; i < count; i++) { HprofBasicType basicType; int basicLen; basicType = buf[2]; basicLen = computeBasicLen(basicType); if (basicLen < 0) { DBUG("ERROR: invalid basicType %d\n", basicType); return -1; } buf += 2 + 1 + basicLen; len -= 2 + 1 + basicLen; if (len < 0) return -1; } count = get2BE(buf); buf += 2; len -= 2; DBUG("CDL: 2nd count is %d\n", count); for (i = 0; i < count; i++) { HprofBasicType basicType; int basicLen; basicType = buf[kIdentSize]; basicLen = computeBasicLen(basicType); if (basicLen < 0) { fprintf(stderr, "ERROR: invalid basicType %d\n", basicType); return -1; } buf += kIdentSize + 1 + basicLen; len -= kIdentSize + 1 + basicLen; if (len < 0) return -1; } count = get2BE(buf); buf += 2; len -= 2; DBUG("CDL: 3rd count is %d\n", count); for (i = 0; i < count; i++) { buf += kIdentSize + 1; len -= kIdentSize + 1; if (len < 0) return -1; } DBUG("Total class dump len: %d\n", buf - origBuf); return buf - origBuf; } /* * Compute the length of a HPROF_INSTANCE_DUMP block. */ static int computeInstanceDumpLen(const unsigned char* origBuf, int len) { int extraCount = get4BE(origBuf + kIdentSize * 2 + 4); return kIdentSize * 2 + 8 + extraCount; } /* * Compute the length of a HPROF_OBJECT_ARRAY_DUMP block. */ static int computeObjectArrayDumpLen(const unsigned char* origBuf, int len) { int arrayCount = get4BE(origBuf + kIdentSize + 4); return kIdentSize * 2 + 8 + arrayCount * kIdentSize; } /* * Compute the length of a HPROF_PRIMITIVE_ARRAY_DUMP block. */ static int computePrimitiveArrayDumpLen(const unsigned char* origBuf, int len) { int arrayCount = get4BE(origBuf + kIdentSize + 4); HprofBasicType basicType = origBuf[kIdentSize + 8]; int basicLen = computeBasicLen(basicType); return kIdentSize + 9 + arrayCount * basicLen; } /* * Crunch through a heap dump record, writing the original or converted * data to "out". */ static int processHeapDump(ExpandBuf* pBuf, FILE* out) { ExpandBuf* pOutBuf = ebAlloc(); unsigned char* origBuf = ebGetBuffer(pBuf); unsigned char* buf = origBuf; int len = ebGetLength(pBuf); int result = -1; pBuf = NULL; /* we just use the raw pointer from here forward */ /* copy the original header to the output buffer */ if (ebAddData(pOutBuf, buf, kRecHdrLen) != 0) goto bail; buf += kRecHdrLen; /* skip past record header */ len -= kRecHdrLen; while (len > 0) { unsigned char subType = buf[0]; int justCopy = TRUE; int subLen; DBUG("--- 0x%02x ", subType); switch (subType) { /* 1.0.2 types */ case HPROF_ROOT_UNKNOWN: subLen = kIdentSize; break; case HPROF_ROOT_JNI_GLOBAL: subLen = kIdentSize * 2; break; case HPROF_ROOT_JNI_LOCAL: subLen = kIdentSize + 8; break; case HPROF_ROOT_JAVA_FRAME: subLen = kIdentSize + 8; break; case HPROF_ROOT_NATIVE_STACK: subLen = kIdentSize + 4; break; case HPROF_ROOT_STICKY_CLASS: subLen = kIdentSize; break; case HPROF_ROOT_THREAD_BLOCK: subLen = kIdentSize + 4; break; case HPROF_ROOT_MONITOR_USED: subLen = kIdentSize; break; case HPROF_ROOT_THREAD_OBJECT: subLen = kIdentSize + 8; break; case HPROF_CLASS_DUMP: subLen = computeClassDumpLen(buf+1, len-1); break; case HPROF_INSTANCE_DUMP: subLen = computeInstanceDumpLen(buf+1, len-1); break; case HPROF_OBJECT_ARRAY_DUMP: subLen = computeObjectArrayDumpLen(buf+1, len-1); break; case HPROF_PRIMITIVE_ARRAY_DUMP: subLen = computePrimitiveArrayDumpLen(buf+1, len-1); break; /* these were added for Android in 1.0.3 */ case HPROF_HEAP_DUMP_INFO: justCopy = FALSE; subLen = kIdentSize + 4; // no 1.0.2 equivalent for this break; case HPROF_ROOT_INTERNED_STRING: buf[0] = HPROF_ROOT_UNKNOWN; subLen = kIdentSize; break; case HPROF_ROOT_FINALIZING: buf[0] = HPROF_ROOT_UNKNOWN; subLen = kIdentSize; break; case HPROF_ROOT_DEBUGGER: buf[0] = HPROF_ROOT_UNKNOWN; subLen = kIdentSize; break; case HPROF_ROOT_REFERENCE_CLEANUP: buf[0] = HPROF_ROOT_UNKNOWN; subLen = kIdentSize; break; case HPROF_ROOT_VM_INTERNAL: buf[0] = HPROF_ROOT_UNKNOWN; subLen = kIdentSize; break; case HPROF_ROOT_JNI_MONITOR: /* keep the ident, drop the next 8 bytes */ buf[0] = HPROF_ROOT_UNKNOWN; justCopy = FALSE; ebAddData(pOutBuf, buf, 1 + kIdentSize); subLen = kIdentSize + 8; break; case HPROF_UNREACHABLE: buf[0] = HPROF_ROOT_UNKNOWN; subLen = kIdentSize; break; case HPROF_PRIMITIVE_ARRAY_NODATA_DUMP: buf[0] = HPROF_PRIMITIVE_ARRAY_DUMP; buf[5] = buf[6] = buf[7] = buf[8] = 0; /* set array len to 0 */ subLen = kIdentSize + 9; break; /* shouldn't get here */ default: fprintf(stderr, "ERROR: unexpected subtype 0x%02x at offset %d\n", subType, buf - origBuf); goto bail; } if (justCopy) { /* copy source data */ DBUG("(%d)\n", 1 + subLen); ebAddData(pOutBuf, buf, 1 + subLen); } else { /* other data has been written, or the sub-record omitted */ DBUG("(adv %d)\n", 1 + subLen); } /* advance to next entry */ buf += 1 + subLen; len -= 1 + subLen; } /* * Update the record length. */ set4BE(ebGetBuffer(pOutBuf) + 5, ebGetLength(pOutBuf) - kRecHdrLen); if (ebWriteData(pOutBuf, out) != 0) goto bail; result = 0; bail: ebFree(pOutBuf); return result; } /* * Filter an hprof data file. */ static int filterData(FILE* in, FILE* out) { ExpandBuf* pBuf; int result = -1; pBuf = ebAlloc(); if (pBuf == NULL) goto bail; /* * Start with the header. */ if (ebReadString(pBuf, in) != 0) goto bail; if (strcmp((const char*)ebGetBuffer(pBuf), "JAVA PROFILE 1.0.3") != 0) { fprintf(stderr, "ERROR: expecting 1.0.3\n"); goto bail; } /* downgrade to 1.0.2 */ (ebGetBuffer(pBuf))[17] = '2'; if (ebWriteData(pBuf, out) != 0) goto bail; /* * Copy: * (4b) identifier size, always 4 * (8b) file creation date */ if (ebReadData(pBuf, in, 12, FALSE) != 0) goto bail; if (ebWriteData(pBuf, out) != 0) goto bail; /* * Read records until we hit EOF. Each record begins with: * (1b) type * (4b) timestamp * (4b) length of data that follows */ while (1) { assert(ebGetLength(pBuf) == 0); /* read type char */ if (ebReadData(pBuf, in, 1, TRUE) != 0) goto bail; if (feof(in)) break; /* read the rest of the header */ if (ebReadData(pBuf, in, kRecHdrLen-1, FALSE) != 0) goto bail; unsigned char* buf = ebGetBuffer(pBuf); unsigned char type; unsigned int timestamp, length; type = buf[0]; timestamp = get4BE(buf + 1); length = get4BE(buf + 5); buf = NULL; /* ptr invalid after next read op */ /* read the record data */ if (length != 0) { if (ebReadData(pBuf, in, length, FALSE) != 0) goto bail; } if (type == HPROF_TAG_HEAP_DUMP || type == HPROF_TAG_HEAP_DUMP_SEGMENT) { DBUG("Processing heap dump 0x%02x (%d bytes)\n", type, length); if (processHeapDump(pBuf, out) != 0) goto bail; ebClear(pBuf); } else { /* keep */ DBUG("Keeping 0x%02x (%d bytes)\n", type, length); if (ebWriteData(pBuf, out) != 0) goto bail; } } result = 0; bail: ebFree(pBuf); return result; } /* * Get args. */ int main(int argc, char** argv) { FILE* in = stdin; FILE* out = stdout; int cc; if (argc != 3) { fprintf(stderr, "Usage: hprof-conf infile outfile\n\n"); fprintf(stderr, "Specify '-' for either or both to use stdin/stdout.\n\n"); fprintf(stderr, "Copyright (C) 2009 The Android Open Source Project\n\n" "This software is built from source code licensed under the " "Apache License,\n" "Version 2.0 (the \"License\"). You may obtain a copy of the " "License at\n\n" " http://www.apache.org/licenses/LICENSE-2.0\n\n" "See the associated NOTICE file for this software for further " "details.\n"); return 2; } if (strcmp(argv[1], "-") != 0) { in = fopen(argv[1], "rb"); if (in == NULL) { fprintf(stderr, "ERROR: failed to open input '%s': %s\n", argv[1], strerror(errno)); return 1; } } if (strcmp(argv[2], "-") != 0) { out = fopen(argv[2], "wb"); if (out == NULL) { fprintf(stderr, "ERROR: failed to open output '%s': %s\n", argv[2], strerror(errno)); if (in != stdin) fclose(in); return 1; } } cc = filterData(in, out); if (in != stdin) fclose(in); if (out != stdout) fclose(out); return (cc != 0); }