본문 바로가기
iOS/Debugging

LLDB (Low-Level Debugger) 사용법

by diosmio 2021. 10. 1.

출처 : https://yagom.net/

 

무료강의이므로 편하게 글 올린다

강의가 궁금하다면 & 이 글이 도움됐다면 사이트 들어가서 야곰에게 커피 선물을

 

 

 

 

LLDB란?

LLVM 프로젝트의 컴포넌트 중 디버깅과 관련된 LLVM의 서브 프로젝트

Xcode는 LLVM과 LLDB를 사용하므로 디버깅하려면 LLDB 사용법을 익혀야 한다

print문 이제 Naver..

 

LLVM이란?

오픈 소스 컴파일러 프로젝트의 이름이다.

애플에서 적극 지원중이며 Swift를 완전히 지원한다

참고로, Swift를 만든 사람과 LLVM을 시작한 사람이 같음 -> 크리스 래트너

Xcode가 LLVM을 사용한다

 

LLVM 구조와 장점 한 스푼

LLVM에서는 우리가 작성하는 high-level 언어와 기계어 사이에,

IR(Intermediate Representation)이라는 middle-level의 코드가 존재한다

 

이런 구조의 장점은,

필요한 컴파일러 개수가 줄어들고 / 재사용(reusalbe)이 가능하다는 것이다

 

예로, N개의 언어와 M개의 타겟머신이 있을 때 중간계층이 없다면 N x M개의 컴파일러가 필요하다

하지만, 중간계층을 둠으로써 N + M개의 컴파일러만 있으면 된다. 

