Что если мы скажем вам, что отладчик может стать не просто инструментом для поиска ошибок, а настоящим «режимом бога» в мире кода? 

В новом переводе от команды Spring АйО рассматривается, как можно исследовать память приложения и изменить его функциональность, при этом не затрагивая исходного кода, а используя только лишь отладчик IntelliJ IDEA.


Раньше компьютерные игры были другими. Их графика и механика эволюционировали, но у них была особенность, которая сегодня не так распространена: чит-коды.

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

Скриншот морпеха из Doom с включенным «режимом бога».
Скриншот морпеха из Doom с включенным «режимом бога».

Вот как выглядел ваш персонаж в Doom, когда вы вводили IDDQD — комбинацию клавиш для режима бога. Эта последовательность стала настолько популярной, что превратилась в мем и вышла за пределы игры.

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

Space Invaders

Вот забавный пример. Даже если вы не знакомы с Doom, вы, вероятно, видели более старую игру под названием Space Invaders. Как и в Doom, в ней нужно сражаться с космическими захватчиками.

Мой друг и коллега Евгений Низиенко создал плагин для IntelliJ IDEA, позволяющий играть в эту игру прямо в редакторе — отличный способ скоротать время, ожидая завершения индексации.

Space Invaders в редакторе IntelliJ IDEA.
Space Invaders в редакторе IntelliJ IDEA.

В этой игре нет режима бога, но можем ли мы сами его добавить? Давайте вернемся к классической традиции взлома программ с помощью отладчика, чтобы это выяснить!

Примечание: Будьте ответственны! Я получил согласие Евгения перед вмешательством в его программу. Если вы используете отладчик для чужого кода, убедитесь, что действуете этично. Иначе просто не делайте этого.

Подготовьте инструменты

Приготовьтесь к необычному опыту — мы будем отлаживать IntelliJ IDEA с помощью ее собственного отладчика.

Но есть небольшая проблема: при отладке IntelliJ IDEA нам нужно приостановить ее работу, что сделает IDE неотзывчивой. Поэтому нам понадобится еще одна копия IDE, которая останется функциональной и будет служить нашим инструментом отладки.

Для управления несколькими копиями IDE я использую JetBrains Toolbox App. Это приложение для организации ваших IDE от JetBrains. С его помощью можно установить несколько версий одной IDE или создать ярлыки для запуска с разными параметрами виртуальной машины.

Давайте установим две копии IntelliJ IDEA:

Если вы используете одну версию IDE для обеих копий, обязательно укажите разные каталоги system, config и logs в Tool actions | Settings | Configuration. Здесь же вы можете присвоить имена копиям IDE для удобства. Я назвал их «Space Invaders» и «Debug».

Чтобы иметь возможность отлаживать копию Space Invaders, нажмите Tool actions рядом с ней, затем перейдите в Settings | Edit JVM options. В открывшемся файле вставьте следующую строку:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

Это запустит JVM с агентом отладки и позволит слушать входящие подключения отладчика на порту 5005.

Запустите игру

Запустите копию «Space Invaders», установите игру и запустите ее, выполнив действие Space Invaders. Чтобы найти это действие, дважды нажмите Shift и начните вводить Space Invaders:

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

Подключение и приостановка

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

Кроме того, IntelliJ IDEA включает стандартную библиотеку Java/Kotlin в шаблон нового проекта, которая может понадобиться нам позже. Я объясню ее использование в следующих разделах.

После создания проекта перейдите в главное меню и выберите Run | Attach to Process. Появится список локальных JVM, ожидающих подключения отладчика. Выберите другую запущенную IDE из списка.

В консоли должно появиться сообщение, подтверждающее успешное подключение отладчика к целевой виртуальной машине.

Connected to the target VM, address: 'localhost:5005', transport: 'socket'

Теперь самое интересное: как приостановить приложение?

Обычно мы устанавливаем точку останова в коде приложения, но у нас нет исходников ни для IntelliJ IDEA, ни для плагина Space Invaders. Это мешает установить точку останова и усложняет понимание работы программы. Кажется, нечего просматривать или пошагово выполнять.

К счастью, в IntelliJ IDEA есть функция Pause Program. Она позволяет приостановить программу в любой момент времени без указания конкретной строки кода. Вы найдете ее на панели инструментов отладчика или в главном меню: Run | Debugging Actions | Pause Program.

Окно отладки при приостановленной копии Space Invaders.

Приложение приостанавливается, давая нам точку начала для отладки.

Совет: Pause Program — мощная техника, особенно полезная в сложных сценариях. Чтобы узнать больше, ознакомьтесь со связанными статьями:

Найдите соответствующие объекты

Наша цель с точки зрения программирования — предотвратить снижение здоровья космического корабля. Давайте найдем объект, хранящий это состояние.

Поскольку мы не знаем код плагина, можем напрямую исследовать кучу с помощью Memory View отладчика IntelliJ IDEA:

