12 April 2010

Отладка EXC_BAD_ACCESS в Mac OS X и iPhone

Development for iOS
Привет! В этой статье я постараюсь рассказать о том, как можно облегчить себе жизнь при отладке iPhone- и Mac-приложений. Речь пойдет об отладке исключения EXC_BAD_ACCESS, одного из самых неприятных в природе.

Исключение EXC_BAD_ACCESS возникает, когда мы пытаемся послать сообщение объекту, который уже был освобожден (released). Ситуация обычно осложняется тем, что к тому моменту, когда выскакивает ошибка, стэк вызова уже не содержит информацию, которая могла бы нам помочь. То есть, непонятно, какой конкретно объект был освобожден. И, тем более, непонятно, в каком месте кода он был освобожден :). Еще веселее становится, если приложение многопоточное.

К счастью, ребята из Apple приложили все усилия, чтобы нам помочь. Они создали класс-заглушку NSZombie. Если включить его поддержу (как это сделать — расскажу ниже), то после удаления каждого объекта на его месте (т.е. по его адресу в памяти) будет жить «зомби» и принимать сообщения, что позволит при отладке определить, какой объект был освобожден и устранить ошибку.

Итак, начнем. В один прекрасный день ваше iPhone- или Mac-приложение начало вылетать и в консоли отладчика вы видите следующее сообщение:

Program received signal: “EXC_BAD_ACCESS”.

И, собственно, больше ничего. Стэк вызова, скорее всего, тоже вам ничем не помог.

В окне Groups & Files (слева в xCode) найдите ветку Executables. Щелкните правой кнопкой на исполняемом файле вашего приложения и в выпадающем меню выберите пункт Get Info. В появившемся окне откройте вкладку Arguments и добавьте две переменные окружения: NSZombieEnabled и MallocStackLoggingNoCompact. Установите NSZombieEnabled значение YES, а MallocStackLoggingNoCompact1. Как это должно выглядеть, показано на скриншоте:

image

Теперь запустите ваше приложение и повторите шаги, необходимые для воспроизведения ошибки. В консоли отладчика вместо сообщения о EXC_BAD_ACCESS вы должны увидеть что-то подобное:

2010-01-25 14:35:24.840 MyApplication[1393:20b] *** -[CFString retain]: message sent to deallocated instance 0x42a5060

Это значит, что когда-то у вас жил объект класса CFString по адресу 0x42a5060. Он уже был освобожден (released), а ваша программа не унимается и пытается послать ему сообщение retain. Осталось найти этот объект и найти в коде место, где был вызван лишний release. Либо не вызван retain, зависит от конкретной ситуации :)

Вам поможет следущее: в консоли отладчика наберите такую команду:

shell malloc_history 1393 0x42a5060

Вместо 1393 вам следует вписать свой идентификатор процесса (PID). Его вы можете подсмотреть в консольном сообщении об ошибке сразу после имени приложения. А вместо 0x42a5060 впишите адрес объекта, который был освобожден (тоже можно посмотреть в сообщении об ошибке).

В результате вашим глазам предстанет страшная картина вроде этой:

image

Не пугайтесь, все не так страшно, как может показаться на первый взгляд. Ближе к низу ищите имена классов и методов, которые присутствуют в вашем коде. То есть, написаны вами. Где-то там и притаился злочастный вызов release. Ищите и исправляйте.

P.S. Краем глаза я где-то прочитал, что можно отлавливать подобные ошибки с помощью Instruments / ObjectAlloc. Был бы очень признателен, если бы кто-то дал мне на водку… Ой, наводку
Tags:iphonedebugотладкаmac os xobjective-cxcode
Hubs: Development for iOS
+23
26.3k 68
Comments 25
Top of the last 24 hours