Trang chủ

Giải mã ký hiệu một báo cáo sự cố từ App Store

Một báo cáo sự cố từ người dùng hay từ App Store Connect thường đến dưới dạng một danh sách địa chỉ thập lục phân:

0   MyApp    0x0000000102a4c8d0 0x102a40000 + 51408
1   MyApp    0x0000000102a4b120 0x102a40000 + 45344
2   UIKitCore 0x00000001a2c31f44 ...

Cái đó không đọc được và hoàn toàn cứu được. Các địa chỉ ánh xạ sang tên hàm và số dòng, và thứ làm việc ánh xạ ấy là cái dSYM.

dSYM là toàn bộ câu chuyện

Khi Xcode build có bật tối ưu, nó bóc các tên ký hiệu khỏi binary rồi ghi chúng vào một gói .dSYM riêng. Không có nó, báo cáo sự cố mãi mãi là hex.

dSYM phải khớp đúng bản build đó. Không phải cùng phiên bản — mà là cùng bản build. Mỗi lần biên dịch sinh ra một UUID mới, và một dSYM từ lần build lại của mã nguồn y hệt cũng sẽ không dùng được.

Kiểm tra UUID của một báo cáo sự cố so với một dSYM:

# UUID của dSYM
dwarfdump --uuid MyApp.app.dSYM

# nó phải khớp với phần "Binary Images" của báo cáo sự cố

Nếu hai cái đó không khớp thì dừng lại — chẳng gì khác chạy được, và giải mã ký hiệu bằng một dSYM lệch sẽ cho ra những câu trả lời nghe hợp lý mà sai.

Tìm nó ở đâu

Nếu bạn đã tải lên App Store Connect, Xcode có nó. Window → Organizer → Archives, chọn bản build, chuột phải → Download Debug Symbols.

Nếu bạn dùng bitcode — ngày càng hiếm — thì Apple đã biên dịch lại ứng dụng của bạn, nên dSYM cục bộ không phải cái đã phát hành. Bạn phải tải bản của Apple về.

Nếu đó là một archive cục bộ, nó nằm trong file .xcarchive:

MyApp.xcarchive/dSYMs/MyApp.app.dSYM

Hãy archive mọi bản build phát hành và giữ lại dSYM. Đây là chỗ hay bẫy người ta — một báo cáo sự cố từ một phiên bản mà bạn không giữ dSYM thì không giải được, vĩnh viễn.

Giải mã ký hiệu

Với cả một file .crash hay .ips, Xcode làm tự động khi bạn kéo nó vào tab Crashes của Organizer, miễn là dSYM nằm trong chỉ mục của Spotlight.

Với một địa chỉ đơn lẻ, atos là công cụ trực tiếp:

atos -o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp \
     -arch arm64 \
     -l 0x102a40000 \
     0x0000000102a4c8d0
  • -o — cái binary bên trong gói dSYM, không phải bản thân cái gói
  • -arch — arm64 cho thiết bị
  • -l — địa chỉ nạp, con số hex thứ hai trên dòng của báo cáo
  • tham số cuối — địa chỉ cần giải

Kết quả:

ProfileViewModel.loadAvatar(for:) (in MyApp) (ProfileViewModel.swift:84)

Mẹo

Địa chỉ nạp (-l) là thứ người ta hay đưa sai. Đó là địa chỉ nền của module, được liệt kê trong phần “Binary Images” của báo cáo và lặp lại trên mỗi khung dưới dạng con số đứng trước dấu +. Dùng địa chỉ khung cho cả hai sẽ cho ra một câu trả lời sai chứ không phải một lỗi.

Đọc báo cáo trước khi giải mã ký hiệu

Một phần của nó đọc được ngay, và hai trường quyết định bạn đang nhìn cái gì.

Loại ngoại lệ:

Loại Thường nghĩa là
EXC_BAD_ACCESS (SIGSEGV) Giải tham chiếu vùng nhớ hỏng — một unowned treo lơ lửng, dùng sau khi giải phóng
EXC_CRASH (SIGABRT) Một cú hủy có chủ đích — mở optional nil, mảng vượt biên, fatalError
EXC_BAD_INSTRUCTION (SIGILL) Một cú sập của runtime Swift — cũng là mở optional và vượt biên
EXC_RESOURCE Vượt giới hạn bộ nhớ hoặc CPU — ứng dụng bị hệ thống đá ra
0x8badf00d Watchdog: luồng chính bị chặn quá lâu lúc khởi động hoặc lúc tạm dừng

Hai cái cuối không phải lỗi code theo nghĩa thông thường, và giải mã ký hiệu cho chúng thì ít hữu ích hơn là đi xem luồng chính đang làm gì.

Lý do chấm dứt thường gọi tên chính xác vấn đề — Fatal error: Unexpectedly found nil while unwrapping an Optional value cho bạn biết lớp lỗi trước khi có địa chỉ nào được giải.

Cái luồng đáng quan tâm

Luồng bị sập được đánh dấu. Hãy đọc nó từ trên xuống rồi tìm khung đầu tiên là code của bạn — các khung phía trên nó thường là nội tạng hệ thống đang phản ứng với con bug của bạn chứ không phải bản thân con bug.

Với EXC_RESOURCE và các cú chấm dứt do watchdog, hãy xem thread 0 bất kể luồng nào được đánh dấu, vì câu hỏi ở đây là thứ gì đang chặn luồng chính.

Thứ thật sự ngăn chuyện này trở nên đau đớn

Hãy archive và giữ mọi dSYM đã phát hành, trong quản lý phiên bản hoặc trong kho lưu trữ đối tượng. Chúng chỉ vài megabyte và chúng là khác biệt giữa việc sửa được một cú sập và việc ngồi đoán.

Hãy dùng một trình báo cáo sự cố tự giải mã ký hiệu hộ bạn — Xcode Organizer lo được các bản App Store, còn một dịch vụ thì lo TestFlight và phân phối nội bộ. Việc tải dSYM lên ở mỗi bản build phát hành nên nằm trong script phát hành của bạn, vì cái ngày bạn cần đến một cái dSYM chính là cái ngày bạn phát hiện ra bước thủ công đó đã bị bỏ qua.

Đừng tắt việc sinh dSYM cho bản Release. Nó bật sẵn theo mặc định và tôi từng thấy người ta tắt nó đi để build nhanh hơn. Nó tiết kiệm cho bạn vài giây và lấy đi mọi báo cáo sự cố mà bạn sẽ từng nhận được.