Commit af73a5c4 authored by Lubomir Bulej's avatar Lubomir Bulej

Initial experimental support for force-loading super classes and interfaces.

To enable, use -Ddisl.forcesuperclass=true or -Ddisl.forceinterfaces=true in the client JVM command line.
parent 303d0f93
......@@ -59,7 +59,8 @@ endif
# Source and object files needed to create the library
SOURCES = bytecode.c common.c jvmtiutil.c connection.c \
connpool.c msgchannel.c network.c dislagent.c
connpool.c msgchannel.c network.c classparser.c \
dislagent.c
HEADERS = $(wildcard *.h) codeflags.h
GENSRCS = bytecode.c codeflags.h
......
#include "common.h"
#include "classparser.h"
#include <assert.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <endian.h>
//
#define CLASS_MAGIC 0xCAFEBABE
#define __PACKED__ __attribute__ ((__packed__))
#define member_sizeof(type, member) sizeof(((type *)0)->member)
struct java_class {
// Class bytecode.
const uint8_t * bytes;
// Size of the class bytecode.
size_t size;
// Constant pool size in bytes.
size_t cp_size;
// Constant pool entry offsets.
size_t cp_offsets [];
};
enum cp_tag {
CP_TAG_UTF8 = 1,
CP_TAG_LONG = 5,
CP_TAG_DOUBLE = 6,
CP_TAG_CLASS = 7,
CP_TAG_STRING = 8,
CP_TAG_METHOD_HANDLE = 15,
CP_TAG_METHOD_TYPE = 16,
};
struct __PACKED__ cp_info {
uint8_t tag;
union __PACKED__ {
struct __PACKED__ cp_info_utf8 {
uint16_t length;
uint8_t bytes [];
} utf8;
struct __PACKED__ cp_info_class {
uint16_t name_index;
} class;
struct __PACKED__ cp_info_method_handle {
uint8_t reference_kind;
uint16_t reference_index;
} method_handle;
struct __PACKED__ cp_info_long_double {
uint32_t high_bytes;
uint32_t low_bytes;
} long_double;
};
};
//
struct __PACKED__ class_file_start {
uint32_t magic;
uint16_t minor_version;
uint16_t major_version;
uint16_t constant_pool_count;
struct cp_info constant_pool []; // [constant_pool_count - 1]
};
struct __PACKED__ class_file_access {
uint16_t access_flags;
uint16_t this_class;
uint16_t super_class;
uint16_t interfaces_count;
uint16_t interfaces []; // [interfaces_count]
};
//
static inline struct class_file_start *
__class_file_start (const uint8_t * class_bytes) {
return (struct class_file_start *) class_bytes;
}
static inline struct class_file_access *
__class_file_access (class_t java_class) {
int offset = sizeof (struct class_file_start) + java_class->cp_size;
return (struct class_file_access *) (java_class->bytes + offset);
}
//
static inline bool
__class_bytes_valid (const uint8_t * class_bytes) {
return be32toh (__class_file_start (class_bytes)->magic) == CLASS_MAGIC;
}
static inline int
__cp_entry_count (const uint8_t * class_bytes) {
return be16toh (__class_file_start (class_bytes)->constant_pool_count);
}
//
static inline int
__class_this_class_index (class_t java_class) {
return be16toh (__class_file_access (java_class)->this_class);
}
static inline int
__class_super_class_index (class_t java_class) {
return be16toh (__class_file_access (java_class)->super_class);
}
static inline int
__class_interface_count (class_t java_class) {
return be16toh (__class_file_access (java_class)->interfaces_count);
}
static inline int
__class_interface_index (class_t java_class, int index) {
return be16toh (__class_file_access (java_class)->interfaces [index]);
}
//
static inline struct cp_info *
__cp_entry (const uint8_t * class_bytes, int offset) {
const uint8_t * cp_base = class_bytes + offsetof (struct class_file_start, constant_pool);
return (struct cp_info *) (cp_base + offset);
}
static struct cp_info *
__class_cp_get_entry (class_t java_class, int index) {
if (index >= 1 && index < __cp_entry_count (java_class->bytes)) {
int offset = java_class->cp_offsets [index];
return __cp_entry (java_class->bytes, offset);
} else {
warn ("invalid const pool index: %d\n", index);
return NULL;
}
}
//
static int
__cp_scan_entries (const uint8_t * class_bytes, size_t * offsets) {
uint_fast32_t offset = 0;
uint_fast16_t count = __cp_entry_count (class_bytes);
for (uint_fast16_t index = 1; index < count; index++) {
offsets [index] = offset;
struct cp_info * entry = __cp_entry (class_bytes, offset);
switch (entry->tag) {
case CP_TAG_UTF8: {
uint_fast16_t length = be16toh (entry->utf8.length);
offset += sizeof (struct cp_info_utf8) + length;
break;
}
case CP_TAG_CLASS:
case CP_TAG_STRING:
case CP_TAG_METHOD_TYPE:
// Indices into constant pool.
offset += sizeof (uint16_t);
break;
case CP_TAG_METHOD_HANDLE:
offset += sizeof (struct cp_info_method_handle);
break;
case CP_TAG_LONG:
case CP_TAG_DOUBLE:
// 64-bit values take an extra constant pool slot.
index++;
offset += sizeof (struct cp_info_long_double);
break;
default:
// All other entries fit into 4 bytes.
offset += 2 * sizeof (uint16_t);
}
// Account for the type tag.
offset += member_sizeof (struct cp_info, tag);
}
return offset;
}
//
static inline struct java_class *
__class_alloc (const uint8_t * class_bytes) {
int const_count = 1 + __cp_entry_count (class_bytes);
int offsets_size = const_count * member_sizeof (struct java_class, cp_offsets[0]);
return (struct java_class *) malloc (sizeof (struct java_class) + offsets_size);
}
static inline void
__class_init (struct java_class * java_class, const uint8_t * class_bytes, size_t byte_count) {
java_class->bytes = class_bytes;
java_class->size = byte_count;
java_class->cp_size = __cp_scan_entries (class_bytes, &java_class->cp_offsets[0]);
}
class_t
class_alloc (const uint8_t * class_bytes, size_t byte_count) {
assert (byte_count > sizeof (struct class_file_start));
assert (class_bytes != NULL);
if (__class_bytes_valid (class_bytes)) {
struct java_class * result = __class_alloc (class_bytes);
if (result != NULL) {
__class_init (result, class_bytes, byte_count);
return result;
} else {
warn ("failed to allocate java_class structure");
}
} else {
warn ("invalid class bytecode passed to java_class_alloc is invalid");
}
return NULL;
}
//
int
class_cp_entry_count (class_t java_class) {
assert (java_class != NULL);
return __cp_entry_count (java_class->bytes);
}
utf8_t
class_cp_get_utf8 (class_t java_class, int index, size_t * utf8_length) {
assert (java_class != NULL);
struct cp_info * entry = __class_cp_get_entry (java_class, index);
if (entry != NULL) {
if (entry->tag == CP_TAG_UTF8) {
if (utf8_length != NULL) {
*utf8_length = be16toh (entry->utf8.length);
}
return &entry->utf8.bytes [0];
} else {
warn ("invalid const tag, expected %d, found %d\n", CP_TAG_UTF8, entry->tag);
}
}
return NULL;
}
utf8_t
class_cp_get_class_name (
class_t java_class, int class_index, size_t * utf8_length
) {
struct cp_info * entry = __class_cp_get_entry (java_class, class_index);
if (entry != NULL) {
if (entry->tag == CP_TAG_CLASS) {
int index = be16toh (entry->class.name_index);
return class_cp_get_utf8 (java_class, index, utf8_length);
} else {
warn ("invalid const tag, expected %d, found %d\n", CP_TAG_CLASS, entry->tag);
}
}
return NULL;
}
//
static inline char *
__utf8dup (utf8_t bytes, size_t length) {
return (bytes != NULL) ? strndup ((const char *) bytes, length) : NULL;
}
char *
class_name (class_t java_class) {
assert (java_class != NULL);
int class_index = __class_this_class_index (java_class);
size_t length = 0;
utf8_t bytes = class_cp_get_class_name (java_class, class_index, &length);
return __utf8dup (bytes, length);
}
char *
class_super_class_name (class_t java_class) {
assert (java_class != NULL);
int class_index = __class_super_class_index (java_class);
if (class_index != 0) {
size_t length = 0;
utf8_t bytes = class_cp_get_class_name (java_class, class_index, &length);
return __utf8dup (bytes, length);
} else {
return NULL;
}
}
bool
class_has_super_class (class_t java_class) {
assert (java_class != NULL);
return __class_super_class_index (java_class) > 1;
}
int
class_interface_count (class_t java_class) {
assert (java_class != NULL);
return __class_interface_count (java_class);
}
char *
class_interface_name (class_t java_class, int index) {
assert (java_class != NULL);
int count = __class_interface_count (java_class);
if (0 <= index && index < count) {
int iface_index = __class_interface_index (java_class, index);
size_t length = 0;
utf8_t bytes = class_cp_get_class_name (java_class, iface_index, &length);
return __utf8dup (bytes, length);
} else {
warn ("requested interface index out of range: %d\n", index);
return NULL;
}
}
void
class_free (class_t java_class) {
assert (java_class != NULL);
free ((void *) java_class);
}
#ifndef _CLASSPARSER_H
#define _CLASSPARSER_H
#include <stdint.h>
#include <stdbool.h>
struct java_class;
typedef const struct java_class * class_t;
typedef const uint8_t * utf8_t;
//
class_t class_alloc (const uint8_t * class_bytes, size_t byte_count);
void class_free (class_t java_class);
int class_cp_entry_count (class_t java_class);
utf8_t class_cp_get_utf8 (class_t java_class, int index, size_t * utf8_length);
utf8_t class_cp_get_class_name (class_t java_class, int class_index, size_t * utf8_length);
char * class_name (class_t java_class);
char * class_super_class_name (class_t java_class);
bool class_has_super_class (class_t java_class);
int class_interface_count (class_t java_class);
char * class_interface_name (class_t java_class, int index);
#endif /* _CLASSPARSER_H */
......@@ -138,7 +138,7 @@ connection_pool_close (struct connection_pool * cp) {
list_destroy (&cp->free_connections, __connection_destructor, (void *) cp);
if (!list_is_empty (&cp->busy_connections)) {
fprintf (stderr, "warning: closing %d active connections", cp->connections_count);
warn ("closing %d active connections", cp->connections_count);
list_destroy (&cp->busy_connections, __connection_destructor, (void *) cp);
}
}
......@@ -7,13 +7,14 @@
#include "common.h"
#include "jvmtiutil.h"
#include "dislagent.h"
#include "connection.h"
#include "connection.h"
#include "network.h"
#include "msgchannel.h"
#include "bytecode.h"
#include "classparser.h"
#include "codeflags.h"
......@@ -36,6 +37,12 @@
#define DISL_CATCH_EXCEPTIONS "disl.excepthandler"
#define DISL_CATCH_EXCEPTIONS_DEFAULT false
#define DISL_FORCE_SUPERCLASS "disl.forcesuperclass"
#define DISL_FORCE_SUPERCLASS_DEFAULT false
#define DISL_FORCE_INTERFACES "disl.forceinterfaces"
#define DISL_FORCE_INTERFACES_DEFAULT false
#define DISL_DEBUG "debug"
#define DISL_DEBUG_DEFAULT false
......@@ -86,6 +93,8 @@ struct config {
enum bypass_mode bypass_mode;
bool split_methods;
bool catch_exceptions;
bool force_superclass;
bool force_interfaces;
bool debug;
};
......@@ -144,6 +153,12 @@ __calc_code_flags (struct config * config, bool jvm_is_booting) {
}
static inline const char *
__safe (const char * class_name) {
return (class_name != NULL) ? class_name : "<unknown class>";
}
/**
* Sends the given class to the remote server for instrumentation. If the
* server modified the class, provided class definition structure is updated
......@@ -186,8 +201,7 @@ __instrument_class (
//
if (response.control_size > 0) {
fprintf (
stderr,
"%sinstrumentation server error:\n%s\n",
stderr, "%sinstrumentation server error:\n%s\n",
ERROR_PREFIX, response.control
);
......@@ -210,6 +224,83 @@ __instrument_class (
}
static void
__force_class (JNIEnv * jni, const char * class_name, const char * kind) {
rdprintf ("\tforce-loading %s %s\n", kind, class_name);
if (jni != NULL) {
jclass found_class = (* jni)->FindClass (jni, class_name);
if (found_class == NULL) {
warn ("failed to force-load %s %s\n", kind, class_name);
}
} else {
rdprintf ("\tJNI not ready, force-loading aborted\n");
}
}
static void
__force_superclass (JNIEnv * jni, class_t inst_class) {
char * super_name = class_super_class_name (inst_class);
if (super_name != NULL) {
__force_class (jni, super_name, "super class");
free (super_name);
} else {
rdprintf ("\tclass does not have super class\n");
}
}
static void
__force_interfaces (JNIEnv * jni, class_t inst_class) {
int count = class_interface_count (inst_class);
if (count == 0) {
rdprintf ("\tclass does not implement interfaces\n");
return;
}
for (int index = 0; index < count; index++) {
char * iface_name = class_interface_name (inst_class, index);
if (iface_name != NULL) {
__force_class (jni, iface_name, "interface");
free (iface_name);
} else {
warn ("failed to get the name of interface %d\n", index);
}
}
}
/**
* Forces loading of the super class (or the implemented interfaces) of the
* class being instrumented. For this, we need to parse the constant pool
* of the class to discover the names of the classes to be force-loaded.
* We then use JNI to find the classes, which will force their loading.
*/
static void
__force_classes (
JNIEnv * jni, const char * class_name,
const unsigned char * class_bytes, jint class_byte_count
) {
class_t inst_class = class_alloc (class_bytes, class_byte_count);
if (inst_class != NULL) {
if (agent_config.force_superclass) {
__force_superclass (jni, inst_class);
}
if (agent_config.force_interfaces) {
__force_interfaces (jni, inst_class);
}
class_free (inst_class);
} else {
warn ("failed to parse class %s\n", __safe (class_name));
}
}
static void JNICALL
jvmti_callback_class_file_load (
jvmtiEnv * jvmti, JNIEnv * jni,
......@@ -222,18 +313,24 @@ jvmti_callback_class_file_load (
rdprintf (
"%s loaded, %ld bytes at %p\n",
(class_name != NULL) ? class_name : "<unknown class>",
(long) class_byte_count, class_bytes
__safe (class_name), (long) class_byte_count, class_bytes
);
//
// Avoid instrumenting the bypass check class.
//
if (class_name != NULL && (strcmp (class_name, BPC_CLASS_NAME) == 0)) {
if (class_name != NULL && strcmp (class_name, BPC_CLASS_NAME) == 0) {
rdprintf ("skipping bypass check class (%s)\n", class_name);
return;
}
//
// Force loading of the super class or the interface classes.
//
if (agent_config.force_superclass || agent_config.force_interfaces) {
rdprintf ("forcing classes for %s\n", __safe (class_name));
__force_classes (jni, class_name, class_bytes, class_byte_count);
}
//
// Instrument the class and if changed by the server, provide the
......@@ -334,8 +431,6 @@ __configure_from_properties (jvmtiEnv * jvmti, struct config * config) {
config->bypass_mode = bypass_index;
free (bypass);
rdprintf ("bypass mode: %s\n", values [bypass_index]);
//
// Get boolean values from system properties
//
......@@ -347,9 +442,26 @@ __configure_from_properties (jvmtiEnv * jvmti, struct config * config) {
jvmti, DISL_CATCH_EXCEPTIONS, DISL_CATCH_EXCEPTIONS_DEFAULT
);
config->force_superclass = jvmti_get_system_property_bool (
jvmti, DISL_FORCE_SUPERCLASS, DISL_FORCE_SUPERCLASS_DEFAULT
);
config->force_interfaces = jvmti_get_system_property_bool (
jvmti, DISL_FORCE_INTERFACES, DISL_FORCE_INTERFACES_DEFAULT
);
config->debug = jvmti_get_system_property_bool (
jvmti, DISL_DEBUG, DISL_DEBUG_DEFAULT
);
//
// Configuration summary.
//
rdprintf ("bypass mode: %s\n", values [bypass_index]);
rdprintf ("split methods: %d\n", config->split_methods);
rdprintf ("catch exceptions: %d\n", config->catch_exceptions);
rdprintf ("force superclass: %d\n", config->force_superclass);
rdprintf ("force interfaces: %d\n", config->force_interfaces);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment