Статьи

Обработка точек останова в JDI

В последнее время я работал над проектом JDI (Java Debug Interface) и публиковал полезные советы по мере продвижения. Прошло несколько лет с тех пор, как я работал с этим API, но хотя я знаю, что было несколько улучшений, API вполне соответствует тому, что я помню. В этом посте я собираюсь обсудить, как использовать API для проверки значений переменных в точке останова.

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

Поиск значений в точке останова

Учитывая экземпляр vm VirtualMachine, вы можете настроить ваш код, использующий события, следующим образом:

EventQueue evtQueue = vm.eventQueue();
while(true)
{
 EventSet evtSet = evtQueue.remove();
 EventIterator evtIter = evtSet.eventIterator();
 while (evtIter.hasNext())
 {
   try
   {
     Event evt = evtIter.next();
     EventRequest evtReq = evt.request();
     if (evtReq instanceof BreakpointRequest)
     {
       BreakpointRequest bpReq = (BreakpointRequest)evtReq;
       BreakpointEvent bpEvt = (BreakpointEvent)evt;
       ThreadReference threadRef = bpEvt.thread();
       StackFrame stackFrame = threadRef.frame(0);
       ...

На данный момент вы можете найти переменные в двух местах:

  • Стек (отсюда извлечение StackFrame); например, локальные переменные в методе;
  • Текущая ObjectReference, для статических и переменных экземпляра.

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

  List visVars = stackFrame.visibleVariables();
for (LocalVariable visibleVar: visVars)
{
  if (visibleVar.name().equals(varName))
  {
    Value val = stackFrame.getValue(visibleVar);
    ...

Как я уже упоминал ранее, JDI API не подходит для классов с конструкторами; большая часть того, что вы видите, — это определения интерфейсов, а impls предоставляется за кулисами. Таким образом, вы не будете создавать экземпляр поля в JDI и искать его; вместо этого вы углубляетесь в API и сравниваете некоторый аспект нужного вам объекта с имеющимися у вас данными. Например, вы перечисляете все LocalVariables в стеке, а затем сравниваете их .name () с именем вашей целевой переменной.

Если вы не нашли свою переменную в стеке, то вы можете проверить поля объекта (я предпочитаю сначала проверить стек). Чтобы получить значения полей объекта, вы должны сначала получить ObjectReference, а затем запросить его:

ObjectReference objRef = stackFrame.thisObject();
Value targetVal = objRef.getValue(field...

Halt. ObjectReference получит значение для вас, но для него требуется объект Field, для которого нет конструктора. Это просто вопрос, который я обсуждал выше. Где взять объект Field? Сначала вы запрашиваете доступные поля для объекта ReferenceType, а затем передаете эти ссылки на поля объекту, чтобы получить их значения для этого экземпляра:

ObjectReference objRef = stackFrame.thisObject();
ReferenceType refType = objRef.referenceType();
List<Field> objFields = refType.allFields();
for (int i=0; i<objFields.size(); i++)
{
  Field nextField = objFields.get(i);
  if (nextField.name().equals(varName))
  {
    Value targetVal = objRef.getValue(nextField);
  }
}

Таким образом, в точке останова находятся ваши значения, полученные из стека или полей экземпляра ObjectReference.

Работа со значениями JDI

Значение, которое вы получаете от JDI, требует небольшого усилия для практического использования. Например, если Value является экземпляром StringReference, вам необходимо привести его к StringReference, а затем вызвать .value (), чтобы получить значение java.lang.String. Если это экземпляр IntegerValue, вам придется преобразовать Value в IntegerValue и вызвать .value (), который в этом случае вернет int! Я считаю этот процесс немного утомительным (примечание: .toString ()
невернуть значение!). В моем случае я собираюсь генерировать либо выходные данные журнала, либо пары ключ-значение для каждого события точки останова, поэтому мой подход заключается в том, чтобы просто написать удобный метод, который скрывает детали и предоставляет значение в форме String.

Интересный случай, когда Value сам по себе является ObjectReference. Так же, как вы можете развернуть переменные до их полей в обычном отладчике, вы также можете выбрать детализацию объектов в точке останова и извлечь
их поля на произвольном уровне глубины. Тот же процесс может использоваться для других типов JDI, таких как StringReference.

Эти поля могут быть не такими знакомыми, как методы, к которым вы привыкли. Например, если вы регистрируете размер строк, которые генерируются в вашей целевой программе, вы не можете вызвать .length () для StringReference, но вы можете получить его поля экземпляра и получить число, которое является именем поле, содержащее длину строки.

Практические соображения

Хотя можно углубиться в произвольную глубину в стеке или в полях экземпляра, имейте в виду, что вы остановили свое приложение, пока это происходит. Лучше всего сводить обработку в этот период к минимуму. Хотя вы можете создать поток для завершения обработки точки останова (например, создания и отправки сообщения JMS), вам потребуется извлечь все переменные из стека и экземпляра, пока целевое приложение остановлено. Так что детализация должна быть быстрой.

Помимо этого, вы можете указать переменные в некотором произвольном формате, например, в точечной нотации, и затем рекурсивно углубляться в поля / переменные, пока не найдете нужное значение. Например, вы могли бы иметь переменную, указанную как fileName.count, и в этом случае вы должны найти fileName StringReference, затем получить поля экземпляра fileName и найти count, который является длиной имени файла.

Для таких полей, как String count, которые могут быть неочевидны для тех, кто настраивает спецификацию точек останова, будет полезен браузер определенного типа. Просмотр стека не подходит, пока вы уже не установили точки останова и не достигли точки останова, поскольку кадр стека не находится в области действия, пока не будет достигнута точка останова. Но для статических полей и полей экземпляра вам нужен только ReferenceType для перечисления доступных полей с некоторой настраиваемой глубиной, прежде чем вы даже начнете отлаживать приложение. Поскольку эти поля доступны в JDI, как только класс загружен, вам не нужно останавливать выполнение программы только для того, чтобы получить статические имена и имена полей экземпляра для класса.

 

С http://wayne-adams.blogspot.com/2011/11/breakpoint-processing-in-jdi.html