2 using System
.Collections
.Generic
;
3 using System
.Diagnostics
;
5 using System
.Globalization
;
6 using System
.Runtime
.InteropServices
;
18 public struct LockRecord
{
22 public LockRecord (SimLock lk
, string frame
) {
28 public class SimThread
31 List
<LockRecord
> locks
= new List
<LockRecord
> ();
33 public SimThread (int t
)
38 public bool HoldsLock (SimLock lk
) {
39 foreach (var l
in locks
) {
47 public int HoldCount (SimLock lk
) {
49 foreach (var l
in locks
)
55 public void Lock (SimLock lk
, string frame
) {
56 foreach (LockRecord lr
in locks
) {
57 if (lk
.WarnAbout (this, lr
.lk
))
58 Console
.WriteLine ("WARNING: tried to acquire lock {0} at {1} while holding {2} at {3}: {4}", lk
, frame
, lr
.lk
, lr
.frame
, lk
.GetWarningMessage (this, lr
.lk
));
59 else if (!lk
.IsValid (this, lr
.lk
))
60 Console
.WriteLine ("ERROR: tried to acquire lock {0} at {1} while holding {2} at {3}: {4}", lk
, frame
, lr
.lk
, lr
.frame
, lk
.GetErrorMessage (this, lr
.lk
));
62 locks
.Add (new LockRecord (lk
, frame
));
65 public void Release (SimLock lk
, string frame
) {
66 if (locks
.Count
== 0) {
67 Console
.WriteLine ("ERROR: released lock {0} at {1} while holding no locks!", lk
, frame
);
70 LockRecord top
= locks
[locks
.Count
- 1];
71 if (top
.lk
!= lk
&& !(lk
.IsGlobalLock
&& HoldCount (lk
) > 1)) {
72 Console
.WriteLine ("WARNING: released lock {0} at {1} out of order with {2} at {3}!", lk
, frame
, top
.lk
, top
.frame
);
74 for (int i
= locks
.Count
-1; i
>= 0; --i
) {
75 if (locks
[i
].lk
== lk
) {
87 Can be acquired at any point regardless of which locks are taken or not.
88 No other locks can be acquired or released while holding a simple lock.
89 Reentrancy is not recomended. (warning)
90 Simple locks are leaf locks on the lock lattice.
93 Must respect locking order, which form a lattice.
94 IOW, to take a given lock, only it's parents might have been taken.
96 Locks around resources count as separate instances of the hierarchy.
99 Must respect locking order.
100 Must be the at the botton of the locking lattice.
101 Can be taken out-of-order by other locks given that it was previously acquired.
102 Adding global locks is not to be taken lightly.
104 The current lock hierarchy:
106 domain lock (complex)
107 domain jit lock (complex)
112 You can take the loader lock without holding a domain lock.
113 You can take the domain load while holding the loader lock
114 You cannot take the loader lock if only the domain lock is held.
115 You cannot take a domain lock while holding the lock to another domain.
120 We have a few known ok violation. We need a way to whitelist them.
124 ERROR: tried to acquire lock DomainLock at mono_domain_code_reserve_align while holding DomainLock at mono_class_create_runtime_vtable: Hierarchy violation.
125 This is triggered when building the vtable of a non-root domain and fetching a vtable trampoline for an offset that has not been built. We'll take the root
126 domain lock while holding the other one.
127 This is ok since we never allow locking to have in the other direction, IOW, the root-domain lock is one level down from the other domain-locks.
129 WARNING: tried to acquire lock ImageDataLock at mono_image_init_name_cache while holding ImageDataLock at mono_class_from_name
130 WARNING: tried to acquire lock ImageDataLock at mono_image_init_name_cache while holding ImageDataLock at mono_image_add_to_name_cache
131 Both of those happen when filling up the name_cache, as it needs to alloc image memory.
132 This one is fixable by spliting mono_image_init_name_cache into a locked and an unlocked variants and calling them appropriatedly.
141 DomainAssembliesLock
,
142 DomainJitCodeHashLock
,
147 LoaderGlobalDataLock
,
156 public SimLock (Lock kind
, int id
) {
161 static int GetLockOrder (Lock kind
) {
163 case Lock
.LoaderLock
:
165 case Lock
.DomainLock
:
167 case Lock
.DomainJitCodeHashLock
:
168 case Lock
.MarshalLock
:
175 bool IsParent (SimLock other
) {
176 return GetLockOrder (kind
) > GetLockOrder (other
.kind
);
179 public bool IsSimpleLock
{
180 get { return GetLockOrder (kind) == 3; }
183 public bool IsGlobalLock
{
184 get { return kind == Lock.LoaderLock; }
187 public bool IsResursiveLock
{
188 get { return kind == Lock.LoaderLock || kind == Lock.DomainLock; }
191 /*locked is already owned by the thread, 'this' is the new one*/
192 bool Compare (SimThread thread
, SimLock locked
, out bool isWarning
, out string msg
)
197 if (locked
!= this) {
198 if (!IsParent (locked
)) {
199 if (IsGlobalLock
) { /*acquiring a global lock*/
200 if (!thread
.HoldsLock (this)) { /*does the thread alread hold it?*/
201 msg
= "Acquired a global lock after a regular lock without having it before.";
205 msg
= "Hierarchy violation.";
209 } else if (IsSimpleLock
) {
210 msg
= "Avoid taking simple locks recursively";
218 public bool IsValid (SimThread thread
, SimLock locked
) {
221 return Compare (thread
, locked
, out warn
, out msg
);
224 public bool WarnAbout (SimThread thread
, SimLock locked
) {
227 Compare (thread
, locked
, out warn
, out msg
);
231 public string GetWarningMessage (SimThread thread
, SimLock locked
) {
234 Compare (thread
, locked
, out warn
, out msg
);
235 return warn
? msg
: null;
238 public string GetErrorMessage (SimThread thread
, SimLock locked
) {
241 bool res
= Compare (thread
, locked
, out warn
, out msg
);
242 return !res
&& !warn
? msg
: null;
245 public override string ToString () {
247 case Lock
.LoaderLock
:
249 case Lock
.AssemblyBindingLock
:
250 case Lock
.MarshalLock
:
251 case Lock
.ClassesLock
:
252 case Lock
.LoaderGlobalDataLock
:
253 case Lock
.ThreadsLock
:
254 return String
.Format ("{0}", kind
);
256 case Lock
.ImageDataLock
:
257 case Lock
.DomainLock
:
258 case Lock
.DomainAssembliesLock
:
259 case Lock
.DomainJitCodeHashLock
:
260 return String
.Format ("{0}[{1}]", kind
, id
);
262 return String
.Format ("Unknown({0})[{1}]", kind
, id
);
267 public class LockSimulator
269 static Dictionary
<int, SimThread
> threads
= new Dictionary
<int, SimThread
> ();
270 static Dictionary
<int, SimLock
> locks
= new Dictionary
<int, SimLock
> ();
274 public LockSimulator (SymbolTable s
) { this.syms = s; }
276 SimLock
GetLock (Trace t
) {
277 if (locks
.ContainsKey (t
.lockPtr
))
278 return locks
[t
.lockPtr
];
280 return locks
[t
.lockPtr
] = new SimLock (t
.lockKind
, t
.lockPtr
);
284 SimThread
GetThread (Trace t
) {
285 if (threads
.ContainsKey (t
.thread
))
286 return threads
[t
.thread
];
288 return threads
[t
.thread
] = new SimThread (t
.thread
);
291 public void PlayBack (IEnumerable
<Trace
> traces
) {
292 foreach (var t
in traces
) {
293 SimThread thread
= GetThread (t
);
294 SimLock lk
= GetLock (t
);
295 string frame
= t
.GetUsefullTopTrace (this.syms
);
298 case Record
.MustNotHoldAny
:
299 case Record
.MustNotHoldOne
:
300 case Record
.MustHoldOne
:
301 throw new Exception ("not supported");
302 case Record
.LockAcquired
:
303 thread
.Lock (lk
, frame
);
305 case Record
.LockReleased
:
306 thread
.Release (lk
, frame
);
309 throw new Exception ("Invalid trace record: "+t
.record
);
317 public Record record
;
318 public Lock lockKind
;
322 static readonly string[] BAD_FRAME_METHODS
= new string[] {
324 "mono_loader_unlock",
330 "mono_locks_lock_acquired",
331 "mono_locks_lock_released",
333 "mono_threads_unlock",
335 "mono_domain_unlock",
338 public Trace (string[] fields
) {
339 thread
= fields
[0].ParseHex ();
340 record
= (Record
)fields
[1].ParseDec ();
341 lockKind
= (Lock
)fields
[2].ParseDec ();
342 lockPtr
= fields
[3].ParseHex ();
343 frames
= new int [fields
.Length
- 4];
344 for (int i
= 0; i
< frames
.Length
; ++i
)
345 frames
[i
] = fields
[i
+ 4].ParseHex ();
348 public void Dump (SymbolTable table
) {
349 Console
.WriteLine ("{0:x} {1} {2} {3:x}", thread
, record
, lockKind
, lockPtr
);
350 for (int i
= 0; i
< frames
.Length
; ++i
)
351 Console
.WriteLine ("\t{0}", table
.Translate (frames
[i
]));
354 public string GetUsefullTopTrace (SymbolTable syms
) {
355 for (int i
= 0; i
< frames
.Length
; ++i
) {
356 string str
= syms
.Translate (frames
[i
]);
358 for (int j
= 0; j
< BAD_FRAME_METHODS
.Length
; ++j
) {
359 if (str
.IndexOf (BAD_FRAME_METHODS
[j
]) >= 0) {
371 public class Symbol
: IComparable
<Symbol
>
377 public Symbol (int o
, int size
, string n
) {
383 public int CompareTo(Symbol other
) {
384 return offset
- other
.offset
;
387 public void AdjustSize (Symbol next
) {
388 size
= next
.offset
- this.offset
;
392 public interface SymbolTable
{
393 string Translate (int offset
);
396 public class OsxSymbolTable
: SymbolTable
400 const int MAX_FUNC_SIZE
= 0x20000;
402 public OsxSymbolTable (string binary
) {
406 void Load (string binary
) {
407 ProcessStartInfo psi
= new ProcessStartInfo ("gobjdump", "-t "+binary
);
408 psi
.UseShellExecute
= false;
409 psi
.RedirectStandardOutput
= true;
411 var proc
= Process
.Start (psi
);
412 var list
= new List
<Symbol
> ();
414 while ((line
= proc
.StandardOutput
.ReadLine ()) != null) {
415 string[] fields
= line
.Split(new char[] {' ', '\t'}
, StringSplitOptions
.RemoveEmptyEntries
);
416 if (fields
.Length
< 7)
419 if (!fields
[3].Equals ("FUN"))
422 int offset
= fields
[0].ParseHex ();
423 string name
= fields
[6];
424 if (name
.StartsWith ("_"))
425 name
= name
.Substring (1);
428 list
.Add (new Symbol (offset
, 0, name
));
430 table
= new Symbol
[list
.Count
];
431 list
.CopyTo (table
, 0);
433 for (int i
= 1; i
< table
.Length
; ++i
) {
434 table
[i
- 1].AdjustSize (table
[i
]);
438 public string Translate (int offset
) {
440 int res
= Array
.BinarySearch (table
, new Symbol (offset
, 0, null));
442 return table
[res
].name
;
445 if (res
>= table
.Length
)
446 sym
= table
[table
.Length
- 1];
448 sym
= table
[res
- 1];
452 int size
= Math
.Max (sym
.size
, 10);
453 if (offset
- sym
.offset
< size
)
456 return String
.Format ("[{0:x}]", offset
);
460 public class LinuxSymbolTable
: SymbolTable
464 const int MAX_FUNC_SIZE
= 0x20000;
466 public LinuxSymbolTable (string binary
) {
470 void Load (string binary
) {
471 ProcessStartInfo psi
= new ProcessStartInfo ("objdump", "-t "+binary
);
472 psi
.UseShellExecute
= false;
473 psi
.RedirectStandardOutput
= true;
475 var proc
= Process
.Start (psi
);
476 var list
= new List
<Symbol
> ();
478 while ((line
= proc
.StandardOutput
.ReadLine ()) != null) {
479 string[] fields
= line
.Split(new char[] {' ', '\t'}
, StringSplitOptions
.RemoveEmptyEntries
);
481 if (fields
.Length
< 6)
483 if (fields
[3] != ".text" || fields
[2] != "F")
486 int offset
= fields
[0].ParseHex ();
487 int size
= fields
[4].ParseHex ();
488 string name
= fields
[fields
.Length
- 1];
490 list
.Add (new Symbol (offset
, size
, name
));
492 table
= new Symbol
[list
.Count
];
493 list
.CopyTo (table
, 0);
497 public string Translate (int offset
) {
499 int res
= Array
.BinarySearch (table
, new Symbol (offset
, 0, null));
501 return table
[res
].name
;
504 if (res
>= table
.Length
)
505 sym
= table
[table
.Length
- 1];
507 sym
= table
[res
- 1];
509 if (sym
!= null && offset
- sym
.offset
< MAX_FUNC_SIZE
)
511 return String
.Format ("[{0:x}]", offset
);
515 public class TraceDecoder
519 public TraceDecoder (string file
) {
523 public IEnumerable
<Trace
> GetTraces () {
524 using (StreamReader reader
= new StreamReader (file
)) {
526 while ((line
= reader
.ReadLine ()) != null) {
527 string[] fields
= line
.Split(new char[] {','}
, StringSplitOptions
.RemoveEmptyEntries
);
528 if (fields
.Length
>= 7) {
529 yield return new Trace (fields
);
539 static extern int uname (IntPtr buf
);
544 IntPtr buf
= Marshal
.AllocHGlobal (8192);
545 if (uname (buf
) == 0) {
546 string os
= Marshal
.PtrToStringAnsi (buf
);
547 isOsx
= os
== "Darwin";
550 Marshal
.FreeHGlobal (buf
);
555 static void Main (string[] args
) {
557 if (args
.Length
!= 2) {
558 Console
.WriteLine ("usage: LockTracerDecoder.exe /path/to/mono /path/to/locks.pid");
562 syms
= new OsxSymbolTable (args
[0]);
564 syms
= new LinuxSymbolTable (args
[0]);
566 var decoder
= new TraceDecoder (args
[1]);
567 var sim
= new LockSimulator (syms
);
568 sim
.PlayBack (decoder
.GetTraces ());
572 public static class Utils
574 public static int ParseHex (this string number
) {
575 while (number
.Length
> 1 && (number
[0] == '0' || number
[0] == 'x' || number
[0] == 'X'))
576 number
= number
.Substring (1);
577 return int.Parse (number
, NumberStyles
.HexNumber
);
580 public static int ParseDec (this string number
) {
581 while (number
.Length
> 1 && number
[0] == '0')
582 number
= number
.Substring (1);
583 return int.Parse (number
);