Uma introdução à interface Java Debug (JDI)
1. Visão geral
Podemos nos perguntar como IDEs amplamente reconhecidos comoIntelliJ IDEAe Eclipse implementamdebugging features. Essas ferramentas dependem bastante da Java Platform Debugger Architecture (JPDA).
Neste artigo introdutório, discutiremos a Java Debug Interface API (JDI) disponível em JPDA.
Ao mesmo tempo,we’ll write a custom debugger program passo a passo, familiarizando-nos com as interfaces úteis JDI.
2. Introdução ao JPDA
A JPDA (Java Platform Debugger Architecture) é um conjunto de interfaces e protocolos bem projetados usados para depurar Java.
Ele fornece três interfaces especialmente projetadas para implementar depuradores personalizados para um ambiente de desenvolvimento em sistemas de desktop.
Para começar, a Java Virtual Machine Tool Interface (JVMTI) nos ajuda a interagir e controlar a execução de aplicativos em execução noJVM.
Então, há o Java Debug Wire Protocol (JDWP) que define o protocolo usado entre o aplicativo em teste (depurado) e o depurador.
Por fim, a Java Debug Interface (JDI) é usada para implementar o aplicativo depurador.
3. O que é JDI?
A API Java Debug Interface é um conjunto de interfaces fornecidas pelo Java, para implementar o frontend do depurador. JDI is the highest-layer of the JPDA.
Um depurador criado com JDI pode depurar aplicativos em execução em qualquer JVM que suporte JPDA. Ao mesmo tempo, podemos conectá-lo a qualquer camada de depuração.
Ele fornece a capacidade de acessar a VM e seu estado, além de acessar as variáveis do depurador. Ao mesmo tempo, permite definir pontos de interrupção, etapas, pontos de controle e manipular threads.
4. Configuração
Exigiremos dois programas separados - um depurador e um depurador - para entender as implementações do JDI.
Primeiro, vamos escrever um programa de amostra como o depurado.
Vamos criar uma classeJDIExampleDebuggee com algumas variáveisString e instruçõesprintln:
public class JDIExampleDebuggee {
public static void main(String[] args) {
String jpda = "Java Platform Debugger Architecture";
System.out.println("Hi Everyone, Welcome to " + jpda); // add a break point here
String jdi = "Java Debug Interface"; // add a break point here and also stepping in here
String text = "Today, we'll dive into " + jdi;
System.out.println(text);
}
}
Então, vamos escrever um programa de depuração.
Vamos criar uma classeJDIExampleDebugger com propriedades para manter o programa de depuração (debugClass) e números de linha para pontos de interrupção (breakPointLines):
public class JDIExampleDebugger {
private Class debugClass;
private int[] breakPointLines;
// getters and setters
}
4.1. LaunchingConnector
Inicialmente, um depurador requer um conector para estabelecer uma conexão com a Máquina Virtual (VM) de destino.
Em seguida, precisaremos definir o debuggee como o argumentomain do conector. Por fim, o conector deve iniciar a VM para depuração.
Para fazer isso, JDI fornece uma classeBootstrap que fornece uma instância deLaunchingConnector. OLaunchingConnector fornece um mapa dedefault arguments, no qual podemos definir o argumentomain.
Portanto, vamos adicionar o métodoconnectAndLaunchVM à classeJDIDebuggerExample:
public VirtualMachine connectAndLaunchVM() throws Exception {
LaunchingConnector launchingConnector = Bootstrap.virtualMachineManager()
.defaultConnector();
Map arguments = launchingConnector.defaultArguments();
arguments.get("main").setValue(debugClass.getName());
return launchingConnector.launch(arguments);
}
Agora, vamos adicionar o métodomain à classeJDIDebuggerExample para depurar oJDIExampleDebuggee:
public static void main(String[] args) throws Exception {
JDIExampleDebugger debuggerInstance = new JDIExampleDebugger();
debuggerInstance.setDebugClass(JDIExampleDebuggee.class);
int[] breakPoints = {6, 9};
debuggerInstance.setBreakPointLines(breakPoints);
VirtualMachine vm = null;
try {
vm = debuggerInstance.connectAndLaunchVM();
vm.resume();
} catch(Exception e) {
e.printStackTrace();
}
}
Vamos compilar ambas as nossas classes,JDIExampleDebuggee (depurado) eJDIExampleDebugger (depurador):
javac -g -cp "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/lib/tools.jar"
com/example/jdi/*.java
Vamos discutir o comandojavac usado aqui, em detalhes.
The -g option generates all the debugging information sem o qual, podemos verAbsentInformationException.
And -cp will add the tools.jar in the classpath to compile the classes. **
All JDI libraries are available under tools.jar of the JDK. Portanto, certifique-se de adicionartools.jar no caminho de classe tanto na compilação quanto na execução.
É isso, agora estamos prontos para executar nosso depurador personalizadoJDIExampleDebugger:
java -cp "/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/lib/tools.jar:."
JDIExampleDebugger
Note o ":." comtools.jar. Isso acrescentarátools.jar ao caminho de classe para o tempo de execução atual (use “;.” no Windows).
4.2. Bootstrap eClassPrepareRequest
Executar o programa de depuração aqui não dará resultados, pois não preparamos a classe para depuração e definimos os pontos de interrupção.
A classeVirtualMachine tem o métodoeventRequestManager para criar várias solicitações comoClassPrepareRequest,BreakpointRequest eStepEventRequest.
Então, vamos adicionar o métodoenableClassPrepareRequest à classeJDIExampleDebugger.
Isso filtrará a classeJDIExampleDebuggee e habilitará oClassPrepareRequest:
public void enableClassPrepareRequest(VirtualMachine vm) {
ClassPrepareRequest classPrepareRequest = vm.eventRequestManager().createClassPrepareRequest();
classPrepareRequest.addClassFilter(debugClass.getName());
classPrepareRequest.enable();
}
4.3. ClassPrepareEvent eBreakpointRequest
Assim queClassPrepareRequest para a classeJDIExampleDebuggee for habilitado, a fila de eventos da VM começará a ter instâncias deClassPrepareEvent.
UsandoClassPrepareEvent,, podemos obter a localização para definir um ponto de interrupção e criar umBreakPointRequest.
Para fazer isso, vamos adicionar o métodosetBreakPoints à classeJDIExampleDebugger:
public void setBreakPoints(VirtualMachine vm, ClassPrepareEvent event) throws AbsentInformationException {
ClassType classType = (ClassType) event.referenceType();
for(int lineNumber: breakPointLines) {
Location location = classType.locationsOfLine(lineNumber).get(0);
BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest(location);
bpReq.enable();
}
}
4.4. BreakPointEvent eStackFrame
Até agora, preparamos a classe para depuração e definimos os pontos de interrupção. Agora, precisamos pegar oBreakPointEvente exibir as variáveis.
JDI fornece a classeStackFrame, para obter a lista de todas as variáveis visíveis do depurado.
Portanto, vamos adicionar o métododisplayVariables à classeJDIExampleDebugger:
public void displayVariables(LocatableEvent event) throws IncompatibleThreadStateException,
AbsentInformationException {
StackFrame stackFrame = event.thread().frame(0);
if(stackFrame.location().toString().contains(debugClass.getName())) {
Map visibleVariables = stackFrame
.getValues(stackFrame.visibleVariables());
System.out.println("Variables at " + stackFrame.location().toString() + " > ");
for (Map.Entry entry : visibleVariables.entrySet()) {
System.out.println(entry.getKey().name() + " = " + entry.getValue());
}
}
}
5. Destino de depuração
Nesta etapa, tudo o que precisamos é atualizar o métodomain deJDIExampleDebugger para iniciar a depuração.
Portanto, usaremos os métodos já discutidos comoenableClassPrepareRequest,setBreakPoints edisplayVariables:
try {
vm = debuggerInstance.connectAndLaunchVM();
debuggerInstance.enableClassPrepareRequest(vm);
EventSet eventSet = null;
while ((eventSet = vm.eventQueue().remove()) != null) {
for (Event event : eventSet) {
if (event instanceof ClassPrepareEvent) {
debuggerInstance.setBreakPoints(vm, (ClassPrepareEvent)event);
}
if (event instanceof BreakpointEvent) {
debuggerInstance.displayVariables((BreakpointEvent) event);
}
vm.resume();
}
}
} catch (VMDisconnectedException e) {
System.out.println("Virtual Machine is disconnected.");
} catch (Exception e) {
e.printStackTrace();
}
Agora, em primeiro lugar, vamos compilar a classeJDIDebuggerExample novamente com o comandojavac já discutido.
E por último, vamos executar o programa depurador junto com todas as alterações para ver a saída:
Variables at com.example.jdi.JDIExampleDebuggee:6 >
args = instance of java.lang.String[0] (id=93)
Variables at com.example.jdi.JDIExampleDebuggee:9 >
jpda = "Java Platform Debugger Architecture"
args = instance of java.lang.String[0] (id=93)
Virtual Machine is disconnected.
Viva! Nós depuramos com sucesso a classeJDIExampleDebuggee. Ao mesmo tempo, exibimos os valores das variáveis nas localizações dos pontos de interrupção (linha número 6 e 9).
Portanto, nosso depurador personalizado está pronto.
5.1. StepRequest
Debugging also requires stepping through the code and checking the state of the variables at subsequent steps. Portanto, criaremos uma solicitação de etapa no ponto de interrupção.
Ao criar a instância deStepRequest,, devemos fornecer o tamanho e a profundidade da etapa. Vamos definirSTEP_LINE eSTEP_OVER respectivamente.
Vamos escrever um método para ativar a solicitação de etapa.
Para simplificar, vamos começar a pisar no último ponto de interrupção (linha número 9):
public void enableStepRequest(VirtualMachine vm, BreakpointEvent event) {
// enable step request for last break point
if (event.location().toString().
contains(debugClass.getName() + ":" + breakPointLines[breakPointLines.length-1])) {
StepRequest stepRequest = vm.eventRequestManager()
.createStepRequest(event.thread(), StepRequest.STEP_LINE, StepRequest.STEP_OVER);
stepRequest.enable();
}
}
Agora, podemos atualizar o métodomain doJDIExampleDebugger, para habilitar a solicitação de etapa quando for umBreakPointEvent:
if (event instanceof BreakpointEvent) {
debuggerInstance.enableStepRequest(vm, (BreakpointEvent)event);
}
5.2. StepEvent
Semelhante aoBreakPointEvent, também podemos exibir as variáveis emStepEvent.
Vamos atualizar o métodomain de acordo:
if (event instanceof StepEvent) {
debuggerInstance.displayVariables((StepEvent) event);
}
Por fim, vamos executar o depurador para ver o estado das variáveis enquanto percorremos o código:
Variables at com.example.jdi.JDIExampleDebuggee:6 >
args = instance of java.lang.String[0] (id=93)
Variables at com.example.jdi.JDIExampleDebuggee:9 >
args = instance of java.lang.String[0] (id=93)
jpda = "Java Platform Debugger Architecture"
Variables at com.example.jdi.JDIExampleDebuggee:10 >
args = instance of java.lang.String[0] (id=93)
jpda = "Java Platform Debugger Architecture"
jdi = "Java Debug Interface"
Variables at com.example.jdi.JDIExampleDebuggee:11 >
args = instance of java.lang.String[0] (id=93)
jpda = "Java Platform Debugger Architecture"
jdi = "Java Debug Interface"
text = "Today, we'll dive into Java Debug Interface"
Variables at com.example.jdi.JDIExampleDebuggee:12 >
args = instance of java.lang.String[0] (id=93)
jpda = "Java Platform Debugger Architecture"
jdi = "Java Debug Interface"
text = "Today, we'll dive into Java Debug Interface"
Virtual Machine is disconnected.
Se compararmos a saída, perceberemos que o depurador entrou na linha número 9 e exibe as variáveis em todas as etapas subsequentes.
6. Saída de execução de leitura
Podemos notar que as declaraçõesprintln da classeJDIExampleDebuggee não fizeram parte da saída do depurador.
De acordo com a documentação JDI, se iniciarmos a VM por meio deLaunchingConnector,, sua saída e os fluxos de erro devem ser lidos pelo objetoProcess.
Portanto, vamos adicioná-lo à cláusulafinally do nosso métodomain:
finally {
InputStreamReader reader = new InputStreamReader(vm.process().getInputStream());
OutputStreamWriter writer = new OutputStreamWriter(System.out);
char[] buf = new char[512];
reader.read(buf);
writer.write(buf);
writer.flush();
}
Agora, a execução do programa de depuração também adicionará as instruçõesprintln da classeJDIExampleDebuggee à saída de depuração:
Hi Everyone, Welcome to Java Platform Debugger Architecture
Today, we'll dive into Java Debug Interface
7. Conclusão
Neste artigo, exploramos a API Java Debug Interface (JDI) disponível na Java Platform Debugger Architecture (JPDA).
Ao longo do caminho, criamos um depurador personalizado utilizando as interfaces úteis fornecidas pelo JDI. Ao mesmo tempo, também adicionamos a capacidade de revisão ao depurador.
Como esta foi apenas uma introdução ao JDI, é recomendável examinar as implementações de outras interfaces disponíveis emJDI API.
Como de costume, todas as implementações de código estão disponíveisover on GitHub.