Watchpoint (punto di osservazione) — un meccanismo di debug che sospende l'esecuzione del programma quando cambia il valore di una variabile o di un'area di memoria specificata. A differenza di un breakpoint, che è legato a una riga di codice, un watchpoint tiene traccia delle modifiche dei dati indipendentemente da dove nel programma avviene la modifica. Secondo la Apple Developer Documentation, i watchpoint sono indispensabili per il debug di race condition, modifiche impreviste delle proprietà e monitoraggio del ciclo di vita degli oggetti.
Punti chiave
Watchpoint — è un meccanismo del debugger che sospende l'esecuzione del programma quando il valore a un indirizzo di memoria specificato cambia. Se un breakpoint reagisce al raggiungimento di una specifica riga di codice, un watchpoint reagisce alla scrittura in una cella di memoria specifica — indipendentemente da quale parte del codice ha eseguito la scrittura. Questo lo rende indispensabile per trovare modifiche impreviste delle variabili, specialmente nelle applicazioni multithread.
Il principio di funzionamento si basa sul supporto hardware del processore. I chip ARM moderni (Apple Silicon, Qualcomm Snapdragon) forniscono da 4 a 8 registri hardware watchpoint. Quando il processore esegue un'istruzione di scrittura a un indirizzo che corrisponde a un watchpoint, si verifica un'interruzione hardware che il debugger intercetta. Quando i registri hardware si esauriscono, LLDB passa alla modalità software — controlla passo dopo passo ogni istruzione di scrittura, rallentando l'esecuzione di decine di volte.
Secondo il ARM Architecture Reference Manual, i watchpoint hardware operano a livello del modulo Data Watchpoint and Trace (DWT) e non richiedono modifica del codice eseguibile. Il tempo di risposta è nell'ordine dei nanosecondi, consentendo di tracciare anche modifiche ad alta frequenza in cicli di elaborazione dati come buffer audio o fotogrammi video.
Watchpoint è indispensabile quando sai quale variabile sta cambiando ma non sai da dove. Scenari tipici: una proprietà frame di UIView cambia senza motivo apparente; un contatore in un thread in background si resetta in modo imprevedibile; un flag isLoading commuta prima che una richiesta di rete venga completata. In ciascuno di questi casi, impostare breakpoint in tutti i punti in cui potrebbe verificarsi una scrittura è poco pratico. Un watchpoint risolve il problema con un singolo comando.
Xcode e LLDB supportano tre tipi di watchpoint: watchpoint set variable — per monitorare una variabile locale; watchpoint set expression — per monitorare un'espressione che restituisce un indirizzo; watchpoint set — per monitorare un indirizzo di memoria grezzo. Ogni tipo ha il proprio ambito di applicazione.
| Tipo di watchpoint | Comando LLDB | Utilizzo |
|---|---|---|
| Variabile | watchpoint set variable -w write self.count | Variabili locali e globali, proprietà di struct |
| Espressione | watchpoint set expression -w write -- &self->mutex.lock | Campi di struct tramite puntatore, elementi di array per indice |
| Indirizzo | watchpoint set -w write 0x600000c4b80 | Indirizzo di memoria specifico da output precedente di LLDB |
In Xcode, puoi impostare un watchpoint tramite l'Area di Debug: fermati su un breakpoint, trova la variabile desiderata nel pannello Variables View, fai clic con il tasto destro e seleziona Watch Variable. Xcode eseguirà automaticamente il comando watchpoint set variable con il nome e contesto corretti della variabile. Dopodiché, il debugger si fermerà a ogni cambiamento di valore — comodo per un debug rapido senza passare alla console. Tuttavia, questo metodo funziona solo finché la variabile è nell'ambito del frame corrente.
func processItems(_ items: [String]) {
var index = 0
// Imposta watchpoint su index tramite GUI:
// fermati qui, clic destro → Watch Variable
for item in items {
index += 1
print("Elemento \(index): \(item)")
}
}
LLDB fornisce un set completo di comandi per gestire i watchpoint dalla console. Questo offre un maggiore controllo rispetto alla GUI: puoi impostare la dimensione dell'area di osservazione, specificare condizioni di attivazione, creare watchpoint su indirizzi ottenuti da espressioni calcolate ed eseguire automaticamente azioni all'attivazione. L'interfaccia a riga di comando è particolarmente utile per il debug di scenari complessi che richiedono di cambiare rapidamente i parametri di osservazione.
Il comando watchpoint set variable prende un nome di variabile considerando l'ambito: per le proprietà self di Objective-C usa self->_property, per Swift usa self.property. Il parametro -w write imposta il tracciamento delle scritture, -w read — il tracciamento delle letture (una modalità disponibile solo su alcune architetture), -s size — la dimensione dell'area in byte. Dopo aver impostato un watchpoint, puoi visualizzare l'elenco con il comando 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'
Come i breakpoint, i watchpoint supportano l'attivazione condizionale. Il parametro -c imposta una condizione in Swift o Objective-C. Ad esempio, un watchpoint con la condizione newValue > 1000 si fermerà solo quando il valore scritto supera il migliaio. Questo è fondamentale per il debug di loop o sensori che generano migliaia di cambiamenti al secondo — altrimenti il debugger si fermerebbe a ogni cambiamento, rendendo il lavoro impossibile.
(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
// Registra automaticamente il valore e continua l'esecuzione
Una caratteristica specifica dei watchpoint per le proprietà di oggetti Objective-C e Swift — un watchpoint viene impostato non sul nome della proprietà ma sull'indirizzo del campo ivar (variabile di istanza) nella memoria dell'oggetto. Ciò significa che a ogni nuova allocazione dell'oggetto (ad esempio, ricreando un ViewController), il watchpoint diventa invalido perché l'indirizzo di memoria è cambiato. Per il monitoraggio persistente di una proprietà tra riavvii, è necessario reimpostare il watchpoint al momento dell'inizializzazione dell'oggetto.
Il monitoraggio di elementi di array e dizionari richiede il calcolo dell'indirizzo di un elemento specifico. Ad esempio, per monitorare il terzo elemento di un array usa watchpoint set expression -- &array[2]. Se l'array rialloca il suo buffer interno (aggiungendo elementi oltre la capacità), il watchpoint diventa invalido — LLDB segnalerà un errore Watchpoint 1 has an invalid address. In questi casi, devi reimpostare il watchpoint dopo aver modificato la dimensione della collezione.
(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
In Objective-C, puoi tracciare il retain count di un oggetto impostando un watchpoint sul campo retainCount nella struttura objc_object. LLDB lo fa tramite watchpoint set expression -w write -- (int*)[object retainCount]. Tuttavia, per gli oggetti Swift con ARC (Automatic Reference Counting), il retain count non è direttamente accessibile — usa invece Instruments o Memory Graph Debugger per l'analisi delle perdite. Secondo Apple, i watchpoint sul retain count funzionano solo in build Debug con ottimizzazione ARC disabilitata.
I watchpoint hanno diverse limitazioni importanti da considerare. La principale — il numero di watchpoint hardware è limitato a 4–8 registri sull'architettura ARM e fino a 4 su x86. Quando tutti i registri hardware sono occupati, LLDB passa alla modalità watchpoint software: modifica ogni istruzione di scrittura nell'intervallo tracciato per generare un'eccezione. Questo rallenta l'esecuzione da 10 a 50 volte, quindi in pratica si consiglia di non usare più di 2–3 watchpoint attivi contemporaneamente.
La seconda limitazione — l'invalidazione dei watchpoint in caso di riallocazione della memoria. Quando si attiva la garbage collection o ARC, e un oggetto si sposta in memoria (nei linguaggi con compattazione dell'heap), l'indirizzo del watchpoint diventa errato. In Swift e Objective-C, ARC non sposta gli oggetti, ma la riallocazione di array e stringhe causa lo stesso effetto. LLDB avvisa di questo con il messaggio Watchpoint N address (0x...) doesn’t contain a valid allocation.
La terza limitazione — la visibilità delle variabili. Un watchpoint su una variabile locale funziona solo finché quella variabile è nell'ambito del frame di stack corrente. Una volta che la funzione termina, il watchpoint viene automaticamente rimosso. Per il monitoraggio di variabili globali o campi di oggetti a lunga vita, un watchpoint persiste fino alla sua eliminazione esplicita tramite watchpoint delete o fino alla terminazione del processo.
(lldb) watchpoint delete 1
1 watchpoints deleted.
(lldb) watchpoint delete # Eliminare tutti i watchpoints
2 watchpoints deleted.
(lldb) watchpoint disable 1 # Disabilitare temporaneamente
(lldb) watchpoint enable 1 # Riabilitare
Secondo ARM, i watchpoint su Apple Silicon (M1–M4) supportano una dimensione dell'area monitorata da 1 a 8 byte. Per monitorare strutture più grandi di 8 byte, devi impostare più watchpoint su ogni campo. Questo è importante per il debug di strutture dati complesse come CGRect (16 byte) o UIEdgeInsets (16 byte).
Domande frequenti
Breakpoint è legato a una riga di codice — si ferma quando quella riga viene raggiunta. Watchpoint è legato a un indirizzo di memoria — si ferma quando si verifica una scrittura a quell'indirizzo da qualsiasi punto del programma. Un watchpoint cerca “chi sta cambiando il valore”, un breakpoint cerca “cosa succede su questa riga”.
4–8 watchpoint hardware su ARM (incluso Apple Silicon). Il superamento fa passare il watchpoint in modalità software, rallentando l'esecuzione da 10 a 50 volte. Si consiglia di non usare più di 2–3 watchpoint attivi alla volta.
I watchpoint non vengono preservati tra sessioni di debug. A ogni nuovo avvio, gli indirizzi di memoria cambiano e il watchpoint deve essere reimpostato. L'eccezione sono i watchpoint su variabili globali con indirizzo fisso.
Sì, ma il watchpoint viene impostato sull'ivar (storage di supporto) della proprietà, non sulla proprietà stessa. In Swift, usa watchpoint set variable self.property — LLDB troverà automaticamente l'ivar corrispondente dal nome della proprietà.
I watchpoint hardware non influiscono sulle prestazioni — l'interruzione avviene a livello del processore. I watchpoint software (quando i registri hardware sono esauriti) rallentano l'esecuzione da 10 a 50 volte, poiché LLDB controlla ogni istruzione di scrittura.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche