有了前幾篇關(guān)于BCEL的使用,現(xiàn)在API轉(zhuǎn)換的問題其實(shí)很簡(jiǎn)單了
實(shí)際API轉(zhuǎn)換要做的比這個(gè)例子要復(fù)雜些,涉及到包名,類名,方法名稱等的變化。把常量池變化搞清楚就夠了
假如這是APIa中的一個(gè)類
實(shí)際只提供APIa的jar包,不提供源代碼
package one.api;


public class MyAPITest
{

public int add(String a,String b)
{
return new Integer(a).intValue()+new Integer(b).intValue();
}

}

如下是APIb的相對(duì)應(yīng)的類,注意兩個(gè)APIa提供的類層次結(jié)構(gòu),類的名稱已經(jīng)方法的名稱是一致的
package one.api;


public class MyAPITest
{

public int add(int a,int b)
{
return a+b;
}

}

現(xiàn)在有一個(gè)應(yīng)用程序使用的是APIa的類來寫的,并且只提供classes文件,
假如現(xiàn)在把此應(yīng)用程序放在另一臺(tái)機(jī)器上運(yùn)行,
但是此機(jī)器上只能提供APIb的jar包,要想此應(yīng)用程序能夠在此機(jī)器上運(yùn)行,則要修改應(yīng)用程序的classes字節(jié)碼。
應(yīng)用程序的代碼如下:
package client;

import one.api.MyAPITest;


public class ClientTest
{


/** *//**
* @param args
*/

public static void main(String[] args)
{
MyAPITest sb = new MyAPITest();
int result = sb.add("1", "2");
System.out.println(result);

}

public int mytest(String a,String b)
{
MyAPITest sb = new MyAPITest();
int result = sb.add(a, b);
return result;
}

}

則要調(diào)用sb.add(a, b);的地方將方法的參數(shù)改為整型的
本來的指令序列如下
ldc "1"
ldc "2"
invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I
則將字節(jié)碼的內(nèi)容加入
ldc "1"
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
ldc "2"
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
invokevirtual
one.api.MyAPITest.add (II)I
紅色的指令是將操作數(shù)棧的棧頂元素由字符串改變?yōu)檎?br />
并且還需要把調(diào)用的方法的新的方法簽名加入到常量池Constant_Pool中
1,這個(gè)在原來的指令序列中插入轉(zhuǎn)換指令需要插入的地方,但方法的參數(shù)比較明確時(shí)如add("1","2")或者add(s1,s2),s1,s2是局部變量,這個(gè)插入的地方比較好早。但是當(dāng)方法的參數(shù)是直接調(diào)用其他方法的而產(chǎn)生返回結(jié)果時(shí),還需要往指令前找其他方法的調(diào)用指令以及這個(gè)其他方法有幾個(gè)參數(shù),在這個(gè)其他方法調(diào)用后將這個(gè)其他方法的返回結(jié)果進(jìn)行整型轉(zhuǎn)換。
說的有的亂,假如main方法中為如下時(shí)

