티스토리 뷰

WWDC

[WWDC19] LLDB: Beyond "po"

Hani_Levenshtein 2022. 2. 12. 05:54

안녕하세요 Hani입니다.

이번에는 LLDB를 이용한 디버깅 방법에 대하여 알아볼 거예욥.

 

 

 


LLDB는 런타임에 앱을 디버깅할 수 있도록 도와주는 디버거예요.🍔

좌하단에서 런타임에 값을 확인할 수 있고

우하단에 lldb 보이시져? 저기다 커멘드 입력하면 됩니다.

 

 

po, p, v 커멘드를 순차적으로 알아볼게욥.

 

 

po 변수명

객체에 대한 description을 출력해주는 명령이에요.

 

 

CustomDebugStringConvertible 프로토콜을 채택하고 debugDescription 프로퍼티를 구현하면

객체의 description이 구현한 문자열로 바뀝니다.

 

 

CustomReflectable 프로토콜을 채택하여 하부 구조를 커스텀할 수도 있습니당.

 

 

 

단순한 출력뿐 아니라 의도한 대로 출력할 수도 있어요. ☺️

 

 

po는 "expression --object-description" 를 줄인 말이라서 저렇게 구구절절 적어도 동일하게 출력됩니다.

command alias를 활용하면 커스텀 명령어도 만들 수 있습니다.

 

 

po의 명령어 뒤에선 어떤 일이 일어나는지 알아볼게욥.

 

 

po 명령어를 입력하면 컴파일 가능한 코드를 생성합니다.

 

 

고거슬 컴파일러가 컴파일 한 다음 실행이 완료되면 LLDB가 결과 값에 접근하여 description을 가져옵니다.

 

 

그걸 다시 래핑하여 컴파일하고 실행하면 또 결과가 나오는데

 

 

실행의 결과는 po 명령어를 통해 출력할 문자열입니다.

 

 

요기서 po 명령은 매무리

 

 

p 변수명

po 명령과 달리 객체의 description을 출력하지 않습니다.

 

대신 객체를 $R0로 표현하고 있네욥.

 

 

이제 $R0는 cruise 변수가 가리키고 있는 객체입니다.

$R0의 destinations 프로퍼티만 따로 출력해보니 cruise.destinations는 $R1라는 이름을 부여받은 상태.

 

이렇게 LLDB는 p 명령을 쓸 때 객체에 이름을 붙여줍니다.

 

 

p명령은 객체의 description을 가져오지 않기 때문에 한 번의 컴파일과 실행만 발생합니다.

 

po와 다른 점이 있다면 실행의 결과를 받고 나서 Dynamic Type Resolution라는 과정을 수행합니다.

요게 뭐냐!

 

 

예시로 살펴보겠습니당.

소스 코드에는 변수가 프로토콜 타입으로 선언되어 있고 구체 타입을 할당하고 있습니다.

(cruise는 컴파일 타임에는 Activity겠지만 런타임에는 Trip)

 

LLDB는 cruise의 값을 출력할 때 보다 더 정확한 타입을 보여주기 위해 타입을 동적으로 체크합니다.

이를 Dynamic Type Resolution라고 하는 것..

 

따라서 p 명령을 통해 출력된 값은 프로토콜 타입이 아닌 구체 타입이 된 것이에요. ☺️

 

 

 

실제 코드에서는 프로토콜에 정의되지 않은 프로퍼티에 접근하지 못하기 때문에 에러가 발생하지만

LLDB 명령어를 입력할 때 다운캐스팅을 해주면 정상적으로 출력이 됩니다. 

 

 

이렇게 Dynamic Type Resolution이 끝나고 나면

 

 

 

Dynamic Type Resolution의 결과를 formatter로 보내서

사람이 읽을 수 있는 구조로 출력해줍니다.

 

 

 

만약 formatter가 없으면 첫 번째 모양처럼 출력이 되는데

당최 알아먹을 수가 없음.. ㅎ

 

formatter 절대 지켜

 

 

 

 

 

마지막 명령어 v를 만나보러 갑시당.

 

 

엥 p랑 달라진 게 없어보이는뎅

 

 

 

일단 v랑 frame variable은 동일한 커멘드.

 

 

 

v 명령어는 po와 p랑은 다르게 컴파일 및 실행을 하지 않기 때문에 속도가 빠릅니다.

코드를 컴파일하지 않기 때문에 디버깅 중인 코드랑 동일한 코드일 필요도 없어욥.

 

 

