Java Debugging Interface - Troubleshooting

Teaxtor

Grünschnabel
Liebe Threader,
ich bin jetzt seit einer Weile dabei, mich mit dem JDI zu befassen, das in Java integriert ist. Mein Ziel ist es, einen kleinen eigenen Debugger zu bauen, den ich hinterher hoffentlich je nach Projekt modifizieren kann.. Das Grundgerüst steht auch schon:
Java:
class StateExaminer
{
  static boolean _connected = false;
  public StateExaminer (String sClassName)
  {
    EventQueue eventQueue;
    LaunchingConnector lconnector       = Bootstrap.virtualMachineManager ().defaultConnector ();
    Map<String, Argument> launchingArgs = lconnector.defaultArguments ();
    VirtualMachine vm;
    
    Argument mainArg = launchingArgs.get ("main");
    mainArg.setValue (sClassName);
    
    for (String sKey: launchingArgs.keySet ())
    {
      System.out.println (sKey + ": " + launchingArgs.get (sKey));
    }    
    
    try
    {
      vm = lconnector.launch (launchingArgs);
      vm.setDebugTraceMode (VirtualMachine.TRACE_EVENTS);
      _connected = true;
      
      new RedirectedStream (vm.process ().getErrorStream ());
      new RedirectedStream (vm.process ().getInputStream ());
      
      EventRequestManager eventManager = vm.eventRequestManager (); 
      
      ClassPrepareRequest classPrepare = eventManager.createClassPrepareRequest ();
      classPrepare.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
      classPrepare.enable ();
      
      MethodExitRequest methodExit = eventManager.createMethodExitRequest ();
      methodExit.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
      methodExit.enable ();
      
      ExceptionRequest exception = eventManager.createExceptionRequest (null, true, true);
      exception.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD);
      exception.enable ();
      
      eventQueue = vm.eventQueue ();
      vm.resume ();
      
      while (_connected)
      {        EventSet eventSet = eventQueue.remove ();
                for (EventIterator it = eventSet.eventIterator (); it.hasNext ();)
        {
          Event event = it.next ();
          
          if (event instanceof MethodExitEvent)
          {
            // MethodExitEvent meEvt = (MethodExitEvent) event;
            // Noch zu erweiternder Code
          }
          else if (event instanceof ClassPrepareEvent)
               {
                 ClassPrepareEvent cpEvt = (ClassPrepareEvent) event;
                 System.out.println ("Loading " + cpEvt.referenceType ().name ());
               }
               else if (event instanceof ExceptionEvent)
                    {
                      ExceptionEvent excEvt = (ExceptionEvent) event;
                      System.out.println ("Error: " + excEvt);
                    }
                    else if (event instanceof VMDisconnectEvent)
                         {
                           System.out.println ("VM disconnected");
                           _connected = false;
                         } 
        }
        
        if (_connected)
        {
          eventSet.resume ();
        }
      }
    }
    catch (Exception exc)
    {
      System.out.println (exc);
    }
  }
  
  /**
   * Liefert den Status der Verbindung zwischen Debugger-JVM und Mirror-JVM
   * @return
   */
  public static boolean getConnection ()
  {
    return _connected;
  }
}

Die benutzte Klasse RedirectedStream enthält Folgendes:
Java:
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

class RedirectedStream extends Thread
{
  InputStreamReader in;
  OutputStreamWriter out;
  public RedirectedStream (InputStream in)
  {
    super ();
    this.in = new InputStreamReader (in);
    out = new OutputStreamWriter (System.out);
    start ();
  } 

  public void run ()
  {
    while (StateExaminer.getConnection ())
    {
      char[] cBuf = new char[1024];
      try
      {
        while (in.read (cBuf) > 0)
        {
          out.write (cBuf);
        }
      }
      catch (IOException e)
      {
        System.out.println ("Could not read from/write to stream");
      }
    }
  }
}

Leider gibt mir das Programm, wenn ich es ausführe und die Datei "DebugDummy" übergebe (diese liegt im gleichen Verzeichnis wie StateExaminer.class), eine NoClassDefNotFoundException zurück, wenn die erzeugte Mirror-VM versucht, diese zu laden -.- Kann mir jemand helfen, den Fehler zu beheben?

Hier noch DebugDummy.class:
Java:
class DebugDummy
{
  public static void main (String[] args)
  {
    int foo = 42;
    String bar = " ist die Antwort:";
    
    System.out.println (foo + bar);
    
    for (int i = 0; i < 42; i++)
    {
      System.out.println (i + 1);
    }
  }
}
 
Zuletzt bearbeitet:
S

SE

Verwende für Java-Codes bitte Java-Code-Tags *siehe meine Signatur* da das Syntaxhighlightning die Lesbarkeit verbessert.
 

Teaxtor

