Program.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Threading.Tasks;
  5. using System.IO;
  6. using System.Text.RegularExpressions;
  7. using System.Collections.Generic;
  8. namespace crashbisector
  9. {
  10. class BisectInfo {
  11. const int timeout = 60;
  12. public string MonoPath { get; set; }
  13. public string OptName { get; set; }
  14. public IEnumerable<string> Args { get; set; }
  15. Random rand;
  16. public BisectInfo () {
  17. rand = new Random ();
  18. }
  19. string Run (string bisectArg) {
  20. var args = Args;
  21. if (bisectArg == null) {
  22. args = new string[] { "-v" }.Concat (args);
  23. } else {
  24. args = new string[] { bisectArg }.Concat (args);
  25. }
  26. var startInfo = new ProcessStartInfo {
  27. FileName = MonoPath,
  28. Arguments = string.Join (" ", args),
  29. UseShellExecute = false,
  30. RedirectStandardOutput = true,
  31. RedirectStandardError = true
  32. };
  33. startInfo.EnvironmentVariables.Add ("MONO_DEBUG", "no-gdb-backtrace");
  34. using (var process = Process.Start (startInfo)) {
  35. var stdoutTask = Task.Factory.StartNew (() => new StreamReader (process.StandardOutput.BaseStream).ReadToEnd (), TaskCreationOptions.LongRunning);
  36. var stderrTask = Task.Factory.StartNew (() => new StreamReader (process.StandardError.BaseStream).ReadToEnd (), TaskCreationOptions.LongRunning);
  37. var success = process.WaitForExit (timeout < 0 ? -1 : (Math.Min (Int32.MaxValue / 1000, timeout) * 1000));
  38. if (!success || process.ExitCode != 0) {
  39. return null;
  40. }
  41. var stdout = stdoutTask.Result;
  42. return stdout;
  43. }
  44. }
  45. bool RunWithMethods (IEnumerable<string> methods) {
  46. var path = Path.GetTempFileName ();
  47. File.WriteAllLines (path, methods);
  48. var stdout = Run (String.Format ("--bisect={0}:{1}", OptName, path));
  49. File.Delete (path);
  50. return stdout != null;
  51. }
  52. IEnumerable<int> EliminationOrder (int numChunks) {
  53. var chunks = new int [numChunks];
  54. for (var i = 0; i < numChunks; ++i)
  55. chunks [i] = i;
  56. for (var i = 0; i < numChunks; ++i) {
  57. var j = rand.Next (i, numChunks);
  58. var tmp = chunks [i];
  59. chunks [i] = chunks [j];
  60. chunks [j] = tmp;
  61. }
  62. return chunks;
  63. }
  64. bool TryEliminate (IEnumerable<string> methods, int chunkSize) {
  65. var count = methods.Count ();
  66. if (chunkSize < 1 || chunkSize > count)
  67. throw new Exception ("I can't do math.");
  68. var numChunks = (count + chunkSize - 1) / chunkSize;
  69. foreach (var i in EliminationOrder (numChunks)) {
  70. var firstIndex = i * chunkSize;
  71. var lastPlusOneIndex = (i + 1) * chunkSize;
  72. var methodsLeft = methods.Take (firstIndex).Concat (methods.Skip (lastPlusOneIndex));
  73. if (chunkSize == 1)
  74. Console.WriteLine ("Running without method at position {0}", firstIndex);
  75. else
  76. Console.WriteLine ("Running without methods at positions {0} to {1}", firstIndex, lastPlusOneIndex - 1);
  77. var success = RunWithMethods (methodsLeft);
  78. Console.WriteLine ("Crashed: {0}", !success);
  79. if (!success) {
  80. Console.WriteLine ("Eliminating further from {0} methods.", methodsLeft.Count ());
  81. return EliminationStep (methodsLeft);
  82. }
  83. }
  84. return false;
  85. }
  86. bool EliminationStep (IEnumerable<string> methods) {
  87. var count = methods.Count ();
  88. if (count < 2) {
  89. Console.WriteLine ("Can't eliminate further. Methods required to crash are:\n{0}",
  90. string.Join ("\n", methods));
  91. return true;
  92. }
  93. if (count >= 9) {
  94. var chunkSize = (int)Math.Floor (Math.Sqrt (count));
  95. Console.WriteLine ("Trying eliminating chunks of {0}.", chunkSize);
  96. if (TryEliminate (methods, chunkSize))
  97. return true;
  98. Console.WriteLine ("Chunks didn't succeed, eliminating individual methods.");
  99. }
  100. if (TryEliminate (methods, 1))
  101. return true;
  102. Console.WriteLine ("Couldn't eliminate any method. Methods required to crash are:\n{0}",
  103. string.Join ("\n", methods));
  104. return true;
  105. }
  106. bool BisectStep (IEnumerable<string> methods) {
  107. var count = methods.Count ();
  108. if (count == 0) {
  109. Console.WriteLine ("Error: No methods left - what happened?");
  110. return false;
  111. }
  112. if (count == 1) {
  113. Console.WriteLine ("Found the offending method: {0}", methods.First ());
  114. return true;
  115. }
  116. var half = count / 2;
  117. var firstHalf = methods.Take (half);
  118. var secondHalf = methods.Skip (half);
  119. Console.WriteLine ("Splitting into two halves: {0} and {1} methods.", firstHalf.Count (), secondHalf.Count ());
  120. Console.WriteLine ("Running first half.");
  121. var firstSuccess = RunWithMethods (firstHalf);
  122. Console.WriteLine ("Crashed: {0}", !firstSuccess);
  123. if (!firstSuccess) {
  124. Console.WriteLine ("Continuing with first half.");
  125. return BisectStep (firstHalf);
  126. }
  127. Console.WriteLine ("Running second half.");
  128. var secondSuccess = RunWithMethods (secondHalf);
  129. Console.WriteLine ("Crashed: {0}", !secondSuccess);
  130. if (!secondSuccess) {
  131. Console.WriteLine ("Continuing with second half.");
  132. return BisectStep (secondHalf);
  133. }
  134. Console.WriteLine ("Error: Both halves succeeded, can't bisect. Trying elimination.");
  135. return EliminationStep (methods);
  136. }
  137. public bool Bisect () {
  138. Console.WriteLine ("Running to gather methods.");
  139. var stdout = Run (null);
  140. if (stdout == null) {
  141. Console.Error.WriteLine ("Error: Failed to execute without optimization.");
  142. Environment.Exit (1);
  143. }
  144. var regex = new Regex ("converting[^\n]* method ([^\n]+)\n");
  145. var matches = regex.Matches (stdout);
  146. var methods = new List<string> ();
  147. foreach (Match match in matches) {
  148. var method = match.Groups [1].Value;
  149. methods.Add (method);
  150. }
  151. Console.WriteLine ("Bisecting {0} methods.", methods.Count);
  152. Console.WriteLine ("Running with all methods, just to make sure.");
  153. var success = RunWithMethods (methods);
  154. if (success) {
  155. Console.WriteLine ("Error: Ran successfully with all methods optimized. Nothing to bisect.");
  156. return false;
  157. }
  158. Console.WriteLine ("Crashed. Bisecting.");
  159. return BisectStep (methods);
  160. }
  161. }
  162. class MainClass
  163. {
  164. static void UsageAndExit (int exitCode) {
  165. Console.Error.WriteLine ("Usage: crash-bisector.exe --mono MONO-EXECUTABLE --opt OPTION-NAME -- MONO-ARG ...");
  166. Environment.Exit (exitCode);
  167. }
  168. public static void Main (string[] args)
  169. {
  170. string monoPath = null;
  171. string optName = null;
  172. var argIndex = 0;
  173. while (argIndex < args.Length) {
  174. if (args [argIndex] == "--mono") {
  175. monoPath = args [argIndex + 1];
  176. argIndex += 2;
  177. } else if (args [argIndex] == "--opt") {
  178. optName = args [argIndex + 1];
  179. argIndex += 2;
  180. } else if (args [argIndex] == "--help") {
  181. UsageAndExit (0);
  182. } else if (args [argIndex] == "--") {
  183. argIndex += 1;
  184. break;
  185. } else {
  186. UsageAndExit (1);
  187. }
  188. }
  189. if (monoPath == null || optName == null || argIndex == args.Length)
  190. UsageAndExit (1);
  191. var bisectInfo = new BisectInfo {
  192. MonoPath = monoPath,
  193. OptName = optName,
  194. Args = args.Skip (argIndex)
  195. };
  196. var success = bisectInfo.Bisect ();
  197. Environment.Exit (success ? 0 : 1);
  198. }
  199. }
  200. }