брейкпойнты java что это
Александр Александров про тренды и технологии тестирования, про влияние Covid19 на рынок QA
Онлайн-тренинги
Что пишут в блогах (EN)
Blogposts:
Разделы портала
Про инструменты
Автор: Кристин Джеквони (Kristin Jackvony)
Оригинал статьи
Перевод: Ольга Алифанова
XHR-брейкпойнт
XHR – это “XMLHttpRequest”. Это способ запрашивать данные от сервера без перезагрузки веб-страницы. Вы можете создать брейкпойнт, который поставит код на паузу, если выполняется XHR-запрос. Чтобы это сделать, посмотрите на правую панель инструментов разработчика (убедитесь, что вы на вкладке Sources) и откройте секцию “XHR/fetch Breakpoints”. Нажмите на кнопку плюса (+) в этой секции, и отметьте чекбокс “Any XHR or fetch” (если вы не видите чекбокса, и вместо него пустое текстовое поле, кликните вне поля, и чекбокс появится).
Теперь у нас есть брейкпойнт, который выполнится, как только будет сделан вызов к серверу. Чтобы спровоцировать это в приложении-счетчике, кликните на кнопку корзины у каждого счетчика, чтобы все они удалились. Затем нажмите на голубую кнопку корзины. Будет инициирован серверный запрос, и вы увидите, как выполнение кода остановится.
Не забудьте нажать на кнопку Resume и удалить брейкпойнт перед переходом к следующему разделу!
Брейкпойнт обработчика событий
Как следует из названия, этот брейкпойнт ждет определенного события – например, анимации, ввода с клавиатуры, движения мыши, и т. д. Мы настроим брейкпойнт на остановку по клику мыши. Откройте секцию Event Listener Breakpoints в правой панели инструментов разработчика и кликните на стрелочку рядом с “Mouse”, чтобы раскрыть секцию. Отметьте чекбокс «click». Теперь при клике на кнопках приложение остановится на брейкпойнте.
Не забудьте нажать на кнопку Resume и удалить брейкпойнт перед переходом к следующему разделу!
Брейкпойнт исключения
Этот брейкпойнт можно настроить на остановку в момент, когда код столкнутся с исключениями, и можно задать паузу на все исключения, включая пойманные, или же только на непойманные. Для установки этого брейкпойнта просто кликните на голубой кнопке паузы в верхнем правом углу инструментов разработчика. Кликните также на чекбоксе “Pause on caught exceptions”. Для инициации исключения в приложении-счетчике кликните на иконке корзины для всех счетчиков, а затем на голубой иконке корзины. Это вызывает исключение в Safari, и код останавливается; изучив код, я думаю, что исключение зависит от браузера, поэтому ваши результаты могут отличаться.
Не забудьте нажать на кнопку Resume и удалить брейкпойнт перед переходом к следующему разделу!
Пошаговый поход по приложению
Брейкпойнты – отличный способ поставить приложение на паузу и посмотреть, в каком состоянии оно находится. Они также хороши для пошагового прохода по приложению. Чтобы сделать это, вернемся назад и используем простой брейкпойнт на строке кода, как мы делали в прошлый раз. Убедитесь, что в верхней части инструментов разработчика выбрана вкладка “Sources”, найдите папку src в левой секции, откройте ее и кликните на файле App.js. Найдите 16 строку и кликните по ней, чтобы добавить брейкпойнт.
Кликните по одной из кнопок-плюсов (+) в приложении, и брейкпойнт остановит его на 16 строке. На этот раз вместо клика по Resume нажмите на соседнюю кнопку. Это кнопка Step Over, и код выполнит следующую свою строку в App.js. Откройте секцию Scope, и вы увидите значения всех переменных на этом моменте кода. Продолжая кликать по кнопке Step Over, вы увидите, как меняется значение счетчика. Посмотрите на счетчики в секции Array(4), и увидите, что используемый вами счетчик увеличил значение.
Эти две статьи дали вам шесть разных способов установить брейкпойнт в инструментах разработчика. Надеюсь, это будет вам полезно при диагностике коварных багов JavaScript. Удачной охоты!
Программный дебаг Java-приложений посредством JDI
Введение
В процессе отладки приложений работающих на JVM посредством дебаггера в Eclipse меня всегда впечатляло то, сколько доступа можно получить к данным приложения — потокам, значениям переменных и т.п. И в то же время периодически возникало желание «заскриптовать» некоторые действия или получить больше контроля над ними.
Например, иногда для того чтоб «мониторить» состояние какой-то переменной, меняющейся в цикле, я использовал условный брейкпойнт, условием к которому был код вроде «System.out.println(theVariable); return false». Этот хак позволял получить лог значений переменной практически не прерывая работы приложения (она, естественно, всё-таки прерывалась на время выполнения кода условия, но не более). Плюс, нередко при просмотре каких-нибудь данных через вид Display порядком раздражало то, что результат евалюейшна кода в Display введённого добавлялся тут же после него.
В общем хотелось получить возможность делать всё то же самое например через Bean Shell или Groovy Shell, что в принципе аналогично программному дебагу. По логике это не должно было быть сложно — ведь делает же это как-то сам Eclipse, верно?
Проведя некоторый рисёрч я смог получить доступ к отладочной информации JVM программно, и спешу поделится примером.
О JPDA и JDI
Для отладки JVM придуманы специальные стандарты, собранные вместе под «зонтичным» термином JPDA — Java Platform Debugger Architecture. В них входят JVMTI — нативный интерфейс для отладки приложений в JVM посредством вызова сишных функций, JDWP — протокол передачи данных между дебаггером и JVM, приложения в которой отлаживают и т.д.
Всё это выглядело не особо релевантно. Но сверх этого всего в JPDA входит некий JDI — Java Debug Interface. Это Java API для отладки JVM приложений — то, что доктор прописал. Официальная страница о JPDA подтвердила наличие reference имплементации JDI от Sun/Oracle. Значит, оставалось только начать ней пользоватся
Пример
В качестве proof of concept я решил попробовать запустить два Groovy Shell-а — один в отладочном режиме в качестве «подопытного», второй в качестве отладчика. В подопытном шелле была задана строчная переменная, значение которой требовалось получить из шелла-«отладчика».
Также в подопытном Groovy Shell была выполнена следующая команда:
Соответственно значение “Some special value” должно было быть получено в отладчике.
Т.к. это не просто значение поля какого-нибудь объекта, для того чтоб его получить надо было немного знать внутренности Groovy Shell (или как минимум подглядывать в исходники), но тем интереснее и реалистичнее мне показалась задача.
Далее дело было за «отладчиком»:
Рассмотрим всё пошагово:
Соединение с JVM
При помощи JDI соединяемся с JVM которую задумали отлаживать (хост == локалхост т.к. я всё делал на одной машине, но сработает аналогично и с удалённой; порт же тот, что был выставлен в debug-параметрах «подопытной» JVM).
JDI позволяет присоединятся к JVM как через сокеты, так и напрямую к локальному процессу. Поэтому VirtualMachineManager возвращает больше одного AttachingConnector-а. Мы выбираем нужный коннектор по имени транспорта («dt_socket»)
Получение стактрейса потока main
Полученный интерфейс к удалённой JVM позволяет посмотреть запущенные в ней потоки, приостановить их и т.п. Но для того, чтоб иметь возможность делать вызовы методов в удалённой JVM нам нужен поток в ней, который был бы остановлен именно брейкпойнтом. О чём собственно гласит следующий пункт JDI javadoc:
«Method invocation can occur only if the specified thread has been suspended by an event which occurred in that thread. Method invocation is not supported when the target VM has been suspended through VirtualMachine.suspend() or when the specified thread is suspended through ThreadReference.suspend().»
Для установки брейкпойнта я пошёл несколько специфическим путём — не заглядывать в сорцы Groovy Shell а просто посмотреть что сейчас происходит в JVM и выставить брейкпойнт прямо в том, что происходит.
В потоках подопытной JVM был обнаружен поток main, и в его стактрейс я и заглянул. Поток был предварительно остановлен — чтоб стактрейс оставался актуален во время последующих манипуляций.
В результате получил такое:
Установка брейкпойнта
Итак, мы имеем стактрейс остановленного потока main. API JDI возвращает для потоков так называемые StackFrame, из которых можно получить их Location. Собственно сей Location и требуется для установки брейкпойнта.
Не долго думая, локейшн я взял из «jline.ConsoleReader$readLine.call», и в него установил брейкпойнт, после чего запустил поток main работать дальше:
Теперь брейкпойнт установлен. Переключившись в подопытный Groovy Shell и нажав ввод я увидел что он действительно остановился. У нас есть остановка потока на брейкпойнте — всё готово к вмешательству в работу подопытной JVM.
Получение ссылки на объект Groovy Shell
API JDI позволяет из StackFrame получать видимые в них переменные. Чтоб получить значение переменной из контекста Groovy Shell надо было сначала вытянуть ссылку на сам шелл. Но где он?
Подсматриваем все видимые переменные во всех стек фреймах:
Обнаружился стек фрейм в объекте «org.codehaus.groovy.tools.shell.Main» с видимой переменной shell:
«48: org.codehaus.groovy.tools.shell.Main:131 in thread instance of java.lang.Thread(name=’main’, >
Получение искомого значения из Groovy Shell
У shell.Main имеется поле interpreter. Зная немного внутренности Groovy Shell я заранее знал что переменные GroovyShell контекста хранятся в объекте типа groovy.lang.Binding, который можно получить вызвав getContext() у Interpreter (вызов метода необходим т.к. соответствующего поля со ссылкой на groovy.lang.Binding в Interpreter нет).
Из Binding значение переменной можно получить вызовом метода getVariable(String varName).
Последняя строка скрипта вернула нам ожидаемое значение «Some special value» — всё работает!
Последний штрих
Шутки ради я решил еще и поменять значение этой переменной из отладчика — для этого достаточно было вызвать у Binding метод setVariable(String varName, Object varValue). Что может быть проще?
Чтоб убедится что всё сработало я также задизейблил брейкпойнт и запустил обратно приостановленный ранее по брейкпойнту поток main.
Переключившись в последний раз в подопытный Groovy Shell я проверил значиение переменной myVar, и оно оказалось равно «Surprise!».
Выводы
Быть Java программистом это счастье, ибо Sun подарил нам мощные инструменты — а значит большие возможности (-:
А если еще дописать к Groovy удобные врапперы (метаклассы) для JDI можно сделать программную отладку из Groovy Shell вполне приятной. К сожалению пока-что она выглядит где-то так же, как, например, доступ к полям и методам через reflection API.
Александр Александров про тренды и технологии тестирования, про влияние Covid19 на рынок QA
Онлайн-тренинги
Что пишут в блогах (EN)
Blogposts:
Разделы портала
Про инструменты
Автор: Кристин Джеквони (Kristin Jackvony)
Оригинал статьи
Перевод: Ольга Алифанова
Современные команды разработки ПО размывают традиционные границы между разработчиком и тестировщиком. Это означает, что разработчики все сильнее вовлекаются в тест-автоматизацию на всех уровнях пирамиды, а тестировщики все глубже погружаются во внутреннюю механику приложения. Разве не здорово бы было найти причины багов JavaScript, а не просто сообщать о них? Это можно сделать при помощи брейкпойнтов инструментов разработчика Chrome!
В этой статье я шаг за шагом проведу вас по использованию Chrome Dev Tools для дебага. Я узнала об этом из этой полезной статьи в блоге Google, но я буду использовать пример приложения, чтобы вы смогли попробовать различные методы самостоятельно.
Важно отметить, что для использования брейкпойнтов вам понадобится локальная установка приложения. Нельзя просто взять релизную версию и дебажить ее. Статья покажет вам, как запустить React-приложение локально, и вы можете уточнить у своих разработчиков, как сделать это с вашим рабочим приложением.
Чтобы следовать моим инструкциям, нужно установить Node.js и Git. Затем клонируйте этот репозиторий. Если вы никогда не клонировали репозитории, инструкции здесь. В этом репозитории находится очень простое приложение-счетчик, которое можно использовать для, скажем, добавления товаров в корзину. Оно не ахти какое сложное, но делает достаточно для проверки работы брейкпойнтов.
После клонирования репозиторий сделайте вот что:
cd counter-app (вы перейдете в папку приложения)
npm install (получение всех необходимых для запуска пакетов, и билд приложения)
npm start (запуск приложения)
Ваше приложение откроется в Chrome и будет выглядеть так:
Затем откройте инструменты разработчика, используя меню многоточия в правом верхнем углу Chrome. Из этого меню выберите «Дополнительные инструменты», а затем «Инструменты разработчика». Они откроются в браузере и будут выглядеть примерно так:
Мы готовы начинать! Мы установим каждый тип брейкпойнта, посмотрим на его работу, нажмем кнопку «Продолжить выполнение скрипта», а затем очистим брейкпойнт. Кнопка «Продолжить выполнение скрипта» (“Resume script execution”) находится наверху самой правой панели и выглядит так:
Для очистки брейкпойнта просто нажмите на него правой клавишей и выберите «Убрать брейкпойнт». Приступим!
Брейкпойнт строки кода
Этот брейкпойнт используется чаще всего. Код приложения, выполняясь и доходя до этой строчки, приостановится. Эту паузу можно использовать для анализа текущего состояния приложения – начиная с видимого состояния и заканчивая значениями переменных.
Если вкладка «Источники» («Sources») еще не выбрана в верхней части инструментов разработчика, выберите ее. Теперь посмотрите в левую часть панели инструментов разработчика, найдите папку src, и кликните на файл App.js в этой папке. Он откроется в центральной панели инструментов разработчика.
Найдите строку 17 и кликните на ее номер. Номер подсветится синим, вот так:
Эта линия кода находится в функции handleIncrement, которая вызывается, когда вы нажимаете один из плюсиков в приложении. Нажмите на плюсик. Вы увидите, как эта строка подсветится, и на правой панели появится сообщение «Пауза на брейкпойнте». Стоя на паузе, вы можете посмотреть на секции Call stack и Scope, чтобы увидеть, что сейчас происходит в приложении.
Не забудьте удалить брейкпойнт на строке 17 и нажать на кнопку Resume, прежде чем перейти к следующему разделу!
Брейкпойнт на условной строке кода
Теперь давайте рассмотрим брейкпойнт на условной строке кода. Этот брейкпойнт сработает только тогда, когда выполнено определенное условие. Кликните правой клавишей на 19 строке кода App.js и выберите «Добавить условный брейкпойнт» (“Add conditional breakpoint”). Брейкпойнт будет добавлен, и появится всплывающее окно для добавления условия. Добавьте условие index === 0 и нажмите на кнопку возврата или ввода.
Теперь, когда брейкпойнт установлен, посмотрим на него в действии. Если вы ничего не нажимали после прошлого упражнения, то у вас есть счетчик, установленный на цифре 1. Снова нажмите на кнопку с плюсом. Код не остановится на брейкпойнте, потому что счетчик стоит на единице. Нажмите на кнопку с минусом, чтобы вернуть счетчик на ноль. Снова нажмите на плюс. На этот раз код остановится на брейкпойнте, потому что текущее состояние счетчика на строке 19 – это ноль.
Не забудьте удалить брейкпойнт на строке 19 и нажать на кнопку Resume, прежде чем перейти к следующему разделу!
DOM-брейкпойнт
Можно также установить брейкпойнт напрямую в DOM. Для этого сначала нажмем на вкладку Elements на верхней панели инструментов разработчика. Измените верхний счетчик с единицы на ноль. Теперь нажмите на этот нулевой счетчик правой кнопкой и выберите «Inspect». Центральная панель инструментов разработчика перейдет к секции DOM с этим счетчиком и подсветит ее. Нажмите на подсвеченной секции правой кнопкой, выберите «Break on», а затем «Attribute modifications». Когда счетчик перейдет с нуля на единицу, он меняет атрибут (в данном случае цвет), и выбирая модификации атрибута, мы говорим коду остановиться, как только изменится атрибут иконки.
Нажмите на плюс, и вы увидите, как код остановится на брейкпойнте, потому что цвет счетчика готов измениться с желтого на голубой. Нажмите кнопку «Resume execution», код продолжит выполнение, счетчик изменится на единицу (и станет голубым). Снова нажмите на кнопку с плюсом, и код не остановится на брейкпойнте, потому что при переходе от 1 к 2 цвет не меняется. Но если дважды нажать на минус и вернуться к нулю, код снова остановится на брейкпойнте, потому что цвет счетчика готов измениться.
Не забудьте удалить брейкпойнт и нажать на кнопку Resume, прежде чем продолжать!
Это довольно длинная статья, а у нас впереди еще четыре брейкпойнта. О них я расскажу в следующий раз.
Про брейкпойнты
Думаю не ошибусь, если напишу, что каждый программист когда-либо пользовался отладчиком, отлаживал программу пошагово, устанавливал брейкпойнты и т.п. При этом некоторые программисты не любят отлаживать. Другие — обожают. А большинство просто использует отладчик не задумываясь о любви и ненависти, ведь это просто еще один удобный инструмент для работы.
Для многих программистов отладчики — это черный ящик. Они умеют с ним обращаться, но не знают, как он работает. Я не говорю, что это плохо — в подавляющем большинстве случаев можно легко отлаживать программу без знания устройства отладчика.
А для тех, кто хочет заглянуть внутрь черного ящика, я написал эту небольшую статью.
Тут я расскажу про одну из самых загадочных (по крайней мере для меня) возможностей отладчиков — про работу с брейкпойнтами. Я постараюсь рассказать это максимально просто и без лишних деталей. Однако я пишу эту статью для тех читателей, который уже знают что такое брейкпойнты и умеют их использовать.
Вы когда-нибудь задумывались, что происходит, когда вы нажимаете «Вставить брейкпойнт»? Или о том, как отладчик может добавлять брейкпойнты в код на лету и удалять их? О том, как работают брейкпойнты с условиями? О том, тормозят ли множественные установленные брейкпойнты выполнение программы?
Сначала немного теории:
Наверняка уже многие из вас сталкивались или слышали про ассемблерную инструкцию int 3 (_asm int 3 в C++). Именно эта инструкция — это основа всей системы брейкпойнтов.
Когда процессор встречает эту инструкцию, то он инициирует отладочное исключение и передает его в обработчик прерываний операционной системы. А операционная система конвертирует его в софтварное исключение и инициирует его в том месте исполняемого файла, в котором было встречено это int 3. Тип этого исключения общеизвестен — это STATUS_BREAKPOINT (0x80000003).
Если к процессу, инициировавшему это исключение, не присоединен отладчик, то этот процесс упадет. И вы увидите сообщения наподобии этих:
Это возможно, если вы запускаете отладочную сборку или же сами оставили в своем коде int 3 в релизной сборке.
Если же процесс работал под отладчиком (а обычно исключения STATUS_BREAKPOINT используются именно отладчиком), то это исключение передается в отладчик и дальше уже отладчик его обрабатывает.
Также на уровне процессора поддерживаются брейкпойнты, срабатывающие на обращение к области памяти (может отлавливаться чтение, запись или исполнение кода). Таких брейкпойнтов может быть не более четырех. Они работают по другой схеме и глобальны для всей системы в целом, а не только для отлаживаемого приложения. Я на их работе подробно останавливаться не буду, а желающие могут погуглить про регистры DR0-DR7.
Когда вы ставите брейкпойнт в отладчике, то происходит следующее:
Отладчик переводит область памяти, куда вы поставили брейкпойнт, в режим read-write (по умолчанию область памяти с исполняемым кодом read only), чтобы можно было менять там код на лету. Затем отладчик заменяет ассемблерную инструкцию в месте, куда вы поставили брейкпойнт, на int 3, а саму эту замененную инструкцию запоминает у себя в памяти.
При этом всегда, когда вы просматриваете ассемблерный код в отладчике, он заменяет эти int 3 на реальные инструкции, но на самом деле там int 3, скрытые от ваших глаз.
Также отладчик всегда заменяет все int 3 обратно на реальные инструкции, когда процесс останавливается на брейкпойнте и управление передается пользователю.
Когда срабатывает брейкпойнт под отладчиком, то:
Отладчик подменяет все int 3 (не только тот, который сработал, а все) на правильные инструкции, которые он запомнил при вставке этих int 3, и показывает точку брейкпойнта пользователю уже с нормальным кодом. Потом, когда пользователь запускает приложение выполняться дальше, то отладчик опять вставляет int 3 во все нужные места.
Если брейкпойнт с условием, то при его срабатывании отладчик сначала проверяет условие и только при выполнении условия делает всю работу, как при обычном брейкпойнте. Если же условие не сработало, то отладчик выполняет запомненную команду, замененную на int 3, а потом продолжает выполнение с инструкции после int 3.
Когда вы удаляете брейкпойнт, то:
Отладчик просто заменяет вставленный int 3 обратно на нужную команду и забывает про этот брейкпойнт.
Выводы, которые можно сделать из всего вышесказанного:
Тормозят ли брейкпойнты, если их много?
Нет, если они без условий. Простой брейкпойнт не требует дополнительных ресурсов отладчика или отлаживаемого процесса — это просто инструкция int 3. Даже если их миллион, но они не срабатывают, то это ни на милисекунду не замедлит выполнение программы.
Но если установить брейкпойнт с условием, то даже один может замедлить программу в разы, если он стоит в часто вызываемом месте, так как в каждом таком брейкпойнте будет вызываться прерывание, потом вызываться исключение, передаваться в отладчик, который будет проверять условие, подменять команды и возвращать выполнение обратно в место брейкпойнта.
Брейкпойнты на память также не тормозят, т.к. они поддерживаются на уровне процессора.
Также надо учесть, что когда вы запускаете приложение под отладчиком, то в момент запуска он будет заменять на int 3 все инструкции в местах брейкпойнтов. И, если их очень много, это может занять долгое время (Например, отладчик MS Visual Studio 2005 при установке брейкпойнта в большом проекте (C++) в частоиспользуемую шаблонную функцию, например std::vector::operator[], иногда подвисает на минуту).
Программный дебаг Java-приложений посредством JDI
Введение
В процессе отладки приложений работающих на JVM посредством дебаггера в Eclipse меня всегда впечатляло то, сколько доступа можно получить к данным приложения — потокам, значениям переменных и т.п. И в то же время периодически возникало желание «заскриптовать» некоторые действия или получить больше контроля над ними.
Например, иногда для того чтоб «мониторить» состояние какой-то переменной, меняющейся в цикле, я использовал условный брейкпойнт, условием к которому был код вроде «System.out.println(theVariable); return false». Этот хак позволял получить лог значений переменной практически не прерывая работы приложения (она, я подозреваю, всё-таки прерывалась на время выполнения кода условия, но не более). Плюс, нередко при просмотре каких-нибудь данных через вид Display порядком раздражало то, что результат евалюейшна кода в Display введённого добавлялся тут же после него.
В общем хотелось получить возможность делать всё то же самое например через Bean Shell или Groovy Shell, что в принципе аналогично программному дебагу. По логике это не должно было быть сложно — ведь делает же это как-то сам Eclipse, верно?
Проведя некоторый рисёрч я смог получить доступ к отладочной информации JVM программно, и спешу поделится примером.
О JPDA и JDI
Для отладки JVM придуманы специальные стандарты, собранные вместе под «зонтичным» термином JPDA — Java Platform Debugger Architecture. В них входят JVMTI — нативный интерфейс для отладки приложений в JVM посредством вызова сишных функций, JDWP — протокол передачи данных между дебаггером и JVM, приложения в которой отлаживают и т.д.
Всё это выглядело не особо релевантно. Но сверх этого всего в JPDA входит некий JDI — Java Debug Interface. Это Java API для отладки JVM приложений — то, что доктор прописал. Официальная страница о JPDA подтвердила наличие referene имплементации JDI от Sun/Oracle. Значит, оставалось только начать ней пользоватся
Пример
В качестве proof of concept я решил попробовать запустить два Groovy Shell-а — один в отладочном режиме в качестве «подопытного», второй в качестве отладчика. В подопытном шелле была задана строчная переменная, значение которой требовалось получить из шелла-«отладчика».
Также в подопытном Groovy Shell была выполнена следующая команда:
Соответственно значение “Some special value” должно было быть получено в отладчике.
Т.к. это не просто значение поля какого-нибудь объекта, для того чтоб его получить надо было немного знать внутренности Groovy Shell (или как минимум подглядывать в исходники), но тем интереснее и реалистичнее мне показалась задача.
Далее дело было за «отладчиком»:
Рассмотрим всё пошагово:
Соединение с JVM
При помощи JDI соединяемся с JVM которую задумали отлаживать (хост == локалхост т.к. я всё делал на одной машине, но сработает аналогично и с удалённой; порт же тот, что был выставлен в debug-параметрах «подопытной» JVM).
JDI позволяет присоединятся к JVM как через сокеты, так и напрямую к локальному процессу. Поэтому VirtualMachineManager возвращает больше одного AttachingConnector-а. Мы выбираем нужный коннектор по имени транспорта («st_socket»)
Получение стактрейса потока main
Полученный интерфейс к удалённой JVM позволяет посмотреть запущенный в ней потоки, приостановить их и т.п. Но для того, чтоб иметь возможность делать вызовы методов в удалённой JVM нам нужен поток в ней, который был бы остановлен именно брейкпойнтом. О чём собственно гласит следующий пункт JDI javadoc:
«Method invocation can occur only if the specified thread has been suspended by an event which occurred in that thread. Method invocation is not supported when the target VM has been suspended through VirtualMachine.suspend() or when the specified thread is suspended through ThreadReference.suspend().»
Для установки брейкпойнта я пошёл несколько специфическим путём — не заглядывать в сорцы Groovy Shell а просто посмотреть что сейчас происходит в JVM и выставить брейкпойнт прямо в том, что происходит.
В потоках подопытной JVM был обнаружен поток main, и в его стактрейс я и заглянул. Поток был предварительно остановлен — чтоб стактрейс оставался актуален во время последующих манипуляций.
В результате получил такое:
Установка брейкпойнта
Итак, мы имеем стактрейс остановленного потока main. API JDI возвращает для потоков так называемые StackFrame, из которых можно получить их Location. Собственно сей Location и требуется для установки брейкпойнта.
Не долго думая, локейшн я взял из «jline.ConsoleReader$readLine.call», и в него установил брейкпойнт, после чего запустил поток main работать дальше:
Теперь брейкпойнт установлен. Переключившись в подопытный Groovy Shell и нажав ввод я увидел что он действительно остановился. У нас есть остановка потока на брейкпойнте — всё готово к вмешательству в работу подопытной JVM.
Получение ссылки на объект Groovy Shell
API JDI позволяет из StackFrame получать видимые в них переменные. Чтоб получить значение переменной из контекста Groovy Shell надо было сначала вытянуть ссылку на сам шелл. Но где он?
Подсматриваем все видимые переменные во всех стек фреймах:
Обнаружился стек фрейм в объекте «org.codehaus.groovy.tools.shell.Main» с видимой переменной shell:
«48: org.codehaus.groovy.tools.shell.Main:131 in thread instance of java.lang.Thread(name=’main’, >
Получение искомого значения из Groovy Shell
У shell.Main имеется поле interpreter. Зная немного внутренности Groovy Shell я заранее знал что переменные GroovyShell контекста хранятся в объекте типа groovy.lang.Binding, который можно получить вызвав getContext() у Interpreter (вызов метода необходим т.к. соответствующего поля со ссылкой на groovy.lang.Binding в Interpreter нет).
Из Binding значение переменной можно получить вызовом метода getVariable(String varName).
Последняя строка скрипта вернула нам ожидаемое значение «Some special value» — всё работает!
Последний штрих
Шутки ради я решил еще и поменять значение этой переменной из отладчика — для этого достаточно было вызвать у Binding метод setVariable(String varName, Object varValue). Что может быть проще?
Чтоб убедится что всё сработало я также задизейблил брейкпойнт и запустил обратно приостановленный ранее по брейкпойнту поток main.
Переключившись в последний раз в подопытный Groovy Shell я проверил значиение переменной myVar, и оно оказалось равно «Surprise!».
Выводы
Быть Java программистом это счастье, ибо Sun подарил нам мощные инструменты — а значит большие возможности (-:
А если еще дописать к Groovy удобные врапперы для JDI можно сделать программную отладку из Groovy Shell вполне приятной. К сожалению пока-что она выглядит где-то так же, как, например, доступ к полям и методам через reflection API.