(N개: 언어->IR   //////   M개: IR->기계어)

 

또한, IR을 한번 만들면 여러 머신 타겟으로 재사용가능하다 

 

 


 

 

LLDB 문법

 

Xcode에서 LLDB 콘솔 활성화

APP을 실행하고 Breakpoint에 걸리거나 pause 시키면 콘솔에 lldb가 나타난다

 

 

LLDB 명령어 문법

(lldb) command [subcommand] -option "this is argument"

예시: (lldb) breakpoint set --file test.c --line 12

 

참고로, argument는 공백이 포함되는 경우가 있어 ""로 묶어줄 수 있다

 

 

 

 

기본 명령어

 

help

- 사용법을 출력한다

# 커맨드
(lldb) help breakpoint 
# 서브커맨드
(lldb) help breakpoint set

 

 

 

apropos

- 원하는 기능의 명령어가 있는지 검색한다

(lldb) apropos "reference count"
# [ 결과 ]
# The following commands may relate to 'reference count':
# refcount -- Inspect the reference count data for a Swift object

 

 

 

 

 

Breakpoint

 

breakpoint 걸기

- 함수 혹은 특정 코드위치에 breakpoint를 설정한다

  • 특정 함수에 걸기
(lldb) breakpoint --name viewDidLoad
(lldb) br s -r '^hello`
(lldb) rb '^hello`

 

  • 정규식을 따르는 모든 함수 모두에 걸기
(lldb) breakpoint set --func-regex '^hello`
(lldb) br -n viewDidLoad

 

  • File & line에 걸기
(lldb) br s --file ViewController.swift --line 20
(lldb) br s -f ViewController.swift --l 20

 

  • breakpoint에 조건 걸기
(lldb) breakpoint set --name "viewWillAppear" --condition animated
(lldb) br s -n "viewWillAppear" -c animated
# animated가 true인 경우에만 break

 

  • break 시 원하는 lldb 커맨드 실행해주고 continue
(lldb) breakpoint set -n "viewDidLoad" --command "po $arg1" -G1
(lldb) br s -n "viewDidLoad" -C "po $arg1" -G1
# "po $arg1" : 현재 함수의 1st Argument의 인스턴스 값 확인
# G1 : 자동 continue

 

  • shorthand breakpoint
# 특정 함수에 걸기
(lldb) b viewDidLoad

# 현재 파일 + 특정 line에 걸기
(lldb) b 20

# 특정 파일 + 특정 line에 걸기
(lldb) b ViewController.swift:20

# 현재 파일의 특정 text를 포함한 line 모두에 걸기
(lldb) b /stop here/

# 특정 주소값에 걸기
(lldb) b 0x12345678

 

 

 

breakpoint 리스트 확인하기

# 상세하게 전체 목록 출력
(lldb) breakpoint list
(lldb) br list

# 간단하게 출력
(lldb) br list -b

# 특정 id로 검색하여 출력
(lldb) br list <id>

- breakpoint는 id와 이름, hit-count, enable여부, 코드 상 위치, 주소값 등의 정보를 가진다

- list라는 subcommand로 이를 확인할 수 있다

 

- hit-count : 프로그램 실행 중 enable breakpoint를 만날 때마다 hit-count를 하나씩 누적시킨다

 

 

 

 

breakpoint 삭제/비활성화 하기

# 모든 breakpoint 삭제
(lldb) breakpoint delete
(lldb) br de

# 특정 br 삭제
(lldb) br de <id>

# 모든 breakpoint 비활성화
(lldb) breakpoint disable
(lldb) br di

#특정 br 비활성화
(lldb) br di <id>

 

 

 

 

Stepping

- 현재 break가 걸린 지점에서 줄단위/함수단위로 코드를 진행시킨다

- Xcode를 잘 찾아보면 버튼으로도 존재한다

 

one step 이동

(lldb) step

- 코드 상 정말 한 줄을 이동한다
- 다음 줄이 function call이면 해당 function의 첫 번째 줄에서 멈춘다

 

 

step over 이동

(lldb) next

- 비슷하게 한 줄 이동이긴 하지만
- 다음 줄이 function call이면 해당 function이 return될때까지 진행시킨 후 멈춘다

 

 

현재 function 끝까지 이동

(lldb) finish

- 현재 진행중인 함수가 return될때까지 프로그램을 진행시킨 후 멈춘다

 

 

 

 

 

Expression

- expression 커맨드는 인스턴스의 현재 값을 읽거나 변경할 수 있다

- 또한, 변수를 할당하고 함수를 실행하는 등의 행위가 가능하다

 

 

오브젝트 읽기

(lldb) po <인스턴스명>
(lldb) expression -O --<인스턴스명>

- "-O"옵션 : 읽으려는게 Object일때

 

 

 

변수 값 읽기 & 변경하기

 

1. 레지스터로 불러오기

(lldb) expression <인스턴스명>
(lldb) e <인스턴스명>

# [ 출력 ]
# (UIView?) $R0 = 0x00007fcd81c {
# 	UIKit.UIResponder = {
#		baseNSObject@0 = {
#			isa = UIView
#		}
#	}
# }

- LLDB는 내부적으로 값이 출력될때마다 Local Variable을 레지스터($R~) 형태로 저장한다

- 이 레지스터는 break context

 

2. 값 변경하기

(lldb) expression $R0!.backgroundColor = UIColor.blue

 

3. break 풀기

(lldb) continue

- 방금 변경한 사항이 적용된채로 남은 코드가 진행된다

 

 

 

변수 값 읽기 & 변경하기

 

1. 변수 할당하기

# 변수이름에 $를 붙혀줘야 한다
(lldb) expr var $view = UIView(frame: CGRect(x: 100, y:100, width: 80, height: 80))

 

 

2. Multi-line expression 사용하기

(lldb) expression
# Enter expressions, then terminate with an empty line to evaluate:

1 $view.backgroundColor = UIColor.blue
2 self.view.addSubview($view)

- continue해보면 self.view에 subview가 추가된 것을 볼 수 있다

 

 

 

expression 중 만나는 breakpoint는 무시하기

(lldb) expression --ignore-breakpoints true --
(lldb) e -i 1 --

(lldb) expression --ignore-breakpoints false --
(lldb) e -i 0 --

- default는 true(무시)로 설정된다

 

 

 

주소값과 타입만으로 변수에 접근하기

- 이를 위해 필요한 구체적인 동작은, 해당 주소에 있는 값을 적절한 타입으로 캐스팅하는 것이다

- 이건 바로 가능한 부분은 아니고 UIKit 모듈의 unsafeBitCast( _, to: ) 함수를 활용해야 한다

 

1. UIKit모듈 import하기

(lldb) expr -l Swift -- import UIKit

 

2. 캐스팅하기

(lldb) expr -l Swift -- var $<변수명> = unsafeBitCast(<주소값>, to: <타입명>.self)
(lldb) expr -l Swift -- var $myLabel = unsafeBitCast(0x7fdf7611, to: UILabel.self)

- myLabel을 이용해 접근할 수 있게 되었다

 

 

 

 

Objective-C 프로젝트에서 Swift로 디버깅하기

(lldb) expression -language swift -- <Swift 옵션>
(lldb) expression -l objc -- <Objective-C 옵션>

 

 

 

 

 

Image

- 현재 Process에 Load된 Module과 Symbol 정보를 보여준다

 

 

모듈과 심볼의 개념

- 말이 나와서 잠깐 짚고 넘어가자

- 이건 심도있게 알지 못하므로 아는 한도 내에서만 기술하겠다. 

  • Module :
    대중적인 정의를 편하게 표현하면 코드묶음으로 볼 수 있으며
    Module로 묶이는 범주는 상황별로 굉장히 다양할 수 있다. 
    APP 프로젝트 전체 혹은 Framework나 Library 등도 Module로 불릴 수 있다

  • Symbol :
    편하게 표현하면 함수, 변수, 타입 등을 구분하는 이름같은 개념이며
    컴파일러가 코드를 분석할 때 사용하는 단위이다

  • Symbol Table :
    컴파일 과정 중에는 high-level 언어로 표현된 메소드, 변수, 클래스 등을
    -> 기계어 Binary로 mapping 하는 것이 포함되어 있을 것이다
    이 mapping 관계를 나타내는 것이 Symbol Table이다

 

 

주의사항

- image를 사용하면, 공개되지 않는 Library에도 접근할 수 있다.

- 3rd Party Library나 뭔가 건드리면 안될것 같던 Battery, StatusBar 같은 Private API에 접근할 수도 있다

- 물론, Apple에서 제공하는 Library의 Private API 사용은 AppStore Reject 사유가 되므로 디버깅할 때만 사용하자

 

 

 

 

현재 Process에 올려진 모든 Module 정보 보기

(lldb) image list

 

 

 

 

특정 Module 검색 (symbol table)

(lldb) imgage dump symtab UIKitCore -s address

- "symtab" : symbol table

- "-s address" : address를 기준으로 오름차순 정렬하여 출력

 

 

 

 

특정 Symbol 검색

(lldb) image lookup -F <함수명>

(lldb) image lookup -a <메모리주소>

(lldb) image lookup -f <파일이름>

(lldb) image lookup -f <파일이름> -l <줄번호>

(lldb) image lookup -rn <정규식>

 

 

 

image lookup으로 Crash Log를 symbolicate 해보자

메모리 주소만 출력되는 Crash Log 예시

- 이처럼 메모리 주소만 던져주는 Crash Log는 디버깅이 거진 불가능하다고 볼 수 있다

- image lookup 커맨드로 각 메모리 주소가 코드에서 어느 위치(심볼)를 가리키는지 알 수 있다

 

 

1. symbol address 계산

symbol address = slide + offset

- symbol address는 Crash Log에 출력되는 offset 값에 slide value를 더해줘야 한다

- slide values는 0x4000( 32bit 머신) 혹은 0x100000000( 64bit 머신)을 가진다

 

2. symbol 추출

(lldb) image lookup --address <계산된 symbol address>

- symbol이 추출되면 어느 함수/변수에서 Crash가 발생했는지 알 수 있다

 

 

 

Terminal에서도 Crash Log를 symbolicate 해보자

 

1. LLDB 실행

Terminal> lldb

 

2. LLDB를 dSYM File에 attach

(lldb) target create <dSYM 경로>

 

3. symbol 추출

(lldb) image lookup --address <계산된 symbol address>

 

 

 

 

 

 

 

 

Alias

- LLDB 명령어 Set을 특정 별명으로 저장해놓을 수도 있다

# 선언
(lldb) command alias <별명> <줄이고 싶은 command>

# 사용
(lldb) <별명>

 

- 사실 별명을 추가하더라도 lldb가 종료되면 사라지므로, 계속 사용하려면 ~/.lldbinit 파일에 저장해야 한다

 

 

 

 

 

 

 

 

오픈소스 LLDB Script

- 잘 만들어놓은 script를 가져다 사용하는 방법도 있다

- Github에서 clone하거나 다운받은 후, ~/.lldbinit 파일에 import 해주면 된다

# 1. DerekSelander/LLDB Script의 Github Repository를 Clone
git clone https://github.com/DerekSelander/LLDB.git

# 2. ~/.lldbinit 파일을 열고, 다음 내용을 추가해서 import
command script import 'clone받은경로'/LLDB/lldb_commands/dslldb.py

 

 

 

 

 

 

참고자료

Apple 문서 : https://developer.apple.com/library/archive/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html#//apple_ref/doc/uid/TP40012917-CH1-SW1

 

LLDB 문서 :

https://lldb.llvm.org/use/map.html

댓글