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 * 36 01234567: 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 */ 47 void 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 */ 137 void 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 */ 151 void 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 */ 163 void dvmFreeOutputTarget(DebugOutputTarget* target) 164 { 165 free(target); 166 } 167 168 /* 169 * Print a debug message, to either a file or the log. 170 */ 171 void 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 */ 200 char* 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 221 std::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 269 std::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 286 std::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 297 std::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 */ 323 char* 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 */ 355 char* 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 */ 398 char* 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 */ 420 char* 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 */ 446 u8 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 */ 464 u8 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 */ 478 u8 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 */ 513 bool 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 */ 563 bool 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. */ 585 size_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 */ 607 void *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 */ 633 bool 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 LOGV("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 LOGI("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 LOGI("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 LOGI("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 LOGI("Warning: strtoul failed on processor ('%.30s...')", cp); 735 736 return true; 737 738 parse_fail: 739 LOGI("stat parse failed (%s)", lineBuf); 740 return false; 741 } 742 743 /* documented in header file */ 744 const 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. 765 void 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 810 std::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 819 void 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