This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
Re: [RFA/JVMTI] Implement GetLocalVariableTable and GetMaxLocals
Kyle Galloway wrote:
Tom Tromey wrote:
"Kyle" == Kyle Galloway <kgallowa@redhat.com> writes:
(_Jv_AllocBytes (strlen (local_var_table[table_slot].name) + 1));
I don't see how memory allocated here can ever be freed.
Kyle> Not sure exactly what you mean here. I thought that memory
allocated
Kyle> with _Jv_AllocBytes is garbage collected.
Oops, I misread this. You are correct.
Thanks.
This patch is ok.
[validation]
Kyle> The only thing that may cause a problem is if the slot values are
Kyle> invalid indicies into the variable array for that method, this is
Kyle> fairly easy to check for so i you think it's worthwhile I can add
Kyle> this. Invalid PC values don't really matter, in my thinking,
since it
Kyle> will just result in garbage values begin in the slots when the
Kyle> debugger reads them.
My concern is that invalid bytecode cannot be used to somehow attack a
debug VM. So I think somewhere we should check indices so we can
never read or write memory out of bounds..
I'm going to rework it to check that the slot is not > max_locals or <
0 to prevent against this. I'll have a new patch for you in a bit.
And here it is. This patch now checks to see if the slot in the
variable table is > method->max_locals (therefore trying to access
invalid memory) or if it is < 0 (which is just plain wrong). I think
that solves the problem.
- Kyle
Index: libjava/include/java-interp.h
===================================================================
--- libjava/include/java-interp.h (revision 121717)
+++ libjava/include/java-interp.h (working copy)
@@ -137,6 +137,21 @@
int line;
};
+// This structure holds local variable information.
+// The pc value is the first pc where the variable must have a value and it
+// must continue to have a value until (start_pc + length).
+// The name is the variable name, and the descriptor contains type information.
+// The slot is the index in the local variable array of this method, long and
+// double occupy slot and slot+1.
+struct _Jv_LocalVarTableEntry
+{
+ int bytecode_start_pc;
+ int length;
+ char *name;
+ char *descriptor;
+ int slot;
+};
+
class _Jv_InterpMethod : public _Jv_MethodBase
{
// Breakpoint instruction
@@ -157,6 +172,10 @@
// Length of the line_table - when this is zero then line_table is NULL.
int line_table_len;
_Jv_LineTableEntry *line_table;
+
+ // The local variable table length and the table itself
+ int local_var_table_len;
+ _Jv_LocalVarTableEntry *local_var_table;
pc_t prepared;
int number_insn_slots;
@@ -224,7 +243,50 @@
{
return static_cast<int> (max_locals);
}
+
+ /* Get info for a local variable of this method.
+ * If there is no loca_var_table for this method it will return -1.
+ * table_slot indicates which slot in the local_var_table to get, if there is
+ * no variable at this location it will return 0.
+ * Otherwise, it will return the number of table slots after the selected
+ * slot, indexed from 0.
+ *
+ * Example: there are 5 slots in the table, you request slot 0 so it will
+ * return 4.
+ */
+ int get_local_var_table (char **name, char **sig, char **generic_sig,
+ long *startloc, jint *length, jint *slot,
+ int table_slot)
+ {
+ if (local_var_table == NULL)
+ return -1;
+ if (table_slot >= local_var_table_len)
+ return 0;
+ else
+ {
+ *name = reinterpret_cast<char *>
+ (_Jv_AllocBytes (strlen (local_var_table[table_slot].name) + 1));
+ strcpy (*name, local_var_table[table_slot].name);
+
+ *sig = reinterpret_cast<char *>
+ (_Jv_AllocBytes (
+ strlen (local_var_table[table_slot].descriptor) + 1));
+ strcpy (*sig, local_var_table[table_slot].descriptor);
+
+ *generic_sig = reinterpret_cast<char *>
+ (_Jv_AllocBytes (
+ strlen (local_var_table[table_slot].descriptor) + 1));
+ strcpy (*generic_sig, local_var_table[table_slot].descriptor);
+
+ *startloc = static_cast<long>
+ (local_var_table[table_slot].bytecode_start_pc);
+ *length = static_cast<jint> (local_var_table[table_slot].length);
+ *slot = static_cast<jint> (local_var_table[table_slot].slot);
+ }
+ return local_var_table_len - table_slot - 1;
+ }
+
/* Installs a break instruction at the given code index. Returns
the pc_t of the breakpoint or NULL if index is invalid. */
pc_t install_break (jlong index);
Index: libjava/jvmti.cc
===================================================================
--- libjava/jvmti.cc (revision 121717)
+++ libjava/jvmti.cc (working copy)
@@ -706,6 +706,77 @@
}
static jvmtiError JNICALL
+_Jv_JVMTI_GetLocalVariableTable (MAYBE_UNUSED jvmtiEnv *env, jmethodID method,
+ jint *num_locals,
+ jvmtiLocalVariableEntry **locals)
+{
+ REQUIRE_PHASE (env, JVMTI_PHASE_LIVE);
+ NULL_CHECK (num_locals);
+ NULL_CHECK (locals);
+
+ CHECK_FOR_NATIVE_METHOD(method);
+
+ jclass klass;
+ jvmtiError jerr = env->GetMethodDeclaringClass (method, &klass);
+ if (jerr != JVMTI_ERROR_NONE)
+ return jerr;
+
+ _Jv_InterpMethod *imeth = reinterpret_cast<_Jv_InterpMethod *>
+ (_Jv_FindInterpreterMethod (klass, method));
+
+ if (imeth == NULL)
+ return JVMTI_ERROR_INVALID_METHODID;
+
+ jerr = env->GetMaxLocals (method, num_locals);
+ if (jerr != JVMTI_ERROR_NONE)
+ return jerr;
+
+ jerr = env->Allocate (static_cast<jlong>
+ ((*num_locals) * sizeof (jvmtiLocalVariableEntry)),
+ reinterpret_cast<unsigned char **> (locals));
+
+ if (jerr != JVMTI_ERROR_NONE)
+ return jerr;
+
+ //the slot in the methods local_var_table to get
+ int table_slot = 0;
+
+ // Get the first variable, and check to make sure the table exists.
+ if (imeth->get_local_var_table (&((*locals)[table_slot].name),
+ &((*locals)[table_slot].signature),
+ &((*locals)[table_slot].generic_signature),
+ reinterpret_cast<long *>
+ (&(((*locals)[table_slot].start_location))),
+ &((*locals)[table_slot].length),
+ &((*locals)[table_slot].slot),
+ table_slot)
+ == -1)
+ return JVMTI_ERROR_ABSENT_INFORMATION;
+
+ do
+ {
+ table_slot++;
+ }
+ while (table_slot < *num_locals
+ && imeth->get_local_var_table (&((*locals)[table_slot].name),
+ &((*locals)[table_slot].signature),
+ &((*locals)[table_slot].generic_signature),
+ reinterpret_cast<long *>
+ (&(((*locals)[table_slot].start_location))),
+ &((*locals)[table_slot].length),
+ &((*locals)[table_slot].slot),
+ table_slot)
+ > 0);
+
+ // If there are double or long variables in the table, the the table will be
+ // smaller than the max number of slots, so correct for this here.
+ if ((table_slot + 1) < *num_locals)
+ *num_locals = table_slot + 1;
+
+ return JVMTI_ERROR_NONE;
+}
+
+static jvmtiError JNICALL
_Jv_JVMTI_IsMethodNative (MAYBE_UNUSED jvmtiEnv *env, jmethodID method,
jboolean *result)
{
@@ -760,6 +831,31 @@
}
static jvmtiError JNICALL
+_Jv_JVMTI_GetMaxLocals (MAYBE_UNUSED jvmtiEnv *env, jmethodID method,
+ jint *max_locals)
+{
+ REQUIRE_PHASE (env, JVMTI_PHASE_START | JVMTI_PHASE_LIVE);
+ NULL_CHECK (max_locals);
+
+ CHECK_FOR_NATIVE_METHOD (method);
+
+ jclass klass;
+ jvmtiError jerr = env->GetMethodDeclaringClass (method, &klass);
+ if (jerr != JVMTI_ERROR_NONE)
+ return jerr;
+
+ _Jv_InterpMethod *imeth = reinterpret_cast<_Jv_InterpMethod *>
+ (_Jv_FindInterpreterMethod (klass, method));
+
+ if (imeth == NULL)
+ return JVMTI_ERROR_INVALID_METHODID;
+
+ *max_locals = imeth->get_max_locals ();
+
+ return JVMTI_ERROR_NONE;
+}
+
+static jvmtiError JNICALL
_Jv_JVMTI_GetMethodDeclaringClass (MAYBE_UNUSED jvmtiEnv *env,
jmethodID method,
jclass *declaring_class_ptr)
@@ -1690,7 +1786,7 @@
UNIMPLEMENTED, // GetArgumentsSize
_Jv_JVMTI_GetLineNumberTable, // GetLineNumberTable
UNIMPLEMENTED, // GetMethodLocation
- UNIMPLEMENTED, // GetLocalVariableTable
+ _Jv_JVMTI_GetLocalVariableTable, // GetLocalVariableTable
RESERVED, // reserved73
RESERVED, // reserved74
UNIMPLEMENTED, // GetBytecodes
Index: libjava/defineclass.cc
===================================================================
--- libjava/defineclass.cc (revision 121717)
+++ libjava/defineclass.cc (working copy)
@@ -299,6 +299,9 @@
/** check an utf8 entry, without creating a Utf8Const object */
bool is_attribute_name (int index, const char *name);
+
+ /** return the value of a utf8 entry in the passed array */
+ int pool_Utf8_to_char_arr (int index, char **entry);
/** here goes the class-loader members defined out-of-line */
void handleConstantPool ();
@@ -784,6 +787,18 @@
return !memcmp (bytes+offsets[index]+2, name, len);
}
+// Get a UTF8 value from the constant pool and turn it into a garbage
+// collected char array.
+int _Jv_ClassReader::pool_Utf8_to_char_arr (int index, char** entry)
+{
+ check_tag (index, JV_CONSTANT_Utf8);
+ int len = get2u (bytes + offsets[index]);
+ *entry = reinterpret_cast<char *> (_Jv_AllocBytes (len + 1));
+ (*entry)[len] = '\0';
+ memcpy (*entry, bytes + offsets[index] + 2, len);
+ return len + 1;
+}
+
void _Jv_ClassReader::read_one_field_attribute (int field_index,
bool *found_value)
{
@@ -979,6 +994,34 @@
method->line_table_len = table_len;
method->line_table = table;
}
+ else if (is_attribute_name (name, "LocalVariableTable"))
+ {
+ _Jv_InterpMethod *method = reinterpret_cast<_Jv_InterpMethod *>
+ (def_interp->interpreted_methods[method_index]);
+ if (method->local_var_table != NULL)
+ throw_class_format_error ("Method already has LocalVariableTable");
+
+ int table_len = read2u ();
+ _Jv_LocalVarTableEntry *table
+ = reinterpret_cast<_Jv_LocalVarTableEntry *>
+ (_Jv_AllocBytes (table_len * sizeof (_Jv_LocalVarTableEntry)));
+
+ for (int i = 0; i < table_len; i++)
+ {
+ table[i].bytecode_start_pc = read2u ();
+ table[i].length = read2u ();
+ int len;
+ len = pool_Utf8_to_char_arr (read2u (), &table[i].name);
+ len = pool_Utf8_to_char_arr (read2u (), &table[i].descriptor);
+ table[i].slot = read2u ();
+
+ if (table[i].slot > method->max_locals || table[i].slot < 0)
+ throw_class_format_error ("Malformed Local Variable Table: Invalid Slot");
+ }
+
+ method->local_var_table_len = table_len;
+ method->local_var_table = table;
+ }
else
{
/* ignore unknown code attributes */