public static void main(String[] args)
{
MyAPITest sb = new MyAPITest();
int result = sb.add("1", "2");
System.out.println(result);
String temp = "999";
int resultone = sb.add(temp, "33");
System.out.println(resultone);
int resulttwo = sb.add(String.valueOf("1"), String.valueOf("2"));
System.out.println(resulttwo);

int resultthree = sb.add(StringUtil.createStringOne("111"),"333");
System.out.println(resultthree);
int resultfour = sb.add(StringUtil.createStringTwo("23", "34"),StringUtil.createStringThree("12", "23", "34"));
System.out.println(resultfour);
int resultfive = sb.add(StringUtil.createStringTwo(StringUtil.createStringOne("88"), "34"),StringUtil.createStringThree("12", "23", "34"));
System.out.println(resultfive);
}
上面的指令序列為如下:
完整的code.toString的信息
public static void main(String[] args)
Code(max_stack = 5, max_locals = 9, code_length = 153)
0: new <one.api.MyAPITest> (16)
3: dup
4: invokespecial one.api.MyAPITest.<init> ()V (18)
7: astore_1
8: aload_1
9: ldc "1" (19)
11: ldc "2" (21)
13: invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I (23)
16: istore_2
17: getstatic java.lang.System.out Ljava/io/PrintStream; (27)
20: iload_2
21: invokevirtual java.io.PrintStream.println (I)V (33)
24: ldc "999" (39)
26: astore_3
27: aload_1
28: aload_3
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
29: ldc "33" (41)
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
31: invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I (23)
34: istore %4
36: getstatic java.lang.System.out Ljava/io/PrintStream; (27)
39: iload %4
41: invokevirtual java.io.PrintStream.println (I)V (33)
44: aload_1
45: ldc "1" (19)
47: invokestatic java.lang.String.valueOf (Ljava/lang/Object;)Ljava/lang/String; (43)
50: ldc "2" (21)
52: invokestatic java.lang.String.valueOf (Ljava/lang/Object;)Ljava/lang/String; (43)
55: invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I (23)
58: istore %5
60: getstatic java.lang.System.out Ljava/io/PrintStream; (27)
63: iload %5
65: invokevirtual java.io.PrintStream.println (I)V (33)
68: aload_1
69: ldc "111" (49)
71: invokestatic client.StringUtil.createStringOne (Ljava/lang/String;)Ljava/lang/String; (51)
74: ldc "333" (57)
76: invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I (23)
79: istore %6
81: getstatic java.lang.System.out Ljava/io/PrintStream; (27)
84: iload %6
86: invokevirtual java.io.PrintStream.println (I)V (33)
89: aload_1
90: ldc "23" (59)
92: ldc "34" (61)
94: invokestatic client.StringUtil.createStringTwo (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (63)
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
97: ldc "12" (67)
99: ldc "23" (59)
101: ldc "34" (61)
103: invokestatic client.StringUtil.createStringThree (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (69)
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
106: invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I (23)
109: istore %7
111: getstatic java.lang.System.out Ljava/io/PrintStream; (27)
114: iload %7
116: invokevirtual java.io.PrintStream.println (I)V (33)
119: aload_1
120: ldc "88" (73)
122: invokestatic client.StringUtil.createStringOne (Ljava/lang/String;)Ljava/lang/String; (51)
125: ldc "34" (61)
127: invokestatic client.StringUtil.createStringTwo (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (63)
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
130: ldc "12" (67)
132: ldc "23" (59)
134: ldc "34" (61)
136: invokestatic client.StringUtil.createStringThree (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (69)
invokestatic java.lang.Integer.parseInt(Ljava/lang/String;)I
139: invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)I (23)
142: istore %8
144: getstatic java.lang.System.out Ljava/io/PrintStream; (27)
147: iload %8
149: invokevirtual java.io.PrintStream.println (I)V (33)
152: return

Attribute(s) =
LineNumber(0, 12), LineNumber(8, 14), LineNumber(17, 15), LineNumber(24, 17),
LineNumber(27, 18), LineNumber(36, 19), LineNumber(44, 21), LineNumber(60, 22),
LineNumber(68, 24), LineNumber(81, 25), LineNumber(89, 27), LineNumber(111, 28),
LineNumber(119, 30), LineNumber(144, 31), LineNumber(152, 32)
LocalVariable(start_pc = 0, length = 153, index = 0:String[] args)
LocalVariable(start_pc = 8, length = 145, index = 1:one.api.MyAPITest sb)
LocalVariable(start_pc = 17, length = 136, index = 2:int result)
LocalVariable(start_pc = 27, length = 126, index = 3:String temp)
LocalVariable(start_pc = 36, length = 117, index = 4:int resultone)
LocalVariable(start_pc = 60, length = 93, index = 5:int resulttwo)
LocalVariable(start_pc = 81, length = 72, index = 6:int resultthree)
LocalVariable(start_pc = 111, length = 42, index = 7:int resultfour)
LocalVariable(start_pc = 144, length = 9, index = 8:int resultfive)
看int resultone = sb.add(temp, "33");
要找對(duì)應(yīng)的這個(gè)插入地方,首先需要判斷第一個(gè)參數(shù)是局部變量使用的aload指令,第二個(gè)參數(shù)是直接LDC的
而對(duì)于
int resultfour = sb.add(StringUtil.createStringTwo("23", "34"),StringUtil.createStringThree("12", "23", "34"));
add的參數(shù)都是由方法調(diào)用的,需要知道在調(diào)用add的指令前有幾個(gè)invokeXXX指令,然后判斷這些指令有幾個(gè)參數(shù),然后選擇合適的地方來插入,總之要根據(jù)方法調(diào)用的參數(shù)個(gè)數(shù)往前找插入的地方,這個(gè)雖然可以實(shí)現(xiàn),但是實(shí)現(xiàn)起來比較麻煩,不是一個(gè)好的辦法。
2.我比較推崇的方法時(shí),但調(diào)用add時(shí),此時(shí)但卻操作棧的前幾個(gè)數(shù)肯定是add的參數(shù),只需要對(duì)這些數(shù)進(jìn)行轉(zhuǎn)換即可,但是要?jiǎng)?chuàng)建額外的局部變量來保存中間結(jié)果,當(dāng)參數(shù)都轉(zhuǎn)換完時(shí),在把這些自己創(chuàng)建的局部變量壓入操作棧中,這些操作都在add所對(duì)應(yīng)的invokevirtual指令前,當(dāng)調(diào)用invokevirtual時(shí),操作數(shù)棧的前幾個(gè)元素已經(jīng)是整型了
package transmit;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantFieldref;
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 ChangeUsingBcel
{


/**//*
* 掃描StringBuilder的各個(gè)方法的指令序列,在其中找Invokexxxx指令,
* 看此指令在常量池中引用的方法引用是否是要其前后加代碼的方法
* 方法所在的類要一致,方法的名稱要一致,方法的簽名要一致
*
*/

/** *//**
*
* @param cgen 要被解析的類class文件
* @param classname 要被修改的方法所在的類名稱,若有包名,則為java/lang/Object類似的one/api/MyAPITest
* @param methodname 要被修改的方法的名稱add
* @param methodSignature 要被修改的方法的簽名(Ljava/lang/String;Ljava/lang/String;)I
*
* Map.put(classname,list.add(map.put(
*/

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();
//留作它用
Map<String,List> map = new HashMap<String,List>();
List list = new ArrayList();
list.add((new HashMap()).put(methodname, methodSignature));
map.put(classname, list);

/**//*
* 先查查此類的常量池中是否有 MyAPITest的引用
*常量池的0號(hào)索引沒有使用
*/
Map classrefMap = new HashMap();
Map methodrefMap = new HashMap();
Map fieldrefMap = new HashMap();
System.out.println("pool.getLength() "+pool.getLength());//1024不是實(shí)際的條數(shù)
Constant[] constants = pool.getConstantPool();
System.out.println("constants.length "+constants.length);//1024

for(int cN=1;cN<constants.length;cN++)
{
Constant tempCon = pool.getConstant(cN);

if(tempCon!=null && tempCon.getTag()==Constants.CONSTANT_Class)
{
ConstantClass tempConClass = (ConstantClass)tempCon;
String classSignature = tempConClass.getBytes(pool);// one/api/MyAPITest

if(classSignature.equals("one/api/MyAPITest"))
{
//池中有此class的引用,然后判斷方法引用或域引用的class_index為此類的index
int class_index = cN;
classrefMap.put(class_index, classSignature);

/**//*
* 再次遍歷常量池找
*/

for(int cN2=1;cN2<constants.length;cN2++)
{
Constant temp = pool.getConstant(cN2);
System.out.println(temp);

if(temp!=null && temp.getTag()==Constants.CONSTANT_Methodref)
{
ConstantMethodref cmr = (ConstantMethodref)temp;

if(cmr.getClassIndex()==class_index)
{
ConstantNameAndType cnat = (ConstantNameAndType)pool.getConstant(cmr.getNameAndTypeIndex());
System.out.println("方法的名稱 "+cnat.getName(pool));
System.out.println("方法的簽名 "+cnat.getSignature(pool));
methodrefMap.put(cnat.getName(pool), cnat.getSignature(pool));
// pool.constantToString(index, tag)
// pool.constantToString(c)
}
}

if(temp!=null && temp.getTag()==Constants.CONSTANT_Fieldref)
{
ConstantFieldref cfr = (ConstantFieldref)temp;

if(cfr.getClassIndex()==class_index)
{
ConstantNameAndType cnat = (ConstantNameAndType)pool.getConstant(cfr.getNameAndTypeIndex());
System.out.println("引用的域的名稱 "+cnat.getName(pool));
System.out.println("引用的域的簽名 "+cnat.getSignature(pool));
fieldrefMap.put(cnat.getName(pool), cnat.getSignature(pool));
}
}
}
}
}
}

/**//*
* 分析類的各個(gè)方法,在各個(gè)方法中找出調(diào)用語句
*/
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();
System.out.println("tempList.getLength() "+tempList.getLength());;
//Instruction[] tempInstructions = tempList.getInstructions();
InstructionHandle[] tempInHandles = tempList.getInstructionHandles();
System.out.println("tempInHandles.length "+tempInHandles.length);

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;
//不加方法簽名的話,當(dāng)類中有重載方法時(shí)不好辦,加的話,if中當(dāng)遇到第一個(gè)時(shí)又把其給改掉了,后面的invokexxx的方法簽名是改后的了,因此這樣的簽名的方法也要加上指令

if(nowClassName.equals(classname) && nowMethodName.equals(methodname) && (nowMethodSignature.equals(methodSignature)||(nowMethodSignature.equals("(II)I"))))
{
cgen.removeMethod(tempMethodGen.getMethod());
InstructionList addList = new InstructionList();

addList.append(ifact.createInvoke("java.lang.Integer", "parseInt", Type.INT, new Type[]
{Type.STRING}, Constants.INVOKESTATIC));
//這個(gè)局部變量的作用范圍如何確定,還是簡(jiǎn)簡(jiǎn)單單的設(shè)置成null表示從開放開始到結(jié)束
LocalVariableGen lvg = tempMethodGen.addLocalVariable("paramTwoint", Type.INT, null, null);
//把當(dāng)前棧的整型結(jié)果保存到局部變量中
addList.append(ifact.createStore(Type.INT, lvg.getIndex()));
//再轉(zhuǎn)換第一個(gè)字符參數(shù)

addList.append(ifact.createInvoke("java.lang.Integer", "parseInt", Type.INT, new Type[]
{Type.STRING}, Constants.INVOKESTATIC));
// LocalVariableGen lvg2 = tempMethodGen.addLocalVariable("paramOneint", Type.INT, null, null);
// ifact.createStore(Type.INT, lvg2.getIndex());
//
// //再把兩個(gè)整型局部局部加到棧中
// ifact.createLoad(Type.INT, lvg2.getIndex());
addList.append(ifact.createLoad(Type.INT, lvg.getIndex()));
//將指令插入到invokeVirtual之前
tempList.insert(ihandle, addList);
//修改方法的簽名到(II)I
//可以保留原來的Methodref,使用addMethodref來添加新的methodref
//原來的methodref根本就沒有用,其實(shí)可以從常量池刪除調(diào)
// int newMethodIndex = pgen.addMethodref("one.api.MyAPITest", "add", "(II)I");
// invokeVirtual.setIndex(newMethodIndex);
//在if語句中改的話,后面的調(diào)用都不會(huì)被加代碼了
//或者自己加一個(gè)NameAndType這樣還避免沖突,然后把新的下標(biāo)賦給原來的方法引用
int new_name_and_type_index = pgen.addNameAndType("add", "(II)I");
cmr.setNameAndTypeIndex(new_name_and_type_index);
//假如方法的class_index也發(fā)生了變化,則也可以
// int new_class_index = pgen.addClass(str);
// cmr.setClassIndex(class_index);
//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();
}

if(nowClassName.equals(classname) && nowMethodName.equals("") && nowMethodSignature.equals(""))
{
//此類中的其他方法的改動(dòng)
//






}
}
//此類中的使用到classname類中的域

if(nowInstruction.getOpcode()==Constants.GETFIELD)
{
//



..
}
}
// tempList.setPositions();
// tempList.findHandle(pos);
}
}


