Tutorials

IntelliJ IDEA 디버거로 JVM 애플리케이션 해킹하기

Read this post in other languages:

옛날의 컴퓨터 게임은 지금과는 달랐습니다. 이후에 그래픽과 기계학이 진화하기도 했고, 옛날에는 오늘날의 게임에서는 매우 흔하지 않은 특성인 치트 코드를 가지고 있었습니다.

치트 코드는 특별한 무언가를 제공하는 키의 순서로, 치트 코드의 예로는 무한의 탄약 또는 벽을 걸어다니는 능력 등이 있습니다. 가장 보편적이고 강력한 것 중 하나는 “갓 모드”로, 사용자를 무적으로 만드는 것이었습니다.

갓 모드가 활성화 된 Doom의 해병의 스크린샷

위의 이미지는 Doom에서 갓 모드용 키 조합인 IDDQD를 입력했을 때의 캐릭터 모습입니다. 사실, 이 특정 키 순서는 게임 자체를 넘어 이 될 정도로 매우 인기가 있었습니다.

갓 모드가 과거에는 게임에서 널리 알려져 있었지만, 게임 속의 IDDQD 밈 시대가 지나가는 것으로 보인다면, 현재 이 밈과 대응하는 것은 무엇일지 궁금하실 것입니다. 저는 개인적으로 IDDQD의 현대적인 해석은 디버거라고 생각합니다. 게임과 직접적으로 관련이 있다기보다는, 초능력을 갖고 있다는 것에서 유사하다고 생각합니다.

스페이스 인베이더

제 생각 좀 더 설명하기 위해 재미있는 시나리오를 살펴 보겠습니다. Doom에 익숙하지 않더라도 아마 더 오래된 게임인 Space Invaders(스페이스 인베이더)를 아시는 분이 더 많을 것입니다. Doom처럼 스페이스 인베이더의 줄거리도 우주에서 침략자와 싸우는 것을 주제를 합니다.

저의 친구이자 동료인 Eugene Nizienko가 이 게임을 에디터에서 바로 플레이할 수 있도록 하는 IntelliJ IDEA 플러그인을 작성했습니다. 색인 생성이 완료되는 것을 기다리는 동안 시간을 보내기에 좋을 것 같습니다.

IntelliJ IDEA 에디터에서 스페이스 인베이더 플레이하기

이 게임에는 갓 모드가 없지만, 만약 원한다면 갓 모드도 추가할 수 있지 않을까요? 그럼 이제부터 디버거로 프로그램을 해킹하는 클래식한 전통을 알아보겠습니다.

주의 책임감있게 행동하세요. 저는 프로그램을 수정하기 전에 원 제작자인 Eugene의 동의를 받았습니다. 본인의 코드가 아닌 코드에서 디버거를 사용할 경우, 윤리적으로 행동해야 합니다. 윤리적으로 행동할 수 없다면, 아무것도 하지 않아야 합니다.

도구 준비

메타 경험을 준비 하세요 – 자체 디버거를 사용하여 IntelliJ IDEA를 디버그 할 에정입니다.

하지만 여기에는 작은 문제가 있습니다. IntelliJ IDEA를 디버그를 하려면 , 중지해야 하는데, 이로 인해 IDE는 응답하지 않게 됩니다. 따라서 이 상태를 유지하고 디버그 도구 역할을 할 추가 IDE 인스턴스를 필요로 합니다.

여러 IDE 인스턴스를 관리하기 위해, 저는 JetBrains Toolbox App을 사용하겠습니다. 이 애플리케이션은 JetBrains IDE들을 체계적으로 정리하여 같은 IDE의 여러 버전을 설치하거나 다른 VM 옵션 모음으로 실행할 수 있는 단축키를 생성할 수 있습니다.

IntelliJ IDEA의 두 인스턴스를 설치하겠습니다.

Space Invaders와 Debug라는 IntelliJ IDEA의 두 인스턴스를 포함한 여러 JetBrains IDE를 보여주는 JetBrains Toolbox

