Бывает такое, что к нам прибегают тестировщики (или, даже, пользователи) с падениями приложения, о чём свидетельствует красующийся на их устройстве 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 говорит:
Она-то и позволит нам вытащить заветные символы на чистую воду!
Для её использования нам понадобятся:
Для начала распотрашим crashlog. Нам потребуется набор адресов, которые мы хотим преобразовать в что-то дельное. Всё остальное - тлен.
Вот этот набор:
0x0012ca9a 0xfb000
0x001294a6 0xfb000
0x002e7418 0xfb000
0x002e737e 0xfb000
0x002e7d38 0xfb000
Во-вторых, нам нужен бинарь самого приложения виновника торжества (естественно, именно тот бинарь, который валиться).
Как только мы собрали всё необходимое - мы готовы действовать!
atos принимает ряд параметров, из них нам интересны следующие:
В результате получаем следующую команду:
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)
Удачно, если падение легко воспроизвести в лабораторных условиях. Но бывает и такое, что вредный баг воспроизводится только у пользователя, и только когда за ним не подсматривают.
Остаётся единственный шанс поймать вреднюгу за "руку" - раскопать полезную информацию из 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)