summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/Hello.java21
-rw-r--r--java/Makefile.am46
-rw-r--r--java/README40
-rw-r--r--java/TestRtflObjects1.java50
-rw-r--r--java/class.c39
-rw-r--r--java/class.h9
-rw-r--r--java/field.c156
-rw-r--r--java/field.h12
-rw-r--r--java/main.c68
-rw-r--r--java/method.c111
-rw-r--r--java/method.h12
-rw-r--r--java/misc.c191
-rw-r--r--java/misc.h41
13 files changed, 796 insertions, 0 deletions
diff --git a/java/Hello.java b/java/Hello.java
new file mode 100644
index 0000000..04eadd7
--- /dev/null
+++ b/java/Hello.java
@@ -0,0 +1,21 @@
+package rtfl;
+
+public class Hello
+{
+ int i1;
+ Integer i2;
+ String s1;
+
+ public static void main (String[] args)
+ {
+ new Hello().hello();
+ }
+
+ public void hello()
+ {
+ System.out.println ("===== Hello Java! =====");
+ i1 = 5;
+ i2 = new Integer(7);
+ s1 = "Hi!";
+ }
+}
diff --git a/java/Makefile.am b/java/Makefile.am
new file mode 100644
index 0000000..9d3f511
--- /dev/null
+++ b/java/Makefile.am
@@ -0,0 +1,46 @@
+AM_CFLAGS = -Wall $(JAVA_CFLAGS)
+
+JAVA = $(JAVA_HOME)/jre/bin/java
+JAVAC = $(JAVA_HOME)/bin/javac
+
+lib_LTLIBRARIES = librtfl-jvm-ti.la
+
+librtfl_jvm_ti_la_SOURCES = \
+ main.c \
+ class.h \
+ class.c \
+ method.h \
+ method.c \
+ field.h \
+ field.c \
+ config.h \
+ config.c \
+ misc.h \
+ misc.c
+
+EXTRA_DIST = README Hello.java TestRtflObjects1.java
+
+# Run tests without installation.
+LIBPATH=./.libs
+
+run-hello: $(LIBPATH)/librtfl-jvm-ti.so rtfl/Hello.class
+ LD_LIBRARY_PATH=$(LIBPATH) $(JAVA) -agentlib:rtfl-jvm-ti rtfl.Hello
+
+run-test-rtfl-objects-1: $(LIBPATH)/librtfl-jvm-ti.so rtfl/TestRtflObjects1.class
+ LD_LIBRARY_PATH=$(LIBPATH) $(JAVA) -agentlib:rtfl-jvm-ti rtfl.TestRtflObjects1
+
+run-test-rtfl-objects-2: $(LIBPATH)/librtfl-jvm-ti.so rtfl/TestRtflObjects2.class
+ LD_LIBRARY_PATH=$(LIBPATH) $(JAVA) -agentlib:rtfl-jvm-ti rtfl.TestRtflObjects2
+
+rtfl/Hello.class: Hello.java
+ $(JAVAC) -g -d . Hello.java
+
+rtfl/TestRtflObjects1.class: TestRtflObjects1.java
+ $(JAVAC) -g -d . TestRtflObjects1.java
+
+rtfl/TestRtflObjects2.class: TestRtflObjects2.java
+ $(JAVAC) -g -d . TestRtflObjects2.java
+
+clean-local:
+ find -name "*.class" | xargs rm -f
+
diff --git a/java/README b/java/README
new file mode 100644
index 0000000..e0196a0
--- /dev/null
+++ b/java/README
@@ -0,0 +1,40 @@
+This sub-project aims to provide an agent for the Java Virtual
+Machine, which uses the JVM Tool Interface (see [1] and [2]) to print
+RTFL messages for Java programs, which must not necessarily be
+prepared. This makes using RTFL much less time-consuming and so lowers
+the entrance barrier.
+
+The agent consists of the file "librtfl-jvm-ti.so", which is passed to
+"java" by the option "-agentlib:rtfl-jvm-ti"; see Makefile.am for
+details.
+
+Run "make run-hello" or "run run-test-rtfl-objects-1" to run some
+sample programs with the agent.
+
+
+How it works
+------------
+Several commands from the object module (like "create") follow the
+program structure, and can so easily be printed when the respective
+JVM-TI event is processed. Others (like "enter") can be printed by
+this approach, but some parameters are missing (aspect and priority)
+and so replaced by standard values (empty aspect and priority 0). It
+is planned to make these parameters configurable, either by files or
+by annotations.
+
+A third group of commands (like "msg") must still be added explicitly
+to code. (For "msg", one could think of an integration with existing
+logging frameworks.)
+
+Since Java programs often use many third-party code (libraries,
+containers, ...), it is crucial for the performance to filter quite
+early, and so reduce the total amount of messages. Filtering by
+packages seems feasible (like "com.acme" and sub-packages when code of
+ACME is debugged). Currently, only "rtfl" and its sub-packages are
+considered; this is hard-coded in the function include_class() in
+"config.c".
+
+----------------------------------------------------------------------
+
+[1] http://docs.oracle.com/javase/8/docs/technotes/guides/jvmti/index.html
+[2] http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html
diff --git a/java/TestRtflObjects1.java b/java/TestRtflObjects1.java
new file mode 100644
index 0000000..fece128
--- /dev/null
+++ b/java/TestRtflObjects1.java
@@ -0,0 +1,50 @@
+package rtfl;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class TestRtflObjects1
+{
+ private static class A
+ {
+ private A other = null;
+ private List<Integer> numbers = new LinkedList<Integer>();
+
+ public void setOther (A other)
+ {
+ this.other = other;
+ }
+
+ public int doSomething (int n)
+ {
+ int r = (int)(Math.random() * 251);
+ numbers.add (new Integer (r));
+
+ if (other != null && n > 0)
+ other.doSomething (n - 1);
+
+ return r;
+ }
+ }
+
+ private static class B extends A
+ {
+ }
+
+ private static class C extends A
+ {
+ }
+
+ public static void main (String[] args)
+ {
+ A x = new A ();
+ B y = new B ();
+ C z = new C ();
+
+ x.setOther (y);
+ y.setOther (z);
+ z.setOther (x);
+
+ x.doSomething (8);
+ }
+}
diff --git a/java/class.c b/java/class.c
new file mode 100644
index 0000000..d4e78ae
--- /dev/null
+++ b/java/class.c
@@ -0,0 +1,39 @@
+#include "class.h"
+#include "config.h"
+#include "misc.h"
+
+void JNICALL class_prepare(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jclass klass)
+{
+ jvmtiError error;
+ char *class_sig = NULL, *class_name = NULL;
+
+ if ((error = (*jvmti)->GetClassSignature (jvmti, klass, &class_sig, NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetClassSignature");
+ else {
+ if ((class_name = get_class_name_from_sig (class_sig, TRUE)) &&
+ include_class (class_name)) {
+ jint field_count;
+ jfieldID* fields;
+ if ((error = (*jvmti)->GetClassFields (jvmti, klass, &field_count,
+ &fields))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetClassFields");
+ else {
+ int i;
+ for (i = 0; i < field_count; i++) {
+ if ((error =
+ (*jvmti)->SetFieldModificationWatch (jvmti, klass,
+ fields[i]))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "SetFieldModificationWatch");
+ }
+ }
+ }
+ }
+
+ jvmti_dealloc (jvmti, class_sig);
+ simple_free (class_name);
+
+}
diff --git a/java/class.h b/java/class.h
new file mode 100644
index 0000000..41ffb74
--- /dev/null
+++ b/java/class.h
@@ -0,0 +1,9 @@
+#ifndef __JAVA_CLASS_H__
+#define __JAVA_CLASS_H__
+
+#include <jvmti.h>
+
+void JNICALL class_prepare(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jclass klass);
+
+#endif /* __JAVA_CLASS_H__ */
diff --git a/java/field.c b/java/field.c
new file mode 100644
index 0000000..3427218
--- /dev/null
+++ b/java/field.c
@@ -0,0 +1,156 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "field.h"
+#include "config.h"
+#include "misc.h"
+
+/* ----------------------------------------------------------------------
+
+ A sketch on how field modifications could be processed:
+
+ A field modification results in a "set" or in an "assoc"
+ command. Neither is the field name *necessarily* the name of the "set"
+ command (or "assoc", if it would support names), nor is the field
+ value necessarily the respective value. Instead, the following rules
+ are used to determine the structure of the fields.
+
+ (A) If the value type is "printable" (i. e. primitive, or a wrapper
+ class for a primitive type, or java.lang.String, or an enum (more
+ cases?)), then a set command is printed, with the name and the value
+ of the field.
+
+ (B) If the value is a visible object, and it is not configured as
+ sub-structure (see below), an "assoc" command is printed, between this
+ object and the value object. (As soon as associations get a name, the
+ field name is used for this.)
+
+ (C) If the value is an invisible object, or a visible object which is
+ configured as sub-structure, regard it as structured, according to the
+ rules which follow.
+
+ Structured values are divided into sub-values with sub-names; the
+ field name, followed by a dot, is used as base-name; all sub-values
+ and sub-names are processed recursively according to the same rules,
+ while the base-name is eventually prepended to all sub-names.
+
+ (C1) If the value is of type java.util.Map, and the keys are printable
+ (see (A)), use the keys as sub-names, the values as sub-values. ("The
+ keys are printable": does this mean that the type for the keys is a
+ class which is generally printable?)
+
+ (C2) If the value is of type java.util.Map, and the keys are not
+ printable (see also C1), regard the value as unsorted list of
+ elements, which are themselves structured sub-values
+ (java.util.Map.Entry?).
+
+ (C3) If the value is of type java.util.List, use the indices as
+ sub-names, and the elements as sub-values.
+
+ (C4) If the value is an unsorted collection, i. e. of type
+ java.util.Collection, but not of java.util.Map or java.util.List,
+ apply a random order and proceed as in (C4). (Unsorted collections are
+ not really supported in RTFL.)
+
+ The rules (C1) to (C4) apply not when configured otherwise ("do not
+ show logical structure").
+
+ (C5) Otherwise (not of type java.util.Collection or java.util.Map),
+ examine fields of the value; regard the names of its fields as
+ sub-names; their values as sub-values;
+
+ (Alternative: Java Beans?)
+
+ For simplification, ignore C1 to C4 in the first place and focus on
+ C5; then implement C1 to C4 by and by.
+
+ ---------------------------------------------------------------------- */
+
+static void print_field (jvmtiEnv *jvmti, JNIEnv* jni, jclass field_klass,
+ jobject object, jfieldID field, jvalue value,
+ char *base_name);
+static void print_string_field (jvmtiEnv *jvmti, JNIEnv* jni, char *object_str,
+ char *base_name, char *field_name,
+ jstring string);
+
+void JNICALL field_modification (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method, jlocation location,
+ jclass field_klass, jobject object,
+ jfieldID field, char signature_type,
+ jvalue new_value)
+{
+ // The class name is irrelevant here, since classes are already
+ // filtered inclass_prepare(), where SetFieldModificationWatch()
+ // is called.
+
+ print_field (jvmti, jni, field_klass, object, field, new_value, "");
+}
+
+void print_field (jvmtiEnv *jvmti, JNIEnv* jni, jclass field_klass,
+ jobject object, jfieldID field, jvalue value, char *base_name)
+{
+ jvmtiError error;
+ char object_buf1[SIZE_OBJECT_BUF], object_buf2[SIZE_OBJECT_BUF];
+ char *class_name = NULL, *field_name = NULL, *field_sig = NULL;
+
+ if ((error = (*jvmti)->GetFieldName (jvmti, field_klass, field, &field_name,
+ &field_sig, NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetFieldName");
+ else {
+ //printf ("==> %s - %s\n", field_name, field_sig);
+
+ fill_object_buf (jni, object_buf1, object);
+
+ if ((class_name = get_class_name_from_sig (field_sig, FALSE))) {
+ if (include_class (class_name)) {
+ // The field represents an instance of a class which is also
+ // included: this is an association, not a simple field.
+ // ("field_name" could be used here, as soon as associations get
+ // a name.)
+
+ if (value.l) {
+ fill_object_buf (jni, object_buf2, value.l);
+ RTFL_OBJ_PRINT ("assoc", "s:s", object_buf1, object_buf2);
+ }
+ } else {
+ if (strcmp (class_name, "java.lang.String")) {
+ print_string_field (jvmti, jni, object_buf1, base_name,
+ field_name, (jstring)(value.l));
+
+ }
+ }
+ }
+ }
+
+ jvmti_dealloc (jvmti, field_name);
+ jvmti_dealloc (jvmti, field_sig);
+ simple_free (class_name);
+}
+
+void print_string_field (jvmtiEnv *jvmti, JNIEnv* jni, char *object_str,
+ char *base_name, char *field_name, jstring string)
+{
+ if (string == NULL)
+ RTFL_OBJ_PRINT ("set", "s:ss:s", object_str, base_name, field_name,
+ "null");
+ else {
+ // TODO: JNI calls somehow cause an abortion. Just some test
+ // code.
+
+ //jsize len = (*jni)->GetStringLength (jni, string);
+ //const jchar *chars = (*jni)->GetStringChars (jni, string, NULL);
+
+ //char *chars0 = (char*)malloc (len + 1);
+ //memcpy (chars0, chars, len);
+ //chars0[len] = 0;
+
+ char *chars0 = "?\"?\"?";
+
+ RTFL_OBJ_PRINT ("set", "s:ss:\"q\"", object_str, base_name, field_name,
+ chars0);
+
+ //free (chars0);
+ //(*jni)->ReleaseStringChars (jni, string, chars);
+ }
+}
diff --git a/java/field.h b/java/field.h
new file mode 100644
index 0000000..6d580ee
--- /dev/null
+++ b/java/field.h
@@ -0,0 +1,12 @@
+#ifndef __JAVA_FIELD_H__
+#define __JAVA_FIELD_H__
+
+#include <jvmti.h>
+
+void JNICALL field_modification (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method, jlocation location,
+ jclass field_klass, jobject object,
+ jfieldID field, char signature_type,
+ jvalue new_value);
+
+#endif /* __JAVA_FIELD_H__ */
diff --git a/java/main.c b/java/main.c
new file mode 100644
index 0000000..dc7e954
--- /dev/null
+++ b/java/main.c
@@ -0,0 +1,68 @@
+#include <jvmti.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "class.h"
+#include "method.h"
+#include "field.h"
+#include "misc.h"
+
+JNIEXPORT jint JNICALL Agent_OnLoad (JavaVM *jvm, char *options, void *reserved)
+{
+ rtfl_print ("obj", RTFL_OBJ_VERSION, "", 0, "s", "noident");
+
+ jvmtiEnv *jvmti = NULL;
+
+ (*jvm)->GetEnv (jvm, (void**)&jvmti, JVMTI_VERSION_1_0);
+
+ jvmtiCapabilities capa;
+ memset (&capa, 0, sizeof(jvmtiCapabilities));
+ capa.can_generate_method_entry_events = 1;
+ capa.can_generate_method_exit_events = 1;
+ capa.can_access_local_variables = 1;
+ capa.can_generate_field_modification_events = 1;
+
+ jvmtiEventCallbacks callbacks;
+ (void)memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.ClassPrepare = &class_prepare;
+ callbacks.MethodEntry = &method_entry;
+ callbacks.MethodExit = &method_exit;
+ callbacks.FieldModification = &field_modification;
+
+ jvmtiError error;
+ if ((error = (*jvmti)->AddCapabilities(jvmti, &capa)) != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "AddCapabilities");
+ else if ((error =
+ (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE,
+ JVMTI_EVENT_CLASS_PREPARE,
+ (jthread)NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error,
+ "SetEventNotificationMode (JVMTI_EVENT_CLASS_PREPARE)");
+ else if ((error =
+ (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE,
+ JVMTI_EVENT_METHOD_ENTRY,
+ (jthread)NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error,
+ "SetEventNotificationMode (JVMTI_EVENT_METHOD_ENTRY)");
+ else if ((error =
+ (*jvmti)->SetEventNotificationMode (jvmti, JVMTI_ENABLE,
+ JVMTI_EVENT_METHOD_EXIT,
+ (jthread)NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error,
+ "SetEventNotificationMode (JVMTI_EVENT_METHOD_EXIT)");
+ else if ((error =
+ (*jvmti)->SetEventNotificationMode
+ (jvmti, JVMTI_ENABLE, JVMTI_EVENT_FIELD_MODIFICATION,
+ (jthread)NULL)) != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error,
+ "SetEventNotificationMode (JVMTI_EVENT_FIELD_MODIFICATION");
+ else if ((error = (*jvmti)->SetEventCallbacks(jvmti, &callbacks,
+ (jint)sizeof(callbacks)))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "SetEventCallbacks");
+
+ return JNI_OK;
+}
diff --git a/java/method.c b/java/method.c
new file mode 100644
index 0000000..35e6022
--- /dev/null
+++ b/java/method.c
@@ -0,0 +1,111 @@
+#include <string.h>
+
+#include "method.h"
+#include "config.h"
+#include "misc.h"
+
+static bool handle_method (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method, char **object, char **method_name,
+ char **class_name)
+{
+ jvmtiError error;
+ char *class_sig = NULL, object_buf[SIZE_OBJECT_BUF];
+ jclass klass;
+ bool success = FALSE;
+
+ *object = *method_name = *class_name = NULL;
+
+ if ((error =
+ (*jvmti)->GetMethodName (jvmti, method, method_name, NULL, NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetMethodName");
+ else if ((error =
+ (*jvmti)->GetMethodDeclaringClass (jvmti, method, &klass))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetMethodDeclaringClass");
+ else if ((error =
+ (*jvmti)->GetClassSignature (jvmti, klass, &class_sig, NULL))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetClassSignature");
+ else {
+ if ((*class_name = get_class_name_from_sig (class_sig, TRUE)) &&
+ include_class (*class_name)) {
+ jint numlocals;
+ jvmtiLocalVariableEntry *locals;
+ if ((error = (*jvmti)->GetLocalVariableTable (jvmti, method,
+ &numlocals, &locals))
+ != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "GetLocalVariableTable");
+ else {
+ jint j, this_slot = -1;
+ for (j = 0; j < numlocals && this_slot == -1; j++)
+ if (strcmp (locals[j].name, "this") == 0)
+ this_slot = locals[j].slot;
+
+ jobject this_obj;
+
+ if (this_slot == -1) {
+ this_obj = NULL;
+ success = TRUE;
+ } else {
+ if ((error = (*jvmti)->GetLocalObject (jvmti, thread, 0,
+ this_slot, &this_obj))
+ != JVMTI_ERROR_NONE) {
+ jvmti_error (jvmti, error, "GetLocalObject");
+ } else
+ success = TRUE;
+ }
+
+ if (success) {
+ fill_object_buf (jni, object_buf, this_obj);
+ *object = strdup (object_buf);
+ }
+ }
+ }
+ }
+
+ jvmti_dealloc (jvmti, class_sig);
+
+ return success;
+}
+
+static void handle_method_free (jvmtiEnv *jvmti, char *object,
+ char *method_name, char *class_name)
+{
+ simple_free (object);
+ jvmti_dealloc (jvmti, method_name);
+ simple_free (class_name);
+}
+
+void JNICALL method_entry (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method)
+{
+ char *object, *method_name, *class_name;
+
+ if (handle_method (jvmti, jni, thread, method, &object, &method_name,
+ &class_name)) {
+ if (strcmp (method_name, "<init>") == 0)
+ RTFL_OBJ_PRINT ("create", "s:s", object, class_name);
+ else
+ RTFL_OBJ_PRINT ("enter", "s:s:d:s:",
+ object, "", 0, method_name, class_name);
+ }
+
+ handle_method_free (jvmti, object, method_name, class_name);
+}
+
+void JNICALL method_exit (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method, jboolean was_popped_by_exception,
+ jvalue return_value)
+{
+ char *object, *method_name, *class_name;
+
+ if (handle_method (jvmti, jni, thread, method, &object, &method_name,
+ &class_name)) {
+ if (strcmp (method_name, "<init>") != 0)
+ // Hopefully, this is the correct method.
+ RTFL_OBJ_PRINT ("leave", "s", object);
+ }
+
+ handle_method_free (jvmti, object, method_name, class_name);
+}
diff --git a/java/method.h b/java/method.h
new file mode 100644
index 0000000..e3dfc45
--- /dev/null
+++ b/java/method.h
@@ -0,0 +1,12 @@
+#ifndef __JAVA_METHOD_H__
+#define __JAVA_METHOD_H__
+
+#include <jvmti.h>
+
+void JNICALL method_entry (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method);
+void JNICALL method_exit (jvmtiEnv *jvmti, JNIEnv* jni, jthread thread,
+ jmethodID method, jboolean was_popped_by_exception,
+ jvalue return_value);
+
+#endif /* __JAVA_METHOD_H__ */
diff --git a/java/misc.c b/java/misc.c
new file mode 100644
index 0000000..09d0668
--- /dev/null
+++ b/java/misc.c
@@ -0,0 +1,191 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "misc.h"
+
+bool str_starts_with (const char *haystack, const char *needle)
+{
+ int haystack_len = strlen (haystack), needle_len = strlen (needle);
+ if (haystack_len < needle_len)
+ return FALSE;
+ else
+ return memcmp (haystack, needle, needle_len * sizeof (char)) == 0;
+}
+
+void jvmti_error (jvmtiEnv *jvmti, jvmtiError error, const char *fmt, ...)
+{
+ char *errorname;
+ if ((*jvmti)->GetErrorName (jvmti, error, &errorname) != JVMTI_ERROR_NONE)
+ errorname = "<error getting error name>";
+
+ fprintf(stderr, "error (jvmtiError %d: %s): ", error, errorname);
+
+ va_list argp;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+
+ fprintf(stderr, "\n");
+}
+
+void other_error (const char *fmt, ...)
+{
+ fprintf(stderr, "error (other): ");
+
+ va_list argp;
+ va_start(argp, fmt);
+ vfprintf(stderr, fmt, argp);
+ va_end(argp);
+
+ fprintf(stderr, "\n");
+}
+
+void jvmti_dealloc (jvmtiEnv *jvmti, void *mem)
+{
+ if (mem) {
+ jvmtiError error;
+ if ((error = (*jvmti)->Deallocate (jvmti, mem)) != JVMTI_ERROR_NONE)
+ jvmti_error (jvmti, error, "Deallocate");
+ }
+}
+
+void simple_free (void *mem)
+{
+ if (mem)
+ free (mem);
+}
+
+char *get_class_name_from_sig (const char *class_sig, bool expect_class)
+{
+ int len_class_sig = strlen (class_sig);
+ if (!(class_sig[0] == 'L' && class_sig[len_class_sig - 1] == ';')) {
+ if (expect_class)
+ other_error ("don't know how to deal with class signature '%s'",
+ class_sig);
+ return NULL;
+ } else {
+ char *class_name =
+ (char*)malloc ((len_class_sig - 2 + 1) * sizeof (char));
+ int i;
+ for (i = 0; i < len_class_sig - 2; i++)
+ class_name[i] = class_sig[i + 1] == '/' ? '.' : class_sig[i + 1];
+ class_name[len_class_sig - 2] = 0;
+ return class_name;
+ }
+}
+
+/* -------------------------------------------------------------------
+
+ The only way to identify objects seems to be the JNI method
+ IsSameObject; although jobject is a pointer, the equality of two
+ jobject's does not imply the identity of the represented Java
+ objects.
+
+ Furthermore, since we only have one object at hand to get an
+ identifier needed for RTFL messages, we store all objects in a
+ list, after creating a global reference (NewGlobalRef); the index
+ in the list then identifies the object. This foils the garbage
+ collection, but for short debugging sessions, this should be
+ acceptable.
+
+ ---------------------------------------------------------------------- */
+
+static size_t reg_objects_size = 0, reg_objects_alloc_size;
+static jobject *reg_objects = NULL;
+
+size_t object_index (JNIEnv* jni, jobject object)
+{
+ size_t i;
+ for (i = 0; i < reg_objects_size; i++)
+ if ((*jni)->IsSameObject (jni, object, reg_objects[i]))
+ return i;
+
+ reg_objects_size++;
+ if (reg_objects == NULL) {
+ reg_objects_alloc_size = 1;
+ reg_objects =
+ (jobject*)malloc (reg_objects_alloc_size * sizeof (jobject));
+ } else {
+ reg_objects_alloc_size <<= 1;
+ reg_objects =
+ (jobject*)realloc (reg_objects,
+ reg_objects_alloc_size * sizeof (jobject));
+ }
+
+ i = reg_objects_size - 1;
+ reg_objects[i] = (*jni)->NewGlobalRef (jni, object);
+ return i;
+}
+
+void fill_object_buf (JNIEnv* jni, char *object_buf, jobject object)
+{
+ if (object)
+ snprintf (object_buf, SIZE_OBJECT_BUF, "%ld", object_index (jni, object));
+ else
+ strcpy (object_buf, "null");
+}
+
+// Copied from "debug_rtfl.hh".
+void rtfl_print (const char *module, const char *version,
+ const char *file, int line, const char *fmt, ...)
+{
+ // "\n" at the beginning just in case that the previous line is not
+ // finished yet.
+ printf ("\n[rtfl-%s-%s]%s:%d:pid(todo):", module, version, file, line);
+
+ va_list args;
+ va_start (args, fmt);
+
+ int i;
+ for (i = 0; fmt[i]; i++) {
+ int n, j;
+ void *p;
+ char *s;
+
+ switch (fmt[i]) {
+ case 'd':
+ n = va_arg(args, int);
+ printf ("%d", n);
+ break;
+
+ case 'p':
+ p = va_arg(args, void*);
+ printf ("%p", p);
+ break;
+
+ case 's':
+ s = va_arg (args, char*);
+ for (j = 0; s[j]; j++) {
+ if (s[j] == ':' || s[j] == '\\')
+ putchar ('\\');
+ putchar (s[j]);
+ }
+ break;
+
+ case 'q':
+ s = va_arg (args, char*);
+ for (j = 0; s[j]; j++) {
+ if (s[j] == ':' || s[j] == '\\')
+ putchar ('\\');
+ else if (s[j] == '\"')
+ printf ("\\\\"); // a quoted quoting character
+ putchar (s[j]);
+ }
+ break;
+
+ case 'c':
+ n = va_arg(args, int);
+ printf ("#%06x", n);
+ break;
+
+ default:
+ putchar (fmt[i]);
+ break;
+ }
+ }
+
+ va_end (args);
+
+ putchar ('\n');
+ fflush (stdout);
+}
diff --git a/java/misc.h b/java/misc.h
new file mode 100644
index 0000000..e88879e
--- /dev/null
+++ b/java/misc.h
@@ -0,0 +1,41 @@
+#ifndef __JAVA_MISC_H__
+#define __JAVA_MISC_H__
+
+#include <jvmti.h>
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#ifndef __GNUC__
+# define __attribute__(x) /* nothing */
+#endif
+
+typedef enum { FALSE = 0, TRUE = 1 } bool;
+enum { SIZE_OBJECT_BUF = 32 };
+
+bool str_starts_with (const char *haystack, const char *needle);
+
+void jvmti_error (jvmtiEnv *jvmti, jvmtiError error, const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+void other_error (const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+
+void jvmti_dealloc (jvmtiEnv *jvmti, void *mem);
+void simple_free (void *mem);
+
+char *get_class_name_from_sig (const char *class_sig, bool expect_class);
+
+size_t object_index (JNIEnv* jni, jobject object);
+void fill_object_buf (JNIEnv* jni, char *object_buf, jobject object);
+
+void rtfl_print (const char *module, const char *version,
+ const char *file, int line, const char *fmt, ...);
+
+#define RTFL_PRINT(module, version, cmd, fmt, ...) \
+ rtfl_print (module, version, "", 1, "s:" fmt, cmd, __VA_ARGS__)
+
+#define RTFL_OBJ_VERSION "1.0"
+
+#define RTFL_OBJ_PRINT(cmd, fmt, ...) \
+ RTFL_PRINT ("obj", RTFL_OBJ_VERSION, cmd, fmt, __VA_ARGS__)
+
+#endif /* __JAVA_MISC_H__ */