LLDB - это огромный инструмент, в котором есть множество полезных команд. Я не буду их всех описывать. Вместо этого я хотел бы рассказать вам о наиболее полезных командах. Итак, вот наш план:

  1. Изучите значения переменных: expression, e, print, po, p
  2. Получить общее состояние приложения + команды для конкретного языка: bugreport, frame, language
  3. Управление процессом выполнения приложения: process, breakpoint, thread, watchpoint
  4. Почетные упоминания: command, platform, gui

Я также подготовил карту полезных команд LLDB с описанием и примерами. Если вам нужно, вы можете повесить его над Mac, чтобы запомнить эти команды 🙂

1. Изучите значение и состояние переменных.

Команды: expression, e, print, po, p

Основная функция отладчика - исследовать и изменять значения переменных. Это то, для чего созданы expression или e (и даже больше). Вы можете оценить практически любое выражение или команду во время выполнения.

Предположим, вы отлаживаете некоторую функцию valueOfLifeWithoutSumOf(), которая суммирует два числа и извлекает результат из 42.

Предположим также, что вы постоянно получаете неправильный ответ и не знаете почему. Итак, чтобы найти проблему, вы можете сделать что-то вроде этого:

Или… лучше использовать выражение LLDB вместо того, чтобы изменять значение во время выполнения. И узнайте, где возникла проблема. Сначала установите точку останова в интересующем вас месте. Затем запустите приложение.

Чтобы распечатать значение конкретной переменной в формате LLDB, вы должны вызвать:

(lldb) e <variable>

И та же самая команда используется для вычисления некоторого выражения:

(lldb) e <expression>

(lldb) e sum 
(Int) $R0 = 6 // You can also use $R0 to refer to this variable in the future (during current debug session)
(lldb) e sum = 4 // Change value of sum variable
(lldb) e sum 
(Int) $R2 = 4 // sum variable will be "4" till the end of debugging session

Команда expression также имеет несколько флагов. Чтобы различать флаги и фактическое выражение, LLDB использует обозначение двойного тире -- после expression команды, например:

(lldb) expression <some flags> -- <variable>

expression имеет около 30 различных флагов. И я призываю вас изучить их все. Напишите в терминале команду ниже, чтобы получить полную документацию:

> lldb
> (lldb) help # To explore all available commands
> (lldb) help expression # To explore all expressions related sub-commands

Я хотел бы остановиться на следующих флагах expression:

  • -D <count> (--depth <count>) - Установите максимальную глубину рекурсии при сбросе типов агрегатов (по умолчанию - бесконечность).
  • -O (--object-description) - отображать с использованием API описания для конкретного языка, если это возможно.
  • -T (--show-types) - Показывать типы переменных при выгрузке значений.
  • -f <format> (--format <format>) –– Укажите формат, который будет использоваться для отображения.
  • -i <boolean> (--ignore-breakpoints <boolean>) - Игнорировать попадания в точку останова при выполнении выражений

Допустим, у нас есть объект с именем logger. Этот объект содержит некоторую строку и структуру как свойства. Например, вы хотите исследовать только свойства первого уровня. Для этого просто используйте флаг -D с соответствующим уровнем глубины:

(lldb) e -D 1 -- logger
(LLDB_Debugger_Exploration.Logger) $R5 = 0x0000608000087e90 {
  currentClassName = "ViewController"
  debuggerStruct ={...}
}

По умолчанию LLDB будет бесконечно заглядывать в объект и показывать вам полное описание каждого вложенного объекта:

(lldb) e -- logger
(LLDB_Debugger_Exploration.Logger) $R6 = 0x0000608000087e90 {
  currentClassName = "ViewController"
  debuggerStruct = (methodName = "name", lineNumber = 2, commandCounter = 23)
}

Вы также можете изучить описание объекта с помощью e -O -- или просто используя псевдоним po, как в примере ниже:

(lldb) po logger
<Logger: 0x608000087e90>

Не очень наглядно, не правда ли? Чтобы получить удобочитаемое описание, вы должны применить свой собственный класс к протоколу CustomStringConvertible и реализовать свойство var description: String { return ...}. Только тогда po вернет вам удобочитаемое описание.

В начале этого раздела я также упомянул команду print.
По сути, print <expression/variable> то же самое, что и expression -- <expression/variable>. За исключением того, что команда print не принимает никаких флагов или дополнительных аргументов.

2. Получите общее состояние приложения + команды для конкретного языка.

bugreport, frame, language

Как часто вы копировали, вставляли и вставляли журналы сбоев в диспетчер задач, чтобы позже изучить проблему? В LLDB есть небольшая замечательная команда под названием bugreport, которая генерирует полный отчет о текущем состоянии приложения. Это может быть действительно полезно, если вы столкнетесь с какой-либо проблемой, но захотите решить ее чуть позже. Чтобы лучше понять состояние приложения, вы можете использовать созданный bugreport отчет.

