Add hprof. This code builds but is untested.

Change-Id: Ied6549ef903761447fb3d28cf28b8bbc76e5ef35
diff --git a/build/Android.common.mk b/build/Android.common.mk
index 4fb0187..1aebb65 100644
--- a/build/Android.common.mk
+++ b/build/Android.common.mk
@@ -120,6 +120,11 @@
 	src/file_linux.cc \
 	src/heap.cc \
 	src/heap_bitmap.cc \
+	src/hprof/hprof.cc \
+	src/hprof/hprof_class.cc \
+	src/hprof/hprof_heap.cc \
+	src/hprof/hprof_output.cc \
+	src/hprof/hprof_string.cc \
 	src/image.cc \
 	src/image_writer.cc \
 	src/indirect_reference_table.cc \
diff --git a/src/hprof/hprof.cc b/src/hprof/hprof.cc
new file mode 100644
index 0000000..9415549
--- /dev/null
+++ b/src/hprof/hprof.cc
@@ -0,0 +1,291 @@
+/*
+ * 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.
+ */
+
+/*
+ * Preparation and completion of hprof data generation.  The output is
+ * written into two files and then combined.  This is necessary because
+ * we generate some of the data (strings and classes) while we dump the
+ * heap, and some analysis tools require that the class and string data
+ * appear first.
+ */
+
+#include "hprof.h"
+#include "heap.h"
+#include "debugger.h"
+#include "stringprintf.h"
+#include "thread_list.h"
+#include "logging.h"
+
+#include <sys/uio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <time.h>
+
+namespace art {
+
+namespace hprof {
+
+#define kHeadSuffix "-hptemp"
+
+hprof_context_t* hprofStartup(const char *outputFileName, int fd,
+                              bool directToDdms)
+{
+    hprofStartup_String();
+    hprofStartup_Class();
+
+    hprof_context_t *ctx = (hprof_context_t *)malloc(sizeof(*ctx));
+    if (ctx == NULL) {
+        LOG(ERROR) << "hprof: can't allocate context.";
+        return NULL;
+    }
+
+    /* pass in name or descriptor of the output file */
+    hprofContextInit(ctx, strdup(outputFileName), fd, false, directToDdms);
+
+    CHECK(ctx->memFp != NULL);
+
+    return ctx;
+}
+
+// TODO: use File::WriteFully
+int sysWriteFully(int fd, const void* buf, size_t count, const char* logMsg)
+{
+    while (count != 0) {
+        ssize_t actual = TEMP_FAILURE_RETRY(write(fd, buf, count));
+        if (actual < 0) {
+            int err = errno;
+            LOG(ERROR) << StringPrintf("%s: write failed: %s", logMsg, strerror(err));
+            return err;
+        } else if (actual != (ssize_t) count) {
+            LOG(DEBUG) << StringPrintf("%s: partial write (will retry): (%d of %zd)",
+                logMsg, (int) actual, count);
+            buf = (const void*) (((const uint8_t*) buf) + actual);
+        }
+        count -= actual;
+    }
+
+    return 0;
+}
+
+/*
+ * Finish up the hprof dump.  Returns true on success.
+ */
+bool hprofShutdown(hprof_context_t *tailCtx)
+{
+    /* flush the "tail" portion of the output */
+    hprofFlushCurrentRecord(tailCtx);
+
+    /*
+     * Create a new context struct for the start of the file.  We
+     * heap-allocate it so we can share the "free" function.
+     */
+    hprof_context_t *headCtx = (hprof_context_t *)malloc(sizeof(*headCtx));
+    if (headCtx == NULL) {
+        LOG(ERROR) << "hprof: can't allocate context.";
+        hprofFreeContext(tailCtx);
+        return false;
+    }
+    hprofContextInit(headCtx, strdup(tailCtx->fileName), tailCtx->fd, true,
+        tailCtx->directToDdms);
+
+    LOG(INFO) << StringPrintf("hprof: dumping heap strings to \"%s\".", tailCtx->fileName);
+    hprofDumpStrings(headCtx);
+    hprofDumpClasses(headCtx);
+
+    /* Write a dummy stack trace record so the analysis
+     * tools don't freak out.
+     */
+    hprofStartNewRecord(headCtx, HPROF_TAG_STACK_TRACE, HPROF_TIME);
+    hprofAddU4ToRecord(&headCtx->curRec, HPROF_NULL_STACK_TRACE);
+    hprofAddU4ToRecord(&headCtx->curRec, HPROF_NULL_THREAD);
+    hprofAddU4ToRecord(&headCtx->curRec, 0);    // no frames
+
+    hprofFlushCurrentRecord(headCtx);
+
+    hprofShutdown_Class();
+    hprofShutdown_String();
+
+    /* flush to ensure memstream pointer and size are updated */
+    fflush(headCtx->memFp);
+    fflush(tailCtx->memFp);
+
+    if (tailCtx->directToDdms) {
+        /* send the data off to DDMS */
+        struct iovec iov[2];
+        iov[0].iov_base = headCtx->fileDataPtr;
+        iov[0].iov_len = headCtx->fileDataSize;
+        iov[1].iov_base = tailCtx->fileDataPtr;
+        iov[1].iov_len = tailCtx->fileDataSize;
+        Dbg::DdmSendChunkV(CHUNK_TYPE("HPDS"), iov, 2);
+    } else {
+        /*
+         * Open the output file, and copy the head and tail to it.
+         */
+        CHECK(headCtx->fd == tailCtx->fd);
+
+        int outFd;
+        if (headCtx->fd >= 0) {
+            outFd = dup(headCtx->fd);
+            if (outFd < 0) {
+                LOG(ERROR) << StringPrintf("dup(%d) failed: %s", headCtx->fd, strerror(errno));
+                /* continue to fail-handler below */
+            }
+        } else {
+            outFd = open(tailCtx->fileName, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+            if (outFd < 0) {
+                LOG(ERROR) << StringPrintf("can't open %s: %s", headCtx->fileName, strerror(errno));
+                /* continue to fail-handler below */
+            }
+        }
+        if (outFd < 0) {
+            hprofFreeContext(headCtx);
+            hprofFreeContext(tailCtx);
+            return false;
+        }
+
+        int result;
+        result = sysWriteFully(outFd, headCtx->fileDataPtr,
+            headCtx->fileDataSize, "hprof-head");
+        result |= sysWriteFully(outFd, tailCtx->fileDataPtr,
+            tailCtx->fileDataSize, "hprof-tail");
+        close(outFd);
+        if (result != 0) {
+            hprofFreeContext(headCtx);
+            hprofFreeContext(tailCtx);
+            return false;
+        }
+    }
+
+    /* throw out a log message for the benefit of "runhat" */
+    LOG(INFO) << StringPrintf("hprof: heap dump completed (%dKB)",
+        (headCtx->fileDataSize + tailCtx->fileDataSize + 1023) / 1024);
+
+    hprofFreeContext(headCtx);
+    hprofFreeContext(tailCtx);
+
+    return true;
+}
+
+/*
+ * Free any heap-allocated items in "ctx", and then free "ctx" itself.
+ */
+void hprofFreeContext(hprof_context_t *ctx)
+{
+    CHECK(ctx != NULL);
+
+    /* we don't own ctx->fd, do not close */
+
+    if (ctx->memFp != NULL)
+        fclose(ctx->memFp);
+    free(ctx->curRec.body);
+    free(ctx->fileName);
+    free(ctx->fileDataPtr);
+    free(ctx);
+}
+
+/*
+ * Visitor invoked on every root reference.
+ */
+void HprofRootVisitor(const Object* obj, void* arg) {
+    uint32_t threadId = 0;  // TODO
+    /*RootType */ size_t type = 0; // TODO
+
+    static const hprof_heap_tag_t xlate[] = {
+        HPROF_ROOT_UNKNOWN,
+        HPROF_ROOT_JNI_GLOBAL,
+        HPROF_ROOT_JNI_LOCAL,
+        HPROF_ROOT_JAVA_FRAME,
+        HPROF_ROOT_NATIVE_STACK,
+        HPROF_ROOT_STICKY_CLASS,
+        HPROF_ROOT_THREAD_BLOCK,
+        HPROF_ROOT_MONITOR_USED,
+        HPROF_ROOT_THREAD_OBJECT,
+        HPROF_ROOT_INTERNED_STRING,
+        HPROF_ROOT_FINALIZING,
+        HPROF_ROOT_DEBUGGER,
+        HPROF_ROOT_REFERENCE_CLEANUP,
+        HPROF_ROOT_VM_INTERNAL,
+        HPROF_ROOT_JNI_MONITOR,
+    };
+    hprof_context_t *ctx;
+
+    CHECK(arg != NULL);
+    CHECK(type < (sizeof(xlate) / sizeof(hprof_heap_tag_t)));
+    if (obj == NULL) {
+        return;
+    }
+    ctx = (hprof_context_t *)arg;
+    ctx->gcScanState = xlate[type];
+    ctx->gcThreadSerialNumber = threadId;
+    hprofMarkRootObject(ctx, obj, 0);
+    ctx->gcScanState = 0;
+    ctx->gcThreadSerialNumber = 0;
+}
+
+/*
+ * Visitor invoked on every heap object.
+ */
+
+static void HprofBitmapCallback(Object *obj, void *arg)
+{
+    CHECK(obj != NULL);
+    CHECK(arg != NULL);
+    hprof_context_t *ctx = (hprof_context_t *)arg;
+    hprofDumpHeapObject(ctx, obj);
+}
+
+/*
+ * Walk the roots and heap writing heap information to the specified
+ * file.
+ *
+ * If "fd" is >= 0, the output will be written to that file descriptor.
+ * Otherwise, "fileName" is used to create an output file.
+ *
+ * If "directToDdms" is set, the other arguments are ignored, and data is
+ * sent directly to DDMS.
+ *
+ * Returns 0 on success, or an error code on failure.
+ */
+int hprofDumpHeap(const char* fileName, int fd, bool directToDdms)
+{
+    hprof_context_t *ctx;
+    int success;
+
+    CHECK(fileName != NULL);
+    ScopedHeapLock lock;
+
+    ThreadList* thread_list = Runtime::Current()->GetThreadList();
+    thread_list->SuspendAll();
+
+    ctx = hprofStartup(fileName, fd, directToDdms);
+    if (ctx == NULL) {
+        return -1;
+    }
+    Runtime::Current()->VisitRoots(HprofRootVisitor, ctx);
+    Heap::GetLiveBits()->Walk(HprofBitmapCallback, ctx);
+    hprofFinishHeapDump(ctx);
+//TODO: write a HEAP_SUMMARY record
+    success = hprofShutdown(ctx) ? 0 : -1;
+    thread_list->ResumeAll();
+    return success;
+}
+
+}  // namespace hprof
+
+}  // namespace art
diff --git a/src/hprof/hprof.h b/src/hprof/hprof.h
new file mode 100644
index 0000000..092f00d
--- /dev/null
+++ b/src/hprof/hprof.h
@@ -0,0 +1,240 @@
+/*
+ * 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.
+ */
+#ifndef HPROF_HPROF_H_
+#define HPROF_HPROF_H_
+
+#include <stdio.h>
+#include "globals.h"
+#include "file.h"
+#include "object.h"
+
+namespace art {
+
+namespace hprof {
+
+#define HPROF_ID_SIZE (sizeof (uint32_t))
+
+#define UNIQUE_ERROR() \
+    -((((uintptr_t)__func__) << 16 | __LINE__) & (0x7fffffff))
+
+#define HPROF_TIME 0
+#define HPROF_NULL_STACK_TRACE   0
+#define HPROF_NULL_THREAD        0
+
+typedef uint32_t hprof_id;
+typedef hprof_id hprof_string_id;
+typedef hprof_id hprof_object_id;
+typedef hprof_id hprof_class_object_id;
+
+enum hprof_basic_type {
+    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,
+};
+
+enum hprof_tag_t {
+    HPROF_TAG_STRING = 0x01,
+    HPROF_TAG_LOAD_CLASS = 0x02,
+    HPROF_TAG_UNLOAD_CLASS = 0x03,
+    HPROF_TAG_STACK_FRAME = 0x04,
+    HPROF_TAG_STACK_TRACE = 0x05,
+    HPROF_TAG_ALLOC_SITES = 0x06,
+    HPROF_TAG_HEAP_SUMMARY = 0x07,
+    HPROF_TAG_START_THREAD = 0x0A,
+    HPROF_TAG_END_THREAD = 0x0B,
+    HPROF_TAG_HEAP_DUMP = 0x0C,
+    HPROF_TAG_HEAP_DUMP_SEGMENT = 0x1C,
+    HPROF_TAG_HEAP_DUMP_END = 0x2C,
+    HPROF_TAG_CPU_SAMPLES = 0x0D,
+    HPROF_TAG_CONTROL_SETTINGS = 0x0E,
+};
+
+/* Values for the first byte of
+ * HEAP_DUMP and HEAP_DUMP_SEGMENT
+ * records:
+ */
+enum hprof_heap_tag_t {
+    /* standard */
+    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 */
+    HPROF_HEAP_DUMP_INFO = 0xfe,
+    HPROF_ROOT_INTERNED_STRING = 0x89,
+    HPROF_ROOT_FINALIZING = 0x8a,  /* obsolete */
+    HPROF_ROOT_DEBUGGER = 0x8b,
+    HPROF_ROOT_REFERENCE_CLEANUP = 0x8c,  /* obsolete */
+    HPROF_ROOT_VM_INTERNAL = 0x8d,
+    HPROF_ROOT_JNI_MONITOR = 0x8e,
+    HPROF_UNREACHABLE = 0x90,  /* obsolete */
+    HPROF_PRIMITIVE_ARRAY_NODATA_DUMP = 0xc3,
+};
+
+/* Represents a top-level hprof record, whose serialized
+ * format is:
+ *
+ *     uint8_t     TAG: denoting the type of the record
+ *     uint32_t     TIME: number of microseconds since the time stamp in the header
+ *     uint32_t     LENGTH: number of bytes that follow this uint32_t field
+ *                    and belong to this record
+ *     [uint8_t]*  BODY: as many bytes as specified in the above uint32_t field
+ */
+struct hprof_record_t {
+    unsigned char *body;
+    uint32_t time;
+    uint32_t length;
+    size_t allocLen;
+    uint8_t tag;
+    bool dirty;
+};
+
+enum HprofHeapId {
+    HPROF_HEAP_DEFAULT = 0,
+    HPROF_HEAP_ZYGOTE = 'Z',
+    HPROF_HEAP_APP = 'A'
+};
+
+struct hprof_context_t {
+    /* curRec *must* be first so that we
+     * can cast from a context to a record.
+     */
+    hprof_record_t curRec;
+
+    uint32_t gcThreadSerialNumber;
+    uint8_t gcScanState;
+    HprofHeapId currentHeap;    // which heap we're currently emitting
+    uint32_t stackTraceSerialNumber;
+    size_t objectsInSegment;
+
+    /*
+     * If directToDdms is set, "fileName" and "fd" will be ignored.
+     * Otherwise, "fileName" must be valid, though if "fd" >= 0 it will
+     * only be used for debug messages.
+     */
+    bool directToDdms;
+    char *fileName;
+    char *fileDataPtr;          // for open_memstream
+    size_t fileDataSize;        // for open_memstream
+    FILE *memFp;
+    int fd;
+};
+
+
+/*
+ * hprof_string.cc functions
+ */
+
+hprof_string_id hprofLookupStringId(String* string);
+hprof_string_id hprofLookupStringId(const char* string);
+hprof_string_id hprofLookupStringId(std::string string);
+
+int hprofDumpStrings(hprof_context_t *ctx);
+
+int hprofStartup_String(void);
+int hprofShutdown_String(void);
+
+
+/*
+ * hprof_class.cc functions
+ */
+
+hprof_class_object_id hprofLookupClassId(Class* clazz);
+
+int hprofDumpClasses(hprof_context_t *ctx);
+
+int hprofStartup_Class(void);
+int hprofShutdown_Class(void);
+
+
+/*
+ * hprof_heap.cc functions
+ */
+
+int hprofStartHeapDump(hprof_context_t *ctx);
+int hprofFinishHeapDump(hprof_context_t *ctx);
+
+int hprofSetGcScanState(hprof_context_t *ctx,
+                        hprof_heap_tag_t state, uint32_t threadSerialNumber);
+int hprofMarkRootObject(hprof_context_t *ctx,
+                        const Object *obj, jobject jniObj);
+
+int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj);
+
+/*
+ * hprof_output.cc functions
+ */
+
+void hprofContextInit(hprof_context_t *ctx, char *fileName, int fd,
+                      bool writeHeader, bool directToDdms);
+
+int hprofFlushRecord(hprof_record_t *rec, FILE *fp);
+int hprofFlushCurrentRecord(hprof_context_t *ctx);
+int hprofStartNewRecord(hprof_context_t *ctx, uint8_t tag, uint32_t time);
+
+int hprofAddU1ToRecord(hprof_record_t *rec, uint8_t value);
+int hprofAddU1ListToRecord(hprof_record_t *rec,
+                           const uint8_t *values, size_t numValues);
+
+int hprofAddUtf8StringToRecord(hprof_record_t *rec, const char *str);
+
+int hprofAddU2ToRecord(hprof_record_t *rec, uint16_t value);
+int hprofAddU2ListToRecord(hprof_record_t *rec,
+                           const uint16_t *values, size_t numValues);
+
+int hprofAddU4ToRecord(hprof_record_t *rec, uint32_t value);
+int hprofAddU4ListToRecord(hprof_record_t *rec,
+                           const uint32_t *values, size_t numValues);
+
+int hprofAddU8ToRecord(hprof_record_t *rec, uint64_t value);
+int hprofAddU8ListToRecord(hprof_record_t *rec,
+                           const uint64_t *values, size_t numValues);
+
+#define hprofAddIdToRecord(rec, id) hprofAddU4ToRecord((rec), (uint32_t)(id))
+#define hprofAddIdListToRecord(rec, values, numValues) \
+            hprofAddU4ListToRecord((rec), (const uint32_t *)(values), (numValues))
+
+/*
+ * hprof.cc functions
+ */
+
+hprof_context_t* hprofStartup(const char *outputFileName, int fd,
+    bool directToDdms);
+bool hprofShutdown(hprof_context_t *ctx);
+void hprofFreeContext(hprof_context_t *ctx);
+int hprofDumpHeap(const char* fileName, int fd, bool directToDdms);
+
+}  // namespace hprof
+
+}  // namespace art
+
+#endif  // HPROF_HPROF_H_
diff --git a/src/hprof/hprof_class.cc b/src/hprof/hprof_class.cc
new file mode 100644
index 0000000..d64e553
--- /dev/null
+++ b/src/hprof/hprof_class.cc
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+/*
+ * Class object pool
+ */
+
+#include "hprof.h"
+#include "object.h"
+#include "logging.h"
+#include "unordered_set.h"
+
+namespace art {
+
+namespace hprof {
+
+typedef std::tr1::unordered_set<Class*, ObjectIdentityHash> ClassSet;
+typedef std::tr1::unordered_set<Class*, ObjectIdentityHash>::iterator ClassSetIterator;
+static Mutex classes_lock_("hprof classes");
+static ClassSet classes_;
+
+int hprofStartup_Class() {
+    return 0;
+}
+
+int hprofShutdown_Class() {
+    return 0;
+}
+
+static int getPrettyClassNameId(Class* clazz) {
+    std::string name(PrettyClass(clazz));
+    return hprofLookupStringId(name); // TODO: leaks
+}
+
+hprof_class_object_id hprofLookupClassId(Class* clazz) {
+    if (clazz == NULL) {
+        /* Someone's probably looking up the superclass
+         * of java.lang.Object or of a primitive class.
+         */
+        return (hprof_class_object_id)0;
+    }
+
+    MutexLock mu(classes_lock_);
+
+    std::pair<ClassSetIterator, bool> result = classes_.insert(clazz);
+    Class* present = *result.first;
+
+    /* Make sure that the class's name is in the string table.
+     * This is a bunch of extra work that we only have to do
+     * because of the order of tables in the output file
+     * (strings need to be dumped before classes).
+     */
+    getPrettyClassNameId(clazz);
+
+    return (hprof_string_id) present;
+}
+
+int hprofDumpClasses(hprof_context_t *ctx) {
+    MutexLock mu(classes_lock_);
+
+    hprof_record_t *rec = &ctx->curRec;
+
+    uint32_t nextSerialNumber = 1;
+
+    for (ClassSetIterator it = classes_.begin(); it != classes_.end(); ++it) {
+        Class* clazz = *it;
+        CHECK(clazz != NULL);
+
+        int err = hprofStartNewRecord(ctx, HPROF_TAG_LOAD_CLASS, HPROF_TIME);
+        if (err != 0) {
+            return err;
+        }
+
+        /* LOAD CLASS format:
+         *
+         * uint32_t:     class serial number (always > 0)
+         * ID:     class object ID
+         * uint32_t:     stack trace serial number
+         * ID:     class name string ID
+         *
+         * We use the address of the class object structure as its ID.
+         */
+        hprofAddU4ToRecord(rec, nextSerialNumber++);
+        hprofAddIdToRecord(rec, (hprof_class_object_id) clazz);
+        hprofAddU4ToRecord(rec, HPROF_NULL_STACK_TRACE);
+        hprofAddIdToRecord(rec, getPrettyClassNameId(clazz));
+    }
+
+    return 0;
+}
+
+}  // namespace hprof
+
+}  // namespace art
diff --git a/src/hprof/hprof_heap.cc b/src/hprof/hprof_heap.cc
new file mode 100644
index 0000000..9545f7c
--- /dev/null
+++ b/src/hprof/hprof_heap.cc
@@ -0,0 +1,476 @@
+/*
+ * 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 "primitive.h"
+#include "object.h"
+#include "logging.h"
+
+namespace art {
+
+namespace hprof {
+
+/* 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)
+
+/* The static field-name for the synthetic object generated to account
+ * for class Static overhead.
+ */
+#define STATIC_OVERHEAD_NAME    "$staticOverhead"
+/* The ID for the synthetic object generated to account for class
+ * Static overhead.
+ */
+#define CLASS_STATICS_ID(clazz) ((hprof_object_id)(((uint32_t)(clazz)) | 1))
+
+int hprofStartHeapDump(hprof_context_t *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,
+                        uint32_t 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: CHECK(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(Primitive::Type prim,
+                                                    size_t *sizeOut)
+{
+    hprof_basic_type ret;
+    size_t size;
+
+    switch (prim) {
+    case Primitive::kPrimBoolean: ret = hprof_basic_boolean; size = 1; break;
+    case Primitive::kPrimChar:    ret = hprof_basic_char;    size = 2; break;
+    case Primitive::kPrimFloat:   ret = hprof_basic_float;   size = 4; break;
+    case Primitive::kPrimDouble:  ret = hprof_basic_double;  size = 8; break;
+    case Primitive::kPrimByte:    ret = hprof_basic_byte;    size = 1; break;
+    case Primitive::kPrimShort:   ret = hprof_basic_short;   size = 2; break;
+    default: CHECK(false);
+    case Primitive::kPrimInt:     ret = hprof_basic_int;     size = 4; break;
+    case Primitive::kPrimLong:    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 = (hprof_heap_tag_t)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:
+        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
+     * uint32_t: thread serial number
+     * uint32_t: 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, (uint32_t)-1);
+        break;
+
+    /* ID: object ID
+     * uint32_t: 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
+     * uint32_t: thread serial number
+     * uint32_t: stack trace serial number
+     */
+    case HPROF_ROOT_THREAD_OBJECT:
+        hprofAddU1ToRecord(rec, heapTag);
+        hprofAddIdToRecord(rec, (hprof_object_id)obj);
+        hprofAddU4ToRecord(rec, ctx->gcThreadSerialNumber);
+        hprofAddU4ToRecord(rec, (uint32_t)-1);    //xxx
+        break;
+
+    default:
+        err = 0;
+        break;
+    }
+
+    ctx->objectsInSegment++;
+
+    return err;
+}
+
+static int stackTraceSerialNumber(const void *obj)
+{
+    return HPROF_NULL_STACK_TRACE;
+}
+
+int hprofDumpHeapObject(hprof_context_t *ctx, const Object *obj)
+{
+    Class* clazz;
+    hprof_record_t *rec = &ctx->curRec;
+    HprofHeapId desiredHeap;
+
+    desiredHeap = false ? HPROF_HEAP_ZYGOTE : HPROF_HEAP_APP; // TODO: zygote objects?
+
+    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, (uint32_t)desiredHeap);   // uint32_t: heap id
+        switch (desiredHeap) {
+        case HPROF_HEAP_APP:
+            nameId = hprofLookupStringId("app");
+            break;
+        case HPROF_HEAP_ZYGOTE:
+            nameId = hprofLookupStringId("zygote");
+            break;
+        default:
+            /* Internal error. */
+            LOG(ERROR) << "Unexpected desiredHeap";
+            nameId = hprofLookupStringId("<ILLEGAL>");
+            break;
+        }
+        hprofAddIdToRecord(rec, nameId);
+        ctx->currentHeap = desiredHeap;
+    }
+
+    clazz = obj->GetClass();
+
+    if (clazz == NULL) {
+        /* This object will bother HprofReader, because it has a NULL
+         * class, so just don't dump it. It could be
+         * gDvm.unlinkedJavaLangClass or it could be an object just
+         * allocated which hasn't been initialized yet.
+         */
+    } else {
+        if (obj->IsClass()) {
+            Class* thisClass = (Class*)obj;
+            /* obj is a ClassObject.
+             */
+            size_t sFieldCount = thisClass->NumStaticFields();
+            if (sFieldCount != 0) {
+                int byteLength = sFieldCount*sizeof(JValue); // TODO bogus; fields are packed
+                /* Create a byte array to reflect the allocation of the
+                 * StaticField array at the end of this class.
+                 */
+                hprofAddU1ToRecord(rec, HPROF_PRIMITIVE_ARRAY_DUMP);
+                hprofAddIdToRecord(rec, CLASS_STATICS_ID(obj));
+                hprofAddU4ToRecord(rec, stackTraceSerialNumber(obj));
+                hprofAddU4ToRecord(rec, byteLength);
+                hprofAddU1ToRecord(rec, hprof_basic_byte);
+                for (int i = 0; i < byteLength; i++) {
+                    hprofAddU1ToRecord(rec, 0);
+                }
+            }
+
+            hprofAddU1ToRecord(rec, HPROF_CLASS_DUMP);
+            hprofAddIdToRecord(rec, hprofLookupClassId(thisClass));
+            hprofAddU4ToRecord(rec, stackTraceSerialNumber(thisClass));
+            hprofAddIdToRecord(rec, hprofLookupClassId(thisClass->GetSuperClass()));
+            hprofAddIdToRecord(rec, (hprof_object_id)thisClass->GetClassLoader());
+            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->IsClassClass()) {
+                // ClassObjects have their static fields appended, so
+                // aren't all the same size. But they're at least this
+                // size.
+                hprofAddU4ToRecord(rec, sizeof(Class)); // instance size
+            } else {
+                hprofAddU4ToRecord(rec, thisClass->GetObjectSize()); // instance size
+            }
+
+            hprofAddU2ToRecord(rec, 0);                     // empty const pool
+
+            /* Static fields
+             */
+            if (sFieldCount == 0) {
+                hprofAddU2ToRecord(rec, (uint16_t)0);
+            } else {
+                hprofAddU2ToRecord(rec, (uint16_t)(sFieldCount+1));
+                hprofAddIdToRecord(rec,
+                                   hprofLookupStringId(STATIC_OVERHEAD_NAME));
+                hprofAddU1ToRecord(rec, hprof_basic_object);
+                hprofAddIdToRecord(rec, CLASS_STATICS_ID(obj));
+
+                for (size_t i = 0; i < sFieldCount; ++i) {
+                    hprof_basic_type t;
+                    size_t size;
+                    Field* f = thisClass->GetStaticField(i);
+
+                    t = signatureToBasicTypeAndSize(f->GetTypeDescriptor(), &size);
+                    hprofAddIdToRecord(rec, hprofLookupStringId(f->GetName()));
+                    hprofAddU1ToRecord(rec, t);
+                    if (size == 1) {
+                        hprofAddU1ToRecord(rec, (uint8_t)f->GetByte(NULL));
+                    } else if (size == 2) {
+                        hprofAddU2ToRecord(rec, (uint16_t)f->GetShort(NULL));
+                    } else if (size == 4) {
+                        hprofAddU4ToRecord(rec, (uint32_t)f->GetInt(NULL));
+                    } else if (size == 8) {
+                        hprofAddU8ToRecord(rec, (uint64_t)f->GetLong(NULL));
+                    } else {
+                        CHECK(false);
+                    }
+                }
+            }
+
+            /* Instance fields for this class (no superclass fields)
+             */
+            int iFieldCount = thisClass->NumInstanceFields();
+            hprofAddU2ToRecord(rec, (uint16_t)iFieldCount);
+            for (int i = 0; i < iFieldCount; ++i) {
+                Field* f = thisClass->GetInstanceField(i);
+                hprof_basic_type t;
+
+                t = signatureToBasicTypeAndSize(f->GetTypeDescriptor(), NULL);
+                hprofAddIdToRecord(rec, hprofLookupStringId(f->GetName()));
+                hprofAddU1ToRecord(rec, t);
+            }
+        } else if (clazz->IsArrayClass()) {
+            Array *aobj = (Array *)obj;
+            uint32_t length = aobj->GetLength();
+
+            if (obj->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->GetRawData(), length);
+            } else {
+                hprof_basic_type t;
+                size_t size;
+
+                t = primitiveToBasicTypeAndSize(clazz->GetComponentType()->
+                                                GetPrimitiveType(), &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 uint8_t *)aobj->GetRawData(),
+                            length);
+                } else if (size == 2) {
+                    hprofAddU2ListToRecord(rec, (const uint16_t *)(void *)aobj->GetRawData(),
+                            length);
+                } else if (size == 4) {
+                    hprofAddU4ListToRecord(rec, (const uint32_t *)(void *)aobj->GetRawData(),
+                            length);
+                } else if (size == 8) {
+                    hprofAddU8ListToRecord(rec, (const uint64_t *)aobj->GetRawData(),
+                            length);
+                }
+#endif
+            }
+        } else {
+            const Class* 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 ifieldCount = sclass->NumInstanceFields();
+                for (int i = 0; i < ifieldCount; i++) {
+                    Field* f = sclass->GetInstanceField(i);
+                    size_t size;
+
+                    (void) signatureToBasicTypeAndSize(f->GetTypeDescriptor(), &size);
+                    if (size == 1) {
+                        hprofAddU1ToRecord(rec, f->GetByte(obj));
+                    } else if (size == 2) {
+                        hprofAddU2ToRecord(rec, f->GetChar(obj));
+                    } else if (size == 4) {
+                        hprofAddU4ToRecord(rec, f->GetInt(obj));
+                    } else if (size == 8) {
+                        hprofAddU8ToRecord(rec, f->GetLong(obj));
+                    } else {
+                        CHECK(false);
+                    }
+                }
+
+                sclass = sclass->GetSuperClass();
+            }
+
+            /* Patch the instance field length.
+             */
+            savedLen = rec->length;
+            rec->length = sizePatchOffset;
+            hprofAddU4ToRecord(rec, savedLen - (sizePatchOffset + 4));
+            rec->length = savedLen;
+        }
+    }
+
+    ctx->objectsInSegment++;
+
+    return 0;
+}
+
+}  // namespace hprof
+
+}  // namespace art
diff --git a/src/hprof/hprof_output.cc b/src/hprof/hprof_output.cc
new file mode 100644
index 0000000..5dfdb31
--- /dev/null
+++ b/src/hprof/hprof_output.cc
@@ -0,0 +1,328 @@
+/*
+ * 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 <sys/time.h>
+#include <cutils/open_memstream.h>
+#include <time.h>
+#include <errno.h>
+#include <stdio.h>
+#include "hprof.h"
+#include "stringprintf.h"
+#include "logging.h"
+
+namespace art {
+
+namespace hprof {
+
+#define HPROF_MAGIC_STRING  "JAVA PROFILE 1.0.3"
+
+#define U2_TO_BUF_BE(buf, offset, value) \
+    do { \
+        unsigned char *buf_ = (unsigned char *)(buf); \
+        int offset_ = (int)(offset); \
+        uint16_t value_ = (uint16_t)(value); \
+        buf_[offset_ + 0] = (unsigned char)(value_ >>  8); \
+        buf_[offset_ + 1] = (unsigned char)(value_      ); \
+    } while (0)
+
+#define U4_TO_BUF_BE(buf, offset, value) \
+    do { \
+        unsigned char *buf_ = (unsigned char *)(buf); \
+        int offset_ = (int)(offset); \
+        uint32_t value_ = (uint32_t)(value); \
+        buf_[offset_ + 0] = (unsigned char)(value_ >> 24); \
+        buf_[offset_ + 1] = (unsigned char)(value_ >> 16); \
+        buf_[offset_ + 2] = (unsigned char)(value_ >>  8); \
+        buf_[offset_ + 3] = (unsigned char)(value_      ); \
+    } while (0)
+
+#define U8_TO_BUF_BE(buf, offset, value) \
+    do { \
+        unsigned char *buf_ = (unsigned char *)(buf); \
+        int offset_ = (int)(offset); \
+        uint64_t value_ = (uint64_t)(value); \
+        buf_[offset_ + 0] = (unsigned char)(value_ >> 56); \
+        buf_[offset_ + 1] = (unsigned char)(value_ >> 48); \
+        buf_[offset_ + 2] = (unsigned char)(value_ >> 40); \
+        buf_[offset_ + 3] = (unsigned char)(value_ >> 32); \
+        buf_[offset_ + 4] = (unsigned char)(value_ >> 24); \
+        buf_[offset_ + 5] = (unsigned char)(value_ >> 16); \
+        buf_[offset_ + 6] = (unsigned char)(value_ >>  8); \
+        buf_[offset_ + 7] = (unsigned char)(value_      ); \
+    } while (0)
+
+/*
+ * Initialize an hprof context struct.
+ *
+ * This will take ownership of "fileName".
+ */
+void hprofContextInit(hprof_context_t *ctx, char *fileName, int fd,
+                      bool writeHeader, bool directToDdms)
+{
+    memset(ctx, 0, sizeof (*ctx));
+
+    /*
+     * Have to do this here, because it must happen after we
+     * memset the struct (want to treat fileDataPtr/fileDataSize
+     * as read-only while the file is open).
+     */
+    FILE *fp = open_memstream(&ctx->fileDataPtr, &ctx->fileDataSize);
+    if (fp == NULL) {
+        /* not expected */
+        LOG(ERROR) << StringPrintf("hprof: open_memstream failed: %s", strerror(errno));
+        CHECK(false);
+    }
+
+    ctx->directToDdms = directToDdms;
+    ctx->fileName = fileName;
+    ctx->memFp = fp;
+    ctx->fd = fd;
+
+    ctx->curRec.allocLen = 128;
+    ctx->curRec.body = (unsigned char *)malloc(ctx->curRec.allocLen);
+//xxx check for/return an error
+
+    if (writeHeader) {
+        char magic[] = HPROF_MAGIC_STRING;
+        unsigned char buf[4];
+        struct timeval now;
+        uint64_t nowMs;
+
+        /* Write the file header.
+         *
+         * [uint8_t]*: NUL-terminated magic string.
+         */
+        fwrite(magic, 1, sizeof(magic), fp);
+
+        /* uint32_t: size of identifiers.  We're using addresses
+         *     as IDs, so make sure a pointer fits.
+         */
+        U4_TO_BUF_BE(buf, 0, sizeof(void *));
+        fwrite(buf, 1, sizeof(uint32_t), fp);
+
+        /* The current time, in milliseconds since 0:00 GMT, 1/1/70.
+         */
+        if (gettimeofday(&now, NULL) < 0) {
+            nowMs = 0;
+        } else {
+            nowMs = (uint64_t)now.tv_sec * 1000 + now.tv_usec / 1000;
+        }
+
+        /* uint32_t: high word of the 64-bit time.
+         */
+        U4_TO_BUF_BE(buf, 0, (uint32_t)(nowMs >> 32));
+        fwrite(buf, 1, sizeof(uint32_t), fp);
+
+        /* uint32_t: low word of the 64-bit time.
+         */
+        U4_TO_BUF_BE(buf, 0, (uint32_t)(nowMs & 0xffffffffULL));
+        fwrite(buf, 1, sizeof(uint32_t), fp); //xxx fix the time
+    }
+}
+
+int hprofFlushRecord(hprof_record_t *rec, FILE *fp)
+{
+    if (rec->dirty) {
+        unsigned char headBuf[sizeof (uint8_t) + 2 * sizeof (uint32_t)];
+        int nb;
+
+        headBuf[0] = rec->tag;
+        U4_TO_BUF_BE(headBuf, 1, rec->time);
+        U4_TO_BUF_BE(headBuf, 5, rec->length);
+
+        nb = fwrite(headBuf, 1, sizeof(headBuf), fp);
+        if (nb != sizeof(headBuf)) {
+            return UNIQUE_ERROR();
+        }
+        nb = fwrite(rec->body, 1, rec->length, fp);
+        if (nb != (int)rec->length) {
+            return UNIQUE_ERROR();
+        }
+
+        rec->dirty = false;
+    }
+//xxx if we used less than half (or whatever) of allocLen, shrink the buffer.
+
+    return 0;
+}
+
+int hprofFlushCurrentRecord(hprof_context_t *ctx)
+{
+    return hprofFlushRecord(&ctx->curRec, ctx->memFp);
+}
+
+int hprofStartNewRecord(hprof_context_t *ctx, uint8_t tag, uint32_t time)
+{
+    hprof_record_t *rec = &ctx->curRec;
+    int err;
+
+    err = hprofFlushRecord(rec, ctx->memFp);
+    if (err != 0) {
+        return err;
+    } else if (rec->dirty) {
+        return UNIQUE_ERROR();
+    }
+
+    rec->dirty = true;
+    rec->tag = tag;
+    rec->time = time;
+    rec->length = 0;
+
+    return 0;
+}
+
+static inline int guaranteeRecordAppend(hprof_record_t *rec, size_t nmore)
+{
+    size_t minSize;
+
+    minSize = rec->length + nmore;
+    if (minSize > rec->allocLen) {
+        unsigned char *newBody;
+        size_t newAllocLen;
+
+        newAllocLen = rec->allocLen * 2;
+        if (newAllocLen < minSize) {
+            newAllocLen = rec->allocLen + nmore + nmore/2;
+        }
+        newBody = (unsigned char *)realloc(rec->body, newAllocLen);
+        if (newBody != NULL) {
+            rec->body = newBody;
+            rec->allocLen = newAllocLen;
+        } else {
+//TODO: set an error flag so future ops will fail
+            return UNIQUE_ERROR();
+        }
+    }
+
+    CHECK(rec->length + nmore <= rec->allocLen);
+    return 0;
+}
+
+int hprofAddU1ListToRecord(hprof_record_t *rec, const uint8_t *values,
+                           size_t numValues)
+{
+    int err;
+
+    err = guaranteeRecordAppend(rec, numValues);
+    if (err != 0) {
+        return err;
+    }
+
+    memcpy(rec->body + rec->length, values, numValues);
+    rec->length += numValues;
+
+    return 0;
+}
+
+int hprofAddU1ToRecord(hprof_record_t *rec, uint8_t value)
+{
+    int err;
+
+    err = guaranteeRecordAppend(rec, 1);
+    if (err != 0) {
+        return err;
+    }
+
+    rec->body[rec->length++] = value;
+
+    return 0;
+}
+
+int hprofAddUtf8StringToRecord(hprof_record_t *rec, const char *str)
+{
+    /* The terminating NUL character is NOT written.
+     */
+//xxx don't do a strlen;  add and grow as necessary, until NUL
+    return hprofAddU1ListToRecord(rec, (const uint8_t *)str, strlen(str));
+}
+
+int hprofAddU2ListToRecord(hprof_record_t *rec, const uint16_t *values,
+                           size_t numValues)
+{
+    int err = guaranteeRecordAppend(rec, numValues * 2);
+    if (err != 0) {
+        return err;
+    }
+
+//xxx this can be way smarter
+//xxx also, don't do this bytewise if aligned and on a matching-endian arch
+    unsigned char *insert = rec->body + rec->length;
+    for (size_t i = 0; i < numValues; i++) {
+        U2_TO_BUF_BE(insert, 0, *values++);
+        insert += sizeof(*values);
+    }
+    rec->length += numValues * 2;
+
+    return 0;
+}
+
+int hprofAddU2ToRecord(hprof_record_t *rec, uint16_t value)
+{
+    return hprofAddU2ListToRecord(rec, &value, 1);
+}
+
+int hprofAddU4ListToRecord(hprof_record_t *rec, const uint32_t *values,
+                           size_t numValues)
+{
+    int err = guaranteeRecordAppend(rec, numValues * 4);
+    if (err != 0) {
+        return err;
+    }
+
+//xxx this can be way smarter
+//xxx also, don't do this bytewise if aligned and on a matching-endian arch
+    unsigned char *insert = rec->body + rec->length;
+    for (size_t i = 0; i < numValues; i++) {
+        U4_TO_BUF_BE(insert, 0, *values++);
+        insert += sizeof(*values);
+    }
+    rec->length += numValues * 4;
+
+    return 0;
+}
+
+int hprofAddU4ToRecord(hprof_record_t *rec, uint32_t value)
+{
+    return hprofAddU4ListToRecord(rec, &value, 1);
+}
+
+int hprofAddU8ListToRecord(hprof_record_t *rec, const uint64_t *values,
+                           size_t numValues)
+{
+    int err = guaranteeRecordAppend(rec, numValues * 8);
+    if (err != 0) {
+        return err;
+    }
+
+//xxx this can be way smarter
+//xxx also, don't do this bytewise if aligned and on a matching-endian arch
+    unsigned char *insert = rec->body + rec->length;
+    for (size_t i = 0; i < numValues; i++) {
+        U8_TO_BUF_BE(insert, 0, *values++);
+        insert += sizeof(*values);
+    }
+    rec->length += numValues * 8;
+
+    return 0;
+}
+
+int hprofAddU8ToRecord(hprof_record_t *rec, uint64_t value)
+{
+    return hprofAddU8ListToRecord(rec, &value, 1);
+}
+
+}  // namespace hprof
+
+}  // namespace art
diff --git a/src/hprof/hprof_string.cc b/src/hprof/hprof_string.cc
new file mode 100644
index 0000000..7fb4027
--- /dev/null
+++ b/src/hprof/hprof_string.cc
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+/*
+ * Common string pool for the profiler
+ */
+#include "hprof.h"
+#include "object.h"
+#include "unordered_set.h"
+#include "logging.h"
+
+namespace art {
+
+namespace hprof {
+
+typedef std::tr1::unordered_set<String*, StringHashCode> StringSet;
+typedef std::tr1::unordered_set<String*, StringHashCode>::iterator StringSetIterator; // TODO: equals by VALUE not REFERENCE
+static Mutex strings_lock_("hprof strings");
+static StringSet strings_;
+
+int hprofStartup_String() {
+    return 0;
+}
+
+int hprofShutdown_String() {
+    return 0;
+}
+
+hprof_string_id hprofLookupStringId(String* string) {
+    MutexLock mu(strings_lock_);
+    std::pair<StringSetIterator, bool> result = strings_.insert(string);
+    String* present = *result.first;
+    return (hprof_string_id) present;
+}
+
+hprof_string_id hprofLookupStringId(const char* string) {
+    return hprofLookupStringId(String::AllocFromModifiedUtf8(string)); // TODO: leaks? causes GC?
+}
+
+hprof_string_id hprofLookupStringId(std::string string) {
+    return hprofLookupStringId(string.c_str()); // TODO: leaks? causes GC?
+}
+
+int hprofDumpStrings(hprof_context_t *ctx) {
+    MutexLock mu(strings_lock_);
+
+    hprof_record_t *rec = &ctx->curRec;
+
+    for (StringSetIterator it = strings_.begin(); it != strings_.end(); ++it) {
+        String* string = *it;
+        CHECK(string != NULL);
+
+        int err = hprofStartNewRecord(ctx, HPROF_TAG_STRING, HPROF_TIME);
+        if (err != 0) {
+            return err;
+        }
+
+        /* STRING format:
+         *
+         * ID:     ID for this string
+         * [uint8_t]*:  UTF8 characters for string (NOT NULL terminated)
+         *         (the record format encodes the length)
+         *
+         * We use the address of the string data as its ID.
+         */
+        err = hprofAddU4ToRecord(rec, (uint32_t) string);
+        if (err != 0) {
+            return err;
+        }
+        err = hprofAddUtf8StringToRecord(rec, string->ToModifiedUtf8().c_str()); // TODO: leak?
+        if (err != 0) {
+            return err;
+        }
+    }
+
+    return 0;
+}
+
+}  // namespace hprof
+
+}  // namespace art
diff --git a/src/object.h b/src/object.h
index 1051985..46c30cd 100644
--- a/src/object.h
+++ b/src/object.h
@@ -396,7 +396,7 @@
 
   void SetDeclaringClass(Class *new_declaring_class);
 
-  const String* GetName() const;
+  String* GetName() const;
 
   void SetName(String* new_name);
 
@@ -2501,7 +2501,13 @@
   DISALLOW_IMPLICIT_CONSTRUCTORS(String);
 };
 
-inline const String* Field::GetName() const {
+struct StringHashCode {
+  int32_t operator()(art::String* string) const {
+    return string->GetHashCode();
+  }
+};
+
+inline String* Field::GetName() const {
   DCHECK(GetDeclaringClass()->IsLoaded() || GetDeclaringClass()->IsErroneous());
   String* result = GetFieldObject<String*>(OFFSET_OF_OBJECT_MEMBER(Field, name_), false);
   DCHECK(result != NULL);