diff options
Diffstat (limited to 'vm/Misc.cpp')
-rw-r--r-- | vm/Misc.cpp | 824 |
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); +} |