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:
111 You can take the loader lock without holding a domain lock.
112 You cannot take a domain lock if the loader lock is held.
113 You cannot take a domain lock while holding the lock to another domain.
121 DomainAssembliesLock,
122 DomainJitCodeHashLock,
130 public SimLock (Lock kind, int id) {
135 static int GetLockOrder (Lock kind) {
137 case Lock.LoaderLock:
139 case Lock.DomainLock:
141 case Lock.DomainJitCodeHashLock:
148 bool IsParent (SimLock other) {
149 return GetLockOrder (kind) > GetLockOrder (other.kind);
152 public bool IsSimpleLock {
153 get { return GetLockOrder (kind) == 3; }
156 public bool IsGlobalLock {
157 get { return kind == Lock.LoaderLock; }
160 /*locked is already owned by the thread, 'this' is the new one*/
161 bool Compare (SimThread thread, SimLock locked, out bool isWarning, out string msg)
166 if (locked != this) {
167 if (!IsParent (locked)) {
168 if (IsGlobalLock) { /*acquiring a global lock*/
169 if (!thread.HoldsLock (this)) { /*does the thread alread hold it?*/
170 msg = "Acquired a global lock after a regular lock without having it before.";
174 msg = "Hierarchy violation.";
178 } else if (IsSimpleLock) {
179 msg = "Avoid taking simple locks recursively";
187 public bool IsValid (SimThread thread, SimLock locked) {
190 return Compare (thread, locked, out warn, out msg);
193 public bool WarnAbout (SimThread thread, SimLock locked) {
196 Compare (thread, locked, out warn, out msg);
200 public string GetWarningMessage (SimThread thread, SimLock locked) {
203 Compare (thread, locked, out warn, out msg);
204 return warn ? msg : null;
207 public string GetErrorMessage (SimThread thread, SimLock locked) {
210 bool res = Compare (thread, locked, out warn, out msg);
211 return !res && !warn ? msg : null;
214 public override string ToString () {
215 return String.Format ("{0}", kind);
219 public class LockSimulator
221 static Dictionary <int, SimThread> threads = new Dictionary <int, SimThread> ();
222 static Dictionary <int, SimLock> locks = new Dictionary <int, SimLock> ();
226 public LockSimulator (SymbolTable s) { this.syms = s; }
228 SimLock GetLock (Trace t) {
229 if (locks.ContainsKey (t.lockPtr))
230 return locks [t.lockPtr];
232 return locks [t.lockPtr] = new SimLock (t.lockKind, t.lockPtr);
236 SimThread GetThread (Trace t) {
237 if (threads.ContainsKey (t.thread))
238 return threads [t.thread];
240 return threads [t.thread] = new SimThread (t.thread);
243 public void PlayBack (IEnumerable<Trace> traces) {
244 foreach (var t in traces) {
245 SimThread thread = GetThread (t);
246 SimLock lk = GetLock (t);
247 string frame = t.GetUsefullTopTrace (this.syms);
250 case Record.MustNotHoldAny:
251 case Record.MustNotHoldOne:
252 case Record.MustHoldOne:
253 throw new Exception ("not supported");
254 case Record.LockAcquired:
255 thread.Lock (lk, frame);
257 case Record.LockReleased:
258 thread.Release (lk, frame);
261 throw new Exception ("Invalid trace record: "+t.record);
269 public Record record;
270 public Lock lockKind;
274 static readonly string[] BAD_FRAME_METHODS = new string[] {
276 "mono_loader_unlock",
281 public Trace (string[] fields) {
282 thread = fields [0].ParseHex ();
283 record = (Record)fields [1].ParseDec ();
284 lockKind = (Lock)fields [2].ParseDec ();
285 lockPtr = fields [3].ParseHex ();
286 frames = new int [fields.Length - 4];
287 for (int i = 0; i < frames.Length; ++i)
288 frames [i] = fields [i + 4].ParseHex ();
291 public void Dump (SymbolTable table) {
292 Console.WriteLine ("{0:x} {1} {2} {3:x}", thread, record, lockKind, lockPtr);
293 for (int i = 0; i < frames.Length; ++i)
294 Console.WriteLine ("\t{0}", table.Translate (frames [i]));
297 public string GetUsefullTopTrace (SymbolTable syms) {
298 for (int i = 0; i < frames.Length; ++i) {
299 string str = syms.Translate (frames [i]);
301 for (int j = 0; j < BAD_FRAME_METHODS.Length; ++j) {
302 if (str.IndexOf (BAD_FRAME_METHODS [j]) >= 0) {
314 public class Symbol : IComparable<Symbol>
320 public Symbol (int o, int size, string n) {
326 public int CompareTo(Symbol other) {
327 return offset - other.offset;
331 public interface SymbolTable {
332 string Translate (int offset);
335 public class OsxSymbolTable : SymbolTable
339 const int MAX_FUNC_SIZE = 0x20000;
341 public OsxSymbolTable (string binary) {
345 void Load (string binary) {
346 ProcessStartInfo psi = new ProcessStartInfo ("gobjdump", "-t "+binary);
347 psi.UseShellExecute = false;
348 psi.RedirectStandardOutput = true;
350 var proc = Process.Start (psi);
351 var list = new List<Symbol> ();
353 while ((line = proc.StandardOutput.ReadLine ()) != null) {
354 string[] fields = line.Split(new char[] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries);
355 if (fields.Length < 4)
357 if (!(fields [1].Equals ("g") || fields [1].Equals ("d")))
359 if (!fields [2].Equals ("*UND*"))
362 int offset = fields [0].ParseHex ();
363 string name = fields [3];
365 list.Add (new Symbol (offset, 0, name));
367 table = new Symbol [list.Count];
368 list.CopyTo (table, 0);
372 public string Translate (int offset) {
374 int res = Array.BinarySearch (table, new Symbol (offset, 0, null));
376 return table [res].name;
379 if (res >= table.Length)
380 sym = table [table.Length - 1];
382 sym = table [res - 1];
386 int size = Math.Max (sym.size, 10);
387 if (offset - sym.offset < size)
390 return String.Format ("[{0:x}]", offset);
394 public class LinuxSymbolTable : SymbolTable
398 const int MAX_FUNC_SIZE = 0x20000;
400 public LinuxSymbolTable (string binary) {
404 void Load (string binary) {
405 ProcessStartInfo psi = new ProcessStartInfo ("objdump", "-t "+binary);
406 psi.UseShellExecute = false;
407 psi.RedirectStandardOutput = true;
409 var proc = Process.Start (psi);
410 var list = new List<Symbol> ();
412 while ((line = proc.StandardOutput.ReadLine ()) != null) {
413 string[] fields = line.Split(new char[] {' ', '\t'}, StringSplitOptions.RemoveEmptyEntries);
415 if (fields.Length < 6)
417 if (fields [3] != ".text" || fields [2] != "F")
420 int offset = fields [0].ParseHex ();
421 int size = fields [4].ParseHex ();
422 string name = fields [fields.Length - 1];
424 list.Add (new Symbol (offset, size, name));
426 table = new Symbol [list.Count];
427 list.CopyTo (table, 0);
431 public string Translate (int offset) {
433 int res = Array.BinarySearch (table, new Symbol (offset, 0, null));
435 return table [res].name;
438 if (res >= table.Length)
439 sym = table [table.Length - 1];
441 sym = table [res - 1];
443 if (sym != null && offset - sym.offset < MAX_FUNC_SIZE)
445 return String.Format ("[{0:x}]", offset);
449 public class TraceDecoder
453 public TraceDecoder (string file) {
457 public IEnumerable<Trace> GetTraces () {
458 using (StreamReader reader = new StreamReader (file)) {
460 while ((line = reader.ReadLine ()) != null) {
461 string[] fields = line.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
462 if (fields.Length >= 7) {
463 yield return new Trace (fields);
473 static extern int uname (IntPtr buf);
478 IntPtr buf = Marshal.AllocHGlobal (8192);
479 if (uname (buf) == 0) {
480 string os = Marshal.PtrToStringAnsi (buf);
481 isOsx = os == "Darwin";
484 Marshal.FreeHGlobal (buf);
489 static void Main (string[] args) {
491 if (args.Length != 2) {
492 Console.WriteLine ("usage: LockTracerDecoder.exe /path/to/mono /path/to/locks.pid");
496 syms = new OsxSymbolTable (args [0]);
498 syms = new LinuxSymbolTable (args [0]);
500 var decoder = new TraceDecoder (args [1]);
501 var sim = new LockSimulator (syms);
502 sim.PlayBack (decoder.GetTraces ());
506 public static class Utils
508 public static int ParseHex (this string number) {
509 while (number.Length > 1 && (number [0] == '0' || number [0] == 'x' || number [0] == 'X'))
510 number = number.Substring (1);
511 return int.Parse (number, NumberStyles.HexNumber);
514 public static int ParseDec (this string number) {
515 while (number.Length > 1 && number [0] == '0')
516 number = number.Substring (1);
517 return int.Parse (number);