17 June 2018

iOS 개발을 좀 더 잘하기 위해, 편하게 버그를 추적하기 위해 LLDB를 이용한 디버깅 방법을 기록합니다.

Thread의 Stack, Frame

Thread가 생겨날 때, 해당 Thread를 위한 Stack이 만들어지며, 해당 Stack에는 Frame이 들어갑니다.

Execution Commands

  • Continue - 정지된 프로그램 실행을 재개함.
(lldb) continue
(lldb) c
  • Step Over - 현재 선택된 Frame에서 소스 수준의 한 단계를 진행.
(lldb) thread step-over
(lldb) next
(lldb) n
  • Step Into - 현재 선택된 Frame에서 소스 수준의 한 단계 안으로 들어감.
(lldb) thread step-in
(lldb) step
(lldb) s
  • Step Out - 현재 선택된 Frame에서 벗어남.
(lldb) thread step-out
(lldb) finish
  • Instruction Level Step Into - 현재 선택된 Frame에서 명령어 수준의 한 단계 안으로 들어감.
(lldb) thread step-inst
(lldb) si
  • Instruction Level Step Over - 현재 선택된 Frame에서 명령어 수준의 한 단계를 진행.
(lldb) thread step-inst-over
(lldb) ni

Examining Variables - 변수 검사

  • Frame Variable - 메모리에서 변수를 읽어 lldb 형태의 description을 출력함.
/// 현재 frame에 argument와 local 변수 출력하기.
(lldb) frame variable
(lldb) fr v

/// 현재 frame에 local 변수 출력하기
(lldb) frame variable --no-args
(lldb) fr v -a

/// local 변수 `bar` 내용 출력하기
(lldb) frame variable bar
(lldb) fr v bar

/// local 변수 `bar`을 hex로 내용 출력하기
(lldb) frame variable --format x bar
(lldb) fr v -f x bar

/// Object의 Description 출력하기
(lldb) frame variable -O self
  • Target Variable - global/static 변수를 출력함.
/// global 변수 `baz` 내용 출력하기
(lldb) target variable baz
(lldb) ta v baz

/// 현재 소스 파일에서 정의된 global/static 변수 출력하기
(lldb) target variable
(lldb) ta v
  • Target Stop-hook / Display - 매번 멈출 때 마다 지정된 명령 실행 / 멈출 때 마다 지정한 변수 내용을 출력
/// `frame variable argc argv` stop hook을 등록하기
(lldb) target stop-hook add --one-liner "frame variable argc argv"
(lldb) ta st a -o "fr v argc argv"

/// 멈출 때 마다 argc, argv 내용을 출력하기
(lldb) display argc
(lldb) display argv

/// `main` 함수 안에서 멈추면 argc, argv 내용을 출력하기
(lldb) target stop-hook add --name main --one-liner "frame variable argc argv"
(lldb) ta st a -n main -o "fr v argc argv"

/// C 클래스 이름인 MyClass 안에서 멈추면 *this 변수 내용을 출력하기
(lldb) target stop-hook add --classname MyClass --one-liner "frame variable *this"
(lldb) ta st a -c MyClass -o "fr v *this"

/// Multiple Line Hook 
(lldb) target stop-hook add
> bt
> disassemble --pc
> DONE

Examining Thread State - 스레드 상태 검사

  • Thread List/Change - 현재 스레드 목록을 보여줌 / 다른 스레드로 이동하기
/// 현재 스레드 목록을 보여주기
(lldb) thread list

/// Thread 2로 이동하기
(lldb) thread select 2
  • Thread Until/Jump/Return - 특정 줄 전까지 실행 후 멈춤 / 특정 주소/줄로 이동 / 현재 stack frame에서 특정 값을 반환(note: Swift에서는 거의 안됨)
/// 특정 줄까지 step over 수행함.(ex: 현재 줄이 10줄이고, 20줄 이전 까지 step over를 수행함.)
(lldb) thread until 20 // 19줄까지 step over, 20줄에서 멈춤

/// 특정 frame의 특정 소스 줄까지 수행함.(frame 2에서 10번째 줄 전 까지 수행)
(lldb) thread until --frame 2 10

/// 명령어 수준의 특정 주소까지 수행함
(lldb) disassemble --frame
(lldb) thread until --address 0x1023653a0

/// 특정 소스라인까지 이동함.
(lldb) thread jump --line 10 // 10번째 줄으로 이동
(lldb) thread jump --by 5 // 현재 줄에서 +5번째 줄로 이동
(lldb) thread jump --by -5 // 현재 줄에서 -5번째 줄로 이동

