Instrumentation + BCEL | ASM

B

Boris Gorjan

I'm trying to utilize instrumentation to modify bytecode of the loaded classes.
Having added MyTransformer to Instrumentation in MyAgent's premain(String,
Instrumentation) and using VM comand line argument -javaagent:myjar.jar=myargs I
can intercept the bytecode before it is loaded, through the

public byte[] transform(
ClassLoader loader,
String className,
Class redefiningClass,
ProtectionDomain domain,
byte[] bytes
)
throws IllegalClassFormatException
{
System.out.println("Transforming: " + className);
return bytes;
}

OK so far.

Now, MyTransformer is doing nothing at the moment, except returning unmodified
bytecode. What I'd like to do is this: for a specified method I'd like to wrap
the whole body of the method in a try - finally, like this:

try {
MyTiming.start();
/*
Old method body / code.
*/
}
finally {
MyTiming.end();
}

What's the easyest way to do this?

I looked into BCEL; using DecendingVisitor (and MyVisitor which implements
Visitor: all the visit*() methods) I can go through the bytecode in a similar
way as using a SAX parser for XML. But, I don't know how to modify the code in
an above mentioned way letting most of it through unmodified.

Can anyone give me some ideas / guidance, please?

How about using ASM? I'd also be happy using that instead of BCEL. I read it's
somewhat faster.

Thanks.
 
M

Marcin Wielgus

bcel can do it. Dont exacly remember how try - catch works in bytecode,
but i remember api is quite simple. if u'r capable of obtaining method
object, u should be able to get instruction list, and put some
instructions at the beginning, and at the end (look for them in the api
docs), but u have to remember to go through all instructions in that
method, and correct all jumps (if, catch, while and so on). If u want some
sample code i can send it to u.
 
B

Boris Gorjan

Marcin said:
bcel can do it. Dont exacly remember how try - catch works in bytecode,
but i remember api is quite simple. if u'r capable of obtaining method
object,

As things stand now I can obtain everything in org.apache.bcel.classfile.Visitor
(now I see that there's also an org.apache.bcel.generic.Visitor).

What I don't know how to do (yet ;-) ) is how to pack all the data I obtain
through that Visitor, into a new bytecode (byte[]). A good exercise would be to
pack it unmodified, I know, but that'd take me a long time.

Intuitively I'd go about it this way: make a new JavaClass(), use its set*()
methods to fill it with data obtained through a Visitor (an implementation of
the classfile one) and finally use some dump(*) or getBytes() method to produce
the bytecode. Am I on the right track?
u should be able to get instruction list,

I can get org.apache.bcel.classfile.Method and org.apache.bcel.classfile.Code
for that method, but neither of them has a "handle" on InstructionList. I don't
see it, to be precise.

I can't seem to "connect" a Visitor, that is instances of classes its visit*()
methods take as arguments, and those with their respective InstructionLists.
and put some
instructions at the beginning, and at the end (look for them in the api
docs), but u have to remember to go through all instructions in that
method, and correct all jumps (if, catch, while and so on). If u want
some sample code i can send it to u.

I'd be most grateful. Thanks. Use the boris.gorjan address, please.
 
B

Boris Gorjan

Boris said:
I can get org.apache.bcel.classfile.Method and
org.apache.bcel.classfile.Code for that method, but neither of them has
a "handle" on InstructionList. I don't see it, to be precise.

I can't seem to "connect" a Visitor, that is instances of classes its
visit*() methods take as arguments, and those with their respective
InstructionLists.

Found it! In Visitors visit*() methods, just use .getCode() from an argument
object and create a new InstructionList. Like this:

public void visitCode(Code code) {
InstructionList instructionList = new InstructionList(code.getCode());
// ...
}
 
B

Boris Gorjan

Boris Gorjan wrote:

Nailed it! With a little help from my firends. ;-)
[imagine a lengthy oscars-style-thank-you litany here]

Just one more thing. As said in my initial post, MyTransformer (implementing
ClassFileTransformer) implements a method

public byte[] transform(ClassLoader classLoader, String className, Class
redefiningClass, ProtectionDomain domain, byte[] bytes),

where byte[] bytes are supposed to be "the input byte buffer in class file
format - must not be modified" (quoting J2SE 1.5 API docs).

I only know how to obtain an instance of org.apache.bcel.classfile.JavaClass
(which I later transform) from org.apache.bcel.Repository like this:

JavaClass clazz = Repository.lookupClass(className.replace('/', '.'));

Is there a (simple) way to obtain that instance using byte[] bytes directly?
 
P

