// IldasmTask.cs // // Giuseppe Greco // Copyright (C) 2004 Agamura, Inc. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // Giuseppe Greco (giuseppe.greco@agamura.com) using System.Globalization; using System.IO; using System.Xml; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Tasks; using NAnt.Core.Types; using NAnt.Core.Util; namespace NAnt.MSNet.Tasks { /// /// Disassembles any portable executable (PE) file that contains /// intermediate language (IL) code. /// /// /// /// Files are only disassembled if the input file is newer than the output /// file, or if the output file does not exist. However, you can /// explicitly force files to be disassembled with the /// attribute. /// /// /// A can be used to select files to disassemble. /// To use a , the /// attribute must be set. The file name of the output file will be equal /// to the file name of the input file, but with extension ".il". /// /// /// /// /// Disassembles helloworld.exe to helloworld.il. /// /// /// /// ]]> /// /// /// /// /// Disassembles a set of PE files into the specified directory. /// /// /// /// /// /// /// /// /// ]]> /// /// [TaskName("ildasm")] [ProgramLocation(LocationType.FrameworkSdkDir)] public class IldasmTask : ExternalProgramBase { #region Private Instance Fields private const string _TargetExt = "il"; private bool _all; private bool _bytes; private bool _forceRebuild; private bool _header; private bool _lineNumbers; private bool _noIL; private bool _publicOnly; private bool _quoteAllNames; private bool _rawExceptionHandling; private bool _source; private bool _tokens; private bool _unicode; private bool _utf8; private string _item; private string _visibility; private DirectoryInfo _toDir; private FileInfo _inputFile; private FileInfo _outputFile; private FileSet _assemblies; private string _options; #endregion Private Instance Fields #region Public Instance Properties /// /// Specifies whether or not the disassembler should combine the /// /HEADER, /BYTE, and /TOKENS options. The default /// is . /// /// /// if the disassembler should combine the /// /HEADER, /BYTE, and /TOKENS options; /// otherwise, . The default is /// . /// /// /// /// Corresponds to the /ALL flag. /// /// [TaskAttribute("all")] [BooleanValidator()] public bool All { get { return _all; } set { _all = value; } } /// /// Specifies whether or not the disassembler should generate the /// IL stream bytes (in hexadecimal notation) as instruction comments. /// The default is . /// /// /// if the IL stream bytes should be generated /// as instruction comments; otherwise, . The /// default is . /// /// /// /// Corresponds to the /BYTE flag. /// /// [TaskAttribute("bytes")] [BooleanValidator()] public bool Bytes { get { return _bytes; } set { _bytes = value; } } /// /// Instructs NAnt to rebuild the output file regardless of the file /// timestamps. The default is . /// /// /// if the output file should be rebuilt /// regardless of its timestamps; otherwise . /// The default is . /// [TaskAttribute("rebuild")] [BooleanValidator()] public bool ForceRebuild { get { return _forceRebuild; } set { _forceRebuild = value; } } /// /// Specifies whether or not the disassembler should include PE header /// information and runtime header information in the output. The default /// is . /// /// /// if PE header information and runtime header /// information should be included in the output; otherwise, /// . The default is . /// /// /// /// Corresponds to the /HEADER flag. /// /// [TaskAttribute("header")] [BooleanValidator()] public bool Header { get { return _header; } set { _header = value; } } /// /// Specifies the PE file to disassemble. /// /// /// A that represents the PE file /// to disassemble. /// [TaskAttribute("input", Required=false)] public FileInfo InputFile { get { return _inputFile; } set { _inputFile = value; } } /// /// Specifies whether or not the disassembler should include /// references to original source lines. The default is . /// /// /// if references to original source lines /// should be included; otherwise, . The /// default is . /// /// /// /// Corresponds to the /LINENUM flag. /// /// [TaskAttribute("linenumbers")] [BooleanValidator()] public bool LineNumbers { get { return _lineNumbers; } set { _lineNumbers = value; } } /// /// Specifies whether or not the disassembler should suppress ILASM /// code output. The default is . /// /// /// if ILASM code output should be suppresses; /// otherwise, . The default is /// . /// /// /// /// Corresponds to the /NOIL flag. /// /// [TaskAttribute("noil")] [BooleanValidator()] public bool NoIL { get { return _noIL; } set { _noIL = value; } } /// /// Specifies whether or not the disassembler should disassemble /// public items only. This is a shortcut for ="pub". /// The default is . /// /// /// if only public items should be /// disassembled; otherwise, . The default is /// . /// /// /// /// Corresponds to the /PUBONLY flag. /// /// [TaskAttribute("publiconly")] [BooleanValidator()] public bool PublicOnly { get { return _publicOnly; } set { _publicOnly = value; } } /// /// Specifies whether or not the disassembler should enclose all names /// in single quotation marks. By default, only names that don't match /// the ILASM definition of a simple name are quoted. The default is /// . /// /// /// if all names should be enclosed in single /// quotation marks; otherwise, . The default /// is . /// /// /// /// Corresponds to the /QUOTEALLNAMES flag. /// /// [TaskAttribute("quoteallnames")] [BooleanValidator()] public bool QuoteAllNames { get { return _quoteAllNames; } set { _quoteAllNames = value; } } /// /// Specifies whether or not the disassembler should generate /// structured exception handling clauses in canonical (label) form. /// The default is . /// /// /// if structured exception handling clauses /// should be generated in canonical form; otherwise, /// . The default is . /// /// /// /// Corresponds to the /RAWEH flag. /// /// [TaskAttribute("rawexceptionhandling")] [BooleanValidator()] public bool RawExceptionHandling { get { return _rawExceptionHandling; } set { _rawExceptionHandling = value; } } /// /// Specifies whether or not the disassembler should generate /// original source lines as comments. The default is . /// /// /// if original source lines should be /// generated as comments; otherwise, . /// The default is . /// /// /// /// Corresponds to the /SOURCE flag. /// /// [TaskAttribute("source")] [BooleanValidator()] public bool Source { get { return _source; } set { _source = value; } } /// /// Specifies whether or not the disassembler should generate metadata /// token values as comments. The default is . /// /// /// if metadata token values should be /// generated as comments; otherwise, . The /// default is . /// /// /// /// Corresponds to the /TOKENS flag. /// /// [TaskAttribute("tokens")] [BooleanValidator()] public bool Tokens { get { return _tokens; } set { _tokens = value; } } /// /// Specifies whether or not the disassembler should use the UNICODE /// encoding when generating the output. The default is ANSI. /// /// /// if the output should be generated using /// the UNICODE encoding; otherwise, . The /// default is . /// /// /// /// Corresponds to the /UNICODE flag. /// /// [TaskAttribute("unicode")] [BooleanValidator()] public bool Unicode { get { return _unicode; } set { _unicode = value; } } /// /// Specifies whether or not the disassembler should use the UTF-8 /// encoding when generating the output. The default is ANSI. /// /// /// if the output should be generated using /// the UTF-8 encoding; otherwise, . The /// default is . /// /// /// /// Corresponds to the /UTF8 flag. /// /// [TaskAttribute("utf8")] [BooleanValidator()] public bool Utf8 { get { return _utf8; } set { _utf8 = value; } } /// /// Instructs the disassembler to disassemble the specified item only. /// /// /// A that specifies the item to /// disassemble. /// /// /// /// Corresponds to the /ITEM flag. /// /// [TaskAttribute("item", Required=false)] [StringValidator(AllowEmpty=false)] public string Item { get { return _item; } set { _item = value; } } /// /// Instructs the disassembler to disassemble only the items with the /// specified visibility. Possible values are PUB, PRI, /// FAM, ASM, FAA, FOA, PSC, /// or any combination of them separated by +. /// /// /// A that contains the visibility /// suboptions. /// /// /// /// Corresponds to the /VISIBILITY flag. /// /// [TaskAttribute("visibility", Required=false)] [StringValidator(AllowEmpty=false)] public string Visibility { get { return _visibility; } set { _visibility = value; } } /// /// Specifies the name of the output file created by the disassembler. /// /// /// A that represents the name of /// the output file. /// /// /// /// Corresponds to the /OUT flag. /// /// [TaskAttribute("output", Required=false)] public FileInfo OutputFile { get { return _outputFile; } set { _outputFile = value; } } /// /// Specifies the directory to which outputs will be stored. /// /// /// A that represents the /// directory to which outputs will be stored. /// [TaskAttribute("todir", Required=false)] public DirectoryInfo ToDirectory { get { return _toDir; } set { _toDir = value; } } /// /// Specifies a list of PE files to disassemble. To use a , /// the attribute must be specified. /// /// /// A that represents the set /// of PE files to disassemble. /// [BuildElement("assemblies")] public FileSet Assemblies { get { return _assemblies; } set { _assemblies = value; } } #endregion Public Instance Properties #region Override implementation of ExternalProgramBase /// /// The command-line arguments for the external program. /// /// /// Overridden to ensure the <arg> elements would not be exposed /// to build authors. /// public override ArgumentCollection Arguments { get { return base.Arguments; } } /// /// Gets the command-line arguments for the external program. /// /// /// A that contains the command-line /// arguments for the external program. /// public override string ProgramArguments { get { return _options; } } /// /// Checks whether the task is initialized with valid attributes. /// /// The used to initialize the task. protected override void InitializeTask(XmlNode taskNode) { if (ToDirectory == null && Assemblies != null && Assemblies.Includes.Count > 0) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA3001")), Location); } if (InputFile != null && Assemblies != null && Assemblies.Includes.Count > 0) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA3002")), Location); } if (OutputFile == null && ToDirectory == null) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA3003")), Location); } if (OutputFile != null && ToDirectory != null) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA3004")), Location); } } /// /// Disassembles the PE file(s). /// protected override void ExecuteTask() { // disassemble a single PE file if (InputFile != null) { DisassemblyFile(InputFile); } else { foreach (string inputFile in Assemblies.FileNames) { DisassemblyFile(new FileInfo(inputFile)); } } } #endregion Override implementation of ExternalProgramBase #region Private Instance Methods /// /// Disassembles the specified PE file. /// /// The PE file to disassemble. private void DisassemblyFile(FileInfo inputFile) { // verify if input file actually exists if (!inputFile.Exists) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA3005"), inputFile.FullName), Location); } // determine output file corresponding with PE file FileInfo outputFile = GetOutputFile(inputFile); // only actually perform disassembly if necessary if (NeedsDisassembling(inputFile, outputFile)) { Log(Level.Info, ResourceUtils.GetString("String_Disassembling"), inputFile.FullName, outputFile.FullName); // ensure output directory exists if (!outputFile.Directory.Exists) { outputFile.Directory.Create(); } // set command-line arguments for the disassembler WriteOptions(inputFile, outputFile); // call base class to do the work base.ExecuteTask(); } } /// /// Determines the full path and extension for the output file. /// /// /// A that represents the PE file /// file for which the corresponding output file should be determined. /// /// /// A that represents the full path /// for the output file. /// /// The path of the output file could not be determined. private FileInfo GetOutputFile(FileInfo inputFile) { FileInfo outputFile; if (OutputFile != null) { outputFile = new FileInfo(OutputFile.FullName); } else if (ToDirectory != null) { outputFile = new FileInfo(Path.Combine(ToDirectory.FullName, inputFile.Name)); outputFile = new FileInfo(Path.ChangeExtension(outputFile.FullName, _TargetExt)); } else { // we should never actually get here (if the checks in // InitializeTask have been executed throw new BuildException(string.Format(CultureInfo.InvariantCulture, ResourceUtils.GetString("NA3006"), inputFile.FullName), Location); } return outputFile; } /// /// Writes the disassembler options. /// private void WriteOptions(FileInfo inputFile, FileInfo outputFile) { using (StringWriter writer = new StringWriter()) { // always direct the output to console WriteOption(writer, "TEXT"); WriteOption(writer, "NOBAR"); if (All) { WriteOption(writer, "ALL"); } if (Bytes) { WriteOption(writer, "BYTES"); } if (Header) { WriteOption(writer, "HEADER"); } if (LineNumbers) { WriteOption(writer, "LINENUM"); } if (NoIL) { WriteOption(writer, "NOIL"); } if (PublicOnly) { WriteOption(writer, "PUBONLY"); } if (QuoteAllNames) { WriteOption(writer, "QUOTEALLNAMES"); } if (RawExceptionHandling) { WriteOption(writer, "RAWEH"); } if (Source) { WriteOption(writer, "SOURCE"); } if (Tokens) { WriteOption(writer, "TOKENS"); } if (Unicode) { WriteOption(writer, "UNICODE"); } if (Utf8) { WriteOption(writer, "UTF8"); } if (Item != null) { WriteOption(writer, "ITEM", Item); } if (Visibility != null) { WriteOption(writer, "VISIBILITY", Visibility.ToUpper()); } // specifiy path of output file WriteOption(writer, "OUT", outputFile.FullName); // specify path of PE file to disassembly writer.Write(" \"" + inputFile.FullName + "\" "); _options = writer.ToString(); // close the StringWriter and the underlying stream writer.Close(); } } /// /// Writes an option using the default output format. /// /// /// The to which the disassembler options /// should be written. /// /// /// A that contains the name of the /// option which should be passed to the disassembler. /// private void WriteOption(StringWriter writer, string name) { writer.Write("/{0} ", name); } /// /// Writes an option and its value using the default output format. /// /// /// The to which the disassembler options /// should be written. /// /// /// A that contains the name of the /// option which should be passed to the disassembler. /// /// /// A that contains the value of the /// option which should be passed to the disassembler. /// private void WriteOption(StringWriter writer, string name, string arg) { // always quote arguments writer.Write("\"/{0}={1}\" ", name, arg); } /// /// Determines whether or not disassembling is needed. /// /// /// if disassembling is needed; otherwise, /// . /// private bool NeedsDisassembling(FileInfo inputFile, FileInfo outputFile) { if (ForceRebuild) { Log(Level.Verbose, ResourceUtils.GetString("String_RebuildAttributeSetToTrue")); return true; } // check if output file already exists if (!outputFile.Exists) { Log(Level.Verbose, ResourceUtils.GetString("String_OutputFileDoesNotExist"), outputFile.FullName); return true; } // check if the source assembly has been updated string fileName = FileSet.FindMoreRecentLastWriteTime( inputFile.FullName, outputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, ResourceUtils.GetString("String_FileHasBeenUpdated"), fileName); return true; } // no need to disassembly the input file return false; } #endregion Private Instance Methods } }