- Bun v1.1.5는 크래시나 패닉이 나도 개인정보 없는 약 150바이트 URL만으로 Zig/C++ 스택 정보를 전달하는
bun.report크래시 리포터를 추가함 - 기존 OS 크래시 리포터와 코어 덤프는 디버그 심볼·성능·개인정보·실행 파일 크기 부담이 커서 CLI 도구인 Bun에 적용하기 어려움
- 새 방식은 ASLR로 의미가 흐려진 주소를 모듈 기준 상대 주소로 바꾸고, 서버가 커밋 SHA와 플랫폼에 맞는 디버그 심볼로 함수명을 복원함
- URL에는 플랫폼, 서브커맨드, 커밋 SHA, 기능 플래그, 스택 주소, 크래시 타입과 메시지가 들어가며, 스택 주소는 base64 VLQ로 짧게 인코딩됨
- JavaScript/TypeScript 소스 코드나 환경 변수는 보내지 않고, Bun 팀이 진단에 필요한 Zig/C++ 스택 정보와 일부 메타데이터만 전달함
Bun이 자체 크래시 리포터를 만든 이유
- Bun에는 작성 시점 기준 2,600개 이상의 열린 GitHub 이슈가 있으며, 일부 이슈는 재현과 디버깅이 특히 어려움
- Sentry 같은 크래시 리포팅 서비스는 앱과 SaaS 제품에는 적합하지만, Bun 같은 CLI 도구에서 코어 덤프를 업로드하면 개인정보, 성능, 실행 파일 크기 문제가 커짐
- Bun v1.1.5는 Zig와 C++ 크래시 리포트를 위한 작은 새 포맷을 도입함
- 크래시 리포트는 약 150바이트 URL에 들어감
- 개인정보는 포함하지 않음
OS 크래시 리포터만으로 부족한 점
- macOS 같은 일부 운영체제에는 내장 크래시 리포터가 있지만, 제대로 활용하려면 보통 애플리케이션과 함께 디버그 심볼을 배포해야 함
- 디버그 심볼은 Bun 배포 크기를 크게 늘림
- Linux 디버그 심볼은 약 30MB
- macOS 디버그 심볼은 약 9MB
- Windows
.pdb파일은 250MB 이상
- Bun 실행 파일 예시는
llvm-strip전후로60M에서51M로 줄어듦 - 디버그 심볼 없이 크래시가 발생하면 스택 트레이스에는
???와 주소만 남아 활용도가 낮음 - ASLR(Address space layout randomization) 때문에 함수 주소에는 무작위 오프셋이 섞이며, 그대로는 함수명을 복원할 수 없음
bun.report의 동작 방식
- Bun v1.1.5에서 크래시나 패닉이 발생하면 Bun은 버전, 플랫폼, 실행 인자, 메모리 사용량, 크래시 메시지와 함께
bun.report링크를 출력함 - 사용자가 링크를 열면 미리 채워진 GitHub 이슈 폼으로 리디렉션됨
- URL 안에는 재매핑된 스택 트레이스가 인코딩되어 있음
- 서버는 URL에 담긴 정보를 바탕으로 스택 주소를 복원하고, Bun 팀이 읽을 수 있는 크래시 리포트로 변환함
주소를 읽을 수 있는 스택 트레이스로 바꾸는 절차
- 함수 주소는 애플리케이션 코드가 메모리에 로드된 위치를 가리키는 포인터이며, 보안상 무작위 오프셋이 포함됨
- 기본 아이디어는 원시 주소에서 바이너리의 기준 주소(base address) 를 빼 상대 주소를 얻는 것임
- 실제 구현은 플랫폼별 API 차이 때문에 더 복잡함
- Windows는
GetModuleHandleExW에GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS플래그를 사용하고, 모듈 포인터를 기준 주소로 삼음 - Linux는
dl_iterate_phdr로 로드된 모듈을 순회하고, 주소가 포함된 모듈의dl_phdr_info.dlpi_addr를 기준 주소로 사용함 - macOS는
_dyld_image_count,_dyld_get_image_header로 모듈을 순회하고_dyld_get_image_vmaddr_slide로 ASLR 슬라이드를 얻음- macOS 결과 주소에는 이미지 오프셋이 남아 있으며, Bun의 경우
0x100000000임 - URL을 짧게 만들기 위해 이 오프셋을 제거하지만,
llvm-symbolizer로 재매핑하기 전에는 다시 더해야 함
- macOS 결과 주소에는 이미지 오프셋이 남아 있으며, Bun의 경우
- Windows는
- Linux와 macOS에서는 첫 번째 모듈이 메인 애플리케이션 바이너리를 가리킴
- Windows에서는 모듈 이름과
peb.ProcessParameters.ImagePathName을 비교해 메인 바이너리인지 판단할 수 있음 - Bun은 로컬에서 디버그 심볼을 내려받고 파싱하지 않고, 서버에 디맹글링을 맡김
- 서버는 디버그 심볼을 캐시할 수 있음
- 스택 트레이스를 몇 초 안에 디맹글링할 수 있음
- 동시에 새 GitHub 이슈를 여는 링크 역할을 함
bun.report URL 구조
bun.reportURL에는 다음 정보가 인코딩됨- Platform: 플랫폼을 나타내는 한 글자. 예를 들어
w는 x86_64 Windows,M은 aarch64 macOS - Subcommand:
bun test,bun install,bun run같은 서브커맨드를 나타내는 한 글자 - Commit SHA: 현재 Bun 버전의 커밋 SHA이며, 나중에 디버그 심볼을 가져오는 데 사용됨
- Feature Flags: 크래시 전 사용된 API와 기능을 나타내는 표시
- Stack Trace Addresses: 앞 단계에서 계산한 주소들
- Crash Type: 크래시 타입을 나타내는 한 글자
- Crash Message: 크래시 타입에 따라 형식이 달라지는 메시지
- Platform: 플랫폼을 나타내는 한 글자. 예를 들어
- URL에 들어간 버전 번호는 실제 처리보다 사람이 읽기 위한 표시임
- 이 정보만으로도 일부 크래시 특징을 손으로 파악할 수 있음
w식별자를 보면 Windows 크래시임을 빠르게 알 수 있음- 문자열 끝부분의
A2를 보면 세그멘테이션 폴트를 식별할 수 있음
짧은 URL을 위한 VLQ 인코딩
- 스택 트레이스 주소는 URL을 짧게 유지하기 위해 base64 Variable Length Quantity(VLQ) 숫자로 인코딩됨
- VLQ는 작은 숫자를 더 적은 문자로 표현하면서 큰 숫자도 인코딩할 수 있음
- JavaScript 소스 맵에서 줄 번호를 저장할 때도 같은 기법이 사용됨
- 서버는 VLQ 값을 다시 상대 주소로 디코딩하고, 커밋 해시와 플랫폼을 사용해 디버그 심볼을 다운로드한 뒤
llvm-symbolizer로 함수명을 디맹글링함 - 예시 크래시에서는 Windows의 모듈 리졸버 코드 일부인
dirInfoCachedMaybeLog에서 어설션이 실패한 사실이 드러남
기능 플래그 인코딩
- URL은 64비트 정수도 인코딩하며, 각 비트는 Bun의 특정 기능 사용 여부에 대응함
- 이 플래그는 어떤 API와 시스템이 크래시에 영향을 줬을 가능성이 있는지 힌트를 줌
.env파일이 자동 로드되면dotenv기능이 설정됨fetch()를 사용하면fetch기능이 설정됨
- Bun은 전역 변수 컨테이너로 기능 사용 여부를 추적하고, 각 API 내부에서 해당 숫자를 증가시켜 사용을 표시함
- Zig의 컴파일 타임 메타프로그래밍을 사용해 기능 목록을 순회하고, 기능마다 1비트씩 쓰는 packed struct를 동적으로 생성함
inline for를 사용하면 컴파일 타임에 기능 목록을 순회하되, 실제 비트 설정은 런타임에 수행할 수 있음- 새 기능을 기존
Featuresstruct에 추가하면 크래시 리포터에서도 반복 작성 없이 처리됨 - 같은 방식은 C나 Rust 매크로로도 가능하지만, Bun 측 구현에서는 Zig
comptime이 더 단순하고 읽기 쉬운 방식으로 사용됨
코어 덤프와의 차이
- 코어 덤프는 훨씬 많은 정보를 담지만, 크기가 크고 디버그 심볼이 있어야 유용하며 민감하거나 기밀인 정보를 많이 포함할 수 있음
- Bun의 새 리포트 방식은 JavaScript/TypeScript 소스 코드, 환경 변수, 기타 민감 정보를 보내는 상황을 피함
- 기본적으로 모든 것을 보내는 대신, 문제 진단에 필요할 가능성이 높은 Zig/C++ 스택 트레이스와 몇 가지 세부 정보만 보냄
- 추가 정보가 필요하면 사용자에게 별도로 요청할 수 있음
- 이전처럼 매핑되지 않은 주소만 남는 상황보다 Bun 팀이 크래시를 진단하기 쉬워짐
데모
- 크래시 리포터를 테스트할 수 있는 작은 웹앱이 bun.report에 제공됨
- 어떤 크래시 리포트 URL이든 끝에
/view를 붙이면 해당 웹앱 화면으로 이동함