2012-05-21 2 views
11

Мне нужно изменить константу строки в развернутой программе Java, то есть значение внутри скомпилированных файлов .class. Его можно перезапустить, но не легко перекомпилировать (хотя это неудобно, если этот вопрос не дает ответов). Это возможно?Изменить константу строки в скомпилированном классе

Обновление: я просто посмотрел файл с шестнадцатеричным редактором, и похоже, что я могу легко изменить строку там. Будет ли это работать, т. Е. Не приведет к аннулированию какой-либо подписи файла? Старая и новая строка являются как буквенно-цифровыми, так и при необходимости могут иметь одинаковую длину.

Обновление 2: я исправил его. Поскольку конкретный класс, который мне нужно изменить, очень мал и не изменился в новой версии проекта, я мог бы просто скомпилировать его и взять новый класс оттуда. Все еще интересуется ответом, который не связан с компиляцией, хотя в образовательных целях.

+0

Существует несколько библиотек манипуляции байткодами, таких как ASM и BCEL, которые позволяют вам настраивать файлы классов по своему вкусу. Лучшим решением, IMO, является извлечение константы как свойства и преодоление неудобства перекомпиляции, которое было необходимо для извлечения. –

+0

@ NathanD.Ryan Я, конечно, собираюсь извлечь его и поместить в конфигурационный файл для будущего, но в этом конкретном случае было бы очень неудобно перекомпилировать развернутую версию, которая довольно старая. –

+0

Является ли данная строка константой времени компиляции? –

ответ

7

Если у вас есть источники для этого класса, то мой подход:

  • Получить файл
  • JAR Получить источник для одного класса
  • Компиляция источник с JAR на пути к классам (таким образом, вам не нужно компилировать что-либо еще, это не значит, что JAR уже содержит двоичный файл). Вы можете использовать последнюю версию Java для этого; просто снимите компилятор с помощью -source и -target.
  • Заменить файл класса в JAR с новым использованием jar u или Ant задачи

Пример для Ant задачи:

 <jar destfile="${jar}" 
      compress="true" update="true" duplicate="preserve" index="true" 
      manifest="tmp/META-INF/MANIFEST.MF" 
     > 
      <fileset dir="build/classes"> 
       <filter /> 
      </fileset> 
      <zipfileset src="${origJar}"> 
       <exclude name="META-INF/*"/> 
      </zipfileset> 
     </jar> 

Здесь я также обновить манифест. Сначала поместите новые классы, а затем добавьте все файлы из исходного JAR. duplicate="preserve" будет убедиться, что новый код не будет перезаписан.

Если код не подписан, вы также можете попытаться заменить байты, если новая строка имеет ту же длину, что и старая. Java выполняет некоторые проверки кода, но есть no checksum in the .class files.

Необходимо сохранить длину; иначе загрузчик классов будет запутан.

+0

Я исправил его с источником (см. Вопрос), но мне все еще интересно найти ответ без источника. При проверке класса в шестнадцатеричном редакторе я видел, что длина строки сохраняется в одном или нескольких байтах перед этим. Если я тоже это обновлю, могу ли я изменить длину строки, или есть дополнительные поля длины, которые также нужно отредактировать? –

+0

Если вы измените длину строки, вам нужно вставить/удалить столько байтов после строки, потому что читатель класса прочитает строку, а затем ожидает следующий действительный элемент -> сбой –

+1

@Aaron, это неверно. Пока вы обновляете поле длины в начале строки, все будет работать нормально. Это не похоже на то, что у загрузчика классов есть магические смещения, жестко закодированные. – Antimony

1

Вы можете изменить .class, используя многие библиотеки инженерии байт-кода. Например, используя javaassist.

Однако, если вы пытаетесь заменить статический конечный элемент, это может не дать вам желаемого эффекта, потому что компилятор будет встроить эту константу везде, где она используется.

Пример кода с использованием javaassist.jar

//ConstantHolder.java

public class ConstantHolder { 

public static final String HELLO="hello"; 

public static void main(String[] args) { 
    System.out.println("Value:" + ConstantHolder.HELLO); 
} 
} 

//ModifyConstant.java

import java.io.IOException; 

import javassist.CannotCompileException; 
import javassist.ClassPool; 
import javassist.CtClass; 
import javassist.CtField; 
import javassist.NotFoundException; 

