Главная > Apple, Coding > Что делать с Apple Сrash Logs

Что делать с Apple Сrash Logs

Давненько не писал в блог, а материала готового пока нет, поэтому сегодня будет скучная, но полезная статья. По новой работе столкнулся с нетривиальной проблемой. Смысл: после локального тестирования и убеждения себя в том, что все вроде бы ОК, собираю AdHoc версию приложения, отправляю тестировщику ipa. Тот инсталлирует приложение себе на девайс, запускает и начинает тестировать. Спустя некоторое время получаю от него репорт, мол вот тут-то упало. Пытаюсь воспроизвести — не выходит, у меня на двух девайсах все работает при тех же исходных. Что делать? Идеальным решением было бы как-то удаленно запустить под дебаггером приложение на девайсе тестировщика по интернету, и даже вроде я встречал упоминания о таких сервисах, но, во-первых как-то боязно доверять третим лицам свое приложение, а во-вторых, времени не было. В итоге попросил у тестировщика Crash Log — журнал аварийного завершения, который формируется внутри iOS каждый раз, когда какое-либо приложение аварийно завершается. Взять то взял, но что с ним делать?

Вот пример такого лога

Incident Identifier: 4186E31D-AAE4-4683-A70C-3DE9686BC1DC
CrashReporter Key:   2e9f789a246d42d4a5ac38a7df65edf30b5b2a91
Hardware Model:      iPod4,1
Process:         MyApp [15863]
Path:            /var/mobile/Applications/2DA57CD3-D8E7-435C-8E4A-17335F6ECE19/MyApp.app/MyApp
Identifier:      MyApp
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]
 
Date/Time:       2011-12-02 17:10:58.569 +0000
OS Version:      iPhone OS 4.3.3 (8J2)
Report Version:  104
 
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x00000000, 0x00000000
Crashed Thread:  0
 
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   CoreFoundation.dylib        	0x0144c5a9 0x321db000 + 185
1   libobjc.A.dylib              	0x015a0313 0x36333000 + 44
2   libsystem_c.dylib             	0x3635ebf8 0x36333000 + 179192
3   libstdc++.6.dylib             	0x30d0ca64 0x30cc8000 + 281188
4   libobjc.A.dylib               	0x306e306c 0x306dd000 + 24684
5   libstdc++.6.dylib             	0x30d0ae36 0x30cc8000 + 273974
6   libstdc++.6.dylib             	0x30d0ae8a 0x30cc8000 + 274058
7   libstdc++.6.dylib             	0x30d0af5a 0x30cc8000 + 274266
8   libobjc.A.dylib               	0x306e1c84 0x306dd000 + 19588
9   CoreFoundation                	0x361c348a 0x36125000 + 648330
10  CoreFoundation                	0x361c34c4 0x36125000 + 648388
11  CoreFoundation                	0x361359d0 0x36125000 + 68048
12  MyApp                        	0x0000e998 0x1000 + 55704
13  MyApp                        	0x0000d972 0x1000 + 51570
14  CoreFoundation                	0x3613356a 0x36125000 + 58730
15  UIKit                         	0x32edfec2 0x32ec3000 + 118466
16  UIKit                         	0x32edfe62 0x32ec3000 + 118370
17  UIKit                         	0x32edfe34 0x32ec3000 + 118324
18  UIKit                         	0x32edfb86 0x32ec3000 + 117638
19  UIKit                         	0x32ee041c 0x32ec3000 + 119836
20  UIKit                         	0x32ec552e 0x32ec3000 + 9518
21  UIKit                         	0x32ec4bfa 0x32ec3000 + 7162
22  CoreFoundation                	0x3619aa2e 0x36125000 + 481838
23  CoreFoundation                	0x3619c45e 0x36125000 + 488542
24  CoreFoundation                	0x3619d754 0x36125000 + 493396
25  CoreFoundation                	0x3612debc 0x36125000 + 36540
26  CoreFoundation                	0x3612ddc4 0x36125000 + 36292
27  GraphicsServices              	0x30f22418 0x30f1e000 + 17432
28  GraphicsServices              	0x30f224c4 0x30f1e000 + 17604
29  UIKit                         	0x32ef1d62 0x32ec3000 + 191842
30  UIKit                         	0x32eef800 0x32ec3000 + 182272
31  MyApp                        	0x0003094c 0x1000 + 194892
32  MyApp                        	0x00002e64 0x1000 + 7780

