Содержание


Модификация байт-кода Java VM

Часть 2. Обзор библиотеки BCEL и других инструментов для изменения байт-кода

Comments

Серия контента:

Этот контент является частью # из серии # статей: Модификация байт-кода Java VM

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Модификация байт-кода Java VM

Следите за выходом новых статей этой серии.

В open-source сообществе существует известное утверждение: «для любой задачи существует несколько способов решения и, следовательно, несколько инструментов, позволяющих реализовать эти решения». Поэтому не стоит ограничиваться единственным средством модификации байт-кода JVM - библиотекой ASM из предыдущей статьи. Для полноты картины необходимо рассмотреть и альтернативные варианты.

1. Библиотека BCEL

BCEL (Byte Code Engineering Library) API представляет собой комплекс программных средств для статического анализа и динамического изменения и создания содержимого class-файлов Java. Артефакты библиотеки BCEL и необходимую документацию можно загрузить с официального сайта http://jakarta.apache.org/bcel/ или найти в соответствующих репозиториях различных дистрибутивов LINUX.

Главным преимуществом библиотеки BCEL является высокоуровневый API, который избавляет от необходимости изучать внутреннюю структуру class-файлов и особенности реализации байт-кода (от этого недостатка страдает библиотека ASM).

1.1. Краткий обзор библиотеки BCEL

Ключевыми компонентами библиотеки BCEL являются:

  • пакет org.apache.bcel.classfile с классами, описывающими элементы class-файла, но не предназначенными для его изменения. Классы этого пакета могут применяться для считывания байт-кода из class-файла и последующей записи байт-кода в файл. При отсутствии исходного кода такая возможность может оказаться полезной для анализа существующих классов. В классе JavaClass содержится функциональность для обращения к методам, полям и атрибутам анализируемого класса;
  • пакет org.apache.bcel.generic с классами для динамического изменения или генерации объектов типа JavaClass или Method с функциональностью для вставки требуемых фрагментов непосредственно в байт-код или удаления ненужной информации из class-файла;
  • пакет org.apache.bcel.util с уже готовыми классами для просмотра class-файлов или преобразования их в различные форматы.

1.2. JavaClass

Важнейший класс JavaClass находится в пакете org.apache.bcel.classfile и представляет детальное отображение структуры class-файла. Этот класс отражает структуру class-файла с высоким уровнем абстракции и поэтому содержит информацию о полях, методах, ссылки на родительский класс (супер-класс) и интерфейсы, реализуемые исследуемым классом. Объекты типа JavaClass создаются классом ClassParser, выполняющим анализ содержимого class-файлов.

Внутри класса JavaClass важную роль играет объект ConstantPool (блок констант). В этом объекте содержится массив элементов типа Constant фиксированного размера, которые можно извлечь с помощью метода getConstant() по заданному индексу. В объектах типа Method и Field хранится сигнатура, определяющая тип данных для полей или типы входных и выходных параметров для методов.

1.3. Схема использования библиотеки BCEL

Работу по анализу содержимого class-файла можно значительно упростить, если прибегнуть к помощи имеющихся в BCEL классов. Класс Repository позволяет за одно действие выполнить считывание содержимого class-файла в объект типа JavaClass:

JavaClass myownclass = Repository.lookupClass( "java.lang.String" );

После этого можно выполнить анализ байт-кода, полученного из класса. Доступ к внутренним компонентам осуществляется через "классические" getter- и setter-методы. В листинге 1 приведен код, выводящий высокоуровневую информацию о структуре класса:

