Страницы

Поиск по вопросам

пятница, 24 января 2020 г.

Как правильно сделать удалённый вызов используя средства JPDA

#java


Есть приложение, которое средствами JPDA коннектиться к другому (на котором настроен
debug: -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8858).

Я получаю инстанс VirtualMachine удаленного приложения используя SocketAttachingConnector
(подключаясь к 8858-порту).

На всякий случай выставляю на объекте виртуальной машины setDebugTraceMode(VirtualMachine.TRACE_EVENTS)
(пока не понял, влияет ли это ещё на что либо, кроме дебага в стандартных sout/serr).

Сам инстанс VirtualMachine содержит действительно актуальную информацию о удаленном
приложении (потоки, классы и тп), однако я не могу сделать удаленный вызов метода на
удаленном объекте (как статические так и обычные методы).

Вот элементарный код, которым я пытаюсь это сделать:

    List list = vm.allClasses().stream().filter(str -> str.name().equals("java.lang.Runtime")).collect(Collectors.toList());
    ReferenceType runtime = list.get(0);
    ObjectReference or = runtime.instances(0).get(0);
    Method method = runtime.allMethods().stream().filter(m -> m.name().contains("freeMemory")).collect(Collectors.toList()).get(0);
    ThreadReference th = vm.allThreads().stream().filter(thread -> thread.name().contains("main")).collect(Collectors.toList()).get(0);
    System.out.println(or.invokeMethod(th, method1, new ArrayList<>(), ObjectReference.INVOKE_SINGLE_THREADED));



В коде получаю инстанс Runtime - объект есть, на первый взгляд всё в
порядке 
Беру у него ссылку на метод freeMemory() - тоже все
выглядит хорошо 
Получаю ссылку на main-поток - все хорошо 
На объекте or (Runtime удаленного приложения) вызываю метод method - получаю ошибку
Exception in thread "main" com.sun.jdi.IncompatibleThreadStateException


При этом получение локальный переменных (даже private) проходит на ура.

Вопросы:


В каком стейте должен находиться удаленный поток, чтобы в нем можно
было сделать удаленный вызов? В документации ObjectReference
есть указание, что поток должен быть в suspend, но не обычном, а
вызванным каким-либо ивентом в этом потоке. Что это за ивенты и как
ими управлять? 
Как правильно организовать удаленный вызов средствами
JDI (некий аналог Watches в IntelliJ IDEA).
Есть ли у кого-нибудь материал по данной теме. В стандартной комплектации JDK есть
парочка примеров, но их совершенно недостаточно для того, чтобы ознакомиться сданной
платформой. Более-менее внятных статей тоже не нашел.

    


Ответы

Ответ 1



