001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.bytecode.hook;
006:
007: import com.tc.asm.ClassAdapter;
008: import com.tc.asm.ClassReader;
009: import com.tc.asm.ClassVisitor;
010: import com.tc.asm.ClassWriter;
011: import com.tc.asm.Label;
012: import com.tc.asm.MethodAdapter;
013: import com.tc.asm.MethodVisitor;
014: import com.tc.asm.Opcodes;
015: import com.tc.asm.Type;
016: import com.tc.util.runtime.Vm;
017:
018: import java.util.HashMap;
019: import java.util.Map;
020:
021: /**
022: * Instruments the java.lang.ClassLoader to plug in the Class PreProcessor mechanism. <p/>
023: *
024: * We are using a lazy initialization of the class preprocessor to allow all class pre processor logic to be in system
025: * classpath and not in bootclasspath. <p/>
026: *
027: * This implementation should support IBM custom JRE
028: */
029: public class ClassLoaderPreProcessorImpl {
030:
031: private final static String CLASSLOADER_CLASS_NAME = "java/lang/ClassLoader";
032: private final static String DEFINECLASS0_METHOD_NAME = "defineClass0";
033:
034: // For JDK5
035: private final static String DEFINECLASS1_METHOD_NAME = "defineClass1";
036: private final static String DEFINECLASS2_METHOD_NAME = "defineClass2";
037:
038: private static final String DESC_CORE = "Ljava/lang/String;[BIILjava/security/ProtectionDomain;";
039: private static final String DESC_PREFIX = "(" + DESC_CORE;
040: private static final String DESC_HELPER = "(Ljava/lang/ClassLoader;"
041: + DESC_CORE + ")[B";
042:
043: private static final String DESC_BYTEBUFFER_CORE = "Ljava/lang/String;Ljava/nio/ByteBuffer;IILjava/security/ProtectionDomain;";
044: private static final String DESC_BYTEBUFFER_PREFIX = "("
045: + DESC_BYTEBUFFER_CORE;
046: private static final String DESC_BYTEBUFFER_HELPER = "(Ljava/lang/ClassLoader;"
047: + DESC_BYTEBUFFER_CORE + ")Ljava/nio/ByteBuffer;";
048:
049: public ClassLoaderPreProcessorImpl() {
050: //
051: }
052:
053: /**
054: * Patch caller side of defineClass0
055: *
056: * <pre>
057: * byte[] weaved = ..hook.impl.ClassPreProcessorHelper.defineClass0Pre(this, args..);
058: * klass = defineClass0(name, weaved, 0, weaved.length, protectionDomain);
059: * </pre>
060: *
061: * @param classLoaderBytecode
062: * @return
063: */
064: public byte[] preProcess(byte[] classLoaderBytecode) {
065: try {
066: ClassReader cr = new ClassReader(classLoaderBytecode);
067: ClassWriter cw = new ClassWriter(cr,
068: ClassWriter.COMPUTE_MAXS);
069: ClassVisitor cv = Vm.isIBM() ? (ClassVisitor) new IBMClassLoaderAdapter(
070: cw)
071: : new ClassLoaderVisitor(cw);
072: cr.accept(cv, ClassReader.SKIP_FRAMES);
073: return cw.toByteArray();
074: } catch (Exception e) {
075: System.err.println("failed to patch ClassLoader:");
076: e.printStackTrace();
077: return classLoaderBytecode;
078: }
079: }
080:
081: static class IBMClassLoaderAdapter extends ClassAdapter implements
082: Opcodes {
083:
084: private String className;
085:
086: public IBMClassLoaderAdapter(ClassVisitor cv) {
087: super (cv);
088: }
089:
090: public void visit(int version, int access, String name,
091: String signature, String super Name, String[] interfaces) {
092: super .visit(version, access, name, signature, super Name,
093: interfaces);
094: this .className = name;
095: }
096:
097: public MethodVisitor visitMethod(int access, String name,
098: String desc, String signature, String[] exceptions) {
099: MethodVisitor mv = super .visitMethod(access, name, desc,
100: signature, exceptions);
101: if (CLASSLOADER_CLASS_NAME.equals(className)
102: && "loadClass".equals(name)
103: && "(Ljava/lang/String;)Ljava/lang/Class;"
104: .equals(desc)) {
105: return new LoadClassVisitor(mv);
106:
107: } else if (CLASSLOADER_CLASS_NAME.equals(className)
108: && "getResource".equals(name)
109: && "(Ljava/lang/String;)Ljava/net/URL;"
110: .equals(desc)) {
111: return new GetResourceVisitor(mv);
112: }
113:
114: return new MethodAdapter(mv) {
115: public void visitMethodInsn(int opcode, String owner,
116: String mname, String mdesc) {
117: if (CLASSLOADER_CLASS_NAME.equals(owner)
118: && "defineClassImpl".equals(mname)) {
119: mname = "__tc_defineClassImpl";
120: }
121: super .visitMethodInsn(opcode, owner, mname, mdesc);
122: }
123: };
124: }
125:
126: public void visitEnd() {
127: MethodVisitor mv = super
128: .visitMethod(
129: ACC_PRIVATE | ACC_SYNTHETIC,
130: "__tc_defineClassImpl",
131: "(Ljava/lang/String;[BIILjava/lang/Object;)Ljava/lang/Class;",
132: null, null);
133: mv.visitCode();
134: mv.visitVarInsn(ALOAD, 0);
135: mv.visitVarInsn(ALOAD, 1);
136: mv.visitVarInsn(ALOAD, 2);
137: mv.visitVarInsn(ILOAD, 3);
138: mv.visitVarInsn(ILOAD, 4);
139: mv.visitVarInsn(ALOAD, 5);
140: mv.visitTypeInsn(CHECKCAST,
141: "java/security/ProtectionDomain");
142: mv
143: .visitMethodInsn(
144: INVOKESTATIC,
145: "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
146: "defineClass0Pre",
147: "(Ljava/lang/ClassLoader;Ljava/lang/String;[BIILjava/security/ProtectionDomain;)[B");
148:
149: mv.visitVarInsn(ASTORE, 6); // byte[] b = CPH.defineClass0Pre(..);
150: // If instrumented use the new array, otherwise pass straight through
151: Label notInstrumented = new Label();
152: mv.visitVarInsn(ALOAD, 2); // if (b != original) {
153: mv.visitVarInsn(ALOAD, 6);
154: mv.visitJumpInsn(IF_ACMPEQ, notInstrumented);
155: mv.visitVarInsn(ALOAD, 0);
156: mv.visitVarInsn(ALOAD, 1);
157: mv.visitVarInsn(ALOAD, 6);
158: mv.visitInsn(ICONST_0);
159: mv.visitVarInsn(ALOAD, 6); // b.length
160: mv.visitInsn(ARRAYLENGTH);
161: mv.visitVarInsn(ALOAD, 5);
162: mv
163: .visitMethodInsn(INVOKESPECIAL,
164: "java/lang/ClassLoader", "defineClassImpl",
165: "(Ljava/lang/String;[BIILjava/lang/Object;)Ljava/lang/Class;");
166: mv.visitInsn(ARETURN);
167: mv.visitLabel(notInstrumented); // } else {
168: mv.visitVarInsn(ALOAD, 0);
169: mv.visitVarInsn(ALOAD, 1);
170: mv.visitVarInsn(ALOAD, 2);
171: mv.visitVarInsn(ILOAD, 3);
172: mv.visitVarInsn(ILOAD, 4);
173: mv.visitVarInsn(ALOAD, 5);
174: mv
175: .visitMethodInsn(INVOKESPECIAL,
176: "java/lang/ClassLoader", "defineClassImpl",
177: "(Ljava/lang/String;[BIILjava/lang/Object;)Ljava/lang/Class;");
178:
179: mv.visitInsn(ARETURN);
180: mv.visitMaxs(0, 0);
181: mv.visitEnd();
182:
183: super .visitEnd();
184: }
185: }
186:
187: private static class ClassLoaderVisitor extends ClassAdapter {
188: private String className;
189:
190: public ClassLoaderVisitor(ClassVisitor cv) {
191: super (cv);
192: }
193:
194: public void visit(int version, int access, String name,
195: String signature, String super Name, String[] interfaces) {
196: super .visit(version, access, name, signature, super Name,
197: interfaces);
198: this .className = name;
199: }
200:
201: public MethodVisitor visitMethod(int access, String name,
202: String desc, String signature, String[] exceptions) {
203: MethodVisitor mv = super .visitMethod(access, name, desc,
204: signature, exceptions);
205: if (CLASSLOADER_CLASS_NAME.equals(className)
206: && "loadClassInternal".equals(name)
207: && "(Ljava/lang/String;)Ljava/lang/Class;"
208: .equals(desc)) {
209: return new LoadClassVisitor(mv);
210: } else if (CLASSLOADER_CLASS_NAME.equals(className)
211: && "getResource".equals(name)
212: && "(Ljava/lang/String;)Ljava/net/URL;"
213: .equals(desc)) {
214: return new GetResourceVisitor(mv);
215: } else if ("initSystemClassLoader".equals(name)) {
216: return new SclSetAdapter(mv);
217: } else {
218: return new ProcessingVisitor(mv, access, desc);
219: }
220: }
221: }
222:
223: private static class SclSetAdapter extends MethodAdapter implements
224: Opcodes {
225:
226: public SclSetAdapter(MethodVisitor mv) {
227: super (mv);
228: }
229:
230: public void visitFieldInsn(int opcode, String owner,
231: String name, String desc) {
232: super .visitFieldInsn(opcode, owner, name, desc);
233:
234: if ("sclSet".equals(name) && (PUTSTATIC == opcode)) {
235: super
236: .visitMethodInsn(
237: INVOKESTATIC,
238: "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
239: "systemLoaderInitialized", "()V");
240: }
241: }
242: }
243:
244: /**
245: * Adding hook for loading tc classes. Uses a primitive state machine to insert new code after first line attribute
246: * (if exists) in order to help with debugging and line-based breakpoints.
247: */
248: public static class GetResourceVisitor extends MethodAdapter
249: implements Opcodes {
250: private boolean isInstrumented = false;
251:
252: public GetResourceVisitor(MethodVisitor mv) {
253: super (mv);
254: }
255:
256: public void visitLineNumber(int line, Label start) {
257: super .visitLineNumber(line, start);
258: if (!isInstrumented)
259: instrument();
260: }
261:
262: public void visitVarInsn(int opcode, int var) {
263: if (!isInstrumented)
264: instrument();
265: super .visitVarInsn(opcode, var);
266: }
267:
268: private void instrument() {
269: Label l = new Label();
270:
271: mv.visitVarInsn(ALOAD, 1);
272: mv.visitVarInsn(ALOAD, 0);
273: mv
274: .visitMethodInsn(
275: INVOKESTATIC,
276: "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
277: "getTCResource",
278: "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/net/URL;");
279: mv.visitVarInsn(ASTORE, 2);
280:
281: mv.visitVarInsn(ALOAD, 2);
282: mv.visitJumpInsn(IFNULL, l);
283:
284: mv.visitVarInsn(ALOAD, 2);
285: mv.visitInsn(ARETURN);
286:
287: mv.visitLabel(l);
288:
289: this .isInstrumented = true;
290: }
291:
292: }
293:
294: /**
295: * Adding hook into ClassLoader.loadClassInternal() method to load tc classes.
296: * </p>
297: *
298: * Primitive state machine is used to insert new code after first line attribute (if exists) in order to help with
299: * debugging and line-based breakpoints.
300: *
301: * <pre>
302: * mv.visitCode();
303: * Label l0 = new Label();
304: * mv.visitLabel(l0);
305: * mv.visitLineNumber(319, l0);
306: * ... right here
307: * mv.visitVarInsn(ALOAD, 0);
308: * mv.visitVarInsn(ALOAD, 1);
309: * mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
310: * mv.visitInsn(ARETURN);
311: * mv.visitMaxs(2, 2);
312: * mv.visitEnd();
313: * </pre>
314: */
315: public static class LoadClassVisitor extends MethodAdapter
316: implements Opcodes {
317: private boolean isInstrumented = false;
318:
319: public LoadClassVisitor(MethodVisitor mv) {
320: super (mv);
321: }
322:
323: public void visitLineNumber(int line, Label start) {
324: super .visitLineNumber(line, start);
325: if (!isInstrumented)
326: instrument();
327: }
328:
329: public void visitVarInsn(int opcode, int var) {
330: if (!isInstrumented)
331: instrument();
332: super .visitVarInsn(opcode, var);
333: }
334:
335: private void instrument() {
336: Label l = new Label();
337:
338: mv.visitVarInsn(ALOAD, 1);
339: mv.visitVarInsn(ALOAD, 0);
340: mv
341: .visitMethodInsn(
342: INVOKESTATIC,
343: "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
344: "getTCClass",
345: "(Ljava/lang/String;Ljava/lang/ClassLoader;)[B");
346: mv.visitVarInsn(ASTORE, 2);
347:
348: mv.visitVarInsn(ALOAD, 2);
349: mv.visitJumpInsn(IFNULL, l);
350:
351: mv.visitVarInsn(ALOAD, 0);
352: mv.visitVarInsn(ALOAD, 1);
353: mv.visitVarInsn(ALOAD, 2);
354: mv.visitInsn(ICONST_0);
355: mv.visitVarInsn(ALOAD, 2);
356: mv.visitInsn(ARRAYLENGTH);
357: mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader",
358: "defineClass",
359: "(Ljava/lang/String;[BII)Ljava/lang/Class;");
360: mv.visitInsn(ARETURN);
361:
362: mv.visitLabel(l);
363:
364: this .isInstrumented = true;
365: }
366:
367: }
368:
369: /**
370: * Wraps calls to defineClass0, defineClass1 and <code>defineClass2</code> methods:
371: *
372: * <pre>
373: * byte[] newbytes = ClassProcessorHelper.defineClass0Pre(loader, name, b, off, len, pd);
374: * if (b == newbytes) {
375: * defineClass0(loader, name, b, off, len, pd);
376: * } else {
377: * defineClass0(loader, name, newbytes, off, newbytes.length, pd);
378: * }
379: * </pre>
380: */
381: private static class ProcessingVisitor extends
382: RemappingMethodVisitor {
383: public ProcessingVisitor(MethodVisitor cv, int access,
384: String desc) {
385: super (cv, access, desc);
386: }
387:
388: public void visitMethodInsn(int opcode, String owner,
389: String name, String desc) {
390: boolean insertPostCall = false;
391:
392: if ((DEFINECLASS0_METHOD_NAME.equals(name) || (DEFINECLASS1_METHOD_NAME
393: .equals(name)))
394: && CLASSLOADER_CLASS_NAME.equals(owner)) {
395: insertPostCall = true;
396: wrapCallToDefineClass01(opcode, owner, name, desc);
397: } else if (DEFINECLASS2_METHOD_NAME.equals(name)
398: && CLASSLOADER_CLASS_NAME.equals(owner)) {
399: insertPostCall = true;
400: wrapCallToDefineClass2(opcode, owner, name, desc);
401: } else {
402: super .visitMethodInsn(opcode, owner, name, desc);
403: }
404:
405: if (insertPostCall) {
406: super .visitInsn(Opcodes.DUP);
407: super .visitVarInsn(Opcodes.ALOAD, 0);
408: // The newly defined class object should be on the stack at this point
409: super
410: .visitMethodInsn(
411: Opcodes.INVOKESTATIC,
412: "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
413: "defineClass0Post",
414: "(Ljava/lang/Class;Ljava/lang/ClassLoader;)V");
415: }
416: }
417:
418: private void wrapCallToDefineClass01(int opcode, String owner,
419: String name, String desc) throws Error {
420: Type[] args = Type.getArgumentTypes(desc);
421: if (args.length < 5 || !desc.startsWith(DESC_PREFIX)) { //
422: throw new Error(
423: "non supported JDK, native call not supported: "
424: + desc);
425: }
426:
427: // store all args in local variables
428: int[] locals = new int[args.length];
429: for (int i = args.length - 1; i >= 0; i--) {
430: mv.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE),
431: locals[i] = nextLocal(args[i].getSize()));
432: }
433: for (int i = 0; i < 5; i++) {
434: mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD),
435: locals[i]);
436: }
437: super
438: .visitMethodInsn(
439: Opcodes.INVOKESTATIC,
440: "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
441: "defineClass0Pre", DESC_HELPER);
442: int returnLocalByteArray = nextLocal(args[1].getSize());
443: mv.visitVarInsn(Opcodes.ASTORE, returnLocalByteArray);
444: mv.visitVarInsn(Opcodes.ALOAD, locals[1]); // bytes
445: mv.visitVarInsn(Opcodes.ALOAD, returnLocalByteArray); // new bytes
446: Label l1 = new Label();
447: mv.visitJumpInsn(Opcodes.IF_ACMPEQ, l1);
448: /*
449: * If the return array is same as the input array, then there was no instrumentation done to the class. So
450: * maintain the offsets
451: */
452: mv.visitVarInsn(Opcodes.ALOAD, 0);
453: mv.visitVarInsn(Opcodes.ALOAD, locals[0]); // name
454: mv.visitVarInsn(Opcodes.ALOAD, returnLocalByteArray); // instrumented bytes
455: mv.visitInsn(Opcodes.ICONST_0); // offset
456: mv.visitVarInsn(Opcodes.ALOAD, returnLocalByteArray);
457: mv.visitInsn(Opcodes.ARRAYLENGTH); // length
458: mv.visitVarInsn(Opcodes.ALOAD, locals[4]); // protection domain
459: for (int i = 5; i < args.length; i++) {
460: mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD),
461: locals[i]);
462: }
463: super .visitMethodInsn(opcode, owner, name, desc);
464: Label l2 = new Label();
465: mv.visitJumpInsn(Opcodes.GOTO, l2);
466: mv.visitLabel(l1);
467: mv.visitVarInsn(Opcodes.ALOAD, 0);
468: for (int i = 0; i < args.length; i++) {
469: mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD),
470: locals[i]);
471: }
472: super .visitMethodInsn(opcode, owner, name, desc);
473: mv.visitLabel(l2);
474: }
475:
476: private void wrapCallToDefineClass2(int opcode, String owner,
477: String name, String desc) throws Error {
478: Type[] args = Type.getArgumentTypes(desc);
479: if (args.length < 5
480: || !desc.startsWith(DESC_BYTEBUFFER_PREFIX)) { //
481: throw new Error(
482: "non supported JDK, bytebuffer native call not supported: "
483: + desc);
484: }
485: // store all args in local variables
486: int[] locals = new int[args.length];
487: for (int i = args.length - 1; i >= 0; i--) {
488: mv.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE),
489: locals[i] = nextLocal(args[i].getSize()));
490: }
491: for (int i = 0; i < 5; i++) {
492: mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD),
493: locals[i]);
494: }
495: super
496: .visitMethodInsn(
497: Opcodes.INVOKESTATIC,
498: "com/tc/object/bytecode/hook/impl/ClassProcessorHelperJDK15",
499: "defineClass0Pre", DESC_BYTEBUFFER_HELPER);
500: mv.visitVarInsn(Opcodes.ASTORE, locals[1]);
501: mv.visitVarInsn(Opcodes.ALOAD, 0);
502: mv.visitVarInsn(Opcodes.ALOAD, locals[0]); // name
503: mv.visitVarInsn(Opcodes.ALOAD, locals[1]); // bytes
504: mv.visitInsn(Opcodes.ICONST_0); // offset
505: mv.visitVarInsn(Opcodes.ALOAD, locals[1]);
506: mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
507: "java/nio/ByteBuffer", "remaining", "()I");
508: mv.visitVarInsn(Opcodes.ALOAD, locals[4]); // protection domain
509: for (int i = 5; i < args.length; i++) {
510: mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD),
511: locals[i]);
512: }
513: super .visitMethodInsn(opcode, owner, name, desc);
514: }
515: }
516:
517: // TODO replace this with LocalVariableSorterAdapter
518: private static class RemappingMethodVisitor extends MethodAdapter {
519: private State state;
520: private IntRef check = new IntRef();
521:
522: public RemappingMethodVisitor(MethodVisitor v, int access,
523: String desc) {
524: super (v);
525: state = new State(access, Type.getArgumentTypes(desc));
526: }
527:
528: public RemappingMethodVisitor(RemappingMethodVisitor wrap) {
529: super (wrap.mv);
530: this .state = wrap.state;
531: }
532:
533: protected int nextLocal(int size) {
534: int var = state.nextLocal;
535: state.nextLocal += size;
536: return var;
537: }
538:
539: private int remap(int var, int size) {
540: if (var < state.firstLocal) {
541: return var;
542: }
543: check.key = (size == 2) ? ~var : var;
544: Integer value = (Integer) state.locals.get(check);
545: if (value == null) {
546: IntRef ref = new IntRef();
547: ref.key = check.key;
548: state.locals.put(ref, value = new Integer(
549: nextLocal(size)));
550: }
551: return value.intValue();
552: }
553:
554: public void visitIincInsn(int var, int increment) {
555: mv.visitIincInsn(remap(var, 1), increment);
556: }
557:
558: public void visitLocalVariable(String name, String desc,
559: String signature, Label start, Label end, int index) {
560: mv.visitLocalVariable(name, desc, signature, start, end,
561: remap(index, 0));
562: }
563:
564: public void visitVarInsn(int opcode, int var) {
565: int size;
566: switch (opcode) {
567: case Opcodes.LLOAD:
568: case Opcodes.LSTORE:
569: case Opcodes.DLOAD:
570: case Opcodes.DSTORE:
571: size = 2;
572: break;
573: default:
574: size = 1;
575: }
576: mv.visitVarInsn(opcode, remap(var, size));
577: }
578:
579: public void visitMaxs(int maxStack, int maxLocals) {
580: mv.visitMaxs(0, 0);
581: }
582: }
583:
584: private static class State {
585: Map locals = new HashMap();
586: int firstLocal;
587: int nextLocal;
588:
589: State(int access, Type[] args) {
590: nextLocal = ((Opcodes.ACC_STATIC & access) != 0) ? 0 : 1;
591: for (int i = 0; i < args.length; i++) {
592: nextLocal += args[i].getSize();
593: }
594: firstLocal = nextLocal;
595: }
596: }
597:
598: private static class IntRef {
599: int key;
600:
601: public boolean equals(Object o) {
602: return key == ((IntRef) o).key;
603: }
604:
605: public int hashCode() {
606: return key;
607: }
608: }
609:
610: }
|