스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
LLDB를 통한 Swift 디버깅
디버깅을 위해 복잡한 Swift 프로젝트를 설정하는 방법을 배울 수 있습니다. LLDB의 내부 및 디버깅 정보에 대해 자세히 알아봅니다. 또한 빌드 서버에 구축된 디버깅 코드 또는 맞춤형 빌드 시스템의 코드와 같은 복잡한 시나리오에 대한 모범 사례를 공유합니다.
리소스
관련 비디오
WWDC21
WWDC19
WWDC18
-
다운로드
♪ ♪
안녕하세요, 여러분 저는 LLDB를 사용한 멋진 디버깅 경험을 위한 프로젝트 설정 방법을 알려드릴 Adrian입니다 LLDB는 Xcode와 함께 제공되는 기본 디버깅 기술로서 LLDB를 사용하면 응용 프로그램에 중단점을 설정하고 실행을 중단할 수 있고 변수 및 개체의 상태를 검사하고 코드를 탐색하는 등의 작업을 수행할 수 있어요 LLDB는 코드가 수행하는 작업을 이해하는 데에 도움이 될 수 있고 코드 동작이 예상과 다른 지점을 찾을 수 있도록 돕기도 하죠 코드를 이해하고 탐색하는 강력한 도구에요 LLDB에 대한 자세한 내용은 WWDC21의 '종단점 개선사항 찾기'와 같은 이전 비디오를 참고해 주세요 오늘은 Swift 코드 디버깅에 특이한 영향을 주는 몇 가지 고급 워크플로를 살펴볼게요 여러분은 타사 프레임워크를 앱에 통합한 상태일 수도 있고 앱과 팀이 대부분의 코드가 지속적 통합 시스템에 의해 빌드되는 지점까지 성장했을 수도 있어요 회사의 인프라와 통합하기 위해 사용자 정의 빌드 시스템을 사용 중일 수도 있겠네요 다른 소프트웨어 개발자를 위한 소프트웨어를 구축 중일 수 있고 아니면 그냥 LLDB에 대해 더 자세히 알고 싶을 수도 있죠 제 목표는 LLDB가 작동하는 방식과 이를 위해 빌드 시스템에서 필요한 정보를 이해하기 쉽게 여러분을 돕는 거라 볼 수 있어요 다뤄볼 예시로 사용할 작은 프로젝트를 준비해봤는데 제가 컴파일러 엔지니어다 보니 게임을 좋아해서 여가 시간에는 텍스트 어드벤처에 쓰는 파서를 작성하곤 합니다 이건 제가 최근에 Swift에서 시작한 건데요 얼마만큼 했는지 한 번 보여드릴게요 게임은 텍스트 인터페이스를 사용하니 터미널에서 실행해보죠 모든 멋진 모험들 처럼 우리도 인벤토리 확인부터 시작해 볼게요
이 게임은 현대적인 배경에서 진행되고 제겐 iPhone이 있네요 다음으로 주위를 살펴볼게요
흠, 이 센서가 흥미로워 보이네요 혹시 센서에 iPhone을 사용할 수도 있을까요?
iPhone을 떨어뜨렸다고요? 음, 이건 제가 보여드리고자 한 게 아니에요 게임에 버그가 있나 보네요 우리가 지금 디버거 얘기를 하고 있어서 다행이에요 파서에 중단점을 설정하고 명령을 다시 실행해볼게요
.
먼저 명령을 올바르게 읽었는지 확인해야 하는데요 'words' 변수에는 토큰화된 명령이 포함됩니다
아, 이게 생각대로 잘 안되는군요 왜 이러는지 모르겠어요 분명 어젠 아무 문제 없이 디버거를 썼고 어젯밤에는 터미널에서 텍스트 스타일을 지정하기 위해 이 UI 프레임워크를 통합했는데 말이죠 해당 프레임워크의 개발자는 프레임워크의 나이틀리 빌드를 만들어내는 지속적 통합 시스템을 가지고 있고 저는 최신 프레임워크에 직접 연결한 상태예요 이 프레임워크가 디버깅 문제와 관련이 있을지도 모르겠네요 예를 들어 전 이미 디버그 빌드를 분명히 다운로드했음에도 불구하고 프레임워크의 소스 코드로 들어갈 수 없다는 것을 알아챘어요 한번 보세요
디스어셈블리만 보이네요
이제 여기서 무슨 일이 있었는지를 한 번 알아내 볼게요 먼저 소스 코드가 보이지 않는 이유를 찾아보겠습니다 소스 코드를 표시하기 위해서 LLDB에는 어떤 것이 필요할까요? 컴파일러는 함수를 컴파일할 때 기계어를 생성하죠
그리고 매핑을 위해 실행 파일의 주소를 소스 파일이나 줄 번호에 디버거의 이동 경로를 남기는 데 그 반대도 마찬가지예요 이러한 이동 경로를 디버그 정보라고 하죠 Apple 플랫폼에서 디버그 정보는 개체 파일에 저장되고 보관, 배포를 위해 디버그 정보를 .dSYM 번들에 연결할 수 있어요 디버그 정보 링커는 dsymutil이라고 하는데요 LLDB는 Spotlight를 사용하여 .dSYM 번들을 찾기 때문에 디스크 상의 위치 측면에서 매우 유연한 특성이 있죠 이제 디버그 정보의 작동 방식을 봤으니 예시로 돌아가 볼게요 먼저 LLDB가 실제로 프레임워크에 대한 dSYM을 찾았는지 확인하는데 이미지 목록 명령을 사용하면 이 작업을 할 수 있어요 이 UI 프레임워크는 'TerminalInterface'라고 부릅니다
맞아요, LLDB는 프레임워크에 대한 dSYM을 찾았습니다 즉, 디버그 정보에 액세스할 수 있다는 말이죠 그리고 '이미지 조회'를 사용하여 현재 주소에 대한 추가 정보도 얻을 수 있어요
혹시 다양한 옵션에 대해 더 알고 싶다면 LLDB에 기본 제공되는 좋은 도움말이 있으니 참고 해 보세요
아, 소스 코드가 왜 없는지 그 이유를 알 것 같네요 여기에서 이 소스 경로는 제 로컬 컴퓨터에 있는 위치가 아니라 빌드 서버에 있던 위치를 가리키네요 이건 고칠 수 있는 부분입니다 LLDB에는 이러한 경로 리디렉션을 위한 소스 맵이 내장되어 있어요
지금 바로 명령을 입력할 수 있지만 이 변경 사항을 더 영구적으로 만들어 볼게요 Scheme 에디터에서는 Product, Scheme이나 Edit scheme… 또는 그냥 재생 버튼을 클릭해서 불러올 수 있는 프로젝트별 LLDB 초기화 파일을 정의할 수 있어요 전 이미 이 프로젝트에 하나를 추가했어요
이제 LLDB를 설정했으므로 프로젝트를 다시 실행해 볼게요
그럼 이제 소스 코드가 생겼습니다
LLDB는 소스 경로를 'settings set target.source-map' 으로 remap할 수 있고 이 명령을 프로젝트의 .lldbinit 파일에 넣으면 자동으로 실행됩니다 아니면 각 .dSYM 번들에 경로 prefix 리맵핑 사전을 넣을 수 있는 XML .plist 파일을 사용할 수도 있어요 서버에서 최신 빌드를 가져오는 다운로드 스크립트가 있다면 해당 스크립트를 수정하여 적절한 리맵핑 사전을 다운로드한.dSYM에 자동으로 삽입할 수 있어요 이 프로세스에 대한 자세한 내용은 LLDB 웹사이트를 참고 하세요
소스 경로는 특정 언어가 아니기 때문에 이 메소드는 Swift, C++ 및 Objective-C 프로젝트에서 모두 작동합니다 Apple 플랫폼 기호에 대한 자세한 내용은 WWDC21의 'Symbolication: 초급 그 이상'을 참고하세요 소스 코드가 빌드 서버 팜에서 컴파일되면 소스 파일에 대한 원격 경로가 시스템마다 다를 수 있어요 시스템 당 리맵핑 prefix를 하나씩 정의해야 하지 않고 디버그 정보에 넣기 전에 소스 경로를 정규화하도록 컴파일러에 지시할 수 있어요 이것은 -debug prefix-map 옵션을 사용하여 수행하는 것이고 이 방법으로 시스템 별 경로 prefix는 LLDB의 로컬 경로에 리맵핑할 수 있는 고유한 표준 플레이스 홀더로 대체될 수 있어요 소스 탄젠트로 이동하기 전에 전 '단어'에 대한 개체 설명을 프린트하려고 했는데요
그게 잘 안됐어요 사실 '단어'라는 표현을 평가하는 것 자체도 잘 안됐어요
최소한 변수 보기를 통해서는 변수를 확인할 수 있죠
Xcode 변수 보기에 해당하는 콘솔은 프레임 변수 또는 'v' 명령입니다
이러한 명령 간의 뉘앙스에 대해 자세히 알아보려면 WWDC19의 'LLDB: Beyond 'po''를 확인해 보세요 그렇다면 po는 무엇이며 왜 여전히 작동하지 않는 걸까요? 이 의미를 알기 위해서는 먼저 LLDB에 대해 더 많이 알아야 해요 참고로 LLDB는 디버거입니다 하지만 LLDB는 단순한 디버거가 아니죠 이건 컴파일러이기도 하니까요! 디버거의 기능 외에도 LLDB에는 완벽하게 작동하는 Swift 및 Clang 컴파일러 사본도 포함되어 있어요 이러한 컴파일러는 p 및 po 명령 에일리어스를 통해 알 수 있는 LLDB의 표현식 평가기를 강화하죠 표현식 평가기를 사용하면 변수를 보는 것 외에도 계산을 수행하고 함수를 호출하고 프로그램 상태를 변경할 수도 있죠 WWDC18의 'Xcode와 LLDB를 통한 고급 디버깅'을 참조하여 해당 명령으로 어떤 작업이 가능한지 확인해 보세요 디버거는 로컬 변수의 형식을 어떻게 지정하나요? 컴파일러에서 제공하는 디버그 정보는 메모리에서 변수가 저장되는 위치를 디버거에 알려줍니다 하지만 그 정보만으로는 LLDB가 원시 바이트의 무작위 분류만 표시할 수 있어요 그럼 LLDB는 이걸 어떻게 형식이 지정된 아웃풋으로 변환할까요? 그 답은 타입에 있습니다 LLDB는 타입 정보를 통해 소스 변수의 구조와 메모리 레이아웃을 이해할 수 있어요 그리고 LLDB는 타입 정보를 통해 집계 타입이 어떤 필드를 가졌는지 인지하고 타입을 통해 LLDB가 적절한 데이터 포맷터를 설정하여 이를 pretty print할 수 있죠 이제 타입 정보가 어디에서 오는지 살펴볼게요 프레임 변수와 v 명령이 있는 디버거 측면에서는 Debug Info에서 타입 정보를 가져와요 또한 LLDB는 Swift 리플렉션 메타데이터에서 타입을 가져오죠 표현식 평가기와 po가 있는 컴파일러 측면에서는 LLDB가 모듈에서 타입 정보를 가져옵니다 이 깔끔한 분리는 Xcode 14의 새로운 기능이며 표현식 평가기가 아닌 경우에도 변수 보기가 완전히 작동가능한 이유를 설명하기도 합니다 모듈은 컴파일러가 타입 선언을 구성하는 방법이며 Swift 컴파일러는 모듈을 가져오는 많은 방법을 알고 있죠 더 자세히 알아보기 전에 우선 편리한 새 기능을 보여드릴게요
컴파일러 측에서 발생하는 문제 진단은 어떻게 시작할까요? LLDB는 올해 'swift-healthcheck' 라는 새로운 명령을 추가했는데요 이건 모듈 가져오기가 실패했는지 확인하기 위한 첫 번째 단계에요 어떻게 작동하는 건지 한 번 보여드릴게요 문제가 발생한 후 swift-healthcheck를 실행하면 Swift 표현식 평가기 구성 로그에 액세스할 수 있어요
그럼 로그 끝부분에서 LLDB가 'TerminalUI' Swift 모듈을 가져오는 데 문제가 있음을 알 수 있죠 이름에 따라 저는 이게 TerminalInterface 프레임워크의 구현 세부 사항이라고 가정해 볼게요 이 누락된 모듈이 문제인데요 self의 타입은 UI 구현에 있어 일반적이고 해당 유형을 포함하는 모듈이 없기 때문에 표현식 평가기는 '셀프'의 동적 타입을 실현할 수 없기 때문이죠 이제 프레임워크 개발자 분에게 메시지를 보내고 조사를 요청해 볼게요 제 경험상 이 분들은 굉장히 응답을 잘 해주세요 누가 아나요? 이 영상이 끝나기도 전에 해결책을 찾을지도 모르죠 그동안 LLDB의 컴파일러가 Swift 모듈을 찾는 방법을 살펴볼게요
제 앱에는 자체적인 Swift 모듈이 있어요 Foundation과 같은 시스템 프레임워크를 가져올 수도 있어요 시스템 프레임워크는 SDK에 있는 안정적인 Swift 인터페이스 파일로 모든 Swift 모듈은 모듈 맵 파일의 도움으로 그룹화되는 하나 이상의 헤더 파일을 부르는 명칭인 Clang 모듈을 가져올 수 있죠 Clang 모듈은 다른 Clang 모듈에 종속될 수 있고
특정 장소에 있는 프레임워크의 Swift 모듈도 앱으로 가져옵니다 SDK의 일부가 아닌 textual Swift 인터페이스 파일도 가져올 수 있어요 방법을 알고 싶다면 WWDC19의 'Swift의 바이너리 프레임워크'를 보세요 또한 앱에서 Swift 코드를 포함하는 정적 라이브러리에 대해 링크할 수 있으며 그 다음에는 Swift 모듈도 함께 제공됩니다 음, 그래도 아직 끝난게 아니에요 브리징 헤더도 Clang 모듈을 가져올 수 있다는 내용도 말씀드려야겠네요 마지막으로 LLDB만의 특수 기능이 있는데요 일부 모듈 내용은 디버그 정보만으로도 재구성 가능합니다 엄청나게 많은 소스라 할 수 있죠 도대체 LLDB는 이 많은 것들을 어떻게 찾는 걸까요?
빌드 시스템은 LLDB가 모듈을 찾도록 모듈을 패키지화합니다 시스템 프레임워크의 모듈은 SDK에 유지되는 거죠 LLDB는 프로그램에 연결할 때 읽을 일치하는 SDK를 찾습니다 개체 파일에서 직접 디버깅할 때 LLDB는 빌드 시간 중에 있던 모든 비 SDK 모듈을 찾아내죠 dsymutil은 모든 동적 라이브러리 프레임워크, dylib, 실행 파일에 대해 .DSYM 번들이란 디버그 정보 아카이브를 패키징 할 수 있습니다
각 .dSYM 번들은 브리징 헤더 textual Swift 인터페이스 파일 및 가장 중요한 디버그 정보를 포함할 수 있는 바이너리 Swift 모듈을 포함할 수 있죠 그럼 다 해결이 되는 거예요 모두 다는 아니고 정적 아카이브에 속하는 Swift 모듈을 제외한 모든 것이 되겠네요
dsymutil이 Swift 모듈을 선택하려면 링커에 등록해야 하는데 동적 라이브러리 및 실행 파일의 경우 빌드 시스템이 자동으로 이 작업을 수행하게 돼요 하지만 정적 아카이브는 링커에 의해 생성되지 않고 zip 파일과 같은 개체 파일의 모음일 뿐이에요 이는 정적 아카이브를 연결하는 모든 실행 가능한 것과 동적 라이브러리가 f링커에 Swift 모듈을 등록하는 책임이 있다는 것을 의미하죠 대부분의 경우 이 작업은 Xcode 빌드 시스템이 수행합니다 그렇지만 고유한 사용자 정의 빌드 시스템을 유지 관리하거나 사용자 정의 빌드 규칙을 정의한 경우라면 이건 꼭 알아둬야 할 사항이에요
Apple 링커를 사용할 때는 -add-ast-path 옵션으로 Swift 모듈을 등록해야 해요 빌드 로그를 확인하여 이런 경우에 해당하는지 확인해보세요 dsymutil을 사용하여 실행 파일의 기호 테이블을 덤프하고 'swiftmodule'에 대한 grep을 사용하여 작동을 확인할 수도 있죠
Linux와 같은 다른 플랫폼에서는 swift 드라이버가 -modulewrap action을 지원하며 이는 바이너리 Swift 모듈 파일을 나머지 디버그 정보와 함께 바이너리에 연결할 수 있는 객체로 변환해요 그건 LLDB가 찾아낼 거예요 프레임워크 개발자 분이 정말 빨리 대응을 해주시는 군요 예상대로 프레임워크 빌드 시스템의 일부로 정적 아카이브가 사용되는 것으로 확인됐네요 그리고 그건 dSYM 번들에서 누락된 정적 아카이브에 속한 Swift 모듈이었어요 이제 저는 프레임워크의 고정된 버전을 설치했습니다 누락된 정적 모듈을 링커에 등록했으니 dsymutil이 이를 수집할 수 있겠네요
이제 스스로 해결이 될 것 같아요
그리고 '단어'의 개체 설명을 프린트할 수 있게 되었어요
어쨌든 콘솔을 사용하고 있으니 s 에일리어스를 사용하여 parseFrom 함수로 들어가 볼게요
이제 여기서 복사 붙여넣기 오류인 버그도 쉽게 찾을 수 있어요
.
그럼 우리는 누락된 Swift 모듈의 퍼즐뿐만 아니라 게임의 첫 번째 퍼즐도 푼거나 다름없어요
마무리하기 전에 주의해야 할 세부 사항이 하나 더 있어요 Swift 컴파일러는 Clang 헤더 검색 경로 및 기타 관련된 옵션을 바이너리 .swiftmodule 파일로 직렬화 하는데 Clang 모듈 종속성 가져오기가 빌드 중에 작동하게 하기 때문에 이건 좋은 점이라 할 수 있어요 하지만 다른 시스템에서 빌드할 때는 안 좋을 수 있어요 그래서 바이너리 .swiftmodule을 다른 시스템으로 보내기 전에 -no-serialize-debugging-options Compiler 플래그를 사용하여 빌드하는 방법을 고려해 보세요 Xcode에서 이것은 SWIFT_SERIALIZE_DEBUGGING _OPTIONS 설정을 통해 제어합니다
다음 설정 중 하나로 LLDB에 이 검색 경로를 재도입할 수도 있죠 그럼 이제 배운 내용을 다시 짚어볼게요 한 시스템에서 다른 시스템으로 코드를 전송하려면 예상되는 디버깅 수준에 대해 한번 고민해 봐야 해요 예를 들어, 바이너리 프레임워크를 다른 개발자에게 제공하고 그것들이 디버거에서 여러분의 코드에 들어갈 것 같지 않다면 Swift 모듈을 textual .swiftinterface파일에 제공하는 게 좋아요 하지만 개발자가 다운로드한 빌드 아티팩트를 디버그해야 하는 빌드 서버 또는 지속적 통합 시스템을 설정하는 경우라면 바이너리 Swift 모듈을 빌드하고 검색 경로 직렬화를 끄는 것을 고려해야 하죠 -debug-prefix 맵 옵션을 사용하면 디버그 정보에서 서버의 소스 경로를 canonicalize할 수도 있어요 이제 모든 내용을 다뤄봤는데요 오늘은 디버거와 컴파일러로서의 LLDB의 이중 특성을 배웠어요 디버거의 작동에는 디버그 정보와 리플렉션 메타데이터가 필요하고 Xcode 변수 보기와 v 명령을 제공합니다 컴파일러는 Module이 필요하고 이건 검색 경로에 민감하죠 이건 expr, p 및 po 명령 뒤에 있다고 볼 수 있고 LLDB의 새로운 swift-healthcheck 명령을 사용하면 컴파일러 진단을 얻을 수 있죠 그럼 시청해 주셔서 감사합니다!
-
-
5:04 - Show info about all loaded dylibs
image list
-
5:24 - Show debug info for a code address
image lookup -va $pc
-
5:58 - Show help for target.source-map
settings list target.source-map
-
6:37 - Remap source paths in LLDB
settings set target.source-map /Volumes/BUILD_SERVER/projects /Users/demo/Desktop/Adventure/3rdparty
-
7:02 - Source path remapping
settings set target.source-map prefix new
-
8:13 - Debug prefix map
-debug-prefix-map $PWD=/BUILDROOT
-
8:32 - Print object description of "words"
po words expr -O -- words
-
8:40 - Evaluate the expression "words"
p words expr words
-
8:58 - Display the variable "words"
v words frame variable words
-
10:10 - Raw memory of a Swift variable
mem read UnsafePointer<Items>(self.inventory)
-
11:59 - See diagnostics from LLDB's embedded Swift compiler
swift-healthcheck
-
15:47 - Register Swift modules with the Linker
ld … -add_ast_path /path/to/My.swiftmodule
-
16:05 - Verify Swift modules were registered in binary
dsymutil -s MyApp | grep .swiftmodule
-
16:12 - Wrapping Swift modules in object files on Linux
swiftc -modulewrap My.swiftmodule -o My.swiftmodule.o
-
16:52 - Evaluate the expression "self"
p self
-
16:58 - Print object description of "words"
po words expr -O -- words
-
17:08 - Step into function call
s thread step-in
-
17:10 - Step over instruction
n thread step-over
-
18:23 - Avoiding serialized search paths in Swift modules (command line)
-no-serialize-debugging-options
-
18:24 - Avoiding serialized search paths in Swift modules (Xcode)
SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO
-
18:32 - Reintroducing search paths in LLDB
settings set target.swift-extra-clang-flags … settings set target.swift-framework-search-paths … settings set target.swift-module-search-paths …
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.