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,
155 public SimLock (Lock kind, int id) {
160 static int GetLockOrder (Lock kind) {
162 case Lock.LoaderLock:
164 case Lock.DomainLock:
166 case Lock.DomainJitCodeHashLock:
167 case Lock.MarshalLock:
174 bool IsParent (SimLock other) {
175 return GetLockOrder (kind) > GetLockOrder (other.kind);
178 public bool IsSimpleLock {
179 get { return GetLockOrder (kind) == 3; }
182 public bool IsGlobalLock {
183 get { return kind == Lock.LoaderLock; }
186 public bool IsResursiveLock {
187 get { return kind == Lock.LoaderLock || kind == Lock.DomainLock; }
190 /*locked is already owned by the thread, 'this' is the new one*/
191 bool Compare (SimThread thread, SimLock locked, out bool isWarning, out string msg)
196 if (locked != this) {
197 if (!IsParent (locked)) {
198 if (IsGlobalLock) { /*acquiring a global lock*/
199 if (!thread.HoldsLock (this)) { /*does the thread alread hold it?*/
200 msg = "Acquired a global lock after a regular lock without having it before.";
204 msg = "Hierarchy violation.";
208 } else if (IsSimpleLock) {
209 msg = "Avoid taking simple locks recursively";
217 public bool IsValid (SimThread thread, SimLock locked) {
220 return Compare (thread, locked, out warn, out msg);
223 public bool WarnAbout (SimThread thread, SimLock locked) {
226 Compare (thread, locked, out warn, out msg);
230 public string GetWarningMessage (SimThread thread, SimLock locked) {
233 Compare (thread, locked, out warn, out msg);
234 return warn ? msg : null;
237 public string GetErrorMessage (SimThread thread, SimLock locked) {
240 bool res = Compare (thread, locked, out warn, out msg);
241 return !res && !warn ? msg : null;
244 public override string ToString () {
245 return String.Format ("{0}", kind);
249 public class LockSimulator
251 static Dictionary <int, SimThread> threads = new Dictionary <int, SimThread> ();
252 static Dictionary <int, SimLock> locks = new Dictionary <int, SimLock> ();
256 public LockSimulator (SymbolTable s) { this.syms = s; }
258 SimLock GetLock (Trace t) {
259 if (locks.ContainsKey (t.lockPtr))
260 return locks [t.lockPtr];
262 return locks [t.lockPtr] = new SimLock (t.lockKind, t.lockPtr);
266 SimThread GetThread (Trace t) {
267 if (threads.ContainsKey (t.thread))
268 return threads [t.thread];
270 return threads [t.thread] = new SimThread (t.thread);
273 public void PlayBack (IEnumerable<Trace> traces) {
274 foreach (var t in traces) {
275 SimThread thread = GetThread (t);
276 SimLock lk = GetLock (t);
277 string frame = t.GetUsefullTopTrace (this.syms);
280 case Record.MustNotHoldAny:
281 case Record.MustNotHoldOne:
282 case Record.MustHoldOne:
283 throw new Exception ("not supported");
284 case Record.LockAcquired:
285 thread.Lock (lk, frame);
287 case Record.LockReleased:
288 thread.Release (lk, frame);
291 throw new Exception ("Invalid trace record: "+t.record);
299 public Record record;
300 public Lock lockKind;
304 static readonly string[] BAD_FRAME_METHODS = new string[] {
306 "mono_loader_unlock",
312 "mono_locks_lock_acquired",
313 "mono_locks_lock_released",
316 public Trace (string[] fields) {
317 thread = fields [0].ParseHex ();
318 record = (Record)fields [1].ParseDec ();
319 lockKind = (Lock)fields [2].ParseDec ();
320 lockPtr = fields [3].ParseHex ();
321 frames = new int [fields.Length - 4];
322 for (int i = 0; i < frames.Length; ++i)
323 frames [i] = fields [i + 4].ParseHex ();
326 public void Dump (SymbolTable table) {
327 Console.WriteLine ("{0:x} {1} {2} {3:x}", thread, record, lockKind, lockPtr);
328 for (int i = 0; i < frames.Length; ++i)
329 Console.WriteLine ("\t{0}", table.Translate (frames [i]));
332 public string GetUsefullTopTrace (SymbolTable syms) {
333 for (int i = 0; i < frames.Length; ++i) {
334 string str = syms.Translate (frames [i]);
336 for (int j = 0; j < BAD_FRAME_METHODS.Length; ++j) {
337 if (str.IndexOf (BAD_FRAME_METHODS [j]) >= 0) {
349 public class Symbol : IComparable<Symbol>
355 public Symbol (int o, int size, string n) {
361 public int CompareTo(Symbol other) {
362 return offset - other.offset;
365 public void AdjustSize (Symbol next) {
366 size = next.offset - this.offset;
370 public interface SymbolTable {
371 string Translate (int offset);
374 public class OsxSymbolTable : SymbolTable
378 const int MAX_FUNC_SIZE = 0x20000;
380 public OsxSymbolTable (string binary) {
384 void Load (string binary) {
385 ProcessStartInfo psi = new ProcessStartInfo ("gobjdump", "-t "+binary);
386 psi.UseShellExecute = false;
387 psi.RedirectStandardOutput = true;
389 var proc = Process.Start (psi);
390 var list = new List<Symbol> ();
392 while ((line = proc.StandardOutput.ReadLine ()) != null) {
393 string[] fields = line.Split(new char[] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries);
394 if (fields.Length < 7)
397 if (!fields [3].Equals ("FUN"))
400 int offset = fields [0].ParseHex ();
401 string name = fields [6];
402 if (name.StartsWith ("_"))
403 name = name.Substring (1);
406 list.Add (new Symbol (offset, 0, name));
408 table = new Symbol [list.Count];
409 list.CopyTo (table, 0);
411 for (int i = 1; i < table.Length; ++i) {
412 table [i - 1].AdjustSize (table [i]);
416 public string Translate (int offset) {
418 int res = Array.BinarySearch (table, new Symbol (offset, 0, null));
420 return table [res].name;
423 if (res >= table.Length)
424 sym = table [table.Length - 1];
426 sym = table [res - 1];
430 int size = Math.Max (sym.size, 10);
431 if (offset - sym.offset < size)
434 return String.Format ("[{0:x}]", offset);
438 public class LinuxSymbolTable : SymbolTable
442 const int MAX_FUNC_SIZE = 0x20000;
444 public LinuxSymbolTable (string binary) {
448 void Load (string binary) {
449 ProcessStartInfo psi = new ProcessStartInfo ("objdump", "-t "+binary);
450 psi.UseShellExecute = false;
451 psi.RedirectStandardOutput = true;
453 var proc = Process.Start (psi);
454 var list = new List<Symbol> ();
456 while ((line = proc.StandardOutput.ReadLine ()) != null) {
457 string[] fields = line.Split(new char[] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries);
459 if (fields.Length < 6)
461 if (fields [3] != ".text" || fields [2] != "F")
464 int offset = fields [0].ParseHex ();
465 int size = fields [4].ParseHex ();
466 string name = fields [fields.Length - 1];
468 list.Add (new Symbol (offset, size, name));
470 table = new Symbol [list.Count];
471 list.CopyTo (table, 0);
475 public string Translate (int offset) {
477 int res = Array.BinarySearch (table, new Symbol (offset, 0, null));
479 return table [res].name;
482 if (res >= table.Length)
483 sym = table [table.Length - 1];
485 sym = table [res - 1];
487 if (sym != null && offset - sym.offset < MAX_FUNC_SIZE)
489 return String.Format ("[{0:x}]", offset);
493 public class TraceDecoder
497 public TraceDecoder (string file) {
501 public IEnumerable<Trace> GetTraces () {
502 using (StreamReader reader = new StreamReader (file)) {
504 while ((line = reader.ReadLine ()) != null) {
505 string[] fields = line.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
506 if (fields.Length >= 7) {
507 yield return new Trace (fields);
517 static extern int uname (IntPtr buf);
522 IntPtr buf = Marshal.AllocHGlobal (8192);
523 if (uname (buf) == 0) {
524 string os = Marshal.PtrToStringAnsi (buf);
525 isOsx = os == "Darwin";
528 Marshal.FreeHGlobal (buf);
533 static void Main (string[] args) {
535 if (args.Length != 2) {
536 Console.WriteLine ("usage: LockTracerDecoder.exe /path/to/mono /path/to/locks.pid");
540 syms = new OsxSymbolTable (args [0]);
542 syms = new LinuxSymbolTable (args [0]);
544 var decoder = new TraceDecoder (args [1]);
545 var sim = new LockSimulator (syms);
546 sim.PlayBack (decoder.GetTraces ());
550 public static class Utils
552 public static int ParseHex (this string number) {
553 while (number.Length > 1 && (number [0] == '0' || number [0] == 'x' || number [0] == 'X'))
554 number = number.Substring (1);
555 return int.Parse (number, NumberStyles.HexNumber);
558 public static int ParseDec (this string number) {
559 while (number.Length > 1 && number [0] == '0')
560 number = number.Substring (1);
561 return int.Parse (number);