Что можно из этого всего вынести?
Видно, что произошло исключение EXC_CRASH (SIGABRT) в главной нити с индексом 0. Далее идет стек вызовов — путь, по которому приложение пришло к краху. Но почему при запуске под дебаггером тот же самый стек выглядит по-другому, и дебаггер даже показывает какой-то текст ошибки?

Dec  2 22:43:30 unknown MyApp[8532] : strEqual [] [ ]
Dec  2 22:43:30 unknown MyApp[8532] : -[NSExternalRefCountedData isEqualToString:]: unrecognized selector sent to instance 0x1d19e0
Dec  2 22:43:30 unknown MyApp[8532] : *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSExternalRefCountedData isEqualToString:]: unrecognized selector sent to instance 0x1d19e0'
 -[NSCFString sdfs]: unrecognized selector sent to instance 0x566b3b0
2011-12-03 00:05:40.682 MyApp[13462:c803] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFString sdfs]: unrecognized selector sent to instance 0x566b3b0'
*** Call stack at first throw:
(
	0   CoreFoundation                      0x0144c5a9 __exceptionPreprocess + 185
	1   libobjc.A.dylib                     0x015a0313 objc_exception_throw + 44
	2   CoreFoundation                      0x0144e0bb -[NSObject(NSObject) doesNotRecognizeSelector:] + 187
	3   CoreFoundation                      0x013bd966 ___forwarding___ + 966
	4   CoreFoundation                      0x013bd522 _CF_forwarding_prep_0 + 50
	5   MyApp                              0x00015b01 -[ProfileViewController isExtraChanged] + 65
	6   MyApp                              0x000188eb -[ProfileViewController onBackClick:] + 267
	7   UIKit                               0x007654fd -[UIApplication sendAction:to:from:forEvent:] + 119
	8   UIKit                               0x007f5799 -[UIControl sendAction:to:forEvent:] + 67
	9   UIKit                               0x007f7c2b -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 527
	10  UIKit                               0x007f67d8 -[UIControl touchesEnded:withEvent:] + 458
	11  UIKit                               0x00789ded -[UIWindow _sendTouchesForEvent:] + 567
	12  UIKit                               0x0076ac37 -[UIApplication sendEvent:] + 447
	13  UIKit                               0x0076ff2e _UIApplicationHandleEvent + 7576
	14  GraphicsServices                    0x02fee992 PurpleEventCallback + 1550
	15  CoreFoundation                      0x0142d944 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 52
	16  CoreFoundation                      0x0138dcf7 __CFRunLoopDoSource1 + 215
	17  CoreFoundation                      0x0138af83 __CFRunLoopRun + 979
	18  CoreFoundation                      0x0138a840 CFRunLoopRunSpecific + 208
	19  CoreFoundation                      0x0138a761 CFRunLoopRunInMode + 97
	20  GraphicsServices                    0x02fed1c4 GSEventRunModal + 217
	21  GraphicsServices                    0x02fed289 GSEventRun + 115
	22  UIKit                               0x00773c93 UIApplicationMain + 1160
	23  MyApp                              0x00057fc9 main + 121
	24  MyApp                              0x00001ff5 start + 53
	}

