diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 06b507e1053..1a457df2005 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,12 @@ +2007-02-16 Keith Seitz + + * gnu/classpath/jdwp/natVMVirtualMachine.cc + (get_line_table): New function. + (handle_single_step): New function. + (jdwpSingleStepCB): New function. + (jdwpVMInitCB): Define a JVMTI single step + callback, but don't enable it until needed. + 2007-02-16 David Daney * java/lang/Thread.java (Thread(ThreadGroup, Runnable, String)): Pass diff --git a/libjava/gnu/classpath/jdwp/natVMVirtualMachine.cc b/libjava/gnu/classpath/jdwp/natVMVirtualMachine.cc index 71aa674054a..c47d440ea2f 100644 --- a/libjava/gnu/classpath/jdwp/natVMVirtualMachine.cc +++ b/libjava/gnu/classpath/jdwp/natVMVirtualMachine.cc @@ -29,6 +29,8 @@ details. */ #include #include +#include +#include #include #include #include @@ -36,6 +38,7 @@ details. */ #include #include #include +#include #include #include #include @@ -67,12 +70,18 @@ struct step_info }; // Forward declarations +static jvmtiError get_linetable (jvmtiEnv *, jmethodID, jint *, + jvmtiLineNumberEntry **); static Location *get_request_location (EventRequest *); static gnu::classpath::jdwp::event::filters::StepFilter * get_request_step_filter (EventRequest *); +static void handle_single_step (jvmtiEnv *, struct step_info *, jthread, + jmethodID, jlocation); static void JNICALL jdwpBreakpointCB (jvmtiEnv *, JNIEnv *, jthread, jmethodID, jlocation); static void JNICALL jdwpClassPrepareCB (jvmtiEnv *, JNIEnv *, jthread, jclass); +static void JNICALL jdwpSingleStepCB (jvmtiEnv *, JNIEnv *, jthread, + jmethodID, jlocation); static void JNICALL jdwpThreadEndCB (jvmtiEnv *, JNIEnv *, jthread); static void JNICALL jdwpThreadStartCB (jvmtiEnv *, JNIEnv *, jthread); static void JNICALL jdwpVMDeathCB (jvmtiEnv *, JNIEnv *); @@ -624,6 +633,38 @@ getSourceFile (MAYBE_UNUSED jclass clazz) return NULL; } +// A simple caching function used while single-stepping +static jvmtiError +get_linetable (jvmtiEnv *env, jmethodID method, jint *count_ptr, + jvmtiLineNumberEntry **table_ptr) +{ + static jint last_count = 0; + static jvmtiLineNumberEntry *last_table = NULL; + static jmethodID last_method = 0; + + if (method == last_method) + { + *count_ptr = last_count; + *table_ptr = last_table; + return JVMTI_ERROR_NONE; + } + + jvmtiError err; + jint count; + jvmtiLineNumberEntry *table; + err = env->GetLineNumberTable (method, &count, &table); + if (err != JVMTI_ERROR_NONE) + { + // Keep last table in cache + return err; + } + + env->Deallocate ((unsigned char *) last_table); + last_table = *table_ptr = table; + last_count = *count_ptr = count; + return JVMTI_ERROR_NONE; +} + static gnu::classpath::jdwp::event::filters::StepFilter * get_request_step_filter (EventRequest *request) { @@ -665,6 +706,71 @@ get_request_location (EventRequest *request) return loc; } +static void +handle_single_step (jvmtiEnv *env, struct step_info *sinfo, jthread thread, + jmethodID method, jlocation location) +{ + using namespace gnu::classpath::jdwp; + + if (sinfo == NULL || sinfo->size == JdwpConstants$StepSize::MIN) + { + // Stop now + goto send_notification; + } + else + { + // Check if we're on a new source line + /* This is a little inefficient when we're stepping OVER, + but this must be done when stepping INTO. */ + jint count; + jvmtiLineNumberEntry *table; + if (get_linetable (env, method, &count, &table) == JVMTI_ERROR_NONE) + { + jint i; + for (i = 0; i < count; ++i) + { + if (table[i].start_location == location) + { + // This is the start of a new line -- stop + goto send_notification; + } + } + + // Not at a new source line -- just keep stepping + return; + } + else + { + /* Something went wrong: either "absent information" + or "out of memory" ("invalid method id" and "native + method" aren't possible -- those are validated before + single stepping is enabled). + + Do what gdb does: just keep going. */ + return; + } + } + + send_notification: + jclass klass; + jvmtiError err = env->GetMethodDeclaringClass (method, &klass); + if (err != JVMTI_ERROR_NONE) + { + fprintf (stderr, "libgcj: internal error: could not find class for method while single stepping -- continuing\n"); + return; + } + + VMMethod *vmmethod = new VMMethod (klass, reinterpret_cast (method)); + Location *loc = new Location (vmmethod, location); + JvAssert (thread->frame.frame_type == frame_interpreter); + _Jv_InterpFrame *iframe + = reinterpret_cast<_Jv_InterpFrame *> (thread->interp_frame); + jobject instance = iframe->get_this_ptr (); + event::SingleStepEvent *event + = new event::SingleStepEvent (thread, loc, instance); + Jdwp::notify (event); +} + static void throw_jvmti_error (jvmtiError err) { @@ -732,6 +838,73 @@ jdwpClassPrepareCB (jvmtiEnv *env, MAYBE_UNUSED JNIEnv *jni_env, Jdwp::notify (event); } +static void JNICALL +jdwpSingleStepCB (jvmtiEnv *env, JNIEnv *jni_env, jthread thread, + jmethodID method, jlocation location) +{ + jobject si = + gnu::classpath::jdwp::VMVirtualMachine::_stepping_threads->get (thread); + struct step_info *sinfo = reinterpret_cast (si); + + if (sinfo == NULL) + { + // no step filter for this thread - simply report it + handle_single_step (env, NULL, thread, method, location); + } + else + { + // A step filter exists for this thread + using namespace gnu::classpath::jdwp; + + _Jv_InterpFrame *frame + = reinterpret_cast<_Jv_InterpFrame *> (thread->interp_frame); + + switch (sinfo->depth) + { + case JdwpConstants$StepDepth::INTO: + /* This is the easy case. We ignore the method and + simply stop at either the next insn, or the next source + line. */ + handle_single_step (env, sinfo, thread, method, location); + break; + + case JdwpConstants$StepDepth::OVER: + /* This is also a pretty easy case. We just make sure that + the methods are the same and that we are at the same + stack depth, but we should also stop on the next + insn/line if the stack depth is LESS THAN it was when + we started stepping. */ + if (method == sinfo->method) + { + // Still in the same method -- must be at same stack depth + // to avoid confusion with recursive methods. + if (frame->depth () == sinfo->stack_depth) + handle_single_step (env, sinfo, thread, method, location); + } + else if (frame->depth () < sinfo->stack_depth) + { + // The method in which we were stepping was popped off + // the stack. We simply need to stop at the next insn/line. + handle_single_step (env, sinfo, thread, method, location); + } + break; + + case JdwpConstants$StepDepth::OUT: + // All we need to do is check the stack depth + if (sinfo->stack_depth > frame->depth ()) + handle_single_step (env, sinfo, thread, method, location); + break; + + default: + /* This should not happen. The JDWP back-end should have + validated the StepFilter. */ + fprintf (stderr, + "libgcj: unknown step depth while single stepping\n"); + return; + } + } +} + static void JNICALL jdwpThreadEndCB (MAYBE_UNUSED jvmtiEnv *env, MAYBE_UNUSED JNIEnv *jni_env, jthread thread) @@ -767,6 +940,7 @@ jdwpVMInitCB (MAYBE_UNUSED jvmtiEnv *env, MAYBE_UNUSED JNIEnv *jni_env, jvmtiEventCallbacks callbacks; DEFINE_CALLBACK (callbacks, Breakpoint); DEFINE_CALLBACK (callbacks, ClassPrepare); + DEFINE_CALLBACK (callbacks, SingleStep); DEFINE_CALLBACK (callbacks, ThreadEnd); DEFINE_CALLBACK (callbacks, ThreadStart); DEFINE_CALLBACK (callbacks, VMDeath); @@ -775,6 +949,7 @@ jdwpVMInitCB (MAYBE_UNUSED jvmtiEnv *env, MAYBE_UNUSED JNIEnv *jni_env, // Enable callbacks ENABLE_EVENT (BREAKPOINT, NULL); ENABLE_EVENT (CLASS_PREPARE, NULL); + // SingleStep is enabled only when needed ENABLE_EVENT (THREAD_END, NULL); ENABLE_EVENT (THREAD_START, NULL); ENABLE_EVENT (VM_DEATH, NULL);