Re: Instrumentation + BCEL | ASM
- From: Boris Gorjan <boris.gorjan@xxxxxxxxxxx>
- Date: Fri, 09 Jun 2006 12:44:52 +0200
Piotr Kobzda wrote:
Boris Gorjan wrote:Piotr Kobzda wrote:
[snip]
This is another problem
I have another one, too. Namely, using your code I can't transform methods
1. which don't have any arguments (verifier says
java.lang.VerifyError: (class: path/to/TimingTest, method: test
signature: ()V) Illegal local variable number) )
and
2. methods with $ in their names (like access$0) -> inner classes? (verifier says java.lang.VerifyError: (class: path/to/TimingTest,
method: access$0 signature: (Lpath/to/TimingTest;)V) Illegal local
variable number)
Could you look into that, please?
It seems to me you have incorrectly computed max locals value for your methods. Have you set computeMaxs option in your ClassWriter?
Erm... I did. Just now. ;-)
According to tests so far, it works fine. Thanks.
My code (with ASM 2.1) transforms perfect all kind of methods in my tests (synthetic, parameterized etc.).
There is thousand possible reasons of your code malfunction, but first of all ensure you are not fighting with some bugs of ASM beta 3, so if you haven't done it yet, switch to the stable ASM version.
Tried that, too. Tests up to now show no visible differences. I haven't compared bytecode, yet.
Another thing: my (first ;-) ) effort of detecting already modified mehods failed miserably. I added another visit method into MethodAdapter:
public void visitMethodInsn(
final int opcode,
final String owner,
final String aName,
final String aDesc
) { super.visitMethodInsn(opcode, owner, aName, desc);
if(
owner.equals("path/to/MyTiming")
&& (
aName.equals("start")
|| aName.equals("end")
)
) {
throw new RuntimeException(name + desc
+ " already contains timing calls. Aborting.");
}
}
Strangely enough, with this method added, I can only transform methods with exactly one argument of type String). For all others a transformed class fails verification.
Very strange... And I have no concept what's the reason of that, show a little more code.
My bad. As I renamed arguments of visitMethodInsn(...) I left "desc" of super.super.visitMethodInsn() unchanged. Should have been _aDesc_. Plus a couple of missing double-quotes which disappeared when I prepared the code for posting (I refactored quite a bit since the beginning and I try to keep some level of consistency for the sake of this conversation).
But now I have another problem. Throwing a RuntimeException effectively aborts transformation: Transformer catches the exception and returns unmodified bytes.
I'd like to be able to do this on a mehod level: skip the methods already containing timing calls (leaving them unmodified), and be able to transform others. I do something similar first thing in visitMethod(...) (see attached code) but, unlike detecting calls within the body of a method, I know which methods are registered in advance.
(I know I keep repeating myself, but...) Any suggestions?
Any suggestions?
After reading this and your previous post I think, that you don't have to care about existing start() and end() calls within your code.
Similar problem exist for example in a case of recursive calls, your start() method will be called many times before end(). And it should be YourTiming start() and end() methods implementation responsibility to treat such a scenarios well.
Currently it only detects them. There are some possibilities:
1. keep only the first call and (silently) ignore other consecutive ones
2. keep the last one and (silently) dispose of the ones before
3. throw an error
4. ?
You said before that there is some kind of stack-trace computed in your implementation, so having it in a calls to end() you can find out which start() this call is related to (simply ignoring all unrecognized end() calls).
That's what I'm doing. But I don't seem to know how to get hold of method signature, so there can be problems if consecutive threads call timed methods with the same name, but different signatures. I can't rely on line numbers, because:
1. MyTiming.start and MyTiming.end are called in different lines (so I can't pair them up)
2. There may even not be line number info if the bytecode is doctored that way (am I right?).
> This needs some kind of method calls stack reflected in your
implementation but will give your users a freedom of choices.
Moreover, you will no longer need end() taking a String parameter in this scenario, because you can assume that each end() call is always related to the last start() call (parameterized or not) performed in the same level of method calls stack.
Hope that's clear. :)
Well, not quite, but I think I'll keep those parametrized calls to be able to time blocks of code within the method. package path.to.instrumentation.asm;
import java.util.Map;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import path.to.MyTiming;
public class TimingTransformerAsmClassAdapter extends ClassAdapter {
private static final boolean DEBUG = false;
private static final boolean VERBOSE = true || DEBUG;
private Map methodNames;
public TimingTransformerAsmClassAdapter(
final ClassVisitor classVisitor,
final Map methodNames
) {
super(classVisitor);
this.methodNames = methodNames;
}
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions
) {
final String nameDesc = name + desc;
if(!methodNames.isEmpty()) {
if(
!methodNames.containsKey(name)
&& !methodNames.containsKey(nameDesc)
) {
verboseln("skipping unregistered method " + nameDesc);
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
//If ClassWriter in TimingTransformerAsm.transform(...) is not
//initialized properly (meaning: if it does not compute the maximum
//stack size and the maximum number of local variables automatically:
//-> new ClassWriter(ClassWriter.COMPUTE_MAXS); for 3.0 beta and
//-> new ClassWriter(true); for 2.2.1),
//the following block must be uncommented. Otherwise transfomed classes
//won't necessarily pass verification!
// if(desc.startsWith("()")) // Can't transform methods with no args!?!
// {
// verboseln("skipping special method (no args!?!) " + nameDesc);
// return cv.visitMethod(access, name, desc, signature, exceptions);
// }
//
// if(name.indexOf("$") >= 0) //Don't transform inner class methods!?!
// {
// verboseln("skipping special method (inner class?!?) " + nameDesc);
// return cv.visitMethod(access, name, desc, signature, exceptions);
// }
verboseln("transforming method " + nameDesc);
return
new MethodAdapter(
super.visitMethod(access, name, desc, signature, exceptions)
) {
final Label startLabel = new Label();
final Label finallyLabel = new Label();
int retOpcode = Opcodes.RETURN;
private void genStartCode() {
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
MyTiming.INTERNAL_CLASS_NAME,
MyTiming.MARK_START_METHOD_NAME,
"()V"
);
}
private void genEndCode() {
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
MyTiming.INTERNAL_CLASS_NAME,
MyTiming.MARK_STOP_METHOD_NAME,
"()V"
);
}
public void visitCode() {
super.visitCode();
super.visitLabel(startLabel);
genStartCode();
}
public void visitInsn(final int opcode) {
if(isRetInsn(opcode)) {
retOpcode = opcode;
super.visitJumpInsn(Opcodes.GOTO, finallyLabel);
}
else {
super.visitInsn(opcode);
}
}
public void visitMethodInsn(
final int opcode,
final String owner,
final String aName,
final String aDesc
) {
super.visitMethodInsn(opcode, owner, aName, aDesc);
if(
owner.equals("path/to/MyTiming)
&& (
aName.equals("start")
|| aName.equals("end")
)
) {
throw new RuntimeException(nameDesc
+ " already contains timing calls. Aborting.");
}
}
public void visitMaxs(final int maxStack, final int maxLocals) { // public void visitEnd() {
final Label endLabel = new Label();
super.visitLabel(endLabel);
super.visitVarInsn(Opcodes.ASTORE, 1);
genEndCode();
super.visitVarInsn(Opcodes.ALOAD, 1);
super.visitInsn(Opcodes.ATHROW);
super.visitLabel(finallyLabel);
genEndCode();
super.visitInsn(retOpcode);
super.visitTryCatchBlock(
startLabel,
endLabel,
endLabel,
null
);
super.visitMaxs(maxStack, maxLocals); //super.visitEnd();
}
};
}
private static boolean isRetInsn(final int opcode) {
switch(opcode) {
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.FRETURN:
case Opcodes.DRETURN:
case Opcodes.ARETURN:
case Opcodes.RETURN:
return true;
default:
return false;
}
}
private static final void out(String s) {
System.out.print(
TimingTransformerAsmClassAdapter.class.getSimpleName()
+ "> " + s
);
}
private static final void outln(String s) {
System.out.println(
TimingTransformerAsmClassAdapter.class.getSimpleName()
+ "> " + s
);
}
private static final void verbose(String s) {
if(VERBOSE)
out(s);
}
private static final void verboseln(String s) {
if(VERBOSE)
outln(s);
}
private static final void debug(String s) {
if(DEBUG)
out(s);
}
private static final void debugln(String s) {
if(DEBUG)
outln(s);
}
}
- Follow-Ups:
- Re: Instrumentation + BCEL | ASM
- From: Boris Gorjan
- Re: Instrumentation + BCEL | ASM
- References:
- Instrumentation + BCEL | ASM
- From: Boris Gorjan
- Re: Instrumentation + BCEL | ASM
- From: Piotr Kobzda
- Re: Instrumentation + BCEL | ASM
- From: Boris Gorjan
- Re: Instrumentation + BCEL | ASM
- From: Piotr Kobzda
- Re: Instrumentation + BCEL | ASM
- From: Boris Gorjan
- Re: Instrumentation + BCEL | ASM
- From: Piotr Kobzda
- Re: Instrumentation + BCEL | ASM
- From: Boris Gorjan
- Re: Instrumentation + BCEL | ASM
- From: Piotr Kobzda
- Instrumentation + BCEL | ASM
- Prev by Date: "abstract interface" in java
- Next by Date: Re: pipedstreams
- Previous by thread: Re: Instrumentation + BCEL | ASM
- Next by thread: Re: Instrumentation + BCEL | ASM
- Index(es):
Relevant Pages
|