Piotr Kobzda

Boris said:
How about using ASM? I'd also be happy using that instead of BCEL. I
read it's somewhat faster.

That is quite easy task for ASM, if you know what is exactly the
bytecode you want to achieve.

See code provided below for one of the possible solutions.
It could be interesting to see, how your already achieved BCEL solution
looks like (I have no experience with BCEL), so if it's not a problem
for you, please send it here.


Also consider using some APO framework, for example AspectJ, which
allows doing similar things (and much more) easier.


Regards,
piotr

--

import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;

class AddTimingCallsClassAdapter extends ClassAdapter {
AddTimingCallsClassAdapter(ClassVisitor cv) {
super(cv);
}

@Override
public MethodVisitor visitMethod(int access, String name, String
desc, String signature, String[] exceptions) {

return new MethodAdapter(super.visitMethod(access, name, desc,
signature, exceptions)) {

Label startLabel = new Label();
Label finallyLabel = new Label();
int retOpcode = RETURN;


private void genStartCode() {
super.visitMethodInsn(INVOKESTATIC, "MyTiming", "start", "()V");
}

private void genEndCode() {
super.visitMethodInsn(INVOKESTATIC, "MyTiming", "end", "()V");
}

@Override
public void visitCode() {
super.visitCode();

super.visitLabel(startLabel);

genStartCode();

// ...
}

@Override
public void visitInsn(int opcode) {
if (isRetInsn(opcode)) {
retOpcode = opcode;
super.visitJumpInsn(GOTO, finallyLabel);
} else {
super.visitInsn(opcode);
}
}

@Override
public void visitEnd() {
// ...

Label endLabel = new Label();
super.visitLabel(endLabel);
super.visitVarInsn(ASTORE, 1);

genEndCode();

super.visitVarInsn(ALOAD, 1);
super.visitInsn(ATHROW);

super.visitLabel(finallyLabel);

genEndCode();

super.visitInsn(retOpcode);

super.visitTryCatchBlock(startLabel, endLabel, endLabel, null);

super.visitEnd();
}

};
}

static boolean isRetInsn(int opcode) {
switch(opcode) {
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case ARETURN:
case RETURN:
return true;
default:
return false;
}
}

}
 
P

Piotr Kobzda

Boris Gorjan napisał(a):
Is there a (simple) way to obtain that instance using byte[] bytes
directly?

Based on BCEL Manual and Javadoc, it should work this way:

JavaClass clazz = new ClassParser(new ByteArrayInputStream(bytes),
className).parse();

