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 () {
246 return String.Format ("{0}", kind);
250 public class LockSimulator
252 static Dictionary <int, SimThread> threads = new Dictionary <int, SimThread> ();
253 static Dictionary <int, SimLock> locks = new Dictionary <int, SimLock> ();
257 public LockSimulator (SymbolTable s) { this.syms = s; }
259 SimLock GetLock (Trace t) {
260 if (locks.ContainsKey (t.lockPtr))
261 return locks [t.lockPtr];
263 return locks [t.lockPtr] = new SimLock (t.lockKind, t.lockPtr);
267 SimThread GetThread (Trace t) {
268 if (threads.ContainsKey (t.thread))
269 return threads [t.thread];
271 return threads [t.thread] = new SimThread (t.thread);
274 public void PlayBack (IEnumerable<Trace> traces) {
275 foreach (var t in traces) {
276 SimThread thread = GetThread (t);
277 SimLock lk = GetLock (t);
278 string frame = t.GetUsefullTopTrace (this.syms);
281 case Record.MustNotHoldAny:
282 case Record.MustNotHoldOne:
283 case Record.MustHoldOne:
284 throw new Exception ("not supported");
285 case Record.LockAcquired:
286 thread.Lock (lk, frame);
288 case Record.LockReleased:
289 thread.Release (lk, frame);
292 throw new Exception ("Invalid trace record: "+t.record);
300 public Record record;
301 public Lock lockKind;
305 static readonly string[] BAD_FRAME_METHODS = new string[] {
307 "mono_loader_unlock",
313 "mono_locks_lock_acquired",
314 "mono_locks_lock_released",
316 "mono_threads_unlock",
319 public Trace (string[] fields) {
320 thread = fields [0].ParseHex ();
321 record = (Record)fields [1].ParseDec ();
322 lockKind = (Lock)fields [2].ParseDec ();
323 lockPtr = fields [3].ParseHex ();
324 frames = new int [fields.Length - 4];
325 for (int i = 0; i < frames.Length; ++i)
326 frames [i] = fields [i + 4].ParseHex ();
329 public void Dump (SymbolTable table) {
330 Console.WriteLine ("{0:x} {1} {2} {3:x}", thread, record, lockKind, lockPtr);
331 for (int i = 0; i < frames.Length; ++i)
332 Console.WriteLine ("\t{0}", table.Translate (frames [i]));
335 public string GetUsefullTopTrace (SymbolTable syms) {
336 for (int i = 0; i < frames.Length; ++i) {
337 string str = syms.Translate (frames [i]);
339 for (int j = 0; j < BAD_FRAME_METHODS.Length; ++j) {
340 if (str.IndexOf (BAD_FRAME_METHODS [j]) >= 0) {
352 public class Symbol : IComparable<Symbol>
358 public Symbol (int o, int size, string n) {
364 public int CompareTo(Symbol other) {
365 return offset - other.offset;
368 public void AdjustSize (Symbol next) {
369 size = next.offset - this.offset;
373 public interface SymbolTable {
374 string Translate (int offset);
377 public class OsxSymbolTable : SymbolTable
381 const int MAX_FUNC_SIZE = 0x20000;
383 public OsxSymbolTable (string binary) {
387 void Load (string binary) {
388 ProcessStartInfo psi = new ProcessStartInfo ("gobjdump", "-t "+binary);
389 psi.UseShellExecute = false;
390 psi.RedirectStandardOutput = true;
392 var proc = Process.Start (psi);
393 var list = new List<Symbol> ();
395 while ((line = proc.StandardOutput.ReadLine ()) != null) {
396 string[] fields = line.Split(new char[] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries);
397 if (fields.Length < 7)
400 if (!fields [3].Equals ("FUN"))
403 int offset = fields [0].ParseHex ();
404 string name = fields [6];
405 if (name.StartsWith ("_"))
406 name = name.Substring (1);
409 list.Add (new Symbol (offset, 0, name));
411 table = new Symbol [list.Count];
412 list.CopyTo (table, 0);
414 for (int i = 1; i < table.Length; ++i) {
415 table [i - 1].AdjustSize (table [i]);
419 public string Translate (int offset) {
421 int res = Array.BinarySearch (table, new Symbol (offset, 0, null));
423 return table [res].name;
426 if (res >= table.Length)
427 sym = table [table.Length - 1];
429 sym = table [res - 1];
433 int size = Math.Max (sym.size, 10);
434 if (offset - sym.offset < size)
437 return String.Format ("[{0:x}]", offset);
441 public class LinuxSymbolTable : SymbolTable
445 const int MAX_FUNC_SIZE = 0x20000;
447 public LinuxSymbolTable (string binary) {
451 void Load (string binary) {
452 ProcessStartInfo psi = new ProcessStartInfo ("objdump", "-t "+binary);
453 psi.UseShellExecute = false;
454 psi.RedirectStandardOutput = true;
456 var proc = Process.Start (psi);
457 var list = new List<Symbol> ();
459 while ((line = proc.StandardOutput.ReadLine ()) != null) {
460 string[] fields = line.Split(new char[] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries);
462 if (fields.Length < 6)
464 if (fields [3] != ".text" || fields [2] != "F")
467 int offset = fields [0].ParseHex ();
468 int size = fields [4].ParseHex ();
469 string name = fields [fields.Length - 1];
471 list.Add (new Symbol (offset, size, name));
473 table = new Symbol [list.Count];
474 list.CopyTo (table, 0);
478 public string Translate (int offset) {
480 int res = Array.BinarySearch (table, new Symbol (offset, 0, null));
482 return table [res].name;
485 if (res >= table.Length)
486 sym = table [table.Length - 1];
488 sym = table [res - 1];
490 if (sym != null && offset - sym.offset < MAX_FUNC_SIZE)
492 return String.Format ("[{0:x}]", offset);
496 public class TraceDecoder
500 public TraceDecoder (string file) {
504 public IEnumerable<Trace> GetTraces () {
505 using (StreamReader reader = new StreamReader (file)) {
507 while ((line = reader.ReadLine ()) != null) {
508 string[] fields = line.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
509 if (fields.Length >= 7) {
510 yield return new Trace (fields);
520 static extern int uname (IntPtr buf);
525 IntPtr buf = Marshal.AllocHGlobal (8192);
526 if (uname (buf) == 0) {
527 string os = Marshal.PtrToStringAnsi (buf);
528 isOsx = os == "Darwin";
531 Marshal.FreeHGlobal (buf);
536 static void Main (string[] args) {
538 if (args.Length != 2) {
539 Console.WriteLine ("usage: LockTracerDecoder.exe /path/to/mono /path/to/locks.pid");
543 syms = new OsxSymbolTable (args [0]);
545 syms = new LinuxSymbolTable (args [0]);
547 var decoder = new TraceDecoder (args [1]);
548 var sim = new LockSimulator (syms);
549 sim.PlayBack (decoder.GetTraces ());
553 public static class Utils
555 public static int ParseHex (this string number) {
556 while (number.Length > 1 && (number [0] == '0' || number [0] == 'x' || number [0] == 'X'))
557 number = number.Substring (1);
558 return int.Parse (number, NumberStyles.HexNumber);
561 public static int ParseDec (this string number) {
562 while (number.Length > 1 && number [0] == '0')
563 number = number.Substring (1);
564 return int.Parse (number);