두 인스턴스 모두 같은 IDE 버전을 사용하는 경우, 두 인스턴스의 시스템, 구성, 로그 디렉터리를 다르게 지정하는 것이 중요합니다. 이는 Tool actions(도구 액션) | Settings(설정) | Configuration(구성) 에서 설정할 수 있습니다 이 페이지에서 IDE 인스턴스에 이름을 지정할 수도 있으며, 저는 ‘Space Invaders’와 ‘Debug’라고 이름을 붙였습니다.

Space Invaders 인스턴스를 디버그 할 수 있도록 하려면, 그 근처에 Tool actions(도구 액션)를 클릭한 다음 Settings(설정) | Edit JVM options(JVM 옵션 편집)로 이동합니다. 파일이 열리면, 다음 줄을 붙여넣습니다.

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

IDE 인스턴스에 전달될 VM 옵션이 있는 파일

이는 타깃 JVM이 디버그 에이전트로 실행되게 하고 포트 5005에서 들어오는 디버거 연결을 리스닝할 수 있습니다.

게임 실행하기

Space Invaders액션을 실행하여 ‘Space Invaders’ 인스턴스를 실행하고, 게임을 설치하고 실행합니다. 액션을 찾으려면 Shift를 두 번 누르고 “Space Invaders”를 입력하세요.

Shift를 두 번 누르면 나타나는 대화상자에서 Space Invaders 액션을 실행하기

게임을 조금 해 보고 수정하고 싶은 동작을 관찰하겠습니다. 우주선이 적 미사일을 맞으면, 왼쪽 상단 모서리의 헬스 바에서 체력을 잃습니다.

연결 및 일시 중지

새로운 Kotlin 프로젝트를 설정하고 “Debug” IDE 인스턴스를 열어 디버그 여정을 시작하겠습니다. 이 프로젝트는 프로젝트 없이는 디버거를 실행할 수 없기 때문에 필요합니다.

또한, IntelliJ IDEA는 새 프로젝트 템플릿에 Java/Kotlin 표준 라이브러리를 포함시키며, 이를 나중에 사용할 수도 있습니다. 이 글 후반 부에 표준 라이브러리의 사용에 대해 자세히 설명하겠습니다.

프로젝트를 생성한 후 메인 메뉴로 이동하고 Run(실행) | Attach to Process(프로세스에 연결)를 선택합니다. 디버거 연결 요청을 기다리는 로컬 JVM의 목록이 표시됩니다. 목록에서 다른 실행 중인 IDE를 선택합니다.

로컬에서 실행 중인 JVM 목록이 나타난 팝업

콘솔에 다음 메시지가 콘솔에 표시되어 디버거가 타깃 VM에 성공적으로 연결되었음을 확인해야 합니다.

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 인스턴스에 대한 Debug 도구 창

애플리케이션이 일시 중지되면 디버그를 시작할 수 있습니다.

: Pause Program(프로그램 일시 중지)는 몇 가지 고급 시나리오에서 특히 도움이 되는 매우 강력한 기술입니다. 자세히 알아보려면, 관련 글을 확인하세요.

관련 객체 찾기

프로그래밍 관점의 목표는 우주선의 체력이 줄어들지 않게 하는 것입니다. 이제 해당 상태를 유지하는 객체를 찾아 보겠습니다.

현재 플러그인 코드에 대해 전혀 모르지만, 그래도 IntelliJ IDEA 디버거의 Memory(메모리) 뷰를 사용하여 힙를 직접 검사할 수 있습니다.

Debug 도구 창의 오른쪽 상단에서 Layout Settings를 클릭하면 나타나는 메뉴

이 기능은 현재 살아있는 모든 객체에 대한 정보를 제공합니다. invaders를 입력하고 정보를 찾을 수 있는지 보겠습니다.

Memory(메모리) 뷰의 검색 필드에 invaders를 입력하면 spaceinvaders 패키지에 속한 클래스의 객체가 보여집니다.

명백하게 plugin 클래스는 com.github.nizienko.spaceinvaders 패키지 안에 있습니다. 이 패키지 안에는 GameState라는 클래스가 있고 이 클래스는 여러 개의 라이브 인스턴스가 있습니다. 처음 보면, 이것이 제가 찾고 있는 것 같습니다.

GameState를 두 번 클릭하면 이 클래스의 모든 인스턴스가 표시됩니다.

GameState 라이브 인스턴스를 보여주는 대화상자