Эта функция показывает все объекты, которые сейчас в памяти. Введем invaders и посмотрим, что найдем:

Классы плагина находятся в пакете com.github.nizienko.spaceinvaders. В этом пакете есть класс GameState с несколькими экземплярами. На первый взгляд, это то, что нам нужно.

Дважды щелкнув по GameState, увидим все экземпляры этого класса:

Оказывается, это перечисление (enum), что не совсем то, что нам нужно. Продолжая поиск, находим единственный экземпляр Game.

Раскрывая узел, можем изучить поля экземпляра:

Свойство health привлекает наше внимание. Среди его полей есть _value. У меня значение равно 100, что соответствует полной шкале здоровья при приостановке игры. Вероятно, это нужное нам поле, и его значение варьируется от 0 до 100.

Проверим эту гипотезу. Щелкните правой кнопкой по _value, затем выберите Set Value. Выберите значение, отличное от текущего, например 50.

На этом шаге получаем ошибку: Cannot evaluate methods after Pause action.

Проблема в том, что мы использовали Pause Program вместо точек останова, и у этой функции есть ограничения. Но мы можем обойти это небольшим трюком.

Я описывал его в одном из предыдущих постов про Pause Program. Если вы не читали эту статью, вот что нужно сделать: после приостановки приложения выполните шаг, например Step Into или Step Over. Благодаря чему появится возможность использовать расширенные функции, такие как Set Value и Evaluate Expression.

Теперь мы можем установить значение для health. Попробуйте изменить значение, затем возобновите приложение, чтобы увидеть изменения на шкале здоровья.

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

Метки и выражения

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

Метки бывают полезны по-разному. Благодаря тому, что мы отметили объект, мы можем напрямую использовать его в функциях, таких как Evaluate Expression, независимо от текущего контекста выполнения.

К сожалению, мы не можем напрямую пометить _value, но можем пометить объект, который его содержит. Для этого щелкните правой кнопкой по health, выберите Mark Object и дайте ему имя.

Теперь проверим, как работает метка. Откройте Evaluate Expression и введите health_object_DebugLabel в качестве выражения. Как видите, объект доступен из любого места программы через окно Evaluate:

Как насчет изменения здоровья корабля через Evaluate? Выражение health_object_DebugLabel._value = 100 не работает.

Похоже, что _value — это неявное поле (backing field) в Kotlin. Если так, Kotlin должен был сгенерировать соответствующий геттер:

health_object_DebugLabel.getValue()

Окно Evaluate не считает это корректным кодом, но все равно попробуем:

Выражение возвращает текущее здоровье корабля, значит, подход работает! Как и ожидалось, сеттер тоже работает:

health_object_DebugLabel.setValue(100)

После выполнения сеттера возобновим приложение и проверим, вступили ли изменения в силу. Да, шкала здоровья полная!

Подключение выражения

Остался последний шаг для достижения нашей цели — автоматизировать изменение состояния, чтобы пополнение здоровья происходило автоматически, позволяя нам играть без перерывов.

Это можно сделать с помощью non-suspending точек останова. Они обычно используются для логирования, но это не обязательно может быть только выражение логирования. Например, мы можем добавить нужный побочный эффект в выражение логирования. Но без исходного кода негде установить такую точку останова.

Помните, я говорил, что можем использовать исходники стандартной библиотеки Java/Kotlin? Идея в том, что IntelliJ IDEA и ее плагины написаны на Java/Kotlin и используют Swing для UI. Значит, Space Invaders вызывает код из этих зависимостей, и мы можем использовать их исходники для установки точек останова.

Примечание: Для простоты мы не указали версию JDK. Вместо этого инициализировали проект с версией, предложенной IntelliJ IDEA. Однако для лучших результатов рекомендуется использовать исходники, которые соответствуют версии, на которой работает ваша программа.

Есть много мест для установки точки останова. Я решил установить точку останова метода в java.awt.event.KeyListener::keyPressed. И теперь каждый раз при нажатии клавиши будет выполняться следующий код:

Примечание: Установка точки останова с выражением в часто вызываемом коде может значительно замедлить работу приложения.

Вернемся к Space Invaders и посмотрим, работает ли наш самодельный IDDQD. Работает!

Заключение

В этой статье мы использовали отладчик, чтобы узнать, как приложение работает изнутри. Мы смогли исследовать его память и изменить функциональность, не касаясь исходного кода! Надеюсь, мое сравнение отладчика с IDDQD не показалось слишком дерзким, и вы узнали техники, которые помогут вам в отладке.

Хочу выразить благодарность Евгению Низиенко за создание плагина Space Invaders и Егору Ушакову за постоянное вдохновение в отладке и программировании. С такими людьми компьютеры становятся вдвойне веселее.

Если у вас есть идеи для отладочных задач, о которых вы хотели бы прочитать в следующих статьях, дайте нам знать!

Удачного хакинга!

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Ждем всех, присоединяйтесь

Комментарии (0)