/// 현재 frame에서 Void를 반환하거나 특정 값을 반환
(lldb) thread return 
(lldb) thread return 0
(lldb) thread return "aa" // Only Objc
error: Error returning from frame 0 of thread 1: We only support setting simple integer and float return types at present..
  • Disassemble
/// 현재 frame의 현재 함수를 disassemble하여 보여줌.
(lldb) disassemble --frame
(lldb) di -f

/// main 이라는 함수를 disassemble하여 보여줌.
(lldb) disassemble --name main
(lldb) di -n main

/// 지정된 주소범위의 명령어 코드를 출력함.
(lldb) disassemble --start-address 0x1eb8 --end-address 0x1ec3
(lldb) di -s 0x1eb8 -e 0x1ec3

/// 시작 주소부터 20개의 명령어를 출력함.
(lldb) disassemble --start-address 0x1eb8 --count 20
(lldb) di -s 0x1eb8 -c 20

/// 현재 frame의 코드에 해당하는 명령어를 코드와 같이 출력함.
(lldb) disassemble --frame --mixed
(lldb) di -f -m

/// 현재 frame에 현재 소스 코드 라인을 disassemble하여 보여줌.
(lldb) disassemble --line
(lldb) di -l
  • Backtrace - 스레드의 stack backtrace를 보여줌.
/// 현재 스레드의 stack backtrace를 보여주기
(lldb) thread backtrace
(lldb) bt

/// 모든 스레드의 stack backtrace를 보여주기
(lldb) thread backtrace all
(lldb) bt all

/// 현재 스레드의 frame에서 1~5번 backtrace를 보여주기
(lldb) thread backtrace -c 5
(lldb) bt 5

/// 현재 스레드에서 특정 stack frame index를 선택하기
(lldb) frame select 12
(lldb) fr s 12
(lldb) f 12

/// 현재 스레드에서 현재 선택된 frame의 정보 출력하기
(lldb) frame info
(lldb) fr info

/// 현재 선택된 stack frame에서 위 아래로 이동
(lldb) up
(lldb) up 3
(lldb) down
(lldb) down 5
  • Assembly Register Calling Convention - 어셈블리 레지스터 호출 규칙
/// 현재 스레드의 범용 레지스터를 보여주기
(lldb) register read
(lldb) re r

/// 현재 스레드의 모든 레지스터를 보여주기
(lldb) register read --all
(lldb) re r -a

/// 현재 스레드의 rax, rsp, rbp 레지스터 값을 보여주기
(lldb) register read rax rsp rbp

/// 현재 스레드의 범용 레지스터의 값을 특정 포맷으로 포맷으로 보여주기
(lldb) register read --format binary // Binary
(lldb) register read --format decimal // Decimal
(lldb) register read --format hex
(lldb) re r -f b
(lldb) re r -f d

/// register rax에 123 값을 기록하기
(lldb) register write rax 123

/// 현재 명령어에서 8 바이트 만큼 앞으로 점프하기
(lldb) register write pc `$pc+8`
  • 함수를 호출할 때, Parameter로 사용되는 Register
    • RDI, RSI, RDX, RCX, R8, R9 (Argument의 순서대로 정의함, 주의. 최신 Xcode 버전에서는 맞는지 확실치 않음)

Evaluating Expression - 표현식 계산하기

  • 현재 frame에서 표현식 계산하기
(lldb) expression print(1 + 2)
(lldb) expr print(1 + 2)
(lldb) e print(1 + 2)

(lldb) expression -- print(1 + 2)
(lldb) print print(1 + 2)
(lldb) p print(1 + 2)
  • LLDB에서 변수를 선언하기
(lldb) expr var $foo = 10
(lldb) expr print($foo) // Output: 10
(lldb) expr $foo += 1
(lldb) expr print($foo) // Output: 11
  • Objc 객체의 description 보여주기
(lldb) expr --object-description -- object
(lldb) expr -o -- object
(lldb) po object
  • 특정 메모리 주소의 값을 출력하기
/// Swift
(lldb) expr -l swift -- import UIKit
(lldb) expr -l swift -- let $vc = unsafeBitCast(0x7fe75a70bb40, to: ViewController.self)
(lldb) po $vc

/// Objc
(lldb) expr -l objc -- @import UIKit
(lldb) expr -l objc -- ViewController *$vc = (ViewController *)0x7fe75a70bb40
(lldb) po $vc

