[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:
Родитель
5472eec991
Коммит
71b6fcc92f
|
@ -132,7 +132,7 @@ namespace Xamarin.Android.Tasks
|
|||
}
|
||||
|
||||
// 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>? 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;
|
||||
|
||||
// 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);
|
||||
|
||||
entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true);
|
||||
entry.JavaNameHash64 = TypeMapHelper.HashJavaName (entry.JavaName, is64Bit: true);
|
||||
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 "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 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
|
||||
|
|
|
@ -87,6 +87,17 @@ namespace Xamarin.Android.AssemblyStore
|
|||
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)
|
||||
{
|
||||
if (level == AssemblyStoreExplorerLogLevel.Error) {
|
||||
|
|
|
@ -74,28 +74,35 @@ namespace tmt
|
|||
return GetSymbol (symbolName) != null;
|
||||
}
|
||||
|
||||
public byte[] GetData (string symbolName)
|
||||
public (byte[] data, ISymbolEntry? symbol) GetData (string symbolName)
|
||||
{
|
||||
Log.Debug ($"Looking for symbol: {symbolName}");
|
||||
ISymbolEntry? symbol = GetSymbol (symbolName);
|
||||
if (symbol == null)
|
||||
return EmptyArray;
|
||||
if (symbol == null) {
|
||||
return (EmptyArray, null);
|
||||
}
|
||||
|
||||
if (Is64Bit) {
|
||||
var symbol64 = symbol as SymbolEntry<ulong>;
|
||||
if (symbol64 == null)
|
||||
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>;
|
||||
if (symbol32 == null)
|
||||
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[] GetDataFromPointer (ulong pointerValue, ulong size);
|
||||
|
||||
public string GetASCIIZFromPointer (ulong pointerValue)
|
||||
{
|
||||
return GetASCIIZ (GetDataFromPointer (pointerValue, 0), 0);
|
||||
}
|
||||
|
||||
public string GetASCIIZ (ulong symbolValue)
|
||||
{
|
||||
|
@ -134,14 +141,27 @@ namespace tmt
|
|||
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)
|
||||
{
|
||||
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 ();
|
||||
|
||||
Log.Debug ($" data length: {data.Length} (long: {data.LongLength})");
|
||||
Log.Debug ($" offset: {offset}; size: {size}");
|
||||
if (section is Section<ulong> sec64) {
|
||||
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)) {
|
||||
Log.Debug ($" not enough data in section");
|
||||
return EmptyArray;
|
||||
}
|
||||
|
||||
|
@ -156,9 +176,18 @@ namespace tmt
|
|||
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)
|
||||
{
|
||||
return GetUInt32 (GetData (symbolName), 0, symbolName);
|
||||
(byte[] data, _) = GetData (symbolName);
|
||||
return GetUInt32 (data, 0, symbolName);
|
||||
}
|
||||
|
||||
public uint GetUInt32 (ulong symbolValue)
|
||||
|
@ -177,7 +206,8 @@ namespace tmt
|
|||
|
||||
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)
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.IO;
|
|||
|
||||
using K4os.Compression.LZ4;
|
||||
using Mono.Cecil;
|
||||
using Xamarin.Android.AssemblyStore;
|
||||
using Xamarin.Tools.Zip;
|
||||
|
||||
namespace tmt
|
||||
|
@ -12,15 +13,44 @@ namespace tmt
|
|||
{
|
||||
const uint CompressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
|
||||
|
||||
Dictionary<string, ZipEntry> assemblies;
|
||||
ZipArchive apk;
|
||||
readonly Dictionary<string, ZipEntry>? individualAssemblies;
|
||||
readonly Dictionary<string, AssemblyStoreAssembly>? blobAssemblies;
|
||||
readonly ZipArchive apk;
|
||||
readonly AssemblyStoreExplorer? assemblyStoreExplorer;
|
||||
|
||||
public ApkManagedTypeResolver (ZipArchive apk, string assemblyEntryPrefix)
|
||||
{
|
||||
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)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -29,35 +59,78 @@ namespace tmt
|
|||
continue;
|
||||
}
|
||||
|
||||
assemblies.Add (Path.GetFileNameWithoutExtension (entry.FullName), entry);
|
||||
assemblies.Add (entry.FullName, entry);
|
||||
string relativeName = entry.FullName.Substring (assemblyEntryPrefix.Length);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (!assemblies.TryGetValue (assemblyName, out ZipEntry? entry) || entry == null) {
|
||||
return null;
|
||||
return assembly.Name;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
var stream = new MemoryStream ();
|
||||
entry.Extract (stream);
|
||||
stream.Seek (0, SeekOrigin.Begin);
|
||||
Stream stream = GetAssemblyStream (assemblyPath);
|
||||
|
||||
//
|
||||
// LZ4 compressed assembly header format:
|
||||
|
@ -65,25 +138,25 @@ namespace tmt
|
|||
// uint descriptor_index; // Index into an internal assembly descriptor table
|
||||
// uint uncompressed_length; // Size of assembly, uncompressed
|
||||
//
|
||||
using (var reader = new BinaryReader (stream)) {
|
||||
uint magic = reader.ReadUInt32 ();
|
||||
if (magic == CompressedDataMagic) {
|
||||
reader.ReadUInt32 (); // descriptor index, ignore
|
||||
uint decompressedLength = reader.ReadUInt32 ();
|
||||
using var reader = new BinaryReader (stream);
|
||||
uint magic = reader.ReadUInt32 ();
|
||||
if (magic == CompressedDataMagic) {
|
||||
reader.ReadUInt32 (); // descriptor index, ignore
|
||||
uint decompressedLength = reader.ReadUInt32 ();
|
||||
|
||||
int inputLength = (int)(stream.Length - 12);
|
||||
byte[] sourceBytes = Utilities.BytePool.Rent (inputLength);
|
||||
reader.Read (sourceBytes, 0, inputLength);
|
||||
int inputLength = (int)(stream.Length - 12);
|
||||
byte[] sourceBytes = Utilities.BytePool.Rent (inputLength);
|
||||
reader.Read (sourceBytes, 0, inputLength);
|
||||
|
||||
assemblyBytes = Utilities.BytePool.Rent ((int)decompressedLength);
|
||||
int decoded = LZ4Codec.Decode (sourceBytes, 0, inputLength, assemblyBytes, 0, (int)decompressedLength);
|
||||
if (decoded != (int)decompressedLength) {
|
||||
throw new InvalidOperationException ($"Failed to decompress LZ4 data of {assemblyPath} (decoded: {decoded})");
|
||||
}
|
||||
Utilities.BytePool.Return (sourceBytes);
|
||||
assemblyBytes = Utilities.BytePool.Rent ((int)decompressedLength);
|
||||
int decoded = LZ4Codec.Decode (sourceBytes, 0, inputLength, assemblyBytes, 0, (int)decompressedLength);
|
||||
if (decoded != (int)decompressedLength) {
|
||||
throw new InvalidOperationException ($"Failed to decompress LZ4 data of {assemblyPath} (decoded: {decoded})");
|
||||
}
|
||||
Utilities.BytePool.Return (sourceBytes);
|
||||
}
|
||||
|
||||
|
||||
if (assemblyBytes != null) {
|
||||
stream.Close ();
|
||||
stream.Dispose ();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using ELFSharp;
|
||||
using ELFSharp.ELF;
|
||||
using ELFSharp.ELF.Sections;
|
||||
|
||||
|
@ -21,6 +21,83 @@ namespace tmt
|
|||
: 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)
|
||||
{
|
||||
checked {
|
||||
|
@ -54,6 +131,24 @@ namespace tmt
|
|||
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)
|
||||
{
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using ELFSharp;
|
||||
using ELFSharp.ELF;
|
||||
using ELFSharp.ELF.Sections;
|
||||
|
||||
|
@ -21,6 +21,78 @@ namespace tmt
|
|||
: 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)
|
||||
{
|
||||
Log.Debug ($"ELF64.GetData: Looking for symbol value {symbolValue:X08}");
|
||||
|
@ -30,7 +102,7 @@ namespace tmt
|
|||
symbol = GetSymbol (Symbols, symbolValue);
|
||||
}
|
||||
|
||||
if (symbol != null) {
|
||||
if (symbol != null && symbol.PointedSection != null) {
|
||||
Log.Debug ($"ELF64.GetData: found in section {symbol.PointedSection.Name}");
|
||||
return GetData (symbol);
|
||||
}
|
||||
|
@ -46,6 +118,26 @@ namespace tmt
|
|||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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");;
|
||||
}
|
||||
}
|
110
tools/tmt/Log.cs
110
tools/tmt/Log.cs
|
@ -4,42 +4,140 @@ namespace tmt
|
|||
{
|
||||
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 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)
|
||||
{
|
||||
showDebug = verbose;
|
||||
}
|
||||
|
||||
public static void Error (string message = "")
|
||||
{
|
||||
Error (tag: String.Empty, message);
|
||||
}
|
||||
|
||||
public static void Error (string tag, string message)
|
||||
{
|
||||
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 = "")
|
||||
{
|
||||
Warning (tag: String.Empty, message);
|
||||
}
|
||||
|
||||
public static void Warning (string tag, string message)
|
||||
{
|
||||
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 = "")
|
||||
{
|
||||
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 = "")
|
||||
{
|
||||
Debug (tag: String.Empty, message);
|
||||
}
|
||||
|
||||
public static void Debug (string tag, string message)
|
||||
{
|
||||
if (!showDebug) {
|
||||
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)
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace tmt
|
|||
AlreadyWarned.Add (assemblyName);
|
||||
}
|
||||
|
||||
return $"[token id: {tokenID}]";
|
||||
return $"`token id: {tokenID}`";
|
||||
}
|
||||
|
||||
protected bool TryLookup (string assemblyPath, Guid mvid, uint tokenID, out string typeName)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
const string FileFieldSeparator = "\t";
|
||||
const string ManagedTypeColumnHeader = "Managed-Type-Name";
|
||||
const string JavaTypeColumnHeader = "Java-Type-Name";
|
||||
const string DuplicateColumnHeader = "Is-Duplicate-Type-Entry?";
|
||||
const string GenericColumnHeader = "Is-Generic-Type?";
|
||||
const string MVIDColumnHeader = "MVID";
|
||||
const string TokenIDColumnHeader = "Token-ID";
|
||||
|
||||
const string FormattedDuplicateColumnHeader = "Is Duplicate?";
|
||||
const string FormattedGenericColumnHeader = "Is Generic?";
|
||||
const string FormattedJavaTypeColumnHeader = "Java type name";
|
||||
const string FormattedMVIDColumnHeader = "MVID";
|
||||
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;
|
||||
Regex? filterRegex;
|
||||
|
@ -37,6 +103,235 @@ namespace tmt
|
|||
}
|
||||
|
||||
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;
|
||||
Action<StreamWriter, MapEntry, bool, bool> fileGenerator;
|
||||
|
@ -157,7 +452,7 @@ namespace tmt
|
|||
{
|
||||
if (firstEntry) {
|
||||
string sep = FileFieldSeparator;
|
||||
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{DuplicateColumnHeader}");
|
||||
sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
|
||||
}
|
||||
|
||||
WriteLineToFile (sw,
|
||||
|
@ -180,7 +475,7 @@ namespace tmt
|
|||
if (!full) {
|
||||
if (firstEntry) {
|
||||
string sep = FileFieldSeparator;
|
||||
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{GenericColumnHeader}");
|
||||
sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedGenericColumnHeader}");
|
||||
}
|
||||
|
||||
WriteLineToFile (
|
||||
|
@ -194,7 +489,7 @@ namespace tmt
|
|||
|
||||
if (firstEntry) {
|
||||
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 (
|
||||
sw,
|
||||
|
@ -238,7 +533,7 @@ namespace tmt
|
|||
{
|
||||
if (firstEntry) {
|
||||
string sep = FileFieldSeparator;
|
||||
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{DuplicateColumnHeader}");
|
||||
sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
|
||||
}
|
||||
|
||||
WriteLineToFile (sw,
|
||||
|
@ -262,7 +557,7 @@ namespace tmt
|
|||
if (full) {
|
||||
if (firstEntry) {
|
||||
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 (
|
||||
sw,
|
||||
|
@ -276,7 +571,7 @@ namespace tmt
|
|||
} else {
|
||||
if (firstEntry) {
|
||||
string sep = FileFieldSeparator;
|
||||
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{DuplicateColumnHeader}");
|
||||
sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
|
||||
}
|
||||
WriteLineToFile (
|
||||
sw,
|
||||
|
@ -291,9 +586,9 @@ namespace tmt
|
|||
void ConsoleGenerateManagedToJavaRelease (MapEntry entry, bool full)
|
||||
{
|
||||
if (!full) {
|
||||
Log.Info ($" {GetManagedTypeNameDebug (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
|
||||
Log.Info ($" {GetManagedTypeNameRelease (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
|
||||
} 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.IO;
|
||||
|
||||
using ELFSharp.ELF.Sections;
|
||||
|
||||
namespace tmt
|
||||
{
|
||||
abstract class XamarinAppDSO : ITypemap
|
||||
{
|
||||
// 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";
|
||||
|
||||
|
@ -18,6 +21,7 @@ namespace tmt
|
|||
|
||||
public MapArchitecture MapArchitecture => ELF.MapArchitecture;
|
||||
public string FullPath { get; } = String.Empty;
|
||||
protected abstract string LogTag { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string FormatVersion { get; }
|
||||
public abstract Map Map { get; }
|
||||
|
@ -64,87 +68,44 @@ namespace tmt
|
|||
|
||||
protected uint ReadUInt32 (byte[] data, ref ulong offset, bool packed = false)
|
||||
{
|
||||
const ulong DataSize = 4;
|
||||
|
||||
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;
|
||||
return Helpers.ReadUInt32 (data, ref offset, Is64Bit, packed);
|
||||
}
|
||||
|
||||
protected ulong ReadUInt64 (byte[] data, ref ulong offset, bool packed = false)
|
||||
{
|
||||
const ulong DataSize = 8;
|
||||
|
||||
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;
|
||||
return Helpers.ReadUInt64 (data, ref offset, Is64Bit, packed);
|
||||
}
|
||||
|
||||
protected ulong ReadPointer (byte[] data, ref ulong offset, bool packed = false)
|
||||
{
|
||||
ulong ret;
|
||||
return Helpers.ReadPointer (data, ref offset, Is64Bit, packed);
|
||||
}
|
||||
|
||||
if (Is64Bit) {
|
||||
ret = ReadUInt64 (data, ref offset, packed);
|
||||
} else {
|
||||
ret = (ulong)ReadUInt32 (data, ref offset, packed);
|
||||
protected ulong ReadPointer (ISymbolEntry symbol, byte[] data, ref ulong offset, bool packed = false)
|
||||
{
|
||||
ulong prevOffset = offset;
|
||||
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)
|
||||
{
|
||||
ulong typeSize = GetTypeSize<S> ();
|
||||
|
||||
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");
|
||||
return Helpers.GetPaddedSize<S> (sizeSoFar, Is64Bit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace tmt
|
|||
XamarinAppDebugDSO_Version? xapp;
|
||||
|
||||
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 Map Map => XAPP.Map;
|
||||
|
||||
|
@ -19,6 +20,8 @@ namespace tmt
|
|||
|
||||
public override bool CanLoad (AnELF elf)
|
||||
{
|
||||
Log.Debug (LogTag, $"Checking if {elf.FilePath} is a Debug DSO");
|
||||
|
||||
xapp = null;
|
||||
ulong format_tag = 0;
|
||||
if (elf.HasSymbol (FormatTag))
|
||||
|
@ -32,8 +35,13 @@ namespace tmt
|
|||
reader = new XamarinAppDebugDSO_V1 (ManagedResolver, elf);
|
||||
break;
|
||||
|
||||
case FormatTag_V2:
|
||||
format_tag = 2;
|
||||
reader = new XamarinAppDebugDSO_V2 (ManagedResolver, elf);
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -76,180 +84,4 @@ namespace tmt
|
|||
protected abstract bool LoadMaps ();
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace tmt
|
||||
{
|
||||
|
@ -10,6 +8,7 @@ namespace tmt
|
|||
XamarinAppReleaseDSO_Version? xapp;
|
||||
|
||||
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 Map Map => XAPP.Map;
|
||||
|
||||
|
@ -25,7 +24,7 @@ namespace tmt
|
|||
|
||||
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;
|
||||
ulong format_tag = 0;
|
||||
|
@ -40,8 +39,13 @@ namespace tmt
|
|||
reader = new XamarinAppReleaseDSO_V1 (ManagedResolver, elf);
|
||||
break;
|
||||
|
||||
case FormatTag_V2:
|
||||
format_tag = 2;
|
||||
reader = new XamarinAppReleaseDSO_V2 (ManagedResolver, elf);
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -90,391 +94,4 @@ namespace tmt
|
|||
protected abstract bool Convert ();
|
||||
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);
|
||||
List<ITypemap> typemaps = loader.TryLoad (loadFrom);
|
||||
if (typemaps.Count == 0) {
|
||||
Log.Info ($"No type maps found in '{loadFrom}");
|
||||
Log.Error ($"No supported type maps found in '{loadFrom}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,9 +13,15 @@
|
|||
<RollForward>Major</RollForward>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="../../src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs" />
|
||||
<Compile Include="..\assembly-store-reader\AssemblyStore*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" />
|
||||
<PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" />
|
||||
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
|
||||
<PackageReference Include="Xamarin.LibZipSharp" Version="$(LibZipSharpVersion)" />
|
||||
<PackageReference Include="ELFSharp" Version="$(ELFSharpVersion)" />
|
||||
<PackageReference Include="K4os.Compression.LZ4" Version="$(LZ4PackageVersion)" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче