Skip to content

Commit 74ec87e

Browse files
committed
Let's do some reporting
1 parent e040a28 commit 74ec87e

File tree

9 files changed

+296
-23
lines changed

9 files changed

+296
-23
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System;
2+
3+
namespace ApplicationUtility;
4+
5+
[AttributeUsage (AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
6+
class AspectReporterAttribute : Attribute
7+
{
8+
public Type AspectType { get; }
9+
10+
public AspectReporterAttribute (Type aspectType)
11+
{
12+
AspectType = aspectType;
13+
}
14+
}

tools/apput/src/Detector.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ namespace ApplicationUtility;
1010
/// detect whether or not the thing is an application aspect
1111
/// we know or we can handle.
1212
/// </summary>
13-
class Detector
13+
public class Detector
1414
{
15-
readonly static List<Type> KnownTopLevelAspects = new () {
15+
// Aspects must be listed in the order of detection, from the biggest (the most generic) to the
16+
// smallest (the least generic) aspects.
17+
public readonly static List<Type> KnownTopLevelAspects = new () {
1618
typeof (ApplicationPackage),
1719
typeof (AssemblyStore),
1820
typeof (ApplicationAssembly),
21+
typeof (NativeAotSharedLibrary),
1922
typeof (SharedLibrary),
2023
};
2124

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using ELFSharp.ELF;
5+
using ELFSharp.ELF.Sections;
6+
7+
namespace ApplicationUtility;
8+
9+
class NativeAotSharedLibrary : SharedLibrary
10+
{
11+
readonly static List<(string sectionName, SectionType type)> NativeAotSections = new () {
12+
("__managedcode", SectionType.ProgBits),
13+
(".dotnet_eh_table", SectionType.ProgBits),
14+
("__unbox", SectionType.ProgBits),
15+
("__modules", SectionType.ProgBits),
16+
(".hydrated", SectionType.NoBits),
17+
};
18+
19+
protected NativeAotSharedLibrary (Stream stream, string libraryName)
20+
: base (stream, libraryName)
21+
{
22+
// TODO: perhaps gather some more stats? Code size, data size, alignment, stuff like that
23+
}
24+
25+
public new static IAspect LoadAspect (Stream stream, IAspectState? state, string? description)
26+
{
27+
if (String.IsNullOrEmpty (description)) {
28+
throw new ArgumentException ("Must be a shared library name", nameof (description));
29+
}
30+
31+
if (!IsNativeAotSharedLibrary (stream, description)) {
32+
throw new InvalidOperationException ("Stream is not a supported NativeAOT shared library");
33+
}
34+
35+
return new NativeAotSharedLibrary (stream, description);
36+
}
37+
38+
public new static IAspectState ProbeAspect (Stream stream, string? description) => new BasicAspectState (IsNativeAotSharedLibrary (stream, description));
39+
40+
static bool IsNativeAotSharedLibrary (Stream stream, string description)
41+
{
42+
if (!IsSupportedELFSharedLibrary (stream, description, out IELF? elf) || elf == null) {
43+
return false;
44+
}
45+
46+
// Just one match should be enough
47+
foreach (var naotSection in NativeAotSections) {
48+
if (HasSection (elf, description, naotSection.sectionName, naotSection.type)) {
49+
return true;
50+
}
51+
}
52+
53+
return false;
54+
}
55+
}

tools/apput/src/Native/SharedLibrary.cs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,36 @@
55
using ELFSharp.ELF;
66
using ELFSharp.ELF.Sections;
77

8-
using ApplicationUtility;
8+
namespace ApplicationUtility;
99

1010
class SharedLibrary : IAspect, IDisposable
1111
{
1212
const uint ELF_MAGIC = 0x464c457f;
1313

14-
public static string AspectName { get; } = "Native shared library";
15-
16-
public bool HasAndroidPayload => payloadSize > 0;
17-
public string Name => libraryName;
18-
1914
readonly ulong payloadOffset;
2015
readonly ulong payloadSize;
2116
readonly string libraryName;
2217
readonly bool is64Bit;
2318
readonly Stream libraryStream;
19+
2420
IELF elf;
2521
bool disposed;
22+
NativeArchitecture nativeArch = NativeArchitecture.Unknown;
23+
24+
public static string AspectName { get; } = "Native shared library";
25+
26+
public bool HasAndroidPayload => payloadSize > 0;
27+
public string Name => libraryName;
28+
public NativeArchitecture TargetArchitecture => nativeArch;
29+
public bool Is64Bit => is64Bit;
30+
31+
protected IELF ELF => elf;
2632

2733
protected SharedLibrary (Stream stream, string libraryName)
2834
{
2935
this.libraryStream = stream;
3036
this.libraryName = libraryName;
31-
(elf, is64Bit) = LoadELF (stream, libraryName);
37+
(elf, is64Bit, nativeArch) = LoadELF (stream, libraryName);
3238
(payloadOffset, payloadSize) = FindAndroidPayload (elf);
3339
}
3440

@@ -80,8 +86,10 @@ public Stream OpenAndroidPayload ()
8086
return new SubStream (libraryStream, (long)payloadOffset, (long)payloadSize);
8187
}
8288

83-
protected static bool IsSupportedELFSharedLibrary (Stream stream, string? description)
89+
protected static bool IsSupportedELFSharedLibrary (Stream stream, string? description, out IELF? elf)
8490
{
91+
elf = null;
92+
8593
if (stream.Length < 4) { // Less than that and we know there isn't room for ELF magic
8694
Log.Debug ($"SharedLibrary: stream ('{description}') is too short to be an ELF image.");
8795
return false;
@@ -102,7 +110,7 @@ protected static bool IsSupportedELFSharedLibrary (Stream stream, string? descri
102110
return false;
103111
}
104112

105-
if (!ELFReader.TryLoad (stream, shouldOwnStream: false, out IELF? elf) || elf == null) {
113+
if (!ELFReader.TryLoad (stream, shouldOwnStream: false, out elf) || elf == null) {
106114
Log.Debug ($"SharedLibrary: stream ('{description}') failed to load as an ELF image while checking support.");
107115
return false;
108116
}
@@ -128,31 +136,38 @@ protected static bool IsSupportedELFSharedLibrary (Stream stream, string? descri
128136
string not = supported ? String.Empty : " not";
129137
Log.Debug ($"SharedLibrary: stream ('{description}') is{not} a supported ELF architecture ('{elf.Machine}')");
130138

131-
elf.Dispose ();
132139
return supported;
133140
}
134141

142+
protected static bool IsSupportedELFSharedLibrary (Stream stream, string? description)
143+
{
144+
if (!IsSupportedELFSharedLibrary (stream, description, out IELF? elf) || elf == null) {
145+
return false;
146+
}
147+
148+
elf.Dispose ();
149+
return true;
150+
}
151+
135152
// We assume below that stream corresponds to a valid and supported by us ELF image. This should have been asserted by
136153
// the `LoadAspect` method.
137-
(IELF elf, bool is64bit) LoadELF (Stream stream, string? libraryName)
154+
(IELF elf, bool is64bit, NativeArchitecture nativeArch) LoadELF (Stream stream, string? libraryName)
138155
{
139156
stream.Seek (0, SeekOrigin.Begin);
140157
if (!ELFReader.TryLoad (stream, shouldOwnStream: false, out IELF? elf) || elf == null) {
141158
Log.Debug ($"SharedLibrary: stream ('{libraryName}') failed to load as an ELF image.");
142159
throw new InvalidOperationException ($"Failed to load ELF library '{libraryName}'.");
143160
}
144161

145-
bool is64 = elf.Machine switch {
146-
Machine.ARM => false,
147-
Machine.Intel386 => false,
148-
149-
Machine.AArch64 => true,
150-
Machine.AMD64 => true,
151-
162+
(bool is64, NativeArchitecture arch) = elf.Machine switch {
163+
Machine.ARM => (false, NativeArchitecture.Arm),
164+
Machine.Intel386 => (false, NativeArchitecture.X86),
165+
Machine.AArch64 => (true, NativeArchitecture.Arm64),
166+
Machine.AMD64 => (true, NativeArchitecture.X64),
152167
_ => throw new NotSupportedException ($"Unsupported ELF architecture '{elf.Machine}'")
153168
};
154169

155-
return (elf, is64);
170+
return (elf, is64, arch);
156171
}
157172

158173
(ulong offset, ulong size) FindAndroidPayload (IELF elf)
@@ -187,8 +202,13 @@ protected static bool IsSupportedELFSharedLibrary (Stream stream, string? descri
187202

188203
public bool HasSection (string name, SectionType type = SectionType.Null)
189204
{
190-
Log.Debug ($"Checking for section '{name}' with type {type} in library '{libraryName}'");
191-
if (!elf.TryGetSection (name, out ISection? section)) {
205+
return HasSection (elf, libraryName, name, type);
206+
}
207+
208+
protected static bool HasSection (IELF elf, string libraryName, string sectionName, SectionType type = SectionType.Null)
209+
{
210+
Log.Debug ($"Checking for section '{sectionName}' with type {type} in library '{libraryName}'");
211+
if (!elf.TryGetSection (sectionName, out ISection? section)) {
192212
Log.Debug ("Section not found");
193213
return false;
194214
}

tools/apput/src/Program.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ static int Main (string[] args)
2020
static int Run (string[] args)
2121
{
2222
IAspect? aspect = Detector.FindAspect (args[0]);
23+
if (aspect == null) {
24+
return 1;
25+
}
26+
Reporter.Report (aspect);
2327
return 0;
2428
}
2529
}

tools/apput/src/Reporter.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace ApplicationUtility;
5+
6+
class Reporter
7+
{
8+
public static void Report (IAspect aspect)
9+
{
10+
Type aspectType = aspect.GetType ();
11+
12+
// We match the type **exactly** on purpose, we only want to dispatch on
13+
// the known top-level aspects here.
14+
Type? knownType = null;
15+
foreach (Type topLevelAspectType in Detector.KnownTopLevelAspects) {
16+
if (topLevelAspectType != aspectType) {
17+
continue;
18+
}
19+
knownType = topLevelAspectType;
20+
break;
21+
}
22+
23+
if (knownType == null) {
24+
throw new InvalidOperationException ($"Internal error: cannot generate report for type '{aspectType}'");
25+
}
26+
27+
IReporter? reporter = CreateSpecificReporter (aspectType, aspect);
28+
if (reporter == null) {
29+
throw new InvalidOperationException ($"Internal error: failed to instantiate reporter for type '{aspectType}'");
30+
}
31+
32+
reporter.Report ();
33+
}
34+
35+
static IReporter? CreateSpecificReporter (Type aspectType, IAspect aspect)
36+
{
37+
Log.Debug ($"Looking for type {aspectType} reporter class");
38+
Assembly asm = typeof(Reporter).Assembly;
39+
Type? reporterType = null;
40+
ConstructorInfo? ctor = null;
41+
var ctorArgs = new Type[] { aspectType };
42+
43+
foreach (Type type in asm.GetTypes ()) {
44+
(reporterType, ctor) = GetAspectReporterRecursively (type, aspectType, ctorArgs);
45+
if (reporterType != null) {
46+
break;
47+
}
48+
}
49+
50+
if (reporterType == null || ctor == null) {
51+
Log.Debug ($"Reporter for type '{aspectType}' not found.");
52+
return null;
53+
}
54+
55+
try {
56+
return (IReporter)ctor.Invoke (new object[] { aspect });
57+
} catch (Exception ex) {
58+
throw new InvalidOperationException ($"Internal error: failed to instantiate reporter type '{reporterType}'", ex);
59+
}
60+
}
61+
62+
static (Type?, ConstructorInfo?) GetAspectReporterRecursively (Type type, Type aspectType, Type[] ctorArgs)
63+
{
64+
if (IsReporterForAspect (type, aspectType, ctorArgs, out ConstructorInfo? ctor)) {
65+
return (type, ctor);
66+
}
67+
68+
foreach (Type nestedType in type.GetNestedTypes ()) {
69+
(Type? matching, ctor) = GetAspectReporterRecursively (nestedType, aspectType, ctorArgs);
70+
if (matching != null) {
71+
return (matching, ctor);
72+
}
73+
}
74+
75+
return (null, null);
76+
}
77+
78+
static bool IsReporterForAspect (Type type, Type aspectType, Type[] ctorArgs, out ConstructorInfo? ctor)
79+
{
80+
ctor = null;
81+
82+
// We don't support generic types, for simplicity
83+
if (type.IsAbstract || !type.IsClass || type.IsGenericType) {
84+
return false;
85+
}
86+
87+
var attr = type.GetCustomAttribute<AspectReporterAttribute> ();
88+
if (attr == null || attr.AspectType != aspectType) {
89+
return false;
90+
}
91+
92+
ctor = type.GetConstructor (ctorArgs);
93+
if (ctor == null) {
94+
throw new InvalidOperationException ($"Internal error: type '{type}' claims to be a reporter for '{aspectType}' but lacks the correct public constructor.");
95+
}
96+
97+
foreach (Type it in type.GetInterfaces ()) {
98+
if (it == typeof(IReporter)) {
99+
return true;
100+
}
101+
}
102+
103+
throw new InvalidOperationException ($"Internal error: type '{type}' claims to be a reporter but it does not implement the IReporter interface");
104+
}
105+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
3+
namespace ApplicationUtility;
4+
5+
abstract class BaseReporter : IReporter
6+
{
7+
const ConsoleColor LabelColor = ConsoleColor.White;
8+
const ConsoleColor ValueColor = ConsoleColor.Green;
9+
10+
public abstract void Report ();
11+
12+
protected void WriteAspectDesc (string text)
13+
{
14+
WriteItem ("Aspect type", text);
15+
}
16+
17+
protected void WriteNativeArch (NativeArchitecture arch)
18+
{
19+
WriteItem ("Native target architecture", arch.ToString ());
20+
}
21+
22+
protected void WriteItem (string label, string value)
23+
{
24+
Write (LabelColor, $"{label}: ");
25+
WriteLine (ValueColor, value);
26+
}
27+
28+
protected void WriteLine ()
29+
{
30+
Console.WriteLine ();
31+
}
32+
33+
protected void WriteLine (ConsoleColor color, string text)
34+
{
35+
Write (color, text);
36+
WriteLine ();
37+
}
38+
protected void Write (ConsoleColor color, string text)
39+
{
40+
ConsoleColor oldFG = Console.ForegroundColor;
41+
try {
42+
Console.ForegroundColor = color;
43+
Console.Write (text);
44+
} finally {
45+
Console.ForegroundColor = oldFG;
46+
}
47+
}
48+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace ApplicationUtility;
2+
3+
interface IReporter
4+
{
5+
void Report ();
6+
}

0 commit comments

Comments
 (0)