/*
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
/* Class reader writer (java_crw_demo) for instrumenting bytecodes */
/*
* As long as the callbacks allow for it and the class number is unique,
* this code is completely re-entrant and any number of classfile
* injections can happen at the same time.
*
* The current logic requires a unique number for this class instance
* or (jclass,jobject loader) pair, this is done via the ClassIndex
* in hprof, which is passed in as the 'unsigned cnum' to java_crw_demo().
* It's up to the user of this interface if it wants to use this
* feature.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Get Java and class file and bytecode information. */
#include <jni.h>
#include "classfile_constants.h"
/* Include our own interface for cross check */
#include "java_crw_demo.h"
/* Macros over error functions to capture line numbers */
#else
#endif
#ifdef _WIN32
#define snprintf(buffer, count, format, ...) _snprintf_s(buffer, count, _TRUNCATE, format, ##__VA_ARGS__)
#endif
/* Typedefs for various integral numbers, just for code clarity */
/* Misc support macros */
/* Given the position of an opcode, find the next 4byte boundary position */
/* Constant Pool Entry (internal table that mirrors pool in file image) */
typedef struct {
struct MethodImage;
/* Class file image storage structure */
typedef struct CrwClassImage {
/* Unique class number for this class */
unsigned number;
/* Name of class, given or gotten out of class image */
const char * name;
/* Input and Output class images tracking */
const unsigned char * input;
unsigned char * output;
/* Mirrored constant pool */
/* Input flags about class (e.g. is it a system class) */
int system_class;
/* Class access flags gotten from file. */
unsigned access_flags;
/* Names of classes and methods. */
/* Constant pool index values for new entries */
/* Count of injections made into this class */
int injection_count;
/* This class must be the java.lang.Object class */
/* This class must be the java.lang.Thread class */
/* Callback functions */
/* Table of method names and descr's */
int method_count;
const char ** method_name;
const char ** method_descr;
/* Injection bytecodes (holds injected bytecodes for each code position) */
typedef struct {
} Injection;
typedef struct MethodImage {
/* Back reference to Class image data. */
/* Unique method number for this class. */
unsigned number;
/* Method name and descr */
const char * name;
const char * descr;
/* Map of input bytecode offsets to output bytecode offsets */
/* Bytecode injections for each input bytecode offset */
/* Widening setting for each input bytecode offset */
signed char * widening;
/* Length of original input bytecodes, and new bytecodes. */
/* Location in input where bytecodes are located. */
/* Original max_stack and new max stack */
unsigned max_stack;
unsigned new_max_stack;
/* Method access flags gotten from file. */
unsigned access_flags;
} MethodImage;
/* ----------------------------------------------------------------- */
/* General support functions (memory and error handling) */
static void
{
} else {
/* Normal operation should NEVER reach here */
/* NO CRW FATAL ERROR HANDLER! */
abort();
}
}
static void
{
} else {
byte_code_offset=-1;
}
"CRW ASSERTION FAILURE: %s (%s:%s:%d)",
}
#endif
static void *
{
void * ptr;
if ( nbytes <= 0 ) {
}
}
return ptr;
}
static void *
{
void * ptr;
}
if ( nbytes <= 0 ) {
}
}
return ptr;
}
static void *
{
void * ptr;
if ( nbytes <= 0 ) {
}
}
return ptr;
}
static const char *
{
char *copy;
return (const char *)copy;
}
static void
{
}
}
/* ----------------------------------------------------------------- */
static unsigned
{
}
static unsigned
{
unsigned res;
}
static signed short
{
unsigned res;
}
static unsigned
{
unsigned res;
}
static void
{
}
}
static void
{
}
static void
{
}
static unsigned
{
unsigned value;
return value;
}
static unsigned
{
unsigned value;
return value;
}
static unsigned
{
unsigned value;
return value;
}
static void
{
}
}
static void
{
}
static void
{
}
static void
{
}
}
static void
{
}
static void
{
}
/* ----------------------------------------------------------------- */
/* Constant Pool handling functions. */
static void
{
}
static CrwCpoolIndex
{
i = ci->cpool_count_plus_one++;
/* NOTE: This implementation does not automatically expand the
* constant pool table beyond the expected number needed
* to handle this particular CrwTrackerInterface injections.
* See MAXIMUM_NEW_CPOOL_ENTRIES
*/
switch (tag) {
case JVM_CONSTANT_Class:
break;
case JVM_CONSTANT_String:
break;
case JVM_CONSTANT_Fieldref:
case JVM_CONSTANT_Methodref:
case JVM_CONSTANT_Integer:
case JVM_CONSTANT_Float:
case JVM_CONSTANT_NameAndType:
break;
case JVM_CONSTANT_Long:
case JVM_CONSTANT_Double:
break;
case JVM_CONSTANT_Utf8:
break;
default:
break;
}
return i;
}
static CrwCpoolIndex
{
int len;
class_name, len);
NULL, 0);
return class_index;
}
static CrwCpoolIndex
{
int len;
}
static CrwConstantPoolEntry
{
}
static void
{
int count_plus_one;
/* Index zero not in class file */
for (i = 1; i < count_plus_one; ++i) {
unsigned int index1;
unsigned int index2;
unsigned len;
char * utf8;
ipos = i;
index1 = 0;
index2 = 0;
len = 0;
switch (tag) {
case JVM_CONSTANT_Class:
break;
case JVM_CONSTANT_String:
break;
case JVM_CONSTANT_Fieldref:
case JVM_CONSTANT_Methodref:
case JVM_CONSTANT_Integer:
case JVM_CONSTANT_Float:
case JVM_CONSTANT_NameAndType:
break;
case JVM_CONSTANT_Long:
case JVM_CONSTANT_Double:
++i; /* // these take two CP entries - duh! */
break;
case JVM_CONSTANT_Utf8:
break;
case JVM_CONSTANT_MethodType:
break;
break;
break;
default:
break;
}
}
}
}
}
ci->obj_init_sig);
}
ci->newarray_sig);
}
}
ci->return_sig);
}
}
/* ----------------------------------------------------------------- */
/* Functions that create the bytecodes to inject */
static ByteOffset
{
} else {
}
return nbytes;
}
static ByteOffset
{
if ( number <= 5 ) {
} else {
}
return nbytes;
}
static ByteOffset
{
unsigned max_stack;
int add_dup;
int add_aload;
int push_cnum;
int push_mnum;
if ( method_index == 0 ) {
return 0;
}
} else {
}
if ( add_dup ) {
}
if ( add_aload ) {
}
if ( push_cnum ) {
} else {
}
}
if ( push_mnum ) {
}
/* Make sure the new max_stack is appropriate */
}
return nbytes;
}
/* Called to create injection code at entry to a method */
static ByteOffset
{
if ( mi->object_init_method ) {
}
if ( !mi->skip_call_return_sites ) {
}
return nbytes;
}
/* Called to create injection code before an opcode */
static ByteOffset
{
switch ( opcode ) {
case JVM_OPC_return:
case JVM_OPC_ireturn:
case JVM_OPC_lreturn:
case JVM_OPC_freturn:
case JVM_OPC_dreturn:
case JVM_OPC_areturn:
if ( !mi->skip_call_return_sites ) {
}
break;
default:
break;
}
return nbytes;
}
/* Called to create injection code after an opcode */
static ByteOffset
{
nbytes = 0;
switch ( opcode ) {
case JVM_OPC_new:
/* Can't inject here cannot pass around uninitialized object */
break;
case JVM_OPC_newarray:
case JVM_OPC_anewarray:
case JVM_OPC_multianewarray:
break;
default:
break;
}
return nbytes;
}
/* Actually inject the bytecodes */
static void
{
/* Either start an injection area or concatenate to what is there */
}
ci->injection_count++;
}
/* ----------------------------------------------------------------- */
/* Method handling functions */
static MethodImage *
{
ByteOffset i;
for(i=0; i<=code_len; i++) {
}
return mi;
}
static void
{
}
}
ByteOffset i;
}
}
}
}
static ByteOffset
{
}
static void
{
}
/* Starting at original byte position 'at', add 'offset' to it's new
* location. This may be a negative value.
* NOTE: That this map is not the new bytecode location of the opcode
* but the new bytecode location that should be used when
* a goto or jump instruction was targeting the old bytecode
* location.
*/
static void
{
ByteOffset i;
}
}
static void
{
int delta;
/* Adjust everything from the current input location by delta */
/* Mark at beginning of instruction */
}
static void
{
switch (wopcode) {
case JVM_OPC_aload: case JVM_OPC_astore:
case JVM_OPC_fload: case JVM_OPC_fstore:
case JVM_OPC_iload: case JVM_OPC_istore:
case JVM_OPC_lload: case JVM_OPC_lstore:
case JVM_OPC_dload: case JVM_OPC_dstore:
case JVM_OPC_ret: case JVM_OPC_iinc:
break;
default:
break;
}
}
static unsigned
{
/* Define array that holds length of an opcode */
if ( opcode > JVM_OPC_MAX ) {
}
return _opcode_length[opcode];
}
/* Walk one instruction and inject instrumentation */
static void
{
int pos;
if (opcode == JVM_OPC_wide) {
/* lvIndex not used */
if ( wopcode==JVM_OPC_iinc ) {
}
} else {
int header;
int instr_len;
int low;
int high;
int npairs;
/* Get bytecodes to inject before this opcode */
if ( len > 0 ) {
/* Adjust map after processing this opcode */
}
/* Process this opcode */
switch (opcode) {
case JVM_OPC_tableswitch:
break;
case JVM_OPC_lookupswitch:
break;
default:
break;
}
/* Get position after this opcode is processed */
/* Adjust for any before_injection_code() */
if ( len > 0 ) {
/* Adjust everything past this opcode.
* Why past it? Because we want any jumps to this bytecode loc
* to go to the injected code, not where the opcode
* was moved too.
* Consider a 'return' opcode that is jumped too.
* NOTE: This may not be correct in all cases, but will
* when we are only dealing with non-variable opcodes
* like the return opcodes. Be careful if the
* before_injection_code() changes to include other
* opcodes that have variable length.
*/
}
/* Get bytecodes to inject after this opcode */
if ( len > 0 ) {
/* Adjust for any after_injection_code() */
}
}
}
/* Map original bytecode location to it's new location. (See adjust_map()). */
static ByteOffset
{
}
static int
{
int pos;
int new_pos;
if (opcode == JVM_OPC_wide) {
/* lvIndex not used */
if ( wopcode==JVM_OPC_iinc ) {
}
} else {
int widened;
int header;
int newHeader;
int low;
int high;
int new_pad;
int old_pad;
int delta;
int new_delta;
int delta_pad;
int npairs;
int instr_len;
switch (opcode) {
case JVM_OPC_tableswitch:
return 0;
}
break;
case JVM_OPC_lookupswitch:
return 0;
}
break;
case JVM_OPC_jsr: case JVM_OPC_goto:
case JVM_OPC_if_acmpeq: case JVM_OPC_if_acmpne:
case JVM_OPC_ifnull: case JVM_OPC_ifnonnull:
if (widened == 0) {
switch (opcode) {
case JVM_OPC_jsr: case JVM_OPC_goto:
break;
default:
break;
}
return 0;
}
}
break;
case JVM_OPC_jsr_w:
case JVM_OPC_goto_w:
break;
default:
break;
}
}
return 1;
}
static void
{
int pos;
int new_pos;
if (new_code_len > 0) {
}
if (opcode == JVM_OPC_wide) {
/* lvIndex not used */
if ( wopcode==JVM_OPC_iinc ) {
}
} else {
int header;
int newHeader;
int low;
int high;
int i;
int npairs;
int widened;
int instr_len;
int delta;
int new_delta;
switch (opcode) {
case JVM_OPC_tableswitch:
}
}
break;
case JVM_OPC_lookupswitch:
}
for (i = 0; i< npairs; ++i) {
}
break;
case JVM_OPC_jsr: case JVM_OPC_goto:
case JVM_OPC_if_acmpeq: case JVM_OPC_if_acmpne:
case JVM_OPC_ifnull: case JVM_OPC_ifnonnull:
new_opcode = opcode;
if (widened == 0) {
} else if (widened == 2) {
switch (opcode) {
case JVM_OPC_jsr:
break;
case JVM_OPC_goto:
break;
default:
break;
}
} else if (widened == 5) {
switch (opcode) {
case JVM_OPC_ifeq:
break;
case JVM_OPC_ifge:
break;
case JVM_OPC_ifgt:
break;
case JVM_OPC_ifle:
break;
case JVM_OPC_iflt:
break;
case JVM_OPC_ifne:
break;
case JVM_OPC_if_icmpeq:
break;
case JVM_OPC_if_icmpne:
break;
case JVM_OPC_if_icmpge:
break;
case JVM_OPC_if_icmpgt:
break;
case JVM_OPC_if_icmple:
break;
case JVM_OPC_if_icmplt:
break;
case JVM_OPC_if_acmpeq:
break;
case JVM_OPC_if_acmpne:
break;
case JVM_OPC_ifnull:
break;
case JVM_OPC_ifnonnull:
break;
default:
break;
}
} else {
}
break;
case JVM_OPC_jsr_w:
case JVM_OPC_goto_w:
break;
default:
break;
}
}
}
static void
{
/* Do injections */
if ( len > 0 ) {
int pos;
pos = 0;
/* Adjust pos 0 to map to new pos 0, you never want to
* jump into this entry code injection. So the new pos 0
* will be past this entry_injection_code().
*/
}
}
/* Adjust instructions */
if (!adjust_instruction(mi)) {
}
}
/* Write new instructions */
}
}
static void
{
int len;
}
static void
{
unsigned i;
unsigned count;
for (i = 0; i < count; ++i) {
}
}
static void
{
unsigned i;
unsigned count;
for (i = 0; i < count; ++i) {
/* access, name, descriptor */
}
}
static void
{
unsigned i;
unsigned count;
for(i=0; i<count; i++) {
if ( start_pc == 0 ) {
new_start_pc = 0; /* Don't skip entry injection code. */
} else {
}
}
}
/* Used for LocalVariableTable and LocalVariableTypeTable attributes */
static void
{
unsigned i;
unsigned count;
for(i=0; i<count; i++) {
if ( start_pc == 0 ) {
new_start_pc = 0; /* Don't skip entry injection code. */
} else {
}
}
}
/* The uoffset field is u2 or u4 depending on the code_len.
* Note that the code_len is likely changing, so be careful here.
*/
static unsigned
{
}
}
static void
{
}
}
static unsigned
{
unsigned uoffset;
return uoffset;
}
/* Copy over verification_type_info structure */
static void
{
/* If there were ntypes, we just copy that over, no changes */
if ( ntypes > 0 ) {
int j;
for ( j = 0 ; j < ntypes ; j++ ) {
unsigned tag;
switch ( tag ) {
case JVM_ITEM_Object:
break;
case JVM_ITEM_Uninitialized:
/* Code offset for 'new' opcode is for this object */
break;
}
}
}
}
/* Process the StackMapTable attribute. We didn't add any basic blocks
* so the frame count remains the same but we may need to process the
* frame types due to offset changes putting things out of range.
*/
static void
{
unsigned i;
unsigned attr_len;
unsigned new_attr_len;
unsigned count;
unsigned delta_adj;
/* Save the position of the attribute length so we can fix it later */
if ( count == 0 ) {
return;
}
/* Process entire stackmap */
last_pc = 0;
last_new_pc = 0;
delta_adj = 0;
for ( i = 0 ; i < count ; i++ ) {
if ( ft <= 63 ) {
/* Frame Type: same_frame ([0,63]) */
if ( new_ft > 63 ) {
/* Change to same_frame_extended (251) */
new_ft = 251;
} else {
}
/* Frame Type: same_locals_1_stack_item_frame ([64,127]) */
/* Change to same_locals_1_stack_item_frame_extended (247) */
new_ft = 247;
} else {
}
/* Frame Type: reserved_for_future_use ([128,246]) */
} else if ( ft == 247 ) {
/* Frame Type: same_locals_1_stack_item_frame_extended (247) */
/* Frame Type: chop_frame ([248,250]) */
} else if ( ft == 251 ) {
/* Frame Type: same_frame_extended (251) */
/* Frame Type: append_frame ([252,254]) */
} else if ( ft == 255 ) {
unsigned ntypes;
/* Frame Type: full_frame (255) */
}
/* Update last_pc and last_new_pc (save on calls to method_code_map) */
/* Delta adjustment, all deltas are -1 now in attribute */
delta_adj = 1;
}
/* Update the attribute length */
}
/* Process the CLDC StackMap attribute. We didn't add any basic blocks
* so the frame count remains the same but we may need to process the
* frame types due to offset changes putting things out of range.
*/
static void
{
unsigned i;
unsigned attr_len;
unsigned new_attr_len;
unsigned count;
/* Save the position of the attribute length so we can fix it later */
if ( count == 0 ) {
return;
}
/* Process entire stackmap */
for ( i = 0 ; i < count ; i++ ) {
unsigned ntypes;
}
/* Update the attribute length */
}
static void
{
unsigned i;
unsigned count;
for(i=0; i<count; i++) {
}
}
static int
{
int len;
return 1;
}
return 0;
}
static void
{
} else {
unsigned len;
}
}
static int
{
return JNI_TRUE;
}
return JNI_FALSE;
}
static int
{
return JNI_TRUE;
}
return JNI_FALSE;
}
static int
{
return JNI_TRUE;
}
return JNI_FALSE;
}
static int
{
if ( system_class ) {
return JNI_TRUE;
return JNI_TRUE;
} else if ( is_clinit_method(name) ) {
return JNI_TRUE;
return JNI_TRUE;
}
/*
if ( access_flags & JVM_ACC_PRIVATE ) {
*pskip_call_return_sites = JNI_TRUE;
}
*/
}
return JNI_FALSE;
}
/* Process all code attributes */
static void
{
unsigned i;
unsigned attr_len;
unsigned max_stack;
unsigned attr_count;
unsigned new_attr_len;
/* Attribute Length */
/* Max Stack */
/* Max Locals */
/* Code Length */
/* Some methods should not be instrumented */
if ( ci->is_object_class &&
/* Copy remainder minus already copied, the U2 max_stack,
* U2 max_locals, and U4 code_length fields have already
* been processed.
*/
return;
}
/* Start Injection */
/* Save the current position as the start of the input bytecodes */
/* The max stack may increase */
/* Adjust all code offsets */
/* Fix up code length (save new_code_len for later attribute processing) */
/* Fixup max stack */
/* Copy exception table */
/* Copy code attributes (needs mi->new_code_len) */
for (i = 0; i < attr_count; ++i) {
}
/* Fix up attribute length */
/* Free method data */
}
static void
{
unsigned i;
unsigned access_flags;
unsigned attr_count;
for (i = 0; i < attr_count; ++i) {
} else {
unsigned len;
}
}
}
static void
{
unsigned i;
unsigned count;
if ( count > 0 ) {
}
for (i = 0; i < count; ++i) {
method_write(ci, i);
}
count);
}
}
/* ------------------------------------------------------------------- */
/* Cleanup function. */
static void
{
}
}
}
for(i=0; i<ci->cpool_count_plus_one; i++) {
}
}
}
}
static jboolean
{
if ( access_flags & JVM_ACC_INTERFACE ) {
return JNI_TRUE;
}
return JNI_FALSE;
}
static long
int system_class,
char* tclass_name,
char* tclass_sig,
char* call_name,
char* call_sig,
char* return_name,
char* return_sig,
char* obj_init_name,
char* obj_init_sig,
char* newarray_name,
char* newarray_sig,
unsigned char *buf,
long buf_len)
{
unsigned magic;
unsigned classfileMajorVersion;
unsigned classfileMinorVersion;
unsigned interface_count;
ci->injection_count = 0;
if ( magic != 0xCAFEBABE ) {
return (long)0;
}
/* minor version number not used */
/* major version number not used */
return (long)0;
}
}
if ( super_class == 0 ) {
}
if ( ci->injection_count == 0 ) {
return (long)0;
}
return (long)ci->output_position;
}
/* ------------------------------------------------------------------- */
/* Exported interfaces */
const char *name,
const unsigned char *file_image,
long file_len,
int system_class,
char* tclass_name, /* Name of class that has tracker methods. */
char* tclass_sig, /* Signature of tclass */
char* call_name, /* Method name to call at offset 0 */
char* call_sig, /* Signature of this method */
char* return_name, /* Method name to call before any return */
char* return_sig, /* Signature of this method */
char* obj_init_name, /* Method name to call in Object <init> */
char* obj_init_sig, /* Signature of this method */
char* newarray_name, /* Method name to call after newarray opcodes */
char* newarray_sig, /* Signature of this method */
unsigned char **pnew_file_image,
long *pnew_file_len,
{
long max_length;
long new_length;
void *new_image;
int len;
/* Initial setup of the CrwClassImage structure */
/* Do some interface error checks */
if ( pnew_file_image==NULL ) {
}
if ( pnew_file_len==NULL ) {
}
/* No file length means do nothing */
*pnew_file_image = NULL;
*pnew_file_len = 0;
if ( file_len==0 ) {
return;
}
/* Do some more interface error checks */
if ( file_image == NULL ) {
}
if ( file_len < 0 ) {
}
}
if ( tclass_name == NULL ) {
}
}
}
}
}
if ( return_name != NULL ) {
}
}
if ( obj_init_name != NULL ) {
}
}
if ( newarray_name != NULL ) {
}
}
/* Finish setup the CrwClassImage structure */
}
}
/* Do the injection */
/* Dispose or shrink the space to be returned. */
if ( new_length == 0 ) {
} else {
}
/* Return the new class image */
*pnew_file_image = (unsigned char *)new_image;
*pnew_file_len = (long)new_length;
/* Cleanup before we leave. */
}
/* Return the classname for this class which is inside the classfile image. */
{
unsigned magic;
char * name;
return name;
}
/* The only fields we need filled in are the image pointer and the error
* handler.
* By not adding an output buffer pointer, no output is created.
*/
/* Read out the bytes from the classfile image */
if ( magic != 0xCAFEBABE ) {
return name;
}
/* Read in constant pool. Since no output setup, writes are NOP's */
cpool_setup(&ci);
/* Get 'this' constant pool entry */
/* Duplicate the name */
/* Cleanup before we leave. */
/* Return malloc space */
return name;
}