| // stacktrace.cc - Functions for unwinding & inspecting the call stack. | |
| /* Copyright (C) 2005, 2006 Free Software Foundation | |
| This file is part of libgcj. | |
| This software is copyrighted work licensed under the terms of the | |
| Libgcj License. Please consult the file "LIBGCJ_LICENSE" for | |
| details. */ | |
| #include <config.h> | |
| #include <platform.h> | |
| #include <jvm.h> | |
| #include <gcj/cni.h> | |
| #include <java-interp.h> | |
| #include <java-stack.h> | |
| #include <stdio.h> | |
| #include <java/lang/Boolean.h> | |
| #include <java/lang/Class.h> | |
| #include <java/lang/Long.h> | |
| #include <java/security/AccessController.h> | |
| #include <java/util/ArrayList.h> | |
| #include <java/util/IdentityHashMap.h> | |
| #include <gnu/classpath/jdwp/Jdwp.h> | |
| #include <gnu/java/lang/MainThread.h> | |
| #include <gnu/gcj/runtime/NameFinder.h> | |
| #include <gnu/gcj/runtime/StringBuffer.h> | |
| #include <sysdep/backtrace.h> | |
| #include <sysdep/descriptor.h> | |
| using namespace java::lang; | |
| using namespace java::lang::reflect; | |
| using namespace java::util; | |
| using namespace gnu::gcj::runtime; | |
| // Maps ncode values to their containing native class. | |
| // NOTE: Currently this Map contradicts class GC for native classes. This map | |
| // (and the "new class stack") will need to use WeakReferences in order to | |
| // enable native class GC. | |
| static java::util::IdentityHashMap *ncodeMap; | |
| // Check the "class stack" for any classes initialized since we were last | |
| // called, and add them to ncodeMap. | |
| void | |
| _Jv_StackTrace::UpdateNCodeMap () | |
| { | |
| // The Map should be large enough so that a typical Java app doesn't cause | |
| // it to rehash, without using too much memory. ~5000 entries should be | |
| // enough. | |
| if (ncodeMap == NULL) | |
| ncodeMap = new java::util::IdentityHashMap (5087); | |
| jclass klass; | |
| while ((klass = _Jv_PopClass ())) | |
| if (!_Jv_IsInterpretedClass (klass)) | |
| { | |
| //printf ("got %s\n", klass->name->data); | |
| for (int i = 0; i < klass->method_count; i++) | |
| { | |
| _Jv_Method *method = &klass->methods[i]; | |
| void *ncode = method->ncode; | |
| // Add non-abstract methods to ncodeMap. | |
| if (ncode) | |
| { | |
| ncode = UNWRAP_FUNCTION_DESCRIPTOR (ncode); | |
| ncodeMap->put ((java::lang::Object *) ncode, klass); | |
| } | |
| } | |
| } | |
| } | |
| // Given a native frame, return the class which this code belongs | |
| // to. Returns NULL if this IP is not associated with a native Java class. | |
| // If NCODE is supplied, it will be set with the ip for the entry point of the | |
| // enclosing method. | |
| jclass | |
| _Jv_StackTrace::ClassForFrame (_Jv_StackFrame *frame) | |
| { | |
| JvAssert (frame->type == frame_native); | |
| jclass klass = NULL; | |
| // look it up in ncodeMap | |
| if (frame->start_ip) | |
| klass = (jclass) ncodeMap->get ((jobject) frame->start_ip); | |
| return klass; | |
| } | |
| _Unwind_Reason_Code | |
| _Jv_StackTrace::UnwindTraceFn (struct _Unwind_Context *context, void *state_ptr) | |
| { | |
| _Jv_UnwindState *state = (_Jv_UnwindState *) state_ptr; | |
| jint pos = state->pos; | |
| // Check if the trace buffer needs to be extended. | |
| if (pos == state->length) | |
| { | |
| int newLength = state->length * 2; | |
| void *newFrames = _Jv_AllocBytes (newLength * sizeof(_Jv_StackFrame)); | |
| memcpy (newFrames, state->frames, state->length * sizeof(_Jv_StackFrame)); | |
| state->frames = (_Jv_StackFrame *) newFrames; | |
| state->length = newLength; | |
| } | |
| void *func_addr = (void *) _Unwind_GetRegionStart (context); | |
| // If we see the interpreter's main function, "pop" an entry off the | |
| // interpreter stack and use that instead, so that the trace goes through | |
| // the java code and not the interpreter itself. This assumes a 1:1 | |
| // correspondance between call frames in the interpreted stack and occurances | |
| // of _Jv_InterpMethod::run() on the native stack. | |
| #ifdef INTERPRETER | |
| void *interp_run = NULL; | |
| if (::gnu::classpath::jdwp::Jdwp::isDebugging) | |
| interp_run = (void *) &_Jv_InterpMethod::run_debug; | |
| else | |
| interp_run = (void *) &_Jv_InterpMethod::run; | |
| if (func_addr == UNWRAP_FUNCTION_DESCRIPTOR (interp_run)) | |
| { | |
| state->frames[pos].type = frame_interpreter; | |
| state->frames[pos].interp.meth = state->interp_frame->self; | |
| state->frames[pos].interp.pc = state->interp_frame->pc; | |
| state->interp_frame = state->interp_frame->next; | |
| } | |
| else | |
| #endif | |
| { | |
| #ifdef HAVE_GETIPINFO | |
| _Unwind_Ptr ip; | |
| int ip_before_insn = 0; | |
| ip = _Unwind_GetIPInfo (context, &ip_before_insn); | |
| // If the unwinder gave us a 'return' address, roll it back a little | |
| // to ensure we get the correct line number for the call itself. | |
| if (! ip_before_insn) | |
| --ip; | |
| #endif | |
| state->frames[pos].type = frame_native; | |
| #ifdef HAVE_GETIPINFO | |
| state->frames[pos].ip = (void *) ip; | |
| #else | |
| state->frames[pos].ip = (void *) _Unwind_GetIP (context); | |
| #endif | |
| state->frames[pos].start_ip = func_addr; | |
| } | |
| _Unwind_Reason_Code result = _URC_NO_REASON; | |
| if (state->trace_function != NULL) | |
| result = (state->trace_function) (state); | |
| state->pos++; | |
| return result; | |
| } | |
| // Return a raw stack trace from the current point of execution. The raw | |
| // trace will include all functions that have unwind info. | |
| _Jv_StackTrace * | |
| _Jv_StackTrace::GetStackTrace(void) | |
| { | |
| int trace_size = 100; | |
| _Jv_StackFrame frames[trace_size]; | |
| _Jv_UnwindState state (trace_size); | |
| state.frames = (_Jv_StackFrame *) &frames; | |
| _Unwind_Backtrace (UnwindTraceFn, &state); | |
| // Copy the trace and return it. | |
| int traceSize = sizeof (_Jv_StackTrace) + | |
| (sizeof (_Jv_StackFrame) * state.pos); | |
| _Jv_StackTrace *trace = (_Jv_StackTrace *) _Jv_AllocBytes (traceSize); | |
| trace->length = state.pos; | |
| memcpy (trace->frames, state.frames, sizeof (_Jv_StackFrame) * state.pos); | |
| return trace; | |
| } | |
| void | |
| _Jv_StackTrace::getLineNumberForFrame(_Jv_StackFrame *frame, NameFinder *finder, | |
| jstring *sourceFileName, jint *lineNum, | |
| jstring *methodName) | |
| { | |
| #ifdef INTERPRETER | |
| if (frame->type == frame_interpreter) | |
| { | |
| _Jv_InterpMethod *interp_meth = frame->interp.meth; | |
| _Jv_InterpClass *interp_class = | |
| (_Jv_InterpClass *) interp_meth->defining_class->aux_info; | |
| *sourceFileName = interp_class->source_file_name; | |
| // The interpreter advances the PC before executing an instruction, | |
| // so roll-back 1 byte to ensure the line number is accurate. | |
| *lineNum = interp_meth->get_source_line(frame->interp.pc - 1); | |
| return; | |
| } | |
| #endif | |
| // Use _Jv_platform_dladdr() to determine in which binary the address IP | |
| // resides. | |
| _Jv_AddrInfo info; | |
| jstring binaryName = NULL; | |
| const char *argv0 = _Jv_GetSafeArg(0); | |
| void *ip = frame->ip; | |
| _Unwind_Ptr offset = 0; | |
| if (_Jv_platform_dladdr (ip, &info)) | |
| { | |
| if (info.file_name) | |
| binaryName = JvNewStringUTF (info.file_name); | |
| else | |
| return; | |
| if (*methodName == NULL && info.sym_name) | |
| *methodName = JvNewStringUTF (info.sym_name); | |
| // addr2line expects relative addresses for shared libraries. | |
| if (strcmp (info.file_name, argv0) == 0) | |
| offset = (_Unwind_Ptr) ip; | |
| else | |
| offset = (_Unwind_Ptr) ip - (_Unwind_Ptr) info.base; | |
| #ifndef HAVE_GETIPINFO | |
| // The unwinder gives us the return address. In order to get the right | |
| // line number for the stack trace, roll it back a little. | |
| offset -= 1; | |
| #endif | |
| finder->lookup (binaryName, (jlong) offset); | |
| *sourceFileName = finder->getSourceFile(); | |
| *lineNum = finder->getLineNum(); | |
| if (*lineNum == -1 && NameFinder::showRaw()) | |
| { | |
| gnu::gcj::runtime::StringBuffer *t = | |
| new gnu::gcj::runtime::StringBuffer(binaryName); | |
| t->append ((jchar)' '); | |
| t->append ((jchar)'['); | |
| // + 1 to compensate for the - 1 adjustment above; | |
| t->append (Long::toHexString (offset + 1)); | |
| t->append ((jchar)']'); | |
| *sourceFileName = t->toString(); | |
| } | |
| } | |
| } | |
| // Look up class and method info for the given stack frame, setting | |
| // frame->klass and frame->meth if they are known. | |
| void | |
| _Jv_StackTrace::FillInFrameInfo (_Jv_StackFrame *frame) | |
| { | |
| jclass klass = NULL; | |
| _Jv_Method *meth = NULL; | |
| if (frame->type == frame_native) | |
| { | |
| klass = _Jv_StackTrace::ClassForFrame (frame); | |
| if (klass != NULL) | |
| // Find method in class | |
| for (int j = 0; j < klass->method_count; j++) | |
| { | |
| void *wncode = UNWRAP_FUNCTION_DESCRIPTOR (klass->methods[j].ncode); | |
| if (wncode == frame->start_ip) | |
| { | |
| meth = &klass->methods[j]; | |
| break; | |
| } | |
| } | |
| } | |
| #ifdef INTERPRETER | |
| else if (frame->type == frame_interpreter) | |
| { | |
| _Jv_InterpMethod *interp_meth = frame->interp.meth; | |
| klass = interp_meth->defining_class; | |
| meth = interp_meth->self; | |
| } | |
| #endif | |
| else | |
| JvFail ("Unknown frame type"); | |
| frame->klass = klass; | |
| frame->meth = meth; | |
| } | |
| // Convert raw stack frames to a Java array of StackTraceElement objects. | |
| JArray< ::java::lang::StackTraceElement *>* | |
| _Jv_StackTrace::GetStackTraceElements (_Jv_StackTrace *trace, | |
| Throwable *throwable __attribute__((unused))) | |
| { | |
| ArrayList *list = new ArrayList (); | |
| #if defined (SJLJ_EXCEPTIONS) && ! defined (WIN32) | |
| // We can't use the nCodeMap without unwinder support. Instead, | |
| // fake the method name by giving the IP in hex - better than nothing. | |
| jstring hex = JvNewStringUTF ("0x"); | |
| for (int i = 0; i < trace->length; i++) | |
| { | |
| jstring sourceFileName = NULL; | |
| jint lineNum = -1; | |
| _Jv_StackFrame *frame = &trace->frames[i]; | |
| jstring className = NULL; | |
| jstring methodName = hex->concat (Long::toHexString ((jlong) frame->ip)); | |
| StackTraceElement *element = new StackTraceElement (sourceFileName, | |
| lineNum, className, methodName, 0); | |
| list->add (element); | |
| } | |
| #else /* SJLJ_EXCEPTIONS && !WIN32 */ | |
| //JvSynchronized (ncodeMap); | |
| UpdateNCodeMap (); | |
| NameFinder *finder = new NameFinder(); | |
| int start_idx = 0; | |
| int end_idx = trace->length - 1; | |
| // First pass: strip superfluous frames from beginning and end of the trace. | |
| for (int i = 0; i < trace->length; i++) | |
| { | |
| _Jv_StackFrame *frame = &trace->frames[i]; | |
| FillInFrameInfo (frame); | |
| if (!frame->klass || !frame->meth) | |
| // Not a Java frame. | |
| continue; | |
| // Throw away the top of the stack till we see: | |
| // - the constructor(s) of this Throwable, or | |
| // - the Throwable.fillInStackTrace call. | |
| if (frame->klass == throwable->getClass() | |
| && strcmp (frame->meth->name->chars(), "<init>") == 0) | |
| start_idx = i + 1; | |
| if (frame->klass == &Throwable::class$ | |
| && strcmp (frame->meth->name->chars(), "fillInStackTrace") == 0) | |
| start_idx = i + 1; | |
| // End the trace at the application's main() method if we see call_main. | |
| if (frame->klass == &gnu::java::lang::MainThread::class$ | |
| && strcmp (frame->meth->name->chars(), "call_main") == 0) | |
| end_idx = i - 1; | |
| } | |
| const jboolean remove_unknown | |
| = gnu::gcj::runtime::NameFinder::removeUnknown(); | |
| // Second pass: Look up line-number info for remaining frames. | |
| for (int i = start_idx; i <= end_idx; i++) | |
| { | |
| _Jv_StackFrame *frame = &trace->frames[i]; | |
| if (frame->klass == NULL && remove_unknown) | |
| // Not a Java frame. | |
| continue; | |
| jstring className = NULL; | |
| if (frame->klass != NULL) | |
| className = frame->klass->getName (); | |
| jstring methodName = NULL; | |
| if (frame->meth) | |
| methodName = JvNewStringUTF (frame->meth->name->chars()); | |
| jstring sourceFileName = NULL; | |
| jint lineNum = -1; | |
| getLineNumberForFrame(frame, finder, &sourceFileName, &lineNum, | |
| &methodName); | |
| StackTraceElement *element = new StackTraceElement (sourceFileName, lineNum, | |
| className, methodName, 0); | |
| list->add (element); | |
| } | |
| finder->close(); | |
| #endif /* SJLJ_EXCEPTIONS && !WIN32 */ | |
| JArray<Object *> *array = JvNewObjectArray (list->size (), | |
| &StackTraceElement::class$, NULL); | |
| return (JArray<StackTraceElement *>*) list->toArray (array); | |
| } | |
| struct CallingClassTraceData | |
| { | |
| jclass checkClass; | |
| jclass foundClass; | |
| _Jv_Method *foundMeth; | |
| bool seen_checkClass; | |
| }; | |
| _Unwind_Reason_Code | |
| _Jv_StackTrace::calling_class_trace_fn (_Jv_UnwindState *state) | |
| { | |
| CallingClassTraceData *trace_data = (CallingClassTraceData *) | |
| state->trace_data; | |
| _Jv_StackFrame *frame = &state->frames[state->pos]; | |
| FillInFrameInfo (frame); | |
| if (trace_data->seen_checkClass | |
| && frame->klass | |
| && frame->klass != trace_data->checkClass) | |
| { | |
| trace_data->foundClass = frame->klass; | |
| trace_data->foundMeth = frame->meth; | |
| return _URC_NORMAL_STOP; | |
| } | |
| if (frame->klass == trace_data->checkClass) | |
| trace_data->seen_checkClass = true; | |
| return _URC_NO_REASON; | |
| } | |
| // Find the class immediately above the given class on the call stack. Any | |
| // intermediate non-Java | |
| // frames are ignored. If the calling class could not be determined (eg because | |
| // the unwinder is not supported on this platform), NULL is returned. | |
| // This function is used to implement calling-classloader checks and reflection | |
| // accessibility checks. | |
| // CHECKCLASS is typically the class calling GetCallingClass. The first class | |
| // above CHECKCLASS on the call stack will be returned. | |
| jclass | |
| _Jv_StackTrace::GetCallingClass (jclass checkClass) | |
| { | |
| jclass result = NULL; | |
| GetCallerInfo (checkClass, &result, NULL); | |
| return result; | |
| } | |
| void | |
| _Jv_StackTrace::GetCallerInfo (jclass checkClass, jclass *caller_class, | |
| _Jv_Method **caller_meth) | |
| { | |
| int trace_size = 20; | |
| _Jv_StackFrame frames[trace_size]; | |
| _Jv_UnwindState state (trace_size); | |
| state.frames = (_Jv_StackFrame *) &frames; | |
| CallingClassTraceData trace_data; | |
| trace_data.checkClass = checkClass; | |
| trace_data.seen_checkClass = false; | |
| trace_data.foundClass = NULL; | |
| trace_data.foundMeth = NULL; | |
| state.trace_function = calling_class_trace_fn; | |
| state.trace_data = (void *) &trace_data; | |
| //JvSynchronized (ncodeMap); | |
| UpdateNCodeMap (); | |
| _Unwind_Backtrace (UnwindTraceFn, &state); | |
| if (caller_class) | |
| *caller_class = trace_data.foundClass; | |
| if (caller_meth) | |
| *caller_meth = trace_data.foundMeth; | |
| } | |
| // Return a java array containing the Java classes on the stack above CHECKCLASS. | |
| JArray<jclass> * | |
| _Jv_StackTrace::GetClassContext (jclass checkClass) | |
| { | |
| JArray<jclass> *result = NULL; | |
| int trace_size = 100; | |
| _Jv_StackFrame frames[trace_size]; | |
| _Jv_UnwindState state (trace_size); | |
| state.frames = (_Jv_StackFrame *) &frames; | |
| //JvSynchronized (ncodeMap); | |
| UpdateNCodeMap (); | |
| _Unwind_Backtrace (UnwindTraceFn, &state); | |
| // Count the number of Java frames on the stack. | |
| int jframe_count = 0; | |
| bool seen_checkClass = false; | |
| int start_pos = -1; | |
| for (int i = 0; i < state.pos; i++) | |
| { | |
| _Jv_StackFrame *frame = &state.frames[i]; | |
| FillInFrameInfo (frame); | |
| if (seen_checkClass) | |
| { | |
| if (frame->klass) | |
| { | |
| jframe_count++; | |
| if (start_pos == -1) | |
| start_pos = i; | |
| } | |
| } | |
| else | |
| seen_checkClass = frame->klass == checkClass; | |
| } | |
| result = (JArray<jclass> *) _Jv_NewObjectArray (jframe_count, &Class::class$, NULL); | |
| int pos = 0; | |
| for (int i = start_pos; i < state.pos; i++) | |
| { | |
| _Jv_StackFrame *frame = &state.frames[i]; | |
| if (frame->klass) | |
| elements(result)[pos++] = frame->klass; | |
| } | |
| return result; | |
| } | |
| _Unwind_Reason_Code | |
| _Jv_StackTrace::non_system_trace_fn (_Jv_UnwindState *state) | |
| { | |
| _Jv_StackFrame *frame = &state->frames[state->pos]; | |
| FillInFrameInfo (frame); | |
| ClassLoader *classLoader = NULL; | |
| if (frame->klass) | |
| { | |
| classLoader = frame->klass->getClassLoaderInternal(); | |
| #ifdef INTERPRETER | |
| if (classLoader != NULL) | |
| { | |
| state->trace_data = (void *) classLoader; | |
| return _URC_NORMAL_STOP; | |
| } | |
| #endif | |
| } | |
| return _URC_NO_REASON; | |
| } | |
| ClassLoader * | |
| _Jv_StackTrace::GetFirstNonSystemClassLoader () | |
| { | |
| int trace_size = 32; | |
| _Jv_StackFrame frames[trace_size]; | |
| _Jv_UnwindState state (trace_size); | |
| state.frames = (_Jv_StackFrame *) &frames; | |
| state.trace_function = non_system_trace_fn; | |
| state.trace_data = NULL; | |
| //JvSynchronized (ncodeMap); | |
| UpdateNCodeMap (); | |
| _Unwind_Backtrace (UnwindTraceFn, &state); | |
| if (state.trace_data) | |
| return (ClassLoader *) state.trace_data; | |
| return NULL; | |
| } | |
| struct AccessControlTraceData | |
| { | |
| jint length; | |
| jboolean privileged; | |
| }; | |
| _Unwind_Reason_Code | |
| _Jv_StackTrace::accesscontrol_trace_fn (_Jv_UnwindState *state) | |
| { | |
| AccessControlTraceData *trace_data = (AccessControlTraceData *) | |
| state->trace_data; | |
| _Jv_StackFrame *frame = &state->frames[state->pos]; | |
| FillInFrameInfo (frame); | |
| if (!(frame->klass && frame->meth)) | |
| return _URC_NO_REASON; | |
| trace_data->length++; | |
| // If the previous frame was a call to doPrivileged, then this is | |
| // the last frame we look at. | |
| if (trace_data->privileged) | |
| return _URC_NORMAL_STOP; | |
| if (frame->klass == &::java::security::AccessController::class$ | |
| && strcmp (frame->meth->name->chars(), "doPrivileged") == 0) | |
| trace_data->privileged = true; | |
| return _URC_NO_REASON; | |
| } | |
| jobjectArray | |
| _Jv_StackTrace::GetAccessControlStack (void) | |
| { | |
| int trace_size = 100; | |
| _Jv_StackFrame frames[trace_size]; | |
| _Jv_UnwindState state (trace_size); | |
| state.frames = (_Jv_StackFrame *) &frames; | |
| AccessControlTraceData trace_data; | |
| trace_data.length = 0; | |
| trace_data.privileged = false; | |
| state.trace_function = accesscontrol_trace_fn; | |
| state.trace_data = (void *) &trace_data; | |
| UpdateNCodeMap(); | |
| _Unwind_Backtrace (UnwindTraceFn, &state); | |
| JArray<jclass> *classes = (JArray<jclass> *) | |
| _Jv_NewObjectArray (trace_data.length, &::java::lang::Class::class$, NULL); | |
| jclass *c = elements (classes); | |
| for (int i = 0, j = 0; i < state.pos; i++) | |
| { | |
| _Jv_StackFrame *frame = &state.frames[i]; | |
| if (!frame->klass || !frame->meth) | |
| continue; | |
| c[j] = frame->klass; | |
| j++; | |
| } | |
| jobjectArray result = | |
| (jobjectArray) _Jv_NewObjectArray (2, &::java::lang::Object::class$, | |
| NULL); | |
| jobject *r = elements (result); | |
| r[0] = (jobject) classes; | |
| r[1] = (jobject) new Boolean (trace_data.privileged); | |
| return result; | |
| } |