1 | //===-- sanitizer_deadlock_detector2.cpp ----------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // Deadlock detector implementation based on adjacency lists. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "sanitizer_deadlock_detector_interface.h" |
14 | #include "sanitizer_common.h" |
15 | #include "sanitizer_allocator_internal.h" |
16 | #include "sanitizer_placement_new.h" |
17 | #include "sanitizer_mutex.h" |
18 | |
19 | #if SANITIZER_DEADLOCK_DETECTOR_VERSION == 2 |
20 | |
21 | namespace __sanitizer { |
22 | |
23 | const int kMaxNesting = 64; |
24 | const u32 kNoId = -1; |
25 | const u32 kEndId = -2; |
26 | const int kMaxLink = 8; |
27 | const int kL1Size = 1024; |
28 | const int kL2Size = 1024; |
29 | const int kMaxMutex = kL1Size * kL2Size; |
30 | |
31 | struct Id { |
32 | u32 id; |
33 | u32 seq; |
34 | |
35 | explicit Id(u32 id = 0, u32 seq = 0) |
36 | : id(id) |
37 | , seq(seq) { |
38 | } |
39 | }; |
40 | |
41 | struct Link { |
42 | u32 id; |
43 | u32 seq; |
44 | u32 tid; |
45 | u32 stk0; |
46 | u32 stk1; |
47 | |
48 | explicit Link(u32 id = 0, u32 seq = 0, u32 tid = 0, u32 s0 = 0, u32 s1 = 0) |
49 | : id(id) |
50 | , seq(seq) |
51 | , tid(tid) |
52 | , stk0(s0) |
53 | , stk1(s1) { |
54 | } |
55 | }; |
56 | |
57 | struct DDPhysicalThread { |
58 | DDReport rep; |
59 | bool report_pending; |
60 | bool visited[kMaxMutex]; |
61 | Link pending[kMaxMutex]; |
62 | Link path[kMaxMutex]; |
63 | }; |
64 | |
65 | struct ThreadMutex { |
66 | u32 id; |
67 | u32 stk; |
68 | }; |
69 | |
70 | struct DDLogicalThread { |
71 | u64 ctx; |
72 | ThreadMutex locked[kMaxNesting]; |
73 | int nlocked; |
74 | }; |
75 | |
76 | struct MutexState { |
77 | StaticSpinMutex mtx; |
78 | u32 seq; |
79 | int nlink; |
80 | Link link[kMaxLink]; |
81 | }; |
82 | |
83 | struct DD final : public DDetector { |
84 | explicit DD(const DDFlags *flags); |
85 | |
86 | DDPhysicalThread* CreatePhysicalThread(); |
87 | void DestroyPhysicalThread(DDPhysicalThread *pt); |
88 | |
89 | DDLogicalThread* CreateLogicalThread(u64 ctx); |
90 | void DestroyLogicalThread(DDLogicalThread *lt); |
91 | |
92 | void MutexInit(DDCallback *cb, DDMutex *m); |
93 | void MutexBeforeLock(DDCallback *cb, DDMutex *m, bool wlock); |
94 | void MutexAfterLock(DDCallback *cb, DDMutex *m, bool wlock, |
95 | bool trylock); |
96 | void MutexBeforeUnlock(DDCallback *cb, DDMutex *m, bool wlock); |
97 | void MutexDestroy(DDCallback *cb, DDMutex *m); |
98 | |
99 | DDReport *GetReport(DDCallback *cb); |
100 | |
101 | void CycleCheck(DDPhysicalThread *pt, DDLogicalThread *lt, DDMutex *mtx); |
102 | void Report(DDPhysicalThread *pt, DDLogicalThread *lt, int npath); |
103 | u32 allocateId(DDCallback *cb); |
104 | MutexState *getMutex(u32 id); |
105 | u32 getMutexId(MutexState *m); |
106 | |
107 | DDFlags flags; |
108 | |
109 | MutexState *mutex[kL1Size]; |
110 | |
111 | SpinMutex mtx; |
112 | InternalMmapVector<u32> free_id; |
113 | int id_gen = 0; |
114 | }; |
115 | |
116 | DDetector *DDetector::Create(const DDFlags *flags) { |
117 | (void)flags; |
118 | void *mem = MmapOrDie(sizeof(DD), "deadlock detector" ); |
119 | return new(mem) DD(flags); |
120 | } |
121 | |
122 | DD::DD(const DDFlags *flags) : flags(*flags) { free_id.reserve(1024); } |
123 | |
124 | DDPhysicalThread* DD::CreatePhysicalThread() { |
125 | DDPhysicalThread *pt = (DDPhysicalThread*)MmapOrDie(sizeof(DDPhysicalThread), |
126 | "deadlock detector (physical thread)" ); |
127 | return pt; |
128 | } |
129 | |
130 | void DD::DestroyPhysicalThread(DDPhysicalThread *pt) { |
131 | pt->~DDPhysicalThread(); |
132 | UnmapOrDie(pt, sizeof(DDPhysicalThread)); |
133 | } |
134 | |
135 | DDLogicalThread* DD::CreateLogicalThread(u64 ctx) { |
136 | DDLogicalThread *lt = (DDLogicalThread*)InternalAlloc( |
137 | sizeof(DDLogicalThread)); |
138 | lt->ctx = ctx; |
139 | lt->nlocked = 0; |
140 | return lt; |
141 | } |
142 | |
143 | void DD::DestroyLogicalThread(DDLogicalThread *lt) { |
144 | lt->~DDLogicalThread(); |
145 | InternalFree(lt); |
146 | } |
147 | |
148 | void DD::MutexInit(DDCallback *cb, DDMutex *m) { |
149 | VPrintf(2, "#%llu: DD::MutexInit(%p)\n" , cb->lt->ctx, m); |
150 | m->id = kNoId; |
151 | m->recursion = 0; |
152 | atomic_store(&m->owner, 0, memory_order_relaxed); |
153 | } |
154 | |
155 | MutexState *DD::getMutex(u32 id) { return &mutex[id / kL2Size][id % kL2Size]; } |
156 | |
157 | u32 DD::getMutexId(MutexState *m) { |
158 | for (int i = 0; i < kL1Size; i++) { |
159 | MutexState *tab = mutex[i]; |
160 | if (tab == 0) |
161 | break; |
162 | if (m >= tab && m < tab + kL2Size) |
163 | return i * kL2Size + (m - tab); |
164 | } |
165 | return -1; |
166 | } |
167 | |
168 | u32 DD::allocateId(DDCallback *cb) { |
169 | u32 id = -1; |
170 | SpinMutexLock l(&mtx); |
171 | if (free_id.size() > 0) { |
172 | id = free_id.back(); |
173 | free_id.pop_back(); |
174 | } else { |
175 | CHECK_LT(id_gen, kMaxMutex); |
176 | if ((id_gen % kL2Size) == 0) { |
177 | mutex[id_gen / kL2Size] = (MutexState *)MmapOrDie( |
178 | kL2Size * sizeof(MutexState), "deadlock detector (mutex table)" ); |
179 | } |
180 | id = id_gen++; |
181 | } |
182 | CHECK_LE(id, kMaxMutex); |
183 | VPrintf(3, "#%llu: DD::allocateId assign id %d\n" , cb->lt->ctx, id); |
184 | return id; |
185 | } |
186 | |
187 | void DD::MutexBeforeLock(DDCallback *cb, DDMutex *m, bool wlock) { |
188 | VPrintf(2, "#%llu: DD::MutexBeforeLock(%p, wlock=%d) nlocked=%d\n" , |
189 | cb->lt->ctx, m, wlock, cb->lt->nlocked); |
190 | DDPhysicalThread *pt = cb->pt; |
191 | DDLogicalThread *lt = cb->lt; |
192 | |
193 | uptr owner = atomic_load(&m->owner, memory_order_relaxed); |
194 | if (owner == (uptr)cb->lt) { |
195 | VPrintf(3, "#%llu: DD::MutexBeforeLock recursive\n" , |
196 | cb->lt->ctx); |
197 | return; |
198 | } |
199 | |
200 | CHECK_LE(lt->nlocked, kMaxNesting); |
201 | |
202 | // FIXME(dvyukov): don't allocate id if lt->nlocked == 0? |
203 | if (m->id == kNoId) |
204 | m->id = allocateId(cb); |
205 | |
206 | ThreadMutex *tm = <->locked[lt->nlocked++]; |
207 | tm->id = m->id; |
208 | if (flags.second_deadlock_stack) |
209 | tm->stk = cb->Unwind(); |
210 | if (lt->nlocked == 1) { |
211 | VPrintf(3, "#%llu: DD::MutexBeforeLock first mutex\n" , |
212 | cb->lt->ctx); |
213 | return; |
214 | } |
215 | |
216 | bool added = false; |
217 | MutexState *mtx = getMutex(m->id); |
218 | for (int i = 0; i < lt->nlocked - 1; i++) { |
219 | u32 id1 = lt->locked[i].id; |
220 | u32 stk1 = lt->locked[i].stk; |
221 | MutexState *mtx1 = getMutex(id1); |
222 | SpinMutexLock l(&mtx1->mtx); |
223 | if (mtx1->nlink == kMaxLink) { |
224 | // FIXME(dvyukov): check stale links |
225 | continue; |
226 | } |
227 | int li = 0; |
228 | for (; li < mtx1->nlink; li++) { |
229 | Link *link = &mtx1->link[li]; |
230 | if (link->id == m->id) { |
231 | if (link->seq != mtx->seq) { |
232 | link->seq = mtx->seq; |
233 | link->tid = lt->ctx; |
234 | link->stk0 = stk1; |
235 | link->stk1 = cb->Unwind(); |
236 | added = true; |
237 | VPrintf(3, "#%llu: DD::MutexBeforeLock added %d->%d link\n" , |
238 | cb->lt->ctx, getMutexId(mtx1), m->id); |
239 | } |
240 | break; |
241 | } |
242 | } |
243 | if (li == mtx1->nlink) { |
244 | // FIXME(dvyukov): check stale links |
245 | Link *link = &mtx1->link[mtx1->nlink++]; |
246 | link->id = m->id; |
247 | link->seq = mtx->seq; |
248 | link->tid = lt->ctx; |
249 | link->stk0 = stk1; |
250 | link->stk1 = cb->Unwind(); |
251 | added = true; |
252 | VPrintf(3, "#%llu: DD::MutexBeforeLock added %d->%d link\n" , |
253 | cb->lt->ctx, getMutexId(mtx1), m->id); |
254 | } |
255 | } |
256 | |
257 | if (!added || mtx->nlink == 0) { |
258 | VPrintf(3, "#%llu: DD::MutexBeforeLock don't check\n" , |
259 | cb->lt->ctx); |
260 | return; |
261 | } |
262 | |
263 | CycleCheck(pt, lt, m); |
264 | } |
265 | |
266 | void DD::MutexAfterLock(DDCallback *cb, DDMutex *m, bool wlock, |
267 | bool trylock) { |
268 | VPrintf(2, "#%llu: DD::MutexAfterLock(%p, wlock=%d, try=%d) nlocked=%d\n" , |
269 | cb->lt->ctx, m, wlock, trylock, cb->lt->nlocked); |
270 | DDLogicalThread *lt = cb->lt; |
271 | |
272 | uptr owner = atomic_load(&m->owner, memory_order_relaxed); |
273 | if (owner == (uptr)cb->lt) { |
274 | VPrintf(3, "#%llu: DD::MutexAfterLock recursive\n" , cb->lt->ctx); |
275 | CHECK(wlock); |
276 | m->recursion++; |
277 | return; |
278 | } |
279 | CHECK_EQ(owner, 0); |
280 | if (wlock) { |
281 | VPrintf(3, "#%llu: DD::MutexAfterLock set owner\n" , cb->lt->ctx); |
282 | CHECK_EQ(m->recursion, 0); |
283 | m->recursion = 1; |
284 | atomic_store(&m->owner, (uptr)cb->lt, memory_order_relaxed); |
285 | } |
286 | |
287 | if (!trylock) |
288 | return; |
289 | |
290 | CHECK_LE(lt->nlocked, kMaxNesting); |
291 | if (m->id == kNoId) |
292 | m->id = allocateId(cb); |
293 | ThreadMutex *tm = <->locked[lt->nlocked++]; |
294 | tm->id = m->id; |
295 | if (flags.second_deadlock_stack) |
296 | tm->stk = cb->Unwind(); |
297 | } |
298 | |
299 | void DD::MutexBeforeUnlock(DDCallback *cb, DDMutex *m, bool wlock) { |
300 | VPrintf(2, "#%llu: DD::MutexBeforeUnlock(%p, wlock=%d) nlocked=%d\n" , |
301 | cb->lt->ctx, m, wlock, cb->lt->nlocked); |
302 | DDLogicalThread *lt = cb->lt; |
303 | |
304 | uptr owner = atomic_load(&m->owner, memory_order_relaxed); |
305 | if (owner == (uptr)cb->lt) { |
306 | VPrintf(3, "#%llu: DD::MutexBeforeUnlock recursive\n" , cb->lt->ctx); |
307 | if (--m->recursion > 0) |
308 | return; |
309 | VPrintf(3, "#%llu: DD::MutexBeforeUnlock reset owner\n" , cb->lt->ctx); |
310 | atomic_store(&m->owner, 0, memory_order_relaxed); |
311 | } |
312 | CHECK_NE(m->id, kNoId); |
313 | int last = lt->nlocked - 1; |
314 | for (int i = last; i >= 0; i--) { |
315 | if (cb->lt->locked[i].id == m->id) { |
316 | lt->locked[i] = lt->locked[last]; |
317 | lt->nlocked--; |
318 | break; |
319 | } |
320 | } |
321 | } |
322 | |
323 | void DD::MutexDestroy(DDCallback *cb, DDMutex *m) { |
324 | VPrintf(2, "#%llu: DD::MutexDestroy(%p)\n" , |
325 | cb->lt->ctx, m); |
326 | DDLogicalThread *lt = cb->lt; |
327 | |
328 | if (m->id == kNoId) |
329 | return; |
330 | |
331 | // Remove the mutex from lt->locked if there. |
332 | int last = lt->nlocked - 1; |
333 | for (int i = last; i >= 0; i--) { |
334 | if (lt->locked[i].id == m->id) { |
335 | lt->locked[i] = lt->locked[last]; |
336 | lt->nlocked--; |
337 | break; |
338 | } |
339 | } |
340 | |
341 | // Clear and invalidate the mutex descriptor. |
342 | { |
343 | MutexState *mtx = getMutex(m->id); |
344 | SpinMutexLock l(&mtx->mtx); |
345 | mtx->seq++; |
346 | mtx->nlink = 0; |
347 | } |
348 | |
349 | // Return id to cache. |
350 | { |
351 | SpinMutexLock l(&mtx); |
352 | free_id.push_back(m->id); |
353 | } |
354 | } |
355 | |
356 | void DD::CycleCheck(DDPhysicalThread *pt, DDLogicalThread *lt, |
357 | DDMutex *m) { |
358 | internal_memset(pt->visited, 0, sizeof(pt->visited)); |
359 | int npath = 0; |
360 | int npending = 0; |
361 | { |
362 | MutexState *mtx = getMutex(m->id); |
363 | SpinMutexLock l(&mtx->mtx); |
364 | for (int li = 0; li < mtx->nlink; li++) |
365 | pt->pending[npending++] = mtx->link[li]; |
366 | } |
367 | while (npending > 0) { |
368 | Link link = pt->pending[--npending]; |
369 | if (link.id == kEndId) { |
370 | npath--; |
371 | continue; |
372 | } |
373 | if (pt->visited[link.id]) |
374 | continue; |
375 | MutexState *mtx1 = getMutex(link.id); |
376 | SpinMutexLock l(&mtx1->mtx); |
377 | if (mtx1->seq != link.seq) |
378 | continue; |
379 | pt->visited[link.id] = true; |
380 | if (mtx1->nlink == 0) |
381 | continue; |
382 | pt->path[npath++] = link; |
383 | pt->pending[npending++] = Link(kEndId); |
384 | if (link.id == m->id) |
385 | return Report(pt, lt, npath); // Bingo! |
386 | for (int li = 0; li < mtx1->nlink; li++) { |
387 | Link *link1 = &mtx1->link[li]; |
388 | // MutexState *mtx2 = getMutex(link->id); |
389 | // FIXME(dvyukov): fast seq check |
390 | // FIXME(dvyukov): fast nlink != 0 check |
391 | // FIXME(dvyukov): fast pending check? |
392 | // FIXME(dvyukov): npending can be larger than kMaxMutex |
393 | pt->pending[npending++] = *link1; |
394 | } |
395 | } |
396 | } |
397 | |
398 | void DD::Report(DDPhysicalThread *pt, DDLogicalThread *lt, int npath) { |
399 | DDReport *rep = &pt->rep; |
400 | rep->n = npath; |
401 | for (int i = 0; i < npath; i++) { |
402 | Link *link = &pt->path[i]; |
403 | Link *link0 = &pt->path[i ? i - 1 : npath - 1]; |
404 | rep->loop[i].thr_ctx = link->tid; |
405 | rep->loop[i].mtx_ctx0 = link0->id; |
406 | rep->loop[i].mtx_ctx1 = link->id; |
407 | rep->loop[i].stk[0] = flags.second_deadlock_stack ? link->stk0 : 0; |
408 | rep->loop[i].stk[1] = link->stk1; |
409 | } |
410 | pt->report_pending = true; |
411 | } |
412 | |
413 | DDReport *DD::GetReport(DDCallback *cb) { |
414 | if (!cb->pt->report_pending) |
415 | return 0; |
416 | cb->pt->report_pending = false; |
417 | return &cb->pt->rep; |
418 | } |
419 | |
420 | } // namespace __sanitizer |
421 | #endif // #if SANITIZER_DEADLOCK_DETECTOR_VERSION == 2 |
422 | |