2012-05-21 7 views
11

Necesito cambiar una constante de cadena en un programa Java implementado, es decir, el valor dentro de los archivos compilados .class. Puede reiniciarse, pero no recompilarse fácilmente (aunque es una opción inconveniente si esta pregunta no da respuestas). es posible?Cambiar constante de cadena en una clase compilada

Actualización: Acabo de mirar el archivo con un editor hexadecimal y parece que puedo cambiar fácilmente la cadena allí. ¿Eso funcionaría, es decir, no invalidaría algún tipo de firma del archivo? Las cadenas antiguas y nuevas son alfanuméricas y pueden tener la misma longitud si es necesario.

Actualización 2: Lo arreglé. Debido a que la clase específica que necesitaba cambiar es muy pequeña y no cambió en la nueva versión del proyecto, pude compilarla y tomar la nueva clase desde allí. Sin embargo, sigue interesado en una respuesta que no implique compilación, con fines educativos.

+0

Hay un par de bibliotecas de manipulación de códigos de bytes, como ASM y BCEL, que le permiten ajustar los archivos de clase a su gusto. La mejor solución, IMO, es extraer la constante como una propiedad y pasar por el inconveniente de la recompilación el único tiempo necesario para la extracción. –

+0

@ NathanD.Ryan Sin duda voy a extraerlo y ponerlo en un archivo de configuración para el futuro, pero en este caso específico sería muy inconveniente recompilar la versión implementada, que es bastante antigua. –

+0

¿La cadena en cuestión es una constante en tiempo de compilación? –

Respuesta

7

Si usted tiene las fuentes de esta clase, entonces mi enfoque es:

  • obtener el archivo JAR
  • Obtener la fuente de la única clase
  • compilar el código fuente con el JAR en la ruta de clases (De esta manera, no tiene que compilar nada más, no hace daño que el JAR ya contenga el binario). Puede usar la última versión de Java para esto; simplemente baje el compilador usando -source y -target.
  • Reemplazar el archivo de clase en el frasco con el nuevo uso de jar u o una tarea Ant

Ejemplo para una tarea de 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> 

Aquí también puedo actualizar el manifiesto. Coloque las nuevas clases primero y luego agregue todos los archivos del JAR original. duplicate="preserve" se asegurará de que el nuevo código no se sobrescriba.

Si el código no está firmado, también puede intentar reemplazar los bytes si la nueva cadena tiene exactamente la misma longitud que la anterior. Java realiza algunas comprobaciones en el código, pero está no checksum in the .class files.

Debe conservar la longitud; de lo contrario, el cargador de clases se confundirá.

+0

Lo arreglé con la fuente (ver pregunta), pero todavía estoy interesado en encontrar una respuesta sin fuente. Al inspeccionar la clase en un editor hexadecimal, vi que la longitud de la cadena se almacena en uno o más bytes antes. Si también actualizo eso, ¿podría cambiar la longitud de la cadena, o hay más campos de longitud que necesitan ser editados también? –

+0

Si cambia la longitud de la cadena, necesita insertar/eliminar tantos bytes después de la cadena porque el lector de clase leerá la cadena y luego esperará que el siguiente elemento válido -> bloqueo –

+1

@ Aaron, eso no es correcto. Siempre que actualice el campo de longitud al comienzo de la cadena, todo funcionará bien. No es como si el cargador de clases tuviera desviaciones mágicas codificadas de manera fija. – Antimony

1

Puede modificar .class utilizando muchas bibliotecas de ingeniería de código de bytes. Por ejemplo, usando javaassist.

Sin embargo, si está tratando de reemplazar un miembro final estático, es posible que no le dé el efecto deseado, ya que el compilador alineará esta constante donde sea que se use.

código

de muestra usando 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); 
    } 
} 
} 

En este caso, el programa modifica con éxito el valor de HOLA de "Hola" a "Infierno". Sin embargo, cuando ejecute la clase ConstantHolder, aún se imprimirá "Value: Hello" debido a la alineación del compilador.

Espero que ayude.

+0

¿Qué significa para una firma? –

+0

por favor explique. No entendí tu pregunta. – krishnakumarp

4

La única información adicional necesaria al modificar una cadena (técnicamente un elemento Utf8) en el conjunto constante es el campo de longitud (2 bytes big endian que preceden a los datos). No hay sumas de verificación adicionales o compensaciones que requieren modificaciones.

Hay dos advertencias:

  • La cadena se pueden utilizar en otros lugares. Por ejemplo, "Código" se utiliza para un atributo de código de método, por lo que cambiarlo rompería el archivo.
  • La cadena se almacena en formato Utf8 modificado. Por lo tanto, los bytes nulos y los caracteres unicode fuera del plano básico se codifican de manera diferente. El campo de longitud es el número de bytes, no de caracteres, y está limitado a 65535.

Si va a hacer esto mucho, es mejor obtener una herramienta de edición de archivos de clase, pero el editor hexadecimal es útil para cambios rápidos

+0

La herramienta de muestra para editar una clase compilada es: http://sourceforge.net/projects/classeditor/ (debe seleccionar un archivo .class, no puede abrir jar). – Marcin

2

poco me escribió mi propio asignador ConstantPool porque ASM y JarJar tenían las siguientes cuestiones:

  • para ralentizar
  • no apoyó la reescritura sin todas las dependencias de clase
  • no apoyó la transmisión de
  • No era compatible con Remapper en modo Tree API
  • Tuve que expandir y contraer StackMaps

que terminó con la siguiente:

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); 
} 

Y luego

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); 
} 

sólo pensé que iba a presentar alternativas sin bibliotecas ya que es bastante un buen lugar para empezar a cortar a partir. Mi código está inspirado en javap código fuente

Cuestiones relacionadas