/** *//**
* @param args
*/

public static void main(String[] args)
{
args[0]="D:\\java to eclipse\\javaeclipsestudy\\workspace\\BCELTest\\bin\\client\\ClientTest.class";

if(args.length==1 && args[0].endsWith(".class"))
{

try
{
JavaClass jclas = new ClassParser(args[0]).parse();
ClassGen cgen = new ClassGen(jclas);
modifyWrapper(cgen,"one/api/MyAPITest","add","(Ljava/lang/String;Ljava/lang/String;)I");
cgen.getJavaClass().dump(args[0]);

}catch(Exception e)
{
e.printStackTrace();
}

}else
{
System.out.println("usage: class-file");
}

}


}

改變字節(jié)碼的片段
129: aload_1
130: ldc "23" (59)
132: ldc "34" (61)
134: invokestatic client.StringUtil.createStringTwo (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (63)
137: ldc "12" (67)
139: ldc "23" (59)
141: ldc "34" (61)
143: invokestatic client.StringUtil.createStringThree (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; (69)
146: invokestatic java.lang.Integer.parseInt (Ljava/lang/String;)I (98)
149: istore %13
151: invokestatic java.lang.Integer.parseInt (Ljava/lang/String;)I (98)
154: iload %13
156: invokevirtual one.api.MyAPITest.add (II)I (23)
posted on 2009-08-16 19:30
Frank_Fang 閱讀(1819)
評(píng)論(2) 編輯 收藏 所屬分類:
bcel javassist