Watchpoint (точка на наблюдение) — механизъм за дебъгване, който спира изпълнението на програмата при промяна на стойността на указана променлива или област от паметта. За разлика от breakpoint-а, който е обвързан с ред код, watchpoint проследява промяната на данни независимо от това в коя част на програмата е настъпила модификацията. Според Apple Developer Documentation, watchpoint-овете са незаменими при дебъгване на състезания за данни, неочаквани промени на свойства и проследяване на жизнения цикъл на обекти.
Основни неща
Watchpoint — механизъм на дебъгера, който спира изпълнението на програмата, когато стойността на указан адрес от паметта се промени. Докато breakpoint реагира на достигане на конкретен ред код, watchpoint реагира на записване в определена клетка от паметта — независимо коя част от кода е извършила записа. Това го прави незаменим при търсене на неочаквани модификации на променливи, особено в многонишкови приложения.
Принципът на работа се основава на хардуерната поддръжка на процесора. Съвременните ARM чипове (Apple Silicon, Qualcomm Snapdragon) предоставят от 4 до 8 хардуерни регистъра за watchpoint. Когато процесорът изпълни инструкция за запис на адрес, съвпадащ с watchpoint, възниква хардуерно прекъсване, което дебъгерът прихваща. Ако хардуерните регистри се изчерпят, LLDB преминава в софтуерен режим — проверява всяка инструкция за запис стъпка по стъпка, което забавя изпълнението десетки пъти.
Според ARM Architecture Reference Manual, хардуерните watchpoints работят на ниво модул Data Watchpoint and Trace (DWT) и не изискват модификация на изпълнимия код. Времето за реакция при задействане е от порядъка на наносекунди, което позволява проследяване дори на високочестотни промени на променливи в цикли за обработка на данни, като аудио буфери или видео кадри.
Watchpoint е незаменим, когато знаете коя променлива се променя, но не знаете откъде. Типични сценарии: стойността на свойството frame в UIView се променя без видима причина; брояч във фонов низ се нулира непредвидимо; флагът isLoading се превключва преди мрежовата заявка да приключи. Във всеки от тези случаи поставянето на breakpoints на всички места, където може да се осъществи запис, е непрактично. Watchpoint решава проблема с една команда.
Xcode и LLDB поддържат три вида watchpoints: watchpoint set variable — за наблюдение на локална променлива; watchpoint set expression — за наблюдение на израз, връщащ адрес; watchpoint set — за наблюдение на суров адрес от паметта. Всеки вид има своя област на приложение.
| Вид watchpoint | LLDB команда | Приложение |
|---|---|---|
| Променлива | watchpoint set variable -w write self.count | Локални и глобални променливи, свойства на структури |
| Израз | watchpoint set expression -w write -- &self->mutex.lock | Полета на структури чрез указател, елементи на масиви по индекс |
| Адрес | watchpoint set -w write 0x600000c4b80 | Конкретен адрес от паметта от предишен изход на LLDB |
В Xcode watchpoint може да се настрои чрез Debug Area: спрете на breakpoint, намерете желаната променлива в панела Variables View, щракнете с десния бутон и изберете Watch Variable. Xcode автоматично ще изпълни командата watchpoint set variable с правилното име на променливата и контекст. След това дебъгерът ще спира при всяка промяна на стойността — удобно за бързо дебъгване без превключване към конзолата. Този метод обаче работи само докато променливата е в обхвата на видимост на текущия кадър.
func processItems(_ items: [String]) {
var index = 0
// Настройте watchpoint на index чрез GUI:
// спрете тук, десен клик → Watch Variable
for item in items {
index += 1
print("Item \(index): \(item)")
}
}
LLDB предоставя пълен набор от команди за управление на watchpoints от конзолата. Това дава по-голям контрол от GUI: можете да зададете размер на областта за наблюдение, да поставите условие за задействане, да създавате watchpoints на адреси, получени от изчислени изрази, и автоматично да изпълнявате действия при задействане. Интерфейсът на командния ред е особено полезен при дебъгване на сложни сценарии, където се изисква бърза промяна на параметрите за наблюдение.
Командата watchpoint set variable приема името на променливата, като взема предвид обхвата на видимост: за self свойства в Objective-C използвайте self->_property, за Swift — self.property. Параметърът -w write задава проследяване на запис, -w read — четене (режим, достъпен само на някои архитектури), -s size — размер на областта в байтове. След настройка на watchpoint, списъкът може да се види с командата watchpoint list.
(lldb) watchpoint set variable -w write -s 8 self.balance
Watchpoint 1: addr = 0x600000c4b80 size = 8 state = enabled type = w
watchpoint spec: 'self.balance'
(lldb) watchpoint set expression -w write -- self->items._storage
Watchpoint 2: addr = 0x600003a4c00 size = 8
(lldb) watchpoint list
1: location = 0x600000c4b80, type = write, variable = '_balance'
2: location = 0x600003a4c00, type = write, expression = 'self->items._storage'
Както breakpoint-овете, watchpoint-овете поддържат условно задействане. Параметърът -c задава условие на Swift или Objective-C. Например watchpoint с условие newValue > 1000 ще спре само когато записаната стойност надвишава хиляда. Това е критично при дебъгване на цикли или сензори, генериращи хиляди промени в секунда — иначе дебъгерът ще спира при всяка промяна, правейки работата невъзможна.
(lldb) watchpoint set variable -w write self.temperature -c "(int)$newValue > 100"
Watchpoint 3: addr = 0x600000e4a20, condition = '(int)$newValue > 100'
(lldb) watchpoint modify 3 -C "po self.temperature" -G true
// Автоматично записване на стойността и продължаване на изпълнението
Особеността на watchpoints за свойства на обекти Objective-C и Swift — watchpoint се настройва не на името на свойството, а на адреса на полето ivar (променлива на инстанция) в паметта на обекта. Това означава, че при всяко ново заделяне на обект (например при повторно създаване на ViewController), watchpoint става невалиден, тъй като адресът на паметта се е променил. За постоянно наблюдение на свойство чрез рестартирания, watchpoint трябва да се настрои отново в момента на инициализация на обекта.
Наблюдението на елементи от масиви и речници изисква изчисляване на адреса на конкретния елемент. Например за наблюдение на третия елемент от масив използвайте watchpoint set expression -- &array[2]. Ако масивът преразпредели вътрешния си буфер (при добавяне на елементи над капацитета), watchpoint става невалиден — LLDB съобщава грешка Watchpoint 1 has an invalid address. В такива случаи watchpoint трябва да се настрои отново след промяна на размера на колекцията.
(lldb) expr var $arr = [10, 20, 30, 40, 50]
(lldb) watchpoint set expression -w write -- &$arr[2]
Watchpoint 4: addr = 0x1000a4b20, size = 8
(lldb) expr $arr[2] = 99
Watchpoint 4 hit: old value: 30, new value: 99
В Objective-C можете да проследявате retain count на обект, като настроите watchpoint на полето retainCount в структурата objc_object. LLDB изпълнява това чрез watchpoint set expression -w write -- (int*)[object retainCount]. За Swift обекти с ARC (Automatic Reference Counting) обаче retain count не е директно достъпен — вместо това използвайте Instruments или Memory Graph Debugger за анализ на изтичане на памет. Според Apple, watchpoints върху retain count работят само в Debug компилации с изключена ARC оптимизация.
Watchpoint-овете имат редица ограничения, които трябва да се вземат предвид. Основното — броят на хардуерните watchpoints е ограничен до 4–8 регистъра на ARM архитектура и до 4 на x86. Когато всички хардуерни регистри са заети, LLDB преминава в режим на софтуерен watchpoint: модифицира всяка инструкция за запис в проследявания диапазон, за да генерира изключение. Това забавя изпълнението 10–50 пъти, поради което на практика се препоръчва използването на не повече от 2–3 активни watchpoints едновременно.
Второто ограничение — невалидност на watchpoints при преразпределяне на памет. По време на работа на garbage collector или ARC, когато обект се премества в паметта (в езици с компресиране на купчината), адресът на watchpoint става неправилен. В Swift и Objective-C ARC не премества обекти, но преразпределянето на масиви и низове води до същия ефект. LLDB предупреждава за това със съобщение Watchpoint N address (0x...) doesn't contain a valid allocation.
Третото ограничение — видимост на променливи. Watchpoint върху локална променлива работи само докато тази променлива е в обхвата на видимост на текущия стеков кадър. Веднага след като функцията приключи, watchpoint автоматично се премахва. За наблюдение на глобални променливи или полета на дълготрайни обекти, watchpoint се запазва до изрично премахване чрез watchpoint delete или до завършване на процеса.
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # Удалить все watchpoints
2 watchpoints deleted.
(lldb) watchpoint disable 1 # Временно отключить
(lldb) watchpoint enable 1 # Включить снова
Според ARM, watchpoints на Apple Silicon (M1–M4) поддържат размер на проследяваната област от 1 до 8 байта. За наблюдение на структури, по-големи от 8 байта, е необходимо поставяне на няколко watchpoints на всяко поле. Това е важно при дебъгване на сложни структури от данни като CGRect (16 байта) или UIEdgeInsets (16 байта).
Често задавани въпроси
Breakpoint е обвързан с ред код — спира при достигането му. Watchpoint е обвързан с адрес от паметта — спира при запис на този адрес от което и да е място в програмата. Watchpoint търси „кой променя стойността“, breakpoint — „какво се случва на този ред“.
4–8 хардуерни на ARM (включително Apple Silicon). Превишаването превключва watchpoint в софтуерен режим, което забавя изпълнението 10–50 пъти. Препоръчват се не повече от 2–3 активни watchpoints едновременно.
Watchpoint-овете не се запазват между сесиите за дебъгване. При всяко ново стартиране адресите на паметта се променят и watchpoint трябва да се настрои отново. Изключение — watchpoints върху глобални променливи с фиксиран адрес.
Да, но watchpoint се настройва на ivar (поддържащо хранилище) на свойството, а не на самото свойство. В Swift използвайте watchpoint set variable self.property — LLDB автоматично ще намери съответния ivar по името на свойството.
Хардуерният watchpoint не влияе на производителността — прекъсването става на ниво процесор. Софтуерният (при изчерпване на хардуерните регистри) забавя изпълнението 10–50 пъти, тъй като LLDB проверява всяка инструкция за запис.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също