Статьи

Изучение переменных в JDI

Как я упоминал в предыдущих статьях, я использую Java Debug Interface (JDI) для создания инструмента мониторинга процессов Java. Я возвращаюсь к своим прежним попыткам, предпринятым несколько лет назад, и, когда я разбираюсь в деталях, я публикую подробности в надежде, что они будут полезны кому-то еще. Иногда я задаюсь вопросом, кого бы действительно заинтересовал этот материал, но я заметил, что мои старые посты о JDI, DTrace и т. Д. Продолжают получать хиты каждую неделю, даже старые, так что я знаю, что вы там. Надеюсь, вы тоже найдете эту статью полезной.

Текущий статус.

На этом этапе разработки приложения я настроил запросы на точки останова и по мере поступления перебираю события. Вы вероятно сделали бы это в цикле, подобном следующему:

  EventQueue evtQueue = virtualMachine.eventQueue();
  EventSet evtSet = null;
  while (!stopRequested)
  {
      try
      {
          evtSet = evtQueue.remove();
          EventIterator evtIter = evtSet.eventIterator();
          while (evtIter.hasNext())
          {
          ...
          }
      }
      catch (Exception exc)
      {
          ...
      }
      finally
      {
          evtSet.resume();
      }
  }

В моем приложении я обрабатываю события отладчика в потоке, поэтому мне нужен способ, чтобы поток знал, должен ли он остановиться (это цель логического stopRequested ). Обратите внимание, что EventSet фактически содержит EventIterator, который может содержать несколько событий. Также обратите внимание, что очень важно убедиться, что resume () всегда вызывается в EventSet , независимо от каких-либо исключений. В противном случае вы можете получить несколько постоянно зависших потоков и перезапустить целевое приложение.

Проверка переменных

В точке останова вы, вероятно, захотите извлечь текущее состояние целевого приложения. В API JDI есть два места для поиска переменных — переменные экземпляра / класса и стек. Статические переменные и переменные экземпляра всегда можно перечислить, проверив ReferenceType класса — они всегда там, пока класс загружен, даже если приложение не запущено (вам, возможно, все еще придется находиться в точке останова, чтобы посмотреть их значения, конечно, но дело в том, что вы также можете увидеть, что такое статические переменные и переменные экземпляра класса, просто взглянув на его определение).

С другой стороны, переменные стека не определены, если вы не находитесь в стеке, где они определены (например, локальные переменные в методе). В JDI вы найдете эти два типа переменных в разных частях API, в местах, которые будут иметь смысл для вас, когда мы продолжим наше обсуждение. Вы видите то же разделение в отладчике. Например, в Eclipse, когда вы находитесь на точке останова и смотрите в окно «Переменные», вы увидите переменные класса / экземпляра в древовидной структуре в разделе « this », а все локальные (стековые) переменные перечислены равными « this » на уровне « this». «под своими местными именами.

В следующих разделах предполагается, что вы извлекли событие из EventIterator , проверили, что это BreakpointEvent , извлекли EventRequest из события и убедились , что это BreakpointRequest (это все классы JDI).

Проверка статических переменных и переменных экземпляра:

С учетом BreakpointEvent и имени переменной (определенной в спецификации запроса точки останова), как бы вы искали переменную в списке статических переменных и переменных экземпляра? Каждый BreakpointEvent имеет Location (получаемый с помощью метода location () ). В дополнение к обычной информации, которую вы ожидаете получить от объекта Location (имя источника, номер строки, имя метода и т. Д.), У Location есть ссылка на ReferenceType класса . ReferenceType имеет метод allFields () , который, как говорится в документации API, » Возвращает список , содержащий каждое поле объявленный в этом типе, и его суперклассы, реализованные интерфейсы и / или суперинтерфейсы. Все объявленные и унаследованные поля включены независимо от того, являются ли они скрытыми или многократно наследуемыми. «Именно то, что вы хотите, верно?

allFields () возвращает список . Тип поля JDI имеет поле имени (method: name () ), которое можно использовать при итерации по списку для поиска целевой переменной. После того, как вы получили правильное поле , вы получите его значение , снова обратившись к ReferenceType , например:

