[tmt] Update to work with current `libxamarin-app.so` (#8694)

Context: 46f10fe0a5
Context? 68368189d6

`tools/tmt` (46f10fe0) is a utility to print typemap entries contained
within an application.

`tools/tmt` no longer supports dumping typemap entries; it was
possibly broken in 68368189:

	% ./dotnet-local.sh build tools/tmt/*.csproj
	% ./dotnet-local.sh build -c Release samples/HelloWorld/HelloWorld/HelloWorld.DotNet.csproj
	% bin/Debug/bin/tmt samples/HelloWorld/HelloWorld/bin/Release/net9.0-android/*-Signed.apk
	No type maps found in 'samples/HelloWorld/HelloWorld/bin/Release/net9.0-android/com.xamarin.android.helloworld-Signed.apk'

Update the `tools/tmt` utility to support the current format of
typemaps within `libxamarin-app.so`, and update it to generate nicely
formatted Markdown report files instead of the text file output:

	% bin/Debug/bin/tmt samples/HelloWorld/HelloWorld/bin/Release/net9.0-android/*-Signed.apk
	samples/HelloWorld/HelloWorld/bin/Release/net9.0-android/com.xamarin.android.helloworld-Signed.apk!lib/arm64-v8a/libxamarin-app.so:
	  File Type: Xamarin App Release DSO
	  Format version: 2
	  Map kind: Release
	  Map architecture: ARM64
	  Managed to Java entries: 56
	  Java to Managed entries: 46 (without duplicates)
	…

`typemap-v2-Release-ARM64.md` will be created (among other files)
which contains the actual typemap data, in Markdown tabular form:

> # Java to Managed

> | Java type name                                     | Managed type name                                                | Type token ID         | Is Generic?      | MVID                                 |
> | -------------------------------------------------- | ---------------------------------------------------------------- | --------------------- | ---------------- | ------------------------------------ |
> | android/app/Activity                               | Android.App.Activity, Mono.Android                               | 0x02000042 (33554498) | no               | 33da2efb-61bb-4fd5-b529-2dee309a3d65 |
> …
> | java/lang/Object                                   | Java.Lang.Object, Mono.Android                                   | 0x0200008B (33554571) | no               | 33da2efb-61bb-4fd5-b529-2dee309a3d65 |
> …

> # Managed to Java

> | Managed type name                                                       | Java type name                                     | Type token ID         | Is Generic? | Is Duplicate? | MVID                                 |
> | ----------------------------------------------------------------------- | -------------------------------------------------- | --------------------- | ----------- | ------------- | ------------------------------------ |
> | HelloLibrary.LibraryActivity, HelloLibrary.DotNet                       | mono/samples/hello/LibraryActivity                 | 0x02000002 (33554434) | no          | false         | ca140934-068f-47d0-a861-6179233e49aa |
> …
This commit is contained in:
Marek Habersack 2024-02-14 19:00:12 +00:00 коммит произвёл GitHub
Родитель 5472eec991
Коммит 71b6fcc92f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
25 изменённых файлов: 2474 добавлений и 726 удалений

Просмотреть файл

@ -132,7 +132,7 @@ namespace Xamarin.Android.Tasks
} }
// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh // Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
const ulong FORMAT_TAG = 0x015E6972616D58; const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
SortedDictionary <string, string>? environmentVariables; SortedDictionary <string, string>? environmentVariables;
SortedDictionary <string, string>? systemProperties; SortedDictionary <string, string>? systemProperties;

Просмотреть файл

@ -0,0 +1,31 @@
using System;
using System.IO.Hashing;
using System.Text;
namespace Xamarin.Android.Tasks;
static class TypeMapHelper
{
/// <summary>
/// Hash the given Java type name for use in java-to-managed typemap array.
/// </summary>
public static ulong HashJavaName (string name, bool is64Bit)
{
if (name.Length == 0) {
return UInt64.MaxValue;
}
// Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do
// the same
return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit);
}
static ulong HashBytes (byte[] bytes, bool is64Bit)
{
if (is64Bit) {
return XxHash64.HashToUInt64 (bytes);
}
return (ulong)XxHash32.HashToUInt32 (bytes);
}
}

Просмотреть файл

@ -403,32 +403,12 @@ namespace Xamarin.Android.Tasks
TypeMapJava entry = cs.JavaMap[i].Instance; TypeMapJava entry = cs.JavaMap[i].Instance;
// The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit // The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit
entry.JavaNameHash32 = (uint)HashName (entry.JavaName, is64Bit: false); entry.JavaNameHash32 = (uint)TypeMapHelper.HashJavaName (entry.JavaName, is64Bit: false);
hashes32.Add (entry.JavaNameHash32); hashes32.Add (entry.JavaNameHash32);
entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true); entry.JavaNameHash64 = TypeMapHelper.HashJavaName (entry.JavaName, is64Bit: true);
hashes64.Add (entry.JavaNameHash64); hashes64.Add (entry.JavaNameHash64);
} }
ulong HashName (string name, bool is64Bit)
{
if (name.Length == 0) {
return UInt64.MaxValue;
}
// Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do
// the same
return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit);
}
ulong HashBytes (byte[] bytes, bool is64Bit)
{
if (is64Bit) {
return XxHash64.HashToUInt64 (bytes);
}
return (ulong)XxHash32.HashToUInt32 (bytes);
}
} }
} }
} }

Просмотреть файл

@ -10,7 +10,7 @@
#include "monodroid.h" #include "monodroid.h"
#include "xxhash.hh" #include "xxhash.hh"
static constexpr uint64_t FORMAT_TAG = 0x015E6972616D58; static constexpr uint64_t FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x5A4C4158; // 'XALZ', little-endian static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x5A4C4158; // 'XALZ', little-endian
static constexpr uint32_t ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian static constexpr uint32_t ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian
static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 1; // Increase whenever an incompatible change is made to the static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 1; // Increase whenever an incompatible change is made to the

Просмотреть файл

@ -87,6 +87,17 @@ namespace Xamarin.Android.AssemblyStore
ProcessStores (); ProcessStores ();
} }
public AssemblyStoreExplorer (ZipArchive archive, string basePathInArchive, Action<AssemblyStoreExplorerLogLevel, string>? customLogger = null, bool keepStoreInMemory = false)
{
logger = customLogger;
this.keepStoreInMemory = keepStoreInMemory;
StorePath = "<in-memory-archive>";
StoreSetName = StorePath;
ReadStoreSetFromArchive (archive, basePathInArchive);
ProcessStores ();
}
void Logger (AssemblyStoreExplorerLogLevel level, string message) void Logger (AssemblyStoreExplorerLogLevel level, string message)
{ {
if (level == AssemblyStoreExplorerLogLevel.Error) { if (level == AssemblyStoreExplorerLogLevel.Error) {

Просмотреть файл

@ -74,28 +74,35 @@ namespace tmt
return GetSymbol (symbolName) != null; return GetSymbol (symbolName) != null;
} }
public byte[] GetData (string symbolName) public (byte[] data, ISymbolEntry? symbol) GetData (string symbolName)
{ {
Log.Debug ($"Looking for symbol: {symbolName}"); Log.Debug ($"Looking for symbol: {symbolName}");
ISymbolEntry? symbol = GetSymbol (symbolName); ISymbolEntry? symbol = GetSymbol (symbolName);
if (symbol == null) if (symbol == null) {
return EmptyArray; return (EmptyArray, null);
}
if (Is64Bit) { if (Is64Bit) {
var symbol64 = symbol as SymbolEntry<ulong>; var symbol64 = symbol as SymbolEntry<ulong>;
if (symbol64 == null) if (symbol64 == null)
throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 64-bit symbol"); throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 64-bit symbol");
return GetData (symbol64); return (GetData (symbol64), symbol);
} }
var symbol32 = symbol as SymbolEntry<uint>; var symbol32 = symbol as SymbolEntry<uint>;
if (symbol32 == null) if (symbol32 == null)
throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 32-bit symbol"); throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 32-bit symbol");
return GetData (symbol32); return (GetData (symbol32), symbol);
} }
public abstract byte[] GetData (ulong symbolValue, ulong size); public abstract byte[] GetData (ulong symbolValue, ulong size);
public abstract byte[] GetDataFromPointer (ulong pointerValue, ulong size);
public string GetASCIIZFromPointer (ulong pointerValue)
{
return GetASCIIZ (GetDataFromPointer (pointerValue, 0), 0);
}
public string GetASCIIZ (ulong symbolValue) public string GetASCIIZ (ulong symbolValue)
{ {
@ -134,14 +141,27 @@ namespace tmt
return GetData (symbol.PointedSection, size, offset); return GetData (symbol.PointedSection, size, offset);
} }
void LogSectionInfo<T> (Section<T> section) where T: struct
{
Log.Debug ($" section offset in file: 0x{section.Offset:x}; section size: {section.Size}; alignment: {section.Alignment}");
}
protected byte[] GetData (ISection section, ulong size, ulong offset) protected byte[] GetData (ISection section, ulong size, ulong offset)
{ {
Log.Debug ($"AnELF.GetData: section == {section.Name}; size == {size}; offset == {offset:X}"); Log.Debug ($"AnELF.GetData: section == '{section.Name}'; requested data size == {size}; offset in section == 0x{offset:x}");
byte[] data = section.GetContents (); byte[] data = section.GetContents ();
Log.Debug ($" data length: {data.Length} (long: {data.LongLength})"); if (section is Section<ulong> sec64) {
Log.Debug ($" offset: {offset}; size: {size}"); LogSectionInfo (sec64);
} else if (section is Section<uint> sec32) {
LogSectionInfo (sec32);
} else {
throw new NotSupportedException ($"Are we in the 128-bit future yet? Unsupported section type {section.GetType ()}");
}
Log.Debug ($" section data length: {data.Length} (long: {data.LongLength})");
if ((ulong)data.LongLength < (offset + size)) { if ((ulong)data.LongLength < (offset + size)) {
Log.Debug ($" not enough data in section");
return EmptyArray; return EmptyArray;
} }
@ -156,9 +176,18 @@ namespace tmt
return ret; return ret;
} }
/// <summary>
/// Find a relocation corresponding to a pointer at offset <paramref name="pointerOffset"/> into
/// the specified <paramref name="symbol"/>. Returns an `ulong`, which needs to be cast to `uint`
/// for 32-pointers (it can be done safely as the upper 32-bits will be 0 in such cases)
/// </summary>
public abstract ulong DeterminePointerAddress (ISymbolEntry symbol, ulong pointerOffset);
public abstract ulong DeterminePointerAddress (ulong symbolValue, ulong pointerOffset);
public uint GetUInt32 (string symbolName) public uint GetUInt32 (string symbolName)
{ {
return GetUInt32 (GetData (symbolName), 0, symbolName); (byte[] data, _) = GetData (symbolName);
return GetUInt32 (data, 0, symbolName);
} }
public uint GetUInt32 (ulong symbolValue) public uint GetUInt32 (ulong symbolValue)
@ -177,7 +206,8 @@ namespace tmt
public ulong GetUInt64 (string symbolName) public ulong GetUInt64 (string symbolName)
{ {
return GetUInt64 (GetData (symbolName), 0, symbolName); (byte[] data, _) = GetData (symbolName);
return GetUInt64 (data, 0, symbolName);
} }
public ulong GetUInt64 (ulong symbolValue) public ulong GetUInt64 (ulong symbolValue)

Просмотреть файл

@ -4,6 +4,7 @@ using System.IO;
using K4os.Compression.LZ4; using K4os.Compression.LZ4;
using Mono.Cecil; using Mono.Cecil;
using Xamarin.Android.AssemblyStore;
using Xamarin.Tools.Zip; using Xamarin.Tools.Zip;
namespace tmt namespace tmt
@ -12,15 +13,44 @@ namespace tmt
{ {
const uint CompressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian const uint CompressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
Dictionary<string, ZipEntry> assemblies; readonly Dictionary<string, ZipEntry>? individualAssemblies;
ZipArchive apk; readonly Dictionary<string, AssemblyStoreAssembly>? blobAssemblies;
readonly ZipArchive apk;
readonly AssemblyStoreExplorer? assemblyStoreExplorer;
public ApkManagedTypeResolver (ZipArchive apk, string assemblyEntryPrefix) public ApkManagedTypeResolver (ZipArchive apk, string assemblyEntryPrefix)
{ {
this.apk = apk; this.apk = apk;
assemblies = new Dictionary<string, ZipEntry> (StringComparer.Ordinal);
foreach (ZipEntry entry in apk) { if (apk.ContainsEntry ($"{assemblyEntryPrefix}assemblies.blob")) {
blobAssemblies = new Dictionary<string, AssemblyStoreAssembly> (StringComparer.Ordinal);
assemblyStoreExplorer = new AssemblyStoreExplorer (apk, assemblyEntryPrefix, keepStoreInMemory: true);
LoadAssemblyBlobs (apk, assemblyEntryPrefix, assemblyStoreExplorer);
} else {
individualAssemblies = new Dictionary<string, ZipEntry> (StringComparer.Ordinal);
LoadIndividualAssemblies (apk, assemblyEntryPrefix);
}
}
void LoadAssemblyBlobs (ZipArchive apkArchive, string assemblyEntryPrefix, AssemblyStoreExplorer explorer)
{
foreach (AssemblyStoreAssembly assembly in explorer.Assemblies) {
string assemblyName = assembly.Name;
string dllName = assembly.DllName;
if (!String.IsNullOrEmpty (assembly.Store.Arch)) {
assemblyName = $"{assembly.Store.Arch}/{assemblyName}";
dllName = $"{assembly.Store.Arch}/{dllName}";
}
blobAssemblies!.Add (assemblyName, assembly);
blobAssemblies!.Add (dllName, assembly);
}
}
void LoadIndividualAssemblies (ZipArchive apkArchive, string assemblyEntryPrefix)
{
foreach (ZipEntry entry in apkArchive) {
if (!entry.FullName.StartsWith (assemblyEntryPrefix, StringComparison.Ordinal)) { if (!entry.FullName.StartsWith (assemblyEntryPrefix, StringComparison.Ordinal)) {
continue; continue;
} }
@ -29,35 +59,78 @@ namespace tmt
continue; continue;
} }
assemblies.Add (Path.GetFileNameWithoutExtension (entry.FullName), entry); string relativeName = entry.FullName.Substring (assemblyEntryPrefix.Length);
assemblies.Add (entry.FullName, entry); string? dir = Path.GetDirectoryName (relativeName);
string name = Path.GetFileNameWithoutExtension (relativeName);
if (!String.IsNullOrEmpty (dir)) {
name = $"{dir}/{name}";
}
individualAssemblies!.Add (name, entry);
individualAssemblies.Add (entry.FullName, entry);
} }
} }
protected override string? FindAssembly (string assemblyName) protected override string? FindAssembly (string assemblyName)
{ {
if (assemblies.Count == 0) { if (individualAssemblies != null) {
if (individualAssemblies.Count == 0) {
return null;
}
if (!individualAssemblies.TryGetValue (assemblyName, out ZipEntry? entry) || entry == null) {
return null;
}
return entry.FullName;
}
if (blobAssemblies == null || !blobAssemblies.TryGetValue (assemblyName, out AssemblyStoreAssembly? assembly) || assembly == null) {
return null; return null;
} }
if (!assemblies.TryGetValue (assemblyName, out ZipEntry? entry) || entry == null) { return assembly.Name;
return null; }
Stream GetAssemblyStream (string assemblyPath)
{
MemoryStream? stream = null;
if (individualAssemblies != null) {
if (!individualAssemblies.TryGetValue (assemblyPath, out ZipEntry? entry) || entry == null) {
// Should "never" happen - if the assembly wasn't there, FindAssembly should have returned `null`
throw new InvalidOperationException ($"Should not happen: assembly '{assemblyPath}' not found in the APK archive.");
}
stream = new MemoryStream ();
entry.Extract (stream);
return PrepStream (stream);
} }
return entry.FullName; if (blobAssemblies == null) {
throw new InvalidOperationException ("Internal error: blobAssemblies shouldn't be null");
}
if (blobAssemblies == null || !blobAssemblies.TryGetValue (assemblyPath, out AssemblyStoreAssembly? assembly) || assembly == null) {
// Should "never" happen - if the assembly wasn't there, FindAssembly should have returned `null`
throw new InvalidOperationException ($"Should not happen: assembly '{assemblyPath}' not found in the assembly blob.");
}
stream = new MemoryStream ();
assembly.ExtractImage (stream);
return PrepStream (stream);
Stream PrepStream (Stream stream)
{
stream.Seek (0, SeekOrigin.Begin);
return stream;
}
} }
protected override AssemblyDefinition ReadAssembly (string assemblyPath) protected override AssemblyDefinition ReadAssembly (string assemblyPath)
{ {
if (!assemblies.TryGetValue (assemblyPath, out ZipEntry? entry) || entry == null) {
// Should "never" happen - if the assembly wasn't there, FindAssembly should have returned `null`
throw new InvalidOperationException ($"Should not happen: assembly {assemblyPath} not found in the APK archive.");
}
byte[]? assemblyBytes = null; byte[]? assemblyBytes = null;
var stream = new MemoryStream (); Stream stream = GetAssemblyStream (assemblyPath);
entry.Extract (stream);
stream.Seek (0, SeekOrigin.Begin);
// //
// LZ4 compressed assembly header format: // LZ4 compressed assembly header format:
@ -65,25 +138,25 @@ namespace tmt
// uint descriptor_index; // Index into an internal assembly descriptor table // uint descriptor_index; // Index into an internal assembly descriptor table
// uint uncompressed_length; // Size of assembly, uncompressed // uint uncompressed_length; // Size of assembly, uncompressed
// //
using (var reader = new BinaryReader (stream)) { using var reader = new BinaryReader (stream);
uint magic = reader.ReadUInt32 (); uint magic = reader.ReadUInt32 ();
if (magic == CompressedDataMagic) { if (magic == CompressedDataMagic) {
reader.ReadUInt32 (); // descriptor index, ignore reader.ReadUInt32 (); // descriptor index, ignore
uint decompressedLength = reader.ReadUInt32 (); uint decompressedLength = reader.ReadUInt32 ();
int inputLength = (int)(stream.Length - 12); int inputLength = (int)(stream.Length - 12);
byte[] sourceBytes = Utilities.BytePool.Rent (inputLength); byte[] sourceBytes = Utilities.BytePool.Rent (inputLength);
reader.Read (sourceBytes, 0, inputLength); reader.Read (sourceBytes, 0, inputLength);
assemblyBytes = Utilities.BytePool.Rent ((int)decompressedLength); assemblyBytes = Utilities.BytePool.Rent ((int)decompressedLength);
int decoded = LZ4Codec.Decode (sourceBytes, 0, inputLength, assemblyBytes, 0, (int)decompressedLength); int decoded = LZ4Codec.Decode (sourceBytes, 0, inputLength, assemblyBytes, 0, (int)decompressedLength);
if (decoded != (int)decompressedLength) { if (decoded != (int)decompressedLength) {
throw new InvalidOperationException ($"Failed to decompress LZ4 data of {assemblyPath} (decoded: {decoded})"); throw new InvalidOperationException ($"Failed to decompress LZ4 data of {assemblyPath} (decoded: {decoded})");
}
Utilities.BytePool.Return (sourceBytes);
} }
Utilities.BytePool.Return (sourceBytes);
} }
if (assemblyBytes != null) { if (assemblyBytes != null) {
stream.Close (); stream.Close ();
stream.Dispose (); stream.Dispose ();

Просмотреть файл

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ELFSharp;
using ELFSharp.ELF; using ELFSharp.ELF;
using ELFSharp.ELF.Sections; using ELFSharp.ELF.Sections;
@ -21,6 +21,83 @@ namespace tmt
: base (stream, filePath, elf, dynsymSection, rodataSection, symSection) : base (stream, filePath, elf, dynsymSection, rodataSection, symSection)
{} {}
public override ulong DeterminePointerAddress (ulong symbolValue, ulong pointerOffset)
{
const string RelDynSectionName = ".rel.dyn";
ISection? sec = GetSection (ELF, RelDynSectionName);
if (sec == null) {
Log.Warning ($"{FilePath} does not contain dynamic relocation section ('{RelDynSectionName}')");
return 0;
}
var relDyn = sec as Section<uint>;
if (relDyn == null) {
Log.Warning ($"Invalid section type, expected 'Section<uint>', got '{sec.GetType ()}'");
return 0;
}
List<ELF32Relocation> rels = LoadRelocations (relDyn);
if (rels.Count == 0) {
Log.Warning ($"Relocation section '{RelDynSectionName}' is empty");
return 0;
}
uint symRelocAddress = (uint)symbolValue + (uint)pointerOffset;
Log.Debug ($"Pointer relocation address == 0x{symRelocAddress:x}");
ulong fileOffset = Relocations.GetValue (ELF, rels, symRelocAddress);
Log.Debug ($"File offset == 0x{fileOffset:x}");
return fileOffset;
}
public override ulong DeterminePointerAddress (ISymbolEntry symbol, ulong pointerOffset)
{
var sym32 = symbol as SymbolEntry<uint>;
if (sym32 == null) {
throw new ArgumentException ("must be of type SymbolEntry<uint>, was {symbol.GetType ()}", nameof (symbol));
}
return DeterminePointerAddress (sym32.Value, pointerOffset);
}
byte[] GetDataFromPointer (uint pointerValue, uint size)
{
Log.Debug ($"Looking for section containing pointer 0x{pointerValue:x}");
uint dataOffset = 0;
Section<uint>? section = null;
foreach (Section<uint> s in ELF.Sections) {
if (s.Type != SectionType.ProgBits) {
continue;
}
if (s.LoadAddress > pointerValue || (s.LoadAddress + s.Size) < pointerValue) {
continue;
}
Log.Debug ($" Section '{s.Name}' matches");
// Pointer is a load address, we convert it to the in-section offset by subtracting section load address from
// the pointer
dataOffset = pointerValue - s.LoadAddress;
Log.Debug ($" Pointer data section offset: 0x{dataOffset:x}");
section = s;
break;
}
if (section == null) {
throw new InvalidOperationException ($"Data for pointer 0x{pointerValue:x} not located");
}
return GetData (section, size, dataOffset);
}
public override byte[] GetDataFromPointer (ulong pointerValue, ulong size)
{
return GetDataFromPointer ((uint)pointerValue, (uint)size);
}
public override byte[] GetData (ulong symbolValue, ulong size = 0) public override byte[] GetData (ulong symbolValue, ulong size = 0)
{ {
checked { checked {
@ -54,6 +131,24 @@ namespace tmt
return GetData (symbol, symbol.Size, offset); return GetData (symbol, symbol.Size, offset);
} }
List<ELF32Relocation> LoadRelocations (Section<uint> section)
{
var ret = new List<ELF32Relocation> ();
byte[] data = section.GetContents ();
ulong offset = 0;
Log.Debug ($"Relocation section '{section.Name}' data length == {data.Length}");
while (offset < (ulong)data.Length) {
uint relOffset = Helpers.ReadUInt32 (data, ref offset, Is64Bit);
uint relInfo = Helpers.ReadUInt32 (data, ref offset, Is64Bit);
ret.Add (new ELF32Relocation (relOffset, relInfo));
}
return ret;
}
Section<uint> FindSectionForValue (uint symbolValue) Section<uint> FindSectionForValue (uint symbolValue)
{ {
Log.Debug ($"FindSectionForValue ({symbolValue:X08})"); Log.Debug ($"FindSectionForValue ({symbolValue:X08})");

Просмотреть файл

@ -0,0 +1,13 @@
namespace tmt;
class ELF32Relocation
{
public uint Offset { get; }
public uint Info { get; }
public ELF32Relocation (uint offset, uint info)
{
Offset = offset;
Info = info;
}
}

Просмотреть файл

@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using ELFSharp;
using ELFSharp.ELF; using ELFSharp.ELF;
using ELFSharp.ELF.Sections; using ELFSharp.ELF.Sections;
@ -21,6 +21,78 @@ namespace tmt
: base (stream, filePath, elf, dynsymSection, rodataSection, symSection) : base (stream, filePath, elf, dynsymSection, rodataSection, symSection)
{} {}
public override ulong DeterminePointerAddress (ISymbolEntry symbol, ulong pointerOffset)
{
var sym64 = symbol as SymbolEntry<ulong>;
if (sym64 == null) {
throw new ArgumentException ("must be of type SymbolEntry<ulong>, was {symbol.GetType ()}", nameof (symbol));
}
return DeterminePointerAddress (sym64.Value, pointerOffset);
}
public override ulong DeterminePointerAddress (ulong symbolValue, ulong pointerOffset)
{
const string RelaDynSectionName = ".rela.dyn";
ISection? sec = GetSection (ELF, RelaDynSectionName);
if (sec == null) {
Log.Warning ($"{FilePath} does not contain dynamic relocation section ('{RelaDynSectionName}')");
return 0;
}
var relaDyn = sec as Section<ulong>;
if (relaDyn == null) {
Log.Warning ($"Invalid section type, expected 'Section<ulong>', got '{sec.GetType ()}'");
return 0;
}
List<ELF64RelocationAddend> rels = LoadRelocationsAddend (relaDyn);
if (rels.Count == 0) {
Log.Warning ($"Relocation section '{RelaDynSectionName}' is empty");
return 0;
}
ulong symRelocAddress = symbolValue + pointerOffset;
Log.Debug ($"Pointer relocation address == 0x{symRelocAddress:x}");
ulong fileOffset = Relocations.GetValue (ELF, rels, symRelocAddress);
Log.Debug ($"File offset == 0x{fileOffset:x}");
return fileOffset;
}
public override byte[] GetDataFromPointer (ulong pointerValue, ulong size)
{
Log.Debug ($"Looking for section containing pointer 0x{pointerValue:x}");
ulong dataOffset = 0;
Section<ulong>? section = null;
foreach (Section<ulong> s in ELF.Sections) {
if (s.Type != SectionType.ProgBits) {
continue;
}
if (s.LoadAddress > pointerValue || (s.LoadAddress + s.Size) < pointerValue) {
continue;
}
Log.Debug ($" Section '{s.Name}' matches");
// Pointer is a load address, we convert it to the in-section offset by subtracting section load address from
// the pointer
dataOffset = pointerValue - s.LoadAddress;
Log.Debug ($" Pointer data section offset: 0x{dataOffset:x}");
section = s;
break;
}
if (section == null) {
throw new InvalidOperationException ($"Data for pointer 0x{pointerValue:x} not located");
}
return GetData (section, size, dataOffset);
}
public override byte[] GetData (ulong symbolValue, ulong size = 0) public override byte[] GetData (ulong symbolValue, ulong size = 0)
{ {
Log.Debug ($"ELF64.GetData: Looking for symbol value {symbolValue:X08}"); Log.Debug ($"ELF64.GetData: Looking for symbol value {symbolValue:X08}");
@ -30,7 +102,7 @@ namespace tmt
symbol = GetSymbol (Symbols, symbolValue); symbol = GetSymbol (Symbols, symbolValue);
} }
if (symbol != null) { if (symbol != null && symbol.PointedSection != null) {
Log.Debug ($"ELF64.GetData: found in section {symbol.PointedSection.Name}"); Log.Debug ($"ELF64.GetData: found in section {symbol.PointedSection.Name}");
return GetData (symbol); return GetData (symbol);
} }
@ -46,6 +118,26 @@ namespace tmt
return GetData (symbol, symbol.Size, OffsetInSection (symbol.PointedSection, symbol.Value)); return GetData (symbol, symbol.Size, OffsetInSection (symbol.PointedSection, symbol.Value));
} }
List<ELF64RelocationAddend> LoadRelocationsAddend (Section<ulong> section)
{
var ret = new List<ELF64RelocationAddend> ();
byte[] data = section.GetContents ();
ulong offset = 0;
ulong numEntries = (ulong)data.Length / 24; // One record is 3 64-bit words
Log.Debug ($"Relocation section '{section.Name}' data length == {data.Length}; entries == {numEntries}");
while ((ulong)ret.Count < numEntries) {
ulong relOffset = Helpers.ReadUInt64 (data, ref offset, Is64Bit);
ulong relInfo = Helpers.ReadUInt64 (data, ref offset, Is64Bit);
long relAddend = Helpers.ReadInt64 (data, ref offset, Is64Bit);
ret.Add (new ELF64RelocationAddend (relOffset, relInfo, relAddend));
}
Log.Debug ($"Read {ret.Count} entries");
return ret;
}
Section<ulong> FindSectionForValue (ulong symbolValue) Section<ulong> FindSectionForValue (ulong symbolValue)
{ {
Log.Debug ($"FindSectionForValue ({symbolValue:X08})"); Log.Debug ($"FindSectionForValue ({symbolValue:X08})");

Просмотреть файл

@ -0,0 +1,15 @@
namespace tmt;
class ELF64RelocationAddend
{
public ulong Offset { get; }
public ulong Info { get; }
public long Addend { get; }
public ELF64RelocationAddend (ulong offset, ulong info, long addend)
{
Offset = offset;
Info = info;
Addend = addend;
}
}

158
tools/tmt/Helpers.cs Normal file
Просмотреть файл

@ -0,0 +1,158 @@
using System;
namespace tmt;
static class Helpers
{
static void PrepareForRead<T> (byte[] data, ref ulong offset, out ulong dataSize, bool is64Bit, bool packed)
{
if (!packed) {
offset = AdjustOffset<T> (offset, is64Bit);
}
dataSize = GetTypeSize<T> (is64Bit);
if ((ulong)data.Length < (offset + dataSize)) {
throw new InvalidOperationException ($"Not enough data to read a {GetNiceName<T> (is64Bit)}");
}
}
static uint ReadUInt32_NoPrep (byte[] data, ref ulong offset, ulong dataSize)
{
uint ret = BitConverter.ToUInt32 (data, (int)offset);
offset += dataSize;
return ret;
}
public static uint ReadUInt32 (byte[] data, ref ulong offset, bool is64Bit, bool packed = false)
{
ulong dataSize;
PrepareForRead<uint> (data, ref offset, out dataSize, is64Bit, packed);
return ReadUInt32_NoPrep (data, ref offset, dataSize);
}
static ulong ReadUInt64_NoPrep (byte[] data, ref ulong offset, ulong dataSize)
{
ulong ret = BitConverter.ToUInt64 (data, (int)offset);
offset += dataSize;
return ret;
}
public static ulong ReadUInt64 (byte[] data, ref ulong offset, bool is64Bit, bool packed = false)
{
ulong dataSize;
PrepareForRead<ulong> (data, ref offset, out dataSize, is64Bit, packed);
return ReadUInt64_NoPrep (data, ref offset, dataSize);
}
public static long ReadInt64 (byte[] data, ref ulong offset, bool is64Bit, bool packed = false)
{
ulong dataSize;
PrepareForRead<long> (data, ref offset, out dataSize, is64Bit, packed);
long ret = BitConverter.ToInt64 (data, (int)offset);
offset += dataSize;
return ret;
}
public static ulong ReadPointer (byte[] data, ref ulong offset, bool is64Bit, bool packed = false)
{
ulong dataSize;
PrepareForRead<UIntPtr> (data, ref offset, out dataSize, is64Bit, packed);
if (is64Bit) {
return ReadUInt64_NoPrep (data, ref offset, dataSize);
}
return (ulong)ReadUInt32_NoPrep (data, ref offset, dataSize);
}
static (ulong modulo, ulong typeSize) GetPaddingModuloAndTypeSize<T> (bool is64Bit)
{
ulong typeSize = GetTypeSize<T> (is64Bit);
ulong modulo;
if (is64Bit) {
modulo = typeSize < 8 ? 4u : 8u;
} else {
modulo = 4u;
}
return (modulo, typeSize);
}
public static ulong GetPaddedSize<S> (ulong sizeSoFar, bool is64Bit)
{
(ulong typeSize, ulong modulo) = GetPaddingModuloAndTypeSize<S> (is64Bit);
ulong alignment = sizeSoFar % modulo;
if (alignment == 0) {
return typeSize;
}
return typeSize + (modulo - alignment);
}
static ulong AdjustOffset<T> (ulong currentOffset, bool is64Bit)
{
(ulong _, ulong modulo) = GetPaddingModuloAndTypeSize<T> (is64Bit);
return currentOffset + (currentOffset % modulo);
}
static ulong GetTypeSize<S> (bool is64Bit)
{
Type type = typeof(S);
if (type == typeof(string) || type == typeof(IntPtr) || type == typeof(UIntPtr)) {
// We treat `string` as a generic pointer
return is64Bit ? 8u : 4u;
}
if (type == typeof(byte)) {
return 1u;
}
if (type == typeof(bool)) {
return 1u;
}
if (type == typeof(Int32) || type == typeof(UInt32)) {
return 4u;
}
if (type == typeof(Int64) || type == typeof(UInt64)) {
return 8u;
}
throw new InvalidOperationException ($"Unable to map managed type {type} to native assembler type");
}
static string GetNiceName<T> (bool is64Bit)
{
Type type = typeof(T);
if (type == typeof(string) || type == typeof(IntPtr) || type == typeof(UIntPtr)) {
// We treat `string` as a generic pointer
string bitness = is64Bit ? "64" : "32";
return $"{bitness}-bit pointer";
}
if (type == typeof(byte)) {
return "byte";
}
if (type == typeof(bool)) {
return "boolean";
}
if (type == typeof(Int32) || type == typeof(UInt32)) {
return "32-bit integer";
}
if (type == typeof(Int64) || type == typeof(UInt64)) {
return "64-bit integer";
}
throw new NotSupportedException ($"Type {type} is not supported");;
}
}

Просмотреть файл

@ -4,42 +4,140 @@ namespace tmt
{ {
static class Log static class Log
{ {
public const ConsoleColor ErrorColor = ConsoleColor.Red;
public const ConsoleColor WarningColor = ConsoleColor.Yellow;
public const ConsoleColor InfoColor = ConsoleColor.Green;
public const ConsoleColor DebugColor = ConsoleColor.DarkGray;
static bool showDebug = false; static bool showDebug = false;
static void WriteStderr (string message)
{
Console.Error.Write (message);
}
static void WriteStderr (ConsoleColor color, string message)
{
ConsoleColor oldFG = Console.ForegroundColor;
Console.ForegroundColor = color;
WriteStderr (message);
Console.ForegroundColor = oldFG;
}
static void WriteLineStderr (string message)
{
Console.Error.WriteLine (message);
}
static void WriteLineStderr (ConsoleColor color, string message)
{
ConsoleColor oldFG = Console.ForegroundColor;
Console.ForegroundColor = color;
WriteLineStderr (message);
Console.ForegroundColor = oldFG;
}
static void Write (string message)
{
Console.Write (message);
}
static void Write (ConsoleColor color, string message)
{
ConsoleColor oldFG = Console.ForegroundColor;
Console.ForegroundColor = color;
Write (message);
Console.ForegroundColor = oldFG;
}
static void WriteLine (string message)
{
Console.WriteLine (message);
}
static void WriteLine (ConsoleColor color, string message)
{
ConsoleColor oldFG = Console.ForegroundColor;
Console.ForegroundColor = color;
WriteLine (message);
Console.ForegroundColor = oldFG;
}
public static void SetVerbose (bool verbose) public static void SetVerbose (bool verbose)
{ {
showDebug = verbose; showDebug = verbose;
} }
public static void Error (string message = "") public static void Error (string message = "")
{
Error (tag: String.Empty, message);
}
public static void Error (string tag, string message)
{ {
if (message.Length > 0) { if (message.Length > 0) {
Console.Error.Write ("Error: "); WriteStderr (ErrorColor, "[E] ");
} }
Console.Error.WriteLine (message);
if (tag.Length > 0) {
WriteStderr (ErrorColor, $"{tag}: ");
}
WriteLineStderr (message);
} }
public static void Warning (string message = "") public static void Warning (string message = "")
{
Warning (tag: String.Empty, message);
}
public static void Warning (string tag, string message)
{ {
if (message.Length > 0) { if (message.Length > 0) {
Console.Error.Write ("Warning: "); WriteStderr (WarningColor, "[W] ");
} }
Console.Error.WriteLine (message); if (tag.Length > 0) {
WriteStderr (WarningColor, $"{tag}: ");
}
WriteLineStderr (message);
} }
public static void Info (string message = "") public static void Info (string message = "")
{ {
Console.WriteLine (message); Info (tag: String.Empty, message);
}
public static void Info (string tag, string message)
{
if (tag.Length > 0) {
Write (InfoColor, $"{tag}: ");
}
WriteLine (InfoColor,message);
} }
public static void Debug (string message = "") public static void Debug (string message = "")
{
Debug (tag: String.Empty, message);
}
public static void Debug (string tag, string message)
{ {
if (!showDebug) { if (!showDebug) {
return; return;
} }
Console.WriteLine (message); if (message.Length > 0) {
Write (DebugColor, "[D] ");
}
if (tag.Length > 0) {
Write (DebugColor, $"{tag}: ");
}
WriteLine (message);
} }
public static void ExceptionError (string message, Exception ex) public static void ExceptionError (string message, Exception ex)

Просмотреть файл

@ -40,7 +40,7 @@ namespace tmt
AlreadyWarned.Add (assemblyName); AlreadyWarned.Add (assemblyName);
} }
return $"[token id: {tokenID}]"; return $"`token id: {tokenID}`";
} }
protected bool TryLookup (string assemblyPath, Guid mvid, uint tokenID, out string typeName) protected bool TryLookup (string assemblyPath, Guid mvid, uint tokenID, out string typeName)

133
tools/tmt/Relocations.cs Normal file
Просмотреть файл

@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using ELFSharp.ELF;
namespace tmt;
static class Relocations
{
public static ulong GetValue (ELF<uint> elf, List<ELF32Relocation> rels, uint symbolOffset)
{
return elf.Machine switch {
Machine.ARM => GetValue_Arm32 (rels, symbolOffset),
Machine.Intel386 => GetValue_x86 (rels, symbolOffset),
_ => throw new NotSupportedException ($"ELF machine {elf.Machine} is not supported")
};
}
public static ulong GetValue (ELF<ulong> elf, List<ELF64RelocationAddend> rels, ulong symbolOffset)
{
return elf.Machine switch {
Machine.AArch64 => GetValue_AArch64 (rels, symbolOffset),
Machine.AMD64 => GetValue_x64 (rels, symbolOffset),
_ => throw new NotSupportedException ($"ELF machine {elf.Machine} is not supported")
};
}
static ulong GetValue_AArch64 (List<ELF64RelocationAddend> rels, ulong symbolOffset)
{
// Documentation: https://github.com/ARM-software/abi-aa/releases/download/2023Q3/aaelf64.pdf page 36, Elf64 Code 1027
const ulong R_AARCH64_RELATIVE = 0x403;
foreach (ELF64RelocationAddend rel in rels) {
if (rel.Offset != symbolOffset) {
continue;
}
ulong relType = GetRelType (rel);
if (relType != R_AARCH64_RELATIVE) {
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
return 0;
}
// In this case, this is offset into the file where pointed-to data begins
return (ulong)rel.Addend;
}
Log.Debug ($"[AArch64] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
return 0;
}
static ulong GetValue_x64 (List<ELF64RelocationAddend> rels, ulong symbolOffset)
{
// Documentation: https://docs.oracle.com/en/operating-systems/solaris/oracle-solaris/11.4/linkers-libraries/x64-relocation-types.html#GUID-369D19D8-60F0-4D3D-B761-4F02BA0BC023
// Value 8
const ulong R_AMD64_RELATIVE = 0x08;
foreach (ELF64RelocationAddend rel in rels) {
if (rel.Offset != symbolOffset) {
continue;
}
ulong relType = GetRelType (rel);
if (relType != R_AMD64_RELATIVE) {
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
return 0;
}
// In this case, this is offset into the file where pointed-to data begins
return (ulong)rel.Addend;
}
Log.Debug ($"[AArch64] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
return 0;
}
static ulong GetValue_Arm32 (List<ELF32Relocation> rels, uint symbolOffset)
{
// Documentation: https://github.com/ARM-software/abi-aa/releases/download/2023Q3/aaelf32.pdf, page 40, Code 23
const uint R_ARM_RELATIVE = 0x17;
foreach (ELF32Relocation rel in rels) {
if (rel.Offset != symbolOffset) {
continue;
}
uint relType = GetRelType (rel);
if (relType != R_ARM_RELATIVE) {
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
return 0;
}
// In this case, offset is the value we need to calculate the location
return rel.Offset;
}
Log.Debug ($"[Arm32] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
return 0;
}
static ulong GetValue_x86 (List<ELF32Relocation> rels, uint symbolOffset)
{
// Documentation: https://docs.oracle.com/en/operating-systems/solaris/oracle-solaris/11.4/linkers-libraries/32-bit-x86-relocation-types.html#GUID-2CE5C854-5AD8-4CC5-AABA-C03BED1C3FC0
// Value 8
const uint R_386_RELATIVE = 0x08;
foreach (ELF32Relocation rel in rels) {
if (rel.Offset != symbolOffset) {
continue;
}
uint relType = GetRelType (rel);
if (relType != R_386_RELATIVE) {
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
return 0;
}
// In this case, offset is the value we need to calculate the location
return rel.Offset;
}
Log.Debug ($"[x86] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
return 0;
}
// the `r_info` (`.Info` for us) field of relocation recold encodes both the relocation type and symbol table index
static ulong GetRelType (ELF64RelocationAddend rel) => rel.Info & 0xffffffff;
static ulong GetRelSym (ELF64RelocationAddend rel) => rel.Info >> 32;
static uint GetRelType (ELF32Relocation rel) => rel.Info & 0xff;
static uint GetRelSym (ELF32Relocation rel) => rel.Info >> 8;
}

Просмотреть файл

@ -9,12 +9,78 @@ namespace tmt
class Report class Report
{ {
const string FileFieldSeparator = "\t"; const string FileFieldSeparator = "\t";
const string ManagedTypeColumnHeader = "Managed-Type-Name";
const string JavaTypeColumnHeader = "Java-Type-Name"; const string FormattedDuplicateColumnHeader = "Is Duplicate?";
const string DuplicateColumnHeader = "Is-Duplicate-Type-Entry?"; const string FormattedGenericColumnHeader = "Is Generic?";
const string GenericColumnHeader = "Is-Generic-Type?"; const string FormattedJavaTypeColumnHeader = "Java type name";
const string MVIDColumnHeader = "MVID"; const string FormattedMVIDColumnHeader = "MVID";
const string TokenIDColumnHeader = "Token-ID"; const string FormattedManagedTypeColumnHeader = "Managed type name";
const string FormattedTokenIDColumnHeader = "Type token ID";
const string RawJavaTypeNameColumnHeader = FormattedJavaTypeColumnHeader;
const string RawManagedModuleIndexColumnHeader = "Managed module index";
const string RawTypeTokenColumnHeader = FormattedTokenIDColumnHeader;
const string TableJavaToManagedTitle = "Java to Managed";
const string TableManagedToJavaTitle = "Managed to Java";
sealed class Column
{
int width = 0;
public int Width => width;
public string Header { get; }
public List<string> Rows { get; } = new List<string> ();
public Column (string header)
{
Header = header;
width = header.Length;
}
public void Add (string rowValue)
{
if (rowValue.Length > width) {
width = rowValue.Length;
}
Rows.Add (rowValue);
}
public void Add (bool rowValue)
{
Add (rowValue ? "true" : "false");
}
public void Add (uint rowValue)
{
Add ($"0x{rowValue:X08} ({rowValue})");
}
public void Add (Guid rowValue)
{
Add (rowValue.ToString ());
}
}
sealed class Table
{
public Column Duplicate { get; } = new Column (FormattedDuplicateColumnHeader);
public Column Generic { get; } = new Column (FormattedGenericColumnHeader);
public Column JavaType { get; } = new Column (FormattedJavaTypeColumnHeader);
public Column MVID { get; } = new Column (FormattedMVIDColumnHeader);
public Column ManagedType { get; } = new Column (FormattedManagedTypeColumnHeader);
public Column TokenID { get; } = new Column (FormattedTokenIDColumnHeader);
public string Title { get; }
public bool ManagedFirst { get; }
public Table (string title, bool managedFirst)
{
Title = title;
ManagedFirst = managedFirst;
}
}
string outputDirectory; string outputDirectory;
Regex? filterRegex; Regex? filterRegex;
@ -37,6 +103,235 @@ namespace tmt
} }
public void Generate (ITypemap typemap) public void Generate (ITypemap typemap)
{
Action<Table, MapEntry, bool> tableGenerator;
Action<MapEntry, bool> consoleGenerator;
var tables = new List<Table> ();
bool filtering = filterRegex != null;
Table table;
if (!onlyManaged) {
typemap.Map.JavaToManaged.Sort ((MapEntry left, MapEntry right) => left.JavaType.Name.CompareTo (right.JavaType.Name));
table = new Table (TableJavaToManagedTitle, managedFirst: false);
tables.Add (table);
if (typemap.Map.Kind == MapKind.Release) {
tableGenerator = TableGenerateJavaToManagedRelease;
consoleGenerator = ConsoleGenerateJavaToManagedRelease;
} else {
tableGenerator = TableGenerateJavaToManagedDebug;
consoleGenerator = ConsoleGenerateJavaToManagedDebug;
}
Generate ("Java to Managed", table, typemap.Map.JavaToManaged, full, tableGenerator, consoleGenerator);
}
if (!onlyJava) {
typemap.Map.ManagedToJava.Sort ((MapEntry left, MapEntry right) => {
int result = String.Compare (left.ManagedType.AssemblyName, right.ManagedType.AssemblyName, StringComparison.OrdinalIgnoreCase);
if (result != 0)
return result;
return left.ManagedType.TypeName.CompareTo (right.ManagedType.TypeName);
});
table = new Table (TableManagedToJavaTitle, managedFirst: true);
tables.Add (table);
if (typemap.Map.Kind == MapKind.Release) {
tableGenerator = TableGenerateManagedToJavaRelease;
consoleGenerator = ConsoleGenerateManagedToJavaRelease;
} else {
tableGenerator = TableGenerateManagedToJavaDebug;
consoleGenerator = ConsoleGenerateManagedToJavaDebug;
}
Generate ("Managed to Java", table, typemap.Map.ManagedToJava, full, tableGenerator, consoleGenerator);
}
string? outputFile = null;
StreamWriter? sw = null;
if (generateFiles) {
outputFile = Utilities.GetOutputFileBaseName (outputDirectory, typemap.FormatVersion, typemap.Map.Kind, typemap.Map.Architecture);
outputFile = $"{outputFile}.md";
Utilities.CreateFileDirectory (outputFile);
sw = new StreamWriter (outputFile, false, new UTF8Encoding (false));
}
try {
if (sw != null) {
sw.WriteLine ("# Info");
sw.WriteLine ();
sw.WriteLine ($"Architecture: **{typemap.MapArchitecture}**");
sw.WriteLine ($" Build kind: **{typemap.Map.Kind}**");
sw.WriteLine ($" Format: **{typemap.FormatVersion}**");
sw.WriteLine ($" Description: **{typemap.Description}**");
sw.WriteLine ();
}
foreach (Table t in tables) {
if (sw != null) {
WriteTable (sw, t);
sw.WriteLine ();
}
}
} finally {
if (sw != null) {
sw.Flush ();
sw.Close ();
sw.Dispose ();
}
}
}
void WriteTable (StreamWriter sw, Table table)
{
sw.WriteLine ($"# {table.Title}");
sw.WriteLine ();
// Just non-empty columns
var columns = new List<Column> ();
if (table.ManagedFirst) {
MaybeAddColumn (table.ManagedType);
MaybeAddColumn (table.JavaType);
} else {
MaybeAddColumn (table.JavaType);
MaybeAddColumn (table.ManagedType);
}
MaybeAddColumn (table.TokenID);
MaybeAddColumn (table.Generic);
MaybeAddColumn (table.Duplicate);
MaybeAddColumn (table.MVID);
if (columns.Count == 0) {
Log.Warning ("No non-empty columns");
return;
}
// All columns must have equal numbers of rows
int rows = columns[0].Rows.Count;
foreach (Column column in columns) {
if (column.Rows.Count != rows) {
throw new InvalidOperationException ($"Column {column.Header} has a different number of rows, {column.Rows.Count}, than the expected value of {rows}");
}
}
var sb = new StringBuilder ();
int width;
string prefix;
var divider = new StringBuilder ();
foreach (Column column in columns) {
width = GetColumnWidth (column);
divider.Append ("| ");
divider.Append ('-', width - 3);
divider.Append (' ');
prefix = $"| {column.Header}";
sw.Write (prefix);
sw.Write (GetPadding (width - prefix.Length));
}
sw.WriteLine ('|');
sw.Write (divider);
sw.WriteLine ('|');
for (int row = 0; row < rows; row++) {
foreach (Column column in columns) {
width = GetColumnWidth (column);
prefix = $"| {column.Rows[row]}";
sw.Write (prefix);
sw.Write (GetPadding (width - prefix.Length));
}
sw.Write ('|');
sw.WriteLine ();
}
void MaybeAddColumn (Column column)
{
if (column.Rows.Count == 0) {
return;
}
columns.Add (column);
}
int GetColumnWidth (Column column) => column.Width + 3; // For the '| ' prefix and ' ' suffix
string GetPadding (int width)
{
sb.Clear ();
return sb.Append (' ', width).ToString ();
}
}
void TableGenerateJavaToManagedDebug (Table table, MapEntry entry, bool full)
{
table.JavaType.Add (entry.JavaType.Name);
table.ManagedType.Add (GetManagedTypeNameDebug (entry));
table.Duplicate.Add (entry.ManagedType.IsDuplicate);
}
void TableGenerateJavaToManagedRelease (Table table, MapEntry entry, bool full)
{
string managedTypeName = GetManagedTypeNameRelease (entry);
string generic = entry.ManagedType.IsGeneric ? IgnoredGeneric : "no";
table.JavaType.Add (entry.JavaType.Name);
table.ManagedType.Add (managedTypeName);
table.Generic.Add (generic);
if (!full) {
return;
}
table.MVID.Add (entry.ManagedType.MVID);
table.TokenID.Add (entry.ManagedType.TokenID);
}
void TableGenerateManagedToJavaDebug (Table table, MapEntry entry, bool full)
{
table.JavaType.Add (entry.JavaType.Name);
table.ManagedType.Add (GetManagedTypeNameDebug (entry));
table.Duplicate.Add (entry.ManagedType.IsDuplicate);
}
void TableGenerateManagedToJavaRelease (Table table, MapEntry entry, bool full)
{
string managedTypeName = GetManagedTypeNameRelease (entry);
string generic = entry.ManagedType.IsGeneric ? IgnoredGeneric : "no";
table.ManagedType.Add (managedTypeName);
table.JavaType.Add (entry.JavaType.Name);
table.Generic.Add (generic);
table.Duplicate.Add (entry.ManagedType.IsDuplicate);
if (!full) {
return;
}
table.MVID.Add (entry.ManagedType.MVID);
table.TokenID.Add (entry.ManagedType.TokenID);
}
void Generate (string label, Table table, List<MapEntry> typemap, bool full, Action<Table, MapEntry, bool> tableGenerator, Action<MapEntry, bool> consoleGenerator)
{
bool firstMatch = true;
foreach (MapEntry entry in typemap) {
if (generateFiles) {
tableGenerator (table, entry, full);
}
if (filterRegex == null || !EntryMatches (entry, filterRegex)) {
continue;
}
if (firstMatch) {
Log.Info ();
Log.Info ($" Matching entries ({label}):");
firstMatch = false;
}
consoleGenerator (entry, full);
}
}
void GenerateOld (ITypemap typemap)
{ {
string baseOutputFile = generateFiles ? Utilities.GetOutputFileBaseName (outputDirectory, typemap.FormatVersion, typemap.Map.Kind, typemap.Map.Architecture) : String.Empty; string baseOutputFile = generateFiles ? Utilities.GetOutputFileBaseName (outputDirectory, typemap.FormatVersion, typemap.Map.Kind, typemap.Map.Architecture) : String.Empty;
Action<StreamWriter, MapEntry, bool, bool> fileGenerator; Action<StreamWriter, MapEntry, bool, bool> fileGenerator;
@ -157,7 +452,7 @@ namespace tmt
{ {
if (firstEntry) { if (firstEntry) {
string sep = FileFieldSeparator; string sep = FileFieldSeparator;
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{DuplicateColumnHeader}"); sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
} }
WriteLineToFile (sw, WriteLineToFile (sw,
@ -180,7 +475,7 @@ namespace tmt
if (!full) { if (!full) {
if (firstEntry) { if (firstEntry) {
string sep = FileFieldSeparator; string sep = FileFieldSeparator;
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{GenericColumnHeader}"); sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedGenericColumnHeader}");
} }
WriteLineToFile ( WriteLineToFile (
@ -194,7 +489,7 @@ namespace tmt
if (firstEntry) { if (firstEntry) {
string sep = FileFieldSeparator; string sep = FileFieldSeparator;
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{MVIDColumnHeader}{sep}{TokenIDColumnHeader}"); sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedMVIDColumnHeader}{sep}{FormattedTokenIDColumnHeader}");
} }
WriteLineToFile ( WriteLineToFile (
sw, sw,
@ -238,7 +533,7 @@ namespace tmt
{ {
if (firstEntry) { if (firstEntry) {
string sep = FileFieldSeparator; string sep = FileFieldSeparator;
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{DuplicateColumnHeader}"); sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
} }
WriteLineToFile (sw, WriteLineToFile (sw,
@ -262,7 +557,7 @@ namespace tmt
if (full) { if (full) {
if (firstEntry) { if (firstEntry) {
string sep = FileFieldSeparator; string sep = FileFieldSeparator;
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{DuplicateColumnHeader}{sep}{MVIDColumnHeader}{sep}{TokenIDColumnHeader}"); sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedDuplicateColumnHeader}{sep}{FormattedMVIDColumnHeader}{sep}{FormattedTokenIDColumnHeader}");
} }
WriteLineToFile ( WriteLineToFile (
sw, sw,
@ -276,7 +571,7 @@ namespace tmt
} else { } else {
if (firstEntry) { if (firstEntry) {
string sep = FileFieldSeparator; string sep = FileFieldSeparator;
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{DuplicateColumnHeader}"); sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
} }
WriteLineToFile ( WriteLineToFile (
sw, sw,
@ -291,9 +586,9 @@ namespace tmt
void ConsoleGenerateManagedToJavaRelease (MapEntry entry, bool full) void ConsoleGenerateManagedToJavaRelease (MapEntry entry, bool full)
{ {
if (!full) { if (!full) {
Log.Info ($" {GetManagedTypeNameDebug (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}"); Log.Info ($" {GetManagedTypeNameRelease (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
} else { } else {
Log.Info ($" {GetManagedTypeNameDebug (entry)}; MVID: {entry.ManagedType.MVID}; Token ID: {TokenIdToString (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}"); Log.Info ($" {GetManagedTypeNameRelease (entry)}; MVID: {entry.ManagedType.MVID}; Token ID: {TokenIdToString (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
} }
} }

Просмотреть файл

@ -1,12 +1,15 @@
using System; using System;
using System.IO; using System.IO;
using ELFSharp.ELF.Sections;
namespace tmt namespace tmt
{ {
abstract class XamarinAppDSO : ITypemap abstract class XamarinAppDSO : ITypemap
{ {
// Corresponds to the `FORMAT_TAG` constant in src/monodroid/xamarin-app.hh // Corresponds to the `FORMAT_TAG` constant in src/monodroid/xamarin-app.hh
protected const ulong FormatTag_V1 = 0x015E6972616D58; protected const ulong FormatTag_V1 = 0x00015E6972616D58;
protected const ulong FormatTag_V2 = 0x00025E6972616D58;
protected const string FormatTag = "format_tag"; protected const string FormatTag = "format_tag";
@ -18,6 +21,7 @@ namespace tmt
public MapArchitecture MapArchitecture => ELF.MapArchitecture; public MapArchitecture MapArchitecture => ELF.MapArchitecture;
public string FullPath { get; } = String.Empty; public string FullPath { get; } = String.Empty;
protected abstract string LogTag { get; }
public abstract string Description { get; } public abstract string Description { get; }
public abstract string FormatVersion { get; } public abstract string FormatVersion { get; }
public abstract Map Map { get; } public abstract Map Map { get; }
@ -64,87 +68,44 @@ namespace tmt
protected uint ReadUInt32 (byte[] data, ref ulong offset, bool packed = false) protected uint ReadUInt32 (byte[] data, ref ulong offset, bool packed = false)
{ {
const ulong DataSize = 4; return Helpers.ReadUInt32 (data, ref offset, Is64Bit, packed);
if ((ulong)data.Length < (offset + DataSize))
throw new InvalidOperationException ("Not enough data to read a 32-bit integer");
uint ret = BitConverter.ToUInt32 (data, (int)offset);
offset += packed ? DataSize : GetPaddedSize<uint> (offset);
return ret;
} }
protected ulong ReadUInt64 (byte[] data, ref ulong offset, bool packed = false) protected ulong ReadUInt64 (byte[] data, ref ulong offset, bool packed = false)
{ {
const ulong DataSize = 8; return Helpers.ReadUInt64 (data, ref offset, Is64Bit, packed);
if ((ulong)data.Length < (offset + DataSize))
throw new InvalidOperationException ("Not enough data to read a 64-bit integer");
ulong ret = BitConverter.ToUInt64 (data, (int)offset);
offset += packed ? DataSize : GetPaddedSize<ulong> (offset);
return ret;
} }
protected ulong ReadPointer (byte[] data, ref ulong offset, bool packed = false) protected ulong ReadPointer (byte[] data, ref ulong offset, bool packed = false)
{ {
ulong ret; return Helpers.ReadPointer (data, ref offset, Is64Bit, packed);
}
if (Is64Bit) { protected ulong ReadPointer (ISymbolEntry symbol, byte[] data, ref ulong offset, bool packed = false)
ret = ReadUInt64 (data, ref offset, packed); {
} else { ulong prevOffset = offset;
ret = (ulong)ReadUInt32 (data, ref offset, packed); ulong pointer = ReadPointer (data, ref offset, packed);
if (pointer == 0) {
pointer = ELF.DeterminePointerAddress (symbol, prevOffset);
} }
return ret; return pointer;
}
protected ulong ReadPointer (ulong symbolValue, byte[] data, ref ulong offset, bool packed = false)
{
ulong prevOffset = offset;
ulong pointer = ReadPointer (data, ref offset, packed);
if (pointer == 0) {
pointer = ELF.DeterminePointerAddress (symbolValue, prevOffset);
}
return pointer;
} }
protected ulong GetPaddedSize<S> (ulong sizeSoFar) protected ulong GetPaddedSize<S> (ulong sizeSoFar)
{ {
ulong typeSize = GetTypeSize<S> (); return Helpers.GetPaddedSize<S> (sizeSoFar, Is64Bit);
ulong modulo;
if (Is64Bit) {
modulo = typeSize < 8 ? 4u : 8u;
} else {
modulo = 4u;
}
ulong alignment = sizeSoFar % modulo;
if (alignment == 0)
return typeSize;
return typeSize + (modulo - alignment);
}
ulong GetTypeSize<S> ()
{
Type type = typeof(S);
if (type == typeof(string)) {
// We treat `string` as a generic pointer
return Is64Bit ? 8u : 4u;
}
if (type == typeof(byte)) {
return 1u;
}
if (type == typeof(bool)) {
return 1u;
}
if (type == typeof(Int32) || type == typeof(UInt32)) {
return 4u;
}
if (type == typeof(Int64) || type == typeof(UInt64)) {
return 8u;
}
throw new InvalidOperationException ($"Unable to map managed type {type} to native assembler type");
} }
} }
} }

Просмотреть файл

@ -8,6 +8,7 @@ namespace tmt
XamarinAppDebugDSO_Version? xapp; XamarinAppDebugDSO_Version? xapp;
public override string FormatVersion => xapp?.FormatVersion ?? "0"; public override string FormatVersion => xapp?.FormatVersion ?? "0";
protected override string LogTag => "DebugDSO";
public override string Description => xapp?.Description ?? "Xamarin App Debug DSO Forwarder"; public override string Description => xapp?.Description ?? "Xamarin App Debug DSO Forwarder";
public override Map Map => XAPP.Map; public override Map Map => XAPP.Map;
@ -19,6 +20,8 @@ namespace tmt
public override bool CanLoad (AnELF elf) public override bool CanLoad (AnELF elf)
{ {
Log.Debug (LogTag, $"Checking if {elf.FilePath} is a Debug DSO");
xapp = null; xapp = null;
ulong format_tag = 0; ulong format_tag = 0;
if (elf.HasSymbol (FormatTag)) if (elf.HasSymbol (FormatTag))
@ -32,8 +35,13 @@ namespace tmt
reader = new XamarinAppDebugDSO_V1 (ManagedResolver, elf); reader = new XamarinAppDebugDSO_V1 (ManagedResolver, elf);
break; break;
case FormatTag_V2:
format_tag = 2;
reader = new XamarinAppDebugDSO_V2 (ManagedResolver, elf);
break;
default: default:
Log.Error ($"{elf.FilePath} format ({format_tag}) is not supported by this version of TMT"); Log.Debug (LogTag, $"{elf.FilePath} format (0x{format_tag:x}) is not supported by this version of TMT");
return false; return false;
} }
@ -76,180 +84,4 @@ namespace tmt
protected abstract bool LoadMaps (); protected abstract bool LoadMaps ();
protected abstract bool Convert (); protected abstract bool Convert ();
} }
class XamarinAppDebugDSO_V1 : XamarinAppDebugDSO_Version
{
sealed class MappedType
{
public string TargetType;
public ulong DuplicateCount = 0;
public MappedType (string targetType)
{
TargetType = targetType;
}
}
const string TypeMapSymbolName = "type_map";
Map? map;
SortedDictionary<string, MappedType> javaToManaged = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
SortedDictionary<string, MappedType> managedToJava = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
public override string FormatVersion => "1";
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
public XamarinAppDebugDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
: base (managedResolver, elf)
{}
public override bool CanLoad (AnELF elf)
{
return HasSymbol (elf, TypeMapSymbolName);
}
protected override bool Convert ()
{
try {
DoConvert ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
return false;
}
return true;
}
void DoConvert ()
{
var managed = new List<MapEntry> ();
foreach (var kvp in managedToJava) {
string managedName = kvp.Key;
MappedType javaType = kvp.Value;
managed.Add (
new MapEntry (
new MapManagedType (managedName) { IsDuplicate = javaType.DuplicateCount > 0 },
new MapJavaType (javaType.TargetType)
)
);
}
var java = new List<MapEntry> ();
foreach (var kvp in javaToManaged) {
string javaName = kvp.Key;
MappedType managedType = kvp.Value;
java.Add (
new MapEntry (
new MapManagedType (managedType.TargetType) { IsDuplicate = managedType.DuplicateCount > 0 },
new MapJavaType (javaName)
)
);
}
map = MakeMap (managed, java);
}
protected override bool LoadMaps ()
{
try {
return DoLoadMaps ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to load maps", ex);
return false;
}
}
bool DoLoadMaps ()
{
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMap)
ulong size = 0;
size += GetPaddedSize<uint> (size); // entry_count
size += GetPaddedSize<string> (size); // assembly_name (pointer)
size += GetPaddedSize<string> (size); // data (pointer)
size += GetPaddedSize<string> (size); // java_to_managed (pointer)
size += GetPaddedSize<string> (size); // managed_to_java (pointer)
string filePath = ELF.FilePath;
byte[] mapData = ELF.GetData (TypeMapSymbolName);
if (mapData.Length == 0) {
Log.Error ($"{filePath} doesn't have a valid '{TypeMapSymbolName}' symbol");
return false;
}
if ((ulong)mapData.Length != size) {
Log.Error ($"Symbol '{TypeMapSymbolName}' in {filePath} has invalid size. Expected {size}, got {mapData.Length}");
return false;
}
ulong offset = 0;
uint entry_count = ReadUInt32 (mapData, ref offset);
ReadPointer (mapData, ref offset); // assembly_name, unused in Debug mode
ReadPointer (mapData, ref offset); // data, unused in Debug mode
ulong pointer = ReadPointer (mapData, ref offset); // java_to_managed
LoadMap ("Java to Managed", pointer, entry_count, AddJavaToManaged);
pointer = ReadPointer (mapData, ref offset); // managed_to_java
LoadMap ("Managed to Java", pointer, entry_count, AddManagedToJava);
return true;
}
void AddManagedToJava (string mapFrom, string mapTo)
{
if (managedToJava.TryGetValue (mapFrom, out MappedType? entry)) {
entry.DuplicateCount++;
return;
}
managedToJava.Add (mapFrom, new MappedType (mapTo));
}
void AddJavaToManaged (string mapFrom, string mapTo)
{
if (javaToManaged.TryGetValue (mapFrom, out MappedType? entry)) {
entry.DuplicateCount++;
return;
}
javaToManaged.Add (mapFrom, new MappedType (mapTo));
}
void LoadMap (string name, ulong pointer, uint entry_count, Action<string, string> addToMap)
{
string entries = entry_count == 1 ? "entry" : "entries";
Log.Info ($" Loading {name} map: {entry_count} {entries}, please wait...");
ulong size = 0;
size += GetPaddedSize<string> (size); // from
size += GetPaddedSize<string> (size); // to
ulong mapSize = entry_count * size;
byte[] data = ELF.GetData (pointer, mapSize);
ulong offset = 0;
string mapFrom;
string mapTo;
for (uint i = 0; i < entry_count; i++) {
pointer = ReadPointer (data, ref offset);
if (pointer != 0) {
mapFrom = ELF.GetASCIIZ (pointer);
} else {
mapFrom = $"#{i}";
}
pointer = ReadPointer (data, ref offset);
if (pointer != 0) {
mapTo = ELF.GetASCIIZ (pointer);
} else {
mapTo = $"#{i}";
}
addToMap (mapFrom, mapTo);
}
}
}
} }

Просмотреть файл

@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
namespace tmt;
class XamarinAppDebugDSO_V1 : XamarinAppDebugDSO_Version
{
sealed class MappedType
{
public string TargetType;
public ulong DuplicateCount = 0;
public MappedType (string targetType)
{
TargetType = targetType;
}
}
const string TypeMapSymbolName = "type_map";
protected override string LogTag => "DebugDSO_V1";
Map? map;
SortedDictionary<string, MappedType> javaToManaged = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
SortedDictionary<string, MappedType> managedToJava = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
public override string FormatVersion => "1";
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
public XamarinAppDebugDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
: base (managedResolver, elf)
{}
public override bool CanLoad (AnELF elf)
{
return HasSymbol (elf, TypeMapSymbolName);
}
protected override bool Convert ()
{
try {
DoConvert ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
return false;
}
return true;
}
void DoConvert ()
{
var managed = new List<MapEntry> ();
foreach (var kvp in managedToJava) {
string managedName = kvp.Key;
MappedType javaType = kvp.Value;
managed.Add (
new MapEntry (
new MapManagedType (managedName) { IsDuplicate = javaType.DuplicateCount > 0 },
new MapJavaType (javaType.TargetType)
)
);
}
var java = new List<MapEntry> ();
foreach (var kvp in javaToManaged) {
string javaName = kvp.Key;
MappedType managedType = kvp.Value;
java.Add (
new MapEntry (
new MapManagedType (managedType.TargetType) { IsDuplicate = managedType.DuplicateCount > 0 },
new MapJavaType (javaName)
)
);
}
map = MakeMap (managed, java);
}
protected override bool LoadMaps ()
{
try {
return DoLoadMaps ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to load maps", ex);
return false;
}
}
bool DoLoadMaps ()
{
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMap)
ulong size = 0;
size += GetPaddedSize<uint> (size); // entry_count
size += GetPaddedSize<string> (size); // assembly_name (pointer)
size += GetPaddedSize<string> (size); // data (pointer)
size += GetPaddedSize<string> (size); // java_to_managed (pointer)
size += GetPaddedSize<string> (size); // managed_to_java (pointer)
string filePath = ELF.FilePath;
(byte[] mapData, _) = ELF.GetData (TypeMapSymbolName);
if (mapData.Length == 0) {
Log.Error ($"{filePath} doesn't have a valid '{TypeMapSymbolName}' symbol");
return false;
}
if ((ulong)mapData.Length != size) {
Log.Error ($"Symbol '{TypeMapSymbolName}' in {filePath} has invalid size. Expected {size}, got {mapData.Length}");
return false;
}
ulong offset = 0;
uint entry_count = ReadUInt32 (mapData, ref offset);
ReadPointer (mapData, ref offset); // assembly_name, unused in Debug mode
ReadPointer (mapData, ref offset); // data, unused in Debug mode
ulong pointer = ReadPointer (mapData, ref offset); // java_to_managed
LoadMap ("Java to Managed", pointer, entry_count, AddJavaToManaged);
pointer = ReadPointer (mapData, ref offset); // managed_to_java
LoadMap ("Managed to Java", pointer, entry_count, AddManagedToJava);
return true;
}
void AddManagedToJava (string mapFrom, string mapTo)
{
if (managedToJava.TryGetValue (mapFrom, out MappedType? entry)) {
entry.DuplicateCount++;
return;
}
managedToJava.Add (mapFrom, new MappedType (mapTo));
}
void AddJavaToManaged (string mapFrom, string mapTo)
{
if (javaToManaged.TryGetValue (mapFrom, out MappedType? entry)) {
entry.DuplicateCount++;
return;
}
javaToManaged.Add (mapFrom, new MappedType (mapTo));
}
void LoadMap (string name, ulong pointer, uint entry_count, Action<string, string> addToMap)
{
string entries = entry_count == 1 ? "entry" : "entries";
Log.Info ($" Loading {name} map: {entry_count} {entries}, please wait...");
ulong size = 0;
size += GetPaddedSize<string> (size); // from
size += GetPaddedSize<string> (size); // to
ulong mapSize = entry_count * size;
byte[] data = ELF.GetData (pointer, mapSize);
ulong offset = 0;
string mapFrom;
string mapTo;
for (uint i = 0; i < entry_count; i++) {
pointer = ReadPointer (data, ref offset);
if (pointer != 0) {
mapFrom = ELF.GetASCIIZ (pointer);
} else {
mapFrom = $"#{i}";
}
pointer = ReadPointer (data, ref offset);
if (pointer != 0) {
mapTo = ELF.GetASCIIZ (pointer);
} else {
mapTo = $"#{i}";
}
addToMap (mapFrom, mapTo);
}
}
}

Просмотреть файл

@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using ELFSharp.ELF.Sections;
namespace tmt;
class XamarinAppDebugDSO_V2 : XamarinAppDebugDSO_Version
{
sealed class MappedType
{
public string TargetType;
public ulong DuplicateCount = 0;
public MappedType (string targetType)
{
TargetType = targetType;
}
}
const string TypeMapSymbolName = "type_map";
protected override string LogTag => "DebugDSO_V2";
Map? map;
SortedDictionary<string, List<MappedType>> javaToManaged = new SortedDictionary<string, List<MappedType>> (StringComparer.Ordinal);
SortedDictionary<string, MappedType> managedToJava = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
public override string FormatVersion => "2";
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
public XamarinAppDebugDSO_V2 (ManagedTypeResolver managedResolver, AnELF elf)
: base (managedResolver, elf)
{}
public override bool CanLoad (AnELF elf)
{
return HasSymbol (elf, TypeMapSymbolName);
}
protected override bool Convert ()
{
try {
DoConvert ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
return false;
}
return true;
}
void DoConvert ()
{
var managed = new List<MapEntry> ();
foreach (var kvp in managedToJava) {
string managedName = kvp.Key;
MappedType javaType = kvp.Value;
managed.Add (
new MapEntry (
new MapManagedType (managedName) { IsDuplicate = javaType.DuplicateCount > 0 },
new MapJavaType (javaType.TargetType)
)
);
}
var java = new List<MapEntry> ();
foreach (var kvp in javaToManaged) {
string javaName = kvp.Key;
List<MappedType> managedTypes = kvp.Value;
var javaType = new MapJavaType (javaName);
foreach (MappedType managedType in managedTypes) {
java.Add (
new MapEntry (
new MapManagedType (managedType.TargetType) { IsDuplicate = managedType.DuplicateCount > 0 },
javaType
)
);
}
}
map = MakeMap (managed, java);
}
protected override bool LoadMaps ()
{
try {
return DoLoadMaps ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to load maps", ex);
return false;
}
}
bool DoLoadMaps ()
{
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMap)
ulong size = 0;
size += GetPaddedSize<uint> (size); // entry_count
size += GetPaddedSize<string> (size); // assembly_name (pointer)
size += GetPaddedSize<string> (size); // data (pointer)
size += GetPaddedSize<string> (size); // java_to_managed (pointer)
size += GetPaddedSize<string> (size); // managed_to_java (pointer)
string filePath = ELF.FilePath;
(byte[] mapData, ISymbolEntry? symbol) = ELF.GetData (TypeMapSymbolName);
if (mapData.Length == 0 || symbol == null) {
Log.Error ($"{filePath} doesn't have a valid '{TypeMapSymbolName}' symbol");
return false;
}
if ((ulong)mapData.Length != size) {
Log.Error ($"Symbol '{TypeMapSymbolName}' in {filePath} has invalid size. Expected {size}, got {mapData.Length}");
return false;
}
ulong offset = 0;
uint entry_count = ReadUInt32 (mapData, ref offset);
ReadPointer (symbol, mapData, ref offset); // assembly_name, unused in Debug mode
ReadPointer (symbol, mapData, ref offset); // data, unused in Debug mode
ulong pointer = ReadPointer (symbol, mapData, ref offset); // java_to_managed
LoadMap ("Java to Managed", pointer, entry_count, AddJavaToManaged);
pointer = ReadPointer (symbol, mapData, ref offset); // managed_to_java
LoadMap ("Managed to Java", pointer, entry_count, AddManagedToJava);
return true;
}
void AddManagedToJava (string mapFrom, string mapTo)
{
if (managedToJava.TryGetValue (mapFrom, out MappedType? entry)) {
entry.DuplicateCount++;
return;
}
managedToJava.Add (mapFrom, new MappedType (mapTo));
}
void AddJavaToManaged (string mapFrom, string mapTo)
{
if (javaToManaged.TryGetValue (mapFrom, out List<MappedType>? types)) {
types.Add (new MappedType (mapTo));
for (int i = 1; i < types.Count; i++) {
MappedType entry = types[i];
entry.DuplicateCount++;
}
return;
}
javaToManaged.Add (mapFrom, new List<MappedType> {new MappedType (mapTo)});
}
void LoadMap (string name, ulong arrayPointer, uint entry_count, Action<string, string> addToMap)
{
string entries = entry_count == 1 ? "entry" : "entries";
Log.Info ($" Loading {name} map: {entry_count} {entries}");
Log.Debug (LogTag, $" pointer == 0x{arrayPointer:X}");
ulong size = 0;
size += GetPaddedSize<string> (size); // from
size += GetPaddedSize<string> (size); // to
ulong mapSize = entry_count * size;
byte[] data = ELF.GetDataFromPointer (arrayPointer, mapSize);
ulong offset = 0;
string mapFrom;
string mapTo;
for (uint i = 0; i < entry_count; i++) {
ulong pointer = ReadPointer (arrayPointer, data, ref offset);
Log.Debug (LogTag, $" [{i}] pointer1 == 0x{pointer:X}");
if (pointer != 0) {
mapFrom = ELF.GetASCIIZFromPointer (pointer);
} else {
mapFrom = $"#{i} <null>";
}
pointer = ReadPointer (arrayPointer, data, ref offset);
Log.Debug (LogTag, $" [{i}] pointer2 == 0x{pointer:X}");
if (pointer != 0) {
mapTo = ELF.GetASCIIZFromPointer (pointer);
} else {
mapTo = $"#{i} <null>";
}
addToMap (mapFrom, mapTo);
}
}
}

Просмотреть файл

@ -1,7 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Text;
namespace tmt namespace tmt
{ {
@ -10,6 +8,7 @@ namespace tmt
XamarinAppReleaseDSO_Version? xapp; XamarinAppReleaseDSO_Version? xapp;
public override string FormatVersion => xapp?.FormatVersion ?? "0"; public override string FormatVersion => xapp?.FormatVersion ?? "0";
protected override string LogTag => "ReleaseDSO";
public override string Description => xapp?.Description ?? "Xamarin App Release DSO Forwarder"; public override string Description => xapp?.Description ?? "Xamarin App Release DSO Forwarder";
public override Map Map => XAPP.Map; public override Map Map => XAPP.Map;
@ -25,7 +24,7 @@ namespace tmt
public override bool CanLoad (AnELF elf) public override bool CanLoad (AnELF elf)
{ {
Log.Debug ($"Checking if {elf.FilePath} is a Release DSO"); Log.Debug (LogTag, $"Checking if {elf.FilePath} is a Release DSO");
xapp = null; xapp = null;
ulong format_tag = 0; ulong format_tag = 0;
@ -40,8 +39,13 @@ namespace tmt
reader = new XamarinAppReleaseDSO_V1 (ManagedResolver, elf); reader = new XamarinAppReleaseDSO_V1 (ManagedResolver, elf);
break; break;
case FormatTag_V2:
format_tag = 2;
reader = new XamarinAppReleaseDSO_V2 (ManagedResolver, elf);
break;
default: default:
Log.Error ($"{elf.FilePath} format ({format_tag}) is not supported by this version of TMT"); Log.Debug (LogTag, $"{elf.FilePath} format (0x{format_tag:x}) is not supported by this version of TMT");
return false; return false;
} }
@ -90,391 +94,4 @@ namespace tmt
protected abstract bool Convert (); protected abstract bool Convert ();
protected abstract void SaveRaw (string baseOutputFilePath, string extension); protected abstract void SaveRaw (string baseOutputFilePath, string extension);
} }
class XamarinAppReleaseDSO_V1 : XamarinAppReleaseDSO_Version
{
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
sealed class TypeMapModuleEntry
{
public uint type_token_id;
public uint java_map_index;
}
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
sealed class TypeMapModule
{
public Guid module_uuid;
public uint entry_count;
public uint duplicate_count;
public List<TypeMapModuleEntry>? map;
public List<TypeMapModuleEntry>? duplicate_map;
public string assembly_name = String.Empty;
// These three aren't used, listed for completeness
public readonly object? image = null;
public readonly uint java_name_width = 0;
public readonly byte[]? java_map = null;
}
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
sealed class TypeMapJava
{
public uint module_index;
public uint type_token_id;
public string java_name = String.Empty;
}
const string MapModulesSymbolName = "map_modules";
const string ModuleCountSymbolName = "map_module_count";
const string JavaTypeCountSymbolName = "java_type_count";
const string JavaNameWidthSymbolName = "java_name_width";
const string MapJavaSymbolName = "map_java";
Map? map;
List<TypeMapModule>? modules;
List<TypeMapJava>? javaTypes;
public override string FormatVersion => "1";
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
public XamarinAppReleaseDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
: base (managedResolver, elf)
{}
public override bool CanLoad (AnELF elf)
{
return
HasSymbol (elf, MapModulesSymbolName) &&
HasSymbol (elf, ModuleCountSymbolName) &&
HasSymbol (elf, JavaTypeCountSymbolName) &&
HasSymbol (elf, JavaNameWidthSymbolName) &&
HasSymbol (elf, MapJavaSymbolName);
}
protected override bool LoadMaps ()
{
try {
string filePath = ELF.FilePath;
modules = LoadMapModules (filePath);
javaTypes = LoadJavaTypes (filePath);
return true;
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to load maps", ex);
return false;
}
}
protected override void SaveRaw (string baseOutputFilePath, string extension)
{
const string indent = "\t";
if (modules == null || javaTypes == null) {
Log.Warning ($"{Description}: cannot save raw report, no data");
return;
}
string outputFilePath = Utilities.GetManagedOutputFileName (baseOutputFilePath, extension);
Utilities.CreateFileDirectory (outputFilePath);
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
sw.WriteLine ("TYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_MAP_INDEX");
uint index = 0;
foreach (TypeMapModule module in modules) {
sw.WriteLine ();
sw.WriteLine ($"Module {index++:D04}: {module.assembly_name} (MVID: {module.module_uuid}; entries: {module.entry_count}; duplicates: {module.duplicate_count})");
if (module.map == null) {
sw.WriteLine ($"{indent}no map");
} else {
WriteManagedMap ("map", module.map, sw);
}
if (module.duplicate_map == null) {
if (module.duplicate_count > 0)
sw.WriteLine ($"{indent}no duplicate map, but there should be {module.duplicate_count} entries");
else
sw.WriteLine ($"{indent}no duplicates");
continue;
}
WriteManagedMap ("duplicate map", module.duplicate_map, sw);
}
sw.Flush ();
}
outputFilePath = Utilities.GetJavaOutputFileName (baseOutputFilePath, extension);
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
sw.WriteLine ("MANAGED_MODULE_INDEX\tTYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_TYPE_NAME");
foreach (TypeMapJava tmj in javaTypes) {
sw.WriteLine ($"{indent}{tmj.module_index}\t{tmj.type_token_id:D08} ({tmj.type_token_id:X08})\t{tmj.java_name}");
}
sw.Flush ();
}
void WriteManagedMap (string name, List<TypeMapModuleEntry> map, StreamWriter sw)
{
sw.WriteLine ($"{indent}{name}:");
foreach (TypeMapModuleEntry entry in map) {
sw.WriteLine ($"{indent}{indent}{entry.type_token_id} ({entry.type_token_id:X08})\t{entry.java_map_index}");
}
}
}
MapManagedType MakeManagedType (Guid mvid, uint tokenID, string assemblyName, string filePath, bool isGeneric, bool isDuplicate)
{
return new MapManagedType (mvid, tokenID, assemblyName, filePath) {
IsGeneric = isGeneric,
IsDuplicate = isDuplicate,
TypeName = ManagedResolver.Lookup (assemblyName, mvid, tokenID)
};
}
protected override bool Convert ()
{
try {
DoConvert ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
return false;
}
return true;
}
bool DoConvert ()
{
if (modules == null || javaTypes == null) {
Log.Warning ($"{Description}: cannot convert maps, no data");
return false;
}
string filePath = ELF.FilePath;
var managedToJava = new List<MapEntry> ();
uint index = 0;
bool somethingFailed = false;
foreach (TypeMapModule m in modules) {
ConvertManagedMap (m, EnsureMap (m), isDuplicate: false);
if (m.duplicate_map != null) {
if (!ConvertManagedMap (m, m.duplicate_map, isDuplicate: true)) {
somethingFailed = true;
}
}
index++;
}
if (somethingFailed) {
return false;
}
index = 0;
var javaToManaged = new List<MapEntry> ();
foreach (TypeMapJava tmj in javaTypes) {
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType (tmj.module_index, tmj.type_token_id);
if (!success) {
somethingFailed = true;
continue;
}
if (module == null) {
throw new InvalidOperationException ("module must not be null here");
}
javaToManaged.Add (
new MapEntry (
MakeManagedType (module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
new MapJavaType (tmj.java_name, filePath)
)
);
index++;
}
map = MakeMap (managedToJava, javaToManaged);
return true;
bool ConvertManagedMap (TypeMapModule module, List<TypeMapModuleEntry> map, bool isDuplicate)
{
foreach (TypeMapModuleEntry entry in map) {
TypeMapJava java;
if ((uint)javaTypes.Count <= entry.java_map_index) {
Log.Error ($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
return false;
}
java = javaTypes[(int)entry.java_map_index];
managedToJava.Add (
new MapEntry (
MakeManagedType (module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
new MapJavaType (java.java_name, filePath)
)
);
}
return true;
}
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) FindManagedType (uint moduleIndex, uint tokenID)
{
if (moduleIndex >= (uint)modules.Count) {
Log.Error ($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
return (false, null, false, false);
}
TypeMapModule m = modules[(int)moduleIndex];
if (tokenID == 0) {
return (true, m, true, false);
}
foreach (TypeMapModuleEntry entry in EnsureMap (m)) {
if (entry.type_token_id == tokenID) {
return (true, m, false, false);
}
}
if (m.duplicate_map != null) {
foreach (TypeMapModuleEntry entry in m.duplicate_map) {
if (entry.type_token_id == tokenID) {
return (true, m, false, true);
}
}
}
Log.Error ($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
return (false, null, false, false);
}
List<TypeMapModuleEntry> EnsureMap (TypeMapModule m)
{
if (m.map == null)
throw new InvalidOperationException ($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
return m.map;
}
}
List<TypeMapJava> LoadJavaTypes (string filePath)
{
ulong javaTypeCount = (ulong)ELF.GetUInt32 (JavaTypeCountSymbolName);
ulong javaNameWidth = (ulong)ELF.GetUInt32 (JavaNameWidthSymbolName);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
ulong size = 0;
size += GetPaddedSize<uint> (size); // module_index
size += GetPaddedSize<uint> (size); // type_token_id
size += javaNameWidth;
byte[] data = ELF.GetData (MapJavaSymbolName);
if (data.Length == 0)
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaSymbolName}' symbol");
ulong calculatedJavaTypeCount = (ulong)data.LongLength / size;
if (calculatedJavaTypeCount != javaTypeCount)
throw new InvalidOperationException ($"{filePath} has invalid '{JavaTypeCountSymbolName}' symbol value ({javaTypeCount}), '{JavaTypeCountSymbolName}' size indicates there are {calculatedJavaTypeCount} managedToJava instead");
var ret = new List<TypeMapJava> ();
ulong offset = 0;
for (ulong i = 0; i < javaTypeCount; i++) {
var javaEntry = new TypeMapJava {
module_index = ReadUInt32 (data, ref offset, packed: true),
type_token_id = ReadUInt32 (data, ref offset, packed: true),
};
javaEntry.java_name = ELF.GetASCIIZ (data, offset);
offset += javaNameWidth;
ret.Add (javaEntry);
}
return ret;
}
List<TypeMapModule> LoadMapModules (string filePath)
{
ulong moduleCount = (ulong)ELF.GetUInt32 (ModuleCountSymbolName);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
ulong size;
size = 16; // module_uuid
size += GetPaddedSize<uint> (size); // entry_count
size += GetPaddedSize<uint> (size); // duplicate_count
size += GetPaddedSize<string> (size); // map (pointer)
size += GetPaddedSize<string> (size); // duplicate_map (pointer)
size += GetPaddedSize<string> (size); // assembly_name (pointer)
size += GetPaddedSize<string> (size); // image (pointer)
size += GetPaddedSize<uint> (size); // java_name_width
size += GetPaddedSize<string> (size); // java_map (pointer)
byte[] moduleData = ELF.GetData (MapModulesSymbolName);
if (moduleData.Length == 0)
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
ulong calculatedModuleCount = (ulong)moduleData.Length / size;
if (calculatedModuleCount != moduleCount)
throw new InvalidOperationException ($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
var ret = new List<TypeMapModule> ();
ulong offset = 0;
for (ulong i = 0; i < moduleCount; i++) {
Log.Debug ($"Module {i + 1}");
var module = new TypeMapModule ();
byte[] mvid = new byte[16];
Array.Copy (moduleData, (int)offset, mvid, 0, mvid.Length);
module.module_uuid = new Guid (mvid);
offset += (ulong)mvid.Length;
Log.Debug ($" module_uuid == {module.module_uuid}");
module.entry_count = ReadUInt32 (moduleData, ref offset);
Log.Debug ($" entry_count == {module.entry_count}");
module.duplicate_count = ReadUInt32 (moduleData, ref offset);
Log.Debug ($" duplicate_count == {module.duplicate_count}");
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
ulong pointer = ReadPointer (moduleData, ref offset);
size = 0;
size += GetPaddedSize<uint> (size); // type_token_id
size += GetPaddedSize<uint> (size); // java_map_index
ulong mapSize = size * module.entry_count;
byte[] data = ELF.GetData (pointer, mapSize);
module.map = new List<TypeMapModuleEntry> ();
ReadMapEntries (module.map, data, module.entry_count);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
pointer = ReadPointer (moduleData, ref offset);
if (pointer != 0) {
mapSize = size * module.duplicate_count;
data = ELF.GetData (pointer, mapSize);
module.duplicate_map = new List<TypeMapModuleEntry> ();
ReadMapEntries (module.duplicate_map, data, module.duplicate_count);
}
pointer = ReadPointer (moduleData, ref offset);
module.assembly_name = ELF.GetASCIIZ (pointer);
Log.Debug ($" assembly_name == {module.assembly_name}");
Log.Debug ("");
// Read the values to properly adjust the offset taking padding into account
ReadPointer (moduleData, ref offset);
ReadUInt32 (moduleData, ref offset);
ReadPointer (moduleData, ref offset);
ret.Add (module);
}
return ret;
void ReadMapEntries (List<TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
{
ulong mapOffset = 0;
for (uint i = 0; i < entryCount; i++) {
var entry = new TypeMapModuleEntry {
type_token_id = ReadUInt32 (inputData, ref mapOffset),
java_map_index = ReadUInt32 (inputData, ref mapOffset)
};
map.Add (entry);
}
}
}
}
} }

Просмотреть файл

@ -0,0 +1,394 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace tmt;
class XamarinAppReleaseDSO_V1 : XamarinAppReleaseDSO_Version
{
protected override string LogTag => "ReleaseDSO_V1";
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
sealed class TypeMapModuleEntry
{
public uint type_token_id;
public uint java_map_index;
}
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
sealed class TypeMapModule
{
public Guid module_uuid;
public uint entry_count;
public uint duplicate_count;
public List<TypeMapModuleEntry>? map;
public List<TypeMapModuleEntry>? duplicate_map;
public string assembly_name = String.Empty;
// These three aren't used, listed for completeness
public readonly object? image = null;
public readonly uint java_name_width = 0;
public readonly byte[]? java_map = null;
}
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
sealed class TypeMapJava
{
public uint module_index;
public uint type_token_id;
public string java_name = String.Empty;
}
const string MapModulesSymbolName = "map_modules";
const string ModuleCountSymbolName = "map_module_count";
const string JavaTypeCountSymbolName = "java_type_count";
const string JavaNameWidthSymbolName = "java_name_width";
const string MapJavaSymbolName = "map_java";
Map? map;
List<TypeMapModule>? modules;
List<TypeMapJava>? javaTypes;
public override string FormatVersion => "1";
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
public XamarinAppReleaseDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
: base (managedResolver, elf)
{}
public override bool CanLoad (AnELF elf)
{
return HasSymbol (elf, MapModulesSymbolName) &&
HasSymbol (elf, ModuleCountSymbolName) &&
HasSymbol (elf, JavaTypeCountSymbolName) &&
HasSymbol (elf, JavaNameWidthSymbolName) &&
HasSymbol (elf, MapJavaSymbolName);
}
protected override bool LoadMaps ()
{
try {
string filePath = ELF.FilePath;
modules = LoadMapModules (filePath);
javaTypes = LoadJavaTypes (filePath);
return true;
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to load maps", ex);
return false;
}
}
protected override void SaveRaw (string baseOutputFilePath, string extension)
{
const string indent = "\t";
if (modules == null || javaTypes == null) {
Log.Warning ($"{Description}: cannot save raw report, no data");
return;
}
string outputFilePath = Utilities.GetManagedOutputFileName (baseOutputFilePath, extension);
Utilities.CreateFileDirectory (outputFilePath);
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
sw.WriteLine ("TYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_MAP_INDEX");
uint index = 0;
foreach (TypeMapModule module in modules) {
sw.WriteLine ();
sw.WriteLine ($"Module {index++:D04}: {module.assembly_name} (MVID: {module.module_uuid}; entries: {module.entry_count}; duplicates: {module.duplicate_count})");
if (module.map == null) {
sw.WriteLine ($"{indent}no map");
} else {
WriteManagedMap ("map", module.map, sw);
}
if (module.duplicate_map == null) {
if (module.duplicate_count > 0)
sw.WriteLine ($"{indent}no duplicate map, but there should be {module.duplicate_count} entries");
else
sw.WriteLine ($"{indent}no duplicates");
continue;
}
WriteManagedMap ("duplicate map", module.duplicate_map, sw);
}
sw.Flush ();
}
outputFilePath = Utilities.GetJavaOutputFileName (baseOutputFilePath, extension);
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
sw.WriteLine ("MANAGED_MODULE_INDEX\tTYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_TYPE_NAME");
foreach (TypeMapJava tmj in javaTypes) {
sw.WriteLine ($"{indent}{tmj.module_index}\t{tmj.type_token_id:D08} ({tmj.type_token_id:X08})\t{tmj.java_name}");
}
sw.Flush ();
}
void WriteManagedMap (string name, List<TypeMapModuleEntry> map, StreamWriter sw)
{
sw.WriteLine ($"{indent}{name}:");
foreach (TypeMapModuleEntry entry in map) {
sw.WriteLine ($"{indent}{indent}{entry.type_token_id} ({entry.type_token_id:X08})\t{entry.java_map_index}");
}
}
}
MapManagedType MakeManagedType (Guid mvid, uint tokenID, string assemblyName, string filePath, bool isGeneric, bool isDuplicate)
{
return new MapManagedType (mvid, tokenID, assemblyName, filePath) {
IsGeneric = isGeneric,
IsDuplicate = isDuplicate,
TypeName = ManagedResolver.Lookup (assemblyName, mvid, tokenID)
};
}
protected override bool Convert ()
{
try {
DoConvert ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
return false;
}
return true;
}
bool DoConvert ()
{
if (modules == null || javaTypes == null) {
Log.Warning ($"{Description}: cannot convert maps, no data");
return false;
}
string filePath = ELF.FilePath;
var managedToJava = new List<MapEntry> ();
uint index = 0;
bool somethingFailed = false;
foreach (TypeMapModule m in modules) {
ConvertManagedMap (m, EnsureMap (m), isDuplicate: false);
if (m.duplicate_map != null) {
if (!ConvertManagedMap (m, m.duplicate_map, isDuplicate: true)) {
somethingFailed = true;
}
}
index++;
}
if (somethingFailed) {
return false;
}
index = 0;
var javaToManaged = new List<MapEntry> ();
foreach (TypeMapJava tmj in javaTypes) {
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType (tmj.module_index, tmj.type_token_id);
if (!success) {
somethingFailed = true;
continue;
}
if (module == null) {
throw new InvalidOperationException ("module must not be null here");
}
javaToManaged.Add (
new MapEntry (
MakeManagedType (module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
new MapJavaType (tmj.java_name, filePath)
)
);
index++;
}
map = MakeMap (managedToJava, javaToManaged);
return true;
bool ConvertManagedMap (TypeMapModule module, List<TypeMapModuleEntry> map, bool isDuplicate)
{
foreach (TypeMapModuleEntry entry in map) {
TypeMapJava java;
if ((uint)javaTypes.Count <= entry.java_map_index) {
Log.Error ($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
return false;
}
java = javaTypes[(int)entry.java_map_index];
managedToJava.Add (
new MapEntry (
MakeManagedType (module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
new MapJavaType (java.java_name, filePath)
)
);
}
return true;
}
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) FindManagedType (uint moduleIndex, uint tokenID)
{
if (moduleIndex >= (uint)modules.Count) {
Log.Error ($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
return (false, null, false, false);
}
TypeMapModule m = modules[(int)moduleIndex];
if (tokenID == 0) {
return (true, m, true, false);
}
foreach (TypeMapModuleEntry entry in EnsureMap (m)) {
if (entry.type_token_id == tokenID) {
return (true, m, false, false);
}
}
if (m.duplicate_map != null) {
foreach (TypeMapModuleEntry entry in m.duplicate_map) {
if (entry.type_token_id == tokenID) {
return (true, m, false, true);
}
}
}
Log.Error ($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
return (false, null, false, false);
}
List<TypeMapModuleEntry> EnsureMap (TypeMapModule m)
{
if (m.map == null)
throw new InvalidOperationException ($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
return m.map;
}
}
List<TypeMapJava> LoadJavaTypes (string filePath)
{
ulong javaTypeCount = (ulong)ELF.GetUInt32 (JavaTypeCountSymbolName);
ulong javaNameWidth = (ulong)ELF.GetUInt32 (JavaNameWidthSymbolName);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
ulong size = 0;
size += GetPaddedSize<uint> (size); // module_index
size += GetPaddedSize<uint> (size); // type_token_id
size += javaNameWidth;
(byte[] data, _) = ELF.GetData (MapJavaSymbolName);
if (data.Length == 0)
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaSymbolName}' symbol");
ulong calculatedJavaTypeCount = (ulong)data.LongLength / size;
if (calculatedJavaTypeCount != javaTypeCount)
throw new InvalidOperationException ($"{filePath} has invalid '{JavaTypeCountSymbolName}' symbol value ({javaTypeCount}), '{JavaTypeCountSymbolName}' size indicates there are {calculatedJavaTypeCount} managedToJava instead");
var ret = new List<TypeMapJava> ();
ulong offset = 0;
for (ulong i = 0; i < javaTypeCount; i++) {
var javaEntry = new TypeMapJava {
module_index = ReadUInt32 (data, ref offset, packed: true),
type_token_id = ReadUInt32 (data, ref offset, packed: true),
};
javaEntry.java_name = ELF.GetASCIIZ (data, offset);
offset += javaNameWidth;
ret.Add (javaEntry);
}
return ret;
}
List<TypeMapModule> LoadMapModules (string filePath)
{
ulong moduleCount = (ulong)ELF.GetUInt32 (ModuleCountSymbolName);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
ulong size;
size = 16; // module_uuid
size += GetPaddedSize<uint> (size); // entry_count
size += GetPaddedSize<uint> (size); // duplicate_count
size += GetPaddedSize<string> (size); // map (pointer)
size += GetPaddedSize<string> (size); // duplicate_map (pointer)
size += GetPaddedSize<string> (size); // assembly_name (pointer)
size += GetPaddedSize<string> (size); // image (pointer)
size += GetPaddedSize<uint> (size); // java_name_width
size += GetPaddedSize<string> (size); // java_map (pointer)
(byte[] moduleData, _) = ELF.GetData (MapModulesSymbolName);
if (moduleData.Length == 0)
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
ulong calculatedModuleCount = (ulong)moduleData.Length / size;
if (calculatedModuleCount != moduleCount)
throw new InvalidOperationException ($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
var ret = new List<TypeMapModule> ();
ulong offset = 0;
for (ulong i = 0; i < moduleCount; i++) {
Log.Debug ($"Module {i + 1}");
var module = new TypeMapModule ();
byte[] mvid = new byte[16];
Array.Copy (moduleData, (int)offset, mvid, 0, mvid.Length);
module.module_uuid = new Guid (mvid);
offset += (ulong)mvid.Length;
Log.Debug ($" module_uuid == {module.module_uuid}");
module.entry_count = ReadUInt32 (moduleData, ref offset);
Log.Debug ($" entry_count == {module.entry_count}");
module.duplicate_count = ReadUInt32 (moduleData, ref offset);
Log.Debug ($" duplicate_count == {module.duplicate_count}");
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
ulong pointer = ReadPointer (moduleData, ref offset);
size = 0;
size += GetPaddedSize<uint> (size); // type_token_id
size += GetPaddedSize<uint> (size); // java_map_index
ulong mapSize = size * module.entry_count;
byte[] data = ELF.GetData (pointer, mapSize);
module.map = new List<TypeMapModuleEntry> ();
ReadMapEntries (module.map, data, module.entry_count);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
pointer = ReadPointer (moduleData, ref offset);
if (pointer != 0) {
mapSize = size * module.duplicate_count;
data = ELF.GetData (pointer, mapSize);
module.duplicate_map = new List<TypeMapModuleEntry> ();
ReadMapEntries (module.duplicate_map, data, module.duplicate_count);
}
pointer = ReadPointer (moduleData, ref offset);
module.assembly_name = ELF.GetASCIIZ (pointer);
Log.Debug ($" assembly_name == {module.assembly_name}");
Log.Debug ("");
// Read the values to properly adjust the offset taking padding into account
ReadPointer (moduleData, ref offset);
ReadUInt32 (moduleData, ref offset);
ReadPointer (moduleData, ref offset);
ret.Add (module);
}
return ret;
void ReadMapEntries (List<TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
{
ulong mapOffset = 0;
for (uint i = 0; i < entryCount; i++) {
var entry = new TypeMapModuleEntry {
type_token_id = ReadUInt32 (inputData, ref mapOffset),
java_map_index = ReadUInt32 (inputData, ref mapOffset)
};
map.Add (entry);
}
}
}
}

Просмотреть файл

@ -0,0 +1,540 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Hashing;
using System.Text;
using ELFSharp.ELF.Sections;
using Xamarin.Android.Tasks;
namespace tmt;
class XamarinAppReleaseDSO_V2 : XamarinAppReleaseDSO_Version
{
protected override string LogTag => "ReleaseDSO_V2";
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
sealed class TypeMapModuleEntry
{
public uint type_token_id;
public uint java_map_index;
}
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
sealed class TypeMapModule
{
public Guid module_uuid;
public uint entry_count;
public uint duplicate_count;
public List<TypeMapModuleEntry>? map;
public List<TypeMapModuleEntry>? duplicate_map;
public string assembly_name = String.Empty;
// These three aren't used, listed for completeness
public readonly object? image = null;
public readonly uint java_name_width = 0;
public readonly byte[]? java_map = null;
}
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
sealed class TypeMapJava
{
public uint module_index;
public uint type_token_id;
public uint java_name_index;
}
const string MapModulesSymbolName = "map_modules";
const string ModuleCountSymbolName = "map_module_count";
const string JavaTypeCountSymbolName = "java_type_count";
const string JavaTypeNamesSymbolName = "java_type_names";
const string MapJavaSymbolName = "map_java";
const string MapJavaHashesSymbolName = "map_java_hashes";
Map? map;
List<TypeMapModule>? modules;
List<TypeMapJava>? javaTypes;
List<string>? javaTypeNames;
List<ulong>? javaTypeNameHashes;
public override string FormatVersion => "2";
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
public XamarinAppReleaseDSO_V2 (ManagedTypeResolver managedResolver, AnELF elf)
: base (managedResolver, elf)
{}
public override bool CanLoad (AnELF elf)
{
return HasSymbol (elf, MapModulesSymbolName) &&
HasSymbol (elf, ModuleCountSymbolName) &&
HasSymbol (elf, JavaTypeCountSymbolName) &&
HasSymbol (elf, JavaTypeNamesSymbolName) &&
HasSymbol (elf, MapJavaSymbolName) &&
HasSymbol (elf, MapJavaHashesSymbolName);
}
protected override bool LoadMaps ()
{
try {
string filePath = ELF.FilePath;
modules = LoadMapModules (filePath);
// Order in which the entries are loaded is important. Farther loads use data gathered in the preceding ones
javaTypes = LoadJavaTypes (filePath);
javaTypeNameHashes = LoadJavaTypeNameHashes (filePath);
javaTypeNames = LoadJavaTypeNames (filePath);
return true;
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to load maps", ex);
return false;
}
}
protected override void SaveRaw (string baseOutputFilePath, string extension)
{
const string indent = "\t";
if (modules == null || javaTypes == null) {
Log.Warning ($"{Description}: cannot save raw report, no data");
return;
}
string outputFilePath = Utilities.GetManagedOutputFileName (baseOutputFilePath, extension);
Utilities.CreateFileDirectory (outputFilePath);
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
sw.WriteLine ("TYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_MAP_INDEX");
uint index = 0;
foreach (TypeMapModule module in modules) {
sw.WriteLine ();
sw.WriteLine ($"Module {index++:D04}: {module.assembly_name} (MVID: {module.module_uuid}; entries: {module.entry_count}; duplicates: {module.duplicate_count})");
if (module.map == null) {
sw.WriteLine ($"{indent}no map");
} else {
WriteManagedMap ("map", module.map, sw);
}
if (module.duplicate_map == null) {
if (module.duplicate_count > 0)
sw.WriteLine ($"{indent}no duplicate map, but there should be {module.duplicate_count} entries");
else
sw.WriteLine ($"{indent}no duplicates");
continue;
}
WriteManagedMap ("duplicate map", module.duplicate_map, sw);
}
sw.Flush ();
}
outputFilePath = Utilities.GetJavaOutputFileName (baseOutputFilePath, extension);
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
sw.WriteLine ("MANAGED_MODULE_INDEX\tTYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_TYPE_NAME");
foreach (TypeMapJava tmj in javaTypes) {
sw.WriteLine ($"{indent}{tmj.module_index}\t{tmj.type_token_id:D08} ({tmj.type_token_id:X08})\t{GetJavaTypeName (tmj)}");
}
sw.Flush ();
}
void WriteManagedMap (string name, List<TypeMapModuleEntry> map, StreamWriter sw)
{
sw.WriteLine ($"{indent}{name}:");
foreach (TypeMapModuleEntry entry in map) {
sw.WriteLine ($"{indent}{indent}{entry.type_token_id} ({entry.type_token_id:X08})\t{entry.java_map_index}");
}
}
}
MapManagedType MakeManagedType (Guid mvid, uint tokenID, string assemblyName, string filePath, bool isGeneric, bool isDuplicate)
{
return new MapManagedType (mvid, tokenID, assemblyName, filePath) {
IsGeneric = isGeneric,
IsDuplicate = isDuplicate,
TypeName = ManagedResolver.Lookup (assemblyName, mvid, tokenID)
};
}
protected override bool Convert ()
{
try {
DoConvert ();
} catch (Exception ex) {
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
return false;
}
return true;
}
bool DoConvert ()
{
const string Present = "present";
const string Absent = "absent";
if (modules == null || javaTypes == null || javaTypeNames == null) {
Log.Warning (LogTag, $"cannot convert maps, missing data:");
Log.Warning ($" {MapModulesSymbolName}: {(modules == null ? Absent : Present)}");
Log.Warning ($" {MapJavaSymbolName}: {(javaTypes == null ? Absent : Present)}");
Log.Warning ($" {JavaTypeNamesSymbolName}: {(javaTypeNames == null ? Absent : Present)}");
return false;
}
if (javaTypes.Count != javaTypeNames.Count) {
Log.Warning (LogTag, $"Java types map has a different number of entries ({javaTypes.Count}) than the Java type names array ({javaTypeNames.Count})");
return false;
}
string filePath = ELF.FilePath;
var managedToJava = new List<MapEntry> ();
uint index = 0;
bool somethingFailed = false;
foreach (TypeMapModule m in modules) {
ConvertManagedMap (m, EnsureMap (m), isDuplicate: false);
if (m.duplicate_map != null) {
if (!ConvertManagedMap (m, m.duplicate_map, isDuplicate: true)) {
somethingFailed = true;
}
}
index++;
}
if (somethingFailed) {
return false;
}
index = 0;
var javaToManaged = new List<MapEntry> ();
foreach (TypeMapJava tmj in javaTypes) {
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType (tmj.module_index, tmj.type_token_id);
if (!success) {
somethingFailed = true;
continue;
}
if (module == null) {
throw new InvalidOperationException ("module must not be null here");
}
javaToManaged.Add (
new MapEntry (
MakeManagedType (module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
new MapJavaType (GetJavaTypeName (tmj), filePath)
)
);
index++;
}
map = MakeMap (managedToJava, javaToManaged);
return true;
bool ConvertManagedMap (TypeMapModule module, List<TypeMapModuleEntry> map, bool isDuplicate)
{
foreach (TypeMapModuleEntry entry in map) {
TypeMapJava java;
if ((uint)javaTypes.Count <= entry.java_map_index) {
Log.Error ($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
return false;
}
java = javaTypes[(int)entry.java_map_index];
managedToJava.Add (
new MapEntry (
MakeManagedType (module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
new MapJavaType (GetJavaTypeName (java), filePath)
)
);
}
return true;
}
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) FindManagedType (uint moduleIndex, uint tokenID)
{
if (moduleIndex >= (uint)modules.Count) {
Log.Error ($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
return (false, null, false, false);
}
TypeMapModule m = modules[(int)moduleIndex];
if (tokenID == 0) {
return (true, m, true, false);
}
foreach (TypeMapModuleEntry entry in EnsureMap (m)) {
if (entry.type_token_id == tokenID) {
return (true, m, false, false);
}
}
if (m.duplicate_map != null) {
foreach (TypeMapModuleEntry entry in m.duplicate_map) {
if (entry.type_token_id == tokenID) {
return (true, m, false, true);
}
}
}
Log.Error ($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
return (false, null, false, false);
}
List<TypeMapModuleEntry> EnsureMap (TypeMapModule m)
{
if (m.map == null) {
throw new InvalidOperationException ($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
}
return m.map;
}
}
List<ulong> LoadJavaTypeNameHashes (string filePath)
{
Log.Debug ();
Log.Debug (LogTag, "Reading Java type name hashes");
ulong size = 0;
if (Is64Bit) {
size += GetPaddedSize<ulong> (size); // hashes are 64-bit
} else {
size += GetPaddedSize<uint> (size); // hashes are 32-bit
}
(byte[] hashesData, ISymbolEntry? symbol) = ELF.GetData (MapJavaHashesSymbolName);
if (hashesData.Length == 0 || symbol == null) {
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaHashesSymbolName}' symbol");
}
var ret = new List<ulong> ();
ulong offset = 0;
for (ulong i = 0; i < (ulong)hashesData.Length / size; i++) {
ulong hash;
if (Is64Bit) {
hash = ReadUInt64 (hashesData, ref offset);
} else {
hash = (ulong)ReadUInt32 (hashesData, ref offset);
}
Log.Debug (LogTag, $" [{i}] 0x{HashToHexString(hash)}");
ret.Add (hash);
}
Log.Debug ();
Log.Debug (LogTag, $"Java type name hashes loaded (count: {ret.Count})");
return ret;
}
List<string> LoadJavaTypeNames (string filePath)
{
Log.Debug ();
Log.Debug (LogTag, "Reading Java type names");
ulong size = 0;
size += GetPaddedSize<string> (size); // pointers
(byte[] namesData, ISymbolEntry? symbol) = ELF.GetData (JavaTypeNamesSymbolName);
if (namesData.Length == 0 || symbol == null) {
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{JavaTypeNamesSymbolName}' symbol");
}
var ret = new List<string> ();
ulong offset = 0;
for (ulong i = 0; i < (ulong)namesData.Length / size; i++) {
ulong pointer = ReadPointer (symbol, namesData, ref offset);
string? name;
if (pointer != 0) {
name = ELF.GetASCIIZFromPointer (pointer);
} else {
name = null;
}
ret.Add (name ?? String.Empty);
ulong hash = TypeMapHelper.HashJavaName (name ?? String.Empty, Is64Bit);
int javaTypeIndex = javaTypeNameHashes!.IndexOf (hash);
if (javaTypeIndex < 0) {
Log.Warning (LogTag, $"Hash 0x{HashToHexString(hash)} for Java type name '{name}' not found in the '{MapJavaHashesSymbolName}' array");
}
Log.Debug (LogTag, $" [{i}] {(name ?? String.Empty)} (hash: 0x{HashToHexString(hash)})");
}
Log.Debug ();
Log.Debug (LogTag, $"Java type names loaded (count: {ret.Count})");
return ret;
}
List<TypeMapJava> LoadJavaTypes (string filePath)
{
Log.Debug ();
Log.Debug (LogTag, "Reading Java types");
ulong javaTypeCount = (ulong)ELF.GetUInt32 (JavaTypeCountSymbolName);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
ulong size = 0;
size += GetPaddedSize<uint> (size); // module_index
size += GetPaddedSize<uint> (size); // type_token_id
size += GetPaddedSize<uint> (size); // java_name_index
(byte[] data, ISymbolEntry? symbol) = ELF.GetData (MapJavaSymbolName);
if (data.Length == 0) {
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaSymbolName}' symbol");
}
ulong calculatedJavaTypeCount = (ulong)data.LongLength / size;
if (calculatedJavaTypeCount != javaTypeCount)
throw new InvalidOperationException ($"{filePath} has invalid '{JavaTypeCountSymbolName}' symbol value ({javaTypeCount}), '{JavaTypeCountSymbolName}' size indicates there are {calculatedJavaTypeCount} managedToJava instead");
var ret = new List<TypeMapJava> ();
ulong offset = 0;
for (ulong i = 0; i < javaTypeCount; i++) {
var javaEntry = new TypeMapJava {
module_index = ReadUInt32 (data, ref offset, packed: true),
type_token_id = ReadUInt32 (data, ref offset, packed: true),
java_name_index = ReadUInt32 (data, ref offset, packed: true),
};
ret.Add (javaEntry);
}
Log.Debug (LogTag, $"Java types loaded (count: {ret.Count})");
Log.Debug ();
return ret;
}
List<TypeMapModule> LoadMapModules (string filePath)
{
Log.Debug ();
Log.Debug (LogTag, "Reading map modules");
ulong moduleCount = (ulong)ELF.GetUInt32 (ModuleCountSymbolName);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
ulong size;
size = 16; // module_uuid
size += GetPaddedSize<uint> (size); // entry_count
size += GetPaddedSize<uint> (size); // duplicate_count
size += GetPaddedSize<string> (size); // map (pointer)
size += GetPaddedSize<string> (size); // duplicate_map (pointer)
size += GetPaddedSize<string> (size); // assembly_name (pointer)
size += GetPaddedSize<string> (size); // image (pointer)
size += GetPaddedSize<uint> (size); // java_name_width
size += GetPaddedSize<string> (size); // java_map (pointer)
(byte[] moduleData, ISymbolEntry? symbol) = ELF.GetData (MapModulesSymbolName);
if (moduleData.Length == 0 || symbol == null) {
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
}
ulong calculatedModuleCount = (ulong)moduleData.Length / size;
if (calculatedModuleCount != moduleCount) {
throw new InvalidOperationException ($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
}
var ret = new List<TypeMapModule> ();
ulong offset = 0;
for (ulong i = 0; i < moduleCount; i++) {
Log.Debug ($"Module {i + 1}");
var module = new TypeMapModule ();
byte[] mvid = new byte[16];
Array.Copy (moduleData, (int)offset, mvid, 0, mvid.Length);
module.module_uuid = new Guid (mvid);
offset += (ulong)mvid.Length;
Log.Debug (LogTag, $" module_uuid == {module.module_uuid} (offset: {offset})");
module.entry_count = ReadUInt32 (moduleData, ref offset);
Log.Debug (LogTag, $" entry_count == {module.entry_count} (offset: {offset})");
module.duplicate_count = ReadUInt32 (moduleData, ref offset);
Log.Debug (LogTag, $" duplicate_count == {module.duplicate_count} (offset: {offset})");
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
ulong pointer = ReadPointer (symbol, moduleData, ref offset);
Log.Debug (LogTag, $" *map == 0x{pointer:x} (offset: {offset})");
if (pointer == 0) {
throw new InvalidOperationException ($"Broken typemap structure, map pointer for module {module.module_uuid} is null");
}
size = 0;
size += GetPaddedSize<uint> (size); // type_token_id
size += GetPaddedSize<uint> (size); // java_map_index
ulong mapSize = size * module.entry_count;
byte[] data = ELF.GetDataFromPointer (pointer, mapSize);
module.map = new List<TypeMapModuleEntry> ();
ReadMapEntries (module.map, data, module.entry_count);
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
pointer = ReadPointer (symbol, moduleData, ref offset);
if (pointer != 0) {
mapSize = size * module.duplicate_count;
data = ELF.GetDataFromPointer (pointer, mapSize);
module.duplicate_map = new List<TypeMapModuleEntry> ();
ReadMapEntries (module.duplicate_map, data, module.duplicate_count);
}
pointer = ReadPointer (symbol, moduleData, ref offset);
if (pointer != 0) {
module.assembly_name = ELF.GetASCIIZFromPointer (pointer);
}
Log.Debug ($" assembly_name == {module.assembly_name}");
Log.Debug ("");
// Read the values to properly adjust the offset taking padding into account
ReadPointer (moduleData, ref offset);
ReadUInt32 (moduleData, ref offset);
ReadPointer (moduleData, ref offset);
ret.Add (module);
// Padding
offset += offset % size;
}
Log.Debug (LogTag, $"Map modules loaded (count: {ret.Count})");
Log.Debug ();
return ret;
void ReadMapEntries (List<TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
{
ulong mapOffset = 0;
for (uint i = 0; i < entryCount; i++) {
var entry = new TypeMapModuleEntry {
type_token_id = ReadUInt32 (inputData, ref mapOffset),
java_map_index = ReadUInt32 (inputData, ref mapOffset)
};
map.Add (entry);
}
}
}
string GetJavaTypeName (TypeMapJava tmj)
{
if (javaTypeNames == null) {
return "<no Java type names data>";
}
if (tmj.java_name_index >= javaTypeNames.Count) {
return $"<invalid name index {tmj.java_name_index}>";
}
return javaTypeNames[(int)tmj.java_name_index];
}
string HashToHexString (ulong hash) => Is64Bit ? $"{hash:X16}" : $"{hash:X8}";
}

Просмотреть файл

@ -150,7 +150,7 @@ namespace tmt
var loader = new Loader (parsedOptions.ArchFilter, parsedOptions.LoadOnlyFirst.Value); var loader = new Loader (parsedOptions.ArchFilter, parsedOptions.LoadOnlyFirst.Value);
List<ITypemap> typemaps = loader.TryLoad (loadFrom); List<ITypemap> typemaps = loader.TryLoad (loadFrom);
if (typemaps.Count == 0) { if (typemaps.Count == 0) {
Log.Info ($"No type maps found in '{loadFrom}"); Log.Error ($"No supported type maps found in '{loadFrom}");
return 1; return 1;
} }

Просмотреть файл

@ -13,9 +13,15 @@
<RollForward>Major</RollForward> <RollForward>Major</RollForward>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="../../src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs" />
<Compile Include="..\assembly-store-reader\AssemblyStore*.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" /> <PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" />
<PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" /> <PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" />
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
<PackageReference Include="Xamarin.LibZipSharp" Version="$(LibZipSharpVersion)" /> <PackageReference Include="Xamarin.LibZipSharp" Version="$(LibZipSharpVersion)" />
<PackageReference Include="ELFSharp" Version="$(ELFSharpVersion)" /> <PackageReference Include="ELFSharp" Version="$(ELFSharpVersion)" />
<PackageReference Include="K4os.Compression.LZ4" Version="$(LZ4PackageVersion)" /> <PackageReference Include="K4os.Compression.LZ4" Version="$(LZ4PackageVersion)" />