(lldb) bugreport unwind --outfile <path to output file>

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

Допустим, вы хотите получить быстрый обзор текущего кадра стека в текущем потоке. В этом вам может помочь команда frame:

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

(lldb) frame info
frame #0: 0x000000010bbe4b4d LLDB-Debugger-Exploration`ViewController.valueOfLifeWithoutSumOf(a=2, b=2, self=0x00007fa0c1406900) -> Int at ViewController.swift:96

Эта информация будет полезна при управлении точками останова позже в статье.

В LLDB есть несколько команд для определенного языка. Есть команды для C ++, Objective-C, Swift и RenderScript. В данном случае нас интересует Swift. Итак, вот эти две команды: demangle и refcount.

demangle, как написано в его имени, просто показывает искаженное имя типа Swift (которое Swift генерирует во время компиляции, чтобы избежать проблем с пространством имен). Если вы хотите узнать об этом больше, я предлагаю вам посмотреть этот сеанс WWDC14 - Расширенная отладка Swift в LLDB.

refcount - тоже довольно простая команда. Он показывает вам количество ссылок для конкретного объекта. Давайте посмотрим на пример вывода с объектом, который мы использовали в предыдущем разделе - logger:

(lldb) language swift refcount logger
refcount data: (strong = 4, weak = 0)

Конечно, это может быть весьма полезно, если вы устраняете утечки памяти.

3. Управляйте процессом выполнения приложения.

process, breakpoint, thread

Эта часть моя любимая. Поскольку, используя эту команду из LLDB (в частности, breakpoint), вы можете автоматизировать множество рутинных операций во время отладки. Что в конечном итоге значительно ускорит процесс отладки.

С process вы можете управлять процессом отладки и подключаться к определенной цели или отсоединять от нее отладчик. Но поскольку Xcode автоматически подключает процесс (LLDB подключается Xcode каждый раз, когда вы запускаете цель), я не буду останавливаться на этом. Вы можете прочитать, как подключиться к цели с помощью терминала, в этом руководстве Apple - Использование LLDB в качестве автономного отладчика.

Используя process status, вы можете исследовать текущее место, где вас ждет отладчик:

