19 July 2015

Symbolicating crashlogs, когда Xсode не справляется.

Бывает такое, что к нам прибегают тестировщики (или, даже, пользователи) с падениями приложения, о чём свидетельствует красующийся на их устройстве crashlog.

Удачно, если падение легко воспроизвести в лабораторных условиях. Но бывает и такое, что вредный баг воспроизводится только у пользователя, и только когда за ним не подсматривают.
Остаётся единственный шанс поймать вреднюгу за "руку" - раскопать полезную информацию из crashlog-ов.

Если Вам повезёт, то Xcode сам всё сделает за вас, как только логи попадут в него. Для этого необходимо, чтобы та самая злополучная сборка была собрана на нём. В противном случае, может помочь наличие .dSYM-а, приложив который к crashlog-у можно добиться желаемого результата - понять что же происходит с нашим приложением.

В большинстве случаев crashlog представляет собой stacktrace вызовов, приведших к падению приложения (именно это нас интересует). Трейс представлен ввиде адресов в памяти на момент выполнения программы. Файл .dSYM позволяет преобразовать эти адреса в человекочитаемые вызовы в исходном коде.

Для этого преобразования Xcode использует утилитку symbolicatecrash, которая находится по адрусу:

/Applications/Xcode.app/Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash

Работа с ней происходит так:

./symbolicatecrash -o ./myapp-resymbolicated.crash ./myapp.crash ./myapp.app.dSYM

Суть заключается в следующем: на вход утилитке передаётся crashlog (./myapp.crash), файл .dSYM (./myapp.app.dSYM) и название файла в который поместить результат (./myapp-resymbolicated.crash).

Зная это можно пользоваться утилиткой в обход Xcode-а если потребуется, главное подложить правильный .dSYM.

Однако, этот способ может не сработать до конца. Как видно, самая интересная часть лога по прежнему окутана тайной:

Last Exception Backtrace:
0   CoreFoundation               0x2f2e5f06 __exceptionPreprocess + 126
1   libobjc.A.dylib              0x39a7cce2 objc_exception_throw + 34
2   CoreFoundation               0x2f21f2e6 -[__NSPlaceholderArray initWithObjects:count:] + 414
3   CoreFoundation               0x2f228c5c +[NSArray arrayWithObjects:count:] + 40
4   MyApp                        0x0012ca9a 0xfb000 + 203418
5   MyApp                        0x001294a6 0xfb000 + 189606
6   MyApp                        0x002e7418 0xfb000 + 2016280
7   MyApp                        0x002e737e 0xfb000 + 2016126
8   MyApp                        0x002e7d38 0xfb000 + 2018616
9   libdispatch.dylib            0x39f65d4e _dispatch_call_block_and_release + 6
10  libdispatch.dylib            0x39f65d3a _dispatch_client_callout + 18
11  libdispatch.dylib            0x39f686be _dispatch_main_queue_callback_4CF + 274
12  CoreFoundation               0x2f2b067c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 4
13  CoreFoundation               0x2f2aef48 __CFRunLoopRun + 1304
14  CoreFoundation               0x2f219764 CFRunLoopRunSpecific + 520
15  CoreFoundation               0x2f219546 CFRunLoopRunInMode + 102
16  GraphicsServices             0x341866ce GSEventRunModal + 134
17  UIKit                        0x31b7888c UIApplicationMain + 1132
18  MyApp                        0x00291ec0 0xfb000 + 1666752
19  libdyld.dylib                0x39f7aab2 tlv_initializer + 2


Что ж, если этот вариант не приводит к желаемому результату рано отчаиваться - есть ещё способ! Команда atos (не путать с мушкетёром) придёт вам на помощь!

man to atos говорит:
"atos -- convert numeric addresses to symbols of binary images or processes"

Она-то и позволит нам вытащить заветные символы на чистую воду!

Для её использования нам понадобятся:

  • Crashlog
  • .ipa или сразу .app (.app получается путём разархивирования .ipa-шки)
  • .dSYM желателен, но не обязателен!


Для начала распотрашим crashlog. Нам потребуется набор адресов, которые мы хотим преобразовать в что-то дельное. Всё остальное - тлен.
Вот этот набор:

0x0012ca9a 0xfb000 
0x001294a6 0xfb000 
0x002e7418 0xfb000 
0x002e737e 0xfb000 
0x002e7d38 0xfb000 

Во-вторых, нам нужен бинарь самого приложения виновника торжества (естественно, именно тот бинарь, который валиться).
Как только мы собрали всё необходимое - мы готовы действовать!

atos принимает ряд параметров, из них нам интересны следующие:

  • -o ... - путь к бинарнику, адреса в котором необходимо преобразовать в символы.
  • -arch ... - архитектура бинариника (её следует знать из настроек проекта: в моём случае это armv7). Указать правильную архитектуру очень важно - в противном случае результаты могут не получиться или получиться неправильные.
  • -l ...    - базовый адрес загрузки (тот адрес, которы одинаковый для всех вызовов: 0xfb000 в нашем случае).
  • Последний аргумент - тот самый адрес, преобразовать который необходимо.


В результате получаем следующую команду:

atos -o ./MyApp.app/MyApp -arch armv7 -l 0xfb000 0x0012ca9a

И результат:

При отсутствии .dSYM:

-[MyClass someMethodInMyClassInstance:] (in MyApp) + 814

При наличии .dSYM (при чём достаточно наличие файла в любом месте в системе - перенеся его лишь только в корзину мне удалось добиться того, чтобы утилита его не нашла):

-[MyClass someMethodInMyClassInstance:] (in MyApp) (MyClass.m:612)


Lifehack:

Часто бывает необходимо преобразовать в символы сразу несколько вызовов.
Чтобы не вызывать утилиту много раз руками можно заменить последний параметр (адрес) на путь к текстовому файлу, в котором расположен набор адресов для преобразования:

atos -o ./MyApp.app/MyApp -arch armv7 -l 0xfb000 -f ./addr.txt

Содержимое addr.txt:

0x0012ca9a
0x001294a6
0x002e7418
0x002e737e
0x002e7d38

В результате получаем список вызовов в том порядке, в котором адреса указаны в файле:

-[MyClass someMethodInMyClassInstance:] (in MyApp) (MyClass.m:612)
-[MyClass someOtherMethodInMyClassInstance:] (in MyApp) (MyClass.m:192)
-[MyClass oneMoreOtherMethodInMyClassInstance:] (in MyApp) (MyClass.m:240)
-[MyClass anotherMethodInMyClassInstance:] (in MyApp) (MyClass.m:227)
__43-[MyClass firstOtherMethodInTrace]_block_invoke (in MyApp) (MyClass.m:304)


No comments:

Post a Comment