/* * Open up the reserved area and throw an exception. The reserved area * should only be needed to create and initialize the exception itself. * * If we already opened it and we're continuing to overflow, abort the VM. * * We have to leave the "reserved" area open until the "catch" handler has * finished doing its processing. This is because the catch handler may * need to resolve classes, which requires calling into the class loader if * the classes aren't already in the "initiating loader" list. */ void dvmHandleStackOverflow(Thread* self) { /* * Can we make the reserved area available? */ if (self->stackOverflowed) { /* * Already did, nothing to do but bail. */ LOGE("DalvikVM: double-overflow of stack in threadid=%d; aborting\n", self->threadId); dvmDumpThread(self, false); dvmAbort(); } /* open it up to the full range */ LOGI("Stack overflow, expanding (%p to %p)\n", self->interpStackEnd, self->interpStackStart - self->interpStackSize); //dvmDumpThread(self, false); self->interpStackEnd = self->interpStackStart - self->interpStackSize; self->stackOverflowed = true; /* * If we were trying to throw an exception when the stack overflowed, * we will blow up when doing the class lookup on StackOverflowError * because of the pending exception. So, we clear it and make it * the cause of the SOE. */ Object* excep = dvmGetException(self); if (excep != NULL) { LOGW("Stack overflow while throwing exception\n"); dvmClearException(self); } dvmThrowChainedException("Ljava/lang/StackOverflowError;", NULL, excep); }
/* * Reduce the available stack size. By this point we should have finished * our overflow processing. */ void dvmCleanupStackOverflow(Thread* self, const Object* exception) { const u1* newStackEnd; assert(self->stackOverflowed); if (exception->clazz != gDvm.exStackOverflowError) { /* exception caused during SOE, not the SOE itself */ return; } newStackEnd = (self->interpStackStart - self->interpStackSize) + STACK_OVERFLOW_RESERVE; if ((u1*)self->interpSave.curFrame <= newStackEnd) { ALOGE("Can't shrink stack: curFrame is in reserved area (%p %p)", self->interpStackEnd, self->interpSave.curFrame); dvmDumpThread(self, false); dvmAbort(); } self->interpStackEnd = newStackEnd; self->stackOverflowed = false; ALOGI("Shrank stack (to %p, curFrame is %p)", self->interpStackEnd, self->interpSave.curFrame); }
/* * static void crash() * * Dump the current thread's interpreted stack and abort the VM. Useful * for seeing both interpreted and native stack traces. * * (Might want to restrict this to debuggable processes as a security * measure, or check SecurityManager.checkExit().) */ static void Dalvik_dalvik_system_VMDebug_crash(const u4* args, JValue* pResult) { UNUSED_PARAMETER(args); UNUSED_PARAMETER(pResult); ALOGW("Crashing VM on request"); dvmDumpThread(dvmThreadSelf(), false); dvmAbort(); }
/* * Track an object that was allocated internally and isn't yet part of the * VM root set. * * We could do this per-thread or globally. If it's global we don't have * to do the thread lookup but we do have to synchronize access to the list. * * NOTE: "obj" is not a fully-formed object; in particular, obj->clazz will * usually be NULL since we're being called from dvmMalloc(). */ void dvmAddTrackedAlloc(Object* obj, Thread* self) { if (self == NULL) self = dvmThreadSelf(); assert(self != NULL); if (!dvmAddToReferenceTable(&self->internalLocalRefTable, obj)) { LOGE("threadid=%d: unable to add %p to internal ref table\n", self->threadId, obj); dvmDumpThread(self, false); dvmAbort(); } }
/* * Open up the reserved area and throw an exception. The reserved area * should only be needed to create and initialize the exception itself. * * If we already opened it and we're continuing to overflow, abort the VM. * * We have to leave the "reserved" area open until the "catch" handler has * finished doing its processing. This is because the catch handler may * need to resolve classes, which requires calling into the class loader if * the classes aren't already in the "initiating loader" list. */ void dvmHandleStackOverflow(Thread* self, const Method* method) { /* * Can we make the reserved area available? */ if (self->stackOverflowed) { /* * Already did, nothing to do but bail. */ LOGE("DalvikVM: double-overflow of stack in threadid=%d; aborting\n", self->threadId); dvmDumpThread(self, false); dvmAbort(); } /* open it up to the full range */ LOGI("threadid=%d: stack overflow on call to %s.%s:%s\n", self->threadId, method->clazz->descriptor, method->name, method->shorty); StackSaveArea* saveArea = SAVEAREA_FROM_FP(self->curFrame); LOGI(" method requires %d+%d+%d=%d bytes, fp is %p (%d left)\n", method->registersSize * 4, sizeof(StackSaveArea), method->outsSize * 4, (method->registersSize + method->outsSize) * 4 + sizeof(StackSaveArea), saveArea, (u1*) saveArea - self->interpStackEnd); LOGI(" expanding stack end (%p to %p)\n", self->interpStackEnd, self->interpStackStart - self->interpStackSize); //dvmDumpThread(self, false); self->interpStackEnd = self->interpStackStart - self->interpStackSize; self->stackOverflowed = true; /* * If we were trying to throw an exception when the stack overflowed, * we will blow up when doing the class lookup on StackOverflowError * because of the pending exception. So, we clear it and make it * the cause of the SOE. */ Object* excep = dvmGetException(self); if (excep != NULL) { LOGW("Stack overflow while throwing exception\n"); dvmClearException(self); } dvmThrowChainedExceptionByClass(gDvm.classJavaLangStackOverflowError, NULL, excep); }
// work-around to get a reference wrapper to an object so that it can be used // for certain calls to the JNI environment. almost verbatim copy from Jni.cpp static jobject dexspyAddLocalReference(::Thread* self, Object* obj) { if (obj == NULL) { return NULL; } IndirectRefTable* pRefTable = &self->jniLocalRefTable; void* curFrame = self->interpSave.curFrame; u4 cookie = SAVEAREA_FROM_FP(curFrame)->xtra.localRefCookie; jobject jobj = (jobject) pRefTable->add(cookie, obj); if (UNLIKELY(jobj == NULL)) { pRefTable->dump("JNI local"); ALOGE("Failed adding to JNI local ref table (has %zd entries)", pRefTable->capacity()); dvmDumpThread(self, false); dvmAbort(); // spec says call FatalError; this is equivalent } if (UNLIKELY(gDvmJni.workAroundAppJniBugs)) { // Hand out direct pointers to support broken old apps. return reinterpret_cast<jobject>(obj); } return jobj; }
/* * Reduce the available stack size. By this point we should have finished * our overflow processing. */ void dvmCleanupStackOverflow(Thread* self) { const u1* newStackEnd; assert(self->stackOverflowed); newStackEnd = (self->interpStackStart - self->interpStackSize) + STACK_OVERFLOW_RESERVE; if ((u1*)self->curFrame <= newStackEnd) { LOGE("Can't shrink stack: curFrame is in reserved area (%p %p)\n", self->interpStackEnd, self->curFrame); dvmDumpThread(self, false); dvmAbort(); } self->interpStackEnd = newStackEnd; self->stackOverflowed = false; LOGI("Shrank stack (to %p, curFrame is %p)\n", self->interpStackEnd, self->curFrame); }
/* Try as hard as possible to allocate some memory. */ static void *tryMalloc(size_t size) { void *ptr; /* Don't try too hard if there's no way the allocation is * going to succeed. We have to collect SoftReferences before * throwing an OOME, though. */ if (size >= gDvm.heapGrowthLimit) { ALOGW("%zd byte allocation exceeds the %zd byte maximum heap size", size, gDvm.heapGrowthLimit); ptr = NULL; goto collect_soft_refs; } //TODO: figure out better heuristics // There will be a lot of churn if someone allocates a bunch of // big objects in a row, and we hit the frag case each time. // A full GC for each. // Maybe we grow the heap in bigger leaps // Maybe we skip the GC if the size is large and we did one recently // (number of allocations ago) (watch for thread effects) // DeflateTest allocs a bunch of ~128k buffers w/in 0-5 allocs of each other // (or, at least, there are only 0-5 objects swept each time) ptr = dvmHeapSourceAlloc(size); if (ptr != NULL) { return ptr; } /* * The allocation failed. If the GC is running, block until it * completes and retry. */ if (gDvm.gcHeap->gcRunning) { /* * The GC is concurrently tracing the heap. Release the heap * lock, wait for the GC to complete, and retrying allocating. */ dvmWaitForConcurrentGcToComplete(); ptr = dvmHeapSourceAlloc(size); if (ptr != NULL) { return ptr; } } /* * Another failure. Our thread was starved or there may be too * many live objects. Try a foreground GC. This will have no * effect if the concurrent GC is already running. */ gcForMalloc(false); ptr = dvmHeapSourceAlloc(size); if (ptr != NULL) { return ptr; } /* Even that didn't work; this is an exceptional state. * Try harder, growing the heap if necessary. */ ptr = dvmHeapSourceAllocAndGrow(size); if (ptr != NULL) { size_t newHeapSize; newHeapSize = dvmHeapSourceGetIdealFootprint(); //TODO: may want to grow a little bit more so that the amount of free // space is equal to the old free space + the utilization slop for // the new allocation. LOGI_HEAP("Grow heap (frag case) to " "%zu.%03zuMB for %zu-byte allocation", FRACTIONAL_MB(newHeapSize), size); return ptr; } /* Most allocations should have succeeded by now, so the heap * is really full, really fragmented, or the requested size is * really big. Do another GC, collecting SoftReferences this * time. The VM spec requires that all SoftReferences have * been collected and cleared before throwing an OOME. */ //TODO: wait for the finalizers from the previous GC to finish collect_soft_refs: LOGI_HEAP("Forcing collection of SoftReferences for %zu-byte allocation", size); gcForMalloc(true); ptr = dvmHeapSourceAllocAndGrow(size); if (ptr != NULL) { return ptr; } //TODO: maybe wait for finalizers and try one last time LOGE_HEAP("Out of memory on a %zd-byte allocation.", size); //TODO: tell the HeapSource to dump its state dvmDumpThread(dvmThreadSelf(), false); return NULL; }