/*
* 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.
*/
/*
* Stacks and their uses (e.g. native --> interpreted method calls).
*
* See the majestic ASCII art in Stack.h.
*/
#include "Dalvik.h"
#include "jni.h"
#include <stdlib.h>
#include <stdarg.h>
#ifdef HAVE_ANDROID_OS
#include <corkscrew/backtrace.h>
#endif
/*
* Initialize the interpreter stack in a new thread.
*
* Currently this doesn't do much, since we don't need to zero out the
* stack (and we really don't want to if it was created with mmap).
*/
bool dvmInitInterpStack(Thread* thread, int stackSize)
{
assert(thread->interpStackStart != NULL);
assert(thread->interpSave.curFrame == NULL);
return true;
}
/*
* We're calling an interpreted method from an internal VM function or
* via reflection.
*
* Push a frame for an interpreted method onto the stack. This is only
* used when calling into interpreted code from native code. (The
* interpreter does its own stack frame manipulation for interp-->interp
* calls.)
*
* The size we need to reserve is the sum of parameters, local variables,
* saved goodies, and outbound parameters.
*
* We start by inserting a "break" frame, which ensures that the interpreter
* hands control back to us after the function we call returns or an
* uncaught exception is thrown.
*/
static bool dvmPushInterpFrame(Thread* self, const Method* method)
{
StackSaveArea* saveBlock;
StackSaveArea* breakSaveBlock;
int stackReq;
u1* stackPtr;
assert(!dvmIsNativeMethod(method));
assert(!dvmIsAbstractMethod(method));
stackReq = method->registersSize * 4 // params + locals
+ sizeof(StackSaveArea) * 2 // break frame + regular frame
+ method->outsSize * 4; // args to other methods
if (self->interpSave.curFrame != NULL)
stackPtr = (u1*) SAVEAREA_FROM_FP(self->interpSave.curFrame);
else
stackPtr = self->interpStackStart;
if (stackPtr - stackReq < self->interpStackEnd) {
/* not enough space */
ALOGW("Stack overflow on call to interp "
"(req=%d top=%p cur=%p size=%d %s.%s)",
stackReq, self->interpStackStart, self->interpSave.curFrame,
self->interpStackSize, method->clazz->descriptor, method->name);
dvmHandleStackOverflow(self, method);
assert(dvmCheckException(self));
return false;
}
/*
* Shift the stack pointer down, leaving space for the function's
* args/registers and save area.
*/
stackPtr -= sizeof(StackSaveArea);
breakSaveBlock = (StackSaveArea*)stackPtr;
stackPtr -= method->registersSize * 4 + sizeof(StackSaveArea);
saveBlock = (StackSaveArea*) stackPtr;
#if !defined(NDEBUG) && !defined(PAD_SAVE_AREA)
/* debug -- memset the new stack, unless we want valgrind's help */
memset(stackPtr - (method->outsSize*4), 0xaf, stackReq);
#endif
#ifdef EASY_GDB
breakSaveBlock->prevSave =
(StackSaveArea*)FP_FROM_SAVEAREA(self->interpSave.curFrame);
saveBlock->prevSave = breakSaveBlock;
#endif
breakSaveBlock->prevFrame = self->interpSave.curFrame;
breakSaveBlock->savedPc = NULL; // not required
breakSaveBlock->xtra.localRefCookie = 0; // not required
breakSaveBlock->method = NULL;
saveBlock->prevFrame = FP_FROM_SAVEAREA(breakSaveBlock);
saveBlock->savedPc = NULL; // not required
saveBlock->xtra.currentPc = NULL; // not required?
saveBlock->method = method;
LOGVV("PUSH frame: old=%p new=%p (size=%d)",
self->interpSave.curFrame, FP_FROM_SAVEAREA(saveBlock),
(u1*)self->interpSave.curFrame - (u1*)FP_FROM_SAVEAREA(saveBlock));
self->interpSave.curFrame = FP_FROM_SAVEAREA(saveBlock);
return true;
}
/*
* We're calling a JNI native method from an internal VM fuction or
* via reflection. This is also used to create the "fake" native-method
* frames at the top of the interpreted stack.
*
* This actually pushes two frames; the first is a "break" frame.
*
* The top frame has additional space for JNI local reference tracking.
*/
bool dvmPushJNIFrame(Thread* self, const Method* method)
{
StackSaveArea* saveBlock;
StackSaveArea* breakSaveBlock;
int stackReq;
u1* stackPtr;
assert(dvmIsNativeMethod(method));
stackReq = method->registersSize * 4 // params only
+ sizeof(StackSaveArea) * 2; // break frame + regular frame
if (self->interpSave.curFrame != NULL)
stackPtr = (u1*) SAVEAREA_FROM_FP(self->interpSave.curFrame);
else
stackPtr = self->interpStackStart;
if (stackPtr - stackReq < self->interpStackEnd) {
/* not enough space */
ALOGW("Stack overflow on call to native "
"(req=%d top=%p cur=%p size=%d '%s')",
stackReq, self->interpStackStart, self->interpSave.curFrame,
self->interpStackSize, method->name);
dvmHandleStackOverflow(self, method);
assert(dvmCheckException(self));
return false;
}
/*
* Shift the stack pointer down, leaving space for just the stack save
* area for the break frame, then shift down farther for the full frame.
* We leave space for the method args, which are copied in later.
*/
stackPtr -= sizeof(StackSaveArea);
breakSaveBlock = (StackSaveArea*)stackPtr;
stackPtr -= method->registersSize * 4 + sizeof(StackSaveArea);
saveBlock = (StackSaveArea*) stackPtr;
#if !defined(NDEBUG) && !defined(PAD_SAVE_AREA)
/* debug -- memset the new stack */
memset(stackPtr, 0xaf, stackReq);
#endif
#ifdef EASY_GDB
if (self->interpSave.curFrame == NULL)
breakSaveBlock->prevSave = NULL;
else {
void* fp = FP_FROM_SAVEAREA(self->interpSave.curFrame);
breakSaveBlock->prevSave = (StackSaveArea*)fp;
}
saveBlock->prevSave = breakSaveBlock;
#endif
breakSaveBlock->prevFrame = self->interpSave.curFrame;
breakSaveBlock->savedPc = NULL; // not required
breakSaveBlock->xtra.localRefCookie = 0; // not required
breakSaveBlock->method = NULL;
saveBlock->prevFrame = FP_FROM_SAVEAREA(breakSaveBlock);
saveBlock->savedPc = NULL; // not required
saveBlock->xtra.localRefCookie = self->jniLocalRefTable.segmentState.all;
saveBlock->method = method;
LOGVV("PUSH JNI frame: old=%p new=%p (size=%d)",
self->interpSave.curFrame, FP_FROM_SAVEAREA(saveBlock),
(u1*)self->interpSave.curFrame - (u1*)FP_FROM_SAVEAREA(saveBlock));
self->interpSave.curFrame = FP_FROM_SAVEAREA(saveBlock);
return true;
}
/*
* This is used by the JNI PushLocalFrame call. We push a new frame onto
* the stack that has no ins, outs, or locals, and no break frame above it.
* It's strictly used for tracking JNI local refs, and will be popped off
* by dvmPopFrame if it's not removed explicitly.
*/
bool dvmPushLocalFrame(Thread* self, const Method* method)
{
StackSaveArea* saveBlock;
int stackReq;
u1* stackPtr;
assert(dvmIsNativeMethod(method));
stackReq = sizeof(StackSaveArea); // regular frame
assert(self->interpSave.curFrame != NULL);
stackPtr = (u1*) SAVEAREA_FROM_FP(self->interpSave.curFrame);
if (stackPtr - stackReq < self->interpStackEnd) {
/* not enough space; let JNI throw the exception */
ALOGW("Stack overflow on PushLocal "
"(req=%d top=%p cur=%p size=%d '%s')",
stackReq, self->interpStackStart, self->interpSave.curFrame,
self->interpStackSize, method->name);
dvmHandleStackOverflow(self, method);
assert(dvmCheckException(self));
return false;
}
/*
* Shift the stack pointer down, leaving space for just the stack save
* area for the break frame, then shift down farther for the full frame.
*/
stackPtr -= sizeof(StackSaveArea);
saveBlock = (StackSaveArea*) stackPtr;
#if !defined(NDEBUG) && !defined(PAD_SAVE_AREA)
/* debug -- memset the new stack */
memset(stackPtr, 0xaf, stackReq);
#endif
#ifdef EASY_GDB
saveBlock->prevSave =
(StackSaveArea*)FP_FROM_SAVEAREA(self->interpSave.curFrame);
#endif
saveBlock->prevFrame = self->interpSave.curFrame;
saveBlock->savedPc = NULL; // not required
saveBlock->xtra.localRefCookie = self->jniLocalRefTable.segmentState.all;
saveBlock->method = method;
LOGVV("PUSH JNI local frame: old=%p new=%p (size=%d)",
self->interpSave.curFrame, FP_FROM_SAVEAREA(saveBlock),
(u1*)self->interpSave.curFrame - (u1*)FP_FROM_SAVEAREA(saveBlock));
self->interpSave.curFrame = FP_FROM_SAVEAREA(saveBlock);
return true;
}
/*
* Pop one frame pushed on by JNI PushLocalFrame.
*
* If we've gone too far, the previous frame is either a break frame or
* an interpreted frame. Either way, the method pointer won't match.
*/
bool dvmPopLocalFrame(Thread* self)
{
StackSaveArea* saveBlock = SAVEAREA_FROM_FP(self->interpSave.curFrame);
assert(!dvmIsBreakFrame((u4*)self->interpSave.curFrame));
if (saveBlock->method != SAVEAREA_FROM_FP(saveBlock->prevFrame)->method) {
/*
* The previous frame doesn't have the same method pointer -- we've
* been asked to pop too much.
*/
assert(dvmIsBreakFrame((u4*)saveBlock->prevFrame) ||
!dvmIsNativeMethod(
SAVEAREA_FROM_FP(saveBlock->prevFrame)->method));
return false;
}
LOGVV("POP JNI local frame: removing %s, now %s",
saveBlock->method->name,
SAVEAREA_FROM_FP(saveBlock->prevFrame)->method->name);
dvmPopJniLocals(self, saveBlock);
self->interpSave.curFrame = saveBlock->prevFrame;
return true;
}
/*
* Pop a frame we added. There should be one method frame and one break
* frame.
*
* If JNI Push/PopLocalFrame calls were mismatched, we might end up
* popping multiple method frames before we find the break.
*
* Returns "false" if there was no frame to pop.
*/
static bool dvmPopFrame(Thread* self)
{
StackSaveArea* saveBlock;
if (self->interpSave.curFrame == NULL)
return false;
saveBlock = SAVEAREA_FROM_FP(self->interpSave.curFrame);
assert(!dvmIsBreakFrame((u4*)self->interpSave.curFrame));
/*
* Remove everything up to the break frame. If this was a call into
* native code, pop the JNI local references table.
*/
while (saveBlock->prevFrame != NULL && saveBlock->method != NULL) {
/* probably a native->native JNI call */
if (dvmIsNativeMethod(saveBlock->method)) {
LOGVV("Popping JNI stack frame for %s.%s%s",
saveBlock->method->clazz->descriptor,
saveBlock->method->name,
(SAVEAREA_FROM_FP(saveBlock->prevFrame)->method == NULL) ?
"" : " (JNI local)");
dvmPopJniLocals(self, saveBlock);
}
saveBlock = SAVEAREA_FROM_FP(saveBlock->prevFrame);
}
if (saveBlock->method != NULL) {
ALOGE("PopFrame missed the break");
assert(false);
dvmAbort(); // stack trashed -- nowhere to go in this thread
}
LOGVV("POP frame: cur=%p new=%p",
self->interpSave.curFrame, saveBlock->prevFrame);
self->interpSave.curFrame = saveBlock->prevFrame;
return true;
}
/*
* Common code for dvmCallMethodV/A and dvmInvokeMethod.
*
* Pushes a call frame on, advancing self->interpSave.curFrame.
*/
static ClassObject* callPrep(Thread* self, const Method* method, Object* obj,
bool checkAccess)
{
ClassObject* clazz;
#ifndef NDEBUG
if (self->status != THREAD_RUNNING) {
ALOGW("threadid=%d: status=%d on call to %s.%s -",
self->threadId, self->status,
method->clazz->descriptor, method->name);
}
#endif
assert(self != NULL);
assert(method != NULL);
if (obj != NULL)
clazz = obj->clazz;
else
clazz = method->clazz;
IF_LOGVV() {
char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
LOGVV("thread=%d native code calling %s.%s %s", self->threadId,
clazz->descriptor, method->name, desc);
free(desc);
}
if (checkAccess) {
/* needed for java.lang.reflect.Method.invoke */
if (!dvmCheckMethodAccess(dvmGetCaller2Class(self->interpSave.curFrame),
method))
{
/* note this throws IAException, not IAError */
dvmThrowIllegalAccessException("access to method denied");
return NULL;
}
}
/*
* Push a call frame on. If there isn't enough room for ins, locals,
* outs, and the saved state, it will throw an exception.
*
* This updates self->interpSave.curFrame.
*/
if (dvmIsNativeMethod(method)) {
/* native code calling native code the hard way */
if (!dvmPushJNIFrame(self, method)) {
assert(dvmCheckException(self));
return NULL;
}
} else {
/* native code calling interpreted code */
if (!dvmPushInterpFrame(self, method)) {
assert(dvmCheckException(self));
return NULL;
}
}
return clazz;
}
/*
* Issue a method call.
*
* Pass in NULL for "obj" on calls to static methods.
*
* (Note this can't be inlined because it takes a variable number of args.)
*/
void dvmCallMethod(Thread* self, const Method* method, Object* obj,
JValue* pResult, ...)
{
va_list args;
va_start(args, pResult);
dvmCallMethodV(self, method, obj, false, pResult, args);
va_end(args);
}
/*
* Issue a method call with a variable number of arguments. We process
* the contents of "args" by scanning the method signature.
*
* Pass in NULL for "obj" on calls to static methods.
*
* We don't need to take the class as an argument because, in Dalvik,
* we don't need to worry about static synchronized methods.
*/
void dvmCallMethodV(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, va_list args)
{
const char* desc = &(method->shorty[1]); // [0] is the return type.
int verifyCount = 0;
ClassObject* clazz;
u4* ins;
clazz = callPrep(self, method, obj, false);
if (clazz == NULL)
return;
/* "ins" for new frame start at frame pointer plus locals */
ins = ((u4*)self->interpSave.curFrame) +
(method->registersSize - method->insSize);
//ALOGD(" FP is %p, INs live at >= %p", self->interpSave.curFrame, ins);
/* put "this" pointer into in0 if appropriate */
if (!dvmIsStaticMethod(method)) {
#ifdef WITH_EXTRA_OBJECT_VALIDATION
assert(obj != NULL && dvmIsHeapAddress(obj));
#endif
*ins++ = (u4) obj;
verifyCount++;
}
while (*desc != '\0') {
switch (*(desc++)) {
case 'D': case 'J': {
u8 val = va_arg(args, u8);
memcpy(ins, &val, 8); // EABI prevents direct store
ins += 2;
verifyCount += 2;
break;
}
case 'F': {
/* floats were normalized to doubles; convert back */
float f = (float) va_arg(args, double);
*ins++ = dvmFloatToU4(f);
verifyCount++;
break;
}
case 'L': { /* 'shorty' descr uses L for all refs, incl array */
void* arg = va_arg(args, void*);
assert(obj == NULL || dvmIsHeapAddress(obj));
jobject argObj = reinterpret_cast<jobject>(arg);
if (fromJni)
*ins++ = (u4) dvmDecodeIndirectRef(self, argObj);
else
*ins++ = (u4) argObj;
verifyCount++;
break;
}
default: {
/* Z B C S I -- all passed as 32-bit integers */
*ins++ = va_arg(args, u4);
verifyCount++;
break;
}
}
}
#ifndef NDEBUG
if (verifyCount != method->insSize) {
ALOGE("Got vfycount=%d insSize=%d for %s.%s", verifyCount,
method->insSize, clazz->descriptor, method->name);
assert(false);
goto bail;
}
#endif
//dvmDumpThreadStack(dvmThreadSelf());
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
#ifndef NDEBUG
bail:
#endif
dvmPopFrame(self);
}
/*
* Issue a method call with arguments provided in an array. We process
* the contents of "args" by scanning the method signature.
*
* The values were likely placed into an uninitialized jvalue array using
* the field specifiers, which means that sub-32-bit fields (e.g. short,
* boolean) may not have 32 or 64 bits of valid data. This is different
* from the varargs invocation where the C compiler does a widening
* conversion when calling a function. As a result, we have to be a
* little more precise when pulling stuff out.
*
* "args" may be NULL if the method has no arguments.
*/
void dvmCallMethodA(Thread* self, const Method* method, Object* obj,
bool fromJni, JValue* pResult, const jvalue* args)
{
const char* desc = &(method->shorty[1]); // [0] is the return type.
int verifyCount = 0;
ClassObject* clazz;
u4* ins;
clazz = callPrep(self, method, obj, false);
if (clazz == NULL)
return;
/* "ins" for new frame start at frame pointer plus locals */
ins = ((u4*)self->interpSave.curFrame) +
(method->registersSize - method->insSize);
/* put "this" pointer into in0 if appropriate */
if (!dvmIsStaticMethod(method)) {
assert(obj != NULL);
*ins++ = (u4) obj; /* obj is a "real" ref */
verifyCount++;
}
while (*desc != '\0') {
switch (*desc++) {
case 'D': /* 64-bit quantity; have to use */
case 'J': /* memcpy() in case of mis-alignment */
memcpy(ins, &args->j, 8);
ins += 2;
verifyCount++; /* this needs an extra push */
break;
case 'L': /* includes array refs */
if (fromJni)
*ins++ = (u4) dvmDecodeIndirectRef(self, args->l);
else
*ins++ = (u4) args->l;
break;
case 'F':
case 'I':
*ins++ = args->i; /* full 32 bits */
break;
case 'S':
*ins++ = args->s; /* 16 bits, sign-extended */
break;
case 'C':
*ins++ = args->c; /* 16 bits, unsigned */
break;
case 'B':
*ins++ = args->b; /* 8 bits, sign-extended */
break;
case 'Z':
*ins++ = args->z; /* 8 bits, zero or non-zero */
break;
default:
ALOGE("Invalid char %c in short signature of %s.%s",
*(desc-1), clazz->descriptor, method->name);
assert(false);
goto bail;
}
verifyCount++;
args++;
}
#ifndef NDEBUG
if (verifyCount != method->insSize) {
ALOGE("Got vfycount=%d insSize=%d for %s.%s", verifyCount,
method->insSize, clazz->descriptor, method->name);
assert(false);
goto bail;
}
#endif
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)((u4*)self->interpSave.curFrame, pResult,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, pResult);
}
bail:
dvmPopFrame(self);
}
static void throwArgumentTypeMismatch(int argIndex, ClassObject* expected, DataObject* arg) {
std::string expectedClassName(dvmHumanReadableDescriptor(expected->descriptor));
std::string actualClassName = dvmHumanReadableType(arg);
dvmThrowExceptionFmt(gDvm.exIllegalArgumentException, "argument %d should have type %s, got %s",
argIndex + 1, expectedClassName.c_str(), actualClassName.c_str());
}
/*
* Invoke a method, using the specified arguments and return type, through
* one of the reflection interfaces. Could be a virtual or direct method
* (including constructors). Used for reflection.
*
* Deals with boxing/unboxing primitives and performs widening conversions.
*
* "invokeObj" will be null for a static method.
*
* If the invocation returns with an exception raised, we have to wrap it.
*/
Object* dvmInvokeMethod(Object* obj, const Method* method,
ArrayObject* argList, ArrayObject* params, ClassObject* returnType,
bool noAccessCheck)
{
ClassObject* clazz;
Object* retObj = NULL;
Thread* self = dvmThreadSelf();
s4* ins;
int verifyCount, argListLength;
JValue retval;
bool needPop = false;
/* verify arg count */
if (argList != NULL)
argListLength = argList->length;
else
argListLength = 0;
if (argListLength != (int) params->length) {
dvmThrowExceptionFmt(gDvm.exIllegalArgumentException,
"wrong number of arguments; expected %d, got %d",
params->length, argListLength);
return NULL;
}
clazz = callPrep(self, method, obj, !noAccessCheck);
if (clazz == NULL)
return NULL;
needPop = true;
/* "ins" for new frame start at frame pointer plus locals */
ins = ((s4*)self->interpSave.curFrame) +
(method->registersSize - method->insSize);
verifyCount = 0;
//ALOGD(" FP is %p, INs live at >= %p", self->interpSave.curFrame, ins);
/* put "this" pointer into in0 if appropriate */
if (!dvmIsStaticMethod(method)) {
assert(obj != NULL);
*ins++ = (s4) obj;
verifyCount++;
}
/*
* Copy the args onto the stack. Primitive types are converted when
* necessary, and object types are verified.
*/
DataObject** args = (DataObject**)(void*)argList->contents;
ClassObject** types = (ClassObject**)(void*)params->contents;
for (int i = 0; i < argListLength; i++) {
int width = dvmConvertArgument(*args++, *types++, ins);
if (width < 0) {
dvmPopFrame(self); // throw wants to pull PC out of stack
needPop = false;
throwArgumentTypeMismatch(i, *(types-1), *(args-1));
goto bail;
}
ins += width;
verifyCount += width;
}
#ifndef NDEBUG
if (verifyCount != method->insSize) {
ALOGE("Got vfycount=%d insSize=%d for %s.%s", verifyCount,
method->insSize, clazz->descriptor, method->name);
assert(false);
goto bail;
}
#endif
if (dvmIsNativeMethod(method)) {
TRACE_METHOD_ENTER(self, method);
/*
* Because we leave no space for local variables, "curFrame" points
* directly at the method arguments.
*/
(*method->nativeFunc)((u4*)self->interpSave.curFrame, &retval,
method, self);
TRACE_METHOD_EXIT(self, method);
} else {
dvmInterpret(self, method, &retval);
}
/*
* Pop the frame immediately. The "wrap" calls below can cause
* allocations, and we don't want the GC to walk the now-dead frame.
*/
dvmPopFrame(self);
needPop = false;
/*
* If an exception is raised, wrap and replace. This is necessary
* because the invoked method could have thrown a checked exception
* that the caller wasn't prepared for.
*
* We might be able to do this up in the interpreted code, but that will
* leave us with a shortened stack trace in the top-level exception.
*/
if (dvmCheckException(self)) {
dvmWrapException("Ljava/lang/reflect/InvocationTargetException;");
} else {
/*
* If this isn't a void method or constructor, convert the return type
* to an appropriate object.
*
* We don't do this when an exception is raised because the value
* in "retval" is undefined.
*/
if (returnType != NULL) {
retObj = (Object*)dvmBoxPrimitive(retval, returnType);
dvmReleaseTrackedAlloc(retObj, NULL);
}
}
bail:
if (needPop) {
dvmPopFrame(self);
}
return retObj;
}
struct LineNumFromPcContext {
u4 address;
u4 lineNum;
};
static int lineNumForPcCb(void *cnxt, u4 address, u4 lineNum)
{
LineNumFromPcContext *pContext = (LineNumFromPcContext *)cnxt;
// We know that this callback will be called in
// ascending address order, so keep going until we find
// a match or we've just gone past it.
if (address > pContext->address) {
// The line number from the previous positions callback
// wil be the final result.
return 1;
}
pContext->lineNum = lineNum;
return (address == pContext->address) ? 1 : 0;
}
/*
* Determine the source file line number based on the program counter.
* "pc" is an offset, in 16-bit units, from the start of the method's code.
*
* Returns -1 if no match was found (possibly because the source files were
* compiled without "-g", so no line number information is present).
* Returns -2 for native methods (as expected in exception traces).
*/
int dvmLineNumFromPC(const Method* method, u4 relPc)
{
const DexCode* pDexCode = dvmGetMethodCode(method);
if (pDexCode == NULL) {
if (dvmIsNativeMethod(method) && !dvmIsAbstractMethod(method))
return -2;
return -1; /* can happen for abstract method stub */
}
LineNumFromPcContext context;
memset(&context, 0, sizeof(context));
context.address = relPc;
// A method with no line number info should return -1
context.lineNum = -1;
dexDecodeDebugInfo(method->clazz->pDvmDex->pDexFile, pDexCode,
method->clazz->descriptor,
method->prototype.protoIdx,
method->accessFlags,
lineNumForPcCb, NULL, &context);
return context.lineNum;
}
/*
* Compute the frame depth.
*
* Excludes "break" frames.
*/
int dvmComputeExactFrameDepth(const void* fp)
{
int count = 0;
for ( ; fp != NULL; fp = SAVEAREA_FROM_FP(fp)->prevFrame) {
if (!dvmIsBreakFrame((u4*)fp))
count++;
}
return count;
}
/*
* Compute the "vague" frame depth, which is just a pointer subtraction.
* The result is NOT an overly generous assessment of the number of
* frames; the only meaningful use is to compare against the result of
* an earlier invocation.
*
* Useful for implementing single-step debugger modes, which may need to
* call this for every instruction.
*/
int dvmComputeVagueFrameDepth(Thread* thread, const void* fp)
{
const u1* interpStackStart = thread->interpStackStart;
assert((u1*) fp >= interpStackStart - thread->interpStackSize);
assert((u1*) fp < interpStackStart);
return interpStackStart - (u1*) fp;
}
/*
* Get the calling frame. Pass in the current fp.
*
* Skip "break" frames and reflection invoke frames.
*/
void* dvmGetCallerFP(const void* curFrame)
{
void* caller = SAVEAREA_FROM_FP(curFrame)->prevFrame;
StackSaveArea* saveArea;
retry:
if (dvmIsBreakFrame((u4*)caller)) {
/* pop up one more */
caller = SAVEAREA_FROM_FP(caller)->prevFrame;
if (caller == NULL)
return NULL; /* hit the top */
/*
* If we got here by java.lang.reflect.Method.invoke(), we don't
* want to return Method's class loader. Shift up one and try
* again.
*/
saveArea = SAVEAREA_FROM_FP(caller);
if (dvmIsReflectionMethod(saveArea->method)) {
caller = saveArea->prevFrame;
assert(caller != NULL);
goto retry;
}
}
return caller;
}
/*
* Get the caller's class. Pass in the current fp.
*
* This is used by e.g. java.lang.Class.
*/
ClassObject* dvmGetCallerClass(const void* curFrame)
{
void* caller;
caller = dvmGetCallerFP(curFrame);
if (caller == NULL)
return NULL;
return SAVEAREA_FROM_FP(caller)->method->clazz;
}
/*
* Get the caller's caller's class. Pass in the current fp.
*
* This is used by e.g. java.lang.Class, which wants to know about the
* class loader of the method that called it.
*/
ClassObject* dvmGetCaller2Class(const void* curFrame)
{
void* caller = SAVEAREA_FROM_FP(curFrame)->prevFrame;
void* callerCaller;
/* at the top? */
if (dvmIsBreakFrame((u4*)caller) &&
SAVEAREA_FROM_FP(caller)->prevFrame == NULL)
return NULL;
/* go one more */
callerCaller = dvmGetCallerFP(caller);
if (callerCaller == NULL)
return NULL;
return SAVEAREA_FROM_FP(callerCaller)->method->clazz;
}
/*
* Get the caller's caller's caller's class. Pass in the current fp.
*
* This is used by e.g. java.lang.Class, which wants to know about the
* class loader of the method that called it.
*/
ClassObject* dvmGetCaller3Class(const void* curFrame)
{
void* caller = SAVEAREA_FROM_FP(curFrame)->prevFrame;
int i;
/* at the top? */
if (dvmIsBreakFrame((u4*)caller) &&
SAVEAREA_FROM_FP(caller)->prevFrame == NULL)
return NULL;
/* Walk up two frames if possible. */
for (i = 0; i < 2; i++) {
caller = dvmGetCallerFP(caller);
if (caller == NULL)
return NULL;
}
return SAVEAREA_FROM_FP(caller)->method->clazz;
}
/*
* Fill a flat array of methods that comprise the current interpreter
* stack trace. Pass in the current frame ptr. Break frames are
* skipped, but reflection invocations are not.
*
* The current frame will be in element 0.
*/
void dvmFillStackTraceArray(const void* fp, const Method** array, size_t length)
{
assert(fp != NULL);
assert(array != NULL);
size_t i = 0;
while (fp != NULL) {
if (!dvmIsBreakFrame((u4*)fp)) {
assert(i < length);
array[i++] = SAVEAREA_FROM_FP(fp)->method;
}
fp = SAVEAREA_FROM_FP(fp)->prevFrame;
}
}
/*
* 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.
*/
ALOGE("DalvikVM: double-overflow of stack in threadid=%d; aborting",
self->threadId);
dvmDumpThread(self, false);
dvmAbort();
}
/* open it up to the full range */
ALOGI("threadid=%d: stack overflow on call to %s.%s:%s",
self->threadId,
method->clazz->descriptor, method->name, method->shorty);
StackSaveArea* saveArea = SAVEAREA_FROM_FP(self->interpSave.curFrame);
ALOGI(" method requires %d+%d+%d=%d bytes, fp is %p (%d left)",
method->registersSize * 4, sizeof(StackSaveArea), method->outsSize * 4,
(method->registersSize + method->outsSize) * 4 + sizeof(StackSaveArea),
saveArea, (u1*) saveArea - self->interpStackEnd);
ALOGI(" expanding stack end (%p to %p)", 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) {
ALOGW("Stack overflow while throwing exception");
dvmClearException(self);
}
dvmThrowChainedException(gDvm.exStackOverflowError, 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);
}
/*
* Extract the object that is the target of a monitor-enter instruction
* in the top stack frame of "thread".
*
* The other thread might be alive, so this has to work carefully.
*
* The thread list lock must be held.
*
* Returns "true" if we successfully recover the object. "*pOwner" will
* be NULL if we can't determine the owner for some reason (e.g. race
* condition on ownership transfer).
*/
static bool extractMonitorEnterObject(Thread* thread, Object** pLockObj,
Thread** pOwner)
{
void* framePtr = thread->interpSave.curFrame;
if (framePtr == NULL || dvmIsBreakFrame((u4*)framePtr))
return false;
const StackSaveArea* saveArea = SAVEAREA_FROM_FP(framePtr);
const Method* method = saveArea->method;
const u2* currentPc = saveArea->xtra.currentPc;
/* check Method* */
if (!dvmLinearAllocContains(method, sizeof(Method))) {
ALOGD("ExtrMon: method %p not valid", method);
return false;
}
/* check currentPc */
u4 insnsSize = dvmGetMethodInsnsSize(method);
if (currentPc < method->insns ||
currentPc >= method->insns + insnsSize)
{
ALOGD("ExtrMon: insns %p not valid (%p - %p)",
currentPc, method->insns, method->insns + insnsSize);
return false;
}
/* check the instruction */
if ((*currentPc & 0xff) != OP_MONITOR_ENTER) {
ALOGD("ExtrMon: insn at %p is not monitor-enter (0x%02x)",
currentPc, *currentPc & 0xff);
return false;
}
/* get and check the register index */
unsigned int reg = *currentPc >> 8;
if (reg >= method->registersSize) {
ALOGD("ExtrMon: invalid register %d (max %d)",
reg, method->registersSize);
return false;
}
/* get and check the object in that register */
u4* fp = (u4*) framePtr;
Object* obj = (Object*) fp[reg];
if (obj != NULL && !dvmIsHeapAddress(obj)) {
ALOGD("ExtrMon: invalid object %p at %p[%d]", obj, fp, reg);
return false;
}
*pLockObj = obj;
/*
* Try to determine the object's lock holder; it's okay if this fails.
*
* We're assuming the thread list lock is already held by this thread.
* If it's not, we may be living dangerously if we have to scan through
* the thread list to find a match. (The VM will generally be in a
* suspended state when executing here, so this is a minor concern
* unless we're dumping while threads are running, in which case there's
* a good chance of stuff blowing up anyway.)
*/
*pOwner = dvmGetObjectLockHolder(obj);
return true;
}
static void printWaitMessage(const DebugOutputTarget* target, const char* detail, Object* obj,
Thread* thread)
{
std::string msg(StringPrintf(" - waiting %s <%p> ", detail, obj));
if (obj->clazz != gDvm.classJavaLangClass) {
// I(16573) - waiting on <0xf5feda38> (a java.util.LinkedList)
// I(16573) - waiting on <0xf5ed54f8> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
msg += "(a " + dvmHumanReadableType(obj) + ")";
}
if (thread != NULL) {
std::string threadName(dvmGetThreadName(thread));
StringAppendF(&msg, " held by tid=%d (%s)", thread->threadId, threadName.c_str());
}
dvmPrintDebugMessage(target, "%s\n", msg.c_str());
}
/*
* Dump stack frames, starting from the specified frame and moving down.
*
* Each frame holds a pointer to the currently executing method, and the
* saved program counter from the caller ("previous" frame). This means
* we don't have the PC for the current method on the stack, which is
* pretty reasonable since it's in the "PC register" for the VM. Because
* exceptions need to show the correct line number we actually *do* have
* an updated version in the fame's "xtra.currentPc", but it's unreliable.
*
* Note "framePtr" could be NULL in rare circumstances.
*/
static void dumpFrames(const DebugOutputTarget* target, void* framePtr,
Thread* thread)
{
const StackSaveArea* saveArea;
const Method* method;
int checkCount = 0;
const u2* currentPc = NULL;
bool first = true;
/*
* We call functions that require us to be holding the thread list lock.
* It's probable that the caller has already done so, but it's not
* guaranteed. If it's not locked, lock it now.
*/
bool needThreadUnlock = dvmTryLockThreadList();
/*
* The "currentPc" is updated whenever we execute an instruction that
* might throw an exception. Show it here.
*/
if (framePtr != NULL && !dvmIsBreakFrame((u4*)framePtr)) {
saveArea = SAVEAREA_FROM_FP(framePtr);
if (saveArea->xtra.currentPc != NULL)
currentPc = saveArea->xtra.currentPc;
}
while (framePtr != NULL) {
saveArea = SAVEAREA_FROM_FP(framePtr);
method = saveArea->method;
if (dvmIsBreakFrame((u4*)framePtr)) {
//dvmPrintDebugMessage(target, " (break frame)\n");
} else {
int relPc;
if (currentPc != NULL)
relPc = currentPc - saveArea->method->insns;
else
relPc = -1;
std::string methodName(dvmHumanReadableMethod(method, false));
if (dvmIsNativeMethod(method)) {
dvmPrintDebugMessage(target, " at %s(Native Method)\n",
methodName.c_str());
} else {
dvmPrintDebugMessage(target, " at %s(%s:%s%d)\n",
methodName.c_str(), dvmGetMethodSourceFile(method),
(relPc >= 0 && first) ? "~" : "",
relPc < 0 ? -1 : dvmLineNumFromPC(method, relPc));
}
if (first) {
/*
* Decorate WAIT and MONITOR threads with some detail on
* the first frame.
*
* warning: wait status not stable, even in suspend
*/
if (thread->status == THREAD_WAIT ||
thread->status == THREAD_TIMED_WAIT)
{
Monitor* mon = thread->waitMonitor;
Object* obj = dvmGetMonitorObject(mon);
if (obj != NULL) {
Thread* joinThread = NULL;
if (obj->clazz == gDvm.classJavaLangVMThread) {
joinThread = dvmGetThreadFromThreadObject(obj);
}
printWaitMessage(target, "on", obj, joinThread);
}
} else if (thread->status == THREAD_MONITOR) {
Object* obj;
Thread* owner;
if (extractMonitorEnterObject(thread, &obj, &owner)) {
printWaitMessage(target, "to lock", obj, owner);
}
}
}
}
/*
* Get saved PC for previous frame. There's no savedPc in a "break"
* frame, because that represents native or interpreted code
* invoked by the VM. The saved PC is sitting in the "PC register",
* a local variable on the native stack.
*/
currentPc = saveArea->savedPc;
first = false;
if (saveArea->prevFrame != NULL && saveArea->prevFrame <= framePtr) {
ALOGW("Warning: loop in stack trace at frame %d (%p -> %p)",
checkCount, framePtr, saveArea->prevFrame);
break;
}
framePtr = saveArea->prevFrame;
checkCount++;
if (checkCount > 300) {
dvmPrintDebugMessage(target,
" ***** printed %d frames, not showing any more\n",
checkCount);
break;
}
}
if (needThreadUnlock) {
dvmUnlockThreadList();
}
}
/*
* Dump the stack for the specified thread.
*/
void dvmDumpThreadStack(const DebugOutputTarget* target, Thread* thread)
{
dumpFrames(target, thread->interpSave.curFrame, thread);
}
/*
* Dump the stack for the specified thread, which is still running.
*
* This is very dangerous, because stack frames are being pushed on and
* popped off, and if the thread exits we'll be looking at freed memory.
* The plan here is to take a snapshot of the stack and then dump that
* to try to minimize the chances of catching it mid-update. This should
* work reasonably well on a single-CPU system.
*
* There is a small chance that calling here will crash the VM.
*/
void dvmDumpRunningThreadStack(const DebugOutputTarget* target, Thread* thread)
{
StackSaveArea* saveArea;
const u1* origStack;
u1* stackCopy = NULL;
int origSize, fpOffset;
void* fp;
int depthLimit = 200;
if (thread == NULL || thread->interpSave.curFrame == NULL) {
dvmPrintDebugMessage(target,
"DumpRunning: Thread at %p has no curFrame (threadid=%d)\n",
thread, (thread != NULL) ? thread->threadId : 0);
return;
}
/* wait for a full quantum */
sched_yield();
/* copy the info we need, then the stack itself */
origSize = thread->interpStackSize;
origStack = (const u1*) thread->interpStackStart - origSize;
stackCopy = (u1*) malloc(origSize);
fpOffset = (u1*) thread->interpSave.curFrame - origStack;
memcpy(stackCopy, origStack, origSize);
/*
* Run through the stack and rewrite the "prev" pointers.
*/
//ALOGI("DR: fpOff=%d (from %p %p)",fpOffset, origStack,
// thread->interpSave.curFrame);
fp = stackCopy + fpOffset;
while (true) {
int prevOffset;
if (depthLimit-- < 0) {
/* we're probably screwed */
dvmPrintDebugMessage(target, "DumpRunning: depth limit hit\n");
dvmAbort();
}
saveArea = SAVEAREA_FROM_FP(fp);
if (saveArea->prevFrame == NULL)
break;
prevOffset = (u1*) saveArea->prevFrame - origStack;
if (prevOffset < 0 || prevOffset > origSize) {
dvmPrintDebugMessage(target,
"DumpRunning: bad offset found: %d (from %p %p)\n",
prevOffset, origStack, saveArea->prevFrame);
saveArea->prevFrame = NULL;
break;
}
saveArea->prevFrame = (u4*)(stackCopy + prevOffset);
fp = saveArea->prevFrame;
}
/*
* We still need to pass the Thread for some monitor wait stuff.
*/
dumpFrames(target, stackCopy + fpOffset, thread);
free(stackCopy);
}
/*
* Dump the native stack for the specified thread.
*/
void dvmDumpNativeStack(const DebugOutputTarget* target, pid_t tid)
{
#ifdef HAVE_ANDROID_OS
const size_t MAX_DEPTH = 32;
backtrace_frame_t backtrace[MAX_DEPTH];
ssize_t frames = unwind_backtrace_thread(tid, backtrace, 0, MAX_DEPTH);
if (frames > 0) {
backtrace_symbol_t backtrace_symbols[MAX_DEPTH];
get_backtrace_symbols(backtrace, frames, backtrace_symbols);
for (size_t i = 0; i < size_t(frames); i++) {
char line[MAX_BACKTRACE_LINE_LENGTH];
format_backtrace_line(i, &backtrace[i], &backtrace_symbols[i],
line, MAX_BACKTRACE_LINE_LENGTH);
dvmPrintDebugMessage(target, " %s\n", line);
}
free_backtrace_symbols(backtrace_symbols, frames);
}
#endif
}