Watchpoint (megfigyelési pont) — egy hibakeresési mechanizmus, amely felfüggeszti a program végrehajtását, amikor egy megadott változó vagy memóriaterület értéke megváltozik. Ellentétben a breakpointtal, amely egy kódsorhoz van kötve, a watchpoint az adatok változását követi, függetlenül attól, hogy a program mely részén történt a módosítás. Az Apple Developer Documentation szerint a watchpointok nélkülözhetetlenek az adatversenyek, a váratlan tulajdonságváltozások hibakeresésénél és az objektumok életciklusának nyomon követésénél.
Főbb pontok
Watchpoint — a debugger egy olyan mechanizmusa, amely felfüggeszti a program végrehajtását, amikor a megadott memóriacímen lévő érték megváltozik. Míg a breakpoint egy adott kódsor elérésére reagál, addig a watchpoint egy adott memóriacellába történő írásra reagál — függetlenül attól, hogy a kód mely része hajtotta végre az írást. Ez nélkülözhetetlenné teszi a váratlan változómódosítások keresésében, különösen többszálú alkalmazásokban.
A működési elv a processzor hardveres támogatásán alapul. A modern ARM chipek (Apple Silicon, Qualcomm Snapdragon) 4–8 hardveres watchpoint regisztert biztosítanak. Amikor a processzor egy írási utasítást hajt végre egy watchpointnak megfelelő címen, hardveres megszakítás keletkezik, amelyet a debugger elkap. Ha a hardveres regiszterek elfogynak, az LLDB szoftveres módba kapcsol — lépésről lépésre ellenőrzi az összes írási utasítást, ami tízszeresére lassítja a végrehajtást.
Az ARM Architecture Reference Manual szerint a hardveres watchpointok a Data Watchpoint and Trace (DWT) modul szintjén működnek, és nem igénylik a végrehajtható kód módosítását. A reakcióidő nanoszekundumos nagyságrendű, ami lehetővé teszi a nagyfrekvenciás változómódosítások nyomon követését is az adatfeldolgozó ciklusokban, mint amilyenek az audiobufferek vagy videokockák.
Watchpoint nélkülözhetetlen, ha tudja, melyik változó változik, de nem tudja, honnan. Tipikus forgatókönyvek: a frame tulajdonság értéke egy UIView-ban látható ok nélkül változik; egy számláló a háttérszálban kiszámíthatatlanul nullázódik; az isLoading zászló a hálózati kérés befejeződése előtt kapcsol át. Mindegyik esetben nem praktikus breakpointokat elhelyezni az összes olyan helyen, ahol írás történhet. A watchpoint egyetlen paranccsal oldja meg a problémát.
Az Xcode és az LLDB három típusú watchpointot támogat: watchpoint set variable — lokális változó megfigyelésére; watchpoint set expression — címet visszaadó kifejezés megfigyelésére; watchpoint set — nyers memóriacím megfigyelésére. Minden típusnak megvan a saját alkalmazási területe.
| Watchpoint típusa | LLDB parancs | Alkalmazás |
|---|---|---|
| Változó | watchpoint set variable -w write self.count | Helyi és globális változók, struktúra tulajdonságai |
| Kifejezés | watchpoint set expression -w write -- &self->mutex.lock | Struktúramezők pointeren keresztül, tömb elemek index alapján |
| Cím | watchpoint set -w write 0x600000c4b80 | Konkrét memóriacím az LLDB előző kimenetéből |
Az Xcode-ban a watchpoint a Debug Area-n keresztül állítható be: álljon meg egy breakpointnál, keresse meg a kívánt változót a Variables View panelen, kattintson jobb gombbal, és válassza a Watch Variable lehetőséget. Az Xcode automatikusan végrehajtja a watchpoint set variable parancsot a helyes változónévvel és kontextussal. Ezt követően a debugger minden értékváltozásnál megáll — kényelmes a gyors hibakereséshez anélkül, hogy a konzolra kellene váltani. Ez a módszer azonban csak addig működik, amíg a változó az aktuális keret láthatósági tartományában van.
func processItems(_ items: [String]) {
var index = 0
// Állítsa be a watchpointot az indexen a GUI-n keresztül:
// álljon meg itt, jobb gomb → Watch Variable
for item in items {
index += 1
print("Item \(index): \(item)")
}
}
LLDB teljes parancskészletet biztosít a watchpointok konzolból történő kezeléséhez. Ez nagyobb irányítást ad, mint a GUI: megadhatja a megfigyelési terület méretét, beállíthat aktiválási feltételt, létrehozhat watchpointokat számított kifejezésekből nyert címeken, és automatikusan végrehajthat műveleteket aktiváláskor. A parancssori felület különösen hasznos összetett forgatókönyvek hibakeresésénél, ahol a megfigyelési paraméterek gyors megváltoztatására van szükség.
A watchpoint set variable parancs a változó nevét a láthatósági tartomány figyelembevételével fogadja: az Objective-C self tulajdonságaihoz használja a self->_property, Swifthez a self.property parancsot. A -w write paraméter az írás nyomon követését, a -w read — az olvasást (csak néhány architektúrán elérhető mód), a -s size — a terület méretét bájtokban állítja be. A watchpoint beállítása után a lista a watchpoint list paranccsal tekinthető meg.
(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'
Akárcsak a breakpointok, a watchpointok is támogatják a feltételes aktiválást. A -c paraméter feltételt állít be Swift vagy Objective-C nyelven. Például egy newValue > 1000 feltétellel rendelkező watchpoint csak akkor áll meg, ha az írt érték meghaladja az ezret. Ez kritikus fontosságú a másodpercenként több ezer változást generáló ciklusok vagy érzékelők hibakeresésénél — ellenkező esetben a debugger minden egyes változásnál megállna, lehetetlenné téve a munkát.
(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
// Automatikusan naplózza az értéket és folytassa a végrehajtást
Az Objective-C és Swift objektumtulajdonságok watchpointjainak sajátossága — a watchpoint nem a tulajdonság nevére, hanem az objektum memóriájában lévő ivar (példányváltozó) mező címére kerül beállításra. Ez azt jelenti, hogy minden új objektumallokációnál (például egy ViewController újralétrehozásakor) a watchpoint érvénytelenné válik, mivel a memóriacím megváltozott. Egy tulajdonság folyamatos megfigyeléséhez az újraindítások során a watchpointot az objektum inicializálásakor újra be kell állítani.
Tömbök és szótárak elemeinek megfigyelése az adott elem címének kiszámítását igényli. Például egy tömb harmadik elemének megfigyeléséhez használja a watchpoint set expression -- &array[2] parancsot. Ha a tömb újraallokálja a belső pufferét (a kapacitást meghaladó elemek hozzáadásakor), a watchpoint érvénytelenné válik — az LLDB Watchpoint 1 has an invalid address hibát jelez. Ilyen esetekben a watchpointot a gyűjtemény méretének megváltozása után újra be kell állítani.
(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
Az Objective-C-ben nyomon követheti egy objektum retain count-ját, ha watchpointot helyez el a retainCount mezőre az objc_object struktúrában. Az LLDB ezt a watchpoint set expression -w write -- (int*)[object retainCount] paranccsal hajtja végre. Az ARC-vel (Automatic Reference Counting) rendelkező Swift objektumok esetében azonban a retain count nem közvetlenül elérhető — ehelyett használja az Instruments vagy a Memory Graph Debugger eszközt a memóriaszivárgások elemzéséhez. Az Apple szerint a retain count-on lévő watchpointok csak Debug build-ekben működnek, kikapcsolt ARC optimalizálás mellett.
A watchpointoknak számos korlátozása van, amelyeket figyelembe kell venni. A fő — a hardveres watchpointok száma ARM architektúrán 4–8 regiszterre, x86-on pedig 4-re korlátozódik. Amikor az összes hardveres regiszter foglalt, az LLDB szoftveres watchpoint módba kapcsol: módosítja az összes írási utasítást a követett tartományban, hogy kivételt generáljon. Ez 10–50-szeresére lassítja a végrehajtást, ezért a gyakorlatban nem ajánlott egyszerre 2–3 aktív watchpointnál többet használni.
A második korlátozás — a watchpointok érvénytelenné válása memória-újraallokációnál. A szemétgyűjtő vagy az ARC működésekor, amikor egy objektum a memóriában kerül áthelyezésre (a kupac tömörítését alkalmazó nyelvekben), a watchpoint címe helytelenné válik. A Swiftben és az Objective-C-ben az ARC nem helyezi át az objektumokat, de a tömbök és karakterláncok újraallokációja ugyanarra az eredményre vezet. Az LLDB figyelmeztet erről a Watchpoint N address (0x...) doesn't contain a valid allocation üzenettel.
A harmadik korlátozás — a változók láthatósága. A lokális változóra helyezett watchpoint csak addig működik, amíg az a változó az aktuális veremkeret láthatósági tartományában van. Amint a függvény befejeződik, a watchpoint automatikusan törlődik. Globális változók vagy hosszú élettartamú objektumok mezőinek megfigyeléséhez a watchpoint a watchpoint delete paranccsal történő explicit törlésig vagy a folyamat végéig megmarad.
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # Összes watchpoint törlése
2 watchpoints deleted.
(lldb) watchpoint disable 1 # Ideiglenes kikapcsolás
(lldb) watchpoint enable 1 # Újbóli bekapcsolás
Az ARM szerint az Apple Silicon (M1–M4) watchpointjai 1–8 bájtos követési területméretet támogatnak. A 8 bájtnál nagyobb struktúrák megfigyeléséhez több watchpointot kell elhelyezni minden mezőn. Ez fontos az olyan összetett adatstruktúrák hibakeresésénél, mint a CGRect (16 bájt) vagy az UIEdgeInsets (16 bájt).
Gyakran Ismételt Kérdések
Breakpoint egy kódsorhoz van kötve — annak elérésekor áll meg. Watchpoint egy memóriacímhez van kötve — a program bármely részéből történő íráskor áll meg. A watchpoint azt keresi, „ki változtatja meg az értéket”, a breakpoint azt, hogy “i történik ezen a soron”.
4–8 hardveres ARM-on (beleértve az Apple Silicon-t). A határ túllépése szoftveres módba kapcsolja a watchpointot, ami 10–50-szeresére lassítja a végrehajtást. Javasolt legfeljebb 2–3 aktív watchpoint egyidejű használata.
A watchpointok nem őrződnek meg a hibakeresési munkamenetek között. Minden új indításkor a memóriacímek megváltoznak, és a watchpointot újra be kell állítani. Kivétel — a fix címmel rendelkező globális változók watchpointjai.
Igen, de a watchpoint a tulajdonság ivar (háttértároló) mezőjére kerül, nem magára a tulajdonságra. Swiftben használja a watchpoint set variable self.property parancsot — az LLDB automatikusan megtalálja a megfelelő ivart a tulajdonság neve alapján.
Hardveres watchpoint nem befolyásolja a teljesítményt — a megszakítás processzorszinten történik. Szoftveres (a hardveres regiszterek kimerülésekor) 10–50-szeresére lassítja a végrehajtást, mivel az LLDB minden írási utasítást ellenőriz.
Összefoglalás
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is