(lldb) process status
Process 27408 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x000000010bbe4889 LLDB-Debugger-Exploration`ViewController.viewDidLoad(self=0x00007fa0c1406900) -> () at ViewController.swift:69
66
67           let a = 2, b = 2
68           let result = valueOfLifeWithoutSumOf(a, and: b)
-> 69           print(result)
70
71
72

Чтобы продолжить выполнение цели до появления следующей точки останова, выполните эту команду:

(lldb) process continue
(lldb) c // Or just type "c" which is the same as previous command

Это эквивалент кнопки «продолжить» на панели инструментов отладчика Xcode:

Команда breakpoint позволяет вам манипулировать точками останова любым возможным способом. Давайте пропустим самые очевидные команды, такие как: breakpoint enable, breakpoint disable и breakpoint delete.

Перво-наперво, чтобы изучить все ваши точки останова, давайте воспользуемся подкомандой list, как в примере ниже:

(lldb) breakpoint list
Current breakpoints:
1: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift', line = 95, exact_match = 0, locations = 1, resolved = 1, hit count = 1
1.1: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int, and : Swift.Int) -> Swift.Int + 27 at ViewController.swift:95, address = 0x0000000107f3eb3b, resolved, hit count = 1
2: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift', line = 60, exact_match = 0, locations = 1, resolved = 1, hit count = 1
2.1: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.viewDidLoad () -> () + 521 at ViewController.swift:60, address = 0x0000000107f3e609, resolved, hit count = 1

Первое число в списке - это идентификатор точки останова, который вы можете использовать для ссылки на любую конкретную точку останова. Давайте установим новую точку останова прямо из консоли:

(lldb) breakpoint set -f ViewController.swift -l 96
Breakpoint 3: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int, and : Swift.Int) -> Swift.Int + 45 at ViewController.swift:96, address = 0x0000000107f3eb4d

В этом примере -f - это имя файла, в котором вы хотите установить точку останова. А -l - это номер строки новой точки останова. Есть более короткий способ установить ту же точку останова с помощью ярлыка b:

(lldb) b ViewController.swift:96

Вы также можете установить точку останова с определенным регулярным выражением (например, с именем функции), используя следующую команду:

(lldb) breakpoint set --func-regex valueOfLifeWithoutSumOf
(lldb) b -r valueOfLifeWithoutSumOf // Short version of the command above

Иногда бывает полезно установить точку останова только для одного попадания. И затем проинструктируйте точку останова немедленно удалить себя. Наверняка для этого есть флаг:

(lldb) breakpoint set --one-shot -f ViewController.swift -l 90
(lldb) br s -o -f ViewController.swift -l 91 // Shorter version of the command above

Теперь перейдем к самому интересному - автоматизации точек останова. Знаете ли вы, что можно установить конкретное действие, которое будет выполняться при возникновении точки останова? Да, ты можешь! Используете ли вы print() в коде, чтобы исследовать значения, которые вас интересуют при отладке? Пожалуйста, не делай этого, есть способ лучше. 🙂

С breakpoint command вы можете настроить команды, которые будут выполняться сразу при достижении точки останова. Вы даже можете создавать «невидимые» точки останова, которые не прерывают выполнение. Что ж, технически эти «невидимые» точки останова будут прерывать выполнение, но вы не заметите этого, если добавите команду continue в конец цепочки команд.

(lldb) b ViewController.swift:96 // Let's add a breakpoint first
Breakpoint 2: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.valueOfLifeWithoutSumOf (Swift.Int, and : Swift.Int) -> Swift.Int + 45 at ViewController.swift:96, address = 0x000000010c555b4d
(lldb) breakpoint command add 2 // Setup some commands 
Enter your debugger command(s).  Type 'DONE' to end.
> p sum // Print value of "sum" variable
> p a + b // Evaluate a + b
> DONE

Чтобы убедиться, что вы добавили правильные команды, используйте breakpoint command list <breakpoint id> подкоманду:

(lldb) breakpoint command list 2
Breakpoint 2:
Breakpoint commands:
p sum
p a + b

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

Process 36612 resuming
p sum
(Int) $R0 = 6
p a + b
(Int) $R1 = 4

Большой! Именно то, что мы ищем. Вы можете сделать его еще более плавным, добавив команду continue в конец цепочки команд. Так что вы даже не станете останавливаться на этой точке останова.

(lldb) breakpoint command add 2 // Setup some commands
Enter your debugger command(s).  Type 'DONE' to end.
> p sum // Print value of "sum" variable
> p a + b // Evaluate a + b
> continue // Resume right after first hit
> DONE

Итак, результат будет:

p sum
(Int) $R0 = 6
p a + b
(Int) $R1 = 4
continue
Process 36863 resuming
Command #3 'continue' continued the target.

С помощью команды thread и ее подкоманд вы можете полностью контролировать поток выполнения: step-over, step-in, step-out и continue. Это прямой эквивалент кнопок управления потоком на панели инструментов отладчика Xcode.

Существует также предопределенный ярлык LLDB для этих конкретных команд:

(lldb) thread step-over
(lldb) next // The same as "thread step-over" command
(lldb) n // The same as "next" command
(lldb) thread step-in
(lldb) step // The same as "thread step-in"
(lldb) s // The same as "step"

Чтобы получить больше информации о текущем потоке, просто вызовите подкоманду info:

(lldb) thread info 
thread #1: tid = 0x17de17, 0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2, b=2, self=0x00007fe775507390) -> Int at ViewController.swift:90, queue = 'com.apple.main-thread', stop reason = step in

Чтобы увидеть список всех текущих активных потоков, используйте подкоманду list:

(lldb) thread list
Process 50693 stopped
* thread #1: tid = 0x17de17, 0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2, b=2, self=0x00007fe775507390) -> Int at ViewController.swift:90, queue = 'com.apple.main-thread', stop reason = step in
  thread #2: tid = 0x17df4a, 0x000000010daa4dc6  libsystem_kernel.dylib`kevent_qos + 10, queue = 'com.apple.libdispatch-manager'
  thread #3: tid = 0x17df4b, 0x000000010daa444e libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #5: tid = 0x17df4e, 0x000000010da9c34a libsystem_kernel.dylib`mach_msg_trap + 10, name = 'com.apple.uikit.eventfetch-thread'

Почетные упоминания

command, platform, gui

В LLDB вы можете найти команду для управления другими командами. Звучит странно, но на практике это довольно полезные маленькие инструменты. Во-первых, он позволяет выполнять некоторые команды LLDB прямо из файла. Таким образом, вы можете создать файл с некоторыми полезными командами и выполнить их сразу, как если бы это была одна команда LLDB. Вот простой пример файла:

thread info // Show current thread info
br list // Show all breakpoints

А вот как выглядит настоящая команда:

