Home | History | Annotate | Download | only in vm
      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  * Exception handling.
     18  */
     19 #include "Dalvik.h"
     20 #include "libdex/DexCatch.h"
     21 
     22 #include <stdlib.h>
     23 
     24 /*
     25 Notes on Exception Handling
     26 
     27 We have one fairly sticky issue to deal with: creating the exception stack
     28 trace.  The trouble is that we need the current value of the program
     29 counter for the method now being executed, but that's only held in a local
     30 variable or hardware register in the main interpreter loop.
     31 
     32 The exception mechanism requires that the current stack trace be associated
     33 with a Throwable at the time the Throwable is constructed.  The construction
     34 may or may not be associated with a throw.  We have three situations to
     35 consider:
     36 
     37  (1) A Throwable is created with a "new Throwable" statement in the
     38      application code, for immediate or deferred use with a "throw" statement.
     39  (2) The VM throws an exception from within the interpreter core, e.g.
     40      after an integer divide-by-zero.
     41  (3) The VM throws an exception from somewhere deeper down, e.g. while
     42      trying to link a class.
     43 
     44 We need to have the current value for the PC, which means that for
     45 situation (3) the interpreter loop must copy it to an externally-accessible
     46 location before handling any opcode that could cause the VM to throw
     47 an exception.  We can't store it globally, because the various threads
     48 would trample each other.  We can't store it in the Thread structure,
     49 because it'll get overwritten as soon as the Throwable constructor starts
     50 executing.  It needs to go on the stack, but our stack frames hold the
     51 caller's *saved* PC, not the current PC.
     52 
     53 Situation #1 doesn't require special handling.  Situation #2 could be dealt
     54 with by passing the PC into the exception creation function.  The trick
     55 is to solve situation #3 in a way that adds minimal overhead to common
     56 operations.  Making it more costly to throw an exception is acceptable.
     57 
     58 There are a few ways to deal with this:
     59 
     60  (a) Change "savedPc" to "currentPc" in the stack frame.  All of the
     61      stack logic gets offset by one frame.  The current PC is written
     62      to the current stack frame when necessary.
     63  (b) Write the current PC into the current stack frame, but without
     64      replacing "savedPc".  The JNI local refs pointer, which is only
     65      used for native code, can be overloaded to save space.
     66  (c) In dvmThrowException(), push an extra stack frame on, with the
     67      current PC in it.  The current PC is written into the Thread struct
     68      when necessary, and copied out when the VM throws.
     69  (d) Before doing something that might throw an exception, push a
     70      temporary frame on with the saved PC in it.
     71 
     72 Solution (a) is the simplest, but breaks Dalvik's goal of mingling native
     73 and interpreted stacks.
     74 
     75 Solution (b) retains the simplicity of (a) without rearranging the stack,
     76 but now in some cases we're storing the PC twice, which feels wrong.
     77 
     78 Solution (c) usually works, because we push the saved PC onto the stack
     79 before the Throwable construction can overwrite the copy in Thread.  One
     80 way solution (c) could break is:
     81  - Interpreter saves the PC
     82  - Execute some bytecode, which runs successfully (and alters the saved PC)
     83  - Throw an exception before re-saving the PC (i.e in the same opcode)
     84 This is a risk for anything that could cause <clinit> to execute, e.g.
     85 executing a static method or accessing a static field.  Attemping to access
     86 a field that doesn't exist in a class that does exist might cause this.
     87 It may be possible to simply bracket the dvmCallMethod*() functions to
     88 save/restore it.
     89 
     90 Solution (d) incurs additional overhead, but may have other benefits (e.g.
     91 it's easy to find the stack frames that should be removed before storage
     92 in the Throwable).
     93 
     94 Current plan is option (b), because it's simple, fast, and doesn't change
     95 the way the stack works.
     96 */
     97 
     98 /* fwd */
     99 static bool initException(Object* exception, const char* msg, Object* cause,
    100     Thread* self);
    101 
    102 
    103 /*
    104  * Cache pointers to some of the exception classes we use locally.
    105  *
    106  * Note this is NOT called during dexopt optimization.  Some of the fields
    107  * are initialized by the verifier (dvmVerifyCodeFlow).
    108  */
    109 bool dvmExceptionStartup(void)
    110 {
    111     gDvm.classJavaLangThrowable =
    112         dvmFindSystemClassNoInit("Ljava/lang/Throwable;");
    113     gDvm.classJavaLangRuntimeException =
    114         dvmFindSystemClassNoInit("Ljava/lang/RuntimeException;");
    115     gDvm.classJavaLangStackOverflowError =
    116         dvmFindSystemClassNoInit("Ljava/lang/StackOverflowError;");
    117     gDvm.classJavaLangError =
    118         dvmFindSystemClassNoInit("Ljava/lang/Error;");
    119     gDvm.classJavaLangStackTraceElement =
    120         dvmFindSystemClassNoInit("Ljava/lang/StackTraceElement;");
    121     gDvm.classJavaLangStackTraceElementArray =
    122         dvmFindArrayClass("[Ljava/lang/StackTraceElement;", NULL);
    123     if (gDvm.classJavaLangThrowable == NULL ||
    124         gDvm.classJavaLangStackTraceElement == NULL ||
    125         gDvm.classJavaLangStackTraceElementArray == NULL)
    126     {
    127         LOGE("Could not find one or more essential exception classes\n");
    128         return false;
    129     }
    130 
    131     /*
    132      * Find the constructor.  Note that, unlike other saved method lookups,
    133      * we're using a Method* instead of a vtable offset.  This is because
    134      * constructors don't have vtable offsets.  (Also, since we're creating
    135      * the object in question, it's impossible for anyone to sub-class it.)
    136      */
    137     Method* meth;
    138     meth = dvmFindDirectMethodByDescriptor(gDvm.classJavaLangStackTraceElement,
    139         "<init>", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
    140     if (meth == NULL) {
    141         LOGE("Unable to find constructor for StackTraceElement\n");
    142         return false;
    143     }
    144     gDvm.methJavaLangStackTraceElement_init = meth;
    145 
    146     /* grab an offset for the stackData field */
    147     gDvm.offJavaLangThrowable_stackState =
    148         dvmFindFieldOffset(gDvm.classJavaLangThrowable,
    149             "stackState", "Ljava/lang/Object;");
    150     if (gDvm.offJavaLangThrowable_stackState < 0) {
    151         LOGE("Unable to find Throwable.stackState\n");
    152         return false;
    153     }
    154 
    155     /* and one for the message field, in case we want to show it */
    156     gDvm.offJavaLangThrowable_message =
    157         dvmFindFieldOffset(gDvm.classJavaLangThrowable,
    158             "detailMessage", "Ljava/lang/String;");
    159     if (gDvm.offJavaLangThrowable_message < 0) {
    160         LOGE("Unable to find Throwable.detailMessage\n");
    161         return false;
    162     }
    163 
    164     /* and one for the cause field, just 'cause */
    165     gDvm.offJavaLangThrowable_cause =
    166         dvmFindFieldOffset(gDvm.classJavaLangThrowable,
    167             "cause", "Ljava/lang/Throwable;");
    168     if (gDvm.offJavaLangThrowable_cause < 0) {
    169         LOGE("Unable to find Throwable.cause\n");
    170         return false;
    171     }
    172 
    173     return true;
    174 }
    175 
    176 /*
    177  * Clean up.
    178  */
    179 void dvmExceptionShutdown(void)
    180 {
    181     // nothing to do
    182 }
    183 
    184 
    185 /*
    186  * Format the message into a small buffer and pass it along.
    187  */
    188 void dvmThrowExceptionFmtV(const char* exceptionDescriptor, const char* fmt,
    189     va_list args)
    190 {
    191     char msgBuf[512];
    192 
    193     vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
    194     dvmThrowChainedException(exceptionDescriptor, msgBuf, NULL);
    195 }
    196 
    197 /*
    198  * Create a Throwable and throw an exception in the current thread (where
    199  * "throwing" just means "set the thread's exception pointer").
    200  *
    201  * "msg" and/or "cause" may be NULL.
    202  *
    203  * If we have a bad exception hierarchy -- something in Throwable.<init>
    204  * is missing -- then every attempt to throw an exception will result
    205  * in another exception.  Exceptions are generally allowed to "chain"
    206  * to other exceptions, so it's hard to auto-detect this problem.  It can
    207  * only happen if the system classes are broken, so it's probably not
    208  * worth spending cycles to detect it.
    209  *
    210  * We do have one case to worry about: if the classpath is completely
    211  * wrong, we'll go into a death spin during startup because we can't find
    212  * the initial class and then we can't find NoClassDefFoundError.  We have
    213  * to handle this case.
    214  *
    215  * [Do we want to cache pointers to common exception classes?]
    216  */
    217 void dvmThrowChainedException(const char* exceptionDescriptor, const char* msg,
    218     Object* cause)
    219 {
    220     ClassObject* excepClass;
    221 
    222     LOGV("THROW '%s' msg='%s' cause=%s\n",
    223         exceptionDescriptor, msg,
    224         (cause != NULL) ? cause->clazz->descriptor : "(none)");
    225 
    226     if (gDvm.initializing) {
    227         if (++gDvm.initExceptionCount >= 2) {
    228             LOGE("Too many exceptions during init (failed on '%s' '%s')\n",
    229                 exceptionDescriptor, msg);
    230             dvmAbort();
    231         }
    232     }
    233 
    234     excepClass = dvmFindSystemClass(exceptionDescriptor);
    235     if (excepClass == NULL) {
    236         /*
    237          * We couldn't find the exception class.  The attempt to find a
    238          * nonexistent class should have raised an exception.  If no
    239          * exception is currently raised, then we're pretty clearly unable
    240          * to throw ANY sort of exception, and we need to pack it in.
    241          *
    242          * If we were able to throw the "class load failed" exception,
    243          * stick with that.  Ideally we'd stuff the original exception
    244          * into the "cause" field, but since we can't find it we can't
    245          * do that.  The exception class name should be in the "message"
    246          * field.
    247          */
    248         if (!dvmCheckException(dvmThreadSelf())) {
    249             LOGE("FATAL: unable to throw exception (failed on '%s' '%s')\n",
    250                 exceptionDescriptor, msg);
    251             dvmAbort();
    252         }
    253         return;
    254     }
    255 
    256     dvmThrowChainedExceptionByClass(excepClass, msg, cause);
    257 }
    258 
    259 /*
    260  * Start/continue throwing process now that we have a class reference.
    261  */
    262 void dvmThrowChainedExceptionByClass(ClassObject* excepClass, const char* msg,
    263     Object* cause)
    264 {
    265     Thread* self = dvmThreadSelf();
    266     Object* exception;
    267 
    268     /* make sure the exception is initialized */
    269     if (!dvmIsClassInitialized(excepClass) && !dvmInitClass(excepClass)) {
    270         LOGE("ERROR: unable to initialize exception class '%s'\n",
    271             excepClass->descriptor);
    272         if (strcmp(excepClass->descriptor, "Ljava/lang/InternalError;") == 0)
    273             dvmAbort();
    274         dvmThrowChainedException("Ljava/lang/InternalError;",
    275             "failed to init original exception class", cause);
    276         return;
    277     }
    278 
    279     exception = dvmAllocObject(excepClass, ALLOC_DEFAULT);
    280     if (exception == NULL) {
    281         /*
    282          * We're in a lot of trouble.  We might be in the process of
    283          * throwing an out-of-memory exception, in which case the
    284          * pre-allocated object will have been thrown when our object alloc
    285          * failed.  So long as there's an exception raised, return and
    286          * allow the system to try to recover.  If not, something is broken
    287          * and we need to bail out.
    288          */
    289         if (dvmCheckException(self))
    290             goto bail;
    291         LOGE("FATAL: unable to allocate exception '%s' '%s'\n",
    292             excepClass->descriptor, msg != NULL ? msg : "(no msg)");
    293         dvmAbort();
    294     }
    295 
    296     /*
    297      * Init the exception.
    298      */
    299     if (gDvm.optimizing) {
    300         /* need the exception object, but can't invoke interpreted code */
    301         LOGV("Skipping init of exception %s '%s'\n",
    302             excepClass->descriptor, msg);
    303     } else {
    304         assert(excepClass == exception->clazz);
    305         if (!initException(exception, msg, cause, self)) {
    306             /*
    307              * Whoops.  If we can't initialize the exception, we can't use
    308              * it.  If there's an exception already set, the constructor
    309              * probably threw an OutOfMemoryError.
    310              */
    311             if (!dvmCheckException(self)) {
    312                 /*
    313                  * We're required to throw something, so we just
    314                  * throw the pre-constructed internal error.
    315                  */
    316                 self->exception = gDvm.internalErrorObj;
    317             }
    318             goto bail;
    319         }
    320     }
    321 
    322     self->exception = exception;
    323 
    324 bail:
    325     dvmReleaseTrackedAlloc(exception, self);
    326 }
    327 
    328 /*
    329  * Throw the named exception using the dotted form of the class
    330  * descriptor as the exception message, and with the specified cause.
    331  */
    332 void dvmThrowChainedExceptionWithClassMessage(const char* exceptionDescriptor,
    333     const char* messageDescriptor, Object* cause)
    334 {
    335     char* message = dvmDescriptorToDot(messageDescriptor);
    336 
    337     dvmThrowChainedException(exceptionDescriptor, message, cause);
    338     free(message);
    339 }
    340 
    341 /*
    342  * Like dvmThrowExceptionWithMessageFromDescriptor, but take a
    343  * class object instead of a name.
    344  */
    345 void dvmThrowExceptionByClassWithClassMessage(ClassObject* exceptionClass,
    346     const char* messageDescriptor)
    347 {
    348     char* message = dvmDescriptorToName(messageDescriptor);
    349 
    350     dvmThrowExceptionByClass(exceptionClass, message);
    351     free(message);
    352 }
    353 
    354 /*
    355  * Find and return an exception constructor method that can take the
    356  * indicated parameters, or return NULL if no such constructor exists.
    357  */
    358 static Method* findExceptionInitMethod(ClassObject* excepClass,
    359     bool hasMessage, bool hasCause)
    360 {
    361     if (hasMessage) {
    362         Method* result;
    363 
    364         if (hasCause) {
    365             result = dvmFindDirectMethodByDescriptor(
    366                     excepClass, "<init>",
    367                     "(Ljava/lang/String;Ljava/lang/Throwable;)V");
    368         } else {
    369             result = dvmFindDirectMethodByDescriptor(
    370                     excepClass, "<init>", "(Ljava/lang/String;)V");
    371         }
    372 
    373         if (result != NULL) {
    374             return result;
    375         }
    376 
    377         if (hasCause) {
    378             return dvmFindDirectMethodByDescriptor(
    379                     excepClass, "<init>",
    380                     "(Ljava/lang/Object;Ljava/lang/Throwable;)V");
    381         } else {
    382             return dvmFindDirectMethodByDescriptor(
    383                     excepClass, "<init>", "(Ljava/lang/Object;)V");
    384         }
    385     } else if (hasCause) {
    386         return dvmFindDirectMethodByDescriptor(
    387                 excepClass, "<init>", "(Ljava/lang/Throwable;)V");
    388     } else {
    389         return dvmFindDirectMethodByDescriptor(excepClass, "<init>", "()V");
    390     }
    391 }
    392 
    393 /*
    394  * Initialize an exception with an appropriate constructor.
    395  *
    396  * "exception" is the exception object to initialize.
    397  * Either or both of "msg" and "cause" may be null.
    398  * "self" is dvmThreadSelf(), passed in so we don't have to look it up again.
    399  *
    400  * If the process of initializing the exception causes another
    401  * exception (e.g., OutOfMemoryError) to be thrown, return an error
    402  * and leave self->exception intact.
    403  */
    404 static bool initException(Object* exception, const char* msg, Object* cause,
    405     Thread* self)
    406 {
    407     enum {
    408         kInitUnknown,
    409         kInitNoarg,
    410         kInitMsg,
    411         kInitMsgThrow,
    412         kInitThrow
    413     } initKind = kInitUnknown;
    414     Method* initMethod = NULL;
    415     ClassObject* excepClass = exception->clazz;
    416     StringObject* msgStr = NULL;
    417     bool result = false;
    418     bool needInitCause = false;
    419 
    420     assert(self != NULL);
    421     assert(self->exception == NULL);
    422 
    423     /* if we have a message, create a String */
    424     if (msg == NULL)
    425         msgStr = NULL;
    426     else {
    427         msgStr = dvmCreateStringFromCstr(msg, ALLOC_DEFAULT);
    428         if (msgStr == NULL) {
    429             LOGW("Could not allocate message string \"%s\" while "
    430                     "throwing internal exception (%s)\n",
    431                     msg, excepClass->descriptor);
    432             goto bail;
    433         }
    434     }
    435 
    436     if (cause != NULL) {
    437         if (!dvmInstanceof(cause->clazz, gDvm.classJavaLangThrowable)) {
    438             LOGE("Tried to init exception with cause '%s'\n",
    439                 cause->clazz->descriptor);
    440             dvmAbort();
    441         }
    442     }
    443 
    444     /*
    445      * The Throwable class has four public constructors:
    446      *  (1) Throwable()
    447      *  (2) Throwable(String message)
    448      *  (3) Throwable(String message, Throwable cause)  (added in 1.4)
    449      *  (4) Throwable(Throwable cause)                  (added in 1.4)
    450      *
    451      * The first two are part of the original design, and most exception
    452      * classes should support them.  The third prototype was used by
    453      * individual exceptions. e.g. ClassNotFoundException added it in 1.2.
    454      * The general "cause" mechanism was added in 1.4.  Some classes,
    455      * such as IllegalArgumentException, initially supported the first
    456      * two, but added the second two in a later release.
    457      *
    458      * Exceptions may be picky about how their "cause" field is initialized.
    459      * If you call ClassNotFoundException(String), it may choose to
    460      * initialize its "cause" field to null.  Doing so prevents future
    461      * calls to Throwable.initCause().
    462      *
    463      * So, if "cause" is not NULL, we need to look for a constructor that
    464      * takes a throwable.  If we can't find one, we fall back on calling
    465      * #1/#2 and making a separate call to initCause().  Passing a null ref
    466      * for "message" into Throwable(String, Throwable) is allowed, but we
    467      * prefer to use the Throwable-only version because it has different
    468      * behavior.
    469      *
    470      * java.lang.TypeNotPresentException is a strange case -- it has #3 but
    471      * not #2.  (Some might argue that the constructor is actually not #3,
    472      * because it doesn't take the message string as an argument, but it
    473      * has the same effect and we can work with it here.)
    474      *
    475      * java.lang.AssertionError is also a strange case -- it has a
    476      * constructor that takes an Object, but not one that takes a String.
    477      * There may be other cases like this, as well, so we generally look
    478      * for an Object-taking constructor if we can't find one that takes
    479      * a String.
    480      */
    481     if (cause == NULL) {
    482         if (msgStr == NULL) {
    483             initMethod = findExceptionInitMethod(excepClass, false, false);
    484             initKind = kInitNoarg;
    485         } else {
    486             initMethod = findExceptionInitMethod(excepClass, true, false);
    487             if (initMethod != NULL) {
    488                 initKind = kInitMsg;
    489             } else {
    490                 /* no #2, try #3 */
    491                 initMethod = findExceptionInitMethod(excepClass, true, true);
    492                 if (initMethod != NULL) {
    493                     initKind = kInitMsgThrow;
    494                 }
    495             }
    496         }
    497     } else {
    498         if (msgStr == NULL) {
    499             initMethod = findExceptionInitMethod(excepClass, false, true);
    500             if (initMethod != NULL) {
    501                 initKind = kInitThrow;
    502             } else {
    503                 initMethod = findExceptionInitMethod(excepClass, false, false);
    504                 initKind = kInitNoarg;
    505                 needInitCause = true;
    506             }
    507         } else {
    508             initMethod = findExceptionInitMethod(excepClass, true, true);
    509             if (initMethod != NULL) {
    510                 initKind = kInitMsgThrow;
    511             } else {
    512                 initMethod = findExceptionInitMethod(excepClass, true, false);
    513                 initKind = kInitMsg;
    514                 needInitCause = true;
    515             }
    516         }
    517     }
    518 
    519     if (initMethod == NULL) {
    520         /*
    521          * We can't find the desired constructor.  This can happen if a
    522          * subclass of java/lang/Throwable doesn't define an expected
    523          * constructor, e.g. it doesn't provide one that takes a string
    524          * when a message has been provided.
    525          */
    526         LOGW("WARNING: exception class '%s' missing constructor "
    527             "(msg='%s' kind=%d)\n",
    528             excepClass->descriptor, msg, initKind);
    529         assert(strcmp(excepClass->descriptor,
    530                       "Ljava/lang/RuntimeException;") != 0);
    531         dvmThrowChainedException("Ljava/lang/RuntimeException;",
    532             "re-throw on exception class missing constructor", NULL);
    533         goto bail;
    534     }
    535 
    536     /*
    537      * Call the constructor with the appropriate arguments.
    538      */
    539     JValue unused;
    540     switch (initKind) {
    541     case kInitNoarg:
    542         LOGVV("+++ exc noarg (ic=%d)\n", needInitCause);
    543         dvmCallMethod(self, initMethod, exception, &unused);
    544         break;
    545     case kInitMsg:
    546         LOGVV("+++ exc msg (ic=%d)\n", needInitCause);
    547         dvmCallMethod(self, initMethod, exception, &unused, msgStr);
    548         break;
    549     case kInitThrow:
    550         LOGVV("+++ exc throw");
    551         assert(!needInitCause);
    552         dvmCallMethod(self, initMethod, exception, &unused, cause);
    553         break;
    554     case kInitMsgThrow:
    555         LOGVV("+++ exc msg+throw");
    556         assert(!needInitCause);
    557         dvmCallMethod(self, initMethod, exception, &unused, msgStr, cause);
    558         break;
    559     default:
    560         assert(false);
    561         goto bail;
    562     }
    563 
    564     /*
    565      * It's possible the constructor has thrown an exception.  If so, we
    566      * return an error and let our caller deal with it.
    567      */
    568     if (self->exception != NULL) {
    569         LOGW("Exception thrown (%s) while throwing internal exception (%s)\n",
    570             self->exception->clazz->descriptor, exception->clazz->descriptor);
    571         goto bail;
    572     }
    573 
    574     /*
    575      * If this exception was caused by another exception, and we weren't
    576      * able to find a cause-setting constructor, set the "cause" field
    577      * with an explicit call.
    578      */
    579     if (needInitCause) {
    580         Method* initCause;
    581         initCause = dvmFindVirtualMethodHierByDescriptor(excepClass, "initCause",
    582             "(Ljava/lang/Throwable;)Ljava/lang/Throwable;");
    583         if (initCause != NULL) {
    584             dvmCallMethod(self, initCause, exception, &unused, cause);
    585             if (self->exception != NULL) {
    586                 /* initCause() threw an exception; return an error and
    587                  * let the caller deal with it.
    588                  */
    589                 LOGW("Exception thrown (%s) during initCause() "
    590                         "of internal exception (%s)\n",
    591                         self->exception->clazz->descriptor,
    592                         exception->clazz->descriptor);
    593                 goto bail;
    594             }
    595         } else {
    596             LOGW("WARNING: couldn't find initCause in '%s'\n",
    597                 excepClass->descriptor);
    598         }
    599     }
    600 
    601 
    602     result = true;
    603 
    604 bail:
    605     dvmReleaseTrackedAlloc((Object*) msgStr, self);     // NULL is ok
    606     return result;
    607 }
    608 
    609 
    610 /*
    611  * Clear the pending exception and the "initExceptionCount" counter.  This
    612  * is used by the optimization and verification code, which has to run with
    613  * "initializing" set to avoid going into a death-spin if the "class not
    614  * found" exception can't be found.
    615  *
    616  * This can also be called when the VM is in a "normal" state, e.g. when
    617  * verifying classes that couldn't be verified at optimization time.  The
    618  * reset of initExceptionCount should be harmless in that case.
    619  */
    620 void dvmClearOptException(Thread* self)
    621 {
    622     self->exception = NULL;
    623     gDvm.initExceptionCount = 0;
    624 }
    625 
    626 /*
    627  * Returns "true" if this is a "checked" exception, i.e. it's a subclass
    628  * of Throwable (assumed) but not a subclass of RuntimeException or Error.
    629  */
    630 bool dvmIsCheckedException(const Object* exception)
    631 {
    632     if (dvmInstanceof(exception->clazz, gDvm.classJavaLangError) ||
    633         dvmInstanceof(exception->clazz, gDvm.classJavaLangRuntimeException))
    634     {
    635         return false;
    636     } else {
    637         return true;
    638     }
    639 }
    640 
    641 /*
    642  * Wrap the now-pending exception in a different exception.  This is useful
    643  * for reflection stuff that wants to hand a checked exception back from a
    644  * method that doesn't declare it.
    645  *
    646  * If something fails, an (unchecked) exception related to that failure
    647  * will be pending instead.
    648  */
    649 void dvmWrapException(const char* newExcepStr)
    650 {
    651     Thread* self = dvmThreadSelf();
    652     Object* origExcep;
    653     ClassObject* iteClass;
    654 
    655     origExcep = dvmGetException(self);
    656     dvmAddTrackedAlloc(origExcep, self);    // don't let the GC free it
    657 
    658     dvmClearException(self);                // clear before class lookup
    659     iteClass = dvmFindSystemClass(newExcepStr);
    660     if (iteClass != NULL) {
    661         Object* iteExcep;
    662         Method* initMethod;
    663 
    664         iteExcep = dvmAllocObject(iteClass, ALLOC_DEFAULT);
    665         if (iteExcep != NULL) {
    666             initMethod = dvmFindDirectMethodByDescriptor(iteClass, "<init>",
    667                             "(Ljava/lang/Throwable;)V");
    668             if (initMethod != NULL) {
    669                 JValue unused;
    670                 dvmCallMethod(self, initMethod, iteExcep, &unused,
    671                     origExcep);
    672 
    673                 /* if <init> succeeded, replace the old exception */
    674                 if (!dvmCheckException(self))
    675                     dvmSetException(self, iteExcep);
    676             }
    677             dvmReleaseTrackedAlloc(iteExcep, NULL);
    678 
    679             /* if initMethod doesn't exist, or failed... */
    680             if (!dvmCheckException(self))
    681                 dvmSetException(self, origExcep);
    682         } else {
    683             /* leave OutOfMemoryError pending */
    684         }
    685     } else {
    686         /* leave ClassNotFoundException pending */
    687     }
    688 
    689     assert(dvmCheckException(self));
    690     dvmReleaseTrackedAlloc(origExcep, self);
    691 }
    692 
    693 /*
    694  * Get the "cause" field from an exception.
    695  *
    696  * The Throwable class initializes the "cause" field to "this" to
    697  * differentiate between being initialized to null and never being
    698  * initialized.  We check for that here and convert it to NULL.
    699  */
    700 Object* dvmGetExceptionCause(const Object* exception)
    701 {
    702     if (!dvmInstanceof(exception->clazz, gDvm.classJavaLangThrowable)) {
    703         LOGE("Tried to get cause from object of type '%s'\n",
    704             exception->clazz->descriptor);
    705         dvmAbort();
    706     }
    707     Object* cause =
    708         dvmGetFieldObject(exception, gDvm.offJavaLangThrowable_cause);
    709     if (cause == exception)
    710         return NULL;
    711     else
    712         return cause;
    713 }
    714 
    715 /*
    716  * Print the stack trace of the current exception on stderr.  This is called
    717  * from the JNI ExceptionDescribe call.
    718  *
    719  * For consistency we just invoke the Throwable printStackTrace method,
    720  * which might be overridden in the exception object.
    721  *
    722  * Exceptions thrown during the course of printing the stack trace are
    723  * ignored.
    724  */
    725 void dvmPrintExceptionStackTrace(void)
    726 {
    727     Thread* self = dvmThreadSelf();
    728     Object* exception;
    729     Method* printMethod;
    730 
    731     exception = self->exception;
    732     if (exception == NULL)
    733         return;
    734 
    735     self->exception = NULL;
    736     printMethod = dvmFindVirtualMethodHierByDescriptor(exception->clazz,
    737                     "printStackTrace", "()V");
    738     if (printMethod != NULL) {
    739         JValue unused;
    740         dvmCallMethod(self, printMethod, exception, &unused);
    741     } else {
    742         LOGW("WARNING: could not find printStackTrace in %s\n",
    743             exception->clazz->descriptor);
    744     }
    745 
    746     if (self->exception != NULL) {
    747         LOGI("NOTE: exception thrown while printing stack trace: %s\n",
    748             self->exception->clazz->descriptor);
    749     }
    750 
    751     self->exception = exception;
    752 }
    753 
    754 /*
    755  * Search the method's list of exceptions for a match.
    756  *
    757  * Returns the offset of the catch block on success, or -1 on failure.
    758  */
    759 static int findCatchInMethod(Thread* self, const Method* method, int relPc,
    760     ClassObject* excepClass)
    761 {
    762     /*
    763      * Need to clear the exception before entry.  Otherwise, dvmResolveClass
    764      * might think somebody threw an exception while it was loading a class.
    765      */
    766     assert(!dvmCheckException(self));
    767     assert(!dvmIsNativeMethod(method));
    768 
    769     LOGVV("findCatchInMethod %s.%s excep=%s depth=%d\n",
    770         method->clazz->descriptor, method->name, excepClass->descriptor,
    771         dvmComputeExactFrameDepth(self->curFrame));
    772 
    773     DvmDex* pDvmDex = method->clazz->pDvmDex;
    774     const DexCode* pCode = dvmGetMethodCode(method);
    775     DexCatchIterator iterator;
    776 
    777     if (dexFindCatchHandler(&iterator, pCode, relPc)) {
    778         for (;;) {
    779             DexCatchHandler* handler = dexCatchIteratorNext(&iterator);
    780 
    781             if (handler == NULL) {
    782                 break;
    783             }
    784 
    785             if (handler->typeIdx == kDexNoIndex) {
    786                 /* catch-all */
    787                 LOGV("Match on catch-all block at 0x%02x in %s.%s for %s\n",
    788                         relPc, method->clazz->descriptor,
    789                         method->name, excepClass->descriptor);
    790                 return handler->address;
    791             }
    792 
    793             ClassObject* throwable =
    794                 dvmDexGetResolvedClass(pDvmDex, handler->typeIdx);
    795             if (throwable == NULL) {
    796                 /*
    797                  * TODO: this behaves badly if we run off the stack
    798                  * while trying to throw an exception.  The problem is
    799                  * that, if we're in a class loaded by a class loader,
    800                  * the call to dvmResolveClass has to ask the class
    801                  * loader for help resolving any previously-unresolved
    802                  * classes.  If this particular class loader hasn't
    803                  * resolved StackOverflowError, it will call into
    804                  * interpreted code, and blow up.
    805                  *
    806                  * We currently replace the previous exception with
    807                  * the StackOverflowError, which means they won't be
    808                  * catching it *unless* they explicitly catch
    809                  * StackOverflowError, in which case we'll be unable
    810                  * to resolve the class referred to by the "catch"
    811                  * block.
    812                  *
    813                  * We end up getting a huge pile of warnings if we do
    814                  * a simple synthetic test, because this method gets
    815                  * called on every stack frame up the tree, and it
    816                  * fails every time.
    817                  *
    818                  * This eventually bails out, effectively becoming an
    819                  * uncatchable exception, so other than the flurry of
    820                  * warnings it's not really a problem.  Still, we could
    821                  * probably handle this better.
    822                  */
    823                 throwable = dvmResolveClass(method->clazz, handler->typeIdx,
    824                     true);
    825                 if (throwable == NULL) {
    826                     /*
    827                      * We couldn't find the exception they wanted in
    828                      * our class files (or, perhaps, the stack blew up
    829                      * while we were querying a class loader). Cough
    830                      * up a warning, then move on to the next entry.
    831                      * Keep the exception status clear.
    832                      */
    833                     LOGW("Could not resolve class ref'ed in exception "
    834                             "catch list (class index %d, exception %s)\n",
    835                             handler->typeIdx,
    836                             (self->exception != NULL) ?
    837                             self->exception->clazz->descriptor : "(none)");
    838                     dvmClearException(self);
    839                     continue;
    840                 }
    841             }
    842 
    843             //LOGD("ADDR MATCH, check %s instanceof %s\n",
    844             //    excepClass->descriptor, pEntry->excepClass->descriptor);
    845 
    846             if (dvmInstanceof(excepClass, throwable)) {
    847                 LOGV("Match on catch block at 0x%02x in %s.%s for %s\n",
    848                         relPc, method->clazz->descriptor,
    849                         method->name, excepClass->descriptor);
    850                 return handler->address;
    851             }
    852         }
    853     }
    854 
    855     LOGV("No matching catch block at 0x%02x in %s for %s\n",
    856         relPc, method->name, excepClass->descriptor);
    857     return -1;
    858 }
    859 
    860 /*
    861  * Find a matching "catch" block.  "pc" is the relative PC within the
    862  * current method, indicating the offset from the start in 16-bit units.
    863  *
    864  * Returns the offset to the catch block, or -1 if we run up against a
    865  * break frame without finding anything.
    866  *
    867  * The class resolution stuff we have to do while evaluating the "catch"
    868  * blocks could cause an exception.  The caller should clear the exception
    869  * before calling here and restore it after.
    870  *
    871  * Sets *newFrame to the frame pointer of the frame with the catch block.
    872  * If "scanOnly" is false, self->curFrame is also set to this value.
    873  */
    874 int dvmFindCatchBlock(Thread* self, int relPc, Object* exception,
    875     bool scanOnly, void** newFrame)
    876 {
    877     void* fp = self->curFrame;
    878     int catchAddr = -1;
    879 
    880     assert(!dvmCheckException(self));
    881 
    882     while (true) {
    883         StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
    884         catchAddr = findCatchInMethod(self, saveArea->method, relPc,
    885                         exception->clazz);
    886         if (catchAddr >= 0)
    887             break;
    888 
    889         /*
    890          * Normally we'd check for ACC_SYNCHRONIZED methods and unlock
    891          * them as we unroll.  Dalvik uses what amount to generated
    892          * "finally" blocks to take care of this for us.
    893          */
    894 
    895         /* output method profiling info */
    896         if (!scanOnly) {
    897             TRACE_METHOD_UNROLL(self, saveArea->method);
    898         }
    899 
    900         /*
    901          * Move up one frame.  If the next thing up is a break frame,
    902          * break out now so we're left unrolled to the last method frame.
    903          * We need to point there so we can roll up the JNI local refs
    904          * if this was a native method.
    905          */
    906         assert(saveArea->prevFrame != NULL);
    907         if (dvmIsBreakFrame(saveArea->prevFrame)) {
    908             if (!scanOnly)
    909                 break;      // bail with catchAddr == -1
    910 
    911             /*
    912              * We're scanning for the debugger.  It needs to know if this
    913              * exception is going to be caught or not, and we need to figure
    914              * out if it will be caught *ever* not just between the current
    915              * position and the next break frame.  We can't tell what native
    916              * code is going to do, so we assume it never catches exceptions.
    917              *
    918              * Start by finding an interpreted code frame.
    919              */
    920             fp = saveArea->prevFrame;           // this is the break frame
    921             saveArea = SAVEAREA_FROM_FP(fp);
    922             fp = saveArea->prevFrame;           // this may be a good one
    923             while (fp != NULL) {
    924                 if (!dvmIsBreakFrame(fp)) {
    925                     saveArea = SAVEAREA_FROM_FP(fp);
    926                     if (!dvmIsNativeMethod(saveArea->method))
    927                         break;
    928                 }
    929 
    930                 fp = SAVEAREA_FROM_FP(fp)->prevFrame;
    931             }
    932             if (fp == NULL)
    933                 break;      // bail with catchAddr == -1
    934 
    935             /*
    936              * Now fp points to the "good" frame.  When the interp code
    937              * invoked the native code, it saved a copy of its current PC
    938              * into xtra.currentPc.  Pull it out of there.
    939              */
    940             relPc =
    941                 saveArea->xtra.currentPc - SAVEAREA_FROM_FP(fp)->method->insns;
    942         } else {
    943             fp = saveArea->prevFrame;
    944 
    945             /* savedPc in was-current frame goes with method in now-current */
    946             relPc = saveArea->savedPc - SAVEAREA_FROM_FP(fp)->method->insns;
    947         }
    948     }
    949 
    950     if (!scanOnly)
    951         self->curFrame = fp;
    952 
    953     /*
    954      * The class resolution in findCatchInMethod() could cause an exception.
    955      * Clear it to be safe.
    956      */
    957     self->exception = NULL;
    958 
    959     *newFrame = fp;
    960     return catchAddr;
    961 }
    962 
    963 /*
    964  * We have to carry the exception's stack trace around, but in many cases
    965  * it will never be examined.  It makes sense to keep it in a compact,
    966  * VM-specific object, rather than an array of Objects with strings.
    967  *
    968  * Pass in the thread whose stack we're interested in.  If "thread" is
    969  * not self, the thread must be suspended.  This implies that the thread
    970  * list lock is held, which means we can't allocate objects or we risk
    971  * jamming the GC.  So, we allow this function to return different formats.
    972  * (This shouldn't be called directly -- see the inline functions in the
    973  * header file.)
    974  *
    975  * If "wantObject" is true, this returns a newly-allocated Object, which is
    976  * presently an array of integers, but could become something else in the
    977  * future.  If "wantObject" is false, return plain malloc data.
    978  *
    979  * NOTE: if we support class unloading, we will need to scan the class
    980  * object references out of these arrays.
    981  */
    982 void* dvmFillInStackTraceInternal(Thread* thread, bool wantObject, int* pCount)
    983 {
    984     ArrayObject* stackData = NULL;
    985     int* simpleData = NULL;
    986     void* fp;
    987     void* startFp;
    988     int stackDepth;
    989     int* intPtr;
    990 
    991     if (pCount != NULL)
    992         *pCount = 0;
    993     fp = thread->curFrame;
    994 
    995     assert(thread == dvmThreadSelf() || dvmIsSuspended(thread));
    996 
    997     /*
    998      * We're looking at a stack frame for code running below a Throwable
    999      * constructor.  We want to remove the Throwable methods and the
   1000      * superclass initializations so the user doesn't see them when they
   1001      * read the stack dump.
   1002      *
   1003      * TODO: this just scrapes off the top layers of Throwable.  Might not do
   1004      * the right thing if we create an exception object or cause a VM
   1005      * exception while in a Throwable method.
   1006      */
   1007     while (fp != NULL) {
   1008         const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
   1009         const Method* method = saveArea->method;
   1010 
   1011         if (dvmIsBreakFrame(fp))
   1012             break;
   1013         if (!dvmInstanceof(method->clazz, gDvm.classJavaLangThrowable))
   1014             break;
   1015         //LOGD("EXCEP: ignoring %s.%s\n",
   1016         //         method->clazz->descriptor, method->name);
   1017         fp = saveArea->prevFrame;
   1018     }
   1019     startFp = fp;
   1020 
   1021     /*
   1022      * Compute the stack depth.
   1023      */
   1024     stackDepth = 0;
   1025     while (fp != NULL) {
   1026         const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
   1027 
   1028         if (!dvmIsBreakFrame(fp))
   1029             stackDepth++;
   1030 
   1031         assert(fp != saveArea->prevFrame);
   1032         fp = saveArea->prevFrame;
   1033     }
   1034     //LOGD("EXCEP: stack depth is %d\n", stackDepth);
   1035 
   1036     if (!stackDepth)
   1037         goto bail;
   1038 
   1039     /*
   1040      * We need to store a pointer to the Method and the program counter.
   1041      * We have 4-byte pointers, so we use '[I'.
   1042      */
   1043     if (wantObject) {
   1044         assert(sizeof(Method*) == 4);
   1045         stackData = dvmAllocPrimitiveArray('I', stackDepth*2, ALLOC_DEFAULT);
   1046         if (stackData == NULL) {
   1047             assert(dvmCheckException(dvmThreadSelf()));
   1048             goto bail;
   1049         }
   1050         intPtr = (int*) stackData->contents;
   1051     } else {
   1052         /* array of ints; first entry is stack depth */
   1053         assert(sizeof(Method*) == sizeof(int));
   1054         simpleData = (int*) malloc(sizeof(int) * stackDepth*2);
   1055         if (simpleData == NULL)
   1056             goto bail;
   1057 
   1058         assert(pCount != NULL);
   1059         intPtr = simpleData;
   1060     }
   1061     if (pCount != NULL)
   1062         *pCount = stackDepth;
   1063 
   1064     fp = startFp;
   1065     while (fp != NULL) {
   1066         const StackSaveArea* saveArea = SAVEAREA_FROM_FP(fp);
   1067         const Method* method = saveArea->method;
   1068 
   1069         if (!dvmIsBreakFrame(fp)) {
   1070             //LOGD("EXCEP keeping %s.%s\n", method->clazz->descriptor,
   1071             //         method->name);
   1072 
   1073             *intPtr++ = (int) method;
   1074             if (dvmIsNativeMethod(method)) {
   1075                 *intPtr++ = 0;      /* no saved PC for native methods */
   1076             } else {
   1077                 assert(saveArea->xtra.currentPc >= method->insns &&
   1078                         saveArea->xtra.currentPc <
   1079                         method->insns + dvmGetMethodInsnsSize(method));
   1080                 *intPtr++ = (int) (saveArea->xtra.currentPc - method->insns);
   1081             }
   1082 
   1083             stackDepth--;       // for verification
   1084         }
   1085 
   1086         assert(fp != saveArea->prevFrame);
   1087         fp = saveArea->prevFrame;
   1088     }
   1089     assert(stackDepth == 0);
   1090 
   1091 bail:
   1092     if (wantObject) {
   1093         dvmReleaseTrackedAlloc((Object*) stackData, dvmThreadSelf());
   1094         return stackData;
   1095     } else {
   1096         return simpleData;
   1097     }
   1098 }
   1099 
   1100 
   1101 /*
   1102  * Given an Object previously created by dvmFillInStackTrace(), use the
   1103  * contents of the saved stack trace to generate an array of
   1104  * java/lang/StackTraceElement objects.
   1105  *
   1106  * The returned array is not added to the "local refs" list.
   1107  */
   1108 ArrayObject* dvmGetStackTrace(const Object* ostackData)
   1109 {
   1110     const ArrayObject* stackData = (const ArrayObject*) ostackData;
   1111     const int* intVals;
   1112     int i, stackSize;
   1113 
   1114     stackSize = stackData->length / 2;
   1115     intVals = (const int*) stackData->contents;
   1116     return dvmGetStackTraceRaw(intVals, stackSize);
   1117 }
   1118 
   1119 /*
   1120  * Generate an array of StackTraceElement objects from the raw integer
   1121  * data encoded by dvmFillInStackTrace().
   1122  *
   1123  * "intVals" points to the first {method,pc} pair.
   1124  *
   1125  * The returned array is not added to the "local refs" list.
   1126  */
   1127 ArrayObject* dvmGetStackTraceRaw(const int* intVals, int stackDepth)
   1128 {
   1129     ArrayObject* steArray = NULL;
   1130     Object** stePtr;
   1131     int i;
   1132 
   1133     /* init this if we haven't yet */
   1134     if (!dvmIsClassInitialized(gDvm.classJavaLangStackTraceElement))
   1135         dvmInitClass(gDvm.classJavaLangStackTraceElement);
   1136 
   1137     /* allocate a StackTraceElement array */
   1138     steArray = dvmAllocArray(gDvm.classJavaLangStackTraceElementArray,
   1139                     stackDepth, kObjectArrayRefWidth, ALLOC_DEFAULT);
   1140     if (steArray == NULL)
   1141         goto bail;
   1142     stePtr = (Object**) steArray->contents;
   1143 
   1144     /*
   1145      * Allocate and initialize a StackTraceElement for each stack frame.
   1146      * We use the standard constructor to configure the object.
   1147      */
   1148     for (i = 0; i < stackDepth; i++) {
   1149         Object* ste;
   1150         Method* meth;
   1151         StringObject* className;
   1152         StringObject* methodName;
   1153         StringObject* fileName;
   1154         int lineNumber, pc;
   1155         const char* sourceFile;
   1156         char* dotName;
   1157 
   1158         ste = dvmAllocObject(gDvm.classJavaLangStackTraceElement,ALLOC_DEFAULT);
   1159         if (ste == NULL)
   1160             goto bail;
   1161 
   1162         meth = (Method*) *intVals++;
   1163         pc = *intVals++;
   1164 
   1165         if (pc == -1)      // broken top frame?
   1166             lineNumber = 0;
   1167         else
   1168             lineNumber = dvmLineNumFromPC(meth, pc);
   1169 
   1170         dotName = dvmDescriptorToDot(meth->clazz->descriptor);
   1171         className = dvmCreateStringFromCstr(dotName, ALLOC_DEFAULT);
   1172         free(dotName);
   1173 
   1174         methodName = dvmCreateStringFromCstr(meth->name, ALLOC_DEFAULT);
   1175         sourceFile = dvmGetMethodSourceFile(meth);
   1176         if (sourceFile != NULL)
   1177             fileName = dvmCreateStringFromCstr(sourceFile, ALLOC_DEFAULT);
   1178         else
   1179             fileName = NULL;
   1180 
   1181         /*
   1182          * Invoke:
   1183          *  public StackTraceElement(String declaringClass, String methodName,
   1184          *      String fileName, int lineNumber)
   1185          * (where lineNumber==-2 means "native")
   1186          */
   1187         JValue unused;
   1188         dvmCallMethod(dvmThreadSelf(), gDvm.methJavaLangStackTraceElement_init,
   1189             ste, &unused, className, methodName, fileName, lineNumber);
   1190 
   1191         dvmReleaseTrackedAlloc(ste, NULL);
   1192         dvmReleaseTrackedAlloc((Object*) className, NULL);
   1193         dvmReleaseTrackedAlloc((Object*) methodName, NULL);
   1194         dvmReleaseTrackedAlloc((Object*) fileName, NULL);
   1195 
   1196         if (dvmCheckException(dvmThreadSelf()))
   1197             goto bail;
   1198 
   1199         *stePtr++ = ste;
   1200     }
   1201 
   1202 bail:
   1203     dvmReleaseTrackedAlloc((Object*) steArray, NULL);
   1204     return steArray;
   1205 }
   1206 
   1207 /*
   1208  * Dump the contents of a raw stack trace to the log.
   1209  */
   1210 void dvmLogRawStackTrace(const int* intVals, int stackDepth)
   1211 {
   1212     int i;
   1213 
   1214     /*
   1215      * Run through the array of stack frame data.
   1216      */
   1217     for (i = 0; i < stackDepth; i++) {
   1218         Method* meth;
   1219         int lineNumber, pc;
   1220         const char* sourceFile;
   1221         char* dotName;
   1222 
   1223         meth = (Method*) *intVals++;
   1224         pc = *intVals++;
   1225 
   1226         if (pc == -1)      // broken top frame?
   1227             lineNumber = 0;
   1228         else
   1229             lineNumber = dvmLineNumFromPC(meth, pc);
   1230 
   1231         // probably don't need to do this, but it looks nicer
   1232         dotName = dvmDescriptorToDot(meth->clazz->descriptor);
   1233 
   1234         if (dvmIsNativeMethod(meth)) {
   1235             LOGI("\tat %s.%s(Native Method)\n", dotName, meth->name);
   1236         } else {
   1237             LOGI("\tat %s.%s(%s:%d)\n",
   1238                 dotName, meth->name, dvmGetMethodSourceFile(meth),
   1239                 dvmLineNumFromPC(meth, pc));
   1240         }
   1241 
   1242         free(dotName);
   1243 
   1244         sourceFile = dvmGetMethodSourceFile(meth);
   1245     }
   1246 }
   1247 
   1248 /*
   1249  * Print the direct stack trace of the given exception to the log.
   1250  */
   1251 static void logStackTraceOf(Object* exception)
   1252 {
   1253     const ArrayObject* stackData;
   1254     StringObject* messageStr;
   1255     int stackSize;
   1256     const int* intVals;
   1257 
   1258     messageStr = (StringObject*) dvmGetFieldObject(exception,
   1259                     gDvm.offJavaLangThrowable_message);
   1260     if (messageStr != NULL) {
   1261         char* cp = dvmCreateCstrFromString(messageStr);
   1262         LOGI("%s: %s\n", exception->clazz->descriptor, cp);
   1263         free(cp);
   1264     } else {
   1265         LOGI("%s:\n", exception->clazz->descriptor);
   1266     }
   1267 
   1268     stackData = (const ArrayObject*) dvmGetFieldObject(exception,
   1269                     gDvm.offJavaLangThrowable_stackState);
   1270     if (stackData == NULL) {
   1271         LOGI("  (no stack trace data found)\n");
   1272         return;
   1273     }
   1274 
   1275     stackSize = stackData->length / 2;
   1276     intVals = (const int*) stackData->contents;
   1277 
   1278     dvmLogRawStackTrace(intVals, stackSize);
   1279 }
   1280 
   1281 /*
   1282  * Print the stack trace of the current thread's exception, as well as
   1283  * the stack traces of any chained exceptions, to the log. We extract
   1284  * the stored stack trace and process it internally instead of calling
   1285  * interpreted code.
   1286  */
   1287 void dvmLogExceptionStackTrace(void)
   1288 {
   1289     Object* exception = dvmThreadSelf()->exception;
   1290     Object* cause;
   1291 
   1292     if (exception == NULL) {
   1293         LOGW("tried to log a null exception?\n");
   1294         return;
   1295     }
   1296 
   1297     for (;;) {
   1298         logStackTraceOf(exception);
   1299         cause = dvmGetExceptionCause(exception);
   1300         if (cause == NULL) {
   1301             break;
   1302         }
   1303         LOGI("Caused by:\n");
   1304         exception = cause;
   1305     }
   1306 }
   1307 
   1308