- PR -

クラスの中でバイトコードをダンプできますか

1
投稿者投稿内容
Jun
大ベテラン
会議室デビュー日: 2003/08/25
投稿数: 141
投稿日時: 2005-12-09 09:03
agentを使ってクラスのバイトコードを変換するときに
変換後のバイトコードをバイナリ値でなく見やすい形で
ダンプしたいのですがどのようにしたらよいのでしょうか
できればagentの中でなく元のクラスの方に組み込めるといいのですが
Anthyhime
ぬし
会議室デビュー日: 2002/09/10
投稿数: 437
投稿日時: 2005-12-09 11:22
Apache BCELなんかはどうでしょうか。
http://jakarta.apache.org/bcel
Jun
大ベテラン
会議室デビュー日: 2003/08/25
投稿数: 141
投稿日時: 2005-12-09 14:50
Anthyhimeさん,どうもありがとうございます.

今objectweb.asmを使っているのでできればそれと同系統のものと思われる
BCELは使いたくないのです.
ASMもまだ全然わかっていないのですが,取りあえずクラスの中のメソッドを
調べるところぐらいまではできるようになりました.

やりたいのはそういう枠組みの中でダンプをするのでなく,ソース内に簡単な一文
程度挿入することでそのクラスのロード後の(ロード前でなく)バイトコード
をニモニックのようなもの(javaのバイトコードについてもこのような呼び方を
するのでしょうか)を出力したいのです.

まだバイトコードについてもASMについてもわかっていないのでそのダンプを見ながら
解析したいのです.agentの中での16進ダンプはできるのだけど
ちょっとそれだとつらいので
シュン
ぬし
会議室デビュー日: 2004/01/06
投稿数: 328
お住まい・勤務地: 東京都
投稿日時: 2005-12-09 15:32
見やすい形式で出力するだけなら

javap -c

という方法がありますが…

バイトコードのパーサで公開されているものがあるのなら、
私もほしいですね…ASTをJavaオブジェクトによるCompsite
ツリーで返してくれたら最高なのですけれど。

[ メッセージ編集済み 編集者: シュン 編集日時 2005-12-09 15:41 ]
ちいにぃ
大ベテラン
会議室デビュー日: 2002/05/28
投稿数: 244
投稿日時: 2005-12-09 17:09
話の流れを切って申し訳ありませんが、後から話の流れを追うために、関連のリンクを貼らせてください。
Java Solution会議室: 再帰するインスタンスメソッドの自分の呼び出しについて(2005-12-08)
Java Solution会議室: javaのagentって具体的に何ですか(2005-12-05)

bytecode操作ライブラリ
ASM
BCEL
Javassist

ついでに、EclipseでByteCode関連の処理を行うプラグイン
Bytecode Outline plugin for Eclipse
Classfile Inspector (有料)
Bugdel
かつのり
ぬし
会議室デビュー日: 2004/03/18
投稿数: 2015
お住まい・勤務地: 札幌
投稿日時: 2005-12-09 18:31
asmはちょっとわからないですが、
Javassistなら動的に変更したクラスをクラスファイルにできました。
これをRuntime#execでJadを起動して逆コンパイルし、
出力ファイルと取り込むという手段もありかと思います。

クラスからバイト列を作れるなら
Javassistじゃなくても可能じゃないでしょうか。
Jun
大ベテラン
会議室デビュー日: 2003/08/25
投稿数: 141
投稿日時: 2005-12-12 09:32
みなさん,どうもありがとうございます

ちいにぃさんに関連づけていただきましたが,今回私の勉強のためにたてた目標は
以下のことです

アノテーション

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;1

@Target({ElementType.METHOD})
@interface Trace
{
public int level() default 0;
public String hierarchy() default ".";
}

を埋め込んだjavaソースがあるとして,それに対しトレースをlog4jを使って埋め込む
agentを作ります.このようにすればアノテーションを使ってトレースを外部から制御
できるし,またログの全く入っていないクラスに対してもトレースの制御ができると
考えたのです.

トレースを入れるのに,おそらくインラインでコードを埋め込むのが最も確実と
思ったのですが,バイトコードレベルの話は私はまだ全然わからないので,
トレースを入れる方法を簡単にできないかと考えました.
そこで対象メソッドの名前を変更して元のメソッドと同じ名前で,トレースを入れて
変更したメソッドを呼び出すものを追加する方法なら何とか出来そうだ
(そのようなクラスをjavapでダンプして結果を見れば大体わかりそうだ)
と考えたのです.(しかし,この方法では再帰したり,superを呼び出しているとき
うまくいくかどうか怪しい)

クラスファイルに対するダンプはjavapを使うのがベストと思いますが,
バイトコード返還後に対してこの方法は直接使えないように思ったので
(バイトコードをクラスファイルとして出力してから使うという方法もあると思うけど)
対象のテストソースに組み込むかagentに組み込めれば便利と思ったのです.

調べてみるとobject.asmにはTraceClassVisitorというのがあって,これを
使うとどうやらニモニックでダンプが出来そうなのですが,やり方がわからず
自分でやってみたところでは出来ませんでした.以下はちょっと長くなりますが
そのコードです.