Дело в том, что, когда приложение запускается под отладчиком, у отладчика имеется карта (или таблица) символов (symbol map), которая представляет собой справочник идентификаторов программы (названий функций, переменных) и их адресов в памяти. Поэтому он может восстановить стэк вызовов используя привычные разработчику имена. Когда приложение на устройстве, у него может и не быть этой информации, поэтому стэк вызовов крэш-репорте он формирует в виде списка абсолютных адресов. Как же с этим быть? Давайте разберем, что из себя представляет запись стэка вызовов, например, вот эти:

	//Crash log from Device
	0   CoreFoundation.dylib        	0x0144c5a9 0x321db000 + 185
	1   libobjc.A.dylib              	0x015a0313 0x36333000 + 44
	...
 
	//Crash log from Simulator
	0   CoreFoundation                      0x0144c5a9 __exceptionPreprocess + 185
	1   libobjc.A.dylib                     0x015a0313 objc_exception_throw + 44
	...

Запись состоит из пяти колонок.
В первой колонке содержится порядковый номер записи. Важно знать, в каком направлении следует стек вызовов. Следует от от самого большого порядкового номера к нулю. То есть, вызов с порядковым номером 0 и есть причина падения, перед ней выполнялся вызов с номером 1, и т.д.
Во второй колонке содержится название фреймворка или системной библиотеки. Видимо, это таблица импорта присутствует всегда на девайсе, поэтому тут нет расхождения между логом девайса и симулятора. Далее следует абсолютный адрес точки входа функции (функция = вызов). Тут тоже пока сходится.
В третьем столбце находится адрес функции.
В четвертом находится адрес какого-то сегмента, вероятно, точка входа во фреймворк или библиотеку, а может быть адрес экземпляра класса. В логе симулятора на этом месте название функции.
Пятый столбец вероятно содержит смешение в байтах от начала фрейворка до машинной инструкции, которая вызвала исключение. Если сложить четвертый и пятый получим третий.
Задача — восстановить из абсолютных адресов название класса и метода, вызвавших крах.
Cуществуют отдельные утилиты с графическим интерфейсом, но мне не охота было искать, и я попробовал стандартную утилиту командной строки atos (Address to Symbol). На вход ей дается имя бинарного файла и интересующий адрес, а на выходе получаем название класса, метода и номер строки в исходном коде.
Что для этого понадобится? Понадобится бандл приложения. Где его взять? Можно тупо выдрать из IPA файла, который ни что иное, как ZIP. Есть и другие способы, но о них долго рассказывать. И так, распаковываем из IPA папку MyApp.app и заходим в нее в терминале (Terminal.app). Внутри можно увидеть папку MyApp.app.dSYM — в ней то как раз и содержится таблица символов (преобразование адреса в название). У компилатора GCC есть опция Generate Debug Symbols — это в Project Build Settings секция Code Generation. Вообще, много любопытных вещей.

Если Generate Debug Symbols установить в NO, то дебаг инфо по таблице символов генерироваться не будет. Разумно так поступать на уровне Release-конфигурации.

Ситаксис использования прост:

# atos -arch armv6 -o MyApp 0x00004ee2

где
# — символ приглашения командной строки
-arch armv6 — указание таргет-платформы. тут важно понимать, под какую платформу собирался проект — это также можно узнать в Build Settings. Если указать неправильную платформу, можно получить не корректный результат.
-o MyApp — название исполняемого файла. Он находится там же, где и .dSYM файл, то есть в папке бандла приложения (MyApp.app)
-0x00004ee2 — собственно, виновник торжества — адрес крэш инструкции.
Ответ не заставляет себя долго ждать:

-[ProfileViewController requestUserRegisterOrUpdate] (in MyApp) (ProfileViewController.m:454)

Ну вот теперь стало гораздо понятнее.
Таким образом, можно отдельно сделать билд приложения без отладочной информации, а таблицу символов хранить отдельно. Когда пользователь пришлет крэш отчет, можно легко восстановить название упавшего вызова. К слову. Ровно в таком же виде Apple ожидает отчеты об ошибках неожиданного завершения той или иной программы, который пользователю предлагается отправить. И опять к слову, в таком же виде присылается отчет из iTunes Connect после reject’a приложения, если в процессе Review это самое приложение упало.

 

Categories: Apple, Coding Tags:
  1. Пока что нет комментариев.
Подписаться на комментарии по RSS