Skip to content

Commit 2edbd6c

Browse files
committed
AssemblyStore + Assembly reading
1 parent 339293b commit 2edbd6c

File tree

9 files changed

+218
-98
lines changed

9 files changed

+218
-98
lines changed

tools/apput/src/AssemblyStore/AssemblyStore.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,41 @@ public class AssemblyStore : IAspect
1818
public AndroidTargetArch Architecture { get; private set; } = AndroidTargetArch.None;
1919
public ulong NumberOfAssemblies => (ulong)(Assemblies?.Count ?? 0);
2020

21+
AssemblyStoreAspectState storeState;
22+
string? description;
23+
24+
AssemblyStore (AssemblyStoreAspectState state, string? description)
25+
{
26+
storeState = state;
27+
this.description = description;
28+
}
29+
30+
bool Read ()
31+
{
32+
if (!storeState.Format.Read ()) {
33+
return false;
34+
}
35+
36+
foreach (ApplicationAssembly asm in storeState.Format.Assemblies) {
37+
Assemblies.Add (asm.Name, asm);
38+
}
39+
40+
return true;
41+
}
42+
2143
public static IAspect LoadAspect (Stream stream, IAspectState state, string? description)
2244
{
2345
var storeState = state as AssemblyStoreAspectState;
24-
throw new NotImplementedException ();
46+
if (storeState == null) {
47+
throw new InvalidOperationException ("Internal error: unexpected aspect state. Was ProbeAspect unsuccessful?");
48+
}
49+
50+
var store = new AssemblyStore (storeState, description);
51+
if (store.Read ()) {
52+
return store;
53+
}
54+
55+
throw new InvalidOperationException ($"Failed to load assembly store '{description}'");
2556
}
2657

2758
public static IAspectState ProbeAspect (Stream stream, string? description)

tools/apput/src/AssemblyStore/AssemblyStoreAspectState.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ namespace ApplicationUtility;
44

55
class AssemblyStoreAspectState : BasicAspectState
66
{
7-
public AssemblyStoreHeader Header { get; }
87
public FormatBase Format { get; }
98

10-
public AssemblyStoreAspectState (bool success)
11-
: base (success)
12-
{}
9+
public AssemblyStoreAspectState (FormatBase format)
10+
: base (success: true)
11+
{
12+
Format = format;
13+
}
1314
}

tools/apput/src/AssemblyStore/AssemblyStoreHeader.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ public AssemblyStoreHeader (AssemblyStoreVersion version)
2222
{
2323
Version = version;
2424
}
25+
26+
internal AssemblyStoreHeader ()
27+
: this (new AssemblyStoreVersion ())
28+
{}
2529
}

tools/apput/src/AssemblyStore/AssemblyStoreVersion.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ class AssemblyStoreVersion
99
public AssemblyStoreABI ABI { get; }
1010
public bool Is64Bit { get; }
1111

