/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (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.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ // // simon // bjorn #include "SysCalls.h" #include "prprf.h" #include "x86ExceptionHandler.h" #include "ExceptionTable.h" #include "NativeCodeCache.h" #include "LogModule.h" UT_DEFINE_LOG_MODULE(x86ExceptionHandler); UT_DEFINE_LOG_MODULE(ExceptionRegs); extern "C" SYSCALL_FUNC(void) x86JumpToHandler(const JavaObject* inObject, Uint8* handler, Uint32 EBX, Uint32 ESI, Uint32 EDI, Uint32* EBP, Uint32* ESP); extern "C" SYSCALL_FUNC(void) x86SoftwareExceptionHandler(Uint32 EBP, Uint32 EDI, Uint32 ESI, Uint32 EBX, const JavaObject& inObject); //-------------------------------------------------------------------------------- /* +-------------------------------+ | return address | ========+===============================+======== Source | EBP link | <- sourceEBP (JITed Java) +-------------------------------+ | Local Store | +-------------------------------+ | Saved Non-volatiles | | eg. EDI | | ESI | | EBX | +-------------------------------+ | thrown object | +-------------------------------+ <- initial restoreESP | return address | ========+===============================+======== sysThrow | EBP link | <- successorEBP (native) +-------------------------------+ | arguments | ========+===============================+======== x86SoftwareExceptionHandler | EBP link | (native) +-------------------------------+ | Saved Non-volatiles | | (variable) | +-------------------------------+ | 'context' structure | +-------------------------------+ */ //-------------------------------------------------------------------------------- #ifdef DEBUG_LOG void printRegs(LogModuleObject &f, Context* context); void printExceptionBegin(LogModuleObject &f, Context* context); #endif #ifdef DEBUG void checkForNativeStub(Context* context); #endif Uint8* findExceptionHandler(Context* context); //-------------------------------------------------------------------------------- extern "C" SYSCALL_FUNC(JavaObject&) x86NewExceptionInstance(StandardClass classKind) { assert(classKind == cNullPointerException || classKind == cArrayIndexOutOfBoundsException || classKind == cClassCastException || classKind == cArithmeticException); return const_cast(&Standard::get(classKind))->newInstance(); } //-------------------------------------------------------------------------------- // Non MSVC code -- FIX make another file for this #ifdef __GNUC__ extern "C" { Uint32 exceptionClass[] = { cNullPointerException, cArrayIndexOutOfBoundsException, cClassCastException, cArrayStoreException, }; } #endif //-------------------------------------------------------------------------------- // Function: x86SoftwareExceptionHandler // // called by sysThrow extern "C" SYSCALL_FUNC(void) x86SoftwareExceptionHandler(Uint32 inEBP, Uint32 inEDI, Uint32 inESI, Uint32 inEBX, const JavaObject& inObject) { Context context; // create a context object on the stack context.EDI = inEDI; context.ESI = inESI; context.EBX = inEBX; context.object = (JavaObject*) &inObject; // get other EBPs context.successorEBP = (Uint32*) inEBP; context.sourceEBP = *((Uint32**)context.successorEBP); // The source address will point to the instruction to return to, NOT the instruction that // generated the exception. The return address is often (always?) in a different control node, so // it may have a different exception table. // The byte before the return address will be part of the call instruction that got us here. context.sourceAddress = (Uint8*)(context.successorEBP[1]) - 1; // cache entries context.sourceCE = NativeCodeCache::getCache().lookupByRange((Uint8*)context.sourceAddress); context.successorCE = NULL; // no information yet // calculate the ESP if we were to proceed directly to the catch // EBP + 4 (return address) + 4 (argument) + 4 (point to last object on stack == saved regs) context.restoreESP = (Uint32*) ((Uint8*)context.successorEBP + 12); // now that the context struct is filled we can print debugging information DEBUG_LOG_ONLY(printExceptionBegin(UT_LOG_MODULE(x86ExceptionHandler), &context)); // find the exception handler Uint8* handler = findExceptionHandler(&context); // jump to it UT_LOG(ExceptionRegs, PR_LOG_DEBUG, ("Exit: ")); DEBUG_LOG_ONLY(printRegs(UT_LOG_MODULE(ExceptionRegs), &context)); UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("\n===============================\n\n")); assert(handler); x86JumpToHandler(context.object, handler, context.EBX, context.ESI, context.EDI, context.sourceEBP, context.restoreESP); } // Function: x86PopStackFrame // In: the current frame context // Out: true if a frame was popped, and false if bottom of stack was reached bool x86PopStackFrame(Context* context) { assert(context); assert(context->sourceEBP); // check that we can unwind Uint32* newSourceEBP = *(Uint32**)(context->sourceEBP); // FIX for now when we get the EBP points to NULL we are at the bottom of the stack // As a temporary measure, die and display the error if(newSourceEBP == NULL) { DEBUG_LOG_ONLY(PR_fprintf(PR_STDOUT,"\n*** Unhandled Exception Error: %s\n", context->object->getClass().getName())); return false; } // roll back to point to previous frame context->successorEBP = context->sourceEBP; context->sourceEBP = newSourceEBP; context->sourceAddress = (Uint8*)(context->successorEBP[1]) - 1; // move back a byte to ensure we're in correct control node context->successorCE = context->sourceCE; context->sourceCE = NativeCodeCache::getCache().lookupByRange(context->sourceAddress); // Restore Registers // The callee saves a set of registers EDI, ESI, EBX. To restore to the current 'source' frame we need // to restore the set of registers saved from our callee (the 'successor' frame). // // We know nothing about the register save policies of native methods. To ensure proper register // restoration we include a dummy stack frame below all native frames that has stored all three // callee-save registers. // // Whenever we see a native method we should restore the registers assuming that the first three locations // after EBP are the callee-save values. The first native method will probably load garbage into the registers // but the last fake frame that we have inserted will properly restore them all. // EBX, ESI and EDI are restored from the successor frame // // remember that sourceCE and successorCE can be null Uint32 numRegs; // number of registers to restore Uint32* restoreReg; // address of first restore location if(context->successorCE) { // successor is java frame; restore regs as per saved policy StackFrameInfo& successorPolicy = context->successorCE->policy; restoreReg = (Uint32*) ((Uint8*)(context->successorEBP) - successorPolicy.getCalleeSavedBeginOffset()); numRegs = successorPolicy.getNumSavedGPRWords(); } else { // successor is native frame; restore all regs from just below EBP restoreReg = (Uint32*) ((Uint8*)(context->successorEBP) - 4); numRegs = 3; } if(numRegs > 2) context->EDI = *restoreReg--; if(numRegs > 1) context->ESI = *restoreReg--; if(numRegs > 0) context->EBX = *restoreReg--; // Calculate the restore ESP if(context->sourceCE) { // source frame is java frame; restore ESP to correct position StackFrameInfo& sourcePolicy = context->sourceCE->policy; context->restoreESP = (Uint32*) ((Uint8*)(context->sourceEBP) - (Uint8*)(sourcePolicy.getStackPointerOffset())); } else { // source frame is native; cannot restore ESP, so we set it to NULL as a sentinel context->restoreESP = NULL; } DEBUG_LOG_ONLY(printContext(UT_LOG_MODULE(x86ExceptionHandler), context)); DEBUG_ONLY(checkForNativeStub(context)); return true; } //-------------------------------------------------------------------------------- #ifdef DEBUG extern void* compileStubReEntryPoint; // Function: isGuardFrame // Purpose: determines whether the source frame is a native method / syscall guard frame bool isGuardFrame(Context* context, bool printIdentifier = false) { Uint32 retAddress = (Uint32)(context->successorEBP[1]); int i; // check unrolled-loop stubs for return address extern void* sysInvokeNativeStubs[1024]; for(i = 0; i < 6; i++) if( ((Uint32)(sysInvokeNativeStubs[i]) + 9 + 5 * i) == retAddress) { if(printIdentifier) UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("Guard Frame (made by sysInvokeNative%d())\n", i)); return true; } // check other stubs for(i = 6; i < 256; i++) if( ((Uint32)(sysInvokeNativeStubs[i]) + 23) == retAddress) { if(printIdentifier) UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("Guard Frame (made by sysInvokeNative(%d))\n", i)); return true; } // check for staticCompileStub if((Uint32)compileStubReEntryPoint == retAddress) { if(printIdentifier) UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("Guard Frame (staticCompileStub)\n")); DEBUG_LOG_ONLY(PR_fprintf(PR_STDOUT,"WARNING: exception unwinding past a static compile stub\n")); return true; } // if we are here then we are not a guard frame return false; } #include "StackWalker.h" // Function: checkForNativeStub // Purpose: if the current frame is a non-Java frame called by a Java method, check that // the frame is a guard frame void checkForNativeStub(Context* context) { // checks // take a peek back at the previous frame Uint8* prevAddress = (Uint8*)(context->sourceEBP[1]) - 1; assert(prevAddress); CacheEntry* prevCacheEntry = NativeCodeCache::getCache().lookupByRange(prevAddress); bool sourceIsJITCode = context->sourceCE && context->sourceCE->eTable; bool previousIsJITCode = prevCacheEntry && prevCacheEntry->eTable; if((!sourceIsJITCode && previousIsJITCode) && !isGuardFrame(context)) { // this is not a GUARD-JAVA interface, but the guard was missing stackWalker((void*) 0); trespass("\nFATAL ERROR: missing guard frame\n"); } } #endif #ifdef DEBUG_LOG // Function: printExceptionBegin // Purpose: call this when the context record has been filled to print out the origin and type of exception void printExceptionBegin(LogModuleObject &f, Context* context) { // print debugging information UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("\n===============================\nEXCEPTION\n")); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Source: ")); if(context->sourceCE) { const char* methodName = context->sourceCE->descriptor.method->getName(); const char* className = context->sourceCE->descriptor.method->getDeclaringClass()->getName(); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("java method '%s'\n", methodName)); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Class: %s\n", className)); } else { UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("a syscall or native method\n")); } UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Object: '%s' (object = 0x%p)\n", context->object->getClass().getName(), context->object)); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Return: 0x%p\n", context->successorEBP[1])); UT_OBJECTLOG(UT_LOG_MODULE(ExceptionRegs), PR_LOG_ALWAYS, ("Entry: ")); printRegs(UT_LOG_MODULE(ExceptionRegs), context); } // Function: printRegs // Purpose: print the state of the registers being restored by the exception handler void printRegs(LogModuleObject &f, Context* context) { UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("EBX %8x ESI %8x EDI %8x\n", context->EBX, context->ESI, context->EDI)); } // Function: printContext // Purpose: dump the state of the exception handler context to the log module void printContext(LogModuleObject &f, Context* context) { UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("\n-------------------------------\n")); // print the method if(context->sourceCE) { const char* methodName = context->sourceCE->descriptor.method->getName(); const char* className = context->sourceCE->descriptor.method->getDeclaringClass()->getName(); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Method: %s\n", methodName)); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Class: %s\n", className)); StackFrameInfo& policy = context->sourceCE->policy; policy.print(f); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Regs: ")); printRegs(f, context); ExceptionTable* eTable = context->sourceCE->eTable; if(eTable && eTable->getNumberofEntries() > 0) { UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Exception Table\n")); eTable->printShort(f); } } else if(!isGuardFrame(context, true)) // isGuardFrame(..., true) prints reference if guard frame found UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Method: anonymous\n")); UT_OBJECTLOG(f, PR_LOG_ALWAYS, ("Return: 0x%p\n", context->successorEBP[1])); } #endif // DEBUG_LOG //-------------------------------------------------------------------------------- // this function assumes that the source frame was set up by Thread::invoke(), and // that it should look like: /* +-----------------------+ | old EBP | <- real sourceEBP |-----------------------| | &uncaughtException | |-----------------------| | NULL | <- sourceEBP as on entry to uncaughtExitPoint |-----------------------| | 4 arguments to invoke | | ... | |-----------------------| | return address | <- restoreESP |-----------------------| */ static Uint8* uncaughtExceptionExit(Context* context) { Uint8* ip = *(Uint8**)(context->sourceEBP+1); // uncaughtException() context->restoreESP = context->sourceEBP-5; // 4 arguments plus the return address context->sourceEBP = *(Uint32**)(context->sourceEBP+2); // old ebp assert(ip); return ip; } //-------------------------------------------------------------------------------- // findExceptionHandler // Uint8* findExceptionHandler(Context* context) { // look for a handler for the object // unwind repeatedly until found ExceptionTableEntry* ete; while (1) { // every jit'd method has an eTable // native frames don't have a sourceCE if(context->sourceCE) { // jit frame assert(context->sourceCE->eTable); ete = context->sourceCE->eTable->findCatchHandler(context->sourceAddress, *(context->object)); } else { // native frame ete = NULL; } if (ete) // ete != null iff there has been an exception match break; if (!x86PopStackFrame(context)) // when popStack() fails, we are faced with an uncaught exception { Uint8* uncaughtHandler = uncaughtExceptionExit(context); UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("\n-------------------------------\n")); UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("Uncaught Exception -- jumping to handler at 0x%p\n", uncaughtHandler)); return uncaughtHandler; } } Uint8* pHandler = context->sourceCE->eTable->getStart()+ete->pHandler; assert(pHandler); UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("\n-------------------------------\n")); UT_LOG(x86ExceptionHandler, PR_LOG_DEBUG, ("Matched to catch handler at %p\n\n", pHandler)); assert(context->restoreESP != NULL); // restore ESP must not be null, could mean that we are trying to return to a native method, NYI return pHandler; }