- LLVM 프론트엔드에 대응하는 디버거
- LLVM은 Apple 에서 진행한 Compiler에 필요한 Toolchain 개발 프로젝트로 컴포넌트들의 재사용성을 중시해서, 모듈화가 잘 되어있다는 특징이 있음. 이렇게 모듈화 되어있는 컴포넌트들을 이용해 진행된 주요 서브 프로젝트들로는 LLVM Core, Clang, libc++, LLDB 등이 존재
- LLDB는 LLVM의 Debugger Component를 개발하는 서브 프로젝트. LLVM 프로젝트를 통해 개발된 Clang Expression Parser, LLVM Diassembler 등 Low-Level 컨트롤이 가능한 모듈들로 이루어져 있어, 기계어에 가까운 영역까지 디버깅 가능하다는 장점이 있음
- C, C++, Objective-C, Swift를 지원하며, 현재 Xcode의 기본 디버거로 내장되어 있음.
(lldb) command [subcommand] -option "this is argument"
-
Command, Subcommand, Option, Argument들로 이루어져 있고, 띄어쓰기로 구분
- Command와 Subcommand는 LLDB 내 Object의 이름. (etc. breakpoint, watchpoint, set, list … ) 이들은 모두 계층화되어있어, Command에 따라 사용가능한 Subcommand 종류가 다름
- Option의 경우, Command 뒤 어느 곳에든 위치 가능하며,
-
(hyphen) 로 시작 - Argument에 공백이 포함 되는 경우도 있기 때문에,
""
로 묶어줄 수 있음
예시
(lldb) breakpoint set --file test.c --line 12
breakpoint set
: breakpoint (Command)와 set (Subcommand)을 이용해 중단점 설정--file test.c
: --file option을 통해 test.c 파일의--line 12
: --line option을 통해 12번째 라인에
- 해당 문법으로 사용가능한 Subcommand, Option 리스트나 사용법을 보여줌
# LLDB에서 제공하는 Command가 궁금하다면,
(lldb) help
# 특정 Command의 Subcommand나, Option이 궁금하다면,
(lldb) help breakpoint
(lldb) help breakpoint set
- 원하는 기능의 명령어가 있는지 관련 키워드를 통해 알아볼 수 있음
# referent count를 체크할 수 있는 명령어가 있을까? 궁금하다면,
(lldb) apropos "reference count"
# 결과
# The following commands may relate to 'reference count':
# refcount -- Inspect the reference count data for a Swift object
- 현재 프로그램을 중단하고, 새로운 Build/Run을 진행
(lldb) run
- 간단하게 Breakpoint 생성을 할 수 있도록 도와주는 Shorthand Command
# 특정 이름을 가진 function에서 break
(lldb) b viewDidLoad
# 현재 파일 20번째 line에서 break
(lldb) b 20
# 특정 파일 20번째 line에서 break
(lldb) b ViewController.swift:12
# 현재 파일 내 특정 text를 포함한 line에서 break
(lldb) b /stop here/
# 특정 주소값에서 break
(lldb) b 0x1234000
breakpoint
->br
로 치환 가능set
->s
로 치환 가능
–name
(-n
) : 특정 이름을 가진 모든 함수에 break를 걸 수 있음
# 함수 이름 이용해 break
(lldb) breakpoint set --name viewDidLoad
👩🏻💻 --name을 -name 처럼 option에 - 한개만 줘도 동작하는 듯. 근데 자동완성이 안됨
–func-regex
(-r
) : 정규표현식 활용 가능
(lldb) breakpoint set --func-regex '^hello'
(lldb) br s -r '^hello'
# 'breakopint set --func-regex'는 줄여서 'rb'로 사용 가능
(lldb) rb '^hello'
-
–file
(-f
),–line
(-l
) : 파일의 이름과 line 번호를 이용해 break 걸기# 특정 파일의 20번째 line에서 break (lldb) br s --file ViewController.swift --line 20 (lldb) br s -f ViewController.swift -l 20
-
-–contidion
(-c
) : breakpoint에 원하는 조건 걸기. (option 뒤의 expression이 true인 경우에만 멈춤)
# viewWillAppear 호출시, animated가 true인 경우에만 break
(lldb) breakpoint set --name "viewWillAppear" --condition animated
(lldb) br s -n "viewWillAppear" -c animated
(lldb) br s -l 27 -condition 'helloWorld == "hello World"'
-command
(-C
) : break시 원하는 lldb command를 실행 가능
# –auto-continue (-G) 는 command 실행 후 break에 걸린채로 있지 않고 프로그램을 자동 진행하게 해줌
(lldb) breakpoint set -n "viewDidLoad" --command "po $arg1" -G1
(lldb) br s -n "viewDidLoad" -C "po $arg1" -G1
-one-shot
(-o
) : 브레이크 포인트가 한번만 실행되고 자동으로 사라짐
(lldb) breakpoint set --one-shot true -f ViewController.swift -l 90
(lldb) br s -o -f ViewController.swift -l 91
- 물론 위에서 설명한 옵션들 중 일부는 UI에서 설정할 수도 있음 (-condition, -command, -auto-continue 등)
🤯🤯🤯🤯 여기서 Action 쪽이 오져버리는게 뭐냐면 만약 내가 tableView.delegate = self
코드를 추가 안해서 에러난거 같다? 싶으면 그 코드 추가한 후에 다시 확인을 위해 run 해야 했음 -> 근데 추가해야하는 부분에 브포 걸고 Action 으로 추가해준뒤 해당 코드 들어오게 trigger 한다? 그리고 잘 된다? 그럼 굳이 run 안해도 되는거;;; ㅁㅊㄷ ㅁㅊㅇ;;;;
(lldb) breakpoint set -line 64 -command "expression textView.delegate = self" -auto-continue 1
(lldb) br s -l 64 -C "expression textView.delegate = self" -G1
- breakpoint와 유사하지만 다음에 해당 값이 변경될 때 디버거를 일시 중지
# 변수 이름으로 watchpoint 설정
(lldb) watchpoint set variable my_var
(lldb) wa s v my_var
# 메모리 주소에 watchpoint 설정
(lldb) watchpoint set expression -- my_ptr
(lldb) wa s e -- my_ptr
- 하단 variable views에서 관찰하고 싶은 속성 우클릭하면 Watch "myProperty" 뜨는데 이걸로도 설정 가능
- 현재 프로그램에 생성되어있는 Breakpoint의 목록 확인 가능
# breakpoint 목록 전체 출력
(lldb) breakpoint list
(lldb) br list
# 특정 id를 가진 breakpoint의 정보만 출력
(lldb) br list 1
/*
Current breakpoints:
1: file = '/Users/kangsujin/Desktop/sample/sample/ViewController.swift', line = 13, exact_match = 0, locations = 1, resolved = 1, hit count = 1
1.1: where = sample`sample.ViewController.viewDidLoad() -> () + 20 at ViewController.swift:13:9, address = 0x00000001024df674, resolved, hit count = 1
*/
-
해당 목록 정보에는 Breakpoint의 id와 이름, hit-count 정보, enable 여부, source 상의 위치, 주소값 등의 정보가 포함
hit count?
프로그램 실행 중 활성 상태인 Breakpoint 지점이 실행되면, Debugger는 hit count를 1씩 늘려가며 기록. 하지만 Breakpoint가 걸려있다 하더라도, disable 상태이면 count되지 않음.
–brief
(-b
) option을 통해 간단한 내용을 확인해 볼 수도 있음
# breakpoint 목록 간단하게 출력
(lldb) br list -b
/*
Current breakpoints:
1: file = '/Users/kangsujin/Desktop/sample/sample/ViewController.swift', line = 13, exact_match = 0, locations = 1, resolved = 1, hit count = 1
2: file = '/Users/kangsujin/Desktop/sample/sample/ViewController.swift', line = 24, exact_match = 0, locations = 1, resolved = 1, hit count = 3
*/
# breakpoint 전체 삭제
(lldb) breakpoint delete
(lldb) br de
# 특정 breakpoint 삭제 (br list 로 확인하면 나오는 breakpoint id)
(lldb) br de 1
# breakpoint 전체 비할성화
(lldb) breakpoint disable
(lldb) br di
# 특정 breakpoint 비활성화
(lldb) br di 4
- 만약
br di 4
로 4번 breakpoint disable 시키고br list -b
로 찍어보면 아래 같이Options: disabled
뜸
4: file = '/Users/kangsujin/Desktop/sample/sample/ViewController.swift', line = 27, exact_match = 0, locations = 1 Options: disabled
br di 4.1
처럼br list
호출하면 나오는 세부 정보(?)로 breakpoint disable 시킬수도 있는듯. 근데 실상 결과는 같은 듯
4: file = '/Users/kangsujin/Desktop/sample/sample/ViewController.swift', line = 27, exact_match = 0, locations = 1
4.1: where = sample`sample.ViewController.aa() -> () + 677 at ViewController.swift:27:15, address = 0x000000010612fad5, unresolved, hit count = 1 Options: disabled
# breakpoint 전체 활성화
(lldb) breakpoint enable
(lldb) br en
# 특정 breakpoint 활성화
(lldb) br en 4
- Execution 명령들은 LLDB Console 상단에 위치한 네개 버튼과 같은 역할. 차례대로 Continue, Step-Over, Step-In, Step-Out 을 의미
- Thread가 생겨날 때, 해당 Thread를 위한 Stack이 만들어지며, 해당 Stack에는 Frame이 들어감
- 현재 스레드의 현재 스택 프레임에 대한 빠른 개요를 얻고 싶다면
frame info
명령어를 통해 확인할 수 있음 - 현재 스레드에 대한 정보는
thread info
로, 모든 스레드 정보는thread list
로 확인 가능
process continue
의 약어- 정지된 프로그램 실행을 재개. 다음 Breakpoint가 나타날때까지 프로그램을 진행
(lldb) process continue
(lldb) continue
(lldb) c
- process 명령어는 현재 플랫폼의 프로세스와 상호작용 할 수 있게 도와주는 명령어로
process status
같은 경우는 현재 디버거가 어디 있는지 찾을 수 있게 해줌
(lldb) process status
-
thread step-over
의 약어 -
현재 Break 걸려있는 지점에서 바로 다음 Statement로 이동. (현재 선택된 Frame에서 소스 수준의 한 단계를 진행)
(lldb) thread step-over
(lldb) next
(lldb) n
thread step-in
의 약어- 현재 선택된 Frame에서 소스 수준의 한 단계 안으로 들어감
- 다음 Statement가 Function Call 인경우 Debugger를 해당 함수 내부에 위치한 시작 지점으로 이동
(lldb) thread step-in
(lldb) step
(lldb) s
####finish
명령어
-
thread step-out
의 약어 -
현재 진행중인 function이 return 될때까지 프로그램을 진행한 후 프로그램 Break 걸어주는 Stepping Action (현재 선택된 Frame에서 벗어남)
-
Stack Memory 관점에서 Stepping Out은 Stack Frame을 Pop하는 것과 동일 (왼쪽 Debug Navigator를 잘 살펴보면 Stepping-In과 Stepping-Out 시 StackFrame이 어떻게 달라지는 지 확인 가능)
(lldb) thread step-out
(lldb) finish
- 단축어로
f
로 되지 않을까 싶었는데 그건frame
명령어의 약자였다
expression
(expr
,ex
,e
) 명령어는 LLDB에서 가장 유용하고 강력한 명령 중 하나로 현재 스레드에서 expression을 evaluateexpression
을 사용하면 코드를 실행할 수 있을 뿐만 아니라 프로젝트를 다시 컴파일하지 않고도 기존 변수를 수정할 수 있음
- expression 을 이용해서 특정 변수의 값을 출력할 수 있음
(lldb) e <variable> # 특정 변수의 값 출력
–-depth
(-D
) : aggregate type 을 dump 할 때 최대 recurse depth 설정 가능 (default 는 infinity)
(lldb) e -D 1 -- self
# 출력
(sample.SecondViewController) $R9 = 0x00007f855101c8f0 {
UIKit.UIViewController ={...}
textView = 0x00007f8552831800{...}
textViewMessage = ""{...}
}
-
expression -O --
의 축약형 (orexpression -object-description --
)여기서
-O
는 object의 설명(debugDescription)을 출력하겠다는 의미. option을 사용하면 반드시--
를 option과 raw input 사이에 넣어야함 (help po
나help expression
치면 설명 나옴)
(lldb) expression -O -- self
(lldb) po self
po
가 출력하는 description은NSObject
의debugDescription
. 따라서 해당 프로퍼티를 override 하면 po 결과 customize 가능
override var debugDescription: String {
return "sujinnaljin의 description \(super.debugDescription)"
}
(lldb) po self
# sujinnaljin의 description <sample.ViewController: 0x7fc66ed08850>
- struct 같이 NSObject 상속 안하는거에 커스텀 디버깅 인포 찍고싶다? 하면
CustomDebugStringConvertible
protocol conform
struct Trip {
var name: String
var desinations: [String]
}
extension Trip: CustomDebugStringConvertible {
var debugDescription: String { "Trip description" }
}
-
하지만 이는 top level description만 변경하기 때문에 substructure까지 변경하고 싶다하면
CustomReflectable
protocol conform 하면 됨. Objective-C 객체라면 description method를 implement하면 됨. -
po
는 변수만 출력하는 것이 아니라 프롬프트에 컴파일되는 모든 내용이 argument로 전달되어 arbitary expression을 수행할 수 있음
(lldb) po cruise.name.uppercased() # 대문자 계산
(lldb) po cruise.desinations.sorted() # 알파벳 정렬 순
-
Swift debugging context에서
expression [self.view recursiveDescription]
과 같이 Objective-C 표현을 사용하면 오류가 나는데 이럴때는 Objective-C 실행을 강제하기 위한-l
(-language
) 옵션 존재 -
기존 po Command에
-l objc
option을 추가해서 Swift Context안에서도 Objective-C Expression을 사용할 수 있고 그 반대도 마찬가지 (아마?)
(lldb) e -l objc -O -- [self.view recursiveDescription]
-
하지만 위의 코드는 에러가 나는데, 이유는 Objective-C 컴파일을 위한 임시 expression context를 컨텍스트를 생성하며 Swift 프레임에서 모든 변수를 상속하지 않기 때문.
-
이때 self.view에 backticks 을 추가해주면 되는데 이 의미는 '현재 frame 에서, backtick 안에 있는 것부터 먼저 evaluate 해서 결과에 넣은 후에 나머지를 evaluate 하는 것 ' 임.
(lldb) e -l objc -O -- [`self.view` recursiveDescription]
-
print
의 약자로expression --
명령어의 축약형 (참고로po
는expression -O --
의 alias)하지만
print
명령어는 flag나 추가적인 argument 를 받지 않음(lldb) e -raw -- cruise.name (Swift.String) $R16 = { _guts = { _object = { _countAndFlagsBits = { _value = -7427134353699732757 } _object = 0xa9000000000000b8 } } } (lldb) p -raw -- cruise.name error: <EXPR>:8:2: error: cannot find 'raw' in scope -raw -- cruise.name ^~~ error: <EXPR>:8:6: error: cannot find operator '--' in scope; did you mean '-= 1'? -raw -- cruise.name
-
LLDB에서 변수를 인쇄하는 또 다른 방법으로, 객체의 설명 없이 print 하는 것이라고 생각하면 됨
-
po에서 제공하는 표현과 약간 다르지만 동일한 정보를 담고 있음 (
po
는debugDescription
정보를 이용하고,p
나v
(frame variable
) 는 lldb의formatters
를 이용한다고 함)
# po cruise
(lldb) po cruise
▿ Trip
- name : "날진호"
▿ desinations : 1 element
- 0 : "Sorrento, Capri, Taormina"
# p cruise
(lldb) p cruise
(sample.Trip) $R4 = {
name = "날진호"
desinations = 1 value {
[0] = "Sorrento, Capri, Taormina"
}
}
- 주목할 점은 결과 값에는 $R0과 같은 이름이 지정된다는 것. 따라서
$R4.name
과 같이 참조 가능
frame variable
의 약자 (notexpression
)- output으로 출력되는 결과가 p 명령어와 같음. 왜냐? 출력시 p와 같은 formatter를 이용하기 때문
struct Trip {
var name: String
var subName: String {
return "sub" + name
}
var desinations: [String]
}
(lldb) p cruise
(sample.Trip) $R2 = {
name = "날진호"
desinations = 1 value {
[0] = "Sorrento, Capri, Taormina"
}
}
(lldb) v cruise
(sample.Trip) cruise = {
name = "날진호"
desinations = 1 value {
[0] = "Sorrento, Capri, Taormina"
}
}
- 다만 po나 p 명령어와 달리 코드를 compile & evaluate 하지 않으므로 속도가 매우 빠름. 메모리에 있는거 바로 읽어옴
- 코드를 컴파일하지 않기 때문에 현재 스택 프레임에 있는 변수에만 액세스 가능하고, computed property는 계산 불가 (exectued 되어야하기 때문)
(lldb) v cruise.subName
error: "subName" is not a member of "(sample.Trip) cruise"
(lldb) p cruise.subName
(String) $R1 = "sub날진호"
(lldb) v cruise.name.isEmpty
error: "isEmpty" is not a member of "(Swift.String) cruise.name"
(lldb) p cruise.name.isEmpty
(Bool) $R4 = false
- Runtime에 여러 정보를 출력할 수 있을 뿐아니라 값을 변경 할 수도 있음
(lldb) expression <expression> # evaluate expression
(lldb) e self.view.backgroundColor = .red
(lldb) c
- LLDB는 내부적으로 값이 출력될때마다 local variable을 $R~의 형태로 만들어 저장하는데 이 값들은 해당 break context가 벗어나도 사용 가능한 값들이고, 수정해서 사용할 수도 있음
(lldb) e self.view
(lldb) e $R0!.backgroundColor = UIColor.blue # R0 은 위의 e self.view 에서 self.view가 저장된 위치
(lldb) continue # 코드 마저 진행
expression
Command를 이용해서 변수를 직접 선언해서 사용할 수도 있음. 단, 사용하고자 하는 변수명 앞에$
문자를 붙여야 함
(lldb) expr let $someNumber = 10
(lldb) expr var $someString = "some string"
- 활용 예시
# 임의의 View 생성해서 얹기
(lldb) e var $view = UIView(frame: CGRect(x: 100, y: 100, width: 80, height: 80))
(lldb) e # `(lldb) expression` 명령어를 입력한 후 return키를 입력하면, Multi-line Command를 입력할 수 있음
Enter expressions, then terminate with an empty line to evaluate:
1 $view.backgroundColor = UIColor.blue
2 self.view.addSubview($view)
3
(lldb) c
# 임의의 UIViewController 생성하여 NavigationViewController에 Push
(lldb) expr var $vc = UIViewController()
(lldb) expr $vc.view.backgroundColor = UIColor.red
(lldb) expr self.navigationController?.pushViewController($vc, animated: true)
- 변수 말고도 method 및 class 도 만들어서 사용 가능
(lldb) po
Enter expressions, then terminate with an empty line to evaluate:
1 class $A {
2 var $b = 0
3 }
4
(lldb) po $A()
<$A: 0x600001bc87e0>
(lldb) po $A().$b
0
- extension에 함수를 만들어 사용할 수도 있음
(lldb) po
Enter expressions, then terminate with an empty line to evaluate:
1 extension ViewController {
2 func $changeBgColor() {
3 self.view.backgroundColor = .red
4 }
5 }
6
(lldb) po self.$changeBgColor()
--ignore-breakpoints
(-i
) option 을 통해 expression 실행 중 만나는 breakpoint를 ignore 여부 선택 가능. (default는 –ignore-breakpoint true)
# 그냥 expression 함수명을 입력하면 해당 함수를 실행함. 이때 함수 안에 걸려있는 break point는 무시
(lldb) expression helloWorld()
# 실행 도중 breakpoint를 만나도 그냥 진행 (default)
(lldb) expression -ignore-breakpoints true -- helloWorld()
(lldb) ex -i 1 -- helloWorld()
# 실행 도중 breakpoint를 만나면 멈춤
(lldb) expression -ignore-breakpoints false -- helloWorld()
(lldb) ex -i 0 -- helloWorld()
- 객체의 주소값과 Type만을 알고있는 경우 Swift의
unsafeBitCast(to:)
함수를 이용해 변수로 사용할 수 있음
(lldb) e let $myObject = unsafeBitCast(0xabcdef, to: UILabel.self) # 0xabcdef에 할당된 UILabel 에 접근해 변수로 선언
(lldb) e $myObject # 변수를 이용해 해당 object의 정보 알 수 있음
(lldb) e $myObject.text = "hello world" # 변경도 가능
- unsafeBitCast 이용하는건 특히 debug View Hierarchy 에서 유용한 듯
- 객체 선택 한 다음에 cmd + c 하면
((UITextView *)0x7f8746047600)
이런식으로 선택된 object 의 casted pointer 을 줌
# debug View Hierarchy 클릭해서 lldb 로 들어왔을 때는 기본적으로 objc context 인듯. 따라서 swift context로 만들어줌. 여기서 한번에 안해주면 아래 매 명령 앞에 e -l swift -- 붙여야함
(lldb) settings set target.language swift
# UIkit을 import하여 LLDB Console에서 UIButton type 사용할 수 있게 함 (view hierarchy 에서 lldb 로 들어왔으니까)
(lldb) e import UIKit
# 0xabcdef에 할당된 UIButton 에 접근해 값 변경
(lldb) e unsafeBitCast(0xabcdef, to: UIButton.self).backgroundColor = .red
# 디버거에서 일시 중지되어 있기 때문에 core animation에서 현재 화면의 프레임 버퍼에 뷰 모듈 변경 사항을 적용하고 있지 않음. 따라서 CATransaction 의 flush 함수를 통해 core animation이 screen의 frame buffer를 업데이트하도록 함
(lldb) e CATransaction.flush()
- 오토레이아웃도 컨스트레인트 잡고 비슷하게 변경 가능
(lldb) e -l swift -- unsafeBitCast(0x600003926710, NSLayoutConstraint.self).constant = 300
(lldb) e -l swift -- CATransaction.flush()
- 사실 viewHierarchy 에서 객체 잡고 cmd + c 하면
((NSLayoutConstraint *)0x600003926710)
이런 식으로 선택된 object 의 casted pointer 을 줌. debug View Hierarchy 클릭해서 lldb 로 들어왔을 때는 기본적으로 objc context 인 듯.
(lldb) e [((NSLayoutConstraint *)0x600003926710) setConstant: 130]
(lldb) e -l swift -- CATransaction.flush()
(lldb) command alias 별명 "줄이고 싶은 Command"
명령어를 이용하면 간단한 별명으로 줄여서 이용 가능- 예를 들면, Swift Code 내에서 Objective-C expression을 출력하기 위해
(lldb) expression -l objc -O --
Command를 사용하는 경우가 종종 있는데, 아래 명령어를 실행하고 난 후에는(lldb) pojc "[Objective-C Expression]"
형태로 명령어 사용 가능
(lldb) command alias pojc expression -l objc -O --
settings set target.language swift
나expr -l swift
이런거 별칭으로 해도 좋을 듯. 아니면e CATransaction.flush()
같은거.- 하지만 Alias로 등록해두어도 별칭은 해당 빌드 시점이 지나면, LLDB Session이 끝남과 동시에 사라지기 때문에 Xcode를 실행시 LLDB 초기화를 위해 사용되는 ~/.lldbinit 파일에 원하는 Command Alias를 추가해두면, 별칭을 매번 정해주지 않고 계속 사용 가능
- Python을 LLDB Script로 사용할 수 있음.
(lldb) script print(1 + 2)
- 그래서 Derek Selander나 Chisel 등을 import 하여 해당 라이브러리에 정의되어 있는 많은 기능들을 사용할 수 있음
-
Module 내에서 나타나는 Symbol에 대한 자세한 정보를 알아낼 수 있는 명령어
Module?
- Process에 Load되어 실행되는 Code를 의미
- 스위프트에서 Module은 메인 실행파일 뿐 아니라, Framework나 Library, Plugin등도 포함하는 개념으로 우리가 만든 Application 프로젝트를 포함해서, UIKit, AppKit 등의 Library 까지도 Module로 볼 수 있음
Symbol?
- Method, Variable, Class 등 말그대로 기계가 아닌 사람의 눈으로 알아볼 수 있는 Source Code를 이루는 작은 단위의 Symbol
- Symbol Table은 Compile된 Binary를 그에 맞는 Method, Variable, Class 등으로 상호 Mapping 해주는 역할 수행
- 또한 Binary가 Symbol로 번역되는 것을 Symbolicate라고 함
-
Framework에 대한 Private한 정보들 (private Class, private Method 등)을 Header File에 공개되어 있는 것 이상으로 알아 볼 수 있음
-
Private API들에 접근 가능하다면, 객체에 대한 숨겨진 Description을 확인하거나, 더 세부적인 속성을 확인할 수 있기 때문에 디버깅이 좀 더 간편해질 것
- 현재 Process에 Load 되어있는 모든 Module들의 정보를 출력
(lldb) image list
- 프로젝트 생성 이후 구현한 내용이 없는데도 무려 350개 이상의 Module이 Load 되어있음 ㅇ0ㅇ
- 각 모듈마다 표현하는 정보는 현재 Load 되어있는 Module들의 UUID, 실제 Memory상 Load되어있는 주소값, 그리고 Disk 내에서 Binary가 존재하는 Path
- Module의 세부적인 정보를 dumping (dump는 기억장치의 내용을 기록하는 것)
(lldb) image dump
- 만약 UIKitCore 라이브러리의 정보를 뽑아내고 싶다하면 아래의 명령어 실행 (UIKitCore Library에 포함되어있는 무려 12만개가 넘는 Symbol Table이 출력됨)
(lldb) image dump symtab UIKitCore -s address # -s address 옵션은 주소값으로 정렬
- 출력된 Symbol Table에는 해당 모듈에 포함되어있는 Variable, Method, Metadata, Protocol 등 모든 Symbol이 들어있음
- Symbol의 Prefix로
_
가 붙어있는 이 Symbol들은 공식 문서에는 나와있지않은 Private API들. 위의 사진에서는 UIStatusBar의 priavte api 들을 캡쳐뜬건데 StatusBar의 Battery, Cellular Item에 접근 가능한걸 알 수 있음. 물론 Apple에서 제공하는 Library의 Private API 이용은 AppStore Reject 사유가 되기 때문에, 디버깅할때만 사용하는것이 좋음. - 3rd Party Library를 제공받아 사용 할 때에도, 공개되지 않은 Method나 Class와 같은 Symbol들을 확인할 때 유용한 Command 일 듯.
- 언제 private api 사용해보고 싶을지 모르니 일단 사용법 링크 남겨둠
- 앞에서 살펴본
image dump
명령을 통해 나온 Module 정보들은 유용하긴 하지만, 너무 많기 때문에 이 내용들을 필터링 해서 볼 수 있도록 해줌
(lldb) image lookup
(lldb) help image lookup
Command를 통해 알아보면 아래와 같은 다양한 필터링 Option들을 제공하고 있음을 확인 가능
# 함수 이름 (--function)
(lldb) image lookup -F "functionName"
# 주소값 (--address)
(lldb) image lookup -a "0x00address"
# 파일 이름 (--filename)
(lldb) image lookup -f "FileName.swift"
# 라인 번호 (--line)
(lldb) image lookup -f "FileName.swift" -l 15
# 정규식 이용 (--regex)
(lldb) image lookup -rn "regexExpression"
-
특정 상황에서 원하는 Symbol들을 보다 쉽게 찾아볼 수 있음.
예를 들면, Animation을 구현하다가 CALayer에서 제공하는 setter가 어떤게 있는지 궁금해질 때, 이렇게 정규식을 이용하면 간단하게 모든 setter API를 출력 가능
(lldb) image lookup -rn '\[CALayer\ set'
-
(lldb) image lookup
Command는 Symbolicate 되지않은 Crash Log를 살펴볼 때 굉장히 유용 -
다음 이미지를 보는 차례대로
- 전혀 Symbolicate 되지 않은 형태의 Log
- 부분적으로 변환된 Log
- 우리가 알아볼 수 있는 형태로 모두 Symbolicate 된 Log
- 만약 Application의 Crash Log를 확인 할 때, Unsymbolicated Crash Log라면 해석 방법은?
0 The Elements 0x000000010003fc20 0x100034000 + 48160
1 UIKit 0x0000000187480070 0x187438000 + 295024
2 UIKit 0x000000018747feb0 0x187438000 + 294576
...
-
첫번째 줄의 Crash Log 는 차례대로 아래와 같은 Symbol에 정보들을 제공
- Binary Image Name : The Elements 는 Crash가 발생한 Main Application 실행 파일
- Stack Address : 해당 Symbol의 Stack 메모리 내 주소값 (
0x000000010003fc20
) - Load Address : Application이 Load되어있는 주소값 (
0x100034000
) - Offset: StackAddress와 LoadAddress 사이의 Offset ( == StackAddress – LoadAddress ) (
48160
)
-
위 정보들을 이용해 Crash를 일으킨 Symbol의 Address를 찾아서, dSYM 내의 문제아 Symbol의 Address를 찾아 어떤 놈인지 밝혀낼 수 있음
symbol address = slide + stack address - load address
= slide + offset
# slide value는 32bit architecture의 경우 0x4000, 64bit architecture의 경우 0x100000000
- 이에 따라 Symbol Address를 계산해보면, 0x10000BC20 이 문제 지점임을 파악 가능
# 참고: 48160 (10진수) == BC20 (16진수)
symbol address = 0x100000000 + BC20 = 0x10000BC20
- 마지막으로, 0x10000BC20 지점에는 어떤 Symbol이 있는지 확인해보면 되는데, 이때 LLDB를 사용!
- Terminal을 열고
$ lldb
를 통해 LLDB Console을 오픈 (Xcode LLDB Console이 아닌 Terminal에서도 LLDB를 실행할 수 있다는데 난 왜 잘 안되지 흠 🤔) (lldb) target create "dSYM 경로"
를 통해 lldb를 dSYM File에 Attach(lldb) image lookup --address 0x10000bc20
Command를 통해 문제의 Symbol 위치를 검색- HelloDebugger.ViewController.init 에서 Crash가 났다는 것 확인 가능! (흠,, 첨부한 사진에서는 init 에서 났다는거 알 수 있나?)
- Terminal을 열고
objc
,swift
등의 subcommand 를 추가 입력 후에 해당 언어에 대한 특정 기능을 수행할 수 있음
(lldb) language swift refcount self
# refcount data: (strong = 1, unowned = 0, weak = 0)
- 자세한 정보는
help language
,help language swift
등의 명령어를 통해 살펴볼 수 있음