| /* |
| * 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); |
| } |