LLDB(Low-Level Debugger)는 LLVM 프로젝트의 차세대 디버거로, iOS, macOS, tvOS, watchOS용 애플리케이션 디버깅을 위해 Xcode에 포함되어 있습니다. GDB와 달리 LLDB는 LLVM 컴파일러와 함께 모듈식 아키텍처를 사용하여 높은 속도와 정확성을 제공합니다. LLVM 프로젝트에 따르면 LLDB는 C, Objective-C, C++ 및 Swift에서 중단점, 감시점, 메모리 검사 및 단계별 실행을 포함한 전체 기능 세트로 디버깅을 지원합니다.
핵심 요점
LLDB는 LLVM 프로젝트 라이브러리 위에 구축된 오픈 소스 디버거입니다. Xcode 5에서 GDB를 대체했으며 이후로 Apple 생태계 전체의 기본 디버깅 도구가 되었습니다. 모놀리식 GDB와 달리 LLDB는 상호 작용하는 라이브러리 집합으로 구현됩니다. 표현식 파싱부터 메모리 관리까지 각 기능이 별도의 모듈에 배치되어 유지 관리 및 확장이 간편합니다.
LLDB의 주요 기능은 다음과 같습니다: 모든 유형의 중단점 설정, 변수 변경 추적을 위한 감시점, 메모리 및 레지스터 검사, 단계별 실행, 중지된 프로그램의 컨텍스트에서 임의 표현식 평가, 자동화를 위한 Python 스크립트 실행. LLVM 저장소에 따르면 LLDB는 200개 이상의 디버그 명령어를 지원하며 Apple 생태계의 주요 디버그 정보 형식인 DWARF 및 Mach-O 형식과 호환됩니다.
LLDB의 중요한 장점은 Clang과의 깊은 통합입니다. 동일한 컴파일러를 사용하여 소스 코드를 파싱하고 컴파일함으로써 LLDB는 GDB에서는 불가능한 정확도로 C++ 및 Objective-C 표현식을 평가할 수 있습니다. Swift 디버깅의 경우 LLDB는 옵셔널 타입, 프로토콜, 제네릭, ARC를 통한 메모리 관리 등 언어 의미론을 이해하는 별도의 Swift Language Runtime 모듈을 사용합니다.
LLDB의 첫 번째 버전은 2010년 LLVM 2.8의 일부로 등장했습니다. 2013년까지 Xcode에서 GDB를 완전히 대체했습니다. 2019년 Xcode 11 출시와 함께 LLDB는 Swift Error Breakpoints 지원과 Swift용 개선된 표현식 파서를 받았습니다. Apple에 따르면 iOS 14부터 시뮬레이터용 디버깅 스택 전체도 LLDB를 통해 작동하여 플랫폼의 기본 디버깅 도구로서의 지위를 확인했습니다.
LLDB 아키텍처는 마이크로서비스 원칙에 따라 구축됩니다. 각 하위 시스템은 공통 API를 통해 서로 연결된 별도의 라이브러리(dylib)로 존재합니다. 이는 모든 기능이 단일 바이너리에 결합된 GDB와 다릅니다. 모듈식 구조를 통해 LLDB 구성 요소를 독립적으로 사용할 수 있습니다. 예를 들어 표현식 파서는 전체 디버거를 연결하지 않고 IDE에 내장할 수 있습니다.
| LLDB 구성 요소 | 목적 | 라이브러리 |
|---|---|---|
| Core | 디버그 프로세스 관리, 이벤트, 스레드 상태 | liblldbCore.dylib |
| Expression Parser | 표현식 파싱 및 실행(C/C++/ObjC/Swift) | liblldbExpression.dylib |
| Symbol File | DWARF, Mach-O, dSYM 읽기 — 디버그 정보 작업 | liblldbSymbol.dylib |
| Target Control | 실행 제어: 시작, 중지, 단계 | liblldbTarget.dylib |
| Interpreter | 명령줄 및 REPL 모드 | liblldbInterpreter.dylib |
dSYM은 Xcode가 빌드 중에 생성하는 디버그 정보 파일입니다. LLDB는 이를 사용하여 기계 코드를 소스 코드에 매핑합니다. dSYM이 없으면 디버거는 함수 이름과 코드 줄 대신 메모리 주소만 표시합니다. App Store 애플리케이션의 경우 dSYM 파일은 Apple 서버에 별도로 업로드되어 CrashReporter를 통해 사용자로부터 받은 충돌 로그를 기호화하는 데 사용됩니다.
(lldb) target create MyApp.app
(lldb) image list MyApp
MyApp - "/path/to/MyApp.app/MyApp" (arm64)
(lldb) image lookup -n fetchUserData
Address: MyApp[0x1000a3b40] (MyApp.__TEXT.__text + 12352)
Summary: `ViewController.fetchUserData()` at ViewController.swift:42
LLDB 명령어는 실행 제어, 중단점 관리, 데이터 검사, 메모리 조작의 여러 범주로 나뉩니다. Xcode GUI와 달리 LLDB 콘솔은 디버깅을 완전히 제어하며 그래픽 인터페이스를 통해 사용할 수 없는 작업(예: 변수 값을 즉석에서 변경하거나 중단점 일괄 편집)을 수행할 수 있습니다.
Continue, Step Over, Step Into, Step Out은 디버그 사이클의 기본입니다. continue는 다음 중단점까지 실행을 재개합니다. step over는 현재 줄을 완전히 실행합니다. step into는 호출된 메서드 안으로 들어갑니다. step out은 현재 함수를 완료하고 호출 코드로 제어를 반환합니다. 추가로 step with type filter(지정된 데이터 형식까지 단계)도 있습니다.
(lldb) thread backtrace # 호출 스택 표시
* thread #1, queue = 'com.apple.main-thread'
frame #0: 0x1000a3b40 ViewController`fetchUserData()
frame #1: 0x1000a2000 ViewController`viewDidLoad()
frame #2: 0x1a2b345 UIKit`UIViewController.loadView()
(lldb) frame variable # 로컬 변수 표시
(Int) userId = 42
(String) endpoint = "https://api.example.com/user/42"
(lldb) thread step-over # Step Over
(lldb) thread step-in # Step Into
LLDB는 모든 형식으로 데이터를 볼 수 있는 명령어를 제공합니다: memory read, frame variable, target variable. 특수 구문 po(print object)는 Objective-C 객체의 debugDescription과 Swift 타입의 description을 호출합니다. 사용자 정의 포맷터는 type summary add로 설정합니다. CGRect나 IndexPath 같은 복잡한 구조체를 디버깅할 때 유용합니다.
(lldb) po userProfile # 객체 설명 출력
<UserProfile: 0x600000c4b80>
- name: "John"
- age: 30
- email: "john@example.com"
(lldb) expression userProfile.age = 31 # 값 변경
(Int) $R0 = 31
(lldb) memory read 0x600000c4b80 0x600000c4bc0
0x600000c4b80: 6a 6f 68 6e 00 00 00 00 1e 00 00 00 00 00 00 00
표현식 평가는 LLDB의 가장 강력한 기능 중 하나이며, GDB가 지배적이었던 시대에는 없었습니다. LLDB는 중지된 프로그램의 컨텍스트에서 C, Objective-C, C++, Swift로 임의 코드를 실행할 수 있으며, 메서드 호출, 객체 생성 및 상태 변경이 포함됩니다. 이를 통해 애플리케이션을 다시 시작하고 다시 컴파일하지 않고도 가설을 테스트할 수 있습니다.
expression 명령어는 디버깅 중인 프로세스의 런타임에서 표현식을 컴파일하고 실행합니다. -O 플래그(object description)는 po를 트리거합니다. 여러 줄 표현식의 경우 expression -l Swift --를 사용합니다. LLDB는 Clang 또는 Swift Compiler를 통해 즉시 코드를 컴파일하고 결과를 현재 컨텍스트에 통합한 다음 값을 반환합니다. Apple에 따르면 표현식은 복잡성에 따라 10~50ms 안에 컴파일됩니다.
(lldb) expr -l Swift -- UIAlertController(title: "Test", message: nil,
preferredStyle: .alert)
(lldb) expr let $arr = [1, 2, 3].map { $0 * 2 }
(lldb) po $arr
▿ 3 elements
- 0 : 2
- 1 : 4
- 2 : 6
LLDB는 디버깅 중에 객체와 변수의 상태를 읽을 뿐만 아니라 수정할 수도 있습니다. 이는 경계 케이스 테스트에 중요합니다. 변수를 nil로 설정하거나, UI 요소의 색상을 변경하거나, 서버 응답을 디버거 내에서 직접 대체할 수 있으며, 다시 컴파일하거나 다시 시작할 필요가 없습니다. 이 기술은 다시 시작하는 데 시간이 많이 걸리는 게임 개발 및 긴 흐름의 애플리케이션에서 널리 사용됩니다.
(lldb) expr self.label.text = @"Updated"
(lldb) expr -l Swift -- (self as! UIViewController).view.backgroundColor = .red
(lldb) expr let $snapshot = self.view.debugQuickLookObject()
LLDB의 Python API를 사용하면 디버깅 자동화를 위한 스크립트를 작성할 수 있습니다. Python을 통해 사용자 정의 명령어 생성, 중단점 이벤트 처리, 보고서 생성, 디버거 동작 재정의까지 가능합니다. 내장 Python 3 인터프리터는 lldb 모듈을 통해 전체 디버깅 API에 액세스하면서 LLDB 내에서 직접 실행됩니다.
Python 스크립트에서 @classmethod 데코레이터를 통해 새 LLDB 명령어를 등록할 수 있습니다. 스크립트를 가져오면 명령어가 내장 명령어처럼 사용 가능해집니다. 예를 들어 printvars 명령어는 현재 프레임의 모든 변수를 해당 유형 및 값과 함께 특정 프로젝트에 맞게 형식화하여 출력할 수 있습니다. Stack Overflow의 iOS 개발자 설문 조사에 따르면 자동화는 일반적인 디버그 작업 시간을 60~80% 단축합니다.
import lldb
class PrintVarsCommand:
@classmethod
def register_class(cls, debugger, _):
handler = PrintVarsCommand()
debugger.HandleCommand('command script add -c \
print_vars.PrintVarsCommand printvars')
def __call__(self, debugger, command, exe_ctx, result):
frame = exe_ctx.frame
for var in frame.variables:
result.AppendMessage(f"{var.name}: {var.type} = {var.value}")
Python API를 통해 스크립트를 중단점 발생에 바인딩할 수 있습니다. 중단점을 설정한 다음 breakpoint command add를 실행하고 Python 함수를 지정합니다. 이를 통해 수동 개입 없이 자동으로 상태를 기록하거나, 데이터를 분석에 보내거나, 불변 조건을 확인할 수 있습니다. LLVM에 따르면 이 접근 방식은 Apple 인프라에서 개발 중 성능 메트릭 수집에 사용됩니다.
(lldb) breakpoint set -f Model.swift -l 100
(lldb) breakpoint command add 1 -s python -o "frame = exe_ctx.frame;
print([var.name for var in frame.variables])"
REPL(Read-Eval-Print Loop)은 LLDB의 대화형 모드로, lldb --repl 명령어 또는 Xcode 디버그 콘솔을 통해 호출됩니다. REPL에서는 플레이그라운드처럼 Swift 또는 C 코드를 실행할 수 있으며 즉각적인 피드백을 받을 수 있습니다. LLDB는 각 줄을 컴파일하고 실행한 다음 결과를 표시합니다. API 실험, 알고리즘 프로토타이핑, 프로젝트를 만들지 않고 언어의 새로운 기능을 배우는 데 편리합니다.
(lldb) --repl
1> let numbers = [1, 2, 3, 4, 5]
2> numbers.filter { $0 % 2 == 0 }
$R0: [Int] = 2 values {
[0] = 2
[1] = 4
}
3> let result = numbers.reduce(0, +)
$R1: Int = 15
REPL 모드는 import를 통한 모듈 및 프레임워크 로드도 지원합니다. 예를 들어 REPL에서 import UIKit은 전체 UIKit 라이브러리를 로드하며 UI 요소 생성, 제약 조건 확인, 애니메이션 테스트가 가능합니다. 이는 GDB에서는 불가능한 iOS 개발자를 위한 고유한 기능으로, 동일한 환경에서 디버깅과 프로토타이핑을 할 수 있습니다.
Swift 컴파일러와의 통합 덕분에 LLDB REPL은 Apple 강좌에서 Swift를 가르치는 데 사용됩니다. 학생들은 프로젝트 설정에 방해받지 않고 코드를 한 줄씩 실행하고 유형과 결과를 볼 수 있습니다. 이 접근 방식은 액티브 러닝 방법론을 따르며, 컴퓨터 과학 교육 연구에 따르면 대화형 피드백이 자료 이해를 40% 가속화합니다.
자주 묻는 질문
LLDB는 모듈식 LLVM 아키텍처 위에 구축되어 표현식 평가 속도와 최신 언어(Swift) 지원에서 이점을 제공합니다. GDB는 Swift를 지원하지 않고 스크립팅 기능이 제한된 모놀리식 디버거입니다.
xcode-select --install로 Command Line Tools를 설치한 다음 터미널에서 lldb --repl을 실행합니다. LLDB는 /Library/Developer/CommandLineTools/usr/bin/에 있습니다.
네, lldb --attach-pid PID 또는 process attach --name AppName으로 가능합니다. LLDB가 프로세스를 일시 중지한 후 애플리케이션을 다시 시작하지 않고 모든 표준 디버그 명령어를 사용할 수 있습니다.
dSYM 디버그 정보 파일이 누락되었습니다. Build Settings를 확인하세요. Generate Debug Symbols는 YES여야 하고 Debug Information Format은 DWARF with dSYM File이어야 합니다.
LLDB는 자동으로 기록을 ~/.lldb/lldb-history에 저장합니다. 내보내려면 session save filename.txt를 사용하세요. 이 명령어는 현재 세션의 모든 실행된 명령어를 텍스트 파일에 저장합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.