1. 클라우드 호스팅
  2. 블로그
  3. 크래시 로그 심볼화와 dSYM 아카이브 파이프라인 구축

크래시 로그 심볼화와 dSYM 아카이브 파이프라인 구축

CI/CD ·약 8분 읽기

크래시 로그 심볼화와 dSYM 아카이브 파이프라인 구축

title: "크래시 로그 심볼화와 dSYM 아카이브 파이프라인 - OnceMini"
summary: "dSYM 생성부터 UUID 검증, 자동 심볼화 스크립트까지, 클라우드 Mac mini에서 과거 릴리스의 크래시를 추적할 수 있는 아카이브 파이프라인 구축법."


크래시 로그 심볼화와 dSYM 아카이브 파이프라인 구축

지난주 테스트 팀이 TestFlight 크래시 로그 하나를 던져줬는데, 스택 트레이스에는 0x1042a3f88 같은 16진수 주소만 가득했고, 게다가 세 버전 전 빌드에서 발생한 문제였다. 로컬 Mac의 ~/Library/Developer/Xcode/Archives를 다 뒤져봤지만 해당 archive는 이미 Xcode의 자동 정리 기능으로 삭제된 뒤였다. 이런 일은 팀 내에서 거의 분기마다 한 번씩 벌어진다——로컬 개발 머신은 디스크 용량이 작고, CI 러너는 다 쓰면 바로 폐기된다. dSYM처럼 "빌드 시점에만 생성되고 이후에는 다시 구할 수 없는" 파일은 본질적으로 전용 아카이브 노드가 필요하다. 클라우드 Mac mini를 주 단위나 월 단위로 대여해서 디스크를 지우지 않고 유지하면 이 빈틈을 정확히 채울 수 있다.

심볼화를 왜 별도 파이프라인으로 분리해야 하는가

크래시 로그의 주소는 해당 빌드를 생성할 때 나온 dSYM과 짝을 맞춰야만 함수 이름과 라인 번호로 복원된다. dSYM과 바이너리의 대응 관계는 UUID로 엄격하게 고정되어 있어서, 다른 버전의 dSYM으로 심볼화를 시도해도 에러는 나지 않고 단지 잘못된 라인 번호가 나올 뿐이다——이게 가장 쉽게 무시되고, 사후에 발견하기도 가장 어려운 함정이다.

CI 플랫폼의 러너는 대개 임시 인스턴스라 빌드가 끝나면 폐기되므로 과거 dSYM을 보관하지 않는다. 로컬 개발 머신들은 각자 따로 관리되기 때문에 어느 한 사람의 컴퓨터에도 팀 전체 버전의 dSYM이 다 모여 있지 않다. 해법은 단순하다: 디스크가 지워지지 않고 언제든 SSH로 접속해 스크립트를 실행할 수 있는 머신을 하나 마련해서, archive마다 생성되는 dSYM을 빌드 번호별로 저장하는 전용 아카이브 노드로 삼는 것이다.

환경 준비: dSYM을 빌드 산출물과 함께 확실히 남기기

먼저 Xcode 프로젝트의 디버그 정보 포맷이 dwarf-with-dsym으로 설정되어 있는지 확인한다(Release 구성에서는 기본값이다). 이렇게 해야 archive 시 독립된 .dSYM 패키지가 생성된다:

xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -archivePath build/MyApp-$(git rev-parse --short HEAD).xcarchive \
  DEBUG_INFORMATION_FORMAT=dwarf-with-dsym

archive가 완료되면 dSYM은 실제로 .xcarchive/dSYMs/ 디렉터리 안에 들어있다. .ipa만 복사하지 말 것——많은 팀이 처음 부딫히는 함정이 바로 설치 패키지를 내보낼 때 이 단계를 빼먹는 것이다. 나중에 되돌리려면 같은 commit으로 빌드를 다시 트리거하는 수밖에 없다.

아카이브 디렉터리 설계와 UUID 검증

클라우드 Mac mini에서 빌드 번호별로 디렉터리를 만들고, 업로드 직후 바로 UUID 검증을 실행해서 올바른 파일이 저장되었는지 확인한다:

dwarfdump --uuid MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
dwarfdump --uuid MyApp.app/MyApp

두 명령의 출력 UUID는 한 글자도 다르지 않게 일치해야 한다. 아카이브 기록은 간단한 표로 관리하면 추적하기 편하다:

build git commit dSYM UUID(arm64) 업로드 시각
214 a91f3c2 6B2E1A4F-... 07-18 09:12
215 c0d7e91 9F1D8C33-... 07-25 14:03
216 e3a4f10 2C77B0A9-... 08-01 10:47

디렉터리 구조는 /archive/dsym/{build}/로 고정하고, 각 계층에는 dSYM 하나와 commit·UUID를 기록한 meta.json 하나만 둔다. 검색 스크립트는 빌드 번호로 바로 위치를 찾을 수 있어서 매번 손으로 grep할 필요가 없다.

