Skip to main content

macOS에서 디버깅하기

JavaScript 애플리케이션이 아닌 Electron 자체에서 발생한 것으로 보이는 크래시나 문제를 겪는다면, 네이티브/C++ 디버깅에 익숙하지 않은 개발자에게는 디버깅이 다소 까다로울 수 있다. 하지만 lldb와 Electron 소스 코드를 사용하면 Electron 소스 코드 내부에 중단점을 설정하고 단계별 디버깅을 진행할 수 있다. 그래픽 인터페이스를 선호한다면 XCode를 사용한 디버깅도 가능하다.

요구 사항

  • Electron 테스트 빌드: 가장 쉬운 방법은 소스 코드를 직접 빌드하는 것이다. 빌드 가이드에 따라 진행할 수 있다. 다운로드한 Electron에 직접 디버거를 연결해 디버깅할 수 있지만, 이 경우 최적화가 많이 되어 있어 디버깅이 상당히 어려워진다. 이때 디버거는 모든 변수의 내용을 보여주지 못할 수 있으며, 인라이닝(inlining), 테일 콜(tail calls), 그리고 기타 컴파일러 최적화로 인해 실행 경로가 이상하게 보일 수 있다.

  • Xcode: Xcode 외에도 Xcode 커맨드라인 도구를 설치해야 한다. 이 도구에는 macOS에서 Xcode의 기본 디버거인 LLDB가 포함되어 있다. LLDB는 데스크톱과 iOS 기기 및 시뮬레이터에서 C, Objective-C, C++ 코드를 디버깅할 수 있다.

  • .lldbinit: Chromium 코드가 소스 맵핑되도록 ~/.lldbinit 파일을 생성하거나 수정한다.

    # 예: ['~/electron/src/tools/lldb']
    script sys.path[:0] = ['<...path/to/electron/src/tools/lldb>']
    script import lldbinit

Electron에 연결하고 디버깅하기

디버깅 세션을 시작하려면 터미널을 열고 lldb를 실행한 후, 릴리스 버전이 아닌 Electron 빌드를 인자로 전달한다.

$ lldb ./out/Testing/Electron.app
(lldb) target create "./out/Testing/Electron.app"
Current executable set to './out/Testing/Electron.app' (x86_64).

중단점 설정하기

LLDB는 강력한 도구로, 코드 검사를 위한 다양한 전략을 지원한다. 이 기본 가이드에서는 JavaScript에서 호출한 명령어가 제대로 동작하지 않을 때, 해당 명령어의 C++ 구현 부분인 Electron 소스 코드 내부에서 중단점을 설정하는 방법을 알아본다.

관련 코드 파일은 ./shell/ 디렉토리에서 찾을 수 있다.

app.setName()을 디버깅하고 싶다고 가정해 보자. 이 함수는 browser.cc 파일에서 Browser::SetName()으로 정의되어 있다. breakpoint 명령어를 사용해 파일과 줄 번호를 지정하여 중단점을 설정한다:

(lldb) breakpoint set --file browser.cc --line 117
Breakpoint 1: where = Electron Framework`atom::Browser::SetName(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) + 20 at browser.cc:118, address = 0x000000000015fdb4

그런 다음, Electron을 실행한다:

(lldb) run

앱이 즉시 중단된다. Electron은 실행 시 앱의 이름을 설정하기 때문이다:

(lldb) run
Process 25244 launched: '/Users/fr/Code/electron/out/Testing/Electron.app/Contents/MacOS/Electron' (x86_64)
Process 25244 stopped
* thread #1: tid = 0x839a4c, 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100162db4 Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 20 at browser.cc:118
115 }
116
117 void Browser::SetName(const std::string& name) {
-> 118 name_override_ = name;
119 }
120
121 int Browser::GetBadgeCount() {
(lldb)

현재 프레임의 인자와 지역 변수를 확인하려면 frame variable (또는 fr v) 명령어를 실행한다. 이 명령어는 앱이 현재 "Electron"이라는 이름을 설정하고 있음을 보여준다.

(lldb) frame variable
(atom::Browser *) this = 0x0000000108b14f20
(const string &) name = "Electron": {
[...]
}

현재 선택된 스레드에서 소스 코드 레벨로 한 단계씩 진행하려면 step (또는 s) 명령어를 실행한다. 이 명령어는 name_override_.empty()로 이동하게 한다. 다음 단계로 넘어가려면 next (또는 n) 명령어를 실행한다.

(lldb) step
Process 25244 stopped
* thread #1: tid = 0x839a4c, 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119, queue = 'com.apple.main-thread', stop reason = step in
frame #0: 0x0000000100162dcc Electron Framework`atom::Browser::SetName(this=0x0000000108b14f20, name="Electron") + 44 at browser.cc:119
116
117 void Browser::SetName(const std::string& name) {
118 name_override_ = name;
-> 119 }
120
121 int Browser::GetBadgeCount() {
122 return badge_count_;

참고: 예상대로 소스 코드가 보이지 않는다면, 앞서 설명한 ~/.lldbinit 파일을 추가하지 않았을 수 있다.

이 시점에서 디버깅을 종료하려면 process continue 명령어를 실행한다. 또한 특정 줄에 도달할 때까지 스레드를 실행할 수도 있다 (thread until 100). 이 명령어는 현재 프레임에서 스레드를 실행하다가 해당 프레임의 100번째 줄에 도달하거나 프레임을 벗어나면 중단한다.

이제 Electron의 개발자 도구를 열고 setName을 호출하면 다시 중단점에 걸리게 된다.

추가 학습 자료

LLDB는 강력한 도구로, 훌륭한 문서를 제공한다. 더 깊이 이해하기 위해 Apple의 디버깅 문서를 참고할 수 있다. 예를 들어 LLDB 명령어 구조 참조독립형 디버거로 LLDB 사용하기 소개 자료를 살펴보는 것이 좋다.

또한, LLDB의 훌륭한 매뉴얼과 튜토리얼을 확인할 수 있다. 이 자료는 더 복잡한 디버깅 시나리오를 설명한다.