Welcome to mirror list, hosted at ThFree Co, Russian Federation.

PeVerifier.cs « TestCasesRunner « Mono.Linker.Tests « test - github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: fe980c78db9893d5e2dcdfa8c35593cd15fea7f7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Win32;
using Mono.Cecil;
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Extensions;
using NUnit.Framework;

namespace Mono.Linker.Tests.TestCasesRunner {
	public class PeVerifier {
		private readonly string _peExecutable;

		public PeVerifier ()
		{
			_peExecutable = Environment.OSVersion.Platform == PlatformID.Win32NT ? FindPeExecutableFromRegistry ().ToString () : "pedump";
		}

		public PeVerifier (string peExecutable)
		{
			_peExecutable = peExecutable;
		}

		public virtual void Check (LinkedTestCaseResult linkResult, AssemblyDefinition original)
		{
			ProcessSkipAttributes (linkResult, original, out bool skipCheckEntirely, out HashSet<string> assembliesToSkip);

			if (skipCheckEntirely)
				return;

			foreach (var file in linkResult.OutputAssemblyPath.Parent.Files ()) {
				if (file.ExtensionWithDot != ".exe" && file.ExtensionWithDot != ".dll")
					continue;

				// Always skip the I18N assemblies, for some reason they end up in the output directory on OSX.
				// verification of these fails due to native pointers
				if (file.FileName.StartsWith ("I18N"))
					continue;

				if (assembliesToSkip.Contains (file.FileName))
					continue;

				CheckAssembly (file);
			}
		}

		private void ProcessSkipAttributes (LinkedTestCaseResult linkResult, AssemblyDefinition original, out bool skipCheckEntirely, out HashSet<string> assembliesToSkip)
		{
			var peVerifyAttrs = original.MainModule.GetType (linkResult.TestCase.ReconstructedFullTypeName).CustomAttributes.Where (attr => attr.AttributeType.Name == nameof (SkipPeVerifyAttribute));
			skipCheckEntirely = false;
			assembliesToSkip = new HashSet<string> ();
			foreach (var attr in peVerifyAttrs) {
				var ctorArg = attr.ConstructorArguments.FirstOrDefault ();

				if (!attr.HasConstructorArguments) {
					skipCheckEntirely = true;
				} else if (ctorArg.Type.Name == nameof (SkipPeVerifyForToolchian)) {
					var skipToolchain = (SkipPeVerifyForToolchian)ctorArg.Value;

					if (skipToolchain == SkipPeVerifyForToolchian.Pedump) {
						if (Environment.OSVersion.Platform != PlatformID.Win32NT)
							skipCheckEntirely = true;
					}
					else
						throw new ArgumentException ($"Unhandled platform and toolchain values of {Environment.OSVersion.Platform} and {skipToolchain}");
				} else if (ctorArg.Type.Name == nameof (String)) {
					assembliesToSkip.Add ((string)ctorArg.Value);
				} else {
					throw new ArgumentException ($"Unhandled constructor argument type of {ctorArg.Type} on {nameof (SkipPeVerifyAttribute)}");
				}
			}
		}

		private void CheckAssembly (NPath assemblyPath)
		{
			var capturedOutput = new List<string> ();
			var process = new Process ();
			SetupProcess (process, assemblyPath);
			process.StartInfo.RedirectStandardOutput = true;
			process.OutputDataReceived += (sender, args) => capturedOutput.Add (args.Data);
			process.Start ();
			process.BeginOutputReadLine ();
			process.WaitForExit ();

			if (process.ExitCode != 0) {
				Assert.Fail ($"Invalid IL detected in {assemblyPath}\n{capturedOutput.Aggregate ((buff, s) => buff + Environment.NewLine + s)}");
			}
		}

		protected virtual void SetupProcess (Process process, NPath assemblyPath)
		{
			var exeArgs = Environment.OSVersion.Platform == PlatformID.Win32NT ? $"/nologo {assemblyPath.InQuotes ()}" : $"--verify metadata,code {assemblyPath.InQuotes ()}";
			process.StartInfo.FileName = _peExecutable;
			process.StartInfo.Arguments = exeArgs;
			process.StartInfo.UseShellExecute = false;
			process.StartInfo.CreateNoWindow = true;
			process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

			if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
				process.StartInfo.Environment ["MONO_PATH"] = assemblyPath.Parent.ToString ();
			}
		}

		public static NPath FindPeExecutableFromRegistry ()
		{
			if (Environment.OSVersion.Platform != PlatformID.Win32NT)
				throw new InvalidOperationException ("This method should only be called on windows");

			if (TryFindPeExecutableFromRegustrySubfolder ("NETFXSDK", out NPath result))
				return result;
			if (TryFindPeExecutableFromRegustrySubfolder ("Windows", out result))
				return result;

			throw new InvalidOperationException ("Could not locate a peverify.exe executable");
		}

		private static bool TryFindPeExecutableFromRegustrySubfolder (string subfolder, out NPath peVerifyPath)
		{
			var keyPath = $"SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SDKs\\{subfolder}";
			var key = Registry.LocalMachine.OpenSubKey (keyPath);

			foreach (var sdkKeyName in key.GetSubKeyNames ().OrderBy (name => new Version (name.TrimStart ('v').TrimEnd ('A'))).Reverse ()) {
				var sdkKey = Registry.LocalMachine.OpenSubKey ($"{keyPath}\\{sdkKeyName}");

				var sdkDir = (string)sdkKey.GetValue ("InstallationFolder");
				if (string.IsNullOrEmpty (sdkDir))
					continue;

				var binDir = sdkDir.ToNPath ().Combine ("bin");
				if (!binDir.Exists ())
					continue;

				foreach (var netSdkDirs in binDir.Directories ().OrderBy (dir => dir.FileName)) {
					peVerifyPath = netSdkDirs.Combine ("PEVerify.exe");
					if (peVerifyPath.FileExists ())
						return true;
				}
			}
			peVerifyPath = null;
			return false;
		}
	}
}