심볼화 스크립트: 크래시 로그를 읽을 수 있는 스택으로

크래시 로그를 받으면 atos로 주소를 하나씩 복원하는 방식이, symbolicatecrash의 전체 리포트를 훑는 것보다 의심되는 프레임 하나를 더 빠르게 찾아낼 수 있다:

atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
     -arch arm64 \
     -l 0x1000 \
     0x1042a3f88 0x1042a4210

-l 뒤에는 바이너리의 로드 베이스 주소를 넣는데, 크래시 로그의 Binary Images 섹션에서 그대로 복사하면 된다. 주소 목록은 한꺼번에 넘길 수 있고, 출력에는 함수 이름과 소스 파일 라인 번호가 함께 나온다. 로그 전체를 일괄 처리할 때는 몇 줄짜리 셸 스크립트로 각 줄에서 주소를 추출하고 명령을 조합해 원본 파일에 다시 써넣으면 되고, GUI 도구에 의존할 필요는 없다.

흔한 함정과 체크리스트

  • 아키텍처 혼동: M 시리즈 칩에서 돌아가는 앱은 arm64 단일 아키텍처만 가지지만, 예전 스크립트에 흔히 남아있는 -arch x86_64 파라미터를 쓰면 atos가 아무 변환도 하지 않고 원래 주소를 그대로 조용히 반환한다.
  • 로드 베이스 주소 계산 오류: 0x100000000 같은 기본값을 그냥 갖다 쓰고 로그의 Binary Images 섹션에서 실제 베이스 주소를 확인하지 않는 것이, 잘못된 라인 번호가 나오는 두 번째로 흔한 원인이다.
  • 아카이브 노드 권한이 너무 넓음: dSYM은 소스 코드 수준의 디버그 정보에 해당하므로, 아카이브 디렉터리는 릴리스 담당자와 CI 서비스 계정에만 읽기 전용 권한을 부여하는 것을 권장한다. 다른 팀과 같은 머신을 공유하다가 실수로 삭제되거나 덮어써지는 걸 막을 수 있다.

우리 내부 경험으로는, 아카이브 노드가 없던 시절에는 3개월 전 크래시 로그 하나에 맞는 dSYM을 찾느라 두 사람이 반나절을 썼다. 고정된 아카이브 플로우를 갖춘 뒤로는 이런 요청이 보통 5분 안에 결과가 나온다.

심볼화 파이프라인을 일상 릴리스에 통합하기

fastlane 릴리스 lane 끝에 한 단계를 추가해서, 방금 생성된 dSYM을 아카이브 노드로 곧바로 동기화한다. 문제가 터진 다음에야 허둥지둥 찾는 게 아니라:

lane :release do
  build_app(scheme: "MyApp")
  sh("scp -r ../build/MyApp.app.dSYM dev@archive-node:/archive/dsym/#{ENV['BUILD_NUMBER']}/")
end

이 단계는 아카이브 노드가 상시 켜져 있고 디스크가 충분해야 성립하는데, 임시 CI 인스턴스로는 불가능하다. 상시 구동되는 클라우드 Mac mini가 이 역할에 딱 맞는다——월 단위로 고정 머신 한 대를 빌려 아카이브와 심볼화 스크립트 전용으로 돌리는 게, 이 용도만을 위해 하드웨어를 직접 사는 것보다 훨씬 경제적이고, 패키징용 빌드 머신 리소스도 잡아먹지 않는다.

자주 묻는 질문

로컬에서는 심볼화가 자주 실패하는데 전용 아카이브 노드에서는 왜 성공하나요?

로컬 개발 머신의 dSYM은 Xcode 정리나 OS 재설치로 사라지고, 팀원 각자의 컴퓨터에도 모든 버전이 갖춰져 있지 않습니다. 아카이브 노드는 빌드 번호별로 dSYM을 영구 보관하므로 과거 크래시 로그도 일치하는 UUID의 심볼 파일을 찾을 수 있습니다.

크래시 로그의 바이너리와 손에 있는 dSYM이 같은 빌드인지 어떻게 확인하나요?

dwarfdump --uuid로 dSYM과 .app 바이너리의 UUID를 비교해 완전히 일치하는지 확인합니다. 불일치해도 오류 없이 줄 번호만 틀리게 나오기 때문에 가장 놓치기 쉬운 함정입니다.

App Store Connect가 자동 생성한 dSYM과 자체 CI에서 내보낸 dSYM은 뭐가 다른가요?

내용은 같지만 App Store Connect 다운로드는 지연이 있고 보관 개수도 제한적입니다. 자체 파이프라인은 아카이브 시점에 즉시 저장돼 반년 전 버전을 추적해야 할 때 더 유리합니다.

빌드 프로세스를 위한 전용 Mac mini가 필요하신가요?

지금 주문