Domanda:
Debug di un deadlock quando il thread del proprietario del mutex è morto?
André Oliveira
2017-10-01 20:35:26 UTC
view on stackexchange narkive permalink

Mi trovo di fronte a uno strano scenario di deadlock che non avevo mai visto prima.

Sto cercando di eseguire il debug di questo deadlock da 3 giorni e non riuscivo a trovare come risolverlo in modo corretto. Spero che qualcuno mi possa aiutare.

Non abbiamo il codice sorgente dell'applicazione, quindi questo deadlock deve essere risolto applicando una patch al codice asm (nessun problema con la patch del binario).

Lo scenario

L'applicazione è un vecchio gioco online x86 che utilizza directx 9 per il rendering della grafica (d3d9.dll). Da quello che ho potuto analizzare ci sono 3 thread relativi a questo deadlock. Per nominare, il thread # 1 è il thread principale (che esegue il rendering di ogni frame), il thread # 2 sembra essere un thread di lavoro d3d9 che accoda un caricamento di risorse asincrone per essere elaborato e estratto dal thread # 1, e poi abbiamo il thread misterioso # 3 che è correlato a una caratteristica del gioco che fondamentalmente fa una richiesta http a un server e scarica una texture da visualizzare nel gioco al volo; ogni volta che questa texture deve essere disegnata, il thread principale crea questo thread # 3 per elaborare l'I / O per scaricare la texture, che sarà passata al thread # 2 (thread di lavoro d3d9) per essere finalmente processata in modo asincrono dal thread # 1 (loop di gioco).

Dopo che il thread # 3 ha fornito al thread # 2 le risorse di texture, il thread # 3 si arresta da solo. Questo thread n. 3 viene creato da crt! _Beginthread e shutdown con crt! _Endthread.

Il deadlock

Per riassumere: thread # 1 (loop di gioco), thread # 2 (thread di lavoro d3d9), thread # 3 (scarica la trama da http).

Il problema si verifica quando (per qualche motivo sconosciuto) il thread # 3 viene terminato senza liberare un blocco atteso dal thread # 2. Il thread n. 1 a sua volta attende che il thread n. 2 acquisisca le risorse da disegnare.

Questo è diverso da tutti gli altri deadlock che ho dovuto affrontare perché il thread che sembra essere la causa del deadlock non esiste più quando si verifica il deadlock. Tutti gli altri deadlock di cui ho eseguito il debug erano più o meno diretti per trovare l'origine del problema perché quando si isolavano i thread in deadlock dovevo semplicemente analizzare lo stack di chiamate da ciascun thread per sapere esattamente cosa stava succedendo. Il grosso problema qui è che poiché il thread # 3 è morto, non ho il suo stack di chiamate per vedere il momento in cui crea il deadlock. Quindi la grande domanda è: come posso trovare cosa sta succedendo all'interno di questo thread n. 3 se non riesco nemmeno a vedere lo stack di chiamate?

Alcuni output di analisi di WinDbg dopo che si sono verificati deadlock

0: 001>! locks

  CritSec + 935a060 a 0935a060WaiterWoken NoLockCount 2RecursionCount 1OwningThread 34fcEntryCount 0ContentionCount 21a *** LockedScanned 27 sezioni critiche  

0: 001>! runaway

  Tempo modalità utente Tempo thread 0: 2888 0 giorni 0: 00: 15.234 11: 2210 0 giorni 0: 00: 02.796 20: 15f0 0 giorni 0: 00: 01.656 17: 584 0 giorni 0: 00: 00.453 21: 2860 0 giorni 0: 00: 00.140 13: 8cc 0 giorni 0: 00: 00.031 7: 1a70 0 giorni 0: 00: 00.031 23: 373c 0 giorni 0: 00: 00.015 14: 2fe0 0 giorni 0: 00: 00.015 22: 1bf4 0 giorni 0: 00: 00.000 19: 3a50 0 giorni 0: 00: 00.000 18: 2980 0 giorni 0: 00: 00.000 16: 1e0c 0 giorni 0: 00: 00.000 15: 2768 0 giorni 0: 00: 00.000 12: 3154 0 giorni 0: 00: 00.000 10: 2cfc 0 giorni 0: 00: 00.000 9: 1e40 0 giorni 0: 00: 00.000 8: 1ea8 0 giorni 0: 00: 00.000 5: 2b64 0 giorni 0: 00: 00.000 4: 338c 0 giorni 0: 00: 00.000 1: 3be8 0 giorni 0: 00: 00.000  