잠깐 v의 동작 과정을 살펴보겠습니다.

명령어를 입력하면 메모리에 있는 변수를 찾기 위해 프로그램 상태를 참조합니다.

 

다음엔 메모리에 있는 변수 값을 읽으면 Dynamic Type Resolution을 수행하는데

만약 변수의 프로퍼티에 대한 접근을 해야 한다면 해당 프로퍼티에 대한 Dynamic Type Resolution도 수행해야 합니다.

 

모든 작업이 끝나면 결과를 formatter에게 전달합니다.

 

 

 

p 명령어에서는 Activity 프로토콜 타입으로 선언된 cruise 변수에서

프로토콜에 없는 프로퍼티인 name에 접근할 수 없었습니다.

 

p 명령어에서는 LLDB에서 확인하기 위해 다운캐스팅을 해야 했었지만

v 명령어에서는 각 단계에서 Dynamic Type Resolution를 수행하기 때문에

cruise가 Trip 객체임을 알고 Trip 클래스가 가진 프로퍼티에 접근할 수 있습니다.

 

 

 

명령어를 정리해보자면

 

객체의 description을 출력해주는 명령은 po 하나뿐.

대신 p와 v는 formatter를 이용하여 객체를 나타냈습니다.

 

po와 p는 언어에 직접 접근하여 컴파일 및 실행을 했지만

v는 언어와 무관하며, 객체의 모든 서브 필드에 대한 Dynamic Type Resolution를 수행했습니다. 

 

 

 

 

 

다음은 formatter를 이용하여 출력을 조금 바꿔볼 거예욥.


Customizing Data Formatters

filter를 통해서 특정 타입에 대하여 원하는 값만 출력하게 만들 수 있습니다.

 

 

summary를 활용하면 객체에 대한 출력을 커스텀 문자열로 만들 수도 있어요.

 

객체를 var로 접근하여 객체의 프로퍼티를 서브스크립트로 접근하고 있는데,

formatter는 count와 같은 연산 프로퍼티에 접근할 수 없기 때문에 원소에 대한 접근을 하드 코딩할 수밖에 없습니다. 🥺

 

 

이를 위해 파이썬 formatter를 이용할 수 있습니다.

파이썬 formatter는 위에서 언급한 연산 프로퍼티 같은 계산을 수행할 수 있으며

현재 디버그 세션의 상태에 접근하기 위한 여러 객체를 공급하는 LLDB의 Scripting Bridge API에 대한 접근 권한을 갖고 있습니다.

 

 

 

 

 

 

 

그럼 파이썬 formatter를 이용하여 출력을 변경시켜보겠습니다.

 

 

script 명령어를 입력하고 나면 파이썬 인터프리터가 되며

현재 프레임은 lldb.frame으로 접근할 수 있습니다.

 

lldb.frame으로 SBFrame 인스턴스를 리턴 받고,

현재 프레임의 cruise 변수에 접근하기 위해 FindVariable을 이용합니다. 

 

 

변수의 서브 필드에 접근하기 위해서 GetChildMemberWithName을 수행합니다.

 

 

전에는 할 수 없었던 count에 대응되는 동작을 GetNumChildren으로 수행한 모습이에요.

destinations 배열 원소의 시작과 끝 원소를 GetChildAtIndex로 접근했습니다.

 

 

GetChildAtIndex를 통해 얻은 원소는 타입과 필드명이 존재하기 때문에

이를 잘라내기 위해서 GetSummary를 수행했습니다.

 

 

 

위에서 수행한 모든 명령을 한 py 파일의 함수 안에 모두 담아두면

 

 

 

lldb에서 커멘드를 통해 py 파일을 import 할 수 있고,

summary 명령과 py 파일의 함수명인 SummaryProvider를 결합하여 동일한 결과를 얻을 수 있습니다.

 

 

이렇게 만든 Summary는 lldb 콘솔뿐 아니라 Xcode에서 변수 값을 보는 부분에서도 적용이 됩니다.

 

마지막으로 name, destinations, 서브스크립트 등 타입의 서브 필드들을 커스터마이징하는 방법을 알아볼 거예요.

 

 

 

py 파일에 SummaryProvider를 만들었던 것처럼 함수를 또 만들어줍니다.

 

 

 

그리고 py 파일을 import 하고

synthetic 커멘드를 이용하여 해당 타입에 적용하면 끝 ☺️

 

 

 

 

 

 


References

https://developer.apple.com/videos/play/wwdc2019/429/

댓글