// // Mono.AssemblyLinker.AssemblyLinker // // Author(s): // Zoltan Varga (vargaz@freemail.hu) // // (C) Ximian, Inc. http://www.ximian.com // Copyright (C) 2005 Novell, Inc (http://www.novell.com) // using System; using System.IO; using System.Collections; using System.Reflection; using System.Reflection.Emit; using System.Security.Cryptography; using System.Text; using System.Configuration.Assemblies; using Mono.Security.Cryptography; namespace Mono.AssemblyLinker { class ModuleInfo { public string fileName; public string target; } class ResourceInfo { public string name; public string fileName; public string target; public bool isEmbedded; public bool isPrivate; } enum Target { Dll, Exe, Win } public class AssemblyLinker { ArrayList inputFiles = new ArrayList (); ArrayList resources = new ArrayList (); ArrayList cattrs = new ArrayList (); bool fullPaths; string outFile; string entryPoint; string win32IconFile; string win32ResFile; string templateFile; bool isTemplateFile = false; Target target; bool delaysign; string keyfile; string keyname; public static int Main (String[] args) { return new AssemblyLinker ().DynMain (args); } private int DynMain (String[] args) { ParseArgs (args); DoIt (); return 0; } static bool IsStrongNamed (Assembly assembly) { object[] attrs = assembly.GetCustomAttributes (true); foreach (object o in attrs) { if (o is AssemblyKeyFileAttribute) return true; else if (o is AssemblyKeyNameAttribute) return true; } return false; } private void ParseArgs (string[] args) { ArrayList flat_args = new ArrayList (); // Process response files Hashtable response_files = new Hashtable (); foreach (string str in args) { if (str [0] != '@') { flat_args.Add (str); continue; } if (str.Length == 1) ReportMissingFileSpec ("@"); string resfile_name = Path.GetFullPath (str.Substring (1)); if (response_files.ContainsKey (resfile_name)) Report (1006, "Response file '" + resfile_name + "' was already included"); response_files [resfile_name] = resfile_name; LoadArgs (resfile_name, flat_args); } if (flat_args.Count == 0) Usage (); foreach (string str in flat_args) { if ((str [0] != '-') && (str [0] != '/')) { string[] parts = str.Split (','); ModuleInfo mod = new ModuleInfo (); mod.fileName = parts [0]; if (parts.Length > 1) mod.target = parts [1]; inputFiles.Add (mod); continue; } string arg; string opt = GetCommand (str, out arg); ResourceInfo res; switch (opt) { case "help": case "?": Usage (); break; case "embed": { if (arg == null) ReportMissingFileSpec (opt); res = new ResourceInfo (); res.isEmbedded = true; String[] parts = arg.Split (','); res.fileName = parts [0]; if (parts.Length > 1) res.name = parts [1]; if (parts.Length > 2) { switch (parts [2]) { case "public": break; case "private": res.isPrivate = true; break; default: ReportInvalidArgument (opt, parts [2]); break; } } resources.Add (res); break; } case "link": { if (arg == null) ReportMissingFileSpec (opt); res = new ResourceInfo (); String[] parts = arg.Split (','); res.fileName = parts [0]; if (parts.Length > 1) res.name = parts [1]; if (parts.Length > 2) res.target = parts [2]; if (parts.Length > 3) { switch (parts [3]) { case "public": break; case "private": res.isPrivate = true; break; default: ReportInvalidArgument (opt, parts [3]); break; } } resources.Add (res); break; } case "algid": if (arg == null) ReportMissingArgument (opt); try { string realArg = arg; if (realArg.StartsWith ("0x")) realArg = realArg.Substring (2); uint val = Convert.ToUInt32 (realArg, 16); AddCattr (typeof (AssemblyAlgorithmIdAttribute), typeof (uint), val); } catch (Exception) { ReportInvalidArgument (opt, arg); } break; case "base": ReportNotImplemented (opt); break; case "baseaddress": ReportNotImplemented (opt); break; case "bugreport": ReportNotImplemented (opt); break; case "comp": case "company": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyCompanyAttribute), arg); break; case "config": case "configuration": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyConfigurationAttribute), arg); break; case "copy": case "copyright": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyCopyrightAttribute), arg); break; case "c": case "culture": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyCultureAttribute), arg); break; case "delay": case "delaysign": case "delay+": case "delaysign+": AddCattr (typeof (AssemblyDelaySignAttribute), typeof (bool), true); delaysign = true; break; case "delay-": case "delaysign-": delaysign = false; break; case "descr": case "description": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyDescriptionAttribute), arg); break; case "e": case "evidence": if (arg == null) ReportMissingFileSpec (opt); res = new ResourceInfo (); res.name = "Security.Evidence"; res.fileName = arg; res.isEmbedded = true; res.isPrivate = true; resources.Add (res); break; case "fileversion": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyFileVersionAttribute), arg); break; case "flags": if (arg == null) ReportMissingArgument (opt); try { string realArg = arg; if (realArg.StartsWith ("0x")) realArg = realArg.Substring (2); uint val = Convert.ToUInt32 (realArg, 16); AddCattr (typeof (AssemblyFlagsAttribute), typeof (uint), val); } catch (Exception) { ReportInvalidArgument (opt, arg); } break; case "fullpaths": fullPaths = true; break; case "keyf": case "keyfile": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyKeyFileAttribute), arg); keyfile = arg; break; case "keyn": case "keyname": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyKeyNameAttribute), arg); keyname = arg; break; case "main": if (arg == null) ReportMissingText (opt); entryPoint = arg; break; case "nologo": break; case "out": if (arg == null) ReportMissingFileSpec (opt); outFile = arg; break; case "prod": case "product": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyProductAttribute), arg); break; case "productv": case "productversion": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyInformationalVersionAttribute), arg); break; case "t": case "target": if (arg == null) ReportMissingText (opt); switch (arg) { case "lib": case "library": target = Target.Dll; break; case "exe": target = Target.Exe; break; case "win": case "winexe": Report (0, "target:win is not implemented"); break; default: ReportInvalidArgument (opt, arg); break; } break; case "template": if (arg == null) ReportMissingFileSpec (opt); isTemplateFile = true; templateFile = arg; break; case "title": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyTitleAttribute), arg); break; case "trade": case "trademark": if (arg == null) ReportMissingText (opt); AddCattr (typeof (AssemblyTrademarkAttribute), arg); break; case "v": case "version": // This option conflicts with the standard UNIX meaning if (arg == null) { Version (); break; } AddCattr (typeof (AssemblyVersionAttribute), arg); break; case "win32icon": if (arg == null) ReportMissingFileSpec (opt); win32IconFile = arg; break; case "win32res": if (arg == null) ReportMissingFileSpec (opt); win32ResFile = arg; break; default: Report (1013, String.Format ("Unrecognized command line option: '{0}'", opt)); break; } } if ((inputFiles.Count == 0) && (resources.Count == 0)) Report (1016, "No valid input files were specified"); if (outFile == null) Report (1017, "No target filename was specified"); if (target == Target.Dll && (entryPoint != null)) Report (1035, "Libraries cannot have an entry point"); if (target == Target.Exe && (entryPoint == null)) Report (1036, "Entry point required for executable applications"); } private string GetCommand (string str, out string command_arg) { if ((str [0] == '-') && (str.Length > 1) && (str [1] == '-')) str = str.Substring (1); int end_index = str.IndexOfAny (new char[] {':', '='}, 1); string command = str.Substring (1, end_index == -1 ? str.Length - 1 : end_index - 1); if (end_index != -1) { command_arg = str.Substring (end_index+1); if (command_arg == String.Empty) command_arg = null; } else { command_arg = null; } return command.ToLower (); } private void AddCattr (Type attrType, Type arg, object value) { cattrs.Add (new CustomAttributeBuilder (attrType.GetConstructor (new Type [] { arg }), new object [] { value })); } private void AddCattr (Type attrType, object value) { AddCattr (attrType, typeof (string), value); } private void AddResource (ResourceInfo res) { foreach (ResourceInfo res2 in resources) { if (res.name == res2.name) { } } resources.Add (res); } private void PrintVersion () { Console.WriteLine ("Mono Assembly Linker (al.exe) version " + Assembly.GetExecutingAssembly ().GetName ().Version.ToString ()); } private void Version () { PrintVersion (); Environment.Exit (0); } private void Usage () { PrintVersion (); foreach (string s in usage) Console.WriteLine (s); Environment.Exit (0); } private void Report (int errorNum, string msg) { Console.WriteLine (String.Format ("ALINK: error A{0:0000}: {1}", errorNum, msg)); Environment.Exit (1); } private void ReportWarning (int errorNum, string msg) { Console.WriteLine (String.Format ("ALINK: warning A{0:0000}: {1}", errorNum, msg)); } private void ReportInvalidArgument (string option, string value) { Report (1012, String.Format ("'{0}' is not a valid setting for option '{1}'", value, option)); } private void ReportMissingArgument (string option) { Report (1003, String.Format ("Compiler option '{0}' must be followed by an argument", option)); } private void ReportNotImplemented (string option) { Report (0, String.Format ("Compiler option '{0}' is not implemented", option)); } private void ReportMissingFileSpec (string option) { Report (1008, String.Format ("Missing file specification for '{0}' command-line option", option)); } private void ReportMissingText (string option) { Report (1010, String.Format ("Missing ':' for '{0}' option", option)); } // copied from /mcs/mcs/codegen.cs private void SetPublicKey (AssemblyName an, byte[] strongNameBlob) { // check for possible ECMA key if (strongNameBlob.Length == 16) { // will be rejected if not "the" ECMA key an.SetPublicKey (strongNameBlob); } else { // take it, with or without, a private key RSA rsa = CryptoConvert.FromCapiKeyBlob (strongNameBlob); // and make sure we only feed the public part to Sys.Ref byte[] publickey = CryptoConvert.ToCapiPublicKeyBlob (rsa); // AssemblyName.SetPublicKey requires an additional header byte[] publicKeyHeader = new byte [12] { 0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00 }; byte[] encodedPublicKey = new byte [12 + publickey.Length]; Buffer.BlockCopy (publicKeyHeader, 0, encodedPublicKey, 0, 12); Buffer.BlockCopy (publickey, 0, encodedPublicKey, 12, publickey.Length); an.SetPublicKey (encodedPublicKey); } } private void SetKeyPair (AssemblyName aname) { if (keyfile != null) { if (!File.Exists (keyfile)) { Report (1044, String.Format ("Couldn't open '{0}' key file.", keyfile)); } using (FileStream fs = File.OpenRead (keyfile)) { byte[] data = new byte [fs.Length]; try { fs.Read (data, 0, data.Length); if (delaysign) { SetPublicKey (aname, data); } else { CryptoConvert.FromCapiPrivateKeyBlob (data); aname.KeyPair = new StrongNameKeyPair (data); } } catch (CryptographicException) { if (!delaysign) { if (data.Length == 16) { // error # is different for ECMA key Report (1019, "Could not strongname the assembly. " + "ECMA key can only be used to delay-sign assemblies"); } else { Report (1028, String.Format ("Key file {0}' is missing it's private key " + "or couldn't be decoded.", keyfile)); } } else { Report (1044, String.Format ("Couldn't decode '{0}' key file.", keyfile)); } } fs.Close (); } } else if (keyname != null) { // delay-sign doesn't apply to key containers aname.KeyPair = new StrongNameKeyPair (keyname); } } private void DoIt () { AssemblyName aname = new AssemblyName (); aname.Name = Path.GetFileNameWithoutExtension (outFile); SetKeyPair (aname); string fileName = Path.GetFileName (outFile); AssemblyBuilder ab; /* * Emit Manifest * */ if(isTemplateFile) { byte[] pk; AssemblyName myAssm = new AssemblyName(); myAssm.Name = Path.GetFileNameWithoutExtension (templateFile); Assembly assembly = Assembly.Load(myAssm); if (!IsStrongNamed(assembly)){ Report (1055, String.Format ("Assembly specified does not have Strong Name '{0}'","template")); } pk = assembly.GetName().GetPublicKey(); aname.SetPublicKey(pk); aname.HashAlgorithm = assembly.GetName().HashAlgorithm; aname.Version = assembly.GetName().Version; aname.HashAlgorithm = assembly.GetName().HashAlgorithm; } if (fileName != outFile) ab = AppDomain.CurrentDomain.DefineDynamicAssembly (aname, AssemblyBuilderAccess.Save, Path.GetDirectoryName (outFile)); else ab = AppDomain.CurrentDomain.DefineDynamicAssembly (aname, AssemblyBuilderAccess.Save); foreach (CustomAttributeBuilder cb in cattrs) ab.SetCustomAttribute (cb); /* * Emit modules */ foreach (ModuleInfo mod in inputFiles) { MethodInfo mi = typeof (AssemblyBuilder).GetMethod ("AddModule", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic); if (mi == null) Report (0, "Cannot add modules on this runtime: try the Mono runtime instead."); if (mod.target != null) { File.Copy (mod.fileName, mod.target, true); mod.fileName = mod.target; } bool isAssembly = false; try { AssemblyName.GetAssemblyName (mod.fileName); isAssembly = true; } catch (Exception) { } if (isAssembly) ReportWarning (1020, "Ignoring included assembly '" + mod.fileName + "'"); else mi.Invoke (ab, new object [] { mod.fileName }); } /* * Set entry point */ if (entryPoint != null) { string mainClass = entryPoint.Substring (0, entryPoint.LastIndexOf ('.')); string mainMethod = entryPoint.Substring (entryPoint.LastIndexOf ('.') + 1); MethodInfo mainMethodInfo = null; try { Type mainType = ab.GetType (mainClass); if (mainType != null) mainMethodInfo = mainType.GetMethod (mainMethod); } catch (Exception ex) { Console.WriteLine (ex); } if (mainMethodInfo != null) ab.SetEntryPoint (mainMethodInfo); else Report (1037, "Unable to find the entry point method '" + entryPoint + "'"); } /* * Emit resources */ ab.DefineVersionInfoResource (); if (win32IconFile != null) { try { MethodInfo mi = typeof (AssemblyBuilder).GetMethod ("DefineIconResource", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic); if (mi == null) Report (0, "Cannot embed win32 icons on this runtime: try the Mono runtime instead."); mi.Invoke (ab, new object [] { win32IconFile }); } catch (Exception ex) { Report (1031, "Error reading icon '" + win32IconFile + "' --" + ex); } } if (win32ResFile != null) { try { ab.DefineUnmanagedResource (win32ResFile); } catch (Exception ex) { Report (1019, "Metadata failure creating assembly -- " + ex); } } foreach (ResourceInfo res in resources) { if (res.name == null) res.name = Path.GetFileName (res.fileName); foreach (ResourceInfo res2 in resources) if ((res != res2) && (res.name == res2.name)) Report (1046, String.Format ("Resource identifier '{0}' has already been used in this assembly", res.name)); if (res.isEmbedded) { MethodInfo mi = typeof (AssemblyBuilder).GetMethod ("EmbedResourceFile", BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic, null, CallingConventions.Any, new Type [] { typeof (string), typeof (string) }, null); if (mi == null) Report (0, "Cannot embed resources on this runtime: try the Mono runtime instead."); mi.Invoke (ab, new object [] { res.name, res.fileName }); } else { if (res.target != null) { File.Copy (res.fileName, res.target, true); res.fileName = res.target; } ab.AddResourceFile (res.name, res.fileName, res.isPrivate ? ResourceAttributes.Private : ResourceAttributes.Public); } } try { ab.Save (fileName); } catch (Exception ex) { Report (1019, "Metadata failure creating assembly -- " + ex); } } private void LoadArgs (string file, ArrayList args) { StreamReader f = null; string line; try { f = new StreamReader (file); StringBuilder sb = new StringBuilder (); while ((line = f.ReadLine ()) != null){ int t = line.Length; for (int i = 0; i < t; i++){ char c = line [i]; if (c == '"' || c == '\''){ char end = c; for (i++; i < t; i++){ c = line [i]; if (c == end) break; sb.Append (c); } } else if (c == ' '){ if (sb.Length > 0){ args.Add (sb.ToString ()); sb.Length = 0; } } else sb.Append (c); } if (sb.Length > 0){ args.Add (sb.ToString ()); sb.Length = 0; } } } catch (Exception ex) { Report (1007, "Error opening response file '" + file + "' -- '" + ex.Message + "'"); } finally { if (f != null) f.Close (); } } string[] usage = { "Usage: al [options] [sources]", "Options: ('/out' must be specified)", "", " /? or /help Display this usage message", " @ Read response file for more options", " /algid: Algorithm used to hash files (in hexadecimal)", " /base[address]: Base address for the library", " /bugreport: Create a 'Bug Report' file", " /comp[any]: Company name", " /config[uration]: Configuration string", " /copy[right]: Copyright message", " /c[ulture]: Supported culture", " /delay[sign][+|-] Delay sign this assembly", " /descr[iption]: Description", " /e[vidence]: Security evidence file to embed", " /fileversion: Optional Win32 version (overrides assembly version)", " /flags: Assembly flags (in hexadecimal)", " /fullpaths Display files using fully-qualified filenames", " /keyf[ile]: File containing key to sign the assembly", " /keyn[ame]: Key container name of key to sign assembly", " /main: Specifies the method name of the entry point", " /nologo Suppress the startup banner and copyright message", " /out: Output file name for the assembly manifest", " /prod[uct]: Product name", " /productv[ersion]: Product version", " /t[arget]:lib[rary] Create a library", " /t[arget]:exe Create a console executable", " /t[arget]:win[exe] Create a Windows executable", " /template: Specifies an assembly to get default options from", " /title: Title", " /trade[mark]: Trademark message", " /v[ersion]: Version (use * to auto-generate remaining numbers)", " /win32icon: Use this icon for the output", " /win32res: Specifies the Win32 resource file", "", "Sources: (at least one source input is required)", " [,] add file to assembly", " /embed[resource]:[,[,Private]]", " embed the file as a resource in the assembly", " /link[resource]:[,[,[,Private]]]", " link the file as a resource to the assembly", }; } }