Статьи

JDI: три способа подключения к процессу Java

Если вы посмотрели мои последние сообщения, вы знаете, что я работаю над плагином для VisualVM , очень полезным инструментом, поставляемым с JDK. В одном примере я показал, как подключиться к ожидающему приложению Java с помощью AttachingConnector на основе сокетов. В то время я говорил, что существует два основных способа подключения к процессу с помощью JDI — через разделяемую память и через сокет.

Оказывается, есть «третий путь». Ниже приведен пример того, почему этот способ полезен и почему он был предоставлен.

Когда я в последний раз писал программы JDI (на Java 5), ​​я заметил, что мое целевое приложение запустится и выведет (на стандартный вывод) порт, на котором оно слушает, как показано ниже:

Listening for transport dt_socket at address: 55779

В Java 5, если вы отсоединили отладчик от этого процесса, вы бы получили другую строку для stdout в консоли цели, например:

Listening for transport dt_socket at address: 55779

и это будет продолжаться до тех пор, пока вы решите прикрепить и отсоединить и т. д.

В какой-то момент (и я не знаю, когда это начало происходить), порт, на котором слушает цель, начал меняться при каждом отключении внешнего отладчика. Если в Java 6 (я использую u20), вы неоднократно присоединяетесь к целевому процессу и отсоединяетесь от него, вы увидите следующее в консоли цели:

Listening for transport dt_socket at address: 55837
ERROR: transport error 202: recv error: Connection reset by peer
Listening for transport dt_socket at address: 55844
ERROR: transport error 202: recv error: Connection reset by peer
Listening for transport dt_socket at address: 55846
ERROR: transport error 202: recv error: Connection reset by peer
Listening for transport dt_socket at address: 55911

Если вы пишете приложение, которое подключается с использованием порта отладки, при каждом подключении вам необходимо выяснить, какой порт использует целевой объект. Эта информация не доступна из самого процесса; другими словами, вы должны играть в обычную неприятную игру захвата вывода консоли, чтобы знать, что это за порт. Даже если вы укажете порт при целевом запуске, вам все равно нужно получить значение.

Вы все еще можете найти исходный запрос на присоединение функции к процессу по его идентификатору процесса, если будете искать в старых отчетах об ошибках Java. Короче говоря: был создан новый AttachingConnector, который подключается с помощью PID. Как вы знаете, иногда найти PID процесса тоже не очень весело. В моем случае, однако, я пишу плагин для VisualVM, и одна вещь, которую вы получаете бесплатно, когда вы делаете это, это API-интерфейс Visual VM , который, как вы можете ожидать, включает вызовы для получения PID. Поэтому моя цель — использовать этот новый коннектор в моем плагине VisualVM, и я подумал, что будет полезно, если я поделюсь подробностями.

Я адаптировал свою тестовую программу из предыдущего поста так, чтобы теперь она выводила детали каждого AttachingConnector; измененный фрагмент кода показан здесь:

List<AttachingConnector> attachingConnectors = vmMgr.attachingConnectors();
for (AttachingConnector ac: attachingConnectors)
{
  Map paramsMap = ac.defaultArguments();
  Iterator keyIter = paramsMap.keySet().iterator();
  System.out.println("AttachingConnector:  '" + ac.getClass().getName() + "'");
  System.out.println("  name: '" + ac.name() + "'");
  System.out.println("  description: '" + ac.description() + "'");
  System.out.println("  transport name: '" + ac.transport().name() + "'");
  System.out.println("  default arguments:");
  while (keyIter.hasNext())
  {
    String nextKey = keyIter.next();
    System.out.println("    key: '" + nextKey + "'; value: '" + paramsMap.get(nextKey) + "'");
  }
}

Вывод этого кода показан ниже:

AttachingConnector:  'com.sun.tools.jdi.SocketAttachingConnector'
 name: 'com.sun.jdi.SocketAttach'
 description: 'Attaches by socket to other VMs'
 transport name: 'dt_socket'
 default arguments:
   key: 'timeout'; value: 'timeout='
   key: 'hostname'; value: 'hostname=AdamsResearch'
   key: 'port'; value: 'port='
AttachingConnector:  'com.sun.tools.jdi.SharedMemoryAttachingConnector'
 name: 'com.sun.jdi.SharedMemoryAttach'
 description: 'Attaches by shared memory to other VMs'
 transport name: 'dt_shmem'
 default arguments:
   key: 'timeout'; value: 'timeout='
   key: 'name'; value: 'name='
AttachingConnector:  'com.sun.tools.jdi.ProcessAttachingConnector'
 name: 'com.sun.jdi.ProcessAttach'
 description: 'Attaches to debuggee by process-id (pid)'
 transport name: 'local'
 default arguments:
   key: 'pid'; value: 'pid='
   key: 'timeout'; value: 'timeout='

Пара вещей, которые я раньше не замечал, состоит в том, что соединитель на основе сокетов поставляется с аргументом имени хоста, предварительно установленным на имя хоста моей машины, и что все три соединителя имеют аргумент времени ожидания по умолчанию. Первое наблюдение поднимает интересный момент: если вы используете локальный PID-коннектор, помните, что вы будете подключаться только к процессам на хосте вашего отладчика.