Листинг 1. Просмотр информации о классе
//вывод информации о самом классе
System.out.println( myownclass );
//переход на уровень ниже – к методам класса
for( Method method : myownclass.getMethods() ) {
   //вывод информации о текущем методе
   System.out.println( methods[i] );
   //получении информации о теле метода
   Code code = methods[[i].getCode();
   //если у метода есть тело, то вывод информации о нем
   if( code != null ) 
     System.out.println( code );
 }

Также BCEL поддерживает шаблон проектирования Visitor, с помощью которого можно создавать анализаторы любой сложности, активизирующиеся при проходе по содержимому class-файла.

1.4. Пример анализа содержимого class-файла

В листинге 2 приведен класс Prim, который в дальнейшем будет анализироваться при помощи классов библиотеки BCEL:

Листинг 2. Класс Prim
public class Prim implements java.io.Serializable {
 protected String name;

 public Prim( String name ) {
   this.name = name;
 }

 public String getName() {
   return name;
 }

 public void setName( String name ) {
   this.name = name;
 }
}

При разработке ПО часто встречается ситуация, когда для используемых классов отсутствует исходный код или документация. Для решения подобной проблемы можно написать программу, которая выполнит анализ содержимого class-файла и распечатает его структуру. Пример простейшей реализации такой программы представлен в листинге 3.

Листинг 3. Программа для вывода информации о Java-классе.
import org.apache.bcel.*;
import org.apache.bcel.classfile.*;

public class InspectorClass {
 protected JavaClass content;

 public InspectorClass( String content ) {
   this.content = Repository.lookupClass( content );
 }

 public static void main( String args[] ) {
   if( args.length != 1 ) {
     System.out.println( "Вызов: java -cp InspectorClass <имя_класса_без_расширения>" );
     System.exit( -1 );
   }

   try {
     InspectorClass inspector = new InspectorClass( args[0] );
     if( inspetor.content == null ) { 
       // указанный class-файл невозможно открыть для считывания содержимого
       throw new Exception();
     } else {
       // вывод общей информации о заданном class-файле
       System.out.println( inspector.content );
     }
   }
   catch( Exception e ) {
     System.err.println( "Необходимо задать имя class-файла без расширения" );
   }
 }
}

Программу из листинга 3 можно запустить, написав в командной строке:

java -cp InspectorClass Prim

В результате работы программы будет распечатан полный список методов, полей и прочих элементов класса Prim, считанных из объекта content типа JavaClass.

1.5. Модификация скомпилированного кода

Разобрав, как в BCEL выполняется анализ содержимого class-файла, можно перейти к более сложной задаче - изменению байт-кода скомпилированного класса. В ходе работы с классом Prim (по сценарию для него отсутствует исходный код и документация) обнаружилось, что в нём отсутствует конструктор по умолчанию. Этот недостаток также можно исправить с помощью BCEL, как показано в листинге 4.

Листинг 4. Программа для добавления в Java-класс конструктора по умолчанию
import org.apache.bcel.*;
import org.apache.bcel.generic.*;
import org.apache.bcel.classfile.*;

public class MakingConstructor implement Constants {
 protected JavaClass originCode;
 protected ClassGen modifiedCode;

 public MakingConstructor( String originCode ) {
   this.originCode = Repository.lookupClass( originCode );
   this.modifiedCode = new ClassGen( this.originCode );
 }

 public static void main( String args[] ) {
   if( args.length != 1 ) {
     System.out.println( "Вызов: java MakingConstructor <имя_класса_без_расширения>" );
     System.exit( -1 );
   }

   try {
     MakingConstructor modifiedByteCode = new MakingConstructor( args[0] );
     if( modifiedByteCode.originCode == null ) { 
       // заданный class-файл невозможно открыть для считывания содержимого
       throw new Exception();
     } else {
       // добавление пустого конструктора по умолчанию в анализируемый класс
       modifiedByteCode.modifiedCode.addEmptyConstructor( ACC_PUBLIC );
       // сохранение измененного класса в отдельный class-файл
       modifiedByteCode.modifiedCode.getJavaClass().dump( "new_prim.class" );
     }
   } catch( Exception e ) {
     System.err.println( "Необходимо задать имя class-файла без расширения" );
   }
 }
}

В отличие от класса для вывода информации из листинга 3, в классе из листинга 4 объявлены два поля. Поле originCode имеет тип JavaClass и хранит исходный байт-код модифицируемого класса. Поле modifiedCode предназначено для измененного байт-кода, поэтому для него используется тип ClassGen из пакета org.apache.bcel.generic, предоставляющий функциональность для добавления полей, методов и атрибутов.

Ссылка на байт-код, считанный из модифицируемого class-файла, сразу же передаётся в поле modifiedCode. После выполнения необходимых проверок в методе main добавляется новый конструктор с уровнем доступа public. При использовании BCEL модификаторы классов задаются в виде битовой маски, и, чтобы установить для метода main набор модификаторов public static final, потребовалось бы передать следующее значение:

( ACC_PUBLIC | ACC_STATIC | ACC_FINAL )

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

2. Другие инструменты для модификации байт-кода JVM

Список инструментов и утилит для анализа и изменения байт-кода виртуальной машины Java не ограничивается описанными выше библиотеками ASM и BCEL: также внимания заслуживают и некоторые альтернативные методики из области Bytecode Engineering.

2.1. Javassist

Библиотека классов Javassist позволяет считывать и записывать байт-код виртуальной машины Java, используя для этого возможности Reflections API (Reflections API является компонентом платформы JSE, дополнительную информацию можно найти в документации к пакету java.lang.reflect). Операции с байт-кодом выполняются по следующему алгоритму: требуемый класс извлекается из пула классов JVM в виде объекта типа CtClass, затем в байт-код этого объекта вносятся изменения, после чего модифицированный байт-код сохраняется в class-файле при помощи методов writeFile или toBytecode, как показано в листинге 5:

Листинг 5. Использование библиотеки Javassist
ClassPool pool = ClassPool.getDefault();
//аргумент - имя класса, тип String
CtClass cc = pool.get( targetClass ); 
//далее вносятся требуемые изменения
...
//измененный байт-код записывается в файл, при необходимости указывается имя файла
cc.writeFile();
//также можно добавлять поля в виде объектов CtField
cc.addField( CtField fld );
//и методы в виде объектов CtMethod
cc.addMethod( CtMethod mtd );

В Javassist имеется возможность вставки байт-кода в конкретное место в классе, указывая позицию относительно текущего метода (объект типа CtMethod) или конструктора (объект типа CtConstructor) модифицируемого класса:

CtMethod cm = ...; // определить текущий метод
cm.InsertBefore(); // вставить байт-код перед текущим методом
cm.InsertAfter();  // вставить байт-код после текущего метода
cm.addCatch();     // добавить блок обработки исключений

и замены заданных фрагментов кода:

cm.setBody(); // замена всего тела текущего метода

Для выполнения последнего действия предлагается класс ExprEditor, позволяющий выборочно заменять части кода в теле модифицируемого метода.

2.2. Serp

В библиотеку Serp (http://serp.sourceforge.net/) входит набор высокоуровневых API для работы с любыми элементами байт-кода: от структур классов, полей и методов до отдельных инструкций в коде методов. Эта библиотека не обладает никакими отличительными особенностями по сравнению с рассмотренными решениями.

2.3. JOIE

Утилита JOIE (http://www.cs.duke.edu/ari/joie/) - ещё один инструмент для "безопасной трансформации байт-кода Java". Особенность этого пакета в том, что функциональность разделена на два уровня: низкоуровневый интерфейс позволяет выполнять операции на уровне отдельных инструкций байт-кода, а высокоуровневый интерфейс позволяет работать со структурой классов, добавлять поля, методы, новые интерфейсы. Главную задачу авторы инструмента видят в обеспечении максимальной безопасности при изменении байт-кода с сохранением удобства применения инструмента.

2.4. Jikes Bytecode Toolkit (IBM)

Jikes Bytecode Toolkit (http://www.alphaworks.ibm.com/tech/jikesbt/) - это библиотека Java-классов, разработанная компанией IBM для выполнения операций чтения/записи скомпилированных class-файлов и создания новых class-файлов. В настоящее время является компонентом платформы Eclipse (Eclipse Concern Manipulation Environment (CME)).

Заключение

В предыдущей статье рассматривались основы процесса модификации байт-кода виртуальной машины Java и одно из инструментальных средств для выполнения модификации - библиотека ASM. В данной статье, завершающей серию «Модификация байт-кода Java VM», подробно рассмотрен еще один инструмент для модификации байт-кода - библиотека BCEL, и приведен краткий обзор других инструментов подобного рода.


Ресурсы для скачивания


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=601988
ArticleTitle=Модификация байт-кода Java VM: Часть 2. Обзор библиотеки BCEL и других инструментов для изменения байт-кода
publish-date=12162010