Allow heap dump requests with a FileDescriptor arg.
Part of a larger change to allow "am" to initiate hprof heap dumps.
The original implementation wrote data to a temp file, because we
compute the second part first while doing GC work. Now all paths
work like DDMS, writing all data to a memstream and then copying it
out to a file at the very end.
Also, fix a potential fd leak on failure in the profiling code.
Bug 2759474.
Change-Id: I0f838cbd9948a23088f08463c3008be7198d76e7
diff --git a/vm/Profile.c b/vm/Profile.c
index f2c0c0f..57c96df 100644
--- a/vm/Profile.c
+++ b/vm/Profile.c
@@ -329,7 +329,8 @@
* trace all threads).
*
* This opens the output file (if an already open fd has not been supplied,
- * and we're not going direct to DDMS) and allocates the data buffer.
+ * and we're not going direct to DDMS) and allocates the data buffer. This
+ * takes ownership of the file descriptor, closing it on completion.
*
* On failure, we throw an exception and return.
*/
@@ -377,6 +378,7 @@
goto fail;
}
}
+ traceFd = -1;
memset(state->buf, (char)FILL_PATTERN, bufferSize);
state->directToDdms = directToDdms;
@@ -425,6 +427,8 @@
free(state->buf);
state->buf = NULL;
}
+ if (traceFd >= 0)
+ close(traceFd);
dvmUnlockMutex(&state->startStopLock);
}
diff --git a/vm/SignalCatcher.c b/vm/SignalCatcher.c
index 2b994da..40c36c6 100644
--- a/vm/SignalCatcher.c
+++ b/vm/SignalCatcher.c
@@ -221,7 +221,7 @@
{
#if WITH_HPROF
LOGI("SIGUSR1 forcing GC and HPROF dump\n");
- hprofDumpHeap(NULL, false);
+ hprofDumpHeap(NULL, -1, false);
#else
LOGI("SIGUSR1 forcing GC (no HPROF)\n");
dvmCollectGarbage(false);
diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c
index 80b5030..474818c 100644
--- a/vm/alloc/Heap.c
+++ b/vm/alloc/Heap.c
@@ -720,7 +720,7 @@
gcHeap->hprofFileName = nameBuf;
}
gcHeap->hprofContext = hprofStartup(gcHeap->hprofFileName,
- gcHeap->hprofDirectToDdms);
+ gcHeap->hprofFd, gcHeap->hprofDirectToDdms);
if (gcHeap->hprofContext != NULL) {
hprofStartHeapDump(gcHeap->hprofContext);
}
@@ -929,11 +929,18 @@
/*
* Perform garbage collection, 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 "fileName" is NULL, a suitable name will be generated automatically.
+ * (TODO: remove this when the SIGUSR1 feature goes away)
+ *
+ * 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, bool directToDdms)
+int hprofDumpHeap(const char* fileName, int fd, bool directToDdms)
{
int result;
@@ -941,6 +948,7 @@
gDvm.gcHeap->hprofDumpOnGc = true;
gDvm.gcHeap->hprofFileName = fileName;
+ gDvm.gcHeap->hprofFd = fd;
gDvm.gcHeap->hprofDirectToDdms = directToDdms;
dvmCollectGarbageInternal(false, GC_HPROF_DUMP_HEAP);
result = gDvm.gcHeap->hprofResult;
diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h
index 728af95..112fc91 100644
--- a/vm/alloc/HeapInternal.h
+++ b/vm/alloc/HeapInternal.h
@@ -117,6 +117,7 @@
#if WITH_HPROF
bool hprofDumpOnGc;
const char* hprofFileName;
+ int hprofFd;
hprof_context_t *hprofContext;
int hprofResult;
bool hprofDirectToDdms;
diff --git a/vm/hprof/Hprof.c b/vm/hprof/Hprof.c
index 0b95aad..bcad8b1 100644
--- a/vm/hprof/Hprof.c
+++ b/vm/hprof/Hprof.c
@@ -25,6 +25,7 @@
#include <string.h>
#include <unistd.h>
+#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <time.h>
@@ -33,35 +34,8 @@
#define kHeadSuffix "-hptemp"
hprof_context_t *
-hprofStartup(const char *outputFileName, bool directToDdms)
+hprofStartup(const char *outputFileName, int fd, bool directToDdms)
{
- FILE* fp = NULL;
-
- if (!directToDdms) {
- int len = strlen(outputFileName);
- char fileName[len + sizeof(kHeadSuffix)];
-
- /* Construct the temp file name. This wasn't handed to us by the
- * application, so we need to be careful about stomping on it.
- */
- sprintf(fileName, "%s" kHeadSuffix, outputFileName);
- if (access(fileName, F_OK) == 0) {
- LOGE("hprof: temp file %s exists, bailing\n", fileName);
- return NULL;
- }
-
- fp = fopen(fileName, "w+");
- if (fp == NULL) {
- LOGE("hprof: can't open %s: %s.\n", fileName, strerror(errno));
- return NULL;
- }
- if (unlink(fileName) != 0) {
- LOGW("hprof: WARNING: unable to remove temp file %s\n", fileName);
- /* keep going */
- }
- LOGI("hprof: dumping VM heap to \"%s\".\n", fileName);
- }
-
hprofStartup_String();
hprofStartup_Class();
#if WITH_HPROF_STACK
@@ -72,70 +46,26 @@
hprof_context_t *ctx = malloc(sizeof(*ctx));
if (ctx == NULL) {
LOGE("hprof: can't allocate context.\n");
- if (fp != NULL)
- fclose(fp);
return NULL;
}
- /* pass in "fp" for the temp file, and the name of the output file */
- hprofContextInit(ctx, strdup(outputFileName), fp, false, directToDdms);
+ /* pass in name or descriptor of the output file */
+ hprofContextInit(ctx, strdup(outputFileName), fd, false, directToDdms);
- assert(ctx->fp != NULL);
+ assert(ctx->memFp != NULL);
return ctx;
}
/*
- * Copy the entire contents of "srcFp" to "dstFp".
- *
- * Returns "true" on success.
- */
-static bool
-copyFileToFile(FILE *dstFp, FILE *srcFp)
-{
- char buf[65536];
- size_t dataRead, dataWritten;
-
- while (true) {
- dataRead = fread(buf, 1, sizeof(buf), srcFp);
- if (dataRead > 0) {
- dataWritten = fwrite(buf, 1, dataRead, dstFp);
- if (dataWritten != dataRead) {
- LOGE("hprof: failed writing data (%d of %d): %s\n",
- dataWritten, dataRead, strerror(errno));
- return false;
- }
- } else {
- if (feof(srcFp))
- return true;
- LOGE("hprof: failed reading data (res=%d): %s\n",
- dataRead, strerror(errno));
- return false;
- }
- }
-}
-
-/*
* Finish up the hprof dump. Returns true on success.
*/
bool
hprofShutdown(hprof_context_t *tailCtx)
{
- FILE *fp = NULL;
-
- /* flush output to the temp file, then prepare the output file */
+ /* flush the "tail" portion of the output */
hprofFlushCurrentRecord(tailCtx);
- LOGI("hprof: dumping heap strings to \"%s\".\n", tailCtx->fileName);
- if (!tailCtx->directToDdms) {
- fp = fopen(tailCtx->fileName, "w");
- if (fp == NULL) {
- LOGE("can't open %s: %s\n", tailCtx->fileName, strerror(errno));
- hprofFreeContext(tailCtx);
- return false;
- }
- }
-
/*
* Create a new context struct for the start of the file. We
* heap-allocate it so we can share the "free" function.
@@ -143,14 +73,13 @@
hprof_context_t *headCtx = malloc(sizeof(*headCtx));
if (headCtx == NULL) {
LOGE("hprof: can't allocate context.\n");
- if (fp != NULL)
- fclose(fp);
hprofFreeContext(tailCtx);
- return NULL;
+ return false;
}
- hprofContextInit(headCtx, strdup(tailCtx->fileName), fp, true,
+ hprofContextInit(headCtx, strdup(tailCtx->fileName), tailCtx->fd, true,
tailCtx->directToDdms);
+ LOGI("hprof: dumping heap strings to \"%s\".\n", tailCtx->fileName);
hprofDumpStrings(headCtx);
hprofDumpClasses(headCtx);
@@ -176,11 +105,11 @@
hprofShutdown_StackFrame();
#endif
- if (tailCtx->directToDdms) {
- /* flush to ensure memstream pointer and size are updated */
- fflush(headCtx->fp);
- fflush(tailCtx->fp);
+ /* 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;
@@ -190,22 +119,50 @@
dvmDbgDdmSendChunkV(CHUNK_TYPE("HPDS"), iov, 2);
} else {
/*
- * Append the contents of the temp file to the output file. The temp
- * file was removed immediately after being opened, so it will vanish
- * when we close it.
+ * Open the output file, and copy the head and tail to it.
*/
- rewind(tailCtx->fp);
- if (!copyFileToFile(headCtx->fp, tailCtx->fp)) {
- LOGW("hprof: file copy failed, hprof data may be incomplete\n");
- /* finish up anyway */
+ assert(headCtx->fd == tailCtx->fd);
+
+ int outFd;
+ if (headCtx->fd >= 0) {
+ outFd = dup(headCtx->fd);
+ if (outFd < 0) {
+ LOGE("dup(%d) failed: %s\n", headCtx->fd, strerror(errno));
+ /* continue to fail-handler below */
+ }
+ } else {
+ outFd = open(tailCtx->fileName, O_WRONLY|O_CREAT, 0644);
+ if (outFd < 0) {
+ LOGE("can't open %s: %s\n", 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" */
+ LOGI("hprof: heap dump completed (%dKB)\n",
+ (headCtx->fileDataSize + tailCtx->fileDataSize + 1023) / 1024);
+
hprofFreeContext(headCtx);
hprofFreeContext(tailCtx);
- /* throw out a log message for the benefit of "runhat" */
- LOGI("hprof: heap dump completed, temp file removed\n");
return true;
}
@@ -217,8 +174,10 @@
{
assert(ctx != NULL);
- if (ctx->fp != NULL)
- fclose(ctx->fp);
+ /* 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);
diff --git a/vm/hprof/Hprof.h b/vm/hprof/Hprof.h
index fd89d06..18f4102 100644
--- a/vm/hprof/Hprof.h
+++ b/vm/hprof/Hprof.h
@@ -133,15 +133,16 @@
size_t objectsInSegment;
/*
- * If "directToDdms" is not set, "fileName" is valid, and "fileDataPtr"
- * and "fileDataSize" are not used. If "directToDdms" is not set,
- * it's the other way around.
+ * 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 *fp;
+ FILE *memFp;
+ int fd;
} hprof_context_t;
@@ -187,7 +188,7 @@
* HprofOutput.c functions
*/
-void hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp,
+void hprofContextInit(hprof_context_t *ctx, char *fileName, int fd,
bool writeHeader, bool directToDdms);
int hprofFlushRecord(hprof_record_t *rec, FILE *fp);
@@ -244,7 +245,8 @@
* Hprof.c functions
*/
-hprof_context_t* hprofStartup(const char *outputFileName, bool directToDdms);
+hprof_context_t* hprofStartup(const char *outputFileName, int fd,
+ bool directToDdms);
bool hprofShutdown(hprof_context_t *ctx);
void hprofFreeContext(hprof_context_t *ctx);
@@ -255,7 +257,7 @@
* the heap implementation; these functions require heap knowledge,
* so they are implemented in Heap.c.
*/
-int hprofDumpHeap(const char* fileName, bool directToDdms);
+int hprofDumpHeap(const char* fileName, int fd, bool directToDdms);
void dvmHeapSetHprofGcScanState(hprof_heap_tag_t state, u4 threadSerialNumber);
#endif // _DALVIK_HPROF_HPROF
diff --git a/vm/hprof/HprofOutput.c b/vm/hprof/HprofOutput.c
index 0677c85..25512b2 100644
--- a/vm/hprof/HprofOutput.c
+++ b/vm/hprof/HprofOutput.c
@@ -59,32 +59,30 @@
/*
* Initialize an hprof context struct.
*
- * This will take ownership of "fileName" and "fp".
+ * This will take ownership of "fileName".
*/
void
-hprofContextInit(hprof_context_t *ctx, char *fileName, FILE *fp,
+hprofContextInit(hprof_context_t *ctx, char *fileName, int fd,
bool writeHeader, bool directToDdms)
{
memset(ctx, 0, sizeof (*ctx));
- if (directToDdms) {
- /*
- * 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).
- */
- assert(fp == NULL);
- fp = open_memstream(&ctx->fileDataPtr, &ctx->fileDataSize);
- if (fp == NULL) {
- /* not expected */
- LOGE("hprof: open_memstream failed: %s\n", strerror(errno));
- dvmAbort();
- }
+ /*
+ * 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 */
+ LOGE("hprof: open_memstream failed: %s\n", strerror(errno));
+ dvmAbort();
}
ctx->directToDdms = directToDdms;
ctx->fileName = fileName;
- ctx->fp = fp;
+ ctx->memFp = fp;
+ ctx->fd = fd;
ctx->curRec.allocLen = 128;
ctx->curRec.body = malloc(ctx->curRec.allocLen);
@@ -158,7 +156,7 @@
int
hprofFlushCurrentRecord(hprof_context_t *ctx)
{
- return hprofFlushRecord(&ctx->curRec, ctx->fp);
+ return hprofFlushRecord(&ctx->curRec, ctx->memFp);
}
int
@@ -167,7 +165,7 @@
hprof_record_t *rec = &ctx->curRec;
int err;
- err = hprofFlushRecord(rec, ctx->fp);
+ err = hprofFlushRecord(rec, ctx->memFp);
if (err != 0) {
return err;
} else if (rec->dirty) {
diff --git a/vm/native/dalvik_system_VMDebug.c b/vm/native/dalvik_system_VMDebug.c
index d27926c..2f79033 100644
--- a/vm/native/dalvik_system_VMDebug.c
+++ b/vm/native/dalvik_system_VMDebug.c
@@ -20,10 +20,40 @@
#include "Dalvik.h"
#include "native/InternalNativePriv.h"
+#include <string.h>
+#include <unistd.h>
#include <errno.h>
/*
+ * Extracts the fd from a FileDescriptor object.
+ *
+ * If an error is encountered, or the extracted descriptor is numerically
+ * invalid, this returns -1 with an exception raised.
+ */
+static int getFileDescriptor(Object* obj)
+{
+ assert(obj != NULL);
+ assert(strcmp(obj->clazz->descriptor, "Ljava/io/FileDescriptor;") == 0);
+
+ InstField* field = dvmFindInstanceField(obj->clazz, "descriptor", "I");
+ if (field == NULL) {
+ dvmThrowException("Ljava/lang/NoSuchFieldException;",
+ "No FileDescriptor.descriptor field");
+ return -1;
+ }
+
+ int fd = dvmGetFieldInt(obj, field->byteOffset);
+ if (fd < 0) {
+ dvmThrowExceptionFmt("Ljava/lang/RuntimeException;",
+ "Invalid file descriptor");
+ return -1;
+ }
+
+ return fd;
+}
+
+/*
* Convert an array of char* into a String[].
*
* Returns NULL on failure, with an exception raised.
@@ -326,7 +356,7 @@
{
#ifdef WITH_PROFILER
StringObject* traceFileStr = (StringObject*) args[0];
- DataObject* traceFd = (DataObject*) args[1];
+ Object* traceFd = (Object*) args[1];
int bufferSize = args[2];
int flags = args[3];
@@ -346,17 +376,14 @@
int fd = -1;
if (traceFd != NULL) {
- InstField* field =
- dvmFindInstanceField(traceFd->obj.clazz, "descriptor", "I");
- if (field == NULL) {
- dvmThrowException("Ljava/lang/NoSuchFieldException;",
- "No FileDescriptor.descriptor field");
+ int origFd = getFileDescriptor(traceFd);
+ if (origFd < 0)
RETURN_VOID();
- }
- fd = dup(dvmGetFieldInt(&traceFd->obj, field->byteOffset));
+
+ fd = dup(origFd);
if (fd < 0) {
dvmThrowExceptionFmt("Ljava/lang/RuntimeException;",
- "dup() failed: %s", strerror(errno));
+ "dup(%d) failed: %s", origFd, strerror(errno));
RETURN_VOID();
}
}
@@ -660,7 +687,7 @@
}
/*
- * static void dumpHprofData(String fileName)
+ * static void dumpHprofData(String fileName, FileDescriptor fd)
*
* Cause "hprof" data to be dumped. We can throw an IOException if an
* error occurs during file handling.
@@ -670,22 +697,37 @@
{
#ifdef WITH_HPROF
StringObject* fileNameStr = (StringObject*) args[0];
+ Object* fileDescriptor = (Object*) args[1];
char* fileName;
int result;
- if (fileNameStr == NULL) {
+ /*
+ * Only one of these may be NULL.
+ */
+ if (fileNameStr == NULL && fileDescriptor == NULL) {
dvmThrowException("Ljava/lang/NullPointerException;", NULL);
RETURN_VOID();
}
- fileName = dvmCreateCstrFromString(fileNameStr);
- if (fileName == NULL) {
- /* unexpected -- malloc failure? */
- dvmThrowException("Ljava/lang/RuntimeException;", "malloc failure?");
- RETURN_VOID();
+ if (fileNameStr != NULL) {
+ fileName = dvmCreateCstrFromString(fileNameStr);
+ if (fileName == NULL) {
+ /* unexpected -- malloc failure? */
+ dvmThrowException("Ljava/lang/RuntimeException;", "malloc failure?");
+ RETURN_VOID();
+ }
+ } else {
+ fileName = strdup("[fd]");
}
- result = hprofDumpHeap(fileName, false);
+ int fd = -1;
+ if (fileDescriptor != NULL) {
+ fd = getFileDescriptor(fileDescriptor);
+ if (fd < 0)
+ RETURN_VOID();
+ }
+
+ result = hprofDumpHeap(fileName, fd, false);
free(fileName);
if (result != 0) {
@@ -712,7 +754,7 @@
#ifdef WITH_HPROF
int result;
- result = hprofDumpHeap("[DDMS]", true);
+ result = hprofDumpHeap("[DDMS]", -1, true);
if (result != 0) {
/* ideally we'd throw something more specific based on actual failure */
@@ -938,7 +980,7 @@
Dalvik_dalvik_system_VMDebug_getLoadedClassCount },
{ "threadCpuTimeNanos", "()J",
Dalvik_dalvik_system_VMDebug_threadCpuTimeNanos },
- { "dumpHprofData", "(Ljava/lang/String;)V",
+ { "dumpHprofData", "(Ljava/lang/String;Ljava/io/FileDescriptor;)V",
Dalvik_dalvik_system_VMDebug_dumpHprofData },
{ "dumpHprofDataDdms", "()V",
Dalvik_dalvik_system_VMDebug_dumpHprofDataDdms },