//ModifyConstant.java 
public class ModifyConstant { 
public static void main(String[] args) { 
    modifyConstant(); 
} 

private static void modifyConstant() { 
    ClassPool pool = ClassPool.getDefault(); 
    try { 
    CtClass pt = pool.get("ConstantHolder"); 
    CtField field = pt.getField("HELLO"); 
    pt.removeField(field); 
    CtField newField = CtField.make("public static final String HELLO=\"hell\";", pt); 
    pt.addField(newField); 
    pt.writeFile(); 
    } catch (NotFoundException e) { 
    e.printStackTrace();System.exit(-1); 
    } catch (CannotCompileException e) { 
    e.printStackTrace();System.exit(-1); 
    } catch (IOException e) { 
    e.printStackTrace();System.exit(-1); 
    } 
} 
} 

В этом случае, программа успешно изменяет значение ПРИВЕТ от «Hello» до «Hell». Однако, когда вы запускаете класс ConstantHolder, он все равно будет печатать «Value: Hello» из-за inlining компилятором.

Надеюсь, это поможет.

+0

Что это означает для подписи? –

+0

поясните пожалуйста. Я не понимаю Ваш вопрос. – krishnakumarp

4

Единственные дополнительные данные, необходимые при модификации строки (технически элемент Utf8) в пуле констант, представляют собой поле длины (2 байта большого конца, предшествующего данным). Нет дополнительных контрольных сумм или смещений, которые требуют модификации.

Есть два предостережения:

  • Строка может быть использована в других местах. Например, «Код» используется для атрибута кода метода, поэтому его изменение разбивает файл.
  • Строка хранится в формате Modified Utf8. Таким образом, нулевые байты и символы Unicode вне базовой плоскости кодируются по-разному. Поле длины - это количество байтов, а не символов, и оно ограничено 65535.

Если вы планируете сделать это много, лучше получить инструмент редактора файлов классов, но шестнадцатеричный редактор полезен для быстрые изменения.

+0

Образец инструмента для редактирования скомпилированного класса: http://sourceforge.net/projects/classeditor/ (вам нужно выбрать файл .class, он не может открыть банки). – Marcin

2

Я недавно написал свой собственный ConstantPool картографа, потому что ASM и JarJar были следующие вопросы:

  • Чтобы замедлить
  • Не поддерживает перезапись без всех зависимостей класса
  • Не поддерживают потоковое
  • Не поддерживал Remapper в режиме API дерева
  • Необходимо было развернуть и свернуть StackMaps

я закончил с следующим:

public void process(DataInputStream in, DataOutputStream out, Function mapper) throws IOException { 
    int magic = in.readInt(); 
    if (magic != 0xcafebabe) throw new ClassFormatError("wrong magic: " + magic); 
    out.writeInt(magic); 

    copy(in, out, 4); // minor and major 

    int size = in.readUnsignedShort(); 
    out.writeShort(size); 

    for (int i = 1; i < size; i++) { 
     int tag = in.readUnsignedByte(); 
     out.writeByte(tag); 

     Constant constant = Constant.constant(tag); 
     switch (constant) { 
      case Utf8: 
       out.writeUTF(mapper.apply(in.readUTF())); 
       break; 
      case Double: 
      case Long: 
       i++; // "In retrospect, making 8-byte constants take two constant pool entries was a poor choice." 
       // See http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5 
      default: 
       copy(in, out, constant.size); 
       break; 
     } 
    } 
    Streams.copyAndClose(in, out); 
} 

private final byte[] buffer = new byte[8]; 

private void copy(DataInputStream in, DataOutputStream out, int amount) throws IOException { 
    in.readFully(buffer, 0, amount); 
    out.write(buffer, 0, amount); 
} 

А потом

public enum Constant { 
    Utf8(1, -1), 
    Integer(3, 4), 
    Float(4, 4), 
    Long(5, 8), 
    Double(6,8), 
    Class(7, 2), 
    String(8, 2), 
    Field(9, 4), 
    Method(10, 4), 
    InterfaceMethod(11, 4), 
    NameAndType(12, 4), 
    MethodHandle(15, 3), 
    MethodType(16, 2), 
    InvokeDynamic(18, 4); 

public final int tag, size; 

Constant(int tag, int size) { this.tag = tag; this.size = size; } 

private static final Constant[] constants; 
static{ 
    constants = new Constant[19]; 
    for (Constant c : Constant.values()) constants[c.tag] = c; 
} 

public static Constant constant(int tag) { 
    try { 
     Constant constant = constants[tag]; 
     if(constant != null) return constant; 
    } catch (IndexOutOfBoundsException ignored) { } 
    throw new ClassFormatError("Unknown tag: " + tag); 
} 

Просто думал, что я хотел бы показать альтернативы без библиотек, как это довольно хорошее место, чтобы начать взлом с. Мой код был вдохновлен javap Исходный код

Смежные вопросы