[iOS] LLDB 를 통한 디버깅

Youngwoo Lee·2021년 9월 6일
1

iOS

목록 보기
29/46
post-thumbnail

참고자료) https://www.vadimbulavin.com/xcode-build-system/
https://yagom.net/courses/start-lldb/
https://ko.wikipedia.org/wiki/LLVM/

LLDB란 Xcode에 내장되어 있는 Command Line Debug 환경이라고 알고 있다. 일단 빌드에 대한 개념부터 공부하고 시작하자!

Build (빌드)

소스 코드 파일을 실행가능한 소프트웨어 산출물로 만드는 일련의 과정. Xcode에서 매일 하는 command + B이 해당 과정!

  • 컴파일러 : 소스코드 전체를 기계어로 번역함
  • 인터프리터 : 소스코드 한 줄씩 번역하면서 실행함
  • 하이브리드 : 소스코드 전체를 중간코드(바이트 코드)로 번역한 뒤 가상머신(VM)에서 한줄씩 실행함



컴파일러

Swift의 경우 컴파일러 언어이기 때문에 컴파일러의 빌드 과정에 대해서 알아보자!

전처리(preprocessing) -> 컴파일(compilation) -> 어셈블(assemble) -> 링크(linking)


전처리

소스코드에 포함된 매크로나 지시자 같은 것을 포함시키는 과정
"소스코드의 중심(main)이 실행되기 전에 사전준비하는 과정"

Swift 컴파일러에는 전처리기가 포함되어 있지 않는다. 그러므로 Xcode에서 매크로를 정의할 수 없다고 한다. 하지만, 프로젝트 빌드 설정에서 Active Compilation Conditions 를 통해서 일부를 보정하고 사전 처리를 수행할 수 있다.


컴파일

소스코드(고급언어)를 저수준 언어(어셈블리어)로 번역한다

Xcode는 Swift용과 Objective-C++ 및 C/C++ 파일의 두 가지 컴파일러를 사용합니다.

clang은 C 언어 계열의 애플 공식 컴파일러이며, Swiftc는 Swift 소스 코드를 컴파일하고 실행하기 위해 Xcode에 의해 사용되는 Swift 컴파일러 실행파일이다


어셈블

그러면 이 저수준 언어(어셈블리어)를 최종적으로 기계어로 번역을 해주어야겠죠? 이 어셈블리어를 기계어로 번역해주는 프로그램을 어셈블러(Assembler)라고 합니다.

이렇게 CPU가 이해할 수 있는 언어로 번역된 파일을 보통 Object File 이라고도 하는데, 직역하면 '객체 파일'이라고 하지만, 대부분은 객체라 하지 않고 '목적 파일'이라고 합니다.


링킹/링크

컴파일 과정을 통해서(어셈블 포함) 각 파일들이 기계어로 번역되었다면 이제 각 파일들을 연결시켜줘야 한다. Object File 들과 필요한 라이브러리들을 연결 시켜주고 최종적으로 하나의 실행가능한 파일로 만들어 준다. 우리가 흔히 어떤 프로그램을 사용할 때 .exe 확장자를 사용하는데 바로 실행가능한 파일이 이에 해당한다.


컴파일 언어의 장점

  1. 빌드가 완료된 실행가능한 파일은 실행 속도가 빠릅니다
  2. 매번 번역할 필요 없이 실행 파일만 실행하면 되기 때문에 전체적인 시간면에서 효율적입니다

컴파일 언어의 단점

  1. 프로그램을 수정해야 할 경우 처음부터 빌드과정을 다시 거쳐야하기 때문에 특히나 대규모 프로그램에서는 생산성이 떨어집니다
  2. 플랫폼에 매우 의존적입니다



LLVM

LLDB를 이해하기 이전에 LLVM에 대한 간단한 이해가 필요하다!

LLVM은 Apple에서 진행한 Compiler에 필요한 Toolchain 개발 프로젝트이다

ToolChain 개발 프로젝트 : 다른 컴퓨터 또는 시스템의 소프트웨어 제품을 만드는데 사용되는 컴퓨터 프로그램 개발 도구들의 집합이다. 일반적으로 여기에 포함된 개발 도구들은 연쇄적으로 사용된다. 즉, 어느 한 개발 도구의 출력은 다른 개발 도구의 입력이 된다. 그러나 요즘에는 서로 관련 있는 개발 도구들의 집합을 가르키는 의미로도 널리 사용되고 있다.

각 컴포넌트들의 재사용성을 중요시해서, 모듈화가 잘 되어있다는 특징이 있습니다. 이렇게 모듈화 되어있는 컴포넌트들을 이용해 진행된 주요 서브 프로젝트로는 LLVM Core, Clang, LLDB 등이 있다.




LLDB

