用 BCEL 设计字节码(三)直接在方法的调用处添加方法
这个最接近之前提出的API转换问题
这个需要扫描源代码找到此类的方法调用处,然后在方法调用处的前后添加指令
即将代码转换成如下形式:
public class StringBuilder
{
public String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char)(i%26 + 'a');
}
return result;
}
public static void main(String[] argv) {
StringBuilder inst = new StringBuilder();
for (int i = 0; i < argv.length; i++) {
long start = System.currentTimeMillis();
String result = inst.buildString(Integer.parseInt(argv[i]));
System.out.println("Call to buildString$impl took " +
(System.currentTimeMillis()-start) + " ms.");
System.out.println("Constructed string of length " +
result.length());
}
}
}
转换代码如下:
先扫描这个类中的各个方法
我准备改变一下生成策略,不自己写指令了,这个自己写指令比较麻烦,但要加的代码比较多时比较容易出错,当所加的代码有if,跳转语句,自己根本就不会如何写添哪些指令,这种写类似汇编指令总是很麻烦的。
先要把加的代码自己在某个类中事先用方法写好,实例方法也好,静态方法也好,方法带有参数或者有返回值,
这些参数和返回值供调用方法中的局部变量来替换和使用。
再要添加代码的方法的前后调用这些已经写好的方法的字节码的指令序列生成InstructionList,然后再添加到使用这个方法的指令序列中
如下
public class ToolUtil {
public static long printStart() {
System.out.println("start start start start");
long start = System.currentTimeMillis();
return start;
}
public static void printEnd(String methodname,long start){
System.out.println("Call to "+methodname+" took " +
(System.currentTimeMillis()-start) + " ms.");
System.out.println("end end end end end end");
}
}
原始的方法
public
class
StringBuilder
{
/**//*
* 要在调用了此方法的方法的代码的前后添加代码
*/
public String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char) (i % 26 + 'a');
}
System.out.println(result);
return result;
}
/**//*
* 调用了buildString方法
*/
private String testInvokeMethod(){
String temp = null;
temp = buildString(10);
return temp;
}
/**//*
* 调用了buildString方法
*/
public static void main(String[] argv) {
StringBuilder inst = new StringBuilder();
for (int i = 0; i < argv.length; i++) {
String result = inst.buildString(Integer.parseInt(argv[i]));
System.out.println("Constructed string of length "
+ result.length());
}
}
}
/**//*
* 要在调用了此方法的方法的代码的前后添加代码
*/
public String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char) (i % 26 + 'a');
}
System.out.println(result);
return result;
}
/**//*
* 调用了buildString方法
*/
private String testInvokeMethod(){
String temp = null;
temp = buildString(10);
return temp;
}
/**//*
* 调用了buildString方法
*/
public static void main(String[] argv) {
StringBuilder inst = new StringBuilder();
for (int i = 0; i < argv.length; i++) {
String result = inst.buildString(Integer.parseInt(argv[i]));
System.out.println("Constructed string of length "
+ result.length());
}
}
}
生成的方法为
public
class
StringBuilder
{
/**//*
* 要在调用了此方法的方法的代码的前后添加代码
*/
public String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char) (i % 26 + 'a');
}
System.out.println(result);
return result;
}
/**//*
* 调用了buildString方法
*/
private String testInvokeMethod(){
String temp = null;
//调用事先写好的方法
long startTime = ToolUtil.printStart();
temp = buildString(10);
//调用事先写好的方法
ToolUtil.printEnd("buildString", startTime);
System.out.println("我是测试方法,我是测试方法,我是测试方法,我是测试方法");
return temp;
}
/**//*
* 调用了buildString方法
*/
public static void main(String[] argv) {
StringBuilder inst = new StringBuilder();
for (int i = 0; i < argv.length; i++) {
//调用事先写好的方法
long startTime = ToolUtil.printStart();
String result = inst.buildString(Integer.parseInt(argv[i]));
//调用事先写好的方法
ToolUtil.printEnd("buildString", startTime);
System.out.println("Constructed string of length "
+ result.length());
}
}
}
main这个调用的代码或者使用INVOKEXXXX指令实现,或者把这些调用方法的字节码指令序列加入到main的指令序列当中来实现,直接加的话可能自己事先写的方法的局部变量与main方法的局部变量会相同,而且涉及到所加的指令序列使用局部变量的问题,比如加的iload_0,istore_2就会引用现在方法的局部变量,但是现在的局部变量根本就不是这种指令所对应的类型/**//*
* 要在调用了此方法的方法的代码的前后添加代码
*/
public String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char) (i % 26 + 'a');
}
System.out.println(result);
return result;
}
/**//*
* 调用了buildString方法
*/
private String testInvokeMethod(){
String temp = null;
//调用事先写好的方法
long startTime = ToolUtil.printStart();
temp = buildString(10);
//调用事先写好的方法
ToolUtil.printEnd("buildString", startTime);
System.out.println("我是测试方法,我是测试方法,我是测试方法,我是测试方法");
return temp;
}
/**//*
* 调用了buildString方法
*/
public static void main(String[] argv) {
StringBuilder inst = new StringBuilder();
for (int i = 0; i < argv.length; i++) {
//调用事先写好的方法
long startTime = ToolUtil.printStart();
String result = inst.buildString(Integer.parseInt(argv[i]));
//调用事先写好的方法
ToolUtil.printEnd("buildString", startTime);
System.out.println("Constructed string of length "
+ result.length());
}
}
}
。所以还是使用方法调用比较方便
import
java.io.FileOutputStream;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
public class BCELTiming3 {
/**//*
* 扫描StringBuilder的各个方法的指令序列,在其中找Invokexxxx指令,
* 看此指令在常量池中引用的方法引用是否是要其前后加代码的方法
* 方法所在的类要一致,方法的名称要一致,方法的签名要一致
*
*/
/** *//**
*
* @param cgen 要被解析的类class文件
* @param classname 要被修改的方法所在的类名称,若有包名,则为java/lang/Object类似的
* @param methodname 要被修改的方法的名称
* @param methodSignature 要被修改的方法的签名
*/
private static void modifyWrapper(ClassGen cgen,String classname,String methodname,String methodSignature){
InstructionFactory ifact = new InstructionFactory(cgen);
ConstantPoolGen pgen = cgen.getConstantPool();
ConstantPool pool = pgen.getConstantPool();
String cname = cgen.getClassName();
Method[] methods = cgen.getMethods();
for(int i=0;i<methods.length;i++){
Method tempMethod = methods[i];
MethodGen tempMethodGen = new MethodGen(tempMethod,cname,pgen);
InstructionList tempList = tempMethodGen.getInstructionList();
tempList.iterator();
Instruction[] tempInstructions = tempList.getInstructions();
InstructionHandle[] tempInHandles = tempList.getInstructionHandles();
for(int j=0;j<tempInHandles.length;j++){
InstructionHandle ihandle = tempInHandles[j];
Instruction nowInstruction = ihandle.getInstruction();
if(nowInstruction.getOpcode()==Constants.INVOKEVIRTUAL){
INVOKEVIRTUAL invokeVirtual = (INVOKEVIRTUAL)nowInstruction;
ConstantMethodref cmr = (ConstantMethodref)pgen.getConstant(invokeVirtual.getIndex());
ConstantClass cc = (ConstantClass)pgen.getConstant(cmr.getClassIndex());
String nowClassName = cc.getBytes(pgen.getConstantPool());
ConstantNameAndType cnt = (ConstantNameAndType)pgen.getConstant(cmr.getNameAndTypeIndex());
String nowMethodName = cnt.getName(pgen.getConstantPool());
String nowMethodSignature = cnt.getSignature(pgen.getConstantPool());
//判断此方法的所属的类,方法的名称,方法的签名是否与所要加的一致(I)Ljava/lang/String;
if(nowClassName.equals(classname) && nowMethodName.equals(methodname) && nowMethodSignature.equals(methodSignature)){
cgen.removeMethod(tempMethodGen.getMethod());
//找到此方法在list的位置
InstructionList beforeList = new InstructionList();
//先加一个局部变量保存starttime的值
LocalVariableGen lvg = tempMethodGen.addLocalVariable("starttime", Type.LONG, null, null);
beforeList.append(ifact.createInvoke("ToolUtil", "printStart", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC));
beforeList.append(InstructionFactory.createStore(Type.LONG, lvg.getIndex()));
tempList.insert(ihandle.getPrev().getPrev(), beforeList);
//加方法后的代码
InstructionList afterList = new InstructionList();
afterList.append(ifact.createConstant(methodname));
afterList.append(ifact.createLoad(Type.LONG, lvg.getIndex()));
afterList.append(ifact.createInvoke("ToolUtil", "printEnd", Type.VOID, new Type[]{Type.STRING,Type.LONG}, Constants.INVOKESTATIC));
tempList.insert(ihandle.getNext().getNext(), afterList);
//finalize the construted method
tempMethodGen.stripAttributes(false);
tempMethodGen.setMaxStack();
tempMethodGen.setMaxLocals();
cgen.addMethod(tempMethodGen.getMethod());
System.out.println(tempMethodGen.getInstructionList());
System.out.println();
System.out.println();
}
}
}
// tempList.setPositions();
// tempList.findHandle(pos);
}
}
/** *//**
* @param args
*/
public static void main(String[] args) {
args[0]="D:\\java to eclipse\\javaeclipsestudy\\workspace\\BCELTest\\bin\\StringBuilder.class";
if(args.length==1 && args[0].endsWith(".class")){
try{
JavaClass jclas = new ClassParser(args[0]).parse();
ClassGen cgen = new ClassGen(jclas);
modifyWrapper(cgen,"StringBuilder","buildString","(I)Ljava/lang/String;");
cgen.getJavaClass().dump(args[0]);
}catch(Exception e){
e.printStackTrace();
}
}else{
System.out.println("usage: class-file");
}
}
}
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;
public class BCELTiming3 {
/**//*
* 扫描StringBuilder的各个方法的指令序列,在其中找Invokexxxx指令,
* 看此指令在常量池中引用的方法引用是否是要其前后加代码的方法
* 方法所在的类要一致,方法的名称要一致,方法的签名要一致
*
*/
/** *//**
*
* @param cgen 要被解析的类class文件
* @param classname 要被修改的方法所在的类名称,若有包名,则为java/lang/Object类似的
* @param methodname 要被修改的方法的名称
* @param methodSignature 要被修改的方法的签名
*/
private static void modifyWrapper(ClassGen cgen,String classname,String methodname,String methodSignature){
InstructionFactory ifact = new InstructionFactory(cgen);
ConstantPoolGen pgen = cgen.getConstantPool();
ConstantPool pool = pgen.getConstantPool();
String cname = cgen.getClassName();
Method[] methods = cgen.getMethods();
for(int i=0;i<methods.length;i++){
Method tempMethod = methods[i];
MethodGen tempMethodGen = new MethodGen(tempMethod,cname,pgen);
InstructionList tempList = tempMethodGen.getInstructionList();
tempList.iterator();
Instruction[] tempInstructions = tempList.getInstructions();
InstructionHandle[] tempInHandles = tempList.getInstructionHandles();
for(int j=0;j<tempInHandles.length;j++){
InstructionHandle ihandle = tempInHandles[j];
Instruction nowInstruction = ihandle.getInstruction();
if(nowInstruction.getOpcode()==Constants.INVOKEVIRTUAL){
INVOKEVIRTUAL invokeVirtual = (INVOKEVIRTUAL)nowInstruction;
ConstantMethodref cmr = (ConstantMethodref)pgen.getConstant(invokeVirtual.getIndex());
ConstantClass cc = (ConstantClass)pgen.getConstant(cmr.getClassIndex());
String nowClassName = cc.getBytes(pgen.getConstantPool());
ConstantNameAndType cnt = (ConstantNameAndType)pgen.getConstant(cmr.getNameAndTypeIndex());
String nowMethodName = cnt.getName(pgen.getConstantPool());
String nowMethodSignature = cnt.getSignature(pgen.getConstantPool());
//判断此方法的所属的类,方法的名称,方法的签名是否与所要加的一致(I)Ljava/lang/String;
if(nowClassName.equals(classname) && nowMethodName.equals(methodname) && nowMethodSignature.equals(methodSignature)){
cgen.removeMethod(tempMethodGen.getMethod());
//找到此方法在list的位置
InstructionList beforeList = new InstructionList();
//先加一个局部变量保存starttime的值
LocalVariableGen lvg = tempMethodGen.addLocalVariable("starttime", Type.LONG, null, null);
beforeList.append(ifact.createInvoke("ToolUtil", "printStart", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC));
beforeList.append(InstructionFactory.createStore(Type.LONG, lvg.getIndex()));
tempList.insert(ihandle.getPrev().getPrev(), beforeList);
//加方法后的代码
InstructionList afterList = new InstructionList();
afterList.append(ifact.createConstant(methodname));
afterList.append(ifact.createLoad(Type.LONG, lvg.getIndex()));
afterList.append(ifact.createInvoke("ToolUtil", "printEnd", Type.VOID, new Type[]{Type.STRING,Type.LONG}, Constants.INVOKESTATIC));
tempList.insert(ihandle.getNext().getNext(), afterList);
//finalize the construted method
tempMethodGen.stripAttributes(false);
tempMethodGen.setMaxStack();
tempMethodGen.setMaxLocals();
cgen.addMethod(tempMethodGen.getMethod());
System.out.println(tempMethodGen.getInstructionList());
System.out.println();
System.out.println();
}
}
}
// tempList.setPositions();
// tempList.findHandle(pos);
}
}
/** *//**
* @param args
*/
public static void main(String[] args) {
args[0]="D:\\java to eclipse\\javaeclipsestudy\\workspace\\BCELTest\\bin\\StringBuilder.class";
if(args.length==1 && args[0].endsWith(".class")){
try{
JavaClass jclas = new ClassParser(args[0]).parse();
ClassGen cgen = new ClassGen(jclas);
modifyWrapper(cgen,"StringBuilder","buildString","(I)Ljava/lang/String;");
cgen.getJavaClass().dump(args[0]);
}catch(Exception e){
e.printStackTrace();
}
}else{
System.out.println("usage: class-file");
}
}
}