Value val = theReferenceType.getValue(theField);

Единственная другая деталь в этой точке — это извлечение значения Value . Это немного запутанная тема, которую мы обсудим после того, как рассмотрим другое расположение переменных — стек.

Проверка переменных стека:

Переменные стека, в частности их значения в точке останова, связаны с классами JDI StackFrame и Frame . В JDI они называются LocalVariable , вы получаете доступ к ним через BreakpointEvent , обращаясь к его ThreadReference , а затем к StackFrame на верхнем уровне (то есть элементу # 0) стека. Затем вы можете запросить видимые переменные в StackFrame по имени, чтобы найти целевую переменную. Другими словами, вы можете сделать что-то вроде этого:

 StackFrame stackFrame = breakpointEvt.thread().frame(0);
 LocalVariable localVar = stackFrame.visibleVariableByName(varName);
 Value val = stackFrame.getValue(localVar);

Обратите внимание , что вы должны получить LocalVariable от StackFrame , но, извлекаться, вы должны снова получить доступ к StackFrame , чтобы получить значение в LocalVariable . Также обратите внимание, что сейчас мы находимся на той же стадии, что и переменные класса / экземпляра — у нас есть значение , но нам нужно выяснить, каково его «значение». Как я уже сказал, это немного грязно и является темой следующего раздела.

Извлечение значений:

В JDI значение — это не просто то, что вы можете осмысленно отобразить с помощью toString () (если вы не найдете хеш-коды особенно значимыми). Документация JDI API содержит очень полезную таблицу в com.sun.jdi.Value, которая описывает семейство подынтерфейсов Value . По сути, все интерфейсы, которые представляют пригодное для использования значение, содержат метод value () , но этот метод определен только в подчиненных интерфейсах (поскольку не все из них определяют один). Это означает, что если вы хотите проверить значение, которое в основе является логическим значением или строкой , вы можете вызвать значение ()на нем, но вы должны сначала определить, является ли это экземпляром нужного вам типа, затем привести его к этому типу и вызвать value () для экземпляра приведенного типа. Я не знаю, есть ли более практичный способ сделать это, который не включает в себя написание чрезвычайно умного (другими словами, не поддерживаемого) кода, поэтому вот как я это делаю:

  if (value instanceof BooleanValue)
  {
    return ((BooleanValue)value).value();
  }
  else if (value instanceof ByteValue)
  {
    return ((ByteValue)value).value();
  }
  ...

и так далее.

Существует два основных подинтерфейса ValuePrimitiveValue и ObjectReference , а также немного больше иерархии, которая будет важна для вас. Например, если вы используете код, такой как приведенный выше пример, для извлечения значений, вы должны проверить StringReference перед ObjectReference . Зачем? StringReference имеет значение () метод, как и следовало ожидать , и надежды, но StringReference является подинтерфейсом ObjectReference , который не не имеет значение () метода. Если вы сначала проверяете ObjectReference , и вы смотрите наString Value , вы упустите доступное приведение к StringReference и упустите шанс проверить значение (да, я случайно сделал это; я сделал это на этот раз, и я вспомнил, что когда я делал это, я делал то же самое пару лет назад).

Дрель-вниз:

Если вы тоже пишете то, что я бы назвал «инструментом реального времени» на основе JDI, вы, вероятно, захотите провести некоторые детализации в точке останова. Например, вы можете захотеть взглянуть на конкретное поле переменной в стеке, а не только на значение переменной. Я справляюсь с этим, чтобы разрешить «точечную» запись, где « abc » позволяет моим пользователям указывать, что они хотят посмотреть на поле c поля b переменной / поле a . Здесь тоже немного интересно, как показывает следующий пример.

Предположим, вы отслеживаете трафик приложения и просто хотите узнать длину строк , обрабатываемых (например, возвращаемых из службы) в точке останова. Вы действительно не хотите отображать саму строку ; это может быть огромно (и помните, ваше целевое приложение останавливается во время обработки этой точки останова, поэтому ввод / вывод большой строки может нанести вред приложению). Все, что вы хотите знать, это длина строки . Но вы не хотите писать код для фактического вызова общих методов в ваших переменных стека / экземпляра. И в этом конкретном случае есть поле, доступное вам из JDI для класса Stringcount, Таким образом, ваши пользователи должны иметь возможность, например, указать, входящий_месяц.count , и получить целое число с длиной строки .

Единственная проблема в том, что не все знают, что в java.lang.String есть поле с именем count — они более привыкли к вызову метода length () . В интерактивном отладчике эта проблема не является проблемой, так как вы заранее не указываете, что хотите — вы просто проверяете состояние, когда возникает точка останова, и отладчик покажет все поля для вас. В отладчике типа, который я разрабатываю, пользователи должны знать, что доступно. Для переменной стека это проблема, потому что вы не можете проверить ее, пока приложение не запущено. Для переменных класса / экземпляра, когда у вас есть ReferenceType класса, вы можете, по крайней мере, проверить эти переменные и выполнить детализацию по желанию.

Способ решения этой проблемы (для переменных стека) заключается в предоставлении диагностической версии запроса точки останова, где пользователь может запросить дамп переменных в стеке на указанную глубину. Обратите внимание, что такая функция должна использоваться с некоторой осторожностью; приложение с очень большим количеством переменных стека, каждая из которых проходит (скажем) до 10 уровней, может в конечном итоге быть приостановлено на секунды, минуты или десятки минут, просто выводя состояние стека (да, я сделал это раньше тоже, но не буду повторять это на этот раз!). Достаточно сказать, что если вы хотите проверить приложение на наличие вложенных переменных, вы должны сделать это в непроизводственной системе.

Как вы углубляетесь в поля? Неудивительно, что для переменных класса / экземпляра существует другая процедура, чем для переменных стека. К сожалению, некоторые вещи в JDI не сразу очевидны (фактически, каждый раз, когда я пишу код для этого, мне приходится «заново открывать», как это сделать), поэтому я проиллюстрирую это ниже.

Для переменных класса / экземпляра вы получили поля, получив ReferenceType и просмотрев его поля s. Получив ссылку на поле , вы можете просто повторить процесс — на самом поле нет метода для детализации, но вы можете получить ReferenceType каждого поля и затем получить поля s каждого поля из каждого поля ‘ s ReferenceType . Вот пример:

  List childFields = bpEvt.location().declaringType().allFields();
   for (Field childField: childFields)
   {
     List grandChildFields = childField.declaringType().allFields();
     for (Field grandChildField: grandChildFields)
     {
       System.out.println(grandChildField.name() + " is a grandchild field");
     }
   }

Ситуация для переменных стека выглядит следующим образом. Если вы пытаетесь углубиться в переменную, по определению это ObjectReference , верно? Другими словами, нет примитива. Если стоимость на StackFrame является экземпляром ObjectReference , то вы можете получить ReferenceType от стоимости и получить его поле s так же , как мы делали выше , когда мы восстановили ReferenceType из класса / экземпляра поля . Обратите внимание, что поскольку StringReference является подынтерфейсом ObjectReference , мы можем сделать что-то вроде следующего:

    else if (value instanceof StringReference)
    {
      // look at the fields:
      List childFields = ((StringReference) value).referenceType().allFields();
      for (Field childField: childFields)
      {
        System.out.println(">> child field: '" + childField.name() + "'");
      }
      return ((StringReference)value).value();
    }

Если мы запустим этот код, мы увидим что-то вроде следующего вывода для строки :

>> дочернее поле: ‘значение’

>> дочернее поле: ‘смещение’

>> дочернее поле: ‘count’

>> дочернее поле: ‘hash’

>> дочернее поле: ‘serialVersionUID’

>> дочернее поле: ‘serialPersistentFields’

>> дочернее поле: ‘CASE_INSENSITIVE_ORDER’

(Обратите внимание на наше Поле подсчета , которое является IntegerValue ). Конечно, вы можете продолжить бурение вниз, используя ту же самую процедуру, на каждом из них поля s

Это все, что у меня есть на данный момент — ищите больше в ближайшие недели, так как проект приходит вместе

С http://wayne-adams.blogspot.com/2011/12/examining-variables-in-jdi.html