0: 001> ~ 0 kb

  # ChildEBP RetAddr Args to Child 00 0019f640 75f48869 000006c0 00000000 0019f688 ntdll! NtWaitForSingleObject + 0xc
01 0019f6b4 75f487c2 000006c0 000003e8 00000000 KERNELBASE! WaitForSingleObjectEx + 0x9902 0019f6c8 68bbac92 000006c0 000003e8 0935a040 KERNELBASE! WaitForSingleObject + 0x1203 0019f6dc 68b7d6e4 88.760.870 0935a040 015c6e38 d3d9! CBatchFilterI :: WaitForBatchWorkerThread + 0x2304 0019f6ec 68c403d1 04f0de60 68c403b0 c9e02d57 d3d9! CBatchFilterI :: FlushBatchWorkerThread + 0xc05 0019f700 68b78522 0935a040 00000000 00011001 d3d9! CBatchFilterI :: LHBatchFlush1 + 0x2106 0019f718 68b99daa 04f0de60 68b54020 04f0dd00 d3d9! Flush + 0x3607 0019f9bc 68b6a661 04f0de60 04f0b634 04f08ac0 d3d9! DdBltLH + 0x45d8a08 0019fa94 68be9fcc 00000000 00000000 00000000 d3d9! CSwapChain :: PresentMain + 0x3a709 0019fabc 68be9e57 00000000 00000000 00000000 d3d9! CBaseDevice :: PresentMain + 0x680a 0019faf4 10.109.099 04f08ac0 00000000 00000000 d3d9! CBaseDevice :: Presente + 0x570b 0019fc10 10107a15 04f08ac0 00000000 00000000 DoNPatch! fIDirect3Device9 :: fPresent + 0x2e90c 0019fc58 005495e0 00000001 03440be8 03448a70 DoNPatch! NKD_IDirect3DDevice_Present + 0x50d 00 19fc7c 00.549.367 00000000 03440be8 00000000 SD_Asgard! Loc_5494D7 + 0x1090e 0019fcc4 0054b7a1 0105a000 03440be8 00b200b0 SD_Asgard! Loc_5493670f 0019fef4 005bb824 00400000 00000000 01503b2d SD_Asgard! Loc_54B784 + 0x1d10 0019ff80 76d28744 00.302.000 76d28720 34.573.170 SD_Asgard! Loc_5BB812 + 0x1211 0019ff94 76f8582d 00.302.000 03e96be8 00000000 KERNEL32! BaseThreadInitThunk + 0x2412 0019ffdc 76f857fd ffffffff 76fa6389 00000000 ntdll! __ RtlUserThreadStart + 0x2f13 0019ffec 00000000 005cc46f 00302000 00000000 ntdll! _RtlUserThreadStart + 0x1b  

0: 001> ~ 11p>

# ChildEBP RetAddr Args per bambini 00 04c2fe58 76f4c07a 0935a064 00000000 00000000 ntdll! NtWaitForAlertByThreadId + 0xc01 04c2fe78 76f4bfbe 00000000 00000000 ffffffff ntdll! RtlpWaitOnAddressWithTimeout + 0x3302 04c2febc 76f4beb5 00000004 00000000 00000000 ntdll! RtlpWaitOnAddress + 0xa503 04c2fefc 76f6b3f1 0935a040 0935a040 00000004 ntdll! RtlpWaitOnCriticalSection + 0xb7 04 04c2ff1c 76f6b315 0935a040 04c2ff38 68b7d1e8 ntdll! RtlpEnterCriticalSectionContended + 0xd105 04c2ff28 68b7d1e8 0935a060 0941a324 04c2ff60 ntdll! RtlEnterCriticalSection + 0x4506 04c2ff38 68b80753 00000001 00000001 0935a040 d3d9! CBatchFilterI :: AcquireSynchronization + 0x2807 04c2ff60 68c42021 0941a320 00000001 68c41760 d3d9! CBatchFilterI :: ProcessBatch + 0x14b08 04c2ff78 68c4176d 04c2ff94 76d28744 0935a040 d3d9! CBatchFilterI :: WorkerThread + 0x2d09 04c2ff80 76d28744 0935a040 76d28720 308c3170 d3d9! CBatchFilterI :: LHBatchWorkerThread + 0xd0a 04c2ff94 76f8582d 0935a040 07326be8 00000000 KERNEL32! BaseThreadInitThunk + 0x240b 04c2ffdc 76f857fd ffffffff 76fa6389 00000000 ntdll! __ RtlUserThreadStart + 0x2f0c 04c2ffec 00000000 68c41760 0935a040 00000000 ntdll! _RtlUserThreadStart + 0x1b

