Use jclasslib para modificar bytecode/código-fonte

  Visualizar o código-fonte é muito simples. Alguns IDEs comumente usados, como ideia e eclipse, fornecem a função de visualizar o código-fonte dos arquivos de classe. Embora haja algumas discrepâncias com o código-fonte (decomposição de açúcar sintático, etc.), a função a implementação é consistente e está mais próxima da JVM do que do código-fonte.situação no tempo de execução.
  Às vezes, precisamos modificar o código-fonte para atender aos requisitos de uso. É relativamente simples refazer o bytecode gerado pelo código java. Uma maneira é herdar e depois reescrever a função a ser modificada. A outra maneira é diretamente crie um arquivo de classe com o mesmo nome, copie o código-fonte descompilado para ele e, após a modificação, substitua o arquivo de classe no pacote jar original pelo arquivo de classe recém-gerado, mas alguns bytecodes são gerados por outras linguagens e o arquivo descompilado não atende à sintaxe de compilação java, não pode ser compilado em um novo arquivo de classe. Nesse caso, uma maneira comum é modificar diretamente o bytecode para alcançá-lo.
  Existem muitos artigos sobre modificação de bytecode na Internet, mas a maioria deles modifica o pool constante para gerar valores diferentes. Este artigo usa jclasslib para modificar diretamente a lógica do código-fonte no bytecode. Para conveniência de demonstração, a demonstração deste artigo pode ser relativamente simples, mas este método pode ser usado em aulas mais complexas, por favor deixe uma mensagem para discutir se necessário.

1. Código-fonte Java

package com.zhanghao.test.jclasslib;

public class JclasslibTest {
    
    
    public static void main(String[] args) {
    
    
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
    
    
        int min = a <= b ? a : b;
        System.out.println(min);
    }
}

Imprima o menor valor dos dois parâmetros: 1
Objetivo: Fazer com que o método printMin imprima um valor maior modificando o bytecode: 2

2. O resultado da descompilação do arquivo de classe

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.zhanghao.test.jclasslib;

public class JclasslibTest {
    
    
    public JclasslibTest() {
    
    
    }

    public static void main(String[] args) {
    
    
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
    
    
        int min = a <= b ? a : b;
        System.out.println(min);
    }
}

  Pode-se ver que o arquivo de classe gerado adiciona apenas um método de construção padrão sem argumentos em comparação com o código-fonte java (porque a demonstração é relativamente simples, não reflete várias estratégias para otimização de compilação)

3. Baixe e instale jclasslib

https://github.com/ingokegel/jclasslib/releases

4. Use jclasslib para abrir o arquivo de classe

insira a descrição da imagem aqui
Os nomes de cada parte do bytecode já estão claros, então não vou explicar muito aqui. Veja o local que precisamos modificar. Methods->printMin O bytecode
insira a descrição da imagem aqui
correspondente ao método printMin tem 19 linhas no total.
0: Push o primeiro parâmetro na pilha (parâmetro a =1)
1: Empurre o segundo parâmetro para a pilha (parâmetro b=2)
2: Compare o tamanho dos dois valores int no topo da pilha e pule para o instrução de bytecode na linha 9 quando o resultado for maior que 0 (a condição de julgamento entra no bloco lógico correspondente)
5: Empurre o primeiro parâmetro para a pilha (escolha a)
6: Salte incondicionalmente para a instrução de bytecode na linha 10
9: Empurre o segundo parâmetro na pilha (escolha b)
10: Coloque o valor int no topo da pilha Armazene na terceira variável local (resultado da seleção de armazenamento c)
11: Obtenha o domínio estático da classe especificada e coloque-o no topo de a pilha (obter a instância e o método a ser executado)
14: Colocar o terceiro parâmetro na pilha (colocar c
15: Chamar o método da instância
: Retornar void do método atual.
Para mais instruções de bytecode, consulte a máquina virtual tabela de instruções de bytecode
. Consulte a tabela de instruções de bytecode da máquina virtual. Para obter um valor maior no parâmetro de saída pringMin, basta alterar o terceiro comando if_icmpgt para if_icmple.

mnemônico if_icmpgt if_icmple
significado do comando Compare o tamanho dos dois valores int no topo da pilha e pule quando o resultado for maior que 0 Compare o tamanho dos dois valores int no topo da pilha e pule quando o resultado for menor ou igual a 0
código de bytes 0xa3 0xa4
número decimal com sinal -93 -92

5. Modifique o bytecode

package com.zhanghao.test.jclasslib;

import com.alibaba.fastjson.JSON;
import java.io.*;
import org.gjt.jclasslib.io.ClassFileWriter;
import org.gjt.jclasslib.structures.AttributeInfo;
import org.gjt.jclasslib.structures.ClassFile;
import org.gjt.jclasslib.structures.MethodInfo;
import org.gjt.jclasslib.structures.attributes.CodeAttribute;

public class JclasslibModify {
    
    
    public static void main(String[] args) throws Exception {
    
    
        String filePath = "/Users/zhanghao/Desktop/jclasslib/JclasslibTest.class";
        FileInputStream fis = new FileInputStream(filePath);

        DataInput di = new DataInputStream(fis);
        ClassFile cf = new ClassFile();
        cf.read(di);
        System.out.println(JSON.toJSONString(cf));
        MethodInfo[] methodInfos = cf.getMethods();
        MethodInfo methodInfo = methodInfos[2];
        AttributeInfo[] attributeInfos = methodInfo.getAttributes();
        CodeAttribute codeAttribute = (CodeAttribute) attributeInfos[0];
        byte[] bytes = codeAttribute.getCode();
        bytes[2] = -92;

        fis.close();
        File f = new File(filePath);
        ClassFileWriter.writeToFile(f, cf);
    }
}

Para ler um arquivo de classe, você pode visualizar a estrutura de classe de ClassFile por meio de depuração ou json de saída, modificar o bytecode na estrutura de classe e reescrever o arquivo.
Precisa usar jclasslib.jar, link para download: jclasslib.jar

6. Veja os resultados modificados

package com.zhanghao.test;

public class JclasslibTest {
    
    
    public JclasslibTest() {
    
    
    }

    public static void main(String[] args) {
    
    
        int a = 1;
        int b = 2;
        printMin(a, b);
    }

    private static void printMin(int a, int b) {
    
    
        int min = a > b ? a : b;
        System.out.println(min);
    }
}

insira a descrição da imagem aqui

Este artigo explica brevemente o método de modificação do bytecode. Quando o bytecode precisar ser modificado no projeto real, a situação será mais complicada. Substitua o bloco de bytecode correspondente no bytecode original e outros arquivos também precisarão ser modificados de acordo , como pool constante, etc.

Acho que você gosta

Origin blog.csdn.net/qq_21033663/article/details/105928982
Recomendado
Clasificación