| /* |
| * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package ilib; |
| |
| import java.io.IOException; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.DataOutputStream; |
| import java.util.List; |
| import java.util.Iterator; |
| import java.util.ArrayList; |
| |
| public class Inject implements RuntimeConstants { |
| |
| public static byte[] instrumentation(Options opt, |
| ClassLoader loader, |
| String className, |
| byte[] classfileBuffer) { |
| ClassReaderWriter c = new ClassReaderWriter(classfileBuffer); |
| (new Inject(className, c, loader == null, opt)).doit(); |
| return c.result(); |
| } |
| |
| static boolean verbose = false; |
| |
| final String className; |
| final ClassReaderWriter c; |
| final boolean isSystem; |
| final Options options; |
| |
| int constantPoolCount; |
| int methodsCount; |
| int methodsCountPos; |
| int profiler; |
| int wrappedTrackerIndex = 0; |
| int thisClassIndex = 0; |
| |
| TrackerInjector callInjector; |
| TrackerInjector allocInjector; |
| TrackerInjector defaultInjector; |
| |
| static interface TrackerInjector extends Injector { |
| void reinit(int tracker); |
| int stackSize(int currentSize); |
| } |
| |
| static class SimpleInjector implements TrackerInjector { |
| byte[] injection; |
| |
| public int stackSize(int currentSize) { |
| return currentSize; |
| } |
| |
| public void reinit(int tracker) { |
| injection = new byte[3]; |
| injection[0] = (byte)opc_invokestatic; |
| injection[1] = (byte)(tracker >> 8); |
| injection[2] = (byte)tracker; |
| } |
| |
| public byte[] bytecodes(String className, String methodName, int location) { |
| return injection; |
| } |
| } |
| |
| static class ObjectInjector implements TrackerInjector { |
| byte[] injection; |
| |
| public int stackSize(int currentSize) { |
| return currentSize + 1; |
| } |
| |
| public void reinit(int tracker) { |
| injection = new byte[4]; |
| injection[0] = (byte)opc_dup; |
| injection[1] = (byte)opc_invokestatic; |
| injection[2] = (byte)(tracker >> 8); |
| injection[3] = (byte)tracker; |
| } |
| |
| public byte[] bytecodes(String className, String methodName, int location) { |
| return injection; |
| } |
| } |
| |
| class IndexedInjector implements TrackerInjector { |
| int counter = 0; |
| int tracker; |
| List<Info> infoList = new ArrayList<Info>(); |
| |
| public int stackSize(int currentSize) { |
| return currentSize + 1; |
| } |
| |
| public void reinit(int tracker) { |
| this.tracker = tracker; |
| } |
| |
| void dump(File outDir, String filename) throws IOException { |
| FileOutputStream fileOut = new FileOutputStream(new File(outDir, filename)); |
| DataOutputStream dataOut = new DataOutputStream(fileOut); |
| |
| String currentClassName = null; |
| |
| dataOut.writeInt(infoList.size()); |
| for (Iterator<Info> it = infoList.iterator(); it.hasNext(); ) { |
| Info info = it.next(); |
| if (!info.className.equals(currentClassName)) { |
| dataOut.writeInt(123456); // class name marker |
| currentClassName = info.className; |
| dataOut.writeUTF(currentClassName); |
| } |
| dataOut.writeInt(info.location); |
| dataOut.writeUTF(info.methodName); |
| } |
| dataOut.close(); |
| } |
| |
| public byte[] bytecodes(String className, String methodName, int location) { |
| byte[] injection = new byte[6]; |
| int injectedIndex = options.fixedIndex != 0? options.fixedIndex : ++counter; |
| infoList.add(new Info(counter, className, methodName, location)); |
| injection[0] = (byte)opc_sipush; |
| injection[1] = (byte)(injectedIndex >> 8); |
| injection[2] = (byte)injectedIndex; |
| injection[3] = (byte)opc_invokestatic; |
| injection[4] = (byte)(tracker >> 8); |
| injection[5] = (byte)tracker; |
| return injection; |
| } |
| } |
| |
| Inject(String className, ClassReaderWriter c, boolean isSystem, Options options) { |
| this.className = className; |
| this.c = c; |
| this.isSystem = isSystem; |
| this.options = options; |
| } |
| |
| void doit() { |
| int i; |
| c.copy(4 + 2 + 2); // magic min/maj version |
| int constantPoolCountPos = c.generatedPosition(); |
| constantPoolCount = c.copyU2(); |
| // copy old constant pool |
| c.copyConstantPool(constantPoolCount); |
| |
| if (verbose) { |
| System.out.println("ConstantPool expanded from: " + |
| constantPoolCount); |
| } |
| |
| profiler = addClassToConstantPool(options.trackerClassName); |
| if (options.shouldInstrumentNew || options.shouldInstrumentObjectInit) { |
| if (options.shouldInstrumentIndexed) { |
| if (allocInjector == null) { |
| // first time - create it |
| allocInjector = new IndexedInjector(); |
| } |
| int allocTracker = addMethodToConstantPool(profiler, |
| options.allocTrackerMethodName, |
| "(I)V"); |
| allocInjector.reinit(allocTracker); |
| } else if (options.shouldInstrumentObject) { |
| if (allocInjector == null) { |
| // first time - create it |
| allocInjector = new ObjectInjector(); |
| } |
| int allocTracker = addMethodToConstantPool(profiler, |
| options.allocTrackerMethodName, |
| "(Ljava/lang/Object;)V"); |
| allocInjector.reinit(allocTracker); |
| } else { |
| if (allocInjector == null) { |
| // first time - create it |
| allocInjector = new SimpleInjector(); |
| } |
| int allocTracker = addMethodToConstantPool(profiler, |
| options.allocTrackerMethodName, |
| "()V"); |
| allocInjector.reinit(allocTracker); |
| } |
| defaultInjector = allocInjector; |
| } |
| if (options.shouldInstrumentCall) { |
| if (options.shouldInstrumentIndexed) { |
| if (callInjector == null) { |
| // first time - create it |
| callInjector = new IndexedInjector(); |
| } |
| int callTracker = addMethodToConstantPool(profiler, |
| options.callTrackerMethodName, |
| "(I)V"); |
| callInjector.reinit(callTracker); |
| } else { |
| if (callInjector == null) { |
| // first time - create it |
| callInjector = new SimpleInjector(); |
| } |
| int callTracker = addMethodToConstantPool(profiler, |
| options.callTrackerMethodName, |
| "()V"); |
| callInjector.reinit(callTracker); |
| } |
| defaultInjector = callInjector; |
| } |
| |
| if (verbose) { |
| System.out.println("To: " + constantPoolCount); |
| } |
| |
| c.setSection(1); |
| |
| c.copy(2 + 2 + 2); // access, this, super |
| int interfaceCount = c.copyU2(); |
| if (verbose) { |
| System.out.println("interfaceCount: " + interfaceCount); |
| } |
| c.copy(interfaceCount * 2); |
| copyFields(); // fields |
| copyMethods(); // methods |
| int attrCountPos = c.generatedPosition(); |
| int attrCount = c.copyU2(); |
| if (verbose) { |
| System.out.println("class attrCount: " + attrCount); |
| } |
| // copy the class attributes |
| copyAttrs(attrCount); |
| |
| c.randomAccessWriteU2(constantPoolCountPos, constantPoolCount); |
| } |
| |
| |
| void copyFields() { |
| int count = c.copyU2(); |
| if (verbose) { |
| System.out.println("fields count: " + count); |
| } |
| for (int i = 0; i < count; ++i) { |
| c.copy(6); // access, name, descriptor |
| int attrCount = c.copyU2(); |
| if (verbose) { |
| System.out.println("field attr count: " + attrCount); |
| } |
| copyAttrs(attrCount); |
| } |
| } |
| |
| void copyMethods() { |
| methodsCountPos = c.generatedPosition(); |
| methodsCount = c.copyU2(); |
| int initialMethodsCount = methodsCount; |
| if (verbose) { |
| System.out.println("methods count: " + methodsCount); |
| } |
| for (int i = 0; i < initialMethodsCount; ++i) { |
| copyMethod(); |
| } |
| } |
| |
| void copyMethod() { |
| int accessFlags = c.copyU2();// access flags |
| if (options.shouldInstrumentNativeMethods && (accessFlags & ACC_NATIVE) != 0) { |
| wrapNativeMethod(accessFlags); |
| return; |
| } |
| int nameIndex = c.copyU2(); // name |
| String methodName = c.constantPoolString(nameIndex); |
| c.copyU2(); // descriptor |
| int attrCount = c.copyU2(); // attribute count |
| if (verbose) { |
| System.out.println("methods attr count: " + attrCount); |
| } |
| for (int i = 0; i < attrCount; ++i) { |
| copyAttrForMethod(methodName, accessFlags); |
| } |
| } |
| |
| void wrapNativeMethod(int accessFlags) { |
| // first, copy the native method with the name changed |
| // accessFlags have already been copied |
| int nameIndex = c.readU2(); // name |
| String methodName = c.constantPoolString(nameIndex); |
| String wrappedMethodName = options.wrappedPrefix + methodName; |
| int wrappedNameIndex = writeCPEntryUtf8(wrappedMethodName); |
| c.writeU2(wrappedNameIndex); // change to the wrapped name |
| |
| int descriptorIndex = c.copyU2(); // descriptor index |
| |
| int attrCount = c.copyU2(); // attribute count |
| // need to replicate these attributes (esp Exceptions) in wrapper |
| // so mark this location so we can rewind |
| c.markLocalPositionStart(); |
| for (int i = 0; i < attrCount; ++i) { |
| copyAttrForMethod(methodName, accessFlags); |
| } |
| if (true) { |
| System.err.println(" wrapped: " + methodName); |
| } |
| |
| // now write the wrapper method |
| c.writeU2(accessFlags & ~ACC_NATIVE); |
| c.writeU2(nameIndex); // original unwrapped name |
| c.writeU2(descriptorIndex); // descriptor is the same |
| |
| c.writeU2(attrCount + 1); // wrapped plus a code attribute |
| // rewind to wrapped attributes |
| c.rewind(); |
| for (int i = 0; i < attrCount; ++i) { |
| copyAttrForMethod(methodName, accessFlags); |
| } |
| |
| // generate a Code attribute for the wrapper method |
| int wrappedIndex = addMethodToConstantPool(getThisClassIndex(), |
| wrappedNameIndex, |
| descriptorIndex); |
| String descriptor = c.constantPoolString(descriptorIndex); |
| createWrapperCodeAttr(nameIndex, accessFlags, descriptor, wrappedIndex); |
| |
| // increment method count |
| c.randomAccessWriteU2(methodsCountPos, ++methodsCount); |
| } |
| |
| void copyAttrs(int attrCount) { |
| for (int i = 0; i < attrCount; ++i) { |
| copyAttr(); |
| } |
| } |
| |
| void copyAttr() { |
| c.copy(2); // name |
| int len = c.copyU4(); // attr len |
| if (verbose) { |
| System.out.println("attr len: " + len); |
| } |
| c.copy(len); // attribute info |
| } |
| |
| void copyAttrForMethod(String methodName, int accessFlags) { |
| int nameIndex = c.copyU2(); // name |
| // check for Code attr |
| if (nameIndex == c.codeAttributeIndex) { |
| try { |
| copyCodeAttr(methodName); |
| } catch (IOException exc) { |
| System.err.println("Code Exception - " + exc); |
| System.exit(1); |
| } |
| } else { |
| int len = c.copyU4(); // attr len |
| if (verbose) { |
| System.out.println("method attr len: " + len); |
| } |
| c.copy(len); // attribute info |
| } |
| } |
| |
| void copyAttrForCode(InjectBytecodes ib) throws IOException { |
| int nameIndex = c.copyU2(); // name |
| |
| // check for Code attr |
| if (nameIndex == c.lineNumberAttributeIndex) { |
| ib.copyLineNumberAttr(); |
| } else if (nameIndex == c.localVarAttributeIndex) { |
| ib.copyLocalVarAttr(); |
| } else { |
| int len = c.copyU4(); // attr len |
| if (verbose) { |
| System.out.println("code attr len: " + len); |
| } |
| c.copy(len); // attribute info |
| } |
| } |
| |
| void copyCodeAttr(String methodName) throws IOException { |
| if (verbose) { |
| System.out.println("Code attr found"); |
| } |
| int attrLengthPos = c.generatedPosition(); |
| int attrLength = c.copyU4(); // attr len |
| int maxStack = c.readU2(); // max stack |
| c.writeU2(defaultInjector == null? maxStack : |
| defaultInjector.stackSize(maxStack)); // big enough for injected code |
| c.copyU2(); // max locals |
| int codeLengthPos = c.generatedPosition(); |
| int codeLength = c.copyU4(); // code length |
| if (options.targetMethod != null && !options.targetMethod.equals(methodName)) { |
| c.copy(attrLength - 8); // copy remainder minus already copied |
| return; |
| } |
| if (isSystem) { |
| if (codeLength == 1 && methodName.equals("finalize")) { |
| if (verbose) { |
| System.out.println("empty system finalizer not instrumented"); |
| } |
| c.copy(attrLength - 8); // copy remainder minus already copied |
| return; |
| } |
| if (codeLength == 1 && methodName.equals("<init>")) { |
| if (verbose) { |
| System.out.println("empty system constructor not instrumented"); |
| } |
| if (!options.shouldInstrumentObjectInit) { |
| c.copy(attrLength - 8); // copy remainder minus already copied |
| return; |
| } |
| } |
| if (methodName.equals("<clinit>")) { |
| if (verbose) { |
| System.out.println("system class initializer not instrumented"); |
| } |
| c.copy(attrLength - 8); // copy remainder minus already copied |
| return; |
| } |
| } |
| if (options.shouldInstrumentObjectInit |
| && (!className.equals("java/lang/Object") |
| || !methodName.equals("<init>"))) { |
| c.copy(attrLength - 8); // copy remainder minus already copied |
| return; |
| } |
| |
| InjectBytecodes ib = new InjectBytecodes(c, codeLength, className, methodName); |
| |
| if (options.shouldInstrumentNew) { |
| ib.injectAfter(opc_new, allocInjector); |
| ib.injectAfter(opc_newarray, allocInjector); |
| ib.injectAfter(opc_anewarray, allocInjector); |
| ib.injectAfter(opc_multianewarray, allocInjector); |
| } |
| if (options.shouldInstrumentCall) { |
| ib.inject(0, callInjector.bytecodes(className, methodName, 0)); |
| } |
| if (options.shouldInstrumentObjectInit) { |
| ib.inject(0, allocInjector.bytecodes(className, methodName, 0)); |
| } |
| |
| ib.adjustOffsets(); |
| |
| // fix up code length |
| int newCodeLength = c.generatedPosition() - (codeLengthPos + 4); |
| c.randomAccessWriteU4(codeLengthPos, newCodeLength); |
| if (verbose) { |
| System.out.println("code length old: " + codeLength + |
| ", new: " + newCodeLength); |
| } |
| |
| ib.copyExceptionTable(); |
| |
| int attrCount = c.copyU2(); |
| for (int i = 0; i < attrCount; ++i) { |
| copyAttrForCode(ib); |
| } |
| |
| // fix up attr length |
| int newAttrLength = c.generatedPosition() - (attrLengthPos + 4); |
| c.randomAccessWriteU4(attrLengthPos, newAttrLength); |
| if (verbose) { |
| System.out.println("attr length old: " + attrLength + |
| ", new: " + newAttrLength); |
| } |
| } |
| |
| int nextDescriptorIndex(String descriptor, int index) { |
| switch (descriptor.charAt(index)) { |
| case 'B': // byte |
| case 'C': // char |
| case 'I': // int |
| case 'S': // short |
| case 'Z': // boolean |
| case 'F': // float |
| case 'D': // double |
| case 'J': // long |
| return index + 1; |
| case 'L': // object |
| int i = index + 1; |
| while (descriptor.charAt(i) != ';') { |
| ++i; |
| } |
| return i + 1; |
| case '[': // array |
| return nextDescriptorIndex(descriptor, index + 1); |
| } |
| throw new InternalError("should not reach here"); |
| } |
| |
| int getWrappedTrackerIndex() { |
| if (wrappedTrackerIndex == 0) { |
| wrappedTrackerIndex = addMethodToConstantPool(profiler, |
| options.wrappedTrackerMethodName, |
| "(Ljava/lang/String;I)V"); |
| } |
| return wrappedTrackerIndex; |
| } |
| |
| int getThisClassIndex() { |
| if (thisClassIndex == 0) { |
| thisClassIndex = addClassToConstantPool(className); |
| } |
| return thisClassIndex; |
| } |
| |
| int computeMaxLocals(String descriptor, int accessFlags) { |
| int index = 1; |
| int slot = 0; |
| |
| if ((accessFlags & ACC_STATIC) == 0) { |
| ++slot; |
| } |
| char type; |
| while ((type = descriptor.charAt(index)) != ')') { |
| switch (type) { |
| case 'B': // byte |
| case 'C': // char |
| case 'I': // int |
| case 'S': // short |
| case 'Z': // boolean |
| case 'F': // float |
| case 'L': // object |
| case '[': // array |
| ++slot; |
| break; |
| case 'D': // double |
| case 'J': // long |
| slot += 2; |
| break; |
| } |
| index = nextDescriptorIndex(descriptor, index); |
| } |
| |
| return slot; |
| } |
| |
| |
| void createWrapperCodeAttr(int methodNameIndex, int accessFlags, |
| String descriptor, int wrappedIndex) { |
| int maxLocals = computeMaxLocals(descriptor, accessFlags); |
| |
| c.writeU2(c.codeAttributeIndex); // |
| int attrLengthPos = c.generatedPosition(); |
| c.writeU4(0); // attr len -- fix up below |
| c.writeU2(maxLocals + 4); // max stack |
| c.writeU2(maxLocals); // max locals |
| int codeLengthPos = c.generatedPosition(); |
| c.writeU4(0); // code length -- fix up below |
| |
| int methodStringIndex = writeCPEntryString(methodNameIndex); |
| |
| c.writeU1(opc_ldc_w); |
| c.writeU2(methodStringIndex); // send the method name |
| c.writeU1(opc_sipush); |
| c.writeU2(options.fixedIndex); |
| c.writeU1(opc_invokestatic); |
| c.writeU2(getWrappedTrackerIndex()); |
| |
| // set-up args |
| int index = 1; |
| int slot = 0; |
| if ((accessFlags & ACC_STATIC) == 0) { |
| c.writeU1(opc_aload_0); // this |
| ++slot; |
| } |
| char type; |
| while ((type = descriptor.charAt(index)) != ')') { |
| switch (type) { |
| case 'B': // byte |
| case 'C': // char |
| case 'I': // int |
| case 'S': // short |
| case 'Z': // boolean |
| c.writeU1(opc_iload); |
| c.writeU1(slot); |
| ++slot; |
| break; |
| case 'F': // float |
| c.writeU1(opc_fload); |
| c.writeU1(slot); |
| ++slot; |
| break; |
| case 'D': // double |
| c.writeU1(opc_dload); |
| c.writeU1(slot); |
| slot += 2; |
| break; |
| case 'J': // long |
| c.writeU1(opc_lload); |
| c.writeU1(slot); |
| slot += 2; |
| break; |
| case 'L': // object |
| case '[': // array |
| c.writeU1(opc_aload); |
| c.writeU1(slot); |
| ++slot; |
| break; |
| } |
| index = nextDescriptorIndex(descriptor, index); |
| } |
| |
| // call the wrapped version |
| if ((accessFlags & ACC_STATIC) == 0) { |
| c.writeU1(opc_invokevirtual); |
| } else { |
| c.writeU1(opc_invokestatic); |
| } |
| c.writeU2(wrappedIndex); |
| |
| // return correct type |
| switch (descriptor.charAt(index+1)) { |
| case 'B': // byte |
| case 'C': // char |
| case 'I': // int |
| case 'S': // short |
| case 'Z': // boolean |
| c.writeU1(opc_ireturn); |
| break; |
| case 'F': // float |
| c.writeU1(opc_freturn); |
| break; |
| case 'D': // double |
| c.writeU1(opc_dreturn); |
| break; |
| case 'J': // long |
| c.writeU1(opc_lreturn); |
| break; |
| case 'L': // object |
| case '[': // array |
| c.writeU1(opc_areturn); |
| break; |
| case 'V': // void |
| c.writeU1(opc_return); |
| break; |
| } |
| |
| // end of code |
| |
| // fix up code length |
| int newCodeLength = c.generatedPosition() - (codeLengthPos + 4); |
| c.randomAccessWriteU4(codeLengthPos, newCodeLength); |
| |
| c.writeU2(0); // exception table length |
| c.writeU2(0); // attribute count |
| |
| // fix up attr length |
| int newAttrLength = c.generatedPosition() - (attrLengthPos + 4); |
| c.randomAccessWriteU4(attrLengthPos, newAttrLength); |
| } |
| |
| |
| int addClassToConstantPool(String className) { |
| int prevSection = c.setSection(0); |
| int classNameIndex = writeCPEntryUtf8(className); |
| int classIndex = writeCPEntryClass(classNameIndex); |
| c.setSection(prevSection); |
| return classIndex; |
| } |
| |
| int addMethodToConstantPool(int classIndex, |
| String methodName, |
| String descr) { |
| int prevSection = c.setSection(0); |
| int methodNameIndex = writeCPEntryUtf8(methodName); |
| int descrIndex = writeCPEntryUtf8(descr); |
| c.setSection(prevSection); |
| return addMethodToConstantPool(classIndex, methodNameIndex, descrIndex); |
| } |
| |
| int addMethodToConstantPool(int classIndex, |
| int methodNameIndex, |
| int descrIndex) { |
| int prevSection = c.setSection(0); |
| int nameAndTypeIndex = writeCPEntryNameAndType(methodNameIndex, |
| descrIndex); |
| int methodIndex = writeCPEntryMethodRef(classIndex, nameAndTypeIndex); |
| c.setSection(prevSection); |
| return methodIndex; |
| } |
| |
| int writeCPEntryUtf8(String str) { |
| int prevSection = c.setSection(0); |
| int len = str.length(); |
| c.writeU1(CONSTANT_UTF8); // Utf8 tag |
| c.writeU2(len); |
| for (int i = 0; i < len; ++i) { |
| c.writeU1(str.charAt(i)); |
| } |
| c.setSection(prevSection); |
| return constantPoolCount++; |
| } |
| |
| int writeCPEntryString(int utf8Index) { |
| int prevSection = c.setSection(0); |
| c.writeU1(CONSTANT_STRING); |
| c.writeU2(utf8Index); |
| c.setSection(prevSection); |
| return constantPoolCount++; |
| } |
| |
| int writeCPEntryClass(int classNameIndex) { |
| int prevSection = c.setSection(0); |
| c.writeU1(CONSTANT_CLASS); |
| c.writeU2(classNameIndex); |
| c.setSection(prevSection); |
| return constantPoolCount++; |
| } |
| |
| int writeCPEntryNameAndType(int nameIndex, int descrIndex) { |
| int prevSection = c.setSection(0); |
| c.writeU1(CONSTANT_NAMEANDTYPE); |
| c.writeU2(nameIndex); |
| c.writeU2(descrIndex); |
| c.setSection(prevSection); |
| return constantPoolCount++; |
| } |
| |
| int writeCPEntryMethodRef(int classIndex, int nameAndTypeIndex) { |
| int prevSection = c.setSection(0); |
| c.writeU1(CONSTANT_METHOD); |
| c.writeU2(classIndex); |
| c.writeU2(nameAndTypeIndex); |
| c.setSection(prevSection); |
| return constantPoolCount++; |
| } |
| } |