Я изменил свою тестовую программу, чтобы использовать локальный разъем, и она работает как раньше! Ну нет, на самом деле это не так. Вот что я сейчас получаю:

java.lang.UnsatisfiedLinkError: no attach in java.library.path
Exception in thread "main" java.io.IOException: no providers installed
at com.sun.tools.jdi.ProcessAttachingConnector.attach(ProcessAttachingConnector.java:86)
at com.adamsresearch.jdiDemo.JDIDemo.main(JDIDemo.java:70)

Означает ли это, что локальный разъем не совсем готов к использованию? Нет, но я был сожжен той же проблемойэто преследует многих других (прокрутите страницу вниз — проблема была найдена читателем этого поста и частично решена другим читателем этого поста). Я работаю на платформе Windows, и когда вы делаете это, вы должны быть немного осторожны; ->. В этом случае проблема вызвана тем, что: 1) используется интерпретатор java, указанный в системном пути, и 2) нет уверенности в том, что путь указывает непосредственно на каталог JDK или JRE. Исполняемый файл будет искать по пути относительно себя необходимые библиотеки, и когда Windows копирует исполняемый файл Java в C: \ Windows \ system32 (или аналогичный) — и если вы используете этот исполняемый файл — этот относительный путь нарушается. Я считаю, что это истинная проблема, в отличие от описанной в комментариях к вышеупомянутому посту, где проводится различие между использованием Java JRE и JDK JDK. Я неНе думаю, что это проблема. Например, ниже приведены результаты моего теста присоединения в 3 различных сценариях:

  1. Используя java из моего пути, первое попадание которого происходит из C: \ Windows \ system32:
    java -cp c:\jdk1.6.0_20\lib\tools.jar;. com.adamsresearch.jdiDemo.JDIDemo 10816 863 fileName
    ...
    java.lang.UnsatisfiedLinkError: no attach in java.library.path
    Exception in thread "main" java.io.IOException: no providers installed
     at com.sun.tools.jdi.ProcessAttachingConnector.attach(ProcessAttachingConnector.java:86)
     at com.adamsresearch.jdiDemo.JDIDemo.main(JDIDemo.java:70)
  2. Используя полный путь к JRE bin Java:
    c:\jdk1.6.0_20\jre\bin\java -cp c:\jdk1.6.0_20\lib\tools.jar;. com.adamsresearch.jdiDemo.JDIDemo 10816 863 fileName
    ...
    Attached to process 'Java HotSpot(TM) 64-Bit Server VM'
  3. Используя полный путь к JDK bin Java:
    c:\jdk1.6.0_20\bin\java -cp c:\jdk1.6.0_20\lib\tools.jar;. com.adamsresearch.jdiDemo.JDIDemo 10816 863 fileName
    ...
    Attached to process 'Java HotSpot(TM) 64-Bit Server VM'

Как вы можете видеть, вышеприведенное, кажется, подтверждает мою теорию о том, что причиной проблемы была не JRE против JDK, а неконтролируемое размещение исполняемого файла java в «обычном» каталоге двоичных файлов Windows. Этой публикации уже несколько лет, поэтому возможно, что в то время необходимые библиотеки JDI фактически не были включены в JRE, но ясно, что сегодня вы увидите то же исключение, если будете использовать исполняемый файл java, найденный в Windows. каталог бинарных файлов по умолчанию.

Теперь, если я запускаю свое приложение JDI для своей утилиты JarView, ища AttachingConnector в каталоге установки JDK, я получаю следующий вывод:

Breakpoint at line 863:
fileName = 'AttachingConnector.class'
Breakpoint at line 863:
fileName = 'GenericAttachingConnector$1.class'
Breakpoint at line 863:
fileName = 'GenericAttachingConnector.class'
Breakpoint at line 863:
fileName = 'ProcessAttachingConnector$1.class'
Breakpoint at line 863:
fileName = 'ProcessAttachingConnector$2.class'
Breakpoint at line 863:
fileName = 'ProcessAttachingConnector.class'
Breakpoint at line 863:
fileName = 'SharedMemoryAttachingConnector$1.class'
Breakpoint at line 863:
fileName = 'SharedMemoryAttachingConnector.class'
Breakpoint at line 863:
fileName = 'SocketAttachingConnector$1.class'
Breakpoint at line 863:
fileName = 'SocketAttachingConnector.class'

и так сделали то, что я намеревался сделать, а именно: 1) отладка-присоединение по идентификатору процесса и 2) перебрать неизбежные ошибки и поделиться решениями. Надеюсь, это будет полезно и вам.

Примечание: на самом деле, есть еще больше способов присоединиться к процессу Java. JPDA Connection and Invocation — это исчерпывающее руководство от Oracle. Если вы собираетесь писать отладчики, вы не ошибетесь, сначала прочитав эту страницу.

 От http://wayne-adams.blogspot.com/2011/10/jdi-three-ways-to-attach-to-java.html