결과를 보니, 이는 열거형으로 제가 찾고 있던 것이 아닌 것을 알 수 있습니다. 검색을 계속해 보니 Game의 단일 인스턴스를 발견했습니다.

노드를 확장하면 인스턴스의 필드를 조사할 수 있습니다.

객체 필드를 보여주는 메모리 보기의 확장된 객체 노드

여기서 관심있는 것은 health 속성으로, 필드 중 _value를 찾았습니다. 제 경우에는, 값은 100으로, 게임을 중지했을 때 헬스 바가 가득 찼을 때와 일치합니다. 따라서 이것이 고려해야 할 올바른 필드일 확률이 높으며, 그 값은 0에서 100까지 범위를 가집니다.

이제 이 가설을 테스트하겠습니다. _value를 마우스 오른쪽 버튼으로 클릭한 후 Set Value(값 설정)를 선택합니다. 여기에서 현재의 값과 다른 값을 선택하세요. 예를 들어, 저는 50을 선택하겠습니다.

사용자가 입력한 값 50을 포함하는 'health' 필드에 대한 텍스트 필드가 있는 Memory(메모리) 뷰

이 단계에서 Cannot evaluate methods after Pause action(일시 정지 액션 이후 메서드를 평가할 수 없음)이라는 에러가 나타납니다.

Cannot evaluate methods after Pause action(일시 정지 액션 이후 메서드를 평가할 수 없음)이라고 표시되는 오류 메시지

문제는 제가 중단점 대신 Pause Program(프로그램 일시 중지)를 사용했기 때문에 발생하며, 이 기능은 일부 제한 사항이 있습니다. 그러나 이를 해결하기 위한 방법이 있습니다.

그 방법은 Pause Program(프로그램 일시 중지) 액션을 다룬 글 에서 확인하실 수 있습니다. 이 글을 못 보신 분을 위해 설명하면, 애플리케이션을 일시 중지한 후, Step Into(스텝인투) 또는 Step Over(스텝오버)와 같은 단계별 실행 작업을 하면 됩니다. 이렇게 하면 Set Value(값 설정)Evaluate Expression(표현식 평가)과 같은 고급 기능을 사용할 수 있게 됩니다.

이제 health의 값을 설정할 수 있습니다. 값을 수정해 보고, 애플리케이션을 계속 실행하여 헬스 바에 변경 사항이 표시되는지 확인합니다.

이제 관련 상태를 보유하는 객체를 찾아냈으며, 최소한 수동으로 헬스 바를 다시 채울 수도 있습니다. 아직 완전히 안전하지는 않지만, 점점 나아지고 있습니다.

레이블 및 표현식

이제 집중할 객체를 식별했으므로, 이를 표기하는 것이 좋습니다. 표시된 객체는 다음과 같습니다.

User 객체의 배열을 보여주는 변수 탭, 그 중 하나는 User_Charlie라는 디버그 레이블로 표시됨

레이블은 여러 방면에서 유용할 수 있습니다. 이 글의 맥락에서, 관련 객체를 표시하면 현재 실행 컨텍스트에 의존하지 않고 Evaluate Expression(표현식 평가)과 같은 기능을 사용할 수 있습니다.

안타깝게도, _value를 직접 표시하는 것은 불가능하지만, 이를 둘러싼 객체를 표시할 수는 있습니다. 이렇게 하려면, health를 마우스 오른쪽 버튼으로 클릭하고, Mark Object(객체에 표시)를 선택하고 이름을 지정합니다.

사용자에게 객체의 이름을 입력하라는 Select Object Label 대화상자

이제 레이블이 다른 곳에서 어떻게 작동하는지 테스트할 수 있습니다. Evaluate Expression(표현식 평가) 대화 상자를 열고 표현식에 health_object_DebugLabel을 입력합니다. 보시다시피, 객체는 프로그램의 어디서나 Evaluate(평가) 대화상자를 통해 접근할 수 있습니다.

디버그 레이블을 표현식으로 입력한 Evaluate(평가) 대화 상자

우주선의 체력 상태를 Evaluate(평가)에서 변경하는 것은 어떨까요? health_object_DebugLabel._value = 100은 작동하지 않습니다.