Grünschnabel
Ok, jetzt habe ich das ganze selbst gelöst: das Problem, woran es IMO lag, war, dass die zu debuggende Klasse nicht im Klassenpfad der initialisierenden VM lag; zudem gab es keine Möglichkeit, diesen zu ändern. Daher habe ich jetzt statt "com.sun.jdi.CommandLineLaunch" den LaunchingConnector mit dem Namen "com.sun.jdi.RawCommandLineLaunch" benutzt, der hier diese Einstellungsmöglichkeiten besitzt.
Dazu habe ich den Code wie folgt geändert:

Java:
import java.io.File;
[..] // sind gegebenfalls angepasst worden
import com.sun.jdi.request.MethodExitRequest;

class StateExaminer
{
  static boolean _connected = false;
  
  /**
   * Konstruktor: Initialisiert den Debugger für die übergebene Klasse (definiert durch Klassenpfad und -name) und startet ihn
   * @param sClassPath
   * @param sClassName
   */
  public StateExaminer (String sClassPath, String sClassName)
  {
    EventQueue eventQueue;
    LaunchingConnector lconnector       = getLaunchingConnector ("com.sun.jdi.RawCommandLineLaunch");
    Map<String, Argument> launchingArgs = lconnector.defaultArguments ();
    PathSearchingVirtualMachine vm;    
    
    Argument commandArg = launchingArgs.get ("command");
    commandArg.setValue ("java " + "-Xrunjdwp:transport=dt_shmem,address=irgendeinstring,suspend=y" + " -cp " + sClassPath  + " " + sClassName);
    
    Argument addressArg = launchingArgs.get ("address");
    addressArg.setValue ("irgendeinstring");
    
    for (String sKey: launchingArgs.keySet ())
    {
      System.out.println (sKey + ": " + launchingArgs.get (sKey));
    }
    
    try
    {
      [...] hat sich nichts geändert
    }
  }
  
  /**
   * Liefert den mit sIdentifier bezeichneten LaunchingConnector - 
   * entweder com.sun.jdi.CommandLineLaunch oder com.sun.jdi.RawCommandLineLaunch
   * @param sIdentifier Bezeichner des Connectors
   * @return
   */
  private LaunchingConnector getLaunchingConnector (String sIdentifier)
  {
    LaunchingConnector lcRet = null;
    List<LaunchingConnector> connectors = Bootstrap.virtualMachineManager ().launchingConnectors ();
    
    for (LaunchingConnector connector: connectors)
    {
      if (connector.name ().equals (sIdentifier))
      {
        lcRet = connector;
      }
    }
    
    return lcRet;
  }
  
  /**
   * Liefert den Status der Verbindung zwischen Debugger-JVM und Target-JVM
   * @return
   */
  public static boolean getConnection ()
  {
    return _connected;
  }

Zur Erläuterung: Bei dem RawCommandLineLauncher muss das gesamte Argument, mit dem man die zu debuggende Klasse in der Konsole debuggen würde, als "command" gesetzt werden..
-> der Vorteil: über die Option -cp <Path> kann ein BaseDirectory definiert werden, von dem aus dann versucht wird, die zu ladende Klasse zu finden.

Zum Argument:
Java:
"java " + "-Xrunjdwp:transport=dt_shmem,address=irgendeinstring,suspend=y" + " -cp " + sClassPath  + " " + sClassName
"java": bezeichnet den zu nutzenden Java-Interpreter.. es gibt glaub ich noch andere, aber mir fallen die grad nicht mehr ein

"-Xrunjdwp:[...]": hier wird der interpreter angewiesen, die verbindung zur VM über das Java Debugging Wire Protocol mit den darauffolgenden Optionen herzustellen; die Optionen wären

"transport=dt_shmem": Der Informationsaustausch zwischen Debugger-VM und Target-VM wird über Shared Memory (nur unter Windows) realisiert; eine andere Möglichkeit wäre "dt_socket", genaueres dazu lest ihr euch bitte in der JDI Spezifikation von Oracle durch

"adress=irgendeinstring": dies ist die Referenzadresse der Target-VM, über die später die Debugger-VM versuchen wird, eine Verbindung aufzubauen; bei der Shared Memory-Variante ist es egal, welche Bezeichnung ihr hier angebt (es sollte eine sinnvolle sein :))

"suspend=y": Definiert, ob die Target-VM nach dem Starten angehalten werden soll oder ob sie mit dem Ausführen er geladenen Klasse anfangen soll; bei "y" wird sie angehalten und muss dann mit
Java:
vm.resume()
wieder gestartet werden - siehe Quelltext


Letzlich muss noch das "address"-Argument des RawCommandLineLaunch-Connectors auf die im Argument benutzte Addresse gesetzt werden - siehe Quelltext