Misc.cpp revision 4308417beec548c2b2c06ecec4f7f4a965b09fb2
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Miscellaneous utility functions.
19 */
20#include "Dalvik.h"
21
22#include <stdlib.h>
23#include <stddef.h>
24#include <string.h>
25#include <strings.h>
26#include <ctype.h>
27#include <time.h>
28#include <sys/time.h>
29#include <fcntl.h>
30#include <cutils/ashmem.h>
31#include <sys/mman.h>
32
33/*
34 * Print a hex dump in this format:
35 *
3601234567: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff  0123456789abcdef\n
37 *
38 * If "mode" is kHexDumpLocal, we start at offset zero, and show a full
39 * 16 bytes on the first line.  If it's kHexDumpMem, we make this look
40 * like a memory dump, using the actual address, outputting a partial line
41 * if "vaddr" isn't aligned on a 16-byte boundary.
42 *
43 * "priority" and "tag" determine the values passed to the log calls.
44 *
45 * Does not use printf() or other string-formatting calls.
46 */
47void dvmPrintHexDumpEx(int priority, const char* tag, const void* vaddr,
48    size_t length, HexDumpMode mode)
49{
50    static const char gHexDigit[] = "0123456789abcdef";
51    const unsigned char* addr = (const unsigned char*)vaddr;
52    char out[77];           /* exact fit */
53    unsigned int offset;    /* offset to show while printing */
54    char* hex;
55    char* asc;
56    int gap;
57    //int trickle = 0;
58
59    if (mode == kHexDumpLocal)
60        offset = 0;
61    else
62        offset = (int) addr;
63
64    memset(out, ' ', sizeof(out)-1);
65    out[8] = ':';
66    out[sizeof(out)-2] = '\n';
67    out[sizeof(out)-1] = '\0';
68
69    gap = (int) offset & 0x0f;
70    while (length) {
71        unsigned int lineOffset = offset & ~0x0f;
72        int i, count;
73
74        hex = out;
75        asc = out + 59;
76
77        for (i = 0; i < 8; i++) {
78            *hex++ = gHexDigit[lineOffset >> 28];
79            lineOffset <<= 4;
80        }
81        hex++;
82        hex++;
83
84        count = ((int)length > 16-gap) ? 16-gap : (int)length; /* cap length */
85        assert(count != 0);
86        assert(count+gap <= 16);
87
88        if (gap) {
89            /* only on first line */
90            hex += gap * 3;
91            asc += gap;
92        }
93
94        for (i = gap ; i < count+gap; i++) {
95            *hex++ = gHexDigit[*addr >> 4];
96            *hex++ = gHexDigit[*addr & 0x0f];
97            hex++;
98            if (*addr >= 0x20 && *addr < 0x7f /*isprint(*addr)*/)
99                *asc++ = *addr;
100            else
101                *asc++ = '.';
102            addr++;
103        }
104        for ( ; i < 16; i++) {
105            /* erase extra stuff; only happens on last line */
106            *hex++ = ' ';
107            *hex++ = ' ';
108            hex++;
109            *asc++ = ' ';
110        }
111
112        LOG_PRI(priority, tag, "%s", out);
113#if 0 //def HAVE_ANDROID_OS
114        /*
115         * We can overrun logcat easily by writing at full speed.  On the
116         * other hand, we can make Eclipse time out if we're showing
117         * packet dumps while debugging JDWP.
118         */
119        {
120            if (trickle++ == 8) {
121                trickle = 0;
122                usleep(20000);
123            }
124        }
125#endif
126
127        gap = 0;
128        length -= count;
129        offset += count;
130    }
131}
132
133
134/*
135 * Fill out a DebugOutputTarget, suitable for printing to the log.
136 */
137void dvmCreateLogOutputTarget(DebugOutputTarget* target, int priority,
138    const char* tag)
139{
140    assert(target != NULL);
141    assert(tag != NULL);
142
143    target->which = kDebugTargetLog;
144    target->data.log.priority = priority;
145    target->data.log.tag = tag;
146}
147
148/*
149 * Fill out a DebugOutputTarget suitable for printing to a file pointer.
150 */
151void dvmCreateFileOutputTarget(DebugOutputTarget* target, FILE* fp)
152{
153    assert(target != NULL);
154    assert(fp != NULL);
155
156    target->which = kDebugTargetFile;
157    target->data.file.fp = fp;
158}
159
160/*
161 * Free "target" and any associated data.
162 */
163void dvmFreeOutputTarget(DebugOutputTarget* target)
164{
165    free(target);
166}
167
168/*
169 * Print a debug message, to either a file or the log.
170 */
171void dvmPrintDebugMessage(const DebugOutputTarget* target, const char* format,
172    ...)
173{
174    va_list args;
175
176    va_start(args, format);
177
178    switch (target->which) {
179    case kDebugTargetLog:
180        LOG_PRI_VA(target->data.log.priority, target->data.log.tag,
181            format, args);
182        break;
183    case kDebugTargetFile:
184        vfprintf(target->data.file.fp, format, args);
185        break;
186    default:
187        LOGE("unexpected 'which' %d", target->which);
188        break;
189    }
190
191    va_end(args);
192}
193
194
195/*
196 * Return a newly-allocated string in which all occurrences of '.' have
197 * been changed to '/'.  If we find a '/' in the original string, NULL
198 * is returned to avoid ambiguity.
199 */
200char* dvmDotToSlash(const char* str)
201{
202    char* newStr = strdup(str);
203    char* cp = newStr;
204
205    if (newStr == NULL)
206        return NULL;
207
208    while (*cp != '\0') {
209        if (*cp == '/') {
210            assert(false);
211            return NULL;
212        }
213        if (*cp == '.')
214            *cp = '/';
215        cp++;
216    }
217
218    return newStr;
219}
220
221std::string dvmHumanReadableDescriptor(const char* descriptor) {
222    // Count the number of '['s to get the dimensionality.
223    const char* c = descriptor;
224    size_t dim = 0;
225    while (*c == '[') {
226        dim++;
227        c++;
228    }
229
230    // Reference or primitive?
231    if (*c == 'L') {
232        // "[[La/b/C;" -> "a.b.C[][]".
233        c++; // Skip the 'L'.
234    } else {
235        // "[[B" -> "byte[][]".
236        // To make life easier, we make primitives look like unqualified
237        // reference types.
238        switch (*c) {
239        case 'B': c = "byte;"; break;
240        case 'C': c = "char;"; break;
241        case 'D': c = "double;"; break;
242        case 'F': c = "float;"; break;
243        case 'I': c = "int;"; break;
244        case 'J': c = "long;"; break;
245        case 'S': c = "short;"; break;
246        case 'Z': c = "boolean;"; break;
247        default: return descriptor;
248        }
249    }
250
251    // At this point, 'c' is a string of the form "fully/qualified/Type;"
252    // or "primitive;". Rewrite the type with '.' instead of '/':
253    std::string result;
254    const char* p = c;
255    while (*p != ';') {
256        char ch = *p++;
257        if (ch == '/') {
258          ch = '.';
259        }
260        result.push_back(ch);
261    }
262    // ...and replace the semicolon with 'dim' "[]" pairs:
263    while (dim--) {
264        result += "[]";
265    }
266    return result;
267}
268
269std::string dvmHumanReadableType(const Object* obj)
270{
271    if (obj == NULL) {
272        return "null";
273    }
274    if (obj->clazz == NULL) {
275        /* should only be possible right after a plain dvmMalloc() */
276        return "(raw)";
277    }
278    std::string result(dvmHumanReadableDescriptor(obj->clazz->descriptor));
279    if (dvmIsClassObject(obj)) {
280        const ClassObject* clazz = reinterpret_cast<const ClassObject*>(obj);
281        result += "<" + dvmHumanReadableDescriptor(clazz->descriptor) + ">";
282    }
283    return result;
284}
285
286std::string dvmHumanReadableField(const Field* field)
287{
288    if (field == NULL) {
289        return "(null)";
290    }
291    std::string result(dvmHumanReadableDescriptor(field->clazz->descriptor));
292    result += '.';
293    result += field->name;
294    return result;
295}
296
297std::string dvmHumanReadableMethod(const Method* method, bool withSignature)
298{
299    if (method == NULL) {
300        return "(null)";
301    }
302    std::string result(dvmHumanReadableDescriptor(method->clazz->descriptor));
303    result += '.';
304    result += method->name;
305    if (withSignature) {
306        // TODO: the types in this aren't human readable!
307        char* signature = dexProtoCopyMethodDescriptor(&method->prototype);
308        result += signature;
309        free(signature);
310    }
311    return result;
312}
313
314/*
315 * Return a newly-allocated string for the "dot version" of the class
316 * name for the given type descriptor. That is, The initial "L" and
317 * final ";" (if any) have been removed and all occurrences of '/'
318 * have been changed to '.'.
319 *
320 * "Dot version" names are used in the class loading machinery.
321 * See also dvmHumanReadableDescriptor.
322 */
323char* dvmDescriptorToDot(const char* str)
324{
325    size_t at = strlen(str);
326    char* newStr;
327
328    if ((at >= 2) && (str[0] == 'L') && (str[at - 1] == ';')) {
329        at -= 2; /* Two fewer chars to copy. */
330        str++; /* Skip the 'L'. */
331    }
332
333    newStr = (char*)malloc(at + 1); /* Add one for the '\0'. */
334    if (newStr == NULL)
335        return NULL;
336
337    newStr[at] = '\0';
338
339    while (at > 0) {
340        at--;
341        newStr[at] = (str[at] == '/') ? '.' : str[at];
342    }
343
344    return newStr;
345}
346
347/*
348 * Return a newly-allocated string for the type descriptor
349 * corresponding to the "dot version" of the given class name. That
350 * is, non-array names are surrounded by "L" and ";", and all
351 * occurrences of '.' have been changed to '/'.
352 *
353 * "Dot version" names are used in the class loading machinery.
354 */
355char* dvmDotToDescriptor(const char* str)
356{
357    size_t length = strlen(str);
358    int wrapElSemi = 0;
359    char* newStr;
360    char* at;
361
362    if (str[0] != '[') {
363        length += 2; /* for "L" and ";" */
364        wrapElSemi = 1;
365    }
366
367    newStr = at = (char*)malloc(length + 1); /* + 1 for the '\0' */
368
369    if (newStr == NULL) {
370        return NULL;
371    }
372
373    if (wrapElSemi) {
374        *(at++) = 'L';
375    }
376
377    while (*str) {
378        char c = *(str++);
379        if (c == '.') {
380            c = '/';
381        }
382        *(at++) = c;
383    }
384
385    if (wrapElSemi) {
386        *(at++) = ';';
387    }
388
389    *at = '\0';
390    return newStr;
391}
392
393/*
394 * Return a newly-allocated string for the internal-form class name for
395 * the given type descriptor. That is, the initial "L" and final ";" (if
396 * any) have been removed.
397 */
398char* dvmDescriptorToName(const char* str)
399{
400    if (str[0] == 'L') {
401        size_t length = strlen(str) - 1;
402        char* newStr = (char*)malloc(length);
403
404        if (newStr == NULL) {
405            return NULL;
406        }
407
408        strlcpy(newStr, str + 1, length);
409        return newStr;
410    }
411
412    return strdup(str);
413}
414
415/*
416 * Return a newly-allocated string for the type descriptor for the given
417 * internal-form class name. That is, a non-array class name will get
418 * surrounded by "L" and ";", while array names are left as-is.
419 */
420char* dvmNameToDescriptor(const char* str)
421{
422    if (str[0] != '[') {
423        size_t length = strlen(str);
424        char* descriptor = (char*)malloc(length + 3);
425
426        if (descriptor == NULL) {
427            return NULL;
428        }
429
430        descriptor[0] = 'L';
431        strcpy(descriptor + 1, str);
432        descriptor[length + 1] = ';';
433        descriptor[length + 2] = '\0';
434
435        return descriptor;
436    }
437
438    return strdup(str);
439}
440
441/*
442 * Get a notion of the current time, in nanoseconds.  This is meant for
443 * computing durations (e.g. "operation X took 52nsec"), so the result
444 * should not be used to get the current date/time.
445 */
446u8 dvmGetRelativeTimeNsec()
447{
448#ifdef HAVE_POSIX_CLOCKS
449    struct timespec now;
450    clock_gettime(CLOCK_MONOTONIC, &now);
451    return (u8)now.tv_sec*1000000000LL + now.tv_nsec;
452#else
453    struct timeval now;
454    gettimeofday(&now, NULL);
455    return (u8)now.tv_sec*1000000000LL + now.tv_usec * 1000LL;
456#endif
457}
458
459/*
460 * Get the per-thread CPU time, in nanoseconds.
461 *
462 * Only useful for time deltas.
463 */
464u8 dvmGetThreadCpuTimeNsec()
465{
466#ifdef HAVE_POSIX_CLOCKS
467    struct timespec now;
468    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
469    return (u8)now.tv_sec*1000000000LL + now.tv_nsec;
470#else
471    return (u8) -1;
472#endif
473}
474
475/*
476 * Get the per-thread CPU time, in nanoseconds, for the specified thread.
477 */
478u8 dvmGetOtherThreadCpuTimeNsec(pthread_t thread)
479{
480#if 0 /*def HAVE_POSIX_CLOCKS*/
481    int clockId;
482
483    if (pthread_getcpuclockid(thread, &clockId) != 0)
484        return (u8) -1;
485
486    struct timespec now;
487    clock_gettime(clockId, &now);
488    return (u8)now.tv_sec*1000000000LL + now.tv_nsec;
489#else
490    return (u8) -1;
491#endif
492}
493
494
495/*
496 * Call this repeatedly, with successively higher values for "iteration",
497 * to sleep for a period of time not to exceed "maxTotalSleep".
498 *
499 * For example, when called with iteration==0 we will sleep for a very
500 * brief time.  On the next call we will sleep for a longer time.  When
501 * the sum total of all sleeps reaches "maxTotalSleep", this returns false.
502 *
503 * The initial start time value for "relStartTime" MUST come from the
504 * dvmGetRelativeTimeUsec call.  On the device this must come from the
505 * monotonic clock source, not the wall clock.
506 *
507 * This should be used wherever you might be tempted to call sched_yield()
508 * in a loop.  The problem with sched_yield is that, for a high-priority
509 * thread, the kernel might not actually transfer control elsewhere.
510 *
511 * Returns "false" if we were unable to sleep because our time was up.
512 */
513bool dvmIterativeSleep(int iteration, int maxTotalSleep, u8 relStartTime)
514{
515    const int minSleep = 10000;
516    u8 curTime;
517    int curDelay;
518
519    /*
520     * Get current time, and see if we've already exceeded the limit.
521     */
522    curTime = dvmGetRelativeTimeUsec();
523    if (curTime >= relStartTime + maxTotalSleep) {
524        LOGVV("exsl: sleep exceeded (start=%llu max=%d now=%llu)",
525            relStartTime, maxTotalSleep, curTime);
526        return false;
527    }
528
529    /*
530     * Compute current delay.  We're bounded by "maxTotalSleep", so no
531     * real risk of overflow assuming "usleep" isn't returning early.
532     * (Besides, 2^30 usec is about 18 minutes by itself.)
533     *
534     * For iteration==0 we just call sched_yield(), so the first sleep
535     * at iteration==1 is actually (minSleep * 2).
536     */
537    curDelay = minSleep;
538    while (iteration-- > 0)
539        curDelay *= 2;
540    assert(curDelay > 0);
541
542    if (curTime + curDelay >= relStartTime + maxTotalSleep) {
543        LOGVV("exsl: reduced delay from %d to %d",
544            curDelay, (int) ((relStartTime + maxTotalSleep) - curTime));
545        curDelay = (int) ((relStartTime + maxTotalSleep) - curTime);
546    }
547
548    if (iteration == 0) {
549        LOGVV("exsl: yield");
550        sched_yield();
551    } else {
552        LOGVV("exsl: sleep for %d", curDelay);
553        usleep(curDelay);
554    }
555    return true;
556}
557
558
559/*
560 * Set the "close on exec" flag so we don't expose our file descriptors
561 * to processes launched by us.
562 */
563bool dvmSetCloseOnExec(int fd)
564{
565    int flags;
566
567    /*
568     * There's presently only one flag defined, so getting the previous
569     * value of the fd flags is probably unnecessary.
570     */
571    flags = fcntl(fd, F_GETFD);
572    if (flags < 0) {
573        LOGW("Unable to get fd flags for fd %d", fd);
574        return false;
575    }
576    if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
577        LOGW("Unable to set close-on-exec for fd %d", fd);
578        return false;
579    }
580    return true;
581}
582
583#if (!HAVE_STRLCPY)
584/* Implementation of strlcpy() for platforms that don't already have it. */
585size_t strlcpy(char *dst, const char *src, size_t size) {
586    size_t srcLength = strlen(src);
587    size_t copyLength = srcLength;
588
589    if (srcLength > (size - 1)) {
590        copyLength = size - 1;
591    }
592
593    if (size != 0) {
594        strncpy(dst, src, copyLength);
595        dst[copyLength] = '\0';
596    }
597
598    return srcLength;
599}
600#endif
601
602/*
603 *  Allocates a memory region using ashmem and mmap, initialized to
604 *  zero.  Actual allocation rounded up to page multiple.  Returns
605 *  NULL on failure.
606 */
607void *dvmAllocRegion(size_t byteCount, int prot, const char *name) {
608    void *base;
609    int fd, ret;
610
611    byteCount = ALIGN_UP_TO_PAGE_SIZE(byteCount);
612    fd = ashmem_create_region(name, byteCount);
613    if (fd == -1) {
614        return NULL;
615    }
616    base = mmap(NULL, byteCount, prot, MAP_PRIVATE, fd, 0);
617    ret = close(fd);
618    if (base == MAP_FAILED) {
619        return NULL;
620    }
621    if (ret == -1) {
622        return NULL;
623    }
624    return base;
625}
626
627/*
628 * Get some per-thread stats.
629 *
630 * This is currently generated by opening the appropriate "stat" file
631 * in /proc and reading the pile of stuff that comes out.
632 */
633bool dvmGetThreadStats(ProcStatData* pData, pid_t tid)
634{
635    /*
636    int pid;
637    char comm[128];
638    char state;
639    int ppid, pgrp, session, tty_nr, tpgid;
640    unsigned long flags, minflt, cminflt, majflt, cmajflt, utime, stime;
641    long cutime, cstime, priority, nice, zero, itrealvalue;
642    unsigned long starttime, vsize;
643    long rss;
644    unsigned long rlim, startcode, endcode, startstack, kstkesp, kstkeip;
645    unsigned long signal, blocked, sigignore, sigcatch, wchan, nswap, cnswap;
646    int exit_signal, processor;
647    unsigned long rt_priority, policy;
648
649    scanf("%d %s %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %ld %ld %ld "
650          "%ld %ld %ld %lu %lu %ld %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu "
651          "%lu %lu %lu %d %d %lu %lu",
652        &pid, comm, &state, &ppid, &pgrp, &session, &tty_nr, &tpgid,
653        &flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime,
654        &cutime, &cstime, &priority, &nice, &zero, &itrealvalue,
655        &starttime, &vsize, &rss, &rlim, &startcode, &endcode,
656        &startstack, &kstkesp, &kstkeip, &signal, &blocked, &sigignore,
657        &sigcatch, &wchan, &nswap, &cnswap, &exit_signal, &processor,
658        &rt_priority, &policy);
659
660        (new: delayacct_blkio_ticks %llu (since Linux 2.6.18))
661    */
662
663    char nameBuf[64];
664    int i, fd;
665
666    /*
667     * Open and read the appropriate file.  This is expected to work on
668     * Linux but will fail on other platforms (e.g. Mac sim).
669     */
670    sprintf(nameBuf, "/proc/self/task/%d/stat", (int) tid);
671    fd = open(nameBuf, O_RDONLY);
672    if (fd < 0) {
673        ALOGV("Unable to open '%s': %s", nameBuf, strerror(errno));
674        return false;
675    }
676
677    char lineBuf[512];      /* > 2x typical */
678    int cc = read(fd, lineBuf, sizeof(lineBuf)-1);
679    if (cc <= 0) {
680        const char* msg = (cc == 0) ? "unexpected EOF" : strerror(errno);
681        ALOGI("Unable to read '%s': %s", nameBuf, msg);
682        close(fd);
683        return false;
684    }
685    close(fd);
686    lineBuf[cc] = '\0';
687
688    /*
689     * Skip whitespace-separated tokens.  For the most part we can assume
690     * that tokens do not contain spaces, and are separated by exactly one
691     * space character.  The only exception is the second field ("comm")
692     * which may contain spaces but is surrounded by parenthesis.
693     */
694    char* cp = strchr(lineBuf, ')');
695    if (cp == NULL)
696        goto parse_fail;
697    cp++;
698    for (i = 2; i < 13; i++) {
699        cp = strchr(cp+1, ' ');
700        if (cp == NULL)
701            goto parse_fail;
702    }
703
704    /*
705     * Grab utime/stime.
706     */
707    char* endp;
708    pData->utime = strtoul(cp+1, &endp, 10);
709    if (endp == cp+1)
710        ALOGI("Warning: strtoul failed on utime ('%.30s...')", cp);
711
712    cp = strchr(cp+1, ' ');
713    if (cp == NULL)
714        goto parse_fail;
715
716    pData->stime = strtoul(cp+1, &endp, 10);
717    if (endp == cp+1)
718        ALOGI("Warning: strtoul failed on stime ('%.30s...')", cp);
719
720    /*
721     * Skip more stuff we don't care about.
722     */
723    for (i = 14; i < 38; i++) {
724        cp = strchr(cp+1, ' ');
725        if (cp == NULL)
726            goto parse_fail;
727    }
728
729    /*
730     * Grab processor number.
731     */
732    pData->processor = strtol(cp+1, &endp, 10);
733    if (endp == cp+1)
734        ALOGI("Warning: strtoul failed on processor ('%.30s...')", cp);
735
736    return true;
737
738parse_fail:
739    ALOGI("stat parse failed (%s)", lineBuf);
740    return false;
741}
742
743/* documented in header file */
744const char* dvmPathToAbsolutePortion(const char* path) {
745    if (path == NULL) {
746        return NULL;
747    }
748
749    if (path[0] == '/') {
750        /* It's a regular absolute path. Return it. */
751        return path;
752    }
753
754    const char* sentinel = strstr(path, "/./");
755
756    if (sentinel != NULL) {
757        /* It's got the sentinel. Return a pointer to the second slash. */
758        return sentinel + 2;
759    }
760
761    return NULL;
762}
763
764// From RE2.
765void StringAppendV(std::string* dst, const char* format, va_list ap) {
766    // First try with a small fixed size buffer
767    char space[1024];
768
769    // It's possible for methods that use a va_list to invalidate
770    // the data in it upon use.  The fix is to make a copy
771    // of the structure before using it and use that copy instead.
772    va_list backup_ap;
773    va_copy(backup_ap, ap);
774    int result = vsnprintf(space, sizeof(space), format, backup_ap);
775    va_end(backup_ap);
776
777    if ((result >= 0) && ((size_t) result < sizeof(space))) {
778        // It fit
779        dst->append(space, result);
780        return;
781    }
782
783    // Repeatedly increase buffer size until it fits
784    int length = sizeof(space);
785    while (true) {
786        if (result < 0) {
787            // Older behavior: just try doubling the buffer size
788            length *= 2;
789        } else {
790            // We need exactly "result+1" characters
791            length = result+1;
792        }
793        char* buf = new char[length];
794
795        // Restore the va_list before we use it again
796        va_copy(backup_ap, ap);
797        result = vsnprintf(buf, length, format, backup_ap);
798        va_end(backup_ap);
799
800        if ((result >= 0) && (result < length)) {
801            // It fit
802            dst->append(buf, result);
803            delete[] buf;
804            return;
805        }
806        delete[] buf;
807    }
808}
809
810std::string StringPrintf(const char* fmt, ...) {
811    va_list ap;
812    va_start(ap, fmt);
813    std::string result;
814    StringAppendV(&result, fmt, ap);
815    va_end(ap);
816    return result;
817}
818
819void StringAppendF(std::string* dst, const char* format, ...) {
820    va_list ap;
821    va_start(ap, format);
822    StringAppendV(dst, format, ap);
823    va_end(ap);
824}
825