Manipulação de
Bytecodes Java
Giuliano Mega
1
Introdução
• O que é bytecode?
• Código intermediário
• Semelhante aos códigos de
montagem (assembly)
• Byte-code – opcodes de 1 byte
• Processadores virtuais (Virtual
Machines)
• Quem usa?
• Java (JVM), Smalltalk (Slang), .NET
(CLR)
2
Introdução
• Assuntos em pauta
• Por que?
• Reflexão
• Como?
• JVM
• Bytecodes
• Técnicas de compilação
• Arcabouços
• Suposição
• Você conhece Java
• Você sabe o que é um Class Loader
3
Reflexão Computacional
• Sistemas computacionais
• Sistema de computador que responde
perguntas ou dá apoio a certas ações
sobre algum domínio
• Sistema representa o seu domínio por meio
de estruturas de dados
• Representação causalmente conexa
4
Reflexão Computacional (cont.)
• Meta-sistema
• Domínio = outro sistema.
• Sistema-objeto.
• Sistema reflexivo
• Meta-sistema causalmente
sistema-objeto é ele próprio.
conexo
cujo
• Conseqüência
• Pode modificar aspectos de sua execução
como efeito dela mesma.
5
Arquiteturas Reflexivas
• Linguagem dá acesso a estruturas que modificam
aspectos da interpretação da linguagem.
• Interpretadores metacirculares e variantes
• Forma muito explorada de implementação;
• poderosa e garantida;
• conexão causal garantida pela circularidade.
programa
interpretador
6
Arquiteturas Reflexivas (cont.)
• Reflexão [1]
• Ad hoc
• Facilidades providas pela linguagem
• Arquitetura reflexiva:
• Reconhece reflexão como conceito;
• provê meios para fazê-la.
• Finalidade aberta (open-ended).
• Alguns exemplos:
• 3-LISP,BROWN (funcionais)
• META-PROLOG, FOL (lógicas)
• TEIRESIAS, SOAR (regras)
• 3-KRS, SuperEgo (OO)
• Prá que que serve afinal?
7
Reflexão - aplicações
• Aplicações
• depuração [1];
• prog. concorrente [2];
• backtracking [3];
• futures [4];
• AOP [5];
• etc.
• Tudo isso no nível da
linguagem
• Sem extensões sinistras
ao interpretador
8
Reflexão Java
• Afinal, Java é “reflexiva”?
• Não conta com uma arquitetura reflexiva.
• Segunda categoria
• Ausência de meta-nível claro
• Introspecção confusa
• Limitada
• Class loaders
• Proxies dinâmicos (JDK 1.3)
• Justificativa: desempenho, type-safety [6].
9
Reflexão Java (cont.)
• Programadores Java também precisam
de reflexão
• logging, estatísticas, tracing, depuração,
persistência, AOP...
• Reflexão comportamental/estrutural
• Proxies dinâmicos
• Factories
• Não-ortogonais
• Aplicações já prontas – difícil.
10
Reflexão Java (cont.)
• “Reflexão” em tempo de compilação (Open Java
[7] e cia.)
• Não é reflexão, é metaprogramação.
• Reflexão em tempo de carga.
•
•
•
•
Class loaders
Portável entre JVMs
Dizem que é reflexão estrutural
Opinião pessoal: forçada de barra
• Formas desajeitadas e não-portáveis de reflexão
comportamental/estrutural pela JVMTI (Sun).
• Java e adaptação não combinam.
• Flexibilidade => adaptação [8].
11
Manipulação em Tempo de Carga
• Forma mais comum e aceita
• Portável
• Não requer código fonte
• “Fácil” e “eficiente”
class SRLoader extends ClassLoader {
public Class loadClass(String name) {
byte[] bytecode = readClassFile(name);
<mexa no bytecode o quanto quiser>
return resolveClass(defineClass(bytecode));
}
}
12
JVM – formato das classes
Formato de classes Java [9]
13
JVM – básico
• JVM é um processador virtual
• pilhas, “registradores”
• Preocupação com segurança
• verificação
• opcodes tipados (tipos primitivos)
14
JVM - A linguagem de montagem
• Um pequeno exemplo:
• Estáticos:
• Tamanho máximo da pilha
• Número de variáveis locais (slots)
15
Manipulação Revisitada
• Manipular classes:
• Complexo
• Muitas dependências
• Classe = array de bytes
• Necessário interpretar os dados
• Arcabouços (BCEL e muitos outros):
16
Arcabouços de Manipulação
• Forma OO de representar/manipular o
bytecode
• Variam em grau de abstração, tamanho,
desempenho...
• ByteCode Engineering Library* (BCEL) [10]
• ASM [11]
• Java Assistant* (Javassist) [12]
• Code Generation LIBrary (CGLIB)
• SERP
• JOIE
• etc.
17
O Java Assistant (Javassist)
• Nosso foco: manipulação de alto nível
• Intuitivo
• Fácil de usar
• Dificilmente produz código inválido
• Exemplo:
• Medir desempenho (interceptadores):
private String buildString(int length) {
String result = "";
for (int i = 0; i < length; i++){
result += (char)(i%26 + 'a');
}
return result;
}
18
Javassist (cont.)
• Desempenho
private String buildString(int length) {
long start = System.currentTimeMillis();
String result = "";
for (int i = 0; i < length; i++) {
result += (char)(i%26 + 'a');
}
System.out.println("Call to buildString took " +
(System.currentTimeMillis()-start) + " ms.");
return result;
}
• Class Loader
• O Javassist já provê um esqueleto
• javassist.Loader extends java.lang.ClassLoader
• Você só escreve os ganchos
• interface javassist.Translator
19
Javassist (cont.)
• Translator
public interface Translator {
public void start(ClassPool pool)
throws NotFoundException, CannotCompileException;
public void onLoad(ClassPool pool, String classname)
throws NotFoundException, CannotCompileException;
}
• Restrições
• variáveis locais (lembre-se do bytecode...)
• como faço para implementar o exemplo?
• “expressão idiomática Javassist” prá isso
20
Javassist (cont.)
CtClass clas = pool.get(cname);
CtMethod[] meths = clas.getDeclaredMethods();
for (int i = 0; i < meths.length; i++) {
CtMethod mold = methds[i];
String nname = mname+"$impl";
/* 1) Muda o nome do método e duplica com o nome antigo */
mold.setName(nname);
CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null);
/* 2) Cria o embrulho-cronômetro para o método original */
String type = mold.getReturnType().getName();
StringBuffer body = new StringBuffer();
body.append("{\nlong start = System.currentTimeMillis();\n");
if (!"void".equals(type)) {
body.append(type + " result = ");
}
body.append(nname + "($$);\n");
...
/* 3) Substitui o código do método embrulhado */
mnew.setBody(body.toString());
clas.addMethod(mnew);
}
21
Javassist (cont.)
• A técnica envolve ainda um “lançador”
• Em geral provido pelo arcabouço
• Limitações
•
•
•
•
•
Não é muito flexível
Classes internas e anônimas
Código original vai parar em outro método
Lento
Modificações internas
• Introspecção limitada
• Proposta não é essa
• Tem uma API para bytecodes.
22
A ByteCode Engineering Library (BCEL)
• Transforma a classe Java num grafo
• ByteCodes orientados a objeto
• Poderoso
• Mais “intuitivo” que o Javassist
• Prá quem conhece bytecodes
• Mais rápido que o Javassist
• Exemplo:
• Instrumentar todos os métodos Run de todos
os Runnables
23
BCEL (cont.)
public JavaClass modifyClass(JavaClass jc) {
/* 1) Testa se a classe é instância de Runnable */
JavaClass runnableIntf = Repository.lookupClass("java.lang.Runnable");
if(!jc.instanceOf(runnableIntf)) return jc;
Method [] methods = jc.getMethods();
int i = 0;
for(i = 0; i < methods.length; i++){
if(methods[i].getName().equals("run")) break;
}
/* 2) Cria as versões maleáveis (de-serializa) e as modifica */
ClassGen newClass = new ClassGen(jc);
ConstantPoolGen cg = newClass.getConstantPool();
MethodGen mg = new MethodGen(m, newClass.getClassName(), cg);
newClass.removeMethod(methods[i]);
...
/* 3) Calcula dados estáticos, re-serializa o bytecode */
mg.setMaxStack();
mg.setMaxLocals();
newClass.addMethod(mg.getMethod());
return newClass.getJavaClass();
}
24
BCEL (cont.)
InstructionList il = new InstructionList();
InstructionFactory iFactory = new InstructionFactory(nc);
il.append(iFactory.createFieldAccess("java.lang.System", "out",
new ObjectType("java.io.PrintStream"), Constants.GETSTATIC));
il.append(new PUSH(cg, "Método \"run\" chamado na classe " + jc.getName());
il.append(iFactory.createInvoke("java.io.PrintStream", "println",
Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));
InstructionList old_il = mg.getInstructionList();
InstructionHandle [] handles = old_il.getInstructionHandles();
old_il.insert(handles[0], il);
mg.setInstructionList(old_il);
25
BCEL (cont.)
• Problemas
•
•
•
•
Ainda é lento;
pode ser muito complexo;
manipulação direta de bytecodes;
muito fácil gerar código inválido.
26
ASM
• Visitor pattern
• Não seria/de-seria o grafo
• Composição de visitors
• Substituição
• Muito rápido
• 700% mais rápido que o BCEL
• 1100% mais rápido que o SERP
• Compacto
• 25 kb vs. 350 kb do BCEL
• Lembra um pull parser prá XML.
• Não alivia muito em manipulações
complexas.
27
Verificação de Bytecode
• O que há de errado com este (pseudo)
bytecode?
00 aload_0
01 invokevirtual “MyClass#test()”
02 ifne 05
03 aload_0
04 invokevirtual “MyClass#test()”
05 return
Rode isso e...:
java.lang.VerifyError: stack sizes differ (1 != 0).
28
Verificação de Bytecode (cont.)
• Esse código não passa pelo verificador
(pág. 12)
• Verificador
• Algoritmo de análise de fluxo
• Prova um teorema ou arruma um contraexemplo
• Problemas de decidibildade
• Restrições mais fortes do que o necessário.
• Rejeita código “válido”.
29
Verificação de Bytecode (cont.)
00 aload_0
01 invokevirtual “MyClass.test()”
02 ifne 05
03 aload_0
04 invokevirtual “MyClass.test()”
05 return
• Análise de tipos + stack merging = código seguro
+ chatice.
• Merging em laços.
30
Verificação de Bytecode (cont.)
• Compilador “burla” o verificador
• Sempre produz código válido;
• equivalente ao código-fonte;
• nós não temos o código-fonte.
• Transformações difíceis
• Armadilhas (traps);
• implementadas com subrotinas.
31
Problemas Adicionais
• Class Loader que instrumenta:
•
•
•
•
Tem que definir a classe;
saber o que “deixar passar” (i.e. java.lang.*);
getSystemClassloader();
a opção -Dendorsed.dirs=...;
• Limitação: classes de sistema.
32
Conclusão
• Java: reflexão deficitária.
• Desempenho.
• Segurança.
• Manipulação:
• Reflexão estrutural limitada.
• Manipulação em tempo de carga:
• em acordo com a filosofia Java.
• Arcabouços:
• facilitam a tarefa.
• compromisso entre flexibilidade,
complexidade e desempenho.
33
Referências:
[1] P. Maes. Concepts and Experiments in Computational Reflection. In
Proceedings of the OOPSLA’87. pages 147-155. Orlando, Florida.
[2] B. Foote. Objects, Reflection and Open Languages. In Proceedings of the
1992 ECOOP Workshop on Object-Oriented reflection and Meta-Level
Architectures. Utrecht, Netherlands
[3] W. R. LaLonde and M. V. Gulik. Building a Backtracking Facility in Smalltalk
Without Kernel Support. In Proceedings of the OOPSLA’88. pages 105-122.
San Diego, California.
[4] B. Foote and R. Johnson. Reflective Facilities in Smalltalk-80. In Proceedings of
the OOPSLA’89. pages 327-335. New Orleans, Louisiana.
[5] S. Kojarski and D. Lorentz. AOP as a First-class reflective mechanism. In
Companion to the OOPSLA’04. pages 216 – 217. Vancouver, Canada.
[6] S. Liang and G. Bracha. Dynamic Class Loading in the Java Virtual Machine.
In Proceedings of the OOPSLA’98. pages 36-44. Vancouver, Canada.
[7] M. Tsubori, S. Chiba et. al. OpenJava: A Class-based Macro System for Java.
In Reflection and Software Engineering, LNCS 1826, Springer-Verlag, 2000.
/15
34
Referências (cont.)
[8] B. Foote. Class Warfare: Classes vs. Prototypes. In Proceedings of the
OOPSLA’89 Workshop on Objects without Classes, New Orleans, LA.
[9] T. Lindholm and F. Yellin. The Java Virtual Machine Specification. AddisonWesley, 2nd edition, 1999.
[10] BCEL website. http://apache.jakarta.org/bcel. [june 9, 2005]
[11] E. Bruneton, R. Lenglet and T. Coupaye. ASM: a code manipulation tool to
implement adaptable systems. In ACM SIGOPS Adaptable and extensible
component systems. 2002, Grenoble, France.
[12] Javassist website. http://www.csg.is.titech.ac.jp/~chiba/javassist [june 8,
2005]
35
Dúvidas?
36
Download

PPT