Xcode에는 이미 “연결 중”이라고 표시되지만 클라우드 Mac의 CPU 사용률은 거의 유휴 상태이고, 중단점은 파란색에서 회색으로 바뀌며, 변수 창에도 아무 결과가 나타나지 않을 수 있습니다. 이때 중지를 반복해서 누르거나 재시작하거나 DerivedData를 삭제하면 문제의 단서만 사라지는 경우가 많습니다. 더 효과적인 방법은 문제를 세 계층으로 나누는 것입니다. 대상 프로세스를 디버깅할 수 있는지, LLDB와 대상 사이에 세션이 수립되었는지, 현재 빌드 산출물에 대응하는 심볼을 찾을 수 있는지 각각 확인해야 합니다.
어느 계층에서 멈췄는지 먼저 판단하기
먼저 발생 시각, 프로젝트 커밋 번호, Scheme, Configuration, 대상 기기, 실행 방식을 기록한 뒤 Xcode 상태를 살펴봅니다. “연결 지연”, “앱 실행 지연”, “심볼 해석 지연”을 같은 문제로 취급해서는 안 됩니다.
| 현상 | 우선 확인할 항목 | 일반적인 결론 |
|---|---|---|
| 앱이 실행되지 않고 LLDB가 계속 대기함 | 대상 프로세스, 실행 인자 | 프로세스가 종료되었거나 다른 산출물이 실행됨 |
| 프로세스 PID는 표시되지만 UI가 응답하지 않음 | 프로세스 상태, 디버그 서비스 | 대상이 일시 정지 또는 차단되었거나 디버그 핸드셰이크가 완료되지 않음 |
| 중단점이 비어 있거나 회색으로 표시됨 | 모듈과 심볼 | 모듈이 로드되지 않았거나 경로가 변경되었거나 UUID가 일치하지 않음 |
| 실행을 멈출 수 있지만 지역 변수가 보이지 않음 | 최적화 수준, 디버그 정보 | Release 최적화로 변수가 병합되거나 제거됨 |
다른 터미널에서 프로세스 스냅샷을 저장합니다.
mkdir -p "$HOME/lldb-case"
date -u > "$HOME/lldb-case/time.txt"
ps -axo pid,ppid,state,%cpu,etime,command \
> "$HOME/lldb-case/processes.txt"
xcrun simctl list devices \
> "$HOME/lldb-case/simulators.txt"
state에 U가 장시간 표시되면 프로세스가 중단할 수 없는 대기 상태일 가능성이 있습니다. T가 계속 표시된다면 프로세스가 일시 정지된 상태입니다. 한 번의 스냅샷만으로는 추세를 확인할 수 없으므로 10초 간격으로 한 번 더 수집하는 편이 유용합니다.
정리 작업보다 증거 보존이 먼저입니다. 재시작으로 작업이 정상화되더라도 근본 원인이 사라졌다는 의미는 아닙니다.
프로세스와 디버그 세션 검증하기
올바른 산출물에 연결했는지 확인하기
이름이 같은 앱이 여러 시뮬레이터, 테스트 디렉터리 또는 이전 빌드 디렉터리에 동시에 존재할 수 있습니다. 먼저 LLDB에서 프로세스와 로드된 이미지를 확인합니다.
(lldb) process status
(lldb) target list
(lldb) image list -o -f
target list는 이번 빌드의 실행 파일을 가리켜야 합니다. image list에 표시된 대상 모듈의 경로는 예상한 Build Products 디렉터리에 있어야 합니다. 경로가 다른 DerivedData를 가리키더라도 곧바로 전체를 삭제하지 마십시오. 먼저 이전 경로를 기록하고 Scheme이 잘못된 빌드 산출물을 재사용했는지 확인해야 합니다.
LLDB 명령줄 자체가 계속 응답한다면 다음 명령을 실행합니다.
(lldb) thread list
(lldb) thread backtrace all
모든 스레드에서 백트레이스를 확인할 수 있다면 대개 디버그 통로는 이미 수립된 것입니다. 문제는 앱 자체의 대기 상태나 심볼 해석에 있을 가능성이 큽니다. 명령이 계속 응답하지 않으면 시스템 측에서 대상 프로세스를 샘플링합니다.
sample <PID> 10 1 -file "$HOME/lldb-case/app-sample.txt"
여러 sample 명령을 연속으로 실행하지 마십시오. 10초 분량의 샘플이면 메인 스레드가 잠금, 파일 I/O, 네트워크 호출 또는 시스템 프레임워크 중 어디에서 대기하는지 판단하기에 충분합니다.
UUID로 dSYM 대조하기
파일 이름이 같다고 해서 심볼까지 일치하는 것은 아닙니다. 링크할 때마다 서로 다른 UUID가 생성될 수 있으며, LLDB는 Mach-O와 대응하는 dSYM만 사용합니다.
dwarfdump --uuid "/path/to/MyApp.app/MyApp"
dwarfdump --uuid "/path/to/MyApp.app.dSYM"
양쪽에서 동일한 아키텍처의 UUID가 반드시 일치해야 합니다. 앱에 arm64가 포함되어 있다면 적어도 arm64 행을 대조해야 합니다. 다른 Archive, 다른 커밋 또는 다시 링크한 뒤 생성된 dSYM으로 대체해서는 안 됩니다.
모듈이 로드되었는지 확인하기
LLDB에서 대상 모듈을 조회합니다.
(lldb) image lookup -n AppDelegate
(lldb) image lookup -r -n 'YourModule\..*'
(lldb) breakpoint list
image lookup으로 심볼을 찾을 수 없지만 image list에는 모듈이 표시된다면 디버그 정보 형식을 먼저 확인해야 합니다. 개발 빌드는 일반적으로 DWARF 또는 DWARF with dSYM을 생성해야 합니다. 최적화 수준이 높은 구성을 사용하면 함수가 인라인 처리되고 지역 변수가 보이지 않을 수 있습니다. 이 경우 팀이 공동으로 사용하는 Release 구성을 임시로 수정하지 말고 진단 전용 Configuration을 만들어야 합니다.
중단점이 pending으로 표시된다고 해서 반드시 오류인 것은 아닙니다. 동적 프레임워크가 아직 로드되지 않았다면 중단점은 해당 이미지가 프로세스에 들어올 때까지 대기합니다. 같은 중단점을 반복해서 삭제하기보다 확실히 로드된 진입점에 중단점을 설정한 뒤 이후 모듈을 관찰해야 합니다.
시스템 로그에서 핸드셰이크 실패 원인 찾기
Xcode UI에는 흔히 “연결 실패”만 표시되지만, 시스템 로그에는 프로세스 종료, 권한 거부, 연결 중단에 관한 더 구체적인 정보가 남습니다. 문제를 재현하기 전에 범위를 제한한 로그 수집을 시작합니다.
log stream --style compact --info \
--predicate 'process == "debugserver" OR process == "lldb-rpc-server"' \
> "$HOME/lldb-case/debug-session.log"
문제를 한 번 재현한 뒤 Control-C로 중지합니다. 로그에는 사용자 이름, 프로젝트 경로, 기기 식별자가 포함될 수 있으므로 지원 요청에 첨부하거나 팀과 공유하기 전에 민감한 정보를 제거해야 합니다. 불필요하게 전체 시스템의 --debug 로그를 장시간 수집하지 마십시오. 대량의 잡음이 생성되어 필요한 정보를 선별하는 비용만 늘어납니다.
로그에 대상 프로세스가 연결 순간 종료된 것으로 나타나면 먼저 LLDB 없이 앱을 단독으로 실행해 안정적으로 유지되는지 확인해야 합니다. 테스트 프로세스만 실패한다면 테스트 호스트, 테스트 Bundle, 테스트 대상 앱을 각각 확인하십시오. 메인 앱의 PID만 살펴봐서는 안 됩니다.
반복 가능한 복구 순서 수립하기
복구 작업은 영향이 가장 적은 단계부터 시작해야 합니다.
- 현재 디버그 세션을 종료하되 대상 앱과 로그는 보존합니다.
- Scheme, Configuration, 실행 대상, 실행 파일 경로를 대조합니다.
- Mach-O와 dSYM UUID를 비교합니다.
- 전체 프로젝트를 정리하지 말고 현재 Target만 다시 빌드합니다.
- 이전 산출물이 재사용되었음을 확인한 경우에만 해당 프로젝트의 DerivedData 디렉터리를 삭제합니다.
- 문제가 계속 재현되면 스레드 백트레이스, 프로세스 샘플, 시스템 로그, 최소 재현 절차를 저장합니다.
다음 명령으로 디렉터리와 업데이트 시간을 먼저 확인하면 다른 작업의 데이터를 실수로 삭제하는 일을 피할 수 있습니다.
find "$HOME/Library/Developer/Xcode/DerivedData" \
-maxdepth 1 -mindepth 1 -type d -print
클라우드 Mac에서는 빌드, 테스트, 그래픽 디버깅 작업이 동시에 실행될 수 있습니다. 정리하기 전에 다른 파이프라인이 같은 디렉터리를 사용하고 있지 않은지 확인해야 합니다. 더 안전한 방법은 작업마다 독립된 -derivedDataPath를 사용해 디버그 현장과 자동 빌드 캐시를 분리하는 것입니다.
진단 결과를 인수 조건으로 전환하기
수정을 마친 뒤에는 최소 네 가지 항목을 검증해야 합니다. 콜드 스타트에서 연결할 수 있는지, 이미 실행 중인 프로세스에 연결할 수 있는지, 소스 코드 중단점이 적중하는지, 예외로 일시 정지되었을 때 핵심 변수를 확인할 수 있는지 점검합니다. 그런 다음 원격 세션을 종료했다가 다시 접속해 한 번 더 테스트하여 결과가 현재 그래픽 세션이나 임시 환경 변수에 의존하지 않는지 확인합니다.
팀은 UUID 대조, 산출물 경로, Configuration을 빌드 체크리스트에 포함할 수 있지만 개인 디렉터리를 스크립트에 하드코딩해서는 안 됩니다. 안정적인 디버그 절차란 단순히 “정리한 뒤 다시 시도”하는 것이 아닙니다. 모든 구성원이 동일한 증거를 바탕으로 장애가 프로세스, 세션, 심볼 중 어느 계층에 있는지 판단하고, 문제가 발생한 계층만 수정할 수 있어야 합니다.
자주 묻는 질문
LLDB가 프로세스에 연결되지만 중단점이 실행되지 않으면 무엇부터 확인해야 하나요?
image list로 대상 모듈의 로드 여부를 확인한 뒤 실행 파일과 dSYM의 UUID를 비교합니다. 모듈이 아직 로드되지 않았거나 UUID가 다르면 소스 위치를 올바르게 해석할 수 없습니다.
디버깅이 멈췄을 때 DerivedData를 바로 삭제해도 되나요?
먼저 프로세스 목록, LLDB 출력, 시스템 로그와 산출물 UUID를 저장해야 합니다. 원인이 이전 빌드 디렉터리로 확인된 뒤 해당 프로젝트의 디렉터리만 삭제하는 편이 안전합니다.
원격 디버깅을 위해 디버그 포트를 인터넷에 공개해야 하나요?
대부분 필요하지 않습니다. 클라우드 Mac 내부에서 Xcode와 LLDB를 실행하고 통제된 원격 데스크톱이나 SSH로 조작하는 방식을 우선 사용합니다.
단일 주문으로 독점 사용하는 물리 Mac mini가 필요하신가요?
M4와 M4 Pro 두 가지 구성, 네 가지 대여 기간, 다섯 개의 선택 가능한 노드를 확인한 후 워크플로에 맞는 장비를 선택하세요.