EDIT: В каком стейте должен находиться удаленный поток, чтобы в нем можно было сделать удаленный вызов? В документации ObjectReference есть указание, что поток должен быть в suspend, но не обычном, а вызванным каким-либо ивентом в этом потоке. Что это за ивенты и как ими управлять? Ниже фрагмент кода, использующего удалённые треды и ивенты: private VirtualMachine launchTarget( String command ) throws IOException { final LaunchingConnector connector = findLaunchingConnector(); final Map arguments = connectorArguments( connector, command ); try { return connector.launch( arguments ); } catch ( IOException e ) { throw new Error( "Unable to launch target VM: " + e ); } catch ( IllegalConnectorArgumentsException e ) { throw new Error( "Internal error: " + e ); } catch ( VMStartException e ) { throw new Error( "Target VM failed to initialize: " + e.getMessage() ); } } private LaunchingConnector findLaunchingConnector() { final List connectors = Bootstrap.virtualMachineManager().allConnectors(); for ( Connector connector : connectors ) { if ( connector.name().equals( "com.sun.jdi.CommandLineLaunch" ) ) { return ( LaunchingConnector ) connector; } } throw new Error( "No launching connector" ); } private Map connectorArguments( LaunchingConnector connector, String mainArgs ) { final Map arguments = connector.defaultArguments(); final Argument mainArg = arguments.get( "main" ); final Argument optionArg = arguments.get( "options" ); if ( mainArg == null ) { throw new Error( "Bad launching connector" ); } if ( optionArg == null ) { throw new Error( "Bad launching connector" ); } mainArg.setValue( mainArgs ); optionArg.setValue( "-classic" ); return arguments; } private void setEventRequests() { final EventRequestManager mgr = vm.eventRequestManager(); final ExceptionRequest exceptionRequest = mgr.createExceptionRequest( null, true, true ); final MethodEntryRequest methodEntryRequest = mgr.createMethodEntryRequest(); final MethodExitRequest methodExitRequest = mgr.createMethodExitRequest(); final ThreadStartRequest threadStartRequest = mgr.createThreadStartRequest(); final ThreadDeathRequest threadDeathRequest = mgr.createThreadDeathRequest(); final ClassPrepareRequest classPrepareRequest = mgr.createClassPrepareRequest(); for ( String include : this.includes ) { methodEntryRequest.addClassFilter( include ); methodExitRequest.addClassFilter( include ); classPrepareRequest.addClassFilter( include ); } for ( String exclude : this.excludes ) { methodEntryRequest.addClassExclusionFilter( exclude ); methodExitRequest.addClassExclusionFilter( exclude ); classPrepareRequest.addClassExclusionFilter( exclude ); } exceptionRequest.setSuspendPolicy( EventRequest.SUSPEND_ALL ); methodEntryRequest.setSuspendPolicy( EventRequest.SUSPEND_ALL ); methodExitRequest.setSuspendPolicy( EventRequest.SUSPEND_ALL ); threadStartRequest.setSuspendPolicy( EventRequest.SUSPEND_ALL ); threadDeathRequest.setSuspendPolicy( EventRequest.SUSPEND_ALL ); classPrepareRequest.setSuspendPolicy( EventRequest.SUSPEND_ALL ); exceptionRequest.enable(); methodEntryRequest.enable(); methodExitRequest.enable(); threadStartRequest.enable(); threadDeathRequest.enable(); classPrepareRequest.enable(); setupJdiEventListener(); } public void run() { this.writer = new PrintWriter( System.out ); // initTree( ); try { launch(); buildProcessTree(); } catch ( IOException e ) { e.printStackTrace(); } } private void setupJdiEventListener() { reader = new EventReader( "EventReader", vm.eventQueue() ); reader.addEventListener( this ); } protected void buildProcessTree() { try { boolean goOn = true; EventQueue queue = vm.eventQueue(); EventSet eventSet; EventIterator it; while ( !stop && this.connected && goOn ) { try { eventSet = queue.remove(); it = eventSet.eventIterator(); while ( it.hasNext() ) { } eventSet.resume(); } catch ( InterruptedException exc ) { System.out.println( "Logic.Converter.EventThread -> run -> InterruptedException" ); } catch ( VMDisconnectedException discExc ) { // this.handleDisconnectedException(); System.out.println( "Logic.Converter.EventThread -> run -> VMDisconnectedException" ); break; } } if ( stop ) { this.vm.exit( 0 ); } } catch ( Exception e ) { e.printStackTrace(); System.out.println( "Logic.Converter.EventThread -> run -> Exception" ); } } AbstractReader.java */** * An abstract reader that continuously reads. */ abstract public class AbstractReader { protected String name; protected Thread readerThread; protected boolean isStopping = false; /** * Constructor * @param name */ public AbstractReader( String name ) { this.name = name; } /** * Continuously reads. Note that if the read involves waiting * it can be interrupted and a InterruptedException will be thrown. */ abstract protected void readerLoop(); /** * Start the thread that reads events. * */ public void start() { readerThread = new Thread( new Runnable() { public void run() { readerLoop(); } }, name ); readerThread.setDaemon( true ); readerThread.start(); } /** * Tells the reader loop that it should stop. */ public void stop() { isStopping = true; if ( readerThread != null ) { readerThread.interrupt(); } } } EventReader.java /** * An event reader that continuously reads events coming from the VM * and dispatch them to the registered listeners. * */ public class EventReader extends AbstractReader { private EventQueue eventQueue; private Vector jdiEventListeners = new Vector(); /** * Constructor * @param name * @param queue */ public EventReader( String name, EventQueue queue ) { super( name ); eventQueue = queue; } /** * Registers the given event listener. * @param listener */ public synchronized void addEventListener( JdiEventListener listener ) { jdiEventListeners.addElement( listener ); } /** * Dispatches the given event to the given listener. * Returns whether the VM should be resumed. */ private boolean dispatch( Event event, JdiEventListener listener ) { if ( event instanceof AccessWatchpointEvent ) { return listener.accessWatchpoint( ( AccessWatchpointEvent ) event ); } if ( event instanceof BreakpointEvent ) { return listener.breakpoint( ( BreakpointEvent ) event ); } if ( event instanceof ClassPrepareEvent ) { return listener.classPrepare( ( ClassPrepareEvent ) event ); } if ( event instanceof ClassUnloadEvent ) { return listener.classUnload( ( ClassUnloadEvent ) event ); } if ( event instanceof ExceptionEvent ) { return listener.exception( ( ExceptionEvent ) event ); } if ( event instanceof MethodEntryEvent ) { return listener.methodEntry( ( MethodEntryEvent ) event ); } if ( event instanceof MethodExitEvent ) { return listener.methodExit( ( MethodExitEvent ) event ); } if ( event instanceof ModificationWatchpointEvent ) { return listener.modificationWatchpoint( ( ModificationWatchpointEvent ) event ); } if ( event instanceof StepEvent ) { return listener.step( ( StepEvent ) event ); } if ( event instanceof ThreadDeathEvent ) { return listener.threadDeath( ( ThreadDeathEvent ) event ); } if ( event instanceof ThreadStartEvent ) { return listener.threadStart( ( ThreadStartEvent ) event ); } if ( event instanceof VMDisconnectEvent ) { return listener.vmDisconnect( ( VMDisconnectEvent ) event ); } if ( event instanceof VMDeathEvent ) { return listener.vmDeath( ( VMDeathEvent ) event ); } return true; } /** * Continuously reads events that are coming from the event queue. */ protected void readerLoop() { while ( !isStopping ) { try { if ( !isStopping ) { // Get the next event EventSet eventSet = eventQueue.remove(); // Dispatch the events boolean shouldGo = true; EventIterator iterator = eventSet.eventIterator(); while ( iterator.hasNext() ) { Event event = iterator.nextEvent(); for ( int i = 0; i < jdiEventListeners.size(); i++ ) { JdiEventListener listener = jdiEventListeners.elementAt( i ); shouldGo = shouldGo & dispatch( event, listener ); } if ( event instanceof VMDeathEvent ) { stop(); } } // Let the VM go if it was interrupted if ( ( !isStopping ) && ( eventSet != null ) && ( eventSet.suspendPolicy() == EventRequest.SUSPEND_ALL ) && shouldGo ) { synchronized ( this ) { eventQueue.virtualMachine().resume(); } } } } catch ( InterruptedException e ) { if ( !isStopping ) { System.out.println( "Event reader loop was interrupted" ); return; } } catch ( VMDisconnectedException e ) { return; } } } /** * De-registers the given event listener. * @param listener */ public synchronized void removeEventListener( JdiEventListener listener ) { jdiEventListeners.removeElement( listener ); } } JdiEventListener.java /** * An event listener that handles all kinds of event coming from the VM. */ public interface JdiEventListener { /** * Handles an access watchpoint event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean accessWatchpoint(AccessWatchpointEvent event); /** * Handles a breakpoint event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean breakpoint(BreakpointEvent event); /** * Handles a class prepare event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean classPrepare(ClassPrepareEvent event); /** * Handles a class unload event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean classUnload(ClassUnloadEvent event); /** * Handles an exception event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean exception(ExceptionEvent event); /** * Handles a method entry event * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean methodEntry(MethodEntryEvent event); /** * Handles a method exit event * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean methodExit(MethodExitEvent event); /** * Handles a modification watchpoint event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean modificationWatchpoint(ModificationWatchpointEvent event); /** * Handles a step event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean step(StepEvent event); /** * Handles a thread death event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean threadDeath(ThreadDeathEvent event); /** * Handles a thread start event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean threadStart(ThreadStartEvent event); /** * Handles a vm death event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean vmDeath(VMDeathEvent event); /** * Handles a vm disconnect event. * Returns whether the VM should be resumed if it was interrupted. * @param event * @return whether the VM should be resumed if it was interrupted. */ public boolean vmDisconnect(VMDisconnectEvent event); } JdiEventWaiter.java /** * Listen for a specific kind of event. */ public class JdiEventWaiter implements JdiEventListener { protected EventRequest request; protected boolean shouldGo; protected Event event; /** * Creates a new EventWaiter for the given request. Sets whether it * should let the VM go after it got the event. * @param request * @param shouldGo */ public JdiEventWaiter( EventRequest request, boolean shouldGo ) { this.request = request; this.shouldGo = shouldGo; } /** */ public boolean accessWatchpoint( AccessWatchpointEvent event ) { return handleEvent( event ); } /** */ public boolean methodEntry( MethodEntryEvent event ) { return handleEvent( event ); } /** */ public boolean methodExit( MethodExitEvent event ) { return handleEvent( event ); } /** */ public boolean breakpoint( BreakpointEvent event ) { return handleEvent( event ); } /** */ public boolean classPrepare( ClassPrepareEvent event ) { return handleEvent( event ); } /** */ public boolean classUnload( ClassUnloadEvent event ) { return handleEvent( event ); } /** */ public boolean exception( ExceptionEvent event ) { return handleEvent( event ); } /** * Handles an incoming event. * Returns whether the VM should be resumed if it was suspended. */ protected boolean handleEvent( Event event ) { if ( ( event.request() != null ) && ( event.request().equals( request ) ) ) { notifyEvent( event ); return shouldGo; } return true; } /** */ public boolean modificationWatchpoint( ModificationWatchpointEvent event ) { return handleEvent( event ); } /** * Notify any object that is waiting for an event. */ synchronized protected void notifyEvent( Event event ) { notify(); this.event = event; } /** */ public boolean step( StepEvent event ) { return handleEvent( event ); } /** */ public boolean threadDeath( ThreadDeathEvent event ) { return handleEvent( event ); } /** */ public boolean threadStart( ThreadStartEvent event ) { return handleEvent( event ); } /** */ public boolean vmDeath( VMDeathEvent event ) { if ( event == null ) { // This is the last event we can ever get an this was not the one we expected notifyEvent( null ); return true; } return handleEvent( event ); } /** */ public boolean vmDisconnect( VMDisconnectEvent event ) { return handleEvent( event ); } /** * Waits for the first event corresponding to this waiter's request. * @return if the vm should be restarted * @throws InterruptedException */ synchronized public Event waitEvent() throws InterruptedException { if ( event == null ) // If event didn't already come in { wait(); } Event result = event; event = null; return result; } /** * Waits for the first event corresponding to this waiter's request * for the given time (in ms). If it times out, return null. * @param time * @return if the vm should be restarted or not * @throws InterruptedException */ synchronized public Event waitEvent( long time ) throws InterruptedException { if ( event == null ) // If event didn't already come in { wait( time ); } Event result = event; event = null; return result; } } P.S. Имеет смысл не вызывать кучу удалённых методов, а просто запустить удаленное приложение, которое содержит и выполняет Ваш код естественным образом, т.е. для того примера, что Вы приводите, можно сделать следующее: Код, добавленный к удалённому приложению: public class A { public static long freeMemory(){ Runtime runtime = Runtime.getRuntime(); return runtime.freeMemory(); } } Алгоритм для Вашего приложения: Получить ReferenceType для класса A; Получить метод A.freeMemory(); Удалённо вызвать метод A.freeMemory();

Комментариев нет:

Отправить комментарий