12+
internal AssemblyStoreVersion ()
13+
{
14+
ABI = AssemblyStoreABI.Unknown;
15+
}
16+
1217
internal AssemblyStoreVersion (uint rawVersion)
1318
{
1419
RawVersion = rawVersion;

tools/apput/src/AssemblyStore/FormatBase.cs

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,64 @@ namespace ApplicationUtility;
77

88
/// <summary>
99
/// `FormatBase` class is the base class for all format-specific validators/readers. It will
10-
/// always implement reading the current (i.e. the `main` branch) assembly store format, with
11-
/// subclasses required to handle differences. Subclasses are expected to override the virtual
10+
/// always implement reading the current (i.e. the `main` branch) assembly store format by default,
11+
/// with subclasses required to handle differences. Subclasses are expected to override the virtual
1212
/// `Read*` methods and completely handle reading of the respective structure, without calling
1313
/// up to the base class.
1414
/// </summary>
1515
abstract class FormatBase
1616
{
17+
protected abstract string LogTag { get; }
18+
1719
protected Stream StoreStream { get; }
1820
protected string? Description { get; }
1921

2022
public AssemblyStoreHeader? Header { get; protected set; }
21-
public List<AssemblyStoreAssemblyDescriptor>? Descriptors { get; protected set; }
23+
public IList<AssemblyStoreAssemblyDescriptor>? Descriptors { get; protected set; }
24+
public IList<ApplicationAssembly> Assemblies { get; protected set; } = null!;
2225

2326
protected FormatBase (Stream storeStream, string? description)
2427
{
2528
this.StoreStream = storeStream;
2629
this.Description = description;
2730
}
2831

29-
public void Read ()
32+
public bool Read ()
3033
{
34+
bool success = true;
3135
using var reader = new BinaryReader (StoreStream, Encoding.UTF8, leaveOpen: true);
3236

3337
// They can be `null` if `Validate` wasn't called for some reason.
34-
if (Header == null && ReadHeader (reader, out AssemblyStoreHeader? header)) {
35-
Header = header;
38+
if (Header == null) {
39+
if (ReadHeader (reader, out AssemblyStoreHeader? header) && header != null) {
40+
Header = header;
41+
} else {
42+
success = false;
43+
Header = new ();
44+
}
3645
}
3746

38-
if (Descriptors == null && ReadAssemblyDescriptors (reader, out List<AssemblyStoreAssemblyDescriptor>? descriptors)) {
39-
Descriptors = descriptors;
47+
if (Descriptors == null) {
48+
if (ReadAssemblyDescriptors (reader, out IList<AssemblyStoreAssemblyDescriptor>? descriptors) && descriptors != null) {
49+
Descriptors = descriptors;
50+
} else {
51+
success = false;
52+
Descriptors = new List<AssemblyStoreAssemblyDescriptor> ().AsReadOnly ();
53+
}
4054
}
55+
56+
if (ReadAssemblies (reader, out IList<ApplicationAssembly>? assemblies) && assemblies != null) {
57+
Assemblies = assemblies;
58+
} else {
59+
success = false;
60+
Assemblies = new List<ApplicationAssembly> ().AsReadOnly ();
61+
}
62+
63+
return success;
4164
}
4265

66+
protected abstract bool ReadAssemblies (BinaryReader reader, out IList<ApplicationAssembly>? assemblies);
67+
4368
public IAspectState Validate ()
4469
{
4570
using var reader = new BinaryReader (StoreStream, Encoding.UTF8, leaveOpen: true);
@@ -48,7 +73,7 @@ public IAspectState Validate ()
4873
Header = header;
4974
}
5075

51-
if (ReadAssemblyDescriptors (reader, out List<AssemblyStoreAssemblyDescriptor>? descriptors)) {
76+
if (ReadAssemblyDescriptors (reader, out IList<AssemblyStoreAssemblyDescriptor>? descriptors)) {
5277
Descriptors = descriptors;
5378
}
5479

@@ -57,6 +82,12 @@ public IAspectState Validate ()
5782

5883
protected abstract IAspectState ValidateInner ();
5984

85+
protected BasicAspectState ValidationFailed (string message)
86+
{
87+
Log.Debug (message);
88+
return new BasicAspectState (false);
89+
}
90+
6091
protected virtual bool ReadHeader (BinaryReader reader, out AssemblyStoreHeader? header)
6192
{
6293
header = null;
@@ -108,7 +139,7 @@ protected virtual bool ReadHeader (BinaryReader reader, out AssemblyStoreHeader?
108139
};
109140
}
110141

111-
protected virtual bool ReadAssemblyDescriptors (BinaryReader reader, out List<AssemblyStoreAssemblyDescriptor>? descriptors)
142+
protected virtual bool ReadAssemblyDescriptors (BinaryReader reader, out IList<AssemblyStoreAssemblyDescriptor>? descriptors)
112143
{
113144
descriptors = null;
114145
try {
@@ -121,7 +152,7 @@ protected virtual bool ReadAssemblyDescriptors (BinaryReader reader, out List<As
121152
return descriptors != null && descriptors.Count > 0;
122153
}
123154

124-
List<AssemblyStoreAssemblyDescriptor>? DoReadAssemblyDescriptors (BinaryReader reader)
155+
IList<AssemblyStoreAssemblyDescriptor>? DoReadAssemblyDescriptors (BinaryReader reader)
125156
{
126157
if (Header == null) {
127158
Log.Debug ($"AssemblyStore/FormatBase: unable to read descriptors, header hasn't been read.");
@@ -165,6 +196,11 @@ protected virtual bool ReadAssemblyDescriptors (BinaryReader reader, out List<As
165196
descriptors.Add (desc);
166197
}
167198

168-
return descriptors;
199+
return descriptors.AsReadOnly ();
200+
}
201+
202+
protected virtual IList<string> ReadAssemblyNames (BinaryReader reader)
203+
{
204+
throw new NotImplementedException ();
169205
}
170206
}

tools/apput/src/AssemblyStore/Format_V2.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34

45
namespace ApplicationUtility;
56

67
class Format_V2 : FormatBase
78
{
9+
protected override string LogTag => "AssemblyStore/Format_V2";
10+
811
public Format_V2 (Stream storeStream, string? description)
912
: base (storeStream, description)
1013
{}
1114

15+
protected override bool ReadAssemblies (BinaryReader reader, out IList<ApplicationAssembly>? assemblies)
16+
{
17+
throw new NotImplementedException ();
18+
}
19+
1220
protected override IAspectState ValidateInner ()
1321
{
1422
throw new NotImplementedException ();
Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,141 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
14
using System.IO;
5+
using System.Text;
26

37
namespace ApplicationUtility;
48

59
class Format_V3 : FormatBase
610
{
11+
protected override string LogTag => "AssemblyStore/Format_V3";
12+
713
public const uint HeaderSize = 5 * sizeof(uint);
814
public const uint IndexEntrySize32 = sizeof(uint) + sizeof(uint) + sizeof(byte);
915
public const uint IndexEntrySize64 = sizeof(ulong) + sizeof(uint) + sizeof(byte);
1016
public const uint AssemblyDescriptorSize = 7 * sizeof(uint);
1117

18+
ulong assemblyNamesOffset;
19+
1220
public Format_V3 (Stream storeStream, string? description)
1321
: base (storeStream, description)
1422
{}
1523

16-
protected override IAspectState ValidateInner ()
24+
protected bool EnsureValidState (string where, out IAspectState? retval)
1725
{
18-
Log.Debug ("AssemblyStore/Format_V3: validating store format.");
26+
retval = null;
1927
if (Header == null || Header.EntryCount == null || Header.IndexEntryCount == null || Header.IndexSize == null) {
20-
return ValidationFailed ($"AssemblyStore/Format_V3: invalid header data.");
28+
retval = ValidationFailed ($"{LogTag}: invalid header data in {where}.");
29+
return false;
2130
}
2231

2332
if (Descriptors == null || Descriptors.Count == 0) {
24-
return ValidationFailed ($"AssemblyStore/Format_V3: no descriptors read.");
33+
retval = ValidationFailed ($"{LogTag}: no descriptors read in {where}.");
34+
return false;
2535
}
2636

27-
// TODO: validate stream size
28-
// TODO: populate
29-
return new AssemblyStoreAspectState (true);
37+
return true;
38+
}
3039

31-
BasicAspectState ValidationFailed (string message)
32-
{
33-
Log.Debug (message);
34-
return new BasicAspectState (false);
40+
protected override IAspectState ValidateInner ()
41+
{
42+
Log.Debug ($"{LogTag}: validating store format.");
43+
if (!EnsureValidState (nameof (ValidateInner), out IAspectState? retval)) {
44+
return retval!;
3545
}
46+
47+
// Repetitive to `EnsureValidState`, but it's better than using `!` all over the place below...
48+
Debug.Assert (Header != null);
49+
Debug.Assert (Header.EntryCount != null);
50+
Debug.Assert (Header.IndexEntryCount != null);
51+
Debug.Assert (Descriptors != null);
52+
53+
ulong indexEntrySize = Header.Version.Is64Bit ? IndexEntrySize64 : IndexEntrySize32;
54+
ulong indexSize = (indexEntrySize * (ulong)Header.IndexEntryCount!);
55+
ulong descriptorsSize = AssemblyDescriptorSize * (ulong)Header.EntryCount!;
56+
ulong requiredStreamSize = HeaderSize + indexSize + descriptorsSize;
57+
58+
// It points to the start of the assembly names block
59+
assemblyNamesOffset = requiredStreamSize;
60+
61+
// This is a trick to avoid having to read all the assembly names, but if the stream is valid, it won't be a
62+
// problem and otherwise, well, we're validating after all. First descriptor's data offset points to the next
63+
// byte after assembly names block.
64+
ulong assemblyNamesSize = ((AssemblyStoreAssemblyDescriptorV3)Descriptors[0]).DataOffset - requiredStreamSize;
65+
requiredStreamSize += assemblyNamesSize;
66+
67+
foreach (var d in Descriptors) {
68+
var desc = (AssemblyStoreAssemblyDescriptorV3)d;
69+
70+
requiredStreamSize += desc.DataSize + desc.DebugDataSize + desc.ConfigDataSize;
71+
}
72+
Log.Debug ($"{LogTag}: calculated the required stream size to be {requiredStreamSize}");
73+
74+
if (requiredStreamSize > Int64.MaxValue) {
75+
return ValidationFailed ($"{LogTag}: required stream size is too long for the stream API to handle.");
76+
}
77+
78+
if ((long)requiredStreamSize != StoreStream.Length) {
79+
return ValidationFailed ($"{LogTag}: stream has invalid size, expected {requiredStreamSize} bytes, found {StoreStream.Length} instead.");
80+
} else {
81+
Log.Debug ($"{LogTag}: stream size is valid.");
82+
}
83+
84+
return new AssemblyStoreAspectState (this);
85+
}
86+
87+
protected override IList<string> ReadAssemblyNames (BinaryReader reader)
88+
{
89+
Debug.Assert (Header != null);
90+
Debug.Assert (Header.EntryCount != null);
91+
92+
reader.BaseStream.Seek ((long)assemblyNamesOffset, SeekOrigin.Begin);
93+
var ret = new List<string> ();
94+
95+
for (ulong i = 0; i < Header.EntryCount; i++) {
96+
uint length = reader.ReadUInt32 ();
97+
if (length == 0) {
98+
continue;
99+
}
100+
101+
byte[] nameBytes = reader.ReadBytes ((int)length);
102+
ret.Add (Encoding.UTF8.GetString (nameBytes));
103+
}
104+
105+
return ret.AsReadOnly ();
106+
}
107+
108+
protected override bool ReadAssemblies (BinaryReader reader, out IList<ApplicationAssembly>? assemblies)
109+
{
110+
Debug.Assert (Descriptors != null);
111+
112+
assemblies = null;
113+
if (!EnsureValidState (nameof (ReadAssemblies), out _)) {
114+
return false;
115+
}
116+
117+
IList<string> assemblyNames = ReadAssemblyNames (reader);
118+
if (assemblyNames.Count != Descriptors.Count) {
119+
Log.Debug ($"{LogTag}: assembly name count ({assemblyNames.Count}) is different to descriptor count ({Descriptors.Count})");
120+
return false;
121+
}
122+
123+
var ret = new List<ApplicationAssembly> ();
124+
for (int i = 0; i < Descriptors.Count; i++) {
125+
var desc = (AssemblyStoreAssemblyDescriptorV3)Descriptors[i];
126+
string name = assemblyNames[i];
127+
var assemblyStream = new SubStream (reader.BaseStream, (long)desc.DataOffset, (long)desc.DataSize);
128+
IAspectState assemblyState = ApplicationAssembly.ProbeAspect (assemblyStream, name);
129+
if (!assemblyState.Success) {
130+
assemblyStream.Dispose ();
131+
continue;
132+
}
133+
134+
var assembly = (ApplicationAssembly)ApplicationAssembly.LoadAspect (assemblyStream, assemblyState, name);
135+
ret.Add (assembly);
136+
}
137+
138+
assemblies = ret.AsReadOnly ();
139+
return true;
36140
}
37141
}

tools/apput/src/Native/SharedLibrary.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public Stream OpenAndroidPayload ()
7777
throw new InvalidOperationException ($"Payload offset of {payloadSize} is too large to support.");
7878
}
7979

80-
return new SharedLibraryPayloadStream (libraryStream, (long)payloadOffset, (long)payloadSize);
80+
return new SubStream (libraryStream, (long)payloadOffset, (long)payloadSize);
8181
}
8282

8383
static bool IsSupportedELFSharedLibrary (Stream stream, string? description)

0 commit comments

Comments
 (0)