随笔-204  评论-149  文章-0  trackbacks-0

有了前几篇关于BCEL的使用,现在API转换的问题其实很简单了
实际API转换要做的比这个例子要复杂些,涉及到包名,类名,方法名称等的变化。把常量池变化搞清楚就够了

假如这是APIa中的一个类
实际只提供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的相对应的类,注意两个APIa提供的类层次结构,类的名称已经方法的名称是一致的

package one.api;

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


}


现在有一个应用程序使用的是APIa的类来写的,并且只提供classes文件,
假如现在把此应用程序放在另一台机器上运行,但是此机器上只能提供APIb的jar包,要想此应用程序能够在此机器上运行,则要修改应用程序的classes字节码。
应用程序的代码如下:
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;
    }


}


则要调用sb.add(a, b);的地方将方法的参数改为整型的
本来的指令序列如下
 ldc  "1" 
 ldc  "2" 
 invokevirtual one.api.MyAPITest.add (Ljava/lang/String;Ljava/lang/String;)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
红色的指令是将操作数栈的栈顶元素由字符串改变为整型
并且还需要把调用的方法的新的方法签名加入到常量池Constant_Pool中
1,这个在原来的指令序列中插入转换指令需要插入的地方,但方法的参数比较明确时如add("1","2")或者add(s1,s2),s1,s2是局部变量,这个插入的地方比较好早。但是当方法的参数是直接调用其他方法的而产生返回结果时,还需要往指令前找其他方法的调用指令以及这个其他方法有几个参数,在这个其他方法调用后将这个其他方法的返回结果进行整型转换。
说的有的乱,假如main方法中为如下时
    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(
012), LineNumber(814), LineNumber(1715), LineNumber(2417), 
LineNumber(
2718), LineNumber(3619), LineNumber(4421), LineNumber(6022), 
LineNumber(
6824), LineNumber(8125), LineNumber(8927), LineNumber(11128), 
LineNumber(
11930), LineNumber(14431), LineNumber(15232)
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");
要找对应的这个插入地方,首先需要判断第一个参数是局部变量使用的aload指令,第二个参数是直接LDC的
而对于
int resultfour = sb.add(StringUtil.createStringTwo("23", "34"),StringUtil.createStringThree("12", "23", "34"));
add的参数都是由方法调用的,需要知道在调用add的指令前有几个invokeXXX指令,然后判断这些指令有几个参数,然后选择合适的地方来插入,总之要根据方法调用的参数个数往前找插入的地方,这个虽然可以实现,但是实现起来比较麻烦,不是一个好的办法。
2.我比较推崇的方法时,但调用add时,此时但却操作栈的前几个数肯定是add的参数,只需要对这些数进行转换即可,但是要创建额外的局部变量来保存中间结果,当参数都转换完时,在把这些自己创建的局部变量压入操作栈中,这些操作都在add所对应的invokevirtual指令前,当调用invokevirtual时,操作数栈的前几个元素已经是整型了
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的各个方法的指令序列,在其中找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号索引没有使用
         
*/

        Map classrefMap 
= new HashMap();
        Map methodrefMap 
= new HashMap();
        Map fieldrefMap 
= new HashMap();
        
        
        
        System.out.println(
"pool.getLength()   "+pool.getLength());//1024不是实际的条数
        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));
                            }

                        }

                    }

                    
                }

            }

        }

        
        
        
/*
         * 分析类的各个方法,在各个方法中找出调用语句
         
*/

        
        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;
                    
//不加方法签名的话,当类中有重载方法时不好办,加的话,if中当遇到第一个时又把其给改掉了,后面的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));
                        
//这个局部变量的作用范围如何确定,还是简简单单的设置成null表示从开放开始到结束
                        LocalVariableGen lvg = tempMethodGen.addLocalVariable("paramTwoint", Type.INT, nullnull);
                        
//把当前栈的整型结果保存到局部变量中
                        addList.append(ifact.createStore(Type.INT, lvg.getIndex()));
                        
                        
//再转换第一个字符参数
                        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());
//                        
//                        //再把两个整型局部局部加到栈中
//                        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根本就没有用,其实可以从常量池删除调
//                        int newMethodIndex = pgen.addMethodref("one.api.MyAPITest", "add", "(II)I");
//                        invokeVirtual.setIndex(newMethodIndex);
                        
                        
                        
//在if语句中改的话,后面的调用都不会被加代码了
                        
//或者自己加一个NameAndType这样还避免冲突,然后把新的下标赋给原来的方法引用
                        int new_name_and_type_index = pgen.addNameAndType("add""(II)I");
                        cmr.setNameAndTypeIndex(new_name_and_type_index);
                        
//假如方法的class_index也发生了变化,则也可以
//                        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("")){
                        
//此类中的其他方法的改动
                        
//
                    }

                    
                    
                }

                
                
//此类中的使用到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");
        }


    }



}


改变字节码的片段
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 阅读(1798) 评论(2)  编辑  收藏 所属分类: bcel javassist

评论:
# re: API转换的问题的解决 2009-08-16 23:38 | Frank_Fang
蓝色的指令是采用方法1所加的位置,这样做不太方便
  回复  更多评论
  
# re: API转换的问题的解决 2009-08-17 21:54 | Frank_Fang
使用反编译工具根据生成的class文件生成的java文件
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: ClientTest.java

package client;

import java.io.PrintStream;
import one.api.MyAPITest;

// Referenced classes of package client:
// StringUtil

public class ClientTest
{

public ClientTest()
{
}

public static void main(String args[])
{
MyAPITest sb = new MyAPITest();
int paramTwoint = Integer.parseInt("2");
int result = sb.add(Integer.parseInt("1"), paramTwoint);
System.out.println(result);
String temp = "999";
int paramTwoint = Integer.parseInt("33");
int resultone = sb.add(Integer.parseInt(temp), paramTwoint);
System.out.println(resultone);
int paramTwoint = Integer.parseInt(String.valueOf("2"));
int resulttwo = sb.add(Integer.parseInt(String.valueOf("1")), paramTwoint);
System.out.println(resulttwo);
int paramTwoint = Integer.parseInt("333");
int resultthree = sb.add(Integer.parseInt(StringUtil.createStringOne("111")), paramTwoint);
System.out.println(resultthree);
int paramTwoint = Integer.parseInt(StringUtil.createStringThree("12", "23", "34"));
int resultfour = sb.add(Integer.parseInt(StringUtil.createStringTwo("23", "34")), paramTwoint);
System.out.println(resultfour);
int paramTwoint = Integer.parseInt(StringUtil.createStringThree("12", "23", "34"));
int resultfive = sb.add(Integer.parseInt(StringUtil.createStringTwo(StringUtil.createStringOne("88"), "34")), paramTwoint);
System.out.println(resultfive);
}

public int mytest(String a, String b)
{
MyAPITest sb = new MyAPITest();
int paramTwoint = Integer.parseInt(b);
int result = sb.add(Integer.parseInt(a), paramTwoint);
return result;
}
}  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: