예제 #1
0
		/// <summary>
		/// Amends the specified target assembly, optionally using assembly amendments defined in the 
		/// specified amendment assemblies.
		/// </summary>
		/// <param name="targetAssembly"></param>
		/// <param name="amendmentAssemblies"></param>
		internal static void Amend(string targetAssembly, string[] amendmentAssemblies, string[] referenceAssemblies)
		{
			// Verify the target assembly exists
			targetAssembly = Path.GetFullPath(targetAssembly);
			if (!File.Exists(targetAssembly))
				throw new ArgumentException("The specified target assembly, " + targetAssembly + ", does not exist.");

			// Verify the amendment assemblies exist
			if (amendmentAssemblies == null)
				amendmentAssemblies = new string[0];
			for (int i = 0; i < amendmentAssemblies.Length; i++)
			{
				var path = amendmentAssemblies[i] = Path.GetFullPath(amendmentAssemblies[i]);
				if (!File.Exists(path))
					throw new ArgumentException("The specified amendment assembly, " + path + ", does not exist.");
			}

			// Verify that the target has not already been amended
			var afterthoughtTracker = targetAssembly + ".afterthought";
			if (File.Exists(afterthoughtTracker) && File.GetLastWriteTime(targetAssembly) == File.GetLastWriteTime(afterthoughtTracker))
				return;

			// Determine the set of target directories and backup locations
			var targetWriteTime = File.GetLastWriteTime(targetAssembly);
			var backupTargetAssembly = targetAssembly + ".backup";
			var targetDirectory = Path.GetDirectoryName(targetAssembly);
			File.Delete(backupTargetAssembly);
			File.Move(targetAssembly, backupTargetAssembly);

			// Build up a set of paths with resolving assemblies
			var referencePaths = new Dictionary<string, string>();
			foreach (string path in amendmentAssemblies
				.Union(referenceAssemblies)
				.Union(Directory.GetFiles(targetDirectory).Where(p => p.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) && p != targetAssembly)))
				referencePaths[Path.GetFileName(path)] = path;

			// Register an assembly resolver to look in assembly directories when resolving assemblies
			AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
			{
				var assemblyName = new System.Reflection.AssemblyName(e.Name).Name + ".dll";
				string referencePath;
				if (referencePaths.TryGetValue(assemblyName, out referencePath))
					return System.Reflection.Assembly.LoadFrom(referencePath);

				return null;
			};

			// Get the set of amendments to apply from all of the specified assemblies
			var assemblies = new System.Reflection.Assembly[] { System.Reflection.Assembly.LoadFrom(backupTargetAssembly) }.Union(amendmentAssemblies.Select(a => System.Reflection.Assembly.LoadFrom(a)));
			var amendments = AmendmentAttribute.GetAmendments(assemblies.First(), assemblies.Skip(1).ToArray()).ToList();

			// Exit immediately if there are no amendments in the target assemblies
			if (amendments.Count == 0)
			    return;

			// Amend the target assembly
			Console.Write("Amending " + Path.GetFileName(targetAssembly));
			var start = DateTime.Now;

			using (var host = new PeReader.DefaultHost())
			{
				foreach (var referencePath in referencePaths.Select(p => Path.GetDirectoryName(p.Value)).Distinct())
					host.AddLibPath(referencePath);

				// Load the target assembly
				IModule module = host.LoadUnitFrom(backupTargetAssembly) as IModule;
				if (module == null || module == Dummy.Module || module == Dummy.Assembly)
					throw new ArgumentException(backupTargetAssembly + " is not a PE file containing a CLR assembly, or an error occurred when loading it.");

				// Copy the assembly to enable it to be mutated
				module = new MetadataDeepCopier(host).Copy(module);

				// Load the debug file if it exists
				PdbReader pdbReader = null;
				var pdbFile = Path.Combine(targetDirectory, Path.GetFileNameWithoutExtension(targetAssembly) + ".pdb");
				var backupPdbFile = pdbFile + ".backup";
				if (File.Exists(pdbFile))
				{
					File.Delete(backupPdbFile);
					File.Move(pdbFile, backupPdbFile);
					using (var pdbStream = File.OpenRead(backupPdbFile))
					{
						pdbReader = new PdbReader(pdbStream, host);
					}
				}
					
				// Amend and persist the target assembly
				using (pdbReader)
				{
					// Create and execute a new assembly amender
					AssemblyAmender amender = new AssemblyAmender(host, pdbReader, amendments, assemblies);
					amender.TargetRuntimeVersion = module.TargetRuntimeVersion;
					module = amender.Visit(module);

					// Save the amended assembly back to the original directory
					var localScopeProvider = pdbReader == null ? null : new ILGenerator.LocalScopeProvider(pdbReader);
					using (var pdbWriter = pdbReader != null ? new PdbWriter(pdbFile, pdbReader) : null)
					{
						using (var dllStream = File.Create(targetAssembly))
						{
							PeWriter.WritePeToStream(module, host, dllStream, pdbReader, localScopeProvider, pdbWriter);
						}
					}
				}

				File.SetLastWriteTime(targetAssembly, targetWriteTime);
				if (pdbReader != null)
					File.SetLastWriteTime(pdbFile, targetWriteTime);
			}

			Console.WriteLine(" (" + DateTime.Now.Subtract(start).TotalSeconds.ToString("0.000") + " seconds)");

			// Set the last write time of the afterthought tracker to match the amended assembly to prevent accidental reamending
			File.WriteAllText(afterthoughtTracker, "");
			File.SetLastWriteTime(afterthoughtTracker, File.GetLastWriteTime(targetAssembly));
		}
예제 #2
0
        internal static void Amend(params string[] targets)
        {
            // Ensure that at least one target assembly was specified
            if (targets == null || targets.Length == 0)
                throw new ArgumentException("At least one target assembly must be specified.");

            // Ensure that the target assemblies exist
            for (int i = 0; i < targets.Length; i++)
            {
                var path = targets[i] = Path.GetFullPath(targets[i]);
                if (!File.Exists(path))
                    throw new ArgumentException("The specified target assembly, " + path + ", does not exist.");
            }

            // Determine the set of target directories and backup locations
            var directories = targets
                .Select(path => Path.GetDirectoryName(path).ToLower())
                .Distinct()
                .Select(directory => new { SourcePath = directory, BackupPath = Directory.CreateDirectory(Path.Combine(directory, "Backup")).FullName });

            // Determine the set of dlls, pdbs, and backup files
            var assemblies = targets
                .Select(dllPath => new
                {
                    DllPath = dllPath,
                    DllBackupPath = Path.Combine(Path.Combine(Path.GetDirectoryName(dllPath), "Backup"), Path.GetFileName(dllPath)),
                    PdbPath = Path.Combine(Path.GetDirectoryName(dllPath), Path.GetFileNameWithoutExtension(dllPath) + ".pdb"),
                    PdbBackupPath = Path.Combine(Path.Combine(Path.GetDirectoryName(dllPath), "Backup"), Path.GetFileNameWithoutExtension(dllPath) + ".pdb")
                });

            // Backup the directories containing the targeted dll and pdb files
            foreach (var directory in directories)
            {
                foreach (var file in Directory.GetFiles(directory.SourcePath))
                {
                    if (file.ToLower().EndsWith("exe") || file.ToLower().EndsWith("dll") || file.ToLower().EndsWith("pdb"))
                    {
                        var backupPath = Path.Combine(directory.BackupPath, Path.GetFileName(file));
                        if (File.Exists(backupPath))
                            File.SetAttributes(backupPath, File.GetAttributes(backupPath) & ~FileAttributes.ReadOnly);
                        File.Copy(file, backupPath, true);
                    }
                }
            }

            // Register an assembly resolver to look in backup folders when resolving assemblies
            AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
            {
                var assemblyName = new AssemblyName(e.Name).Name + ".dll";

                // First see if the assembly is located in one of the target backup directories
                foreach (var directory in directories)
                {
                    var dependency = Path.Combine(directory.BackupPath, assemblyName);
                    if (File.Exists(dependency))
                        return System.Reflection.Assembly.LoadFrom(dependency);
                }

                return null;
            };

            // Get the set of amendments to apply from all of the specified assemblies
            var amendments = AmendmentAttribute.GetAmendments(assemblies.Select(a => System.Reflection.Assembly.LoadFrom(a.DllBackupPath)).ToArray()).ToList();

            // Exit immediately if there are no amendments in the target assemblies
            if (amendments.Count == 0)
                return;

            // Process each target assembly individually
            foreach (var assembly in assemblies)
            {
                var assemblyAmendments = amendments.Where(a => a.Type.Assembly.Location == assembly.DllBackupPath).ToArray();
                if (assemblyAmendments.Length == 0)
                    continue;

                Console.Write("Amending " + Path.GetFileName(assembly.DllPath));
                var start = DateTime.Now;

                using (var host = new PeReader.DefaultHost())
                {
                    // Load the target assembly
                    IModule module = host.LoadUnitFrom(assembly.DllBackupPath) as IModule;
                    if (module == null || module == Dummy.Module || module == Dummy.Assembly)
                        throw new ArgumentException(assembly.DllBackupPath + " is not a PE file containing a CLR assembly, or an error occurred when loading it.");

                    // Copy the assembly to enable it to be mutated
                    module = new MetadataDeepCopier(host).Copy(module);

                    // Load the debug file if it exists
                    PdbReader pdbReader = null;
                    if (File.Exists(assembly.PdbBackupPath))
                    {
                        using (var pdbStream = File.OpenRead(assembly.PdbBackupPath))
                        {
                            pdbReader = new PdbReader(pdbStream, host);
                        }
                    }

                    // Amend and persist the target assembly
                    using (pdbReader)
                    {
                        // Create and execute a new assembly amender
                        AssemblyAmender amender = new AssemblyAmender(host, pdbReader, assemblyAmendments);
                        amender.TargetRuntimeVersion = module.TargetRuntimeVersion;
                        module = amender.Visit(module);

                        // Save the amended assembly back to the original directory
                        using (var pdbWriter = pdbReader != null ? new PdbWriter(assembly.PdbPath, pdbReader) : null)
                        {
                            using (var dllStream = File.Create(assembly.DllPath))
                            {
                                PeWriter.WritePeToStream(module, host, dllStream, pdbReader, null, pdbWriter);
                            }
                        }
                    }
                }
                Console.WriteLine(" (" + DateTime.Now.Subtract(start).TotalSeconds.ToString("0.000") + " seconds)");
            }
        }