NoClassDefFoundError после инструментального кода - PullRequest
1 голос
/ 01 августа 2020

Я динамически подключаю свой агент Java к процессу java, который инструментирует код. Обычно он добавляет вызов stati c к каждому запуску метода:

//method start   
AgentClass.staticMethod();  
//method body  

AgentClass лежит в .jar агента. Но после инструментирования процесс начинает выполнение нового кода и выдает NoClassDefFoundError, он не может найти AgentClass. Я попытался настроить классы таким образом, чтобы включить блок try-catch и загрузить AgentClass с forName следующим образом:

try {
    AgentClass.staticMethod();
} catch(NoClassDefFoundError e) {
    Class.forName("AgentClass");
}

Но потом у меня появилось несколько ошибок, связанных с пересчетом кадров стека. например: Caused by: java.lang.VerifyError: Inconsistent stackmap frames at branch target 20 Я решил это с помощью visitMaxs() (я использую ASM библиотеку). Затем я получил это: StackMapTable error: bad offset. Это было решено с помощью GOTO вместо RETURN, но затем я получил: ClassFormatError: Illegal local variable table in method.

Есть ли более простой способ решить мою первоначальную NoClassDefFoundError ошибку?

UPDATE : Мои классы агентов загружаются с помощью Application Classloader (sun.misc.Launcher$AppClassLoader), а процесс, который я хотел инструментировать, загружает классы с помощью настраиваемого URL-загрузчика классов.

UPDATE2: Это то, что Я хотел преобразовать в байт-код:

 try {
        AgentClass agent = AgentClass.staticMethod();
     } catch (Throwable e) {
        try {
           Class.forName("AgentClass");
        } catch (ClassNotFoundException ex) {
     }
   }

My MethodVisitor (я не очень хорошо разбираюсь в байт-коде, поэтому байт-код был автоматически сгенерирован ASM с использованием TraceClassVisitor.):

protected MethodVisitor createVisitor(MethodVisitor mv,final String name,final String desc,int access,String signature,String[]exceptions){
        int variablesCount = (8 & access) != 0 ? 0 : 1;
        Type[]args=Type.getArgumentTypes(desc);
       
        for(int i=0;i<args.length; ++i){
        Type arg=args[i];
        variablesCount+=arg.getSize();
        }

        final int varCount=variablesCount;


        return new MethodVisitor(458752,mv){
public void visitCode(){
        Label label0=new Label();
        Label label1=new Label();
        Label label2=new Label();
        this.mv.visitTryCatchBlock(label0,label1,label2,"java/lang/Throwable");
        Label label3=new Label();
        Label label4=new Label();
        Label label5=new Label();
        this.mv.visitTryCatchBlock(label3,label4,label5,"java/lang/ClassNotFoundException");
        this.mv.visitLabel(label0);
        this.mv.visitLineNumber(42,label0);
        this.mv.visitMethodInsn(Opcodes.INVOKESTATIC,"AgentClass","staticMethod","()LAgentClass;",false);
        this.mv.visitVarInsn(Opcodes.ASTORE,varCount);
        this.mv.visitLabel(label1);
        this.mv.visitLineNumber(48,label1);
        Label label6=new Label();
        this.mv.visitJumpInsn(Opcodes.GOTO,label6);
        this.mv.visitLabel(label2);
        this.mv.visitLineNumber(43,label2);
        this.mv.visitFrame(Opcodes.F_SAME1,0,null,1,new Object[]{"java/lang/Throwable"});
        this.mv.visitVarInsn(Opcodes.ASTORE,0);
        this.mv.visitLabel(label3);
        this.mv.visitLineNumber(45,label3);
        this.mv.visitLdcInsn("AgentClass");
        this.mv.visitMethodInsn(Opcodes.INVOKESTATIC,"java/lang/Class","forName","(Ljava/lang/String;)Ljava/lang/Class;",false);
        this.mv.visitInsn(Opcodes.POP);
        this.mv.visitLabel(label4);
        this.mv.visitLineNumber(47,label4);
        this.mv.visitJumpInsn(Opcodes.GOTO,label6);
        this.mv.visitLabel(label5);
        this.mv.visitLineNumber(46,label5);
        this.mv.visitFrame(Opcodes.F_FULL,1,new Object[]{"java/lang/Throwable"},1,new Object[]{"java/lang/ClassNotFoundException"});
        this.mv.visitVarInsn(Opcodes.ASTORE,1);
        this.mv.visitLabel(label6);
        this.mv.visitLineNumber(49,label6);
        this.mv.visitFrame(Opcodes.F_CHOP,1,null,0,null);
        this.mv.visitInsn(Opcodes.RETURN);
        this.mv.visitLocalVariable("e","Ljava/lang/Throwable;",null,label3,label6,0);
        this.mv.visitMaxs(1, 2);
        
        super.visitCode();
        }
        ...
        }
        }

ОБНОВЛЕНИЕ 3 Вот как я подключаю свой агент во время выполнения:

final VirtualMachine attachedVm = VirtualMachine.attach(String.valueOf(processID));
attachedVm.loadAgent(pathOfAgent, argStr);
attachedVm.detach();
                                  

1 Ответ

3 голосов
/ 03 августа 2020

На данный момент я предполагаю, что ваша иерархия загрузчика классов выглядит примерно так:

boot class loader
  platform class loader
    system/application class loader
    custom URL class loader

Или, может быть:

boot class loader
  platform class loader
    system/application class loader
  custom URL class loader

Т.е. загрузчик классов приложения и пользовательский загрузчик классов URL являются братьев и сестер или каким-либо другим образом в разных частях иерархии загрузчика классов, т.е. классы, загруженные в один из них, неизвестны другому.

Способ решить эту проблему - найти общего предка и сделать убедитесь, что туда загружены классы, необходимые для вашей схемы инструментовки. Обычно я использую загрузчик классов bootstrap. Прежде чем я объясню вам, как программно добавлять классы в загрузчик классов bootstrap, попробуйте добавить JAR вашего агента в путь к классам bootstrap вручную в командной строке Java через -Xbootclasspath/a:/path/to/your/agent.jar и посмотрите, есть ли настраиваемый класс URL затем загрузчик находит класс. Я был бы очень удивлен, если бы это не сработало. Затем сообщите об этом, и мы сможем продолжить.

Также объясните, как вы прикрепляете инструментальный агент:

  • через -javaagent:/path/to/your/agent.jar или
  • через горячее подключение во время время выполнения (если да, покажите код)

Обновление после некоторых поясняющих комментариев OP:

Можно добавить JAR (не отдельные классы) к пути класса bootstrap, вызвав метод Instrumentation.appendToBootstrapClassLoaderSearch(JarFile) . В методах вашего агента premain или (для горячего подключения) agentmain JVM передает вам экземпляр Instrumentation, который вы можете использовать для этой цели.

Предостережение: вам необходимо добавить JAR перед любым из классы, которые вам нужны в пути к классам bootstrap, были импортированы или использованы другими, уже загруженными классами (включая сам класс агента). Поэтому, если в вашем случае метод AgentClass, вызываемый другим классом в загрузчике классов-родственников, находится внутри того же класса, в котором находятся методы premain и agentmain, вы хотите учитывать этот метод (и все другие, которые могут вызывается извне) в другой служебный класс. Кроме того, не обращайтесь напрямую к этому классу из основного класса агента, а сначала заставьте агент добавить свой собственный JAR в путь загрузочного класса, а затем вызывать любые методы в нем через отражение, а не напрямую из основного класса агента. После того, как основной класс агента выполнил свою работу, другие классы могут напрямую ссылаться на классы, которые сейчас находятся на пути классов bootstrap, проблема решена.

Однако остается одна проблема: как агент узнать путь JAR для добавления к пути класса bootstrap? Это зависит от вас. Вы можете установить системное свойство в командной строке, прочитать путь из файла, ввести жесткий код, передать его как строку конфигурации агента, передаваемую на premain/agentmain через attachedVm.loadAgent(agentPath, configString) (в данном случае configString, содержащую путь к агенту снова) или что-то еще. В качестве альтернативы создайте внутренний JAR как ресурс внутри JAR основного агента, содержащий классы, которые будут помещены в загрузчик классов bootstrap. Агент может загрузить ресурс, сохранить его во временный файл, а затем добавить путь к временному файлу в путь к классу bootstrap. Это немного сложно, но понятно и поэтому довольно популярно среди разработчиков агентов. Иногда эту схему называют подходом «батутного агента».

...