Conclusione

Negli output sopra, l'id del thread che possiede la sezione critica bloccata (34fc) è il thread # 3 (effettua la richiesta http), che non è presente nell'elenco! runaway. Nell'elenco! Runaway, il numero di thread 0 è il # 1 (loop di gioco) e il numero di thread 11 è il # 2 (d3d9 batch worker). Se hai bisogno di altri dati che posso raccogliere, chiedilo. In questa analisi ho usato IDA Pro 6.9 e WinDbg, ma posso procurarmi altri strumenti se disponibili.

Alla fine, scusa per il testo lungo e grazie in anticipo.

Ciao @blabb, grazie per la risposta. Non pensi che la tua idea sia troppo rischiosa in una fase di produzione? Mi vengono in mente tante cose che possono andare storte facendo questo tipo di patch. Mi sembra più un palliativo che una vera soluzione al problema.
non so così ho cancellato il commento
Una risposta:
Igor Skochinsky
2017-10-02 00:00:23 UTC
view on stackexchange narkive permalink

Questo è più un problema di sviluppo SW che RE, ma puoi provare a usare ! htrace per scoprire dove era originariamente allocato il mutex (traccia dello stack di creazione).

In alternativa, prova a capire perché il thread n. 3 esce senza rilasciare il blocco. Potrebbe essere un po 'complicato, ma se riesci a riprodurre i due scenari (con e senza rilasciare il blocco), il debug differenziale può essere utile per capire dove divergono i percorsi del codice.

Ciao @Igor, grazie per la risposta e scusa per la risposta tardiva. Ho dimenticato di menzionare nel testo ma ho già fatto! Htrace per vedere se il tracciamento dell'allocazione del mutex fosse utile in questo contesto, ma non ho potuto ottenere nulla da lì. Sai se invece c'è un modo per risalire al blocco / sblocco della sezione critica? Se potesse essere fatto, penso di poter rintracciare esattamente dove il thread # 3 sta acquisendo il blocco e tracciare il codice da lì per vedere perché non viene liberato.
Riguardo all'analisi ExitThread, l'ho già fatto anch'io. Ho tracciato il flusso di esecuzione dal thread n. 3 quando causa il deadlock e almeno per una prospettiva di conteggio frame superiore non sta accadendo nulla di insolito, il thread esegue il suo codice come qualsiasi altro thread che non genera il deadlock. Il problema è probabilmente alla fine dello stack di chiamate del thread e non ho idea di come rintracciarlo una volta che sono state eseguite circa 300k istruzioni nel tempo di vita del thread (ottenuto dal comando "wt").
bene, allora prova a scoprire perché non sta liberando il blocco in questo percorso di uscita. Ad ogni modo, non è davvero un problema di RE quindi non sono sicuro di cos'altro suggerire.
Sai come posso analizzare un flusso di codice da circa 300k istruzioni in modo pratico? Voglio dire, se dovessi risalire a ogni funzione finirò per analizzare rapidamente i lavori interni di d3d9.dll e i suoi difetti. Ho pensato che se potessi in qualche modo riprodurre il deadlock salvare ogni flusso di esecuzione del thread n.3, avrei potuto confrontare esattamente dove il flusso di esecuzione dal thread difettoso differisce dagli altri. Conosci un modo per farlo? (o pensi che potrebbe mostrare il problema?)
nessuno strumento specifico, ma controlla https://reverseengineering.stackexchange.com/a/2567/60
Hum, mi hai appena dato un'idea per costruirlo in uno script Ida. Potrei tenere traccia dell'output dalla finestra "chiamate di funzione" di ida e tenerlo ricorsivamente per ogni chiamata, salvare un elenco di chiamate, impostare i punti di interruzione e quindi registrare i risultati (in pratica quello che hai detto nel post che hai dato xD) . Grazie mille Igor. Accetto la tua risposta ma forse potresti modificarla con quel link in modo che le persone la vedano meglio?


Questa domanda e risposta è stata tradotta automaticamente dalla lingua inglese. Il contenuto originale è disponibile su stackexchange, che ringraziamo per la licenza cc by-sa 3.0 con cui è distribuito.
Loading...