(I've never tried it out)


Regards,
piotr
 
B

Boris Gorjan

Piotr said:
Boris Gorjan napisał(a):
Is there a (simple) way to obtain that instance using byte[] bytes
directly?

Based on BCEL Manual and Javadoc, it should work this way:

JavaClass clazz = new ClassParser(new ByteArrayInputStream(bytes),
className).parse();

(I've never tried it out)

It works. Thanks.
 
P

Piotr Kobzda

Boris said:
Thanks for your code. Mine is also attached.

Thanks for yours. Comparing it with my ASM code I start to see more
differences between this two bytecode manipulation frameworks. Now I
think these differences, in general, are like a differences between DOM
and SAX, both gives us possibilities for processing bytecode/XML, but
each in a different way.
As the result ASM is still my favorite Java bytecode processing
framework. :)

Just one comment on your code. I feel a bytecode generated using your
code is not exactly what you'd like to achieve.

If I understand your code well, your transformation results are similar
to the following sample code:

void someMethod() {
| try {
| MyTiming.start();

....

RuntimeException e ...
| MyTiming.end();
throw e;

....

| MyTiming.end();
return;

| } catch(Throwable t) {
| MyTiming.end();
| throw t;
| }
}

(where | indicates the method changes performed by transformation)

First problem with that is in a places where the original method's code
throws an exception. In such a cases your end() method is called twice
(first time from call added into try clause body, second time from a
catch body).

Second similar problem is with end() calls added into a try body, an
exception thrown from your end() method will cause another call of end()
from a catch body.


To achieve the result mentioned in your first post, you should generate
(which my ASM code strive to do) a bytecode similar to the following
sketch code:

void someMethod() {
| try {
| MyTiming.start();

....

RuntimeException e ...
throw e;

....

| goto end;
| //return; <- replaced with goto

| } catch(Throwable t) {
| MyTiming.end();
| throw t;
| }
| end:
| MyTiming.end();
}


If you'd like try my ASM code to see differences, simply add the
following code to your transform method:

ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(true);
ClassVisitor cv = new AddTimingCallsClassAdapter(cw);
cr.accept(cv, false);
return cw.toByteArray();


Regards,
piotr
 
P

Piotr Kobzda

Boris said:
public class TimingTransformerAsm implements ClassFileTransformer { [...]
//dump(bytes);
try {
ClassWriter classWriter = new ClassWriter(0);

I do not recognize this constructor of ClassWriter, which version of ASM
are you using?

public class AddTimingCallsClassAdapter extends ClassAdapter {

private ClassVisitor parent;

You don't need that field. ClassAdapter has a protected instance
variable called 'cv' for you.
private void genStartCode() {
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
MyTiming.class.getName(),

Here is the problem. You are using Java language class name, not the JVM
expected one. You can use ASM helper Type class to get that name, this way:

Type.getType(MyTiming.class).getClassName()
MyTiming.class.getName(),

The same here.
public void visitEnd() {

This is another problem (my bug, just discovered by me). visitEnd() will
be called for abstract and native methods too.
To fix that replace with:

public void visitMaxs(int maxStack, int maxLocals) {
super.visitEnd();

and this, with:

super.visitMaxs(maxStack, maxLocals);

After transformation:

// access flags 1
public example1(Ljava/lang/String;)V
TRYCATCHBLOCK L0 L1 L1 null
L0
INVOKESTATIC path.MyTiming.start ()V
^
should be '/' after correction.


Regards,
piotr
 
B

Boris Gorjan

Piotr said:
Boris said:
public class TimingTransformerAsm implements ClassFileTransformer { [...]
//dump(bytes);
try {
ClassWriter classWriter = new ClassWriter(0);

I do not recognize this constructor of ClassWriter, which version of ASM
are you using?

I downloaded 3.0 beta 2. Perhaps I should play it safe and stay with 2.2.1. If
3.0 isn't released before I release my code, I'll have to go with 2.2.1 anyway.

The API docs for 3.0 beta 2 say:

---------------------

ClassWriter

public ClassWriter(int flags)

Constructs a new ClassWriter object.

Parameters:
flags - option flags that can be used to modify the default behavior of
this class. See COMPUTE_MAXS, COMPUTE_FRAMES.

---

COMPUTE_MAXS

public static final int COMPUTE_MAXS

Flag to automatically compute the maximum stack size and the maximum number
of local variables of methods. If this flag is set, then the arguments of the
visitMaxs method of the MethodVisitor returned by the visitMethod method will be
ignored, and computed automatically from the signature and the bytecode of each
method.

---

COMPUTE_FRAMES

public static final int COMPUTE_FRAMES

Flag to automatically compute the stack map frames of methods from scratch.
If this flag is set, then the calls to the MethodVisitor.visitFrame(int, int,
java.lang.Object[], int, java.lang.Object[]) method are ignored, and the stack
map frames are recomputed from the methods bytecode. The arguments of the
visitMaxs method are also ignored and recomputed from the bytecode. In other
words, computeFrames implies computeMaxs.

---------------------

I thought I'd use 0, hoping for some reasonable default behaviour. Should I use
something else?
You don't need that field. ClassAdapter has a protected instance
variable called 'cv' for you.
OK.


Here is the problem. You are using Java language class name, not the JVM
expected one. You can use ASM helper Type class to get that name, this way:

Type.getType(MyTiming.class).getClassName()

Tried this. Surprisingly, to no avail. But then I used
MyTiming.class.getName().replace('.', '/') and it worked like a charm. Thanks a
million.
The same here.


This is another problem (my bug, just discovered by me). visitEnd() will
be called for abstract and native methods too.
To fix that replace with:

public void visitMaxs(int maxStack, int maxLocals) {


and this, with:

super.visitMaxs(maxStack, maxLocals);

Which reminds me...

Not only should abstract and native methods stay unmodified, but, to be on the
safe side, already modified methods (or the ones with hardcoded calls of
MyTiming.start() and MyTiming.end()) should not be modified again (starting the
timer twice in a row could even be regarded as an error). I'll have to include
that in my code. If I run into trouble, and I most probably will, I'll have to
scream for help again. So, stay tuned or I'll be
back^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H, please. ;-)
^
should be '/' after correction.


Regards,
piotr

Again, thanks a lot.
 
P

Piotr Kobzda

Boris said:
I downloaded 3.0 beta 2. Perhaps I should play it safe and stay with
2.2.1. If 3.0 isn't released before I release my code, I'll have to go
with 2.2.1 anyway.

OK, it explains my confusion, I'm still using ASM 2.1.

I thought I'd use 0, hoping for some reasonable default behaviour.
Should I use something else?

As I've shown in my code usage example, I'm using COMPUTE_MAXS
equivalent (true for computeMaxs parameter of ClassWriter's
constructor). This is needed because generated additional bytecode may
require an increasing of minimum local variables stack size (of course
you can compute it in visitMaxs, if you wish). COMPUTE_FRAMES option is
new for me, and i think you can freely skip it in your case.
Tried this. Surprisingly, to no avail. But then I used
MyTiming.class.getName().replace('.', '/') and it worked like a charm.

Heh, I'm to hurry... I thought about Type.getInternalName(), which in
fact, gives the same results as your workaround.

Which reminds me...

Not only should abstract and native methods stay unmodified, but, to be
on the safe side, already modified methods (or the ones with hardcoded
calls of MyTiming.start() and MyTiming.end()) should not be modified
again (starting the timer twice in a row could even be regarded as an
error). I'll have to include that in my code. If I run into trouble, and
I most probably will, I'll have to scream for help again. So, stay tuned
or I'll be back^H^H^H^H^H^H^H^H^H^H^H^H^H^H^H, please. ;-)

OK. :)

But I think you can simply skip this problem, simply don't do use your
start() and end() methods directly. :)
If you are really care about it, make these methods private and change
(transform) to public at runtime.


Regards,
piotr
 
B

Boris Gorjan

Piotr said:
OK. :)

But I think you can simply skip this problem, simply don't do use your
start() and end() methods directly. :)

Ideally, yes. However, MyTiming has an additional functionality not covered by
automatical transformation (yet :) ) : it can time an arbitrary block of code.
Provided one uses unique ids (unique within a method), one can call:

void myMethod() {

try {
MyTiming.start("myid");
//do something here
}
finally {
MyTiming.end("myid");
}

//do something here

try {
MyTiming.start("anotherid");
//do something here
}
finally {
MyTiming.end("anotherid");
}

}

As I don't know how to identify particular blocks to do this through
instrumentation / transformation, I thought I'd leave this functionality for
"manual" use.

By "identify" I mean:

1. how to specify those blocks in an init (properties) file used to initialize
the adapter and

2. how to transform those blocks from within the adapter.

That complicates things a bit. Adapter and MyTiming have to be compatible
(compatibly intialized, to be exact): adapter has to transform only those
methods that MyTiming will later take into account. And those are generally
quite few because MyTiming has to store quite a bit of data: at least a stack
trace equivalent (+ blockid, if there is one) and a list of durations for every
timed block / method.
If you are really care about it, make these methods private and change
(transform) to public at runtime.

A sneaky idea. :) A good one, though. But easyer said than done. Perhaps someday.
 
B

Boris Gorjan

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?

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.

Any suggestions?

[snip]
 
P

Piotr Kobzda

Boris said:
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?

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.

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.

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.

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). 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. :)


Regards,
piotr
 
B

Boris Gorjan

Piotr said:
Boris said:
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.
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?
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);
}
}
 