import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import static org.apache.log4j.Level.*;
import static org.apache.log4j.Logger.getLogger;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.commons.EmptyVisitor;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.TraceClassVisitor;

class MethodTag
{ public int access;
public int level;
public String[] hierarchy;

public MethodTag(final int access,
final int level,
final String[] hierarchy)
{ this.access = access;
this.level = level;
this.hierarchy = hierarchy;
}
}

class MethodMap extends EmptyVisitor
{ private static final String _ANNOTATION = "Ldxf/Trace;";
private static final int _DEFAULT_LEVEL = 0;
private static final String[] _DEFAULT_HIERARCHY = new String[0];

private boolean _is_included;
private int _level;
private String[] _hierarchy;
private int _access;
private String _name;
private String _desc;
private Map<String, Map<String, MethodTag>> _map;
private int _limit_level;
private String[] _limit_hierarchy;

public MethodMap()
{ super();
_is_included = false;
_map = new HashMap<String, Map<String, MethodTag>>();
_level = _DEFAULT_LEVEL;
_hierarchy = _DEFAULT_HIERARCHY;
_access = 0;
_name = "";
_desc = "";
_limit_level = _DEFAULT_LEVEL;
_limit_hierarchy = _DEFAULT_HIERARCHY;
}

private void finishMethod()
{ if (!_is_included) return;
if (!_map.containsKey(_name))
_map.put(_name, new HashMap<String, MethodTag>());
_map.get(_name).put(_desc, new MethodTag(_access, _level, _hierarchy));
_is_included = false;
_level = _DEFAULT_LEVEL;
_hierarchy = _DEFAULT_HIERARCHY;
}

public AnnotationVisitor visitAnnotation(final String name,
final boolean visible)
{ if (name.equals(_ANNOTATION)) _is_included = true;
return super.visitAnnotation(name, visible);
}

public void visit(final String name, final Object value)
{ if (_is_included)
if (name.equals("level"))
_is_included = (_level = (Integer)value) >= _limit_level;
else if (name.equals("hierarchy"))
{ _hierarchy = ((String)value).split(".");
for(int i = 0; i < _limit_hierarchy.length; i++)
if (!_hierarchy[i].equals(_limit_hierarchy[i]))
{ _is_included = false;
break;
}
}
super.visit(name, value);
}

public MethodVisitor visitMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions)
{ finishMethod();
_access = access;
_name = name;
_desc = desc;
return super.visitMethod(access, name, desc, signature, exceptions);
}

public Map<String, Map<String, MethodTag>> getMap()
{ finishMethod();
return _map;
}
}

abstract class HexDump{省略}
class TraceInserter extends ClassAdapter
{ private Map<String, Map<String, MethodTag>> _map;

private TraceInserter(final ClassWriter writer,
final Map<String, Map<String, MethodTag>> map)
{ super(writer);
_map = map;
}

public MethodVisitor visitMethod(final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions)
{ if (!_map.containsKey(name) || !_map.get(name).containsKey(desc))
return super.visitMethod(access, name, desc, signature, exceptions);
final String dummy = "$" + name;
final MethodVisitor visitor =
super.visitMethod(access, name, desc, signature, exceptions);
visitor.visitCode();
// この辺になんかいれるとうまくいく(といいのだが,よくわからん)
visitor.visitEnd();
return super.visitMethod(access, dummy, desc, signature, exceptions);
}

public static byte[] toByteArray
(final ClassReader reader,
final Map<String, Map<String, MethodTag>> map)
{ final ClassWriter writer = new ClassWriter(false);
final TraceInserter instance = new TraceInserter(writer, map);
reader.accept(instance, false);
return writer.toByteArray();
}
}

public class TraceAgent implements ClassFileTransformer
{ public byte[] transform(final ClassLoader loader,
final String class_name,
final Class<?> class_being_redefined,
final ProtectionDomain protection_domain,
final byte[] classfile_buffer)
throws IllegalClassFormatException
{ try
{ final ClassReader reader = new ClassReader(classfile_buffer);
// 取りあえず変更前のダンプをいれてみたのだがうまくいかない
final TraceClassVisitor tracer =
new TraceClassVisitor(null, new PrintWriter(System.out, true));
reader.accept(tracer, false);
final MethodMap visitor = new MethodMap();
reader.accept(visitor, true);
final Map<String, Map<String, MethodTag>> map = visitor.getMap();
final byte[] new_class =
map.size() == 0 ? null: TraceInserter.toByteArray(reader, map);
if (map.size() == 0) return null;
System.out.println("old class");
HexDump.print(classfile_buffer);
System.out.println("new class");
HexDump.print(new_class);

return new_class;
}
catch (IllegalStateException e)
{ throw new IllegalClassFormatException
("Error: " + e.getMessage() + " on class '" + class_name + "'.");
}
}
public static void premain(final String options,
final Instrumentation inst)
{ DOMConfigurator.configure(options);
inst.addTransformer(new TraceAgent());
}
}
Jun
大ベテラン
会議室デビュー日: 2003/08/25
投稿数: 141
投稿日時: 2005-12-12 09:38
バイトコード返還->バイトコード変換

の誤りでした.
1

スキルアップ/キャリアアップ(JOB@IT)