Сегодня мы поговорим о LLDB — замечательном инструменте для отладки и тестирования ваших проектов.
Поймай их всех
Наверное, каждому из нас доводилось иметь дело с исправлением различных багов в своих проектах. Зачастую, мы даже не задумываемся о систематизации этого процесса, просто стараемся быстрее найти и исправить ошибку. Такой подход может дорого обойтись в больших проектах с нетривиальной архитектурой, где уже сложно прослеживаются зависимости между конкретными участками кода, из-за высокого уровня абстракции.
Отладка в среде Xcode
Тестирование и отладка довольной сложный и трудоемкий процесс, программист зачастую должен держать огромный контекст из различных блоков кода прямо у себя в голове. Вам наверняка уже приходилось слышать о существовании программ, помогающих разработчикам отлаживать и тестировать код. Xcode здесь не стал исключением, среда имеет встроенный отладчик по умолчанию расположенный в нижней панели IDE, он поддерживает несколько видов взаимодействий, о которых мы и поговорим в этой статье.

Впрочем, Xcode не единственное место откуда вы можете управлять процессом отладки вашего приложения, в некоторых ситуациях будет удобней воспользоваться отладчиком прямо из консоли:

Немного о LLDB
LLDB это кроссплатформенный, высокопроизводительный отладчик, написанный на C++. На текущий момент он поддерживает i386, x86 и ARM архитектуры и может работать под управлением Unix-like и Linux операционных систем. В Xcode LLDB появился начиная с версии 4.3, где и остался стандартным отладчиком по сегодняшний день.
Виды ошибок
Существует бесчисленное множество ошибок, которое мы можем допустить при написании программного кода. Что-бы упростить себе задачу, систематизируем это множество, разделив его на:
- Ошибки компиляции
- Ошибки исполнения
- Ошибки логики
Пока что нас будут интересовать, только ошибки исполнения, они характерны тем, что возникают во время исполнения программы, приводят к ее остановке и оставляют программиста с глазу на глаз со стэком вызовов. Как правило, такие ошибки обнаруживаются на низком уровне, где ни о каком семантическом и уж тем более лексическом анализе говорить не приходится. В подавляющем большинстве случаев вы будете встречать:
- EXC_BAD_INSTRUCTION
- SIGABRT
- EXC_BAD_ACCESS
Начнем по порядку, EXC_BAD_INSTRUCTION означает, что вашу программу остановил один из Assertion’ов стандартной библиотеки по соображениям безопасности, вы точно знакомы с этой ошибкой если пишите на Swift’е, что-бы ее воспроизвести достаточно развернуть пустой опционал там, где этого делать не следует.
SIGABRT, если вы когда-нибудь слышали о POSIX, возможно вы уже знакомы с этим сигналом. Sigabrt говорит о невозможности дальнейшего выполнения программы. Обычно, в Cocoa такой сигнал будет говорить о необработанном исключении и может показаться, что это не сильно относится к Swift программистам, которые в добровольно-принудительном порядке обязаны написать обработчик для исключения, если надобность в таковом заявлена в сигнатуре словом throws, но стоит помнить, что необработанное исключение может прилететь к вам из Objective-C библиотеки, если вы в той или иной мере пользуетесь бриджингом.
EXC_BAD_ACCESS, ну а с этой ошибкой, думаю, знакомы без исключения все Си-программисты, вы попытались обратиться к области в памяти, которая была освобождена.
Для примера напишем пару строчек, чтобы воспроизвести все рассмотренные ошибки:
import Foundation
class CBDebugSource {
var data = Array(
arrayLiteral: 1,2,3
)
var objc_data = NSArray()
var computedProperty: Int {
return self.computedProperty
}
func getBadInstructionExeption() -> Void {
for index in 0...self.data.count {
_ = self.data[index]
}
}
func getSignalAbort() -> Void {
_ = self.objc_data[0]
}
func getBadAccessExeption() -> Void {
_ = self.computedProperty
}
}
Этим кодом мы декларируем класс CBDebugSource, содержащий три метода, соответствующих вышеприведенным типам ошибок. В контексте этого кода, на примере отладки одного из методов мы и рассмотрим простейшие виды работы с отладчиком.
Запускаем LLDB
Итак, подключаем нашу «игровую площадку» к точке входа:
import Foundation
let source = CBDebugSource()
source.getBadInstructionExeption()
source.getSignalAbort()
source.getBadAccessExeption()
Готово, можно начинать. Собираем проект, видим, что статический анализатор действительно молчит, значит наш код синтаксически и семантически валиден, давай-те запускаться:

Это ожидаемое поведение, давай-те узнаем больше информации о том, что случилось с нашим потоком, введя команду bt в терминале:

Нас интересует строчка stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0), итак мы узнали причину остановки, сработал assertion. Если внимательно посмотреть на стэк, можно увидеть, что наша логика последний раз вызывалась во втором фрэйме, давайте ее детализируем, набрав команду f 2 в терминале:

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

Помня, что массивы в Swift индексируются начиная с нуля, подтверждаем свою гипотезу и исправляем нашу функцию, используя оператор полуоткрытого диапазона:
func getBadInstructionExeption() -> Void {
for index in 0..<self.data.count {
_ = self.data[index]
}
}
Перед тем как вносить изменения в код, проверим нашу задумку на месте, не выходя из отладчика. Откроем read-eval-print loop, набрав repl в консоли:

Отлично, мы последовательно определили причину ошибки, локализовали ее место, проверили текущее состояние некорректного выражения и наконец исправили его, попутно проверив ход своих мыслей в REPL.
Заключение
Сегодня мы рассмотрели несколько простых взаимодействий с LLDB, а так же обсудили возможные типы фатальных ошибок и причины их возникновения, оставив довольно много интересных и полезных вещей за рамками данной статьи, например работу с breakpoint’ами. Мы обязательно вернемся и рассмотрим их в следующих статьях.
Автор фото: @madeawkward
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.