(lldb) command source /Users/Ahmed/Desktop/lldb-test-script
Executing commands in '/Users/Ahmed/Desktop/lldb-test-script'.
thread info
thread #1: tid = 0x17de17, 0x0000000109429a90 LLDB-Debugger-Exploration`ViewController.sumOf(a=2, b=2, self=0x00007fe775507390) -> Int at ViewController.swift:90, queue = 'com.apple.main-thread', stop reason = step in
br list
Current breakpoints:
1: file = '/Users/Ahmed/Desktop/Recent/LLDB-Debugger-Exploration/LLDB-Debugger-Exploration/ViewController.swift', line = 60, exact_match = 0, locations = 1, resolved = 1, hit count = 0
1.1: where = LLDB-Debugger-Exploration`LLDB_Debugger_Exploration.ViewController.viewDidLoad () -> () + 521 at ViewController.swift:60, address = 0x0000000109429609, resolved, hit count = 0

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

Если вам нужно что-то более продвинутое, вы всегда можете использовать подкоманду script. Это позволит вам управлять пользовательскими скриптами Python (add, delete, import и list). С script становится возможной настоящая автоматизация. Пожалуйста, ознакомьтесь с этим прекрасным руководством по Написанию сценариев Python для LLDB. Просто для демонстрации давайте создадим файл сценария script.py и напишем простую команду print_hello (), которая просто напечатает Hello Debugger! в консоли:

Затем нам нужно импортировать модуль Python и начать использовать нашу команду сценария в обычном режиме:

(lldb) command import ~/Desktop/script.py
The "print_hello" python command has been installed and is ready for use.
(lldb) print_hello
Hello Debugger!

Вы можете быстро проверить текущую информацию о платформе с помощью подкоманды status. status сообщит вам: путь к SDK, архитектуру процессора, версию ОС и даже список доступных устройств для этого SDK.

(lldb) platform status
Platform: ios-simulator
Triple: x86_64-apple-macosx
OS Version: 10.12.5 (16F73)
Kernel: Darwin Kernel Version 16.6.0: Fri Apr 14 16:21:16 PDT 2017; root:xnu-3789.60.24~6/RELEASE_X86_64
Hostname: 127.0.0.1
WorkingDir: /
SDK Path: "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk"
Available devices:
614F8701-3D93-4B43-AE86-46A42FEB905A: iPhone 4s
CD516CF7-2AE7-4127-92DF-F536FE56BA22: iPhone 5
0D76F30F-2332-4E0C-9F00-B86F009D59A3: iPhone 5s
3084003F-7626-462A-825B-193E6E5B9AA7: iPhone 6
...

Ну, вы не можете использовать режим графического интерфейса LLDB в Xcode, но вы всегда можете сделать это из терминала.

(lldb) gui
// You'll see this error if you try to execute gui command in Xcode
error: the gui command requires an interactive terminal.

Заключение:

В этой статье я лишь поверхностно рассмотрел истинные возможности LLDB. Несмотря на то, что LLDB существует с нами целую вечность, есть еще много людей, которые не используют весь его потенциал. Я сделал краткий обзор основных функций и того, как LLDB может автоматизировать процесс отладки. Надеюсь, это было полезно.

Так много функций LLDB осталось позади. Есть также некоторые методы отладки просмотра, о которых я даже не упомянул. Если вам интересна такая тема, оставьте, пожалуйста, комментарий ниже. Я был бы более чем счастлив написать об этом.

Я настоятельно рекомендую вам открыть терминал, включить LLDB и просто ввести help. Это покажет вам полную документацию. И вы можете часами читать его. Но я гарантирую, что это будет разумное вложение времени. Потому что знание своих инструментов - единственный способ для инженеров стать действительно продуктивными.

Ссылки и полезные статьи по LLDB

  • Официальный сайт LLDB - здесь вы найдете все возможные материалы, связанные с LLDB. Документация, руководства, учебные пособия, источники и многое другое.
  • Краткое руководство по LLDB от Apple - как обычно, у Apple отличная документация. Это руководство поможет вам очень быстро начать работу с LLDB. Кроме того, они описали, как выполнять отладку с помощью LLDB без Xcode.
  • Как работают отладчики: Часть 1 - Основы - мне очень понравилась эта серия статей. Это просто фантастический обзор того, как на самом деле работают отладчики. В статье описаны все основные принципы с использованием кода ручного отладчика, написанного на C. Я настоятельно рекомендую вам прочитать все части этой замечательной серии (Часть 2, Часть 3).
  • WWDC14 Расширенная отладка Swift в LLDB - отличный обзор того, что нового в LLDB с точки зрения отладки Swift. И как LLDB помогает вам быть более продуктивным в общем процессе отладки с использованием встроенных функций и возможностей.
  • Introduction To LLDB Python Scripting - руководство по написанию сценариев Python для LLDB, которое позволяет вам начать очень быстро.
  • Танцы в отладчике. Вальс с LLDB - умное введение в некоторые основы LLDB. Некоторая информация немного устарела (например, команда (lldb) thread return. К сожалению, она не работает со Swift должным образом, поскольку потенциально может нанести некоторый ущерб подсчету ссылок). Тем не менее, это отличная статья для начала вашего пути к LLDB.