동시에, _value는 Kotlin 속성의 백킹 필드로 보입니다. 이것이 사실이라면, Kotlin은 대응하는 getter를 생성했을 것입니다.

health_object_DebugLabel.getValue()

Evaluate(평가) 대화상자는 이것이 유효한 코드라고 생각하지 않지만, 일단 시도해 보겠습니다.

Evaluate(평가) 대화 상자에서 디버그 레이블을 통해 속성을 참조

표현식은 현재 우주선의 체력 상태를 반환하므로 이 접근 방식은 작동합니다! 예상하셨겠지만, setter도 작동합니다.

health_object_DebugLabel.setValue(100)

setter를 평가한 후에는 애플리케이션을 다시 시작하고 변경 내용이 적용되었는지 확인해 보면, 전체 헬스 바를 볼 수 있습니다!

표현식 훅

목표에 도달하기 위해 남은 유일한 단계는 상태 수정을 자동화하여 체력이 백그라운드에서 충전되게끔 하는 것입니다. 이렇게 하면 방해받지 않고 게임 플레이를 즐길 수 있습니다.

이 작업은 일시 중지되지 않은 중단점을 사용하여 할 수 있습니다. 이 중단점 타입은 일반적으로 로깅에 사용되지만, 로깅 표현식은 반드시 순수할 필요는 없습니다. 따라서, 로깅 표현식 내에서 원하는 부수 효과를 도입할 수 있습니다. 하지만 소스 코드가 없다면, 중단점을 설정할 곳이 어디인지 알 수 없습니다.

Java/Kotlin 표준 라이브러리의 소스를 사용할 수 있을 것이라고 말했던 것을 기억하시나요? 여기서 중요한 점은, IntelliJ IDEA와 관련 플러그인은 Java/Kotlin으로 작성되었고, Swing을 UI 프레임워크로 사용한다는 것입니다. 결과적으로, Space Invaders는 이 종속성에서 코드를 호출합니다. 즉, 소스를 사용하여 중단점을 설정할 수 있다는 것을 의미합니다.

주의: 간단하게 하기 위해, JDK 버전을 지정하지 않았습니다. 대신 IntelliJ IDEA가 제안한 버전으로 프로젝트를 초기화했습니다. 그러나, 최고의 결과를 얻기 위해, 프로그램을 실행하는 데 사용된 버전에 가까운 소스를 사용하는 것을 권장합니다.

중단점을 설정할 수 있는 많은 위치가 있지만, java.awt.event.KeyListener::keyPressed에서 메서드 중단점을 설정하기로 결정했습니다. 이렇게 하면 키를 누를 때마다 부수 효과를 트리거 합니다.

java.awt.event.KeyListener::keyPressed에 대한 로깅 중단점을 보여주는 중단점 대화상자

주의: 핫 코드에서 표현식이 있는 중단점을 설정하면 타깃 애플리케이션 속도가 크게 느려질 수 있습니다.

Space Invaders로 돌아가서 직접 만든 IDDQD가 작동하는지 확인해 보면, 동작합니다!

IntelliJ IDEA에서 Space Invaders를 플레이하는 모습 - 우주선이 맞을 때마다 헬스 바가 자동으로 충전됩니다

결론

이 글에서는 디버거를 사용하여 애플리케이션이 어떻게 작동하는지 알아보았습니다. 그리고 메모리를 통해 탐색하고 기능을 수정했습니다. 이 모든 작업은 애플리케이션의 소스에 손대지 않고도 할 수 있었습니다. 디버거를 IDDQD에 비교한 것이 너무 대담하거나 뻔뻔한 것으로 받아들여지지 않았기를 바라며, 디버그 과제에서 여러분에게 도움이 될 기술을 배울 수 있으셨기를 바랍니다.

Space Invaders 플러그인을 만든 Eugene Nizienko와, 디버그와 프로그래밍에 대한 끊임없는 영감의 원천이 되어 준 Egor Ushakov에게 큰 감사의 말을 전합니다. 여러분들 덕에 컴퓨터로 하는 모든 작업이 훨씬 즐겁습니다.

앞으로 제가 디버그와 관련하여 다루길 원하는 주제가 있다면 알려주세요.

즐겁게 해킹하세요!

게시물 원문 작성자

Igor Kulakov

Igor Kulakov

image description