B

Boris Gorjan

Boris Gorjan wrote:

[snip]

If during transformation, instead of

public static void MyTiming.start() and
public static void MyTiming.end()

I wanted to incorporate

public static void MyTiming.start(String) and
public static void MyTiming.end(String),

where String would be a signature of the method being modified (argument desc in
this case), what do I have to do?

In my BCEL code (see BCEL code in my post from June 6thin this thread) I'd

1. replace "V()" with "(Ljava/lang/String;)V" in both calls of
constantPoolGen.addMethodref(...),

2. PUSH a "signature" onto InstructionList before calling INVOKESTATIC. Like this:
InstructionList additional = new InstructionList();
additional.append(new PUSH(constantPoolGen, method.getSignature()));
additional.append(new INVOKESTATIC(startRef));

(same for endRef)

3. increase i by 1


OK, I know that (in TimingTransformerAsmClassAdapter below) I have to replace
the signature "()V" with "(Ljava/lang/String;)V" in private void genStartCode()
and private void genEndCode(). After that, I... don't know. Any hints?
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);
}
}
 
B

Boris Gorjan

Boris Gorjan wrote:

[snip]
OK, I know that (in TimingTransformerAsmClassAdapter below) I have to
replace the signature "()V" with "(Ljava/lang/String;)V" in private void
genStartCode() and private void genEndCode(). After that, I... don't
know.

Now I know: insert super.visitLdcInsn(desc); into genStartCode() and genEndCode();

[snip]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,989
Messages
2,570,207
Members
46,782
Latest member
ThomasGex

Latest Threads

Top