/// 실행 중에 Pause를 한 후, 특정 메모리 주소의 값을 확인하는 경우
(lldb) expr -l Swift --
Enter expressions, then terminate with an empty line to evaluate:
1 let $vc = unsafeBitCast(0x7fe75a70bb40, to: ViewController.self)
2 print($vc)
3

/// 위 명령들을 축약
(lldb) expr import UIKit 
(lldb) p import UIKit 
(lldb) expr let $vc = unsafeBitCast(0x7fe75a70bb40, to: ViewController.self)
(lldb) po $vc
<SomeProject.ViewController: 0x7fe75a70bb40>
  • 임의의 UIViewController 생성하여 NavigationViewController에 Push하기
(lldb) expr var $vc = UIViewController()
(lldb) expr $vc.view.backgroundColor = UIColor.red
(lldb) expr self.navigationController?.pushViewController($vc, animated: true)
  • 애니메이션 Transaction을 즉시 실행하기
(lldb) expr -l swift -- import UIKit 
(lldb) expr -l swift -- CATransaction.flush()
  • Printing Modes

    • frame variable (fr v) - Code를 실행하지 않으며, LLDB formatter를 사용
    • expression -- (p) - Code를 실행하며, LLDB formatter를 사용
    • expression -O -- (po) - Code를 실행하며, debugDescription와 같이 개발자가 만든 출력 형태를 사용
  • BreakPoint 설정된 코드를 실행시 Pause되도록 하기

(lldb) expr --ignore-breakpoints false -- <Expression>
  • 메소드 및 클래스 선언하기

특정한 메소드 및 클래스를 만들어 사용할 수 있습니다.

(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()

BreakPoint - BreakPoint 설정하기

/// viewDidLoad 이름인 모든 함수에 breakpoint를 설정하기 - Swift
(lldb) breakpoint set --name viewDidLoad
(lldb) br s -n viewDidLoad
(lldb) b viewDidLoad

/// viewDidLoad 이름인 모든 함수에 breakpoint를 설정하기 - Objc
(lldb) breakpoint set --name "-[UIViewController viewDidLoad]"

/// 특정 파일 특정 줄에 breakpoint 설정하기
(lldb) breakpoint set --file test.c --line 12
(lldb) br s -f test.c -l 12
(lldb) b test.c:12

/// 현재 파일의 특정 줄에 breakpoint 설정하기
(lldb) breakpoint set --line 12
(lldb) br s -l 12
(lldb) b 12

/// 특정 이름을 가진 Select에 breakpoint 설정하기
(lldb) breakpoint set --selector dealloc

/// breakpoint에 global이 5이면 중단되도록 조건 설정
(lldb) b 12
(lldb) breakpoint modify -c "global == 5"

/// breakpoint 목록 보기
(lldb) breakpoint list
(lldb) br l

/// breakpoint 지우기
(lldb) breakpoint delete 1
(lldb) br del 1

Watchpoint - 변수에 값이 기록될 때마다 중단되도록 설정

/// 전역 변수에 watchpoint 설정
(lldb) watchpoint set variable global_var
(lldb) wa s v global_var

/// 메모리 주소에 watchpoint 설정
(lldb) watchpoint set expression -- my_ptr
(lldb) wa s e -- my_ptr

/// watchpoint에 global이 5이면 중단되도록 조건 설정
(lldb) watch set var global
(lldb) watchpoint modify -c "global == 5"

/// watchpoint 목록 보기
(lldb) watchpoint list
(lldb) watch l

/// watchpoint 지우기
(lldb) watchpoint delete 1
(lldb) watch del 1

Script - Python REPL

  • Python을 LLDB Script로 사용할 수 있음.
(lldb) script print(1 + 2) // Output: 3
(lldb) script import os
(lldb) script print(os.getcwd())
  • import - 필요한 script 소스를 import하여 사용함.
(lldb) command script import ~/myCommands.py

또는 /.lldbinit 파일 내에 command script import ~/myCommands.py 를 추가함.

기타

/// 특정 키워드의 상세한 설명을 보여줌
(lldb) apropos keyword

/// 기본 언어 설정을 바꿈
(lldb) settings set target.language swift

/// Alias 설정
(lldb) command alias es expression -l swift --

LLDB 확장 툴

  • Chisel - python으로 대부분 작성되어 있으며, View Debugging 관련하여 손쉽게 사용할 수 있도록 도와주며, 여기에서 많은 명령을 살펴볼 수 있음.
  • DerekSelander - LLDB - A collection of LLDB aliases/regexes and Python scripts to aid in your debugging sessions

참고자료