LLDB는 Xcode IDE에 기본으로 내장되어있는 Command-Line Debug 환경이다. Xcode IDE의 Terminal에 곧바로 접근해서 실행중인 프로세스의 값을 변경하거나, 흐름을 제어하는 등 다양한 디버깅 작업을 할 수 있다.

LLDB는 LLVM의 Debugger Componenet를 개발하는 서브 프로젝트이다. LLVM 프로젝트를 통해 개발된 Clang Expression Parser, LLVM Diassembler 등 Low-Level 컨트롤이 가능한 모듈들로 이루어져 있어, 기계어에 가까운 영역까지 디버깅 가능하다는 장점이 있다. 현재 Xcode의 기본 디버거로 내장되어 있으며, LLDB와 함께라면 실제 프로그램이 어떤 식으로 동작하는지 더 깊이 이해할 수 있다.

LLDB 콘솔은 Xcode를 통해서 실행된 프로세스만 사용할 수 있는 건 아니다. Terminal 에서 lldb - n Finder를 입력하면, 실행중인 앱(Finder) 의 프로세스에도 LLDB를 Attach해서 사용할 수 있습니다. (단, csrutil status를 통해 System Integrity Protection을 disable 시켜야 가능합니다. 주의 요망




명령어

명령어 문법 구성

(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 (Command)와 set --file test.c --line 12

--file option을 통해 test.c 파일 내
--line option을 통해 12번째 라인에 중단점을 set 해줍니다.


Help

해당 문법으로 사용가능한 Subcommand, Option 리스트나 사용법을 보여주는 명령어이다

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 set [option] "arguments"
# 줄여서는,
(lldb) br s [option] "arguments"

대부분의 명령어와 옵션들은 command 맨 앞 1~2개 알파벳으로 줄여서 사용할 수 있습니다

Function Name

특정 이름을 가진 모든 함수에 -name (-n) option을 이용해 break를 걸 수 있다

# 함수 이름 이용해 break
(lldb) breakpoint --name viewDidLoad
(lldb) b -n viewDidLoad

또한, -func-regex (-r) option을 이용해 정규표현식을 활용할 수 있습니다

(lldb) breakpoint set --func-regex `^hello`
(lldb) br s -r `^hello`
# 'breakpoint set --func-regex'는 줄여서 'rb'로 사용 가능
(lldb) rb '^hello'

File

파일의 이름과 line 번호를 이용해 break를 걸기 위해서는 , -file (-f), -line (-l) option을 이용할 수 있습니다.

# 특정 파일의 20번째 line에서 break
(lldb) br s --file ViewController.swift --line 20
(lldb) br s -f ViewController.swift -l 20

Condition

-condition (-c) option을 이용하면, breakpoint에 원하는 조건을 걸 수도 있습니다. -c option 뒤의 expression이 true인 경우에만 breakpoint에서 멈춥니다.

# viewWillAppear 호출시, animated가 true인 경우에만 break
(lldb) breakpoint set --name "viewWillAppear" --condition animated
(lldb) br s -n "viewWillAppear" -c animated


Quiz

그렇다면, 아래 명령어

(lldb) br s -f ViewController.swift -l 17 -c 'helloWorld == "abc"'

는 어떤 의미인가?

my opinion : breakpoint를 세팅한다 ViewController.swift 파일의 17번째 줄에 만약에 helloWorldrk "abc"인 경우에!

Command 실행 & AutoContinue

-command (-C) option을 이용하면 break시 원하는 lldb command를 실행할 수 있습니다

(lldb) breakpoint set -n "viewDidLoad" --command "po $arg1" -G1
(lldb) br s -n "viewDidLoad" -C "po "

-auto-continue (-G) option의 기능은 auto continue로, command 실행 후 break에 걸린채로 있지 않고 프로그램을 자동 진행하게 해줍니다.

b(_regexp-break) Command

(lldb) regexp-break는 간단하게 Breakpoint 생성을 할 수 있돍 도와주는 Shorthand Command입니다.

(lldb) b로 줄여서 사용할 수 있습니다

  • 사용방법
# 특정 이름을 가진 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
(lldb) breakpoint list

해당 command를 통해 현재 프로그램에 생성되어 있는 Breakpoint의 목록을 확인할 수 있습니다. 또한, 이 목록 정보에는 Breakpoint의 id와 이름, hit-count 정보, enable 여부, source 상의 위치, 주소값 등등 유용한 정보가 포함되어 있습니다.



hit-count 란?

프로그램 실행 중 활성 상태인 Breakpoint 지점이 실행되면, Debugger는 hit count를 1씩 늘려가며 기록합니다. 하지만 Breakpoint가 걸려있다 하더라도, disable 상태이면 count되지 않습니다

Breakpoint id를 통해 원하는 내용만 출력하거나, -brief (-b) option을 통해 간단한 내용을 확인해 볼 수도 있습니다.

# breakpoint 목록 전체 출력
(lldb) breakpoint list
(lldb) br list
# breakpoint 목록 간단하게 출력
(lldb) br list -b
# 특정 id를 가진 breakpoint의 정보만 출력
(lldb) br list 1

delete, diable Subcommand를 이용해 Breakpoint를 삭제하거나, 비활성화 할 수 있습니다

# breakpoint 전체 삭제
(lldb) breakpoint delete
(lldb) br de
# 특정 breakpoint 삭제
(lldb) br de 1
# breakpoint 전체 비활성화
(lldb) breakpoint disable
(lldb) br di
# 특정 breakpoint 비활성화
(lldb) br di 1.1

LLDB의 가장 중요한 Action중 하나인 Stepping... Stepping은 프로세스를 단계별로 진행하면서 상태 변화를 관찰해 볼 수 있는 유용한 기능입니다.


Stepping Over

(lldb) next Command를 이용하면, 현재 Break 걸려있는 지점에서 바로 다음 Statement로 Step Over 할 수 있습니다.

줄여서 (lldb) n으로 사용 가능합니다.


Stepping In

Stepping In은 다음 Statement가 Function Call인 경우 Debugger를 해당 함수 내부에 위치한 시작 지점으로 이동하게 해줍니다. (lldb) step Command를 이용해 실행할 수 있습니다.

줄여서 (lldb) s으로 사용 가능합니다

Stepping in은 주로 바로 위에서 소개된 Stepping Over와 비슷하게 동작하는 경우가 많다
LLDB의 경우에는 기본적으로 Debug Symbol이 없는 함수에 대해서는 Stepping in을 무시하고, 프로그램을 진행하기 때문입니다.

Debug Symbol에 대한 내용이 궁금하다면, 아래 Image 파트를 참고하자!!!

Stepping Out

현재 진행중인 function이 return 될때까지 프로그램을 진행한 후 프로그램 Break걸어주는 Stepping Action을 Stepping Out이라고 합니다.

(lldb) finish Command로 실행할 수 있다

Stack Memory 관점에서 Stepping Out은 Stack Frame을 Pop하는 것과 동일하다

(lldb) next 명령을 통해 한 줄씩 진행하고,
(lldb) step 명령을 통해 UIKit의 ViewDidLoad 함수 내부 Debug Symbol로 이동한 후,
(lldb) finish 명령을 통해 함수 return 이후 지점까지 진행하는 모습을 볼 수 있다

(lldb) run 명령은 말그대로 현재 프로그램을 중단하고, 새로운 Build/Run을 진행한다

또한, 마지막에 쓰인 (lldb) continue 명령은 다음 Breakpoint가 나타낼때까지 프로그램을 진행한다

앞에서 Breakpoint를 잡는 법에 대해 열심히 배워보았다

이번에는 멈춰있는 동안 새로운 동작을 실행시킬 수 있는 (lldb) expression Command에 대해 알아보자



Expression Command

아마 실무에서 LLDB Command중 가장 많이 사용되는 Command가 (lldb) po일텐데, 객체에 대한 다양한 정보를 Console에 출력하여 확인할 수 있기 때문이다.

(lldb) help po를 통해 확인할 수 있듯, po는 (lldb) expression -0 --의 shorthand이다. 여기서 -O option은 object의 description을 출력하겠다는 뜻이다


(lldb) expression Command는 Runtime에 여러 정보를 출력할 수 있을 뿐만아니라 값을 변경 해줄 수도 있다. LLDB는 내부적으로 값이 출력될때마다 local variable을 $R-의 형태로 만들어 저장한다. 이 값들은 해당 break context가 벗어나도 사용 가능한 값들이고, 심지어 수정해서 사용할 수도 있다.

(lldb) expression self.view

// self.view 정보를 출력하는데, 출력된 정보에는 $R0이라는 이름의 변수에 self.view가 저장된 것으로 보인다

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

// 위에서 나온 self.view가 저장된 $R0의 속성인 배경 색상(backgroundColor)을 변경한다

(lldb) continue

// Code를 마저 진행합니다

두줄의 expression Command로 인해 실제 ViewController.view의 background Color 속성이 변경되었음을 알 수 있다


또, 아래 예시와 같이 (lldb) expression Command를 이용해서 변수를 직접 선언해서 사용할 수도 있다. 단, 사용하고자 하는 변수명 앞에 $ 문자를 붙여줘야 한다

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

실행 도중 breakpoint를 만나면 멈춤

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

주소값 이용해서 변수 사용해보기

객체의 주소값과 Type만을 알고있는 경우, 해당 변수의 정보를 알아볼 수 있을까요? 네! Swift의 unsafeBitCast(to:)함수를 이용하면 가능합니다.



천천히 프로젝트 생성 과정에서 조금씩 쓰면서 익숙해져보자!! expression 부터 적용해보자!

profile
iOS Developer Student

0개의 댓글