summaryrefslogtreecommitdiff
path: root/vm/Misc.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vm/Misc.cpp')
-rw-r--r--vm/Misc.cpp824
1 files changed, 824 insertions, 0 deletions
diff --git a/vm/Misc.cpp b/vm/Misc.cpp
new file mode 100644
index 0000000..1365516
--- /dev/null
+++ b/vm/Misc.cpp
@@ -0,0 +1,824 @@
+/*
+ * 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.
+ */
+
+/*
+ * Miscellaneous utility functions.
+ */
+#include "Dalvik.h"
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <time.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+
+/*
+ * Print a hex dump in this format:
+ *
+01234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef\n
+ *
+ * If "mode" is kHexDumpLocal, we start at offset zero, and show a full
+ * 16 bytes on the first line. If it's kHexDumpMem, we make this look
+ * like a memory dump, using the actual address, outputting a partial line
+ * if "vaddr" isn't aligned on a 16-byte boundary.
+ *
+ * "priority" and "tag" determine the values passed to the log calls.
+ *
+ * Does not use printf() or other string-formatting calls.
+ */
+void dvmPrintHexDumpEx(int priority, const char* tag, const void* vaddr,
+ size_t length, HexDumpMode mode)
+{
+ static const char gHexDigit[] = "0123456789abcdef";
+ const unsigned char* addr = (const unsigned char*)vaddr;
+ char out[77]; /* exact fit */
+ unsigned int offset; /* offset to show while printing */
+ char* hex;
+ char* asc;
+ int gap;
+ //int trickle = 0;
+
+ if (mode == kHexDumpLocal)
+ offset = 0;
+ else
+ offset = (int) addr;
+
+ memset(out, ' ', sizeof(out)-1);
+ out[8] = ':';
+ out[sizeof(out)-2] = '\n';
+ out[sizeof(out)-1] = '\0';
+
+ gap = (int) offset & 0x0f;
+ while (length) {
+ unsigned int lineOffset = offset & ~0x0f;
+ int i, count;
+
+ hex = out;
+ asc = out + 59;
+
+ for (i = 0; i < 8; i++) {
+ *hex++ = gHexDigit[lineOffset >> 28];
+ lineOffset <<= 4;
+ }
+ hex++;
+ hex++;
+
+ count = ((int)length > 16-gap) ? 16-gap : (int)length; /* cap length */
+ assert(count != 0);
+ assert(count+gap <= 16);
+
+ if (gap) {
+ /* only on first line */
+ hex += gap * 3;
+ asc += gap;
+ }
+
+ for (i = gap ; i < count+gap; i++) {
+ *hex++ = gHexDigit[*addr >> 4];
+ *hex++ = gHexDigit[*addr & 0x0f];
+ hex++;
+ if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/)
+ *asc++ = *addr;
+ else
+ *asc++ = '.';
+ addr++;
+ }
+ for ( ; i < 16; i++) {
+ /* erase extra stuff; only happens on last line */
+ *hex++ = ' ';
+ *hex++ = ' ';
+ hex++;
+ *asc++ = ' ';
+ }
+
+ LOG_PRI(priority, tag, "%s", out);
+#if 0 //def HAVE_ANDROID_OS
+ /*
+ * We can overrun logcat easily by writing at full speed. On the
+ * other hand, we can make Eclipse time out if we're showing
+ * packet dumps while debugging JDWP.
+ */
+ {
+ if (trickle++ == 8) {
+ trickle = 0;
+ usleep(20000);
+ }
+ }
+#endif
+
+ gap = 0;
+ length -= count;
+ offset += count;
+ }
+}
+
+
+/*
+ * Fill out a DebugOutputTarget, suitable for printing to the log.
+ */
+void dvmCreateLogOutputTarget(DebugOutputTarget* target, int priority,
+ const char* tag)
+{
+ assert(target != NULL);
+ assert(tag != NULL);
+
+ target->which = kDebugTargetLog;
+ target->data.log.priority = priority;
+ target->data.log.tag = tag;
+}
+
+/*
+ * Fill out a DebugOutputTarget suitable for printing to a file pointer.
+ */
+void dvmCreateFileOutputTarget(DebugOutputTarget* target, FILE* fp)
+{
+ assert(target != NULL);
+ assert(fp != NULL);
+
+ target->which = kDebugTargetFile;
+ target->data.file.fp = fp;
+}
+
+/*
+ * Free "target" and any associated data.
+ */
+void dvmFreeOutputTarget(DebugOutputTarget* target)
+{
+ free(target);
+}
+
+/*
+ * Print a debug message, to either a file or the log.
+ */
+void dvmPrintDebugMessage(const DebugOutputTarget* target, const char* format,
+ ...)
+{
+ va_list args;
+
+ va_start(args, format);
+
+ switch (target->which) {
+ case kDebugTargetLog:
+ LOG_PRI_VA(target->data.log.priority, target->data.log.tag,
+ format, args);
+ break;
+ case kDebugTargetFile:
+ vfprintf(target->data.file.fp, format, args);
+ break;
+ default:
+ LOGE("unexpected 'which' %d", target->which);
+ break;
+ }
+
+ va_end(args);
+}
+
+
+/*
+ * Return a newly-allocated string in which all occurrences of '.' have
+ * been changed to '/'. If we find a '/' in the original string, NULL
+ * is returned to avoid ambiguity.
+ */
+char* dvmDotToSlash(const char* str)
+{
+ char* newStr = strdup(str);
+ char* cp = newStr;
+
+ if (newStr == NULL)
+ return NULL;
+
+ while (*cp != '\0') {
+ if (*cp == '/') {
+ assert(false);
+ return NULL;
+ }
+ if (*cp == '.')
+ *cp = '/';
+ cp++;
+ }
+
+ return newStr;
+}
+
+std::string dvmHumanReadableDescriptor(const char* descriptor) {
+ // Count the number of '['s to get the dimensionality.
+ const char* c = descriptor;
+ size_t dim = 0;
+ while (*c == '[') {
+ dim++;
+ c++;
+ }
+
+ // Reference or primitive?
+ if (*c == 'L') {
+ // "[[La/b/C;" -> "a.b.C[][]".
+ c++; // Skip the 'L'.
+ } else {
+ // "[[B" -> "byte[][]".
+ // To make life easier, we make primitives look like unqualified
+ // reference types.
+ switch (*c) {
+ case 'B': c = "byte;"; break;
+ case 'C': c = "char;"; break;
+ case 'D': c = "double;"; break;
+ case 'F': c = "float;"; break;
+ case 'I': c = "int;"; break;
+ case 'J': c = "long;"; break;
+ case 'S': c = "short;"; break;
+ case 'Z': c = "boolean;"; break;
+ default: return descriptor;
+ }
+ }
+
+ // At this point, 'c' is a string of the form "fully/qualified/Type;"
+ // or "primitive;". Rewrite the type with '.' instead of '/':
+ std::string result;
+ const char* p = c;
+ while (*p != ';') {
+ char ch = *p++;
+ if (ch == '/') {
+ ch = '.';
+ }
+ result.push_back(ch);
+ }
+ // ...and replace the semicolon with 'dim' "[]" pairs:
+ while (dim--) {
+ result += "[]";
+ }
+ return result;
+}
+
+std::string dvmHumanReadableType(const Object* obj)
+{
+ if (obj == NULL) {
+ return "null";
+ }
+ if (obj->clazz == NULL) {
+ /* should only be possible right after a plain dvmMalloc() */
+ return "(raw)";
+ }
+ std::string result(dvmHumanReadableDescriptor(obj->clazz->descriptor));
+ if (dvmIsClassObject(obj)) {
+ const ClassObject* clazz = reinterpret_cast<const ClassObject*>(obj);
+ result += "<" + dvmHumanReadableDescriptor(clazz->descriptor) + ">";
+ }
+ return result;
+}
+
+std::string dvmHumanReadableField(const Field* field)
+{
+ if (field == NULL) {
+ return "(null)";
+ }
+ std::string result(dvmHumanReadableDescriptor(field->clazz->descriptor));
+ result += '.';
+ result += field->name;
+ return result;
+}
+
+std::string dvmHumanReadableMethod(const Method* method, bool withSignature)
+{
+ if (method == NULL) {
+ return "(null)";
+ }
+ std::string result(dvmHumanReadableDescriptor(method->clazz->descriptor));
+ result += '.';
+ result += method->name;
+ if (withSignature) {
+ // TODO: the types in this aren't human readable!
+ char* signature = dexProtoCopyMethodDescriptor(&method->prototype);
+ result += signature;
+ free(signature);
+ }
+ return result;
+}
+
+/*
+ * Return a newly-allocated string for the "dot version" of the class
+ * name for the given type descriptor. That is, The initial "L" and
+ * final ";" (if any) have been removed and all occurrences of '/'
+ * have been changed to '.'.
+ *
+ * "Dot version" names are used in the class loading machinery.
+ * See also dvmHumanReadableDescriptor.
+ */
+char* dvmDescriptorToDot(const char* str)
+{
+ size_t at = strlen(str);
+ char* newStr;
+
+ if ((at >= 2) && (str[0] == 'L') && (str[at - 1] == ';')) {
+ at -= 2; /* Two fewer chars to copy. */
+ str++; /* Skip the 'L'. */
+ }
+
+ newStr = (char*)malloc(at + 1); /* Add one for the '\0'. */
+ if (newStr == NULL)
+ return NULL;
+
+ newStr[at] = '\0';
+
+ while (at > 0) {
+ at--;
+ newStr[at] = (str[at] == '/') ? '.' : str[at];
+ }
+
+ return newStr;
+}
+
+/*
+ * Return a newly-allocated string for the type descriptor
+ * corresponding to the "dot version" of the given class name. That
+ * is, non-array names are surrounded by "L" and ";", and all
+ * occurrences of '.' have been changed to '/'.
+ *
+ * "Dot version" names are used in the class loading machinery.
+ */
+char* dvmDotToDescriptor(const char* str)
+{
+ size_t length = strlen(str);
+ int wrapElSemi = 0;
+ char* newStr;
+ char* at;
+
+ if (str[0] != '[') {
+ length += 2; /* for "L" and ";" */
+ wrapElSemi = 1;
+ }
+
+ newStr = at = (char*)malloc(length + 1); /* + 1 for the '\0' */
+
+ if (newStr == NULL) {
+ return NULL;
+ }
+
+ if (wrapElSemi) {
+ *(at++) = 'L';
+ }
+
+ while (*str) {
+ char c = *(str++);
+ if (c == '.') {
+ c = '/';
+ }
+ *(at++) = c;
+ }
+
+ if (wrapElSemi) {
+ *(at++) = ';';
+ }
+
+ *at = '\0';
+ return newStr;
+}
+
+/*
+ * Return a newly-allocated string for the internal-form class name for
+ * the given type descriptor. That is, the initial "L" and final ";" (if
+ * any) have been removed.
+ */
+char* dvmDescriptorToName(const char* str)
+{
+ if (str[0] == 'L') {
+ size_t length = strlen(str) - 1;
+ char* newStr = (char*)malloc(length);
+
+ if (newStr == NULL) {
+ return NULL;
+ }
+
+ strlcpy(newStr, str + 1, length);
+ return newStr;
+ }
+
+ return strdup(str);
+}
+
+/*
+ * Return a newly-allocated string for the type descriptor for the given
+ * internal-form class name. That is, a non-array class name will get
+ * surrounded by "L" and ";", while array names are left as-is.
+ */
+char* dvmNameToDescriptor(const char* str)
+{
+ if (str[0] != '[') {
+ size_t length = strlen(str);
+ char* descriptor = (char*)malloc(length + 3);
+
+ if (descriptor == NULL) {
+ return NULL;
+ }
+
+ descriptor[0] = 'L';
+ strcpy(descriptor + 1, str);
+ descriptor[length + 1] = ';';
+ descriptor[length + 2] = '\0';
+
+ return descriptor;
+ }
+
+ return strdup(str);
+}
+
+/*
+ * Get a notion of the current time, in nanoseconds. This is meant for
+ * computing durations (e.g. "operation X took 52nsec"), so the result
+ * should not be used to get the current date/time.
+ */
+u8 dvmGetRelativeTimeNsec()
+{
+#ifdef HAVE_POSIX_CLOCKS
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return (u8)now.tv_sec*1000000000LL + now.tv_nsec;
+#else
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ return (u8)now.tv_sec*1000000000LL + now.tv_usec * 1000LL;
+#endif
+}
+
+/*
+ * Get the per-thread CPU time, in nanoseconds.
+ *
+ * Only useful for time deltas.
+ */
+u8 dvmGetThreadCpuTimeNsec()
+{
+#ifdef HAVE_POSIX_CLOCKS
+ struct timespec now;
+ clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
+ return (u8)now.tv_sec*1000000000LL + now.tv_nsec;
+#else
+ return (u8) -1;
+#endif
+}
+
+/*
+ * Get the per-thread CPU time, in nanoseconds, for the specified thread.
+ */
+u8 dvmGetOtherThreadCpuTimeNsec(pthread_t thread)
+{
+#if 0 /*def HAVE_POSIX_CLOCKS*/
+ int clockId;
+
+ if (pthread_getcpuclockid(thread, &clockId) != 0)
+ return (u8) -1;
+
+ struct timespec now;
+ clock_gettime(clockId, &now);
+ return (u8)now.tv_sec*1000000000LL + now.tv_nsec;
+#else
+ return (u8) -1;
+#endif
+}
+
+
+/*
+ * Call this repeatedly, with successively higher values for "iteration",
+ * to sleep for a period of time not to exceed "maxTotalSleep".
+ *
+ * For example, when called with iteration==0 we will sleep for a very
+ * brief time. On the next call we will sleep for a longer time. When
+ * the sum total of all sleeps reaches "maxTotalSleep", this returns false.
+ *
+ * The initial start time value for "relStartTime" MUST come from the
+ * dvmGetRelativeTimeUsec call. On the device this must come from the
+ * monotonic clock source, not the wall clock.
+ *
+ * This should be used wherever you might be tempted to call sched_yield()
+ * in a loop. The problem with sched_yield is that, for a high-priority
+ * thread, the kernel might not actually transfer control elsewhere.
+ *
+ * Returns "false" if we were unable to sleep because our time was up.
+ */
+bool dvmIterativeSleep(int iteration, int maxTotalSleep, u8 relStartTime)
+{
+ const int minSleep = 10000;
+ u8 curTime;
+ int curDelay;
+
+ /*
+ * Get current time, and see if we've already exceeded the limit.
+ */
+ curTime = dvmGetRelativeTimeUsec();
+ if (curTime >= relStartTime + maxTotalSleep) {
+ LOGVV("exsl: sleep exceeded (start=%llu max=%d now=%llu)",
+ relStartTime, maxTotalSleep, curTime);
+ return false;
+ }
+
+ /*
+ * Compute current delay. We're bounded by "maxTotalSleep", so no
+ * real risk of overflow assuming "usleep" isn't returning early.
+ * (Besides, 2^30 usec is about 18 minutes by itself.)
+ *
+ * For iteration==0 we just call sched_yield(), so the first sleep
+ * at iteration==1 is actually (minSleep * 2).
+ */
+ curDelay = minSleep;
+ while (iteration-- > 0)
+ curDelay *= 2;
+ assert(curDelay > 0);
+
+ if (curTime + curDelay >= relStartTime + maxTotalSleep) {
+ LOGVV("exsl: reduced delay from %d to %d",
+ curDelay, (int) ((relStartTime + maxTotalSleep) - curTime));
+ curDelay = (int) ((relStartTime + maxTotalSleep) - curTime);
+ }
+
+ if (iteration == 0) {
+ LOGVV("exsl: yield");
+ sched_yield();
+ } else {
+ LOGVV("exsl: sleep for %d", curDelay);
+ usleep(curDelay);
+ }
+ return true;
+}
+
+
+/*
+ * Set the "close on exec" flag so we don't expose our file descriptors
+ * to processes launched by us.
+ */
+bool dvmSetCloseOnExec(int fd)
+{
+ int flags;
+
+ /*
+ * There's presently only one flag defined, so getting the previous
+ * value of the fd flags is probably unnecessary.
+ */
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0) {
+ LOGW("Unable to get fd flags for fd %d", fd);
+ return false;
+ }
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+ LOGW("Unable to set close-on-exec for fd %d", fd);
+ return false;
+ }
+ return true;
+}
+
+#if (!HAVE_STRLCPY)
+/* Implementation of strlcpy() for platforms that don't already have it. */
+size_t strlcpy(char *dst, const char *src, size_t size) {
+ size_t srcLength = strlen(src);
+ size_t copyLength = srcLength;
+
+ if (srcLength > (size - 1)) {
+ copyLength = size - 1;
+ }
+
+ if (size != 0) {
+ strncpy(dst, src, copyLength);
+ dst[copyLength] = '\0';
+ }
+
+ return srcLength;
+}
+#endif
+
+/*
+ * Allocates a memory region using ashmem and mmap, initialized to
+ * zero. Actual allocation rounded up to page multiple. Returns
+ * NULL on failure.
+ */
+void *dvmAllocRegion(size_t byteCount, int prot, const char *name) {
+ void *base;
+ int fd, ret;
+
+ byteCount = ALIGN_UP_TO_PAGE_SIZE(byteCount);
+ fd = ashmem_create_region(name, byteCount);
+ if (fd == -1) {
+ return NULL;
+ }
+ base = mmap(NULL, byteCount, prot, MAP_PRIVATE, fd, 0);
+ ret = close(fd);
+ if (base == MAP_FAILED) {
+ return NULL;
+ }
+ if (ret == -1) {
+ return NULL;
+ }
+ return base;
+}
+
+/*
+ * Get some per-thread stats.
+ *
+ * This is currently generated by opening the appropriate "stat" file
+ * in /proc and reading the pile of stuff that comes out.
+ */
+bool dvmGetThreadStats(ProcStatData* pData, pid_t tid)
+{
+ /*
+ int pid;
+ char comm[128];
+ char state;
+ int ppid, pgrp, session, tty_nr, tpgid;
+ unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
+ long cutime, cstime, priority, nice, zero, itrealvalue;
+ unsigned long starttime, vsize;
+ long rss;
+ unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
+ unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
+ int exit_signal, processor;
+ unsigned long rt_priority, policy;
+
+ scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
+ "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
+ "%lu %lu %lu %d %d %lu %lu",
+ &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
+ &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
+ &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
+ &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
+ &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
+ &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
+ &rt_priority, &policy);
+
+ (new: delayacct_blkio_ticks %llu (since Linux 2.6.18))
+ */
+
+ char nameBuf[64];
+ int i, fd;
+
+ /*
+ * Open and read the appropriate file. This is expected to work on
+ * Linux but will fail on other platforms (e.g. Mac sim).
+ */
+ sprintf(nameBuf, "/proc/self/task/%d/stat", (int) tid);
+ fd = open(nameBuf, O_RDONLY);
+ if (fd < 0) {
+ LOGV("Unable to open '%s': %s", nameBuf, strerror(errno));
+ return false;
+ }
+
+ char lineBuf[512]; /* > 2x typical */
+ int cc = read(fd, lineBuf, sizeof(lineBuf)-1);
+ if (cc <= 0) {
+ const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno);
+ LOGI("Unable to read '%s': %s", nameBuf, msg);
+ close(fd);
+ return false;
+ }
+ close(fd);
+ lineBuf[cc] = '\0';
+
+ /*
+ * Skip whitespace-separated tokens. For the most part we can assume
+ * that tokens do not contain spaces, and are separated by exactly one
+ * space character. The only exception is the second field ("comm")
+ * which may contain spaces but is surrounded by parenthesis.
+ */
+ char* cp = strchr(lineBuf, ')');
+ if (cp == NULL)
+ goto parse_fail;
+ cp++;
+ for (i = 2; i < 13; i++) {
+ cp = strchr(cp+1, ' ');
+ if (cp == NULL)
+ goto parse_fail;
+ }
+
+ /*
+ * Grab utime/stime.
+ */
+ char* endp;
+ pData->utime = strtoul(cp+1, &endp, 10);
+ if (endp == cp+1)
+ LOGI("Warning: strtoul failed on utime ('%.30s...')", cp);
+
+ cp = strchr(cp+1, ' ');
+ if (cp == NULL)
+ goto parse_fail;
+
+ pData->stime = strtoul(cp+1, &endp, 10);
+ if (endp == cp+1)
+ LOGI("Warning: strtoul failed on stime ('%.30s...')", cp);
+
+ /*
+ * Skip more stuff we don't care about.
+ */
+ for (i = 14; i < 38; i++) {
+ cp = strchr(cp+1, ' ');
+ if (cp == NULL)
+ goto parse_fail;
+ }
+
+ /*
+ * Grab processor number.
+ */
+ pData->processor = strtol(cp+1, &endp, 10);
+ if (endp == cp+1)
+ LOGI("Warning: strtoul failed on processor ('%.30s...')", cp);
+
+ return true;
+
+parse_fail:
+ LOGI("stat parse failed (%s)", lineBuf);
+ return false;
+}
+
+/* documented in header file */
+const char* dvmPathToAbsolutePortion(const char* path) {
+ if (path == NULL) {
+ return NULL;
+ }
+
+ if (path[0] == '/') {
+ /* It's a regular absolute path. Return it. */
+ return path;
+ }
+
+ const char* sentinel = strstr(path, "/./");
+
+ if (sentinel != NULL) {
+ /* It's got the sentinel. Return a pointer to the second slash. */
+ return sentinel + 2;
+ }
+
+ return NULL;
+}
+
+// From RE2.
+void StringAppendV(std::string* dst, const char* format, va_list ap) {
+ // First try with a small fixed size buffer
+ char space[1024];
+
+ // It's possible for methods that use a va_list to invalidate
+ // the data in it upon use. The fix is to make a copy
+ // of the structure before using it and use that copy instead.
+ va_list backup_ap;
+ va_copy(backup_ap, ap);
+ int result = vsnprintf(space, sizeof(space), format, backup_ap);
+ va_end(backup_ap);
+
+ if ((result >= 0) && ((size_t) result < sizeof(space))) {
+ // It fit
+ dst->append(space, result);
+ return;
+ }
+
+ // Repeatedly increase buffer size until it fits
+ int length = sizeof(space);
+ while (true) {
+ if (result < 0) {
+ // Older behavior: just try doubling the buffer size
+ length *= 2;
+ } else {
+ // We need exactly "result+1" characters
+ length = result+1;
+ }
+ char* buf = new char[length];
+
+ // Restore the va_list before we use it again
+ va_copy(backup_ap, ap);
+ result = vsnprintf(buf, length, format, backup_ap);
+ va_end(backup_ap);
+
+ if ((result >= 0) && (result < length)) {
+ // It fit
+ dst->append(buf, result);
+ delete[] buf;
+ return;
+ }
+ delete[] buf;
+ }
+}
+
+std::string StringPrintf(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ std::string result;
+ StringAppendV(&result, fmt, ap);
+ va_end(ap);
+ return result;
+}
+
+void StringAppendF(std::string* dst, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ StringAppendV(dst, format, ap);
+ va_end(ap);
+}