2016-04-21 15:57:02 +03:00
|
|
|
// Copyright 2012 Xamarin Inc. All rights reserved.
|
|
|
|
//#define DEBUG_COMPARE
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.IO;
|
2018-05-04 12:29:50 +03:00
|
|
|
using System.Linq;
|
2016-04-21 15:57:02 +03:00
|
|
|
using System.Text;
|
2017-06-06 23:32:25 +03:00
|
|
|
using Xamarin.Utils;
|
2016-04-21 15:57:02 +03:00
|
|
|
using Xamarin.Bundler;
|
|
|
|
|
2017-01-03 17:14:47 +03:00
|
|
|
public class Cache {
|
2016-04-21 15:57:02 +03:00
|
|
|
#if MMP
|
|
|
|
const string NAME = "mmp";
|
|
|
|
#elif MTOUCH
|
|
|
|
const string NAME = "mtouch";
|
2020-07-17 17:38:40 +03:00
|
|
|
#elif BUNDLER
|
|
|
|
const string NAME = "dotnet-linker";
|
2016-04-21 15:57:02 +03:00
|
|
|
#else
|
|
|
|
#error Wrong defines
|
|
|
|
#endif
|
|
|
|
|
2017-01-03 17:14:47 +03:00
|
|
|
string cache_dir;
|
|
|
|
bool temporary_cache;
|
2017-01-31 12:18:45 +03:00
|
|
|
string[] arguments;
|
|
|
|
|
|
|
|
public Cache (string[] arguments)
|
|
|
|
{
|
|
|
|
this.arguments = arguments;
|
|
|
|
}
|
2016-04-21 15:57:02 +03:00
|
|
|
|
2017-01-03 17:14:47 +03:00
|
|
|
public bool IsCacheTemporary {
|
2016-04-21 15:57:02 +03:00
|
|
|
get { return temporary_cache; }
|
|
|
|
}
|
|
|
|
|
|
|
|
// see --cache=DIR
|
2017-01-03 17:14:47 +03:00
|
|
|
public string Location {
|
2016-04-21 15:57:02 +03:00
|
|
|
get {
|
|
|
|
if (cache_dir == null) {
|
|
|
|
do {
|
|
|
|
cache_dir = Path.Combine (Path.GetTempPath (), NAME + ".cache", Path.GetRandomFileName ());
|
|
|
|
if (File.Exists (cache_dir) || Directory.Exists (cache_dir))
|
|
|
|
continue;
|
|
|
|
Directory.CreateDirectory (cache_dir);
|
|
|
|
break;
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
cache_dir = Target.GetRealPath (cache_dir);
|
|
|
|
|
|
|
|
temporary_cache = true;
|
|
|
|
if (!Directory.Exists (cache_dir))
|
|
|
|
Directory.CreateDirectory (cache_dir);
|
|
|
|
#if DEBUG
|
|
|
|
Console.WriteLine ("Cache defaults to {0}", cache_dir);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
return cache_dir;
|
|
|
|
}
|
|
|
|
set {
|
|
|
|
cache_dir = value;
|
|
|
|
if (!Directory.Exists (cache_dir))
|
|
|
|
Directory.CreateDirectory (cache_dir);
|
|
|
|
cache_dir = Target.GetRealPath (Path.GetFullPath (cache_dir));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-03 17:14:47 +03:00
|
|
|
public void Clean ()
|
2016-04-21 15:57:02 +03:00
|
|
|
{
|
|
|
|
#if DEBUG
|
|
|
|
Console.WriteLine ("Cache.Clean: {0}" , Location);
|
|
|
|
#endif
|
|
|
|
Directory.Delete (Location, true);
|
|
|
|
Directory.CreateDirectory (Location);
|
|
|
|
}
|
|
|
|
|
2017-05-19 18:05:38 +03:00
|
|
|
public static bool CompareDirectories (string a, string b, bool ignore_cache = false)
|
|
|
|
{
|
|
|
|
if (Driver.Force && !ignore_cache) {
|
|
|
|
Driver.Log (6, "Directories {0} and {1} are considered different because -f was passed to " + NAME + ".", a, b);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var diff = new StringBuilder ();
|
2019-10-14 17:18:46 +03:00
|
|
|
if (Driver.RunCommand ("diff", new [] { "-ur", a, b, }, output: diff, suppressPrintOnErrors: true) != 0) {
|
2017-05-19 18:05:38 +03:00
|
|
|
Driver.Log (1, "Directories {0} and {1} are considered different because diff said so:\n{2}", a, b, diff);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-04-21 15:57:02 +03:00
|
|
|
public static bool CompareFiles (string a, string b, bool ignore_cache = false)
|
|
|
|
{
|
|
|
|
if (Driver.Force && !ignore_cache) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, "Files {0} and {1} are considered different because -f was passed to " + NAME + ".", a, b);
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!File.Exists (b)) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, "Files {0} and {1} are considered different because the latter doesn't exist.", a, b);
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-31 12:19:10 +03:00
|
|
|
using (var astream = new FileStream (a, FileMode.Open, FileAccess.Read, FileShare.Read)) {
|
|
|
|
using (var bstream = new FileStream (b, FileMode.Open, FileAccess.Read, FileShare.Read)) {
|
2016-04-21 15:57:02 +03:00
|
|
|
bool rv;
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, "Comparing files {0} and {1}...", a, b);
|
2016-04-21 15:57:02 +03:00
|
|
|
rv = CompareStreams (astream, bstream, ignore_cache);
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > {0}", rv ? "Identical" : "Different");
|
2016-04-21 15:57:02 +03:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static bool CompareAssemblies (string a, string b, bool ignore_cache = false, bool compare_guids = false)
|
|
|
|
{
|
|
|
|
if (Driver.Force && !ignore_cache) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, "Assemblies {0} and {1} are considered different because -f was passed to " + NAME + ".", a, b);
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!File.Exists (b)) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, "Assemblies {0} and {1} are considered different because the latter doesn't exist.", a, b);
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
using (var astream = new AssemblyReader (a) { CompareGUIDs = compare_guids }) {
|
|
|
|
using (var bstream = new AssemblyReader (b) { CompareGUIDs = compare_guids }) {
|
|
|
|
bool rv;
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, "Comparing assemblies {0} and {1}...", a, b);
|
2016-04-21 15:57:02 +03:00
|
|
|
rv = CompareStreams (astream, bstream, ignore_cache);
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > {0}", rv ? "Identical" : "Different");
|
2016-04-21 15:57:02 +03:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public unsafe static bool CompareStreams (Stream astream, Stream bstream, bool ignore_cache = false)
|
|
|
|
{
|
|
|
|
if (Driver.Force && !ignore_cache) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > streams are considered different because -f was passed to " + NAME + ".");
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (astream.Length != bstream.Length) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > streams are considered different because their lengths do not match.");
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
var ab = new byte[2048];
|
|
|
|
var bb = new byte[2048];
|
|
|
|
|
|
|
|
do {
|
|
|
|
int ar = astream.Read (ab, 0, ab.Length);
|
|
|
|
int br = bstream.Read (bb, 0, bb.Length);
|
|
|
|
|
|
|
|
if (ar != br) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > streams are considered different because their lengths do not match.");
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ar == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
fixed (byte *aptr = ab, bptr = bb) {
|
|
|
|
long *l1 = (long *) aptr;
|
|
|
|
long *l2 = (long *) bptr;
|
|
|
|
int len = ar;
|
|
|
|
// Compare one long at a time.
|
|
|
|
for (int i = 0; i < len / 8; i++) {
|
|
|
|
if (l1 [i] != l2 [i]) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > streams differ at index {0}-{1}", i, i + 8);
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Compare any remaining bytes.
|
|
|
|
int mod = len % 8;
|
|
|
|
if (mod > 0) {
|
|
|
|
for (int i = len - mod; i < len; i++) {
|
|
|
|
if (ab [i] != bb [i]) {
|
2016-10-31 23:53:02 +03:00
|
|
|
Driver.Log (6, " > streams differ at byte index {0}", i);
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (true);
|
|
|
|
}
|
|
|
|
|
2017-01-31 12:18:45 +03:00
|
|
|
string GetArgumentsForCacheData ()
|
2016-04-21 15:57:02 +03:00
|
|
|
{
|
|
|
|
var sb = new StringBuilder ();
|
2017-01-31 12:18:45 +03:00
|
|
|
var args = new List<string> (arguments);
|
2016-04-21 15:57:02 +03:00
|
|
|
|
|
|
|
sb.Append ("# Version: ").Append (Constants.Version).Append ('.').Append (Constants.Revision).AppendLine ();
|
|
|
|
sb.Append (Driver.GetFullPath ()).AppendLine (" \\");
|
[mtouch] Fix cache.cs wrt response files. Fix #7514 (#7650)
TL&DR
* re-apply the fix to cache.cs from https://github.com/xamarin/xamarin-macios/pull/7544
* which was reverted in https://github.com/xamarin/xamarin-macios/pull/7589
* since it regressed mscorlib/sim testing in xharness (for other reasons)
* Final part to fix https://github.com/xamarin/xamarin-macios/issues/7514
This was the ~night~ day before christmas... amd a tough nut to crack!
Thanksfully we had a good test case (inside #7514) and then xharness
regressed one test in consistent, reproducible manner.
xharness builds mscorlib tests twice (32 and 64 bits) even if it's a
fat application (could be reused). That should not be a huge problem
since the 2nd build should be identical and the cache should be (re)used.
An earlier attempt fixed this (comparison was true for the wrong
reasons [1]) but the fix did not end up with the same arguments !?! and
was reverted.
This is the diff between the first and second builds:
```diff
--- /Users/poupou/a.txt 2019-12-23 09:55:01.000000000 -0500
+++ /Users/poupou/b.txt 2019-12-23 09:55:01.000000000 -0500
@@ -182,5 +182,5 @@
-r=/Users/poupou/git/xamarin/xamarin-macios/builds/downloads/ios-release-Darwin-8f396bbb408b5758fccb8602030b9fa5293ce718/ios-bcl/monotouch/tests/Xunit.NetCore.Extensions.dll \
' --target-framework=Xamarin.iOS,v1.0' \
--root-assembly=/Users/poupou/git/xamarin/xamarin-macios/tests/xharness/tmp-test-dir/mscorlib/bin/mscorlib/iPhoneSimulator/Debug-unified/com.xamarin.bcltests.mscorlib.exe \
- ' -v -v -v -v' \
+ ' -v -v' \
@/Users/poupou/git/xamarin/xamarin-macios/tests/xharness/tmp-test-dir/mscorlib/obj/iPhoneSimulator/Debug/response-file.rsp \
```
Since they are not identical the cache is invalidated (which is normal,
cache-wise) and produce an output app that is incorrect (and crash
32bits).
Now there is code to ignore verbosity options (both `-v` and -q`) since
they will not affect what `mtouch` generates. However this was broken
because mtouch's response-file parser is quite basic and stricter the the
specification
spec: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/response-file-compiler-option
issue: https://github.com/xamarin/xamarin-macios/issues/7644
That failed in two different ways
1. note the extra space before the first `-v` in the diff (before the `'`
quote). That skipped the line.
2. there are multiple `-v` in the same line, again that make the
filtering skip the line.
*Unknowns*
It's too close to xmas/vacation so I might not find the reasons/issues
for the following, unanswered questions...
1. Why is the re-build app bundle failing at runtime when p/invoking ?
Something is not regenerated (symbol maps?) ?
2. Why xharness 2nd build has more verbosity than the first one (likely
harmless) ?
[1] the original cache.cs issue (prequel)
issue w/test case: https://github.com/xamarin/xamarin-macios/issues/7514
first attempt: https://github.com/xamarin/xamarin-macios/pull/7544
While incorrect the first attempt to fix `cache.cs` was a logical, if
not entirely complete, fix. Without it this is what we _currently_ cache:
```
/Users/poupou/git/xamarin/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/lib/mtouch/mtouch.exe \
```
and that does not include any of mtouch's arguments, that can change
between executions and (should) invalidate the cache.
In this case it means the cache is used (no difference) but this does
**not** parse the content of the **response file** which is obviously
wrong (and we do have code to process it).
On the original issue's test case this is what makes the difference
between using the same *old* nuget assembly after an update (and fail)
by itself and also because the updated framework was not copied (due
to the 2nd part of the bug report wrt `copyfile`).
OTOH re-using (incorrectly) the cache is what makes xharness's mscorlib
unit tests works right now :(
2020-01-02 23:01:55 +03:00
|
|
|
CollectArgumentsForCache (args, 0, sb);
|
2018-05-04 12:29:50 +03:00
|
|
|
return sb.ToString ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CollectArgumentsForCache (IList<string> args, int firstArgument, StringBuilder sb)
|
|
|
|
{
|
|
|
|
for (int i = firstArgument; i < args.Count; i++) {
|
|
|
|
var arg = args [i];
|
|
|
|
switch (arg) {
|
2016-04-21 15:57:02 +03:00
|
|
|
// Remove arguments that don't affect the cache status.
|
2018-05-04 12:29:50 +03:00
|
|
|
case "":
|
2016-04-21 15:57:02 +03:00
|
|
|
case "/v":
|
|
|
|
case "-v":
|
|
|
|
case "--v":
|
|
|
|
case "/f":
|
|
|
|
case "-f":
|
|
|
|
case "--f":
|
|
|
|
case "/time":
|
|
|
|
case "-time":
|
|
|
|
case "--time":
|
|
|
|
break;
|
|
|
|
default:
|
2018-05-04 12:29:50 +03:00
|
|
|
if (arg [0] == '@')
|
|
|
|
CollectArgumentsForCache (File.ReadAllLines (arg.Substring (1)), 0, sb);
|
|
|
|
|
|
|
|
sb.Append ('\t').Append (StringUtils.Quote (arg)).AppendLine (" \\");
|
2016-04-21 15:57:02 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-31 12:23:08 +03:00
|
|
|
public bool IsCacheValid ()
|
2016-04-21 15:57:02 +03:00
|
|
|
{
|
|
|
|
var name = "arguments";
|
|
|
|
var pcache = Path.Combine (Location, name);
|
|
|
|
|
|
|
|
if (!File.Exists (pcache)) {
|
|
|
|
Driver.Log (3, "A full rebuild will be performed because the cache is either incomplete or entirely missing.");
|
|
|
|
return false;
|
|
|
|
} else if (GetArgumentsForCacheData () != File.ReadAllText (pcache)) {
|
|
|
|
Driver.Log (3, "A full rebuild will be performed because the arguments to " + NAME + " has changed with regards to the cached data.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if mtouch/mmp has been modified.
|
2017-01-31 12:16:48 +03:00
|
|
|
var executable = System.Reflection.Assembly.GetExecutingAssembly ().Location;
|
|
|
|
if (!Application.IsUptodate (executable, pcache)) {
|
|
|
|
Driver.Log (3, "A full rebuild will be performed because " + NAME + " has been modified.");
|
2016-04-21 15:57:02 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-01-03 17:14:47 +03:00
|
|
|
public bool VerifyCache ()
|
2016-04-21 15:57:02 +03:00
|
|
|
{
|
|
|
|
if (!IsCacheValid ()) {
|
|
|
|
Clean ();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-01-03 17:14:47 +03:00
|
|
|
public void ValidateCache ()
|
2016-04-21 15:57:02 +03:00
|
|
|
{
|
|
|
|
var name = "arguments";
|
|
|
|
var pcache = Path.Combine (Location, name);
|
|
|
|
File.WriteAllText (pcache, GetArgumentsForCacheData ());
|
|
|
|
}
|
|
|
|
|
|
|
|
// A stream that reads an assembly and skips the header and the GUID table.
|
|
|
|
class AssemblyReader : Stream {
|
|
|
|
string filename;
|
|
|
|
FileStream stream;
|
|
|
|
long guid_table_start;
|
|
|
|
long guid_table_length;
|
|
|
|
|
|
|
|
public bool CompareGUIDs;
|
|
|
|
|
|
|
|
public AssemblyReader (string filename)
|
|
|
|
{
|
|
|
|
this.filename = filename;
|
|
|
|
|
|
|
|
// Need to figure out where the #GUID table is so we can ignore it.
|
|
|
|
FindGUIDTable ();
|
|
|
|
|
|
|
|
stream = File.OpenRead (filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
public override int Read (byte[] buffer, int offset, int count)
|
|
|
|
{
|
|
|
|
// read the header, always the same 136 bytes, followed by a 4 bytes timestamp (which we must ignore)
|
|
|
|
// the rest (except the #GUID table) is safe to compare.
|
|
|
|
if (stream.Position < 136) {
|
|
|
|
// read the first 136 bytes
|
|
|
|
int read = stream.Read (buffer, offset, 136 - (int) stream.Position);
|
|
|
|
if (stream.Position == 136) {
|
|
|
|
// skip the timestamp
|
|
|
|
stream.Position += 4;
|
|
|
|
// this prints the timestamp:
|
|
|
|
// byte[] buf = new byte[4];
|
|
|
|
// stream.Read (buf, 0, 4);
|
|
|
|
// int t2 = (buf [3] << 24) + (buf [2] << 16) + (buf [1] << 8) + buf [0];
|
|
|
|
// var d = new DateTime (1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
|
|
// var d2 = d.AddSeconds (t2);
|
|
|
|
// Console.WriteLine ("TS of {1}: {0}", d2, filename);
|
|
|
|
}
|
|
|
|
return read; // don't bother reading more, this makes the implementation easier.
|
|
|
|
}
|
|
|
|
|
|
|
|
if (CompareGUIDs)
|
|
|
|
return stream.Read (buffer, offset, count);
|
|
|
|
|
|
|
|
if (stream.Position + count < guid_table_start) {
|
|
|
|
// entire read before guid table
|
|
|
|
return stream.Read (buffer, offset, count);
|
|
|
|
} else if (stream.Position >= guid_table_start + guid_table_length) {
|
|
|
|
// entire read after guid table
|
|
|
|
return stream.Read (buffer, offset, count);
|
|
|
|
} else {
|
|
|
|
int read = 0;
|
|
|
|
// read up intil guid table
|
|
|
|
read = stream.Read (buffer, offset, (int) (guid_table_start - stream.Position));
|
|
|
|
// skip guid table
|
|
|
|
stream.Position += guid_table_length;
|
|
|
|
// read after guid table
|
|
|
|
if (count - read > 0)
|
|
|
|
read += stream.Read (buffer, offset + read, count - read);
|
|
|
|
return read;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FindGUIDTable ()
|
|
|
|
{
|
|
|
|
using (var fs = File.OpenRead (filename)) {
|
|
|
|
using (var str = new BinaryReader (fs)) {
|
|
|
|
str.BaseStream.Position = 0;
|
|
|
|
if (str.ReadByte () != 0x4d || str.ReadByte () != 0x5a)
|
|
|
|
return; // MZ header
|
|
|
|
|
|
|
|
str.BaseStream.Position = 0x80;
|
|
|
|
if (str.ReadByte () != 'P' || str.ReadByte () != 'E' || str.ReadByte () != 0 || str.ReadByte () != 0)
|
|
|
|
return; // PE signature ("PE\0\0")
|
|
|
|
|
|
|
|
// Read the PE file header
|
|
|
|
|
|
|
|
if (str.ReadByte () != 0x4c || str.ReadByte () != 0x01)
|
|
|
|
return; // PE file header -> Machine (always 0x014c)
|
|
|
|
|
|
|
|
ushort sectionCount = str.ReadUInt16 ();
|
|
|
|
str.BaseStream.Position += 12;
|
|
|
|
ushort optionalHeaderSize = str.ReadUInt16 ();
|
|
|
|
if (optionalHeaderSize < 224)
|
|
|
|
return; // optional header is not big enough
|
|
|
|
|
|
|
|
str.BaseStream.Position += 2;
|
|
|
|
|
|
|
|
// Read the optional PE header
|
|
|
|
str.BaseStream.Position += 208;
|
|
|
|
int cliHeaderRVA = str.ReadInt32 ();
|
|
|
|
/*int cliHeaderSize = */str.ReadInt32 ();
|
|
|
|
|
|
|
|
str.BaseStream.Position += 8;
|
|
|
|
|
|
|
|
// Read the sections, looking for the ".text" section.
|
|
|
|
int sectionHeaderPosition = (int) str.BaseStream.Position;
|
|
|
|
int textSectionPosition = -1;
|
|
|
|
uint virtualAddress = uint.MaxValue;
|
|
|
|
uint pointerToRawData = 0;
|
|
|
|
for (int i = 0; i < sectionCount; i++) {
|
|
|
|
str.BaseStream.Position = sectionHeaderPosition + 40 * i;
|
|
|
|
if (str.ReadByte () != '.' || str.ReadByte () != 't' || str.ReadByte () != 'e' || str.ReadByte () != 'x' || str.ReadByte () != 't' || str.ReadByte () != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
textSectionPosition = sectionHeaderPosition + 40 * i;
|
|
|
|
str.BaseStream.Position = textSectionPosition + 12;
|
|
|
|
virtualAddress = str.ReadUInt32 ();
|
|
|
|
str.BaseStream.Position += 4;
|
|
|
|
pointerToRawData = str.ReadUInt32 ();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virtualAddress == uint.MaxValue)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Now we can calculate the file position of the CLI header
|
|
|
|
str.BaseStream.Position = cliHeaderRVA - (virtualAddress - pointerToRawData);
|
|
|
|
str.BaseStream.Position += 8;
|
|
|
|
uint metadataRVA = str.ReadUInt32 ();
|
|
|
|
/*uint metadataSize = */str.ReadUInt32 ();
|
|
|
|
|
|
|
|
// Find and read the metadata header
|
|
|
|
uint metadataRootPosition = metadataRVA - (virtualAddress - pointerToRawData);
|
|
|
|
str.BaseStream.Position = metadataRootPosition;
|
|
|
|
if (str.ReadByte () != 0x42 || str.ReadByte () != 0x53 || str.ReadByte () != 0x4a || str.ReadByte () != 0x42)
|
|
|
|
return; // Invalid magic signature.
|
|
|
|
|
|
|
|
str.BaseStream.Position += 8;
|
|
|
|
int dynamicLength = str.ReadInt32 ();
|
|
|
|
str.BaseStream.Position += dynamicLength;
|
|
|
|
str.BaseStream.Position += 2; // flags
|
|
|
|
ushort metadataStreams = str.ReadUInt16 ();
|
|
|
|
for (ushort i = 0; i < metadataStreams; i++) {
|
|
|
|
uint offset = str.ReadUInt32 ();
|
|
|
|
uint size = str.ReadUInt32 ();
|
|
|
|
byte[] name = new byte [32];
|
|
|
|
|
|
|
|
for (int k = 0; k < 8; k++) {
|
|
|
|
str.Read (name, k * 4, 4);
|
|
|
|
if (name [k * 4 + 3] == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name [0] == '#' && name [1] == 'G' && name [2] == 'U' && name [3] == 'I' && name [4] == 'D' && name [5] == 0) {
|
|
|
|
// found the GUID table.
|
|
|
|
guid_table_start = metadataRootPosition + offset;
|
|
|
|
guid_table_length = size;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void Dispose (bool disposing)
|
|
|
|
{
|
|
|
|
base.Dispose (disposing);
|
|
|
|
stream.Dispose ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void Flush ()
|
|
|
|
{
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override long Seek (long offset, SeekOrigin origin)
|
|
|
|
{
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void SetLength (long value)
|
|
|
|
{
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override void Write (byte[] buffer, int offset, int count)
|
|
|
|
{
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
|
|
|
|
public override bool CanRead {
|
|
|
|
get {
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override bool CanSeek {
|
|
|
|
get {
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override bool CanWrite {
|
|
|
|
get {
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override long Length {
|
|
|
|
get {
|
|
|
|
return stream.Length - 140 - guid_table_length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public override long Position {
|
|
|
|
get {
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
set {
|
|
|
|
throw new NotImplementedException ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if false
|
|
|
|
static public void ComputeDependencies (IEnumerable<string> assemblies, MonoTouchResolver resolver)
|
|
|
|
{
|
|
|
|
// note: Parallel.ForEach (with lock to add on 'digests') turns out (much) slower
|
|
|
|
// (linksdk.app with 20 assemblies)
|
|
|
|
// likely because it's faster (using commoncrypto) than it seems
|
|
|
|
foreach (string a in assemblies) {
|
|
|
|
string key = Path.GetFileNameWithoutExtension (a);
|
|
|
|
using (Stream fs = File.OpenRead (a)) {
|
|
|
|
string digest = ComputeDigest (fs, 140);
|
|
|
|
digests.Add (key, digest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Dictionary<string, HashSet<string>> dependencies = new Dictionary<string, HashSet<string>> ();
|
|
|
|
foreach (string a in assemblies) {
|
|
|
|
HashSet<string> references;
|
|
|
|
AssemblyDefinition ad = resolver.Load (a);
|
|
|
|
foreach (AssemblyNameReference ar in ad.MainModule.AssemblyReferences) {
|
|
|
|
if (!dependencies.TryGetValue (ar.Name, out references)) {
|
|
|
|
references = new HashSet<string> ();
|
|
|
|
dependencies.Add (ar.Name, references);
|
|
|
|
}
|
|
|
|
references.Add (ad.Name.Name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#if DEBUG
|
|
|
|
foreach (var kvp in dependencies) {
|
|
|
|
Console.WriteLine ("The following assemblies depends on {0}", kvp.Key);
|
|
|
|
foreach (var s in kvp.Value)
|
|
|
|
Console.WriteLine ("\t{0}", s);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
// if a dependency has changed everything that depends on it must be cleaned
|
|
|
|
foreach (var kvp in dependencies) {
|
|
|
|
string cname = kvp.Key + ".*.cache." + GetDigestForAssembly (kvp.Key) + ".o";
|
|
|
|
var files = Directory.GetFiles (Location, cname);
|
|
|
|
if (files.Length != 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Clean (kvp.Key + "*");
|
|
|
|
foreach (var deps in kvp.Value)
|
|
|
|
Clean (deps + "*");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|