sungyup's.

react_native / Release Engineering / 4.1 Symbolication

4.1Symbolication

난독화된 번들과 코드를 매핑할 수 있는 심볼리케이션

TL;DR

추억의 쪽지 시험

React Native 프로젝트의 크래시 리포트를 관리하기 위해 여러가지 툴 중 Bugsnag을 도입하던 중, Source map, Android Map, Apple symbol files를 업로드해야 full stacktrace를 볼 수 있다는 문구를 접했다.

bugsnag official doc
이미지 출처 : #
난독화된 번들 정보만으론 디버깅하기 어렵다. 심볼리케이션 데이터를 공유해야 난독화된 코드가 소스 코드의 어느 곳에서 온 건지 정확히 파악할 수 있다.

그런데 이것들이 다 무엇이고, 어디에 쓰이며, 언제 어떻게 어디에 생성되는지를 몰라 이번 기회에 알아보았다.

1. JavaScript Source Map

JavaScript Source Map은 RN의 JS 번들 파일들의 난독화(obfuscation)/압축(minify)된 코드와 원본 JS/TS 파일 위치를 이어주는 매핑 표다. 즉, 에러 메시지를 보면 뜨는 런타임의 at xxx(37:337) 같은 이상한 스택을 src/screens/Home.tsx:87:10 같은 형태로 복원할 수 있게 도와주는 지도이다.

RN의 JS 번들 파일은 안드로이드는 index.android.bundle, ios는 main.jsbundle라는 이름으로 만들어진다.

JavaScript Source Map은 번들링 시 Metro가 생성하며, 안드로이드의 경우 index.android.bundle.map, ios는 main.jsbundle.map과 같은 이름으로 생성된다. 난독화를 수행하는 것은 Terser 라는 라이브러리로, Metro가 minifier 플러그인으로 호출해 사용한다.

React Native에서 0.70 버전부터 기본이 된 Hermes 엔진을 사용한다면, Hermes는 Ahead-Of-Time으로 자바스크립트 코드를 바이트코드로 컴파일을 하여 실행하기 때문에 바이트코드 전용 소스맵(Hermes 맵)이 따로 있다. 이 바이트코드는 안드로이드의 경우 index.android.bundle.hbc, iOS의 경우 main.jsbundle.hbc처럼 .hbc가 붙는 형태로 만들어지며, 이에 대한 Hermes 맵은 index.android.bundle.hbc.mapmain.jsbundle.hbc.map이다.

즉, Hermes를 쓰면 두 단계의 매핑이 생긴다. Hermes 맵은 바이트코드를 압축된 JS 번들로 바꿀 수 있게 해주고, JavaScript Source Map은 이 압축된 JS 번들을 원본 JS/TS 파일의 위치와 매핑할 수 있게 해준다.

2. Android Mappings, Android NDK Symbols

Android에서도 minifyEnabled 설정을 통해 release 빌드에서 난독화를 일으킬 수 있다. ProGuard와 R8은 클래스/메소드/이름을 a.b.c.A와 같은 형태로 바꾸는데, 이것을 원래 Java나 Kotlin 심볼로 역매핑하기 위한 mapping.txt 테이블이 안드로이드 매핑 테이블이다.

R8은 Google이 만든 Java/Kotlin 바이트코드용 압축/난독/최적화를 하는 도구(shrinker/optimizer/obfuscator)이다. Android Gradle Plugin에서 release 빌드때 기본적으로 사용되며, classes.dex라는 앱 덱스 파일을 만들고, mapping.txt라는 난독화 전후 심볼을 매핑할 수 있는 테이블을 만들어 버그를 추적할 때 스택을 복원하는데 쓸 수 있게 한다.

ProGuard는 R8 이전에 쓰이던 도구로, ProGuard의 문법은 그대로 R8에도 계승되어 R8에서도 ProGuard 문법이 쓰인다.

Android NDK Symbols는 네이티브 크래시가 스택에 기계어 주소만 남기기 때문에 이를 파일/함수/라인으로 되돌리기(symbolication)위한 파일이다. NDK(Native Development Kit) 빌드 시 .so 또는 전용 심볼 파일 .sym과 같은 형태를 띄는 파일로 생성된다.

NDK 심볼은 ABI(arm64-v8a, armeabi-v7a, x86_64 등) 별로 각각 필요한데, 배포용 .so는 심볼이 스트립되어 있기 때문에 심볼이 보존된 언스트립트 산출물을 업로드해야 한다.

3. iOS debug symbol maps (dSYM)

iOS는 C/C++/Objective-C(++)의 컴파일러인 Clang과 최적화/코드생성 백엔드인 LLVM(원래는 Low-Level Virtual Machine의 약자였으나, 이제는 그런 의미로 쓰이지 않음)이 만든 기계어 주소와 원본 함수/파일/라인 매핑이 담긴 디버그 심볼 묶음인 .dSYM 파일이 이 역할을 한다.

Xcode 아카이브/빌드 시 생성되며, ~/Library/Developer/Xcode/Archives/<날짜>/<앱명> <시간>.xarchive/dSYMs/*.dSYM 과 같은 위치/이름으로 저장된다.