You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
9.1 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.IO;
  5. using System.Linq;
  6. using McMaster.Extensions.CommandLineUtils;
  7. using ICSharpCode.Decompiler.CSharp;
  8. using ICSharpCode.Decompiler.TypeSystem;
  9. using ICSharpCode.Decompiler.Metadata;
  10. using ICSharpCode.Decompiler.Disassembler;
  11. using System.Threading;
  12. using System.Reflection.Metadata;
  13. using System.Reflection.PortableExecutable;
  14. using ICSharpCode.Decompiler.DebugInfo;
  15. using ICSharpCode.Decompiler.PdbProvider;
  16. using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
  17. // ReSharper disable All
  18. namespace ICSharpCode.Decompiler.Console
  19. {
  20. [Command(Name = "ilspycmd", Description = "dotnet tool for decompiling .NET assemblies and generating portable PDBs",
  21. ExtendedHelpText = @"
  22. Remarks:
  23. -o is valid with every option and required when using -p.
  24. ")]
  25. [HelpOption("-h|--help")]
  26. [ProjectOptionRequiresOutputDirectoryValidation]
  27. class ILSpyCmdProgram
  28. {
  29. public static int Main(string[] args) => CommandLineApplication.Execute<ILSpyCmdProgram>(args);
  30. [FileExists]
  31. [Required]
  32. [Argument(0, "Assembly file name", "The assembly that is being decompiled. This argument is mandatory.")]
  33. public string InputAssemblyName { get; }
  34. [DirectoryExists]
  35. [Option("-o|--outputdir <directory>", "The output directory, if omitted decompiler output is written to standard out.", CommandOptionType.SingleValue)]
  36. public string OutputDirectory { get; }
  37. [Option("-p|--project", "Decompile assembly as compilable project. This requires the output directory option.", CommandOptionType.NoValue)]
  38. public bool CreateCompilableProjectFlag { get; }
  39. [Option("-t|--type <type-name>", "The fully qualified name of the type to decompile.", CommandOptionType.SingleValue)]
  40. public string TypeName { get; }
  41. [Option("-il|--ilcode", "Show IL code.", CommandOptionType.NoValue)]
  42. public bool ShowILCodeFlag { get; }
  43. [Option("--il-sequence-points", "Show IL with sequence points. Implies -il.", CommandOptionType.NoValue)]
  44. public bool ShowILSequencePointsFlag { get; }
  45. [Option("-genpdb", "Generate PDB.", CommandOptionType.NoValue)]
  46. public bool CreateDebugInfoFlag { get; }
  47. [FileExistsOrNull]
  48. [Option("-usepdb", "Use PDB.", CommandOptionType.SingleOrNoValue)]
  49. public (bool IsSet, string Value) InputPDBFile { get; }
  50. [Option("-l|--list <entity-type(s)>", "Lists all entities of the specified type(s). Valid types: c(lass), i(nterface), s(truct), d(elegate), e(num)", CommandOptionType.MultipleValue)]
  51. public string[] EntityTypes { get; } = new string[0];
  52. [Option("-v|--version", "Show version of ICSharpCode.Decompiler used.", CommandOptionType.NoValue)]
  53. public bool ShowVersion { get; }
  54. [Option("-lv|--languageversion <version>", "C# Language version: CSharp1, CSharp2, CSharp3, CSharp4, CSharp5, CSharp6, CSharp7_0, CSharp7_1, CSharp7_2, CSharp7_3, CSharp8_0 or Latest", CommandOptionType.SingleValue)]
  55. public LanguageVersion LanguageVersion { get; } = LanguageVersion.Latest;
  56. [DirectoryExists]
  57. [Option("-r|--referencepath <path>", "Path to a directory containing dependencies of the assembly that is being decompiled.", CommandOptionType.MultipleValue)]
  58. public string[] ReferencePaths { get; } = new string[0];
  59. [Option("--no-dead-code", "Remove dead code.", CommandOptionType.NoValue)]
  60. public bool RemoveDeadCode { get; }
  61. [Option("--no-dead-stores", "Remove dead stores.", CommandOptionType.NoValue)]
  62. public bool RemoveDeadStores { get; }
  63. private int OnExecute(CommandLineApplication app)
  64. {
  65. TextWriter output = System.Console.Out;
  66. bool outputDirectorySpecified = !string.IsNullOrEmpty(OutputDirectory);
  67. try {
  68. if (CreateCompilableProjectFlag) {
  69. return DecompileAsProject(InputAssemblyName, OutputDirectory);
  70. } else if (EntityTypes.Any()) {
  71. var values = EntityTypes.SelectMany(v => v.Split(',', ';')).ToArray();
  72. HashSet<TypeKind> kinds = TypesParser.ParseSelection(values);
  73. if (outputDirectorySpecified) {
  74. string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
  75. output = File.CreateText(Path.Combine(OutputDirectory, outputName) + ".list.txt");
  76. }
  77. return ListContent(InputAssemblyName, output, kinds);
  78. } else if (ShowILCodeFlag || ShowILSequencePointsFlag) {
  79. if (outputDirectorySpecified) {
  80. string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
  81. output = File.CreateText(Path.Combine(OutputDirectory, outputName) + ".il");
  82. }
  83. return ShowIL(InputAssemblyName, output);
  84. } else if (CreateDebugInfoFlag) {
  85. string pdbFileName = null;
  86. if (outputDirectorySpecified) {
  87. string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
  88. pdbFileName = Path.Combine(OutputDirectory, outputName) + ".pdb";
  89. } else {
  90. pdbFileName = Path.ChangeExtension(InputAssemblyName, ".pdb");
  91. }
  92. return GeneratePdbForAssembly(InputAssemblyName, pdbFileName, app);
  93. } else if (ShowVersion) {
  94. string vInfo = "ilspycmd: " + typeof(ILSpyCmdProgram).Assembly.GetName().Version.ToString() +
  95. Environment.NewLine
  96. + "ICSharpCode.Decompiler: " +
  97. typeof(FullTypeName).Assembly.GetName().Version.ToString();
  98. output.WriteLine(vInfo);
  99. } else {
  100. if (outputDirectorySpecified) {
  101. string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
  102. output = File.CreateText(Path.Combine(OutputDirectory,
  103. (string.IsNullOrEmpty(TypeName) ? outputName : TypeName) + ".decompiled.cs"));
  104. }
  105. return Decompile(InputAssemblyName, output, TypeName);
  106. }
  107. } catch (Exception ex) {
  108. app.Error.WriteLine(ex.ToString());
  109. return ProgramExitCodes.EX_SOFTWARE;
  110. } finally {
  111. output.Close();
  112. }
  113. return 0;
  114. }
  115. DecompilerSettings GetSettings(PEFile module)
  116. {
  117. return new DecompilerSettings(LanguageVersion) {
  118. ThrowOnAssemblyResolveErrors = false,
  119. RemoveDeadCode = RemoveDeadCode,
  120. RemoveDeadStores = RemoveDeadStores,
  121. UseSdkStyleProjectFormat = WholeProjectDecompiler.CanUseSdkStyleProjectFormat(module),
  122. };
  123. }
  124. CSharpDecompiler GetDecompiler(string assemblyFileName)
  125. {
  126. var module = new PEFile(assemblyFileName);
  127. var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Reader.DetectTargetFrameworkId());
  128. foreach (var path in ReferencePaths) {
  129. resolver.AddSearchDirectory(path);
  130. }
  131. return new CSharpDecompiler(assemblyFileName, resolver, GetSettings(module)) {
  132. DebugInfoProvider = TryLoadPDB(module)
  133. };
  134. }
  135. int ListContent(string assemblyFileName, TextWriter output, ISet<TypeKind> kinds)
  136. {
  137. CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
  138. foreach (var type in decompiler.TypeSystem.MainModule.TypeDefinitions) {
  139. if (!kinds.Contains(type.Kind))
  140. continue;
  141. output.WriteLine($"{type.Kind} {type.FullName}");
  142. }
  143. return 0;
  144. }
  145. int ShowIL(string assemblyFileName, TextWriter output)
  146. {
  147. var module = new PEFile(assemblyFileName);
  148. output.WriteLine($"// IL code: {module.Name}");
  149. var disassembler = new ReflectionDisassembler(new PlainTextOutput(output), CancellationToken.None)
  150. {
  151. DebugInfo = TryLoadPDB(module),
  152. ShowSequencePoints = ShowILSequencePointsFlag,
  153. };
  154. disassembler.WriteModuleContents(module);
  155. return 0;
  156. }
  157. int DecompileAsProject(string assemblyFileName, string outputDirectory)
  158. {
  159. var module = new PEFile(assemblyFileName);
  160. var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Reader.DetectTargetFrameworkId());
  161. foreach (var path in ReferencePaths) {
  162. resolver.AddSearchDirectory(path);
  163. }
  164. var decompiler = new WholeProjectDecompiler(GetSettings(module), resolver, resolver, TryLoadPDB(module));
  165. decompiler.DecompileProject(module, outputDirectory);
  166. return 0;
  167. }
  168. int Decompile(string assemblyFileName, TextWriter output, string typeName = null)
  169. {
  170. CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
  171. if (typeName == null) {
  172. output.Write(decompiler.DecompileWholeModuleAsString());
  173. } else {
  174. var name = new FullTypeName(typeName);
  175. output.Write(decompiler.DecompileTypeAsString(name));
  176. }
  177. return 0;
  178. }
  179. int GeneratePdbForAssembly(string assemblyFileName, string pdbFileName, CommandLineApplication app)
  180. {
  181. var module = new PEFile(assemblyFileName,
  182. new FileStream(assemblyFileName, FileMode.Open, FileAccess.Read),
  183. PEStreamOptions.PrefetchEntireImage,
  184. metadataOptions: MetadataReaderOptions.None);
  185. if (!PortablePdbWriter.HasCodeViewDebugDirectoryEntry(module)) {
  186. app.Error.WriteLine($"Cannot create PDB file for {assemblyFileName}, because it does not contain a PE Debug Directory Entry of type 'CodeView'.");
  187. return ProgramExitCodes.EX_DATAERR;
  188. }
  189. using (FileStream stream = new FileStream(pdbFileName, FileMode.OpenOrCreate, FileAccess.Write)) {
  190. var decompiler = GetDecompiler(assemblyFileName);
  191. PortablePdbWriter.WritePdb(module, decompiler, GetSettings(module), stream);
  192. }
  193. return 0;
  194. }
  195. IDebugInfoProvider TryLoadPDB(PEFile module)
  196. {
  197. if (InputPDBFile.IsSet) {
  198. if (InputPDBFile.Value == null)
  199. return DebugInfoUtils.LoadSymbols(module);
  200. return DebugInfoUtils.FromFile(module, InputPDBFile.Value);
  201. }
  202. return null;
  203. }
  204. }
  205. }