Swift 디버그 정보에서 모듈을 정확히 추적하기
Module Tracking in Swift Debug Info
핵심 요약
- Swift 6.3부터 명시적으로 빌드한 Swift 모듈의 의존성과 경로를 기록해 LLDB가 모듈 이름만으로 대상을 찾던 문제를 줄인다.
- SwiftPM과 Xcode는 Swift driver가 새 경로 정보를 전달하므로 별도 설정이 필요 없지만, 자체 빌드 시스템은 컴파일 작업에 -debug-module-path를 전달해야 할 수 있다.
- Swift 6.4부터 자체 빌드 시스템은 Linux·Windows의 -modulewrap 작업과 Darwin의 -add_ast_path 옵션을 제거할 수 있으며, dSYM과 디버그 바이너리의 크기도 줄어든다.
도입
LLDB는 중단점에서 사용자 표현식을 컴파일하고 실행할 때 해당 코드가 속한 Swift 모듈을 가져온다. 기존에는 디버그 정보에서 모듈 이름을 찾은 뒤 같은 이름의 모듈을 탐색해, 동일 이름의 모듈이 여러 개 있거나 모듈 캐시가 비어 있으면 정확성과 속도에 문제가 생길 수 있었다. Swift 6.3부터 컴파일러와 LLDB는 모듈의 경로와 명시적 의존성을 추적하도록 바뀐다.
상세 요약
LLDB가 모듈을 찾는 방식
LLDB의 표현식 평가기는 중단점에서 Swift 코드를 JIT 컴파일해 실행한다. 로컬 변수의 위치와 타입을 확인하는 작업은 디버그 정보와 reflection metadata로 처리할 수 있지만, computed property처럼 함수 호출이 필요한 표현식은 현재 모듈을 Swift 컴파일러에 가져와야 한다.
기존에는 디버그 정보에 기록된 모듈 이름을 바탕으로 모듈을 암시적으로 가져왔다. 같은 이름을 가진 모듈의 복사본이 여러 개라면 잘못된 대상을 고를 수 있었다. 모듈 캐시가 비어 있을 때는 모듈과 의존성을 다시 컴파일하는 비용도 발생했다.
기존 빌드 방식의 비용
Darwin에서는 빌드 시스템이 링크 시 -add_ast_path 옵션으로 바이너리 Swift 모듈을 전달했다. dsymutil은 모듈을 dSYM의 __swift_ast 섹션에 넣고, dSYM 없이 디버깅할 때는 LLDB가 바이너리의 심볼 테이블을 읽었다. Windows, Linux, FreeBSD처럼 Swift를 모르는 링커를 쓰는 플랫폼에서는 -modulewrap으로 모듈을 담은 오브젝트 파일을 만들었다.
이 방식은 바이너리와 dSYM에 많은 모듈을 포함시켜 크기를 늘릴 수 있었다. 모듈이 이름으로 검색되므로 정확한 대상을 찾기 어려웠고, 바이너리 Swift 모듈은 생성한 컴파일러 버전에 묶여 장기 보관용 dSYM과도 맞지 않았다. 명시 모듈을 찾지 못하면 LLDB가 소스에서 모듈을 다시 컴파일할 수 있었다.
Swift 6.3의 경로·의존성 추적
Swift 6.3부터 명시적으로 빌드한 바이너리 모듈은 Swift 모듈 의존성도 기록한다. LLDB는 의존성을 이름으로 다시 찾지 않고 해당 정보에 따라 가져온다. 컴파일러는 각 오브젝트 파일이 속한 모듈의 경로를 디버그 정보에 저장하며, 이를 위해 -debug-module-path 옵션이 추가됐다.
SwiftPM과 Xcode는 Swift driver가 이 경로를 컴파일 작업에 전달하므로 프로젝트를 수정할 필요가 없다. Swift frontend를 직접 호출하는 자체 빌드 시스템은 각 컴파일 작업에 최상위 모듈 경로를 전달해야 한다. 분산 빌드에서 쓰는 경로 재매핑 옵션은 모듈 경로에도 적용된다.
Swift 6.4부터 가능한 빌드 변경
자체 빌드 시스템은 Swift 6.4부터 Linux와 Windows의 -modulewrap 작업을 제거하고, Darwin에서 -add_ast_path 링커 옵션을 제거할 수 있다. dsymutil은 더 이상 바이너리 Swift 모듈을 처리하지 않으므로 dSYM도 작아진다. 이 변화는 정적 아카이브 링크에서 -add_ast_path를 빠뜨려 발생하던 디버깅 문제도 없애도록 설계됐다.
바이너리 모듈이 dSYM에서 빠져도 변수 내용을 확인하거나 po로 객체 설명을 출력하는 데는 영향이 없다. LLDB는 Swift 5.6부터 바이너리의 reflection metadata로 이 작업을 수행할 수 있다. 함수 호출이나 computed getter 같은 복잡한 표현식 평가에는 바이너리 Swift 모듈이 필요하므로, LLDB가 원래 위치 또는 재매핑된 위치에서 모듈을 찾아야 한다.
컴파일 캐시를 사용하는 경우 모듈과 디버그 정보의 경로는 파일 위치가 아닌 콘텐츠로 식별되는 저장소 참조다. 또한 최신 개발 툴체인에서는 LLDB가 명시적 모듈 정보를 이용해 미리 컴파일한 bridging header와 그 의존성을 가져올 수 있다. Swift 6.4의 다른 변경 사항은 Swift 6.4 출시: Swift Build 기본 적용과 상호운용성·메모리 안전성 개선에서 확인할 수 있다.
결론
새 추적 방식은 LLDB가 모듈을 이름이 아닌 경로와 의존성 정보로 찾도록 바꾸며, Swift 6.4부터 자체 빌드 시스템은 일부 모듈 래핑·링커 단계를 제거할 수 있다. SwiftPM과 Xcode 사용자는 별도 변경 없이 혜택을 받는다. 자체 빌드 시스템은 Swift driver를 우회하는지 확인하고, 필요하면 각 컴파일 작업에 -debug-module-path를 전달해야 한다. dSYM에 바이너리 Swift 모듈이 없어져도 변수 검사와 po는 영향을 받지 않는다.