[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
|
// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
|
||||||
const ulong FORMAT_TAG = 0x015E6972616D58;
|
const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
|
||||||
|
|
||||||
SortedDictionary <string, string>? environmentVariables;
|
SortedDictionary <string, string>? environmentVariables;
|
||||||
SortedDictionary <string, string>? systemProperties;
|
SortedDictionary <string, string>? systemProperties;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using System.IO.Hashing;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Xamarin.Android.Tasks;
|
||||||
|
|
||||||
|
static class TypeMapHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hash the given Java type name for use in java-to-managed typemap array.
|
||||||
|
/// </summary>
|
||||||
|
public static ulong HashJavaName (string name, bool is64Bit)
|
||||||
|
{
|
||||||
|
if (name.Length == 0) {
|
||||||
|
return UInt64.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do
|
||||||
|
// the same
|
||||||
|
return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ulong HashBytes (byte[] bytes, bool is64Bit)
|
||||||
|
{
|
||||||
|
if (is64Bit) {
|
||||||
|
return XxHash64.HashToUInt64 (bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ulong)XxHash32.HashToUInt32 (bytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -403,32 +403,12 @@ namespace Xamarin.Android.Tasks
|
||||||
TypeMapJava entry = cs.JavaMap[i].Instance;
|
TypeMapJava entry = cs.JavaMap[i].Instance;
|
||||||
|
|
||||||
// The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit
|
// The cast is safe, xxHash will return a 32-bit value which (for convenience) was upcast to 64-bit
|
||||||
entry.JavaNameHash32 = (uint)HashName (entry.JavaName, is64Bit: false);
|
entry.JavaNameHash32 = (uint)TypeMapHelper.HashJavaName (entry.JavaName, is64Bit: false);
|
||||||
hashes32.Add (entry.JavaNameHash32);
|
hashes32.Add (entry.JavaNameHash32);
|
||||||
|
|
||||||
entry.JavaNameHash64 = HashName (entry.JavaName, is64Bit: true);
|
entry.JavaNameHash64 = TypeMapHelper.HashJavaName (entry.JavaName, is64Bit: true);
|
||||||
hashes64.Add (entry.JavaNameHash64);
|
hashes64.Add (entry.JavaNameHash64);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong HashName (string name, bool is64Bit)
|
|
||||||
{
|
|
||||||
if (name.Length == 0) {
|
|
||||||
return UInt64.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native code (EmbeddedAssemblies::typemap_java_to_managed in embedded-assemblies.cc) will operate on wchar_t cast to a byte array, we need to do
|
|
||||||
// the same
|
|
||||||
return HashBytes (Encoding.Unicode.GetBytes (name), is64Bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong HashBytes (byte[] bytes, bool is64Bit)
|
|
||||||
{
|
|
||||||
if (is64Bit) {
|
|
||||||
return XxHash64.HashToUInt64 (bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (ulong)XxHash32.HashToUInt32 (bytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
#include "monodroid.h"
|
#include "monodroid.h"
|
||||||
#include "xxhash.hh"
|
#include "xxhash.hh"
|
||||||
|
|
||||||
static constexpr uint64_t FORMAT_TAG = 0x015E6972616D58;
|
static constexpr uint64_t FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
|
||||||
static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x5A4C4158; // 'XALZ', little-endian
|
static constexpr uint32_t COMPRESSED_DATA_MAGIC = 0x5A4C4158; // 'XALZ', little-endian
|
||||||
static constexpr uint32_t ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian
|
static constexpr uint32_t ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian
|
||||||
static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 1; // Increase whenever an incompatible change is made to the
|
static constexpr uint32_t ASSEMBLY_STORE_FORMAT_VERSION = 1; // Increase whenever an incompatible change is made to the
|
||||||
|
|
|
@ -87,6 +87,17 @@ namespace Xamarin.Android.AssemblyStore
|
||||||
ProcessStores ();
|
ProcessStores ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AssemblyStoreExplorer (ZipArchive archive, string basePathInArchive, Action<AssemblyStoreExplorerLogLevel, string>? customLogger = null, bool keepStoreInMemory = false)
|
||||||
|
{
|
||||||
|
logger = customLogger;
|
||||||
|
this.keepStoreInMemory = keepStoreInMemory;
|
||||||
|
StorePath = "<in-memory-archive>";
|
||||||
|
StoreSetName = StorePath;
|
||||||
|
ReadStoreSetFromArchive (archive, basePathInArchive);
|
||||||
|
|
||||||
|
ProcessStores ();
|
||||||
|
}
|
||||||
|
|
||||||
void Logger (AssemblyStoreExplorerLogLevel level, string message)
|
void Logger (AssemblyStoreExplorerLogLevel level, string message)
|
||||||
{
|
{
|
||||||
if (level == AssemblyStoreExplorerLogLevel.Error) {
|
if (level == AssemblyStoreExplorerLogLevel.Error) {
|
||||||
|
|
|
@ -74,28 +74,35 @@ namespace tmt
|
||||||
return GetSymbol (symbolName) != null;
|
return GetSymbol (symbolName) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetData (string symbolName)
|
public (byte[] data, ISymbolEntry? symbol) GetData (string symbolName)
|
||||||
{
|
{
|
||||||
Log.Debug ($"Looking for symbol: {symbolName}");
|
Log.Debug ($"Looking for symbol: {symbolName}");
|
||||||
ISymbolEntry? symbol = GetSymbol (symbolName);
|
ISymbolEntry? symbol = GetSymbol (symbolName);
|
||||||
if (symbol == null)
|
if (symbol == null) {
|
||||||
return EmptyArray;
|
return (EmptyArray, null);
|
||||||
|
}
|
||||||
|
|
||||||
if (Is64Bit) {
|
if (Is64Bit) {
|
||||||
var symbol64 = symbol as SymbolEntry<ulong>;
|
var symbol64 = symbol as SymbolEntry<ulong>;
|
||||||
if (symbol64 == null)
|
if (symbol64 == null)
|
||||||
throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 64-bit symbol");
|
throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 64-bit symbol");
|
||||||
return GetData (symbol64);
|
return (GetData (symbol64), symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
var symbol32 = symbol as SymbolEntry<uint>;
|
var symbol32 = symbol as SymbolEntry<uint>;
|
||||||
if (symbol32 == null)
|
if (symbol32 == null)
|
||||||
throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 32-bit symbol");
|
throw new InvalidOperationException ($"Symbol '{symbolName}' is not a valid 32-bit symbol");
|
||||||
|
|
||||||
return GetData (symbol32);
|
return (GetData (symbol32), symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract byte[] GetData (ulong symbolValue, ulong size);
|
public abstract byte[] GetData (ulong symbolValue, ulong size);
|
||||||
|
public abstract byte[] GetDataFromPointer (ulong pointerValue, ulong size);
|
||||||
|
|
||||||
|
public string GetASCIIZFromPointer (ulong pointerValue)
|
||||||
|
{
|
||||||
|
return GetASCIIZ (GetDataFromPointer (pointerValue, 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
public string GetASCIIZ (ulong symbolValue)
|
public string GetASCIIZ (ulong symbolValue)
|
||||||
{
|
{
|
||||||
|
@ -134,14 +141,27 @@ namespace tmt
|
||||||
return GetData (symbol.PointedSection, size, offset);
|
return GetData (symbol.PointedSection, size, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogSectionInfo<T> (Section<T> section) where T: struct
|
||||||
|
{
|
||||||
|
Log.Debug ($" section offset in file: 0x{section.Offset:x}; section size: {section.Size}; alignment: {section.Alignment}");
|
||||||
|
}
|
||||||
|
|
||||||
protected byte[] GetData (ISection section, ulong size, ulong offset)
|
protected byte[] GetData (ISection section, ulong size, ulong offset)
|
||||||
{
|
{
|
||||||
Log.Debug ($"AnELF.GetData: section == {section.Name}; size == {size}; offset == {offset:X}");
|
Log.Debug ($"AnELF.GetData: section == '{section.Name}'; requested data size == {size}; offset in section == 0x{offset:x}");
|
||||||
byte[] data = section.GetContents ();
|
byte[] data = section.GetContents ();
|
||||||
|
|
||||||
Log.Debug ($" data length: {data.Length} (long: {data.LongLength})");
|
if (section is Section<ulong> sec64) {
|
||||||
Log.Debug ($" offset: {offset}; size: {size}");
|
LogSectionInfo (sec64);
|
||||||
|
} else if (section is Section<uint> sec32) {
|
||||||
|
LogSectionInfo (sec32);
|
||||||
|
} else {
|
||||||
|
throw new NotSupportedException ($"Are we in the 128-bit future yet? Unsupported section type {section.GetType ()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($" section data length: {data.Length} (long: {data.LongLength})");
|
||||||
if ((ulong)data.LongLength < (offset + size)) {
|
if ((ulong)data.LongLength < (offset + size)) {
|
||||||
|
Log.Debug ($" not enough data in section");
|
||||||
return EmptyArray;
|
return EmptyArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,9 +176,18 @@ namespace tmt
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find a relocation corresponding to a pointer at offset <paramref name="pointerOffset"/> into
|
||||||
|
/// the specified <paramref name="symbol"/>. Returns an `ulong`, which needs to be cast to `uint`
|
||||||
|
/// for 32-pointers (it can be done safely as the upper 32-bits will be 0 in such cases)
|
||||||
|
/// </summary>
|
||||||
|
public abstract ulong DeterminePointerAddress (ISymbolEntry symbol, ulong pointerOffset);
|
||||||
|
public abstract ulong DeterminePointerAddress (ulong symbolValue, ulong pointerOffset);
|
||||||
|
|
||||||
public uint GetUInt32 (string symbolName)
|
public uint GetUInt32 (string symbolName)
|
||||||
{
|
{
|
||||||
return GetUInt32 (GetData (symbolName), 0, symbolName);
|
(byte[] data, _) = GetData (symbolName);
|
||||||
|
return GetUInt32 (data, 0, symbolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public uint GetUInt32 (ulong symbolValue)
|
public uint GetUInt32 (ulong symbolValue)
|
||||||
|
@ -177,7 +206,8 @@ namespace tmt
|
||||||
|
|
||||||
public ulong GetUInt64 (string symbolName)
|
public ulong GetUInt64 (string symbolName)
|
||||||
{
|
{
|
||||||
return GetUInt64 (GetData (symbolName), 0, symbolName);
|
(byte[] data, _) = GetData (symbolName);
|
||||||
|
return GetUInt64 (data, 0, symbolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong GetUInt64 (ulong symbolValue)
|
public ulong GetUInt64 (ulong symbolValue)
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System.IO;
|
||||||
|
|
||||||
using K4os.Compression.LZ4;
|
using K4os.Compression.LZ4;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
|
using Xamarin.Android.AssemblyStore;
|
||||||
using Xamarin.Tools.Zip;
|
using Xamarin.Tools.Zip;
|
||||||
|
|
||||||
namespace tmt
|
namespace tmt
|
||||||
|
@ -12,15 +13,44 @@ namespace tmt
|
||||||
{
|
{
|
||||||
const uint CompressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
|
const uint CompressedDataMagic = 0x5A4C4158; // 'XALZ', little-endian
|
||||||
|
|
||||||
Dictionary<string, ZipEntry> assemblies;
|
readonly Dictionary<string, ZipEntry>? individualAssemblies;
|
||||||
ZipArchive apk;
|
readonly Dictionary<string, AssemblyStoreAssembly>? blobAssemblies;
|
||||||
|
readonly ZipArchive apk;
|
||||||
|
readonly AssemblyStoreExplorer? assemblyStoreExplorer;
|
||||||
|
|
||||||
public ApkManagedTypeResolver (ZipArchive apk, string assemblyEntryPrefix)
|
public ApkManagedTypeResolver (ZipArchive apk, string assemblyEntryPrefix)
|
||||||
{
|
{
|
||||||
this.apk = apk;
|
this.apk = apk;
|
||||||
assemblies = new Dictionary<string, ZipEntry> (StringComparer.Ordinal);
|
|
||||||
|
|
||||||
foreach (ZipEntry entry in apk) {
|
if (apk.ContainsEntry ($"{assemblyEntryPrefix}assemblies.blob")) {
|
||||||
|
blobAssemblies = new Dictionary<string, AssemblyStoreAssembly> (StringComparer.Ordinal);
|
||||||
|
assemblyStoreExplorer = new AssemblyStoreExplorer (apk, assemblyEntryPrefix, keepStoreInMemory: true);
|
||||||
|
LoadAssemblyBlobs (apk, assemblyEntryPrefix, assemblyStoreExplorer);
|
||||||
|
} else {
|
||||||
|
individualAssemblies = new Dictionary<string, ZipEntry> (StringComparer.Ordinal);
|
||||||
|
LoadIndividualAssemblies (apk, assemblyEntryPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadAssemblyBlobs (ZipArchive apkArchive, string assemblyEntryPrefix, AssemblyStoreExplorer explorer)
|
||||||
|
{
|
||||||
|
foreach (AssemblyStoreAssembly assembly in explorer.Assemblies) {
|
||||||
|
string assemblyName = assembly.Name;
|
||||||
|
string dllName = assembly.DllName;
|
||||||
|
|
||||||
|
if (!String.IsNullOrEmpty (assembly.Store.Arch)) {
|
||||||
|
assemblyName = $"{assembly.Store.Arch}/{assemblyName}";
|
||||||
|
dllName = $"{assembly.Store.Arch}/{dllName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
blobAssemblies!.Add (assemblyName, assembly);
|
||||||
|
blobAssemblies!.Add (dllName, assembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadIndividualAssemblies (ZipArchive apkArchive, string assemblyEntryPrefix)
|
||||||
|
{
|
||||||
|
foreach (ZipEntry entry in apkArchive) {
|
||||||
if (!entry.FullName.StartsWith (assemblyEntryPrefix, StringComparison.Ordinal)) {
|
if (!entry.FullName.StartsWith (assemblyEntryPrefix, StringComparison.Ordinal)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -29,35 +59,78 @@ namespace tmt
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
assemblies.Add (Path.GetFileNameWithoutExtension (entry.FullName), entry);
|
string relativeName = entry.FullName.Substring (assemblyEntryPrefix.Length);
|
||||||
assemblies.Add (entry.FullName, entry);
|
string? dir = Path.GetDirectoryName (relativeName);
|
||||||
|
string name = Path.GetFileNameWithoutExtension (relativeName);
|
||||||
|
if (!String.IsNullOrEmpty (dir)) {
|
||||||
|
name = $"{dir}/{name}";
|
||||||
|
}
|
||||||
|
|
||||||
|
individualAssemblies!.Add (name, entry);
|
||||||
|
individualAssemblies.Add (entry.FullName, entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string? FindAssembly (string assemblyName)
|
protected override string? FindAssembly (string assemblyName)
|
||||||
{
|
{
|
||||||
if (assemblies.Count == 0) {
|
if (individualAssemblies != null) {
|
||||||
|
if (individualAssemblies.Count == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!individualAssemblies.TryGetValue (assemblyName, out ZipEntry? entry) || entry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry.FullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blobAssemblies == null || !blobAssemblies.TryGetValue (assemblyName, out AssemblyStoreAssembly? assembly) || assembly == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!assemblies.TryGetValue (assemblyName, out ZipEntry? entry) || entry == null) {
|
return assembly.Name;
|
||||||
return null;
|
}
|
||||||
|
|
||||||
|
Stream GetAssemblyStream (string assemblyPath)
|
||||||
|
{
|
||||||
|
MemoryStream? stream = null;
|
||||||
|
if (individualAssemblies != null) {
|
||||||
|
if (!individualAssemblies.TryGetValue (assemblyPath, out ZipEntry? entry) || entry == null) {
|
||||||
|
// Should "never" happen - if the assembly wasn't there, FindAssembly should have returned `null`
|
||||||
|
throw new InvalidOperationException ($"Should not happen: assembly '{assemblyPath}' not found in the APK archive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = new MemoryStream ();
|
||||||
|
entry.Extract (stream);
|
||||||
|
return PrepStream (stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry.FullName;
|
if (blobAssemblies == null) {
|
||||||
|
throw new InvalidOperationException ("Internal error: blobAssemblies shouldn't be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blobAssemblies == null || !blobAssemblies.TryGetValue (assemblyPath, out AssemblyStoreAssembly? assembly) || assembly == null) {
|
||||||
|
// Should "never" happen - if the assembly wasn't there, FindAssembly should have returned `null`
|
||||||
|
throw new InvalidOperationException ($"Should not happen: assembly '{assemblyPath}' not found in the assembly blob.");
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = new MemoryStream ();
|
||||||
|
assembly.ExtractImage (stream);
|
||||||
|
|
||||||
|
return PrepStream (stream);
|
||||||
|
|
||||||
|
Stream PrepStream (Stream stream)
|
||||||
|
{
|
||||||
|
stream.Seek (0, SeekOrigin.Begin);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override AssemblyDefinition ReadAssembly (string assemblyPath)
|
protected override AssemblyDefinition ReadAssembly (string assemblyPath)
|
||||||
{
|
{
|
||||||
if (!assemblies.TryGetValue (assemblyPath, out ZipEntry? entry) || entry == null) {
|
|
||||||
// Should "never" happen - if the assembly wasn't there, FindAssembly should have returned `null`
|
|
||||||
throw new InvalidOperationException ($"Should not happen: assembly {assemblyPath} not found in the APK archive.");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[]? assemblyBytes = null;
|
byte[]? assemblyBytes = null;
|
||||||
var stream = new MemoryStream ();
|
Stream stream = GetAssemblyStream (assemblyPath);
|
||||||
entry.Extract (stream);
|
|
||||||
stream.Seek (0, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// LZ4 compressed assembly header format:
|
// LZ4 compressed assembly header format:
|
||||||
|
@ -65,25 +138,25 @@ namespace tmt
|
||||||
// uint descriptor_index; // Index into an internal assembly descriptor table
|
// uint descriptor_index; // Index into an internal assembly descriptor table
|
||||||
// uint uncompressed_length; // Size of assembly, uncompressed
|
// uint uncompressed_length; // Size of assembly, uncompressed
|
||||||
//
|
//
|
||||||
using (var reader = new BinaryReader (stream)) {
|
using var reader = new BinaryReader (stream);
|
||||||
uint magic = reader.ReadUInt32 ();
|
uint magic = reader.ReadUInt32 ();
|
||||||
if (magic == CompressedDataMagic) {
|
if (magic == CompressedDataMagic) {
|
||||||
reader.ReadUInt32 (); // descriptor index, ignore
|
reader.ReadUInt32 (); // descriptor index, ignore
|
||||||
uint decompressedLength = reader.ReadUInt32 ();
|
uint decompressedLength = reader.ReadUInt32 ();
|
||||||
|
|
||||||
int inputLength = (int)(stream.Length - 12);
|
int inputLength = (int)(stream.Length - 12);
|
||||||
byte[] sourceBytes = Utilities.BytePool.Rent (inputLength);
|
byte[] sourceBytes = Utilities.BytePool.Rent (inputLength);
|
||||||
reader.Read (sourceBytes, 0, inputLength);
|
reader.Read (sourceBytes, 0, inputLength);
|
||||||
|
|
||||||
assemblyBytes = Utilities.BytePool.Rent ((int)decompressedLength);
|
assemblyBytes = Utilities.BytePool.Rent ((int)decompressedLength);
|
||||||
int decoded = LZ4Codec.Decode (sourceBytes, 0, inputLength, assemblyBytes, 0, (int)decompressedLength);
|
int decoded = LZ4Codec.Decode (sourceBytes, 0, inputLength, assemblyBytes, 0, (int)decompressedLength);
|
||||||
if (decoded != (int)decompressedLength) {
|
if (decoded != (int)decompressedLength) {
|
||||||
throw new InvalidOperationException ($"Failed to decompress LZ4 data of {assemblyPath} (decoded: {decoded})");
|
throw new InvalidOperationException ($"Failed to decompress LZ4 data of {assemblyPath} (decoded: {decoded})");
|
||||||
}
|
|
||||||
Utilities.BytePool.Return (sourceBytes);
|
|
||||||
}
|
}
|
||||||
|
Utilities.BytePool.Return (sourceBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (assemblyBytes != null) {
|
if (assemblyBytes != null) {
|
||||||
stream.Close ();
|
stream.Close ();
|
||||||
stream.Dispose ();
|
stream.Dispose ();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using ELFSharp;
|
|
||||||
using ELFSharp.ELF;
|
using ELFSharp.ELF;
|
||||||
using ELFSharp.ELF.Sections;
|
using ELFSharp.ELF.Sections;
|
||||||
|
|
||||||
|
@ -21,6 +21,83 @@ namespace tmt
|
||||||
: base (stream, filePath, elf, dynsymSection, rodataSection, symSection)
|
: base (stream, filePath, elf, dynsymSection, rodataSection, symSection)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
public override ulong DeterminePointerAddress (ulong symbolValue, ulong pointerOffset)
|
||||||
|
{
|
||||||
|
const string RelDynSectionName = ".rel.dyn";
|
||||||
|
ISection? sec = GetSection (ELF, RelDynSectionName);
|
||||||
|
if (sec == null) {
|
||||||
|
Log.Warning ($"{FilePath} does not contain dynamic relocation section ('{RelDynSectionName}')");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relDyn = sec as Section<uint>;
|
||||||
|
if (relDyn == null) {
|
||||||
|
Log.Warning ($"Invalid section type, expected 'Section<uint>', got '{sec.GetType ()}'");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ELF32Relocation> rels = LoadRelocations (relDyn);
|
||||||
|
if (rels.Count == 0) {
|
||||||
|
Log.Warning ($"Relocation section '{RelDynSectionName}' is empty");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint symRelocAddress = (uint)symbolValue + (uint)pointerOffset;
|
||||||
|
Log.Debug ($"Pointer relocation address == 0x{symRelocAddress:x}");
|
||||||
|
|
||||||
|
ulong fileOffset = Relocations.GetValue (ELF, rels, symRelocAddress);
|
||||||
|
Log.Debug ($"File offset == 0x{fileOffset:x}");
|
||||||
|
|
||||||
|
return fileOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ulong DeterminePointerAddress (ISymbolEntry symbol, ulong pointerOffset)
|
||||||
|
{
|
||||||
|
var sym32 = symbol as SymbolEntry<uint>;
|
||||||
|
if (sym32 == null) {
|
||||||
|
throw new ArgumentException ("must be of type SymbolEntry<uint>, was {symbol.GetType ()}", nameof (symbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeterminePointerAddress (sym32.Value, pointerOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] GetDataFromPointer (uint pointerValue, uint size)
|
||||||
|
{
|
||||||
|
Log.Debug ($"Looking for section containing pointer 0x{pointerValue:x}");
|
||||||
|
uint dataOffset = 0;
|
||||||
|
Section<uint>? section = null;
|
||||||
|
|
||||||
|
foreach (Section<uint> s in ELF.Sections) {
|
||||||
|
if (s.Type != SectionType.ProgBits) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.LoadAddress > pointerValue || (s.LoadAddress + s.Size) < pointerValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($" Section '{s.Name}' matches");
|
||||||
|
|
||||||
|
// Pointer is a load address, we convert it to the in-section offset by subtracting section load address from
|
||||||
|
// the pointer
|
||||||
|
dataOffset = pointerValue - s.LoadAddress;
|
||||||
|
Log.Debug ($" Pointer data section offset: 0x{dataOffset:x}");
|
||||||
|
section = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == null) {
|
||||||
|
throw new InvalidOperationException ($"Data for pointer 0x{pointerValue:x} not located");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetData (section, size, dataOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] GetDataFromPointer (ulong pointerValue, ulong size)
|
||||||
|
{
|
||||||
|
return GetDataFromPointer ((uint)pointerValue, (uint)size);
|
||||||
|
}
|
||||||
|
|
||||||
public override byte[] GetData (ulong symbolValue, ulong size = 0)
|
public override byte[] GetData (ulong symbolValue, ulong size = 0)
|
||||||
{
|
{
|
||||||
checked {
|
checked {
|
||||||
|
@ -54,6 +131,24 @@ namespace tmt
|
||||||
return GetData (symbol, symbol.Size, offset);
|
return GetData (symbol, symbol.Size, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ELF32Relocation> LoadRelocations (Section<uint> section)
|
||||||
|
{
|
||||||
|
var ret = new List<ELF32Relocation> ();
|
||||||
|
|
||||||
|
byte[] data = section.GetContents ();
|
||||||
|
ulong offset = 0;
|
||||||
|
|
||||||
|
Log.Debug ($"Relocation section '{section.Name}' data length == {data.Length}");
|
||||||
|
while (offset < (ulong)data.Length) {
|
||||||
|
uint relOffset = Helpers.ReadUInt32 (data, ref offset, Is64Bit);
|
||||||
|
uint relInfo = Helpers.ReadUInt32 (data, ref offset, Is64Bit);
|
||||||
|
|
||||||
|
ret.Add (new ELF32Relocation (relOffset, relInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
Section<uint> FindSectionForValue (uint symbolValue)
|
Section<uint> FindSectionForValue (uint symbolValue)
|
||||||
{
|
{
|
||||||
Log.Debug ($"FindSectionForValue ({symbolValue:X08})");
|
Log.Debug ($"FindSectionForValue ({symbolValue:X08})");
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
class ELF32Relocation
|
||||||
|
{
|
||||||
|
public uint Offset { get; }
|
||||||
|
public uint Info { get; }
|
||||||
|
|
||||||
|
public ELF32Relocation (uint offset, uint info)
|
||||||
|
{
|
||||||
|
Offset = offset;
|
||||||
|
Info = info;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
using ELFSharp;
|
|
||||||
using ELFSharp.ELF;
|
using ELFSharp.ELF;
|
||||||
using ELFSharp.ELF.Sections;
|
using ELFSharp.ELF.Sections;
|
||||||
|
|
||||||
|
@ -21,6 +21,78 @@ namespace tmt
|
||||||
: base (stream, filePath, elf, dynsymSection, rodataSection, symSection)
|
: base (stream, filePath, elf, dynsymSection, rodataSection, symSection)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
public override ulong DeterminePointerAddress (ISymbolEntry symbol, ulong pointerOffset)
|
||||||
|
{
|
||||||
|
var sym64 = symbol as SymbolEntry<ulong>;
|
||||||
|
if (sym64 == null) {
|
||||||
|
throw new ArgumentException ("must be of type SymbolEntry<ulong>, was {symbol.GetType ()}", nameof (symbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
return DeterminePointerAddress (sym64.Value, pointerOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ulong DeterminePointerAddress (ulong symbolValue, ulong pointerOffset)
|
||||||
|
{
|
||||||
|
const string RelaDynSectionName = ".rela.dyn";
|
||||||
|
ISection? sec = GetSection (ELF, RelaDynSectionName);
|
||||||
|
if (sec == null) {
|
||||||
|
Log.Warning ($"{FilePath} does not contain dynamic relocation section ('{RelaDynSectionName}')");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relaDyn = sec as Section<ulong>;
|
||||||
|
if (relaDyn == null) {
|
||||||
|
Log.Warning ($"Invalid section type, expected 'Section<ulong>', got '{sec.GetType ()}'");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ELF64RelocationAddend> rels = LoadRelocationsAddend (relaDyn);
|
||||||
|
if (rels.Count == 0) {
|
||||||
|
Log.Warning ($"Relocation section '{RelaDynSectionName}' is empty");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong symRelocAddress = symbolValue + pointerOffset;
|
||||||
|
Log.Debug ($"Pointer relocation address == 0x{symRelocAddress:x}");
|
||||||
|
|
||||||
|
ulong fileOffset = Relocations.GetValue (ELF, rels, symRelocAddress);
|
||||||
|
Log.Debug ($"File offset == 0x{fileOffset:x}");
|
||||||
|
|
||||||
|
return fileOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override byte[] GetDataFromPointer (ulong pointerValue, ulong size)
|
||||||
|
{
|
||||||
|
Log.Debug ($"Looking for section containing pointer 0x{pointerValue:x}");
|
||||||
|
ulong dataOffset = 0;
|
||||||
|
Section<ulong>? section = null;
|
||||||
|
|
||||||
|
foreach (Section<ulong> s in ELF.Sections) {
|
||||||
|
if (s.Type != SectionType.ProgBits) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.LoadAddress > pointerValue || (s.LoadAddress + s.Size) < pointerValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($" Section '{s.Name}' matches");
|
||||||
|
|
||||||
|
// Pointer is a load address, we convert it to the in-section offset by subtracting section load address from
|
||||||
|
// the pointer
|
||||||
|
dataOffset = pointerValue - s.LoadAddress;
|
||||||
|
Log.Debug ($" Pointer data section offset: 0x{dataOffset:x}");
|
||||||
|
section = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (section == null) {
|
||||||
|
throw new InvalidOperationException ($"Data for pointer 0x{pointerValue:x} not located");
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetData (section, size, dataOffset);
|
||||||
|
}
|
||||||
|
|
||||||
public override byte[] GetData (ulong symbolValue, ulong size = 0)
|
public override byte[] GetData (ulong symbolValue, ulong size = 0)
|
||||||
{
|
{
|
||||||
Log.Debug ($"ELF64.GetData: Looking for symbol value {symbolValue:X08}");
|
Log.Debug ($"ELF64.GetData: Looking for symbol value {symbolValue:X08}");
|
||||||
|
@ -30,7 +102,7 @@ namespace tmt
|
||||||
symbol = GetSymbol (Symbols, symbolValue);
|
symbol = GetSymbol (Symbols, symbolValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (symbol != null) {
|
if (symbol != null && symbol.PointedSection != null) {
|
||||||
Log.Debug ($"ELF64.GetData: found in section {symbol.PointedSection.Name}");
|
Log.Debug ($"ELF64.GetData: found in section {symbol.PointedSection.Name}");
|
||||||
return GetData (symbol);
|
return GetData (symbol);
|
||||||
}
|
}
|
||||||
|
@ -46,6 +118,26 @@ namespace tmt
|
||||||
return GetData (symbol, symbol.Size, OffsetInSection (symbol.PointedSection, symbol.Value));
|
return GetData (symbol, symbol.Size, OffsetInSection (symbol.PointedSection, symbol.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ELF64RelocationAddend> LoadRelocationsAddend (Section<ulong> section)
|
||||||
|
{
|
||||||
|
var ret = new List<ELF64RelocationAddend> ();
|
||||||
|
byte[] data = section.GetContents ();
|
||||||
|
ulong offset = 0;
|
||||||
|
ulong numEntries = (ulong)data.Length / 24; // One record is 3 64-bit words
|
||||||
|
|
||||||
|
Log.Debug ($"Relocation section '{section.Name}' data length == {data.Length}; entries == {numEntries}");
|
||||||
|
while ((ulong)ret.Count < numEntries) {
|
||||||
|
ulong relOffset = Helpers.ReadUInt64 (data, ref offset, Is64Bit);
|
||||||
|
ulong relInfo = Helpers.ReadUInt64 (data, ref offset, Is64Bit);
|
||||||
|
long relAddend = Helpers.ReadInt64 (data, ref offset, Is64Bit);
|
||||||
|
|
||||||
|
ret.Add (new ELF64RelocationAddend (relOffset, relInfo, relAddend));
|
||||||
|
}
|
||||||
|
Log.Debug ($"Read {ret.Count} entries");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
Section<ulong> FindSectionForValue (ulong symbolValue)
|
Section<ulong> FindSectionForValue (ulong symbolValue)
|
||||||
{
|
{
|
||||||
Log.Debug ($"FindSectionForValue ({symbolValue:X08})");
|
Log.Debug ($"FindSectionForValue ({symbolValue:X08})");
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
class ELF64RelocationAddend
|
||||||
|
{
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Info { get; }
|
||||||
|
public long Addend { get; }
|
||||||
|
|
||||||
|
public ELF64RelocationAddend (ulong offset, ulong info, long addend)
|
||||||
|
{
|
||||||
|
Offset = offset;
|
||||||
|
Info = info;
|
||||||
|
Addend = addend;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
static class Log
|
||||||
{
|
{
|
||||||
|
public const ConsoleColor ErrorColor = ConsoleColor.Red;
|
||||||
|
public const ConsoleColor WarningColor = ConsoleColor.Yellow;
|
||||||
|
public const ConsoleColor InfoColor = ConsoleColor.Green;
|
||||||
|
public const ConsoleColor DebugColor = ConsoleColor.DarkGray;
|
||||||
|
|
||||||
static bool showDebug = false;
|
static bool showDebug = false;
|
||||||
|
|
||||||
|
static void WriteStderr (string message)
|
||||||
|
{
|
||||||
|
Console.Error.Write (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteStderr (ConsoleColor color, string message)
|
||||||
|
{
|
||||||
|
ConsoleColor oldFG = Console.ForegroundColor;
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
WriteStderr (message);
|
||||||
|
Console.ForegroundColor = oldFG;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteLineStderr (string message)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteLineStderr (ConsoleColor color, string message)
|
||||||
|
{
|
||||||
|
ConsoleColor oldFG = Console.ForegroundColor;
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
WriteLineStderr (message);
|
||||||
|
Console.ForegroundColor = oldFG;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Write (string message)
|
||||||
|
{
|
||||||
|
Console.Write (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Write (ConsoleColor color, string message)
|
||||||
|
{
|
||||||
|
ConsoleColor oldFG = Console.ForegroundColor;
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
Write (message);
|
||||||
|
Console.ForegroundColor = oldFG;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteLine (string message)
|
||||||
|
{
|
||||||
|
Console.WriteLine (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteLine (ConsoleColor color, string message)
|
||||||
|
{
|
||||||
|
ConsoleColor oldFG = Console.ForegroundColor;
|
||||||
|
Console.ForegroundColor = color;
|
||||||
|
WriteLine (message);
|
||||||
|
Console.ForegroundColor = oldFG;
|
||||||
|
}
|
||||||
|
|
||||||
public static void SetVerbose (bool verbose)
|
public static void SetVerbose (bool verbose)
|
||||||
{
|
{
|
||||||
showDebug = verbose;
|
showDebug = verbose;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error (string message = "")
|
public static void Error (string message = "")
|
||||||
|
{
|
||||||
|
Error (tag: String.Empty, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Error (string tag, string message)
|
||||||
{
|
{
|
||||||
if (message.Length > 0) {
|
if (message.Length > 0) {
|
||||||
Console.Error.Write ("Error: ");
|
WriteStderr (ErrorColor, "[E] ");
|
||||||
}
|
}
|
||||||
Console.Error.WriteLine (message);
|
|
||||||
|
if (tag.Length > 0) {
|
||||||
|
WriteStderr (ErrorColor, $"{tag}: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLineStderr (message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warning (string message = "")
|
public static void Warning (string message = "")
|
||||||
|
{
|
||||||
|
Warning (tag: String.Empty, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Warning (string tag, string message)
|
||||||
{
|
{
|
||||||
if (message.Length > 0) {
|
if (message.Length > 0) {
|
||||||
Console.Error.Write ("Warning: ");
|
WriteStderr (WarningColor, "[W] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.Error.WriteLine (message);
|
if (tag.Length > 0) {
|
||||||
|
WriteStderr (WarningColor, $"{tag}: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLineStderr (message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Info (string message = "")
|
public static void Info (string message = "")
|
||||||
{
|
{
|
||||||
Console.WriteLine (message);
|
Info (tag: String.Empty, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Info (string tag, string message)
|
||||||
|
{
|
||||||
|
if (tag.Length > 0) {
|
||||||
|
Write (InfoColor, $"{tag}: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine (InfoColor,message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Debug (string message = "")
|
public static void Debug (string message = "")
|
||||||
|
{
|
||||||
|
Debug (tag: String.Empty, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Debug (string tag, string message)
|
||||||
{
|
{
|
||||||
if (!showDebug) {
|
if (!showDebug) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Console.WriteLine (message);
|
if (message.Length > 0) {
|
||||||
|
Write (DebugColor, "[D] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag.Length > 0) {
|
||||||
|
Write (DebugColor, $"{tag}: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteLine (message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ExceptionError (string message, Exception ex)
|
public static void ExceptionError (string message, Exception ex)
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace tmt
|
||||||
AlreadyWarned.Add (assemblyName);
|
AlreadyWarned.Add (assemblyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"[token id: {tokenID}]";
|
return $"`token id: {tokenID}`";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool TryLookup (string assemblyPath, Guid mvid, uint tokenID, out string typeName)
|
protected bool TryLookup (string assemblyPath, Guid mvid, uint tokenID, out string typeName)
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using ELFSharp.ELF;
|
||||||
|
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
static class Relocations
|
||||||
|
{
|
||||||
|
public static ulong GetValue (ELF<uint> elf, List<ELF32Relocation> rels, uint symbolOffset)
|
||||||
|
{
|
||||||
|
return elf.Machine switch {
|
||||||
|
Machine.ARM => GetValue_Arm32 (rels, symbolOffset),
|
||||||
|
Machine.Intel386 => GetValue_x86 (rels, symbolOffset),
|
||||||
|
_ => throw new NotSupportedException ($"ELF machine {elf.Machine} is not supported")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ulong GetValue (ELF<ulong> elf, List<ELF64RelocationAddend> rels, ulong symbolOffset)
|
||||||
|
{
|
||||||
|
return elf.Machine switch {
|
||||||
|
Machine.AArch64 => GetValue_AArch64 (rels, symbolOffset),
|
||||||
|
Machine.AMD64 => GetValue_x64 (rels, symbolOffset),
|
||||||
|
_ => throw new NotSupportedException ($"ELF machine {elf.Machine} is not supported")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static ulong GetValue_AArch64 (List<ELF64RelocationAddend> rels, ulong symbolOffset)
|
||||||
|
{
|
||||||
|
// Documentation: https://github.com/ARM-software/abi-aa/releases/download/2023Q3/aaelf64.pdf page 36, Elf64 Code 1027
|
||||||
|
const ulong R_AARCH64_RELATIVE = 0x403;
|
||||||
|
|
||||||
|
foreach (ELF64RelocationAddend rel in rels) {
|
||||||
|
if (rel.Offset != symbolOffset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong relType = GetRelType (rel);
|
||||||
|
if (relType != R_AARCH64_RELATIVE) {
|
||||||
|
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this case, this is offset into the file where pointed-to data begins
|
||||||
|
return (ulong)rel.Addend;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($"[AArch64] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ulong GetValue_x64 (List<ELF64RelocationAddend> rels, ulong symbolOffset)
|
||||||
|
{
|
||||||
|
// Documentation: https://docs.oracle.com/en/operating-systems/solaris/oracle-solaris/11.4/linkers-libraries/x64-relocation-types.html#GUID-369D19D8-60F0-4D3D-B761-4F02BA0BC023
|
||||||
|
// Value 8
|
||||||
|
const ulong R_AMD64_RELATIVE = 0x08;
|
||||||
|
|
||||||
|
foreach (ELF64RelocationAddend rel in rels) {
|
||||||
|
if (rel.Offset != symbolOffset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong relType = GetRelType (rel);
|
||||||
|
if (relType != R_AMD64_RELATIVE) {
|
||||||
|
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this case, this is offset into the file where pointed-to data begins
|
||||||
|
return (ulong)rel.Addend;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($"[AArch64] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ulong GetValue_Arm32 (List<ELF32Relocation> rels, uint symbolOffset)
|
||||||
|
{
|
||||||
|
// Documentation: https://github.com/ARM-software/abi-aa/releases/download/2023Q3/aaelf32.pdf, page 40, Code 23
|
||||||
|
const uint R_ARM_RELATIVE = 0x17;
|
||||||
|
|
||||||
|
foreach (ELF32Relocation rel in rels) {
|
||||||
|
if (rel.Offset != symbolOffset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint relType = GetRelType (rel);
|
||||||
|
if (relType != R_ARM_RELATIVE) {
|
||||||
|
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this case, offset is the value we need to calculate the location
|
||||||
|
return rel.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($"[Arm32] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ulong GetValue_x86 (List<ELF32Relocation> rels, uint symbolOffset)
|
||||||
|
{
|
||||||
|
// Documentation: https://docs.oracle.com/en/operating-systems/solaris/oracle-solaris/11.4/linkers-libraries/32-bit-x86-relocation-types.html#GUID-2CE5C854-5AD8-4CC5-AABA-C03BED1C3FC0
|
||||||
|
// Value 8
|
||||||
|
const uint R_386_RELATIVE = 0x08;
|
||||||
|
|
||||||
|
foreach (ELF32Relocation rel in rels) {
|
||||||
|
if (rel.Offset != symbolOffset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint relType = GetRelType (rel);
|
||||||
|
if (relType != R_386_RELATIVE) {
|
||||||
|
Log.Warning ($"Found relocation for symbol at offset 0x{symbolOffset:x}, but it has an usupported type 0x{relType:x}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this case, offset is the value we need to calculate the location
|
||||||
|
return rel.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($"[x86] Relocation of supported type for symbol at offset 0x{symbolOffset:x} not found");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the `r_info` (`.Info` for us) field of relocation recold encodes both the relocation type and symbol table index
|
||||||
|
|
||||||
|
static ulong GetRelType (ELF64RelocationAddend rel) => rel.Info & 0xffffffff;
|
||||||
|
static ulong GetRelSym (ELF64RelocationAddend rel) => rel.Info >> 32;
|
||||||
|
|
||||||
|
static uint GetRelType (ELF32Relocation rel) => rel.Info & 0xff;
|
||||||
|
static uint GetRelSym (ELF32Relocation rel) => rel.Info >> 8;
|
||||||
|
}
|
|
@ -9,12 +9,78 @@ namespace tmt
|
||||||
class Report
|
class Report
|
||||||
{
|
{
|
||||||
const string FileFieldSeparator = "\t";
|
const string FileFieldSeparator = "\t";
|
||||||
const string ManagedTypeColumnHeader = "Managed-Type-Name";
|
|
||||||
const string JavaTypeColumnHeader = "Java-Type-Name";
|
const string FormattedDuplicateColumnHeader = "Is Duplicate?";
|
||||||
const string DuplicateColumnHeader = "Is-Duplicate-Type-Entry?";
|
const string FormattedGenericColumnHeader = "Is Generic?";
|
||||||
const string GenericColumnHeader = "Is-Generic-Type?";
|
const string FormattedJavaTypeColumnHeader = "Java type name";
|
||||||
const string MVIDColumnHeader = "MVID";
|
const string FormattedMVIDColumnHeader = "MVID";
|
||||||
const string TokenIDColumnHeader = "Token-ID";
|
const string FormattedManagedTypeColumnHeader = "Managed type name";
|
||||||
|
const string FormattedTokenIDColumnHeader = "Type token ID";
|
||||||
|
|
||||||
|
const string RawJavaTypeNameColumnHeader = FormattedJavaTypeColumnHeader;
|
||||||
|
const string RawManagedModuleIndexColumnHeader = "Managed module index";
|
||||||
|
const string RawTypeTokenColumnHeader = FormattedTokenIDColumnHeader;
|
||||||
|
|
||||||
|
const string TableJavaToManagedTitle = "Java to Managed";
|
||||||
|
const string TableManagedToJavaTitle = "Managed to Java";
|
||||||
|
|
||||||
|
sealed class Column
|
||||||
|
{
|
||||||
|
int width = 0;
|
||||||
|
|
||||||
|
public int Width => width;
|
||||||
|
public string Header { get; }
|
||||||
|
public List<string> Rows { get; } = new List<string> ();
|
||||||
|
|
||||||
|
public Column (string header)
|
||||||
|
{
|
||||||
|
Header = header;
|
||||||
|
width = header.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add (string rowValue)
|
||||||
|
{
|
||||||
|
if (rowValue.Length > width) {
|
||||||
|
width = rowValue.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rows.Add (rowValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add (bool rowValue)
|
||||||
|
{
|
||||||
|
Add (rowValue ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add (uint rowValue)
|
||||||
|
{
|
||||||
|
Add ($"0x{rowValue:X08} ({rowValue})");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add (Guid rowValue)
|
||||||
|
{
|
||||||
|
Add (rowValue.ToString ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Table
|
||||||
|
{
|
||||||
|
public Column Duplicate { get; } = new Column (FormattedDuplicateColumnHeader);
|
||||||
|
public Column Generic { get; } = new Column (FormattedGenericColumnHeader);
|
||||||
|
public Column JavaType { get; } = new Column (FormattedJavaTypeColumnHeader);
|
||||||
|
public Column MVID { get; } = new Column (FormattedMVIDColumnHeader);
|
||||||
|
public Column ManagedType { get; } = new Column (FormattedManagedTypeColumnHeader);
|
||||||
|
public Column TokenID { get; } = new Column (FormattedTokenIDColumnHeader);
|
||||||
|
|
||||||
|
public string Title { get; }
|
||||||
|
public bool ManagedFirst { get; }
|
||||||
|
|
||||||
|
public Table (string title, bool managedFirst)
|
||||||
|
{
|
||||||
|
Title = title;
|
||||||
|
ManagedFirst = managedFirst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string outputDirectory;
|
string outputDirectory;
|
||||||
Regex? filterRegex;
|
Regex? filterRegex;
|
||||||
|
@ -37,6 +103,235 @@ namespace tmt
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Generate (ITypemap typemap)
|
public void Generate (ITypemap typemap)
|
||||||
|
{
|
||||||
|
Action<Table, MapEntry, bool> tableGenerator;
|
||||||
|
Action<MapEntry, bool> consoleGenerator;
|
||||||
|
var tables = new List<Table> ();
|
||||||
|
bool filtering = filterRegex != null;
|
||||||
|
Table table;
|
||||||
|
|
||||||
|
if (!onlyManaged) {
|
||||||
|
typemap.Map.JavaToManaged.Sort ((MapEntry left, MapEntry right) => left.JavaType.Name.CompareTo (right.JavaType.Name));
|
||||||
|
table = new Table (TableJavaToManagedTitle, managedFirst: false);
|
||||||
|
tables.Add (table);
|
||||||
|
if (typemap.Map.Kind == MapKind.Release) {
|
||||||
|
tableGenerator = TableGenerateJavaToManagedRelease;
|
||||||
|
consoleGenerator = ConsoleGenerateJavaToManagedRelease;
|
||||||
|
} else {
|
||||||
|
tableGenerator = TableGenerateJavaToManagedDebug;
|
||||||
|
consoleGenerator = ConsoleGenerateJavaToManagedDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
Generate ("Java to Managed", table, typemap.Map.JavaToManaged, full, tableGenerator, consoleGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!onlyJava) {
|
||||||
|
typemap.Map.ManagedToJava.Sort ((MapEntry left, MapEntry right) => {
|
||||||
|
int result = String.Compare (left.ManagedType.AssemblyName, right.ManagedType.AssemblyName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (result != 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return left.ManagedType.TypeName.CompareTo (right.ManagedType.TypeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
table = new Table (TableManagedToJavaTitle, managedFirst: true);
|
||||||
|
tables.Add (table);
|
||||||
|
if (typemap.Map.Kind == MapKind.Release) {
|
||||||
|
tableGenerator = TableGenerateManagedToJavaRelease;
|
||||||
|
consoleGenerator = ConsoleGenerateManagedToJavaRelease;
|
||||||
|
} else {
|
||||||
|
tableGenerator = TableGenerateManagedToJavaDebug;
|
||||||
|
consoleGenerator = ConsoleGenerateManagedToJavaDebug;
|
||||||
|
}
|
||||||
|
|
||||||
|
Generate ("Managed to Java", table, typemap.Map.ManagedToJava, full, tableGenerator, consoleGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
string? outputFile = null;
|
||||||
|
StreamWriter? sw = null;
|
||||||
|
if (generateFiles) {
|
||||||
|
outputFile = Utilities.GetOutputFileBaseName (outputDirectory, typemap.FormatVersion, typemap.Map.Kind, typemap.Map.Architecture);
|
||||||
|
outputFile = $"{outputFile}.md";
|
||||||
|
Utilities.CreateFileDirectory (outputFile);
|
||||||
|
sw = new StreamWriter (outputFile, false, new UTF8Encoding (false));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (sw != null) {
|
||||||
|
sw.WriteLine ("# Info");
|
||||||
|
sw.WriteLine ();
|
||||||
|
sw.WriteLine ($"Architecture: **{typemap.MapArchitecture}**");
|
||||||
|
sw.WriteLine ($" Build kind: **{typemap.Map.Kind}**");
|
||||||
|
sw.WriteLine ($" Format: **{typemap.FormatVersion}**");
|
||||||
|
sw.WriteLine ($" Description: **{typemap.Description}**");
|
||||||
|
sw.WriteLine ();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Table t in tables) {
|
||||||
|
if (sw != null) {
|
||||||
|
WriteTable (sw, t);
|
||||||
|
sw.WriteLine ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (sw != null) {
|
||||||
|
sw.Flush ();
|
||||||
|
sw.Close ();
|
||||||
|
sw.Dispose ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteTable (StreamWriter sw, Table table)
|
||||||
|
{
|
||||||
|
sw.WriteLine ($"# {table.Title}");
|
||||||
|
sw.WriteLine ();
|
||||||
|
|
||||||
|
// Just non-empty columns
|
||||||
|
var columns = new List<Column> ();
|
||||||
|
if (table.ManagedFirst) {
|
||||||
|
MaybeAddColumn (table.ManagedType);
|
||||||
|
MaybeAddColumn (table.JavaType);
|
||||||
|
} else {
|
||||||
|
MaybeAddColumn (table.JavaType);
|
||||||
|
MaybeAddColumn (table.ManagedType);
|
||||||
|
}
|
||||||
|
MaybeAddColumn (table.TokenID);
|
||||||
|
MaybeAddColumn (table.Generic);
|
||||||
|
MaybeAddColumn (table.Duplicate);
|
||||||
|
MaybeAddColumn (table.MVID);
|
||||||
|
|
||||||
|
if (columns.Count == 0) {
|
||||||
|
Log.Warning ("No non-empty columns");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All columns must have equal numbers of rows
|
||||||
|
int rows = columns[0].Rows.Count;
|
||||||
|
foreach (Column column in columns) {
|
||||||
|
if (column.Rows.Count != rows) {
|
||||||
|
throw new InvalidOperationException ($"Column {column.Header} has a different number of rows, {column.Rows.Count}, than the expected value of {rows}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb = new StringBuilder ();
|
||||||
|
int width;
|
||||||
|
string prefix;
|
||||||
|
var divider = new StringBuilder ();
|
||||||
|
foreach (Column column in columns) {
|
||||||
|
width = GetColumnWidth (column);
|
||||||
|
divider.Append ("| ");
|
||||||
|
divider.Append ('-', width - 3);
|
||||||
|
divider.Append (' ');
|
||||||
|
|
||||||
|
prefix = $"| {column.Header}";
|
||||||
|
sw.Write (prefix);
|
||||||
|
sw.Write (GetPadding (width - prefix.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
sw.WriteLine ('|');
|
||||||
|
sw.Write (divider);
|
||||||
|
sw.WriteLine ('|');
|
||||||
|
|
||||||
|
for (int row = 0; row < rows; row++) {
|
||||||
|
foreach (Column column in columns) {
|
||||||
|
width = GetColumnWidth (column);
|
||||||
|
prefix = $"| {column.Rows[row]}";
|
||||||
|
sw.Write (prefix);
|
||||||
|
sw.Write (GetPadding (width - prefix.Length));
|
||||||
|
}
|
||||||
|
sw.Write ('|');
|
||||||
|
sw.WriteLine ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MaybeAddColumn (Column column)
|
||||||
|
{
|
||||||
|
if (column.Rows.Count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.Add (column);
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetColumnWidth (Column column) => column.Width + 3; // For the '| ' prefix and ' ' suffix
|
||||||
|
|
||||||
|
string GetPadding (int width)
|
||||||
|
{
|
||||||
|
sb.Clear ();
|
||||||
|
return sb.Append (' ', width).ToString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableGenerateJavaToManagedDebug (Table table, MapEntry entry, bool full)
|
||||||
|
{
|
||||||
|
table.JavaType.Add (entry.JavaType.Name);
|
||||||
|
table.ManagedType.Add (GetManagedTypeNameDebug (entry));
|
||||||
|
table.Duplicate.Add (entry.ManagedType.IsDuplicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableGenerateJavaToManagedRelease (Table table, MapEntry entry, bool full)
|
||||||
|
{
|
||||||
|
string managedTypeName = GetManagedTypeNameRelease (entry);
|
||||||
|
string generic = entry.ManagedType.IsGeneric ? IgnoredGeneric : "no";
|
||||||
|
|
||||||
|
table.JavaType.Add (entry.JavaType.Name);
|
||||||
|
table.ManagedType.Add (managedTypeName);
|
||||||
|
table.Generic.Add (generic);
|
||||||
|
if (!full) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.MVID.Add (entry.ManagedType.MVID);
|
||||||
|
table.TokenID.Add (entry.ManagedType.TokenID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableGenerateManagedToJavaDebug (Table table, MapEntry entry, bool full)
|
||||||
|
{
|
||||||
|
table.JavaType.Add (entry.JavaType.Name);
|
||||||
|
table.ManagedType.Add (GetManagedTypeNameDebug (entry));
|
||||||
|
table.Duplicate.Add (entry.ManagedType.IsDuplicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TableGenerateManagedToJavaRelease (Table table, MapEntry entry, bool full)
|
||||||
|
{
|
||||||
|
string managedTypeName = GetManagedTypeNameRelease (entry);
|
||||||
|
string generic = entry.ManagedType.IsGeneric ? IgnoredGeneric : "no";
|
||||||
|
|
||||||
|
table.ManagedType.Add (managedTypeName);
|
||||||
|
table.JavaType.Add (entry.JavaType.Name);
|
||||||
|
table.Generic.Add (generic);
|
||||||
|
table.Duplicate.Add (entry.ManagedType.IsDuplicate);
|
||||||
|
if (!full) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.MVID.Add (entry.ManagedType.MVID);
|
||||||
|
table.TokenID.Add (entry.ManagedType.TokenID);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Generate (string label, Table table, List<MapEntry> typemap, bool full, Action<Table, MapEntry, bool> tableGenerator, Action<MapEntry, bool> consoleGenerator)
|
||||||
|
{
|
||||||
|
bool firstMatch = true;
|
||||||
|
foreach (MapEntry entry in typemap) {
|
||||||
|
if (generateFiles) {
|
||||||
|
tableGenerator (table, entry, full);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterRegex == null || !EntryMatches (entry, filterRegex)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstMatch) {
|
||||||
|
Log.Info ();
|
||||||
|
Log.Info ($" Matching entries ({label}):");
|
||||||
|
firstMatch = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
consoleGenerator (entry, full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenerateOld (ITypemap typemap)
|
||||||
{
|
{
|
||||||
string baseOutputFile = generateFiles ? Utilities.GetOutputFileBaseName (outputDirectory, typemap.FormatVersion, typemap.Map.Kind, typemap.Map.Architecture) : String.Empty;
|
string baseOutputFile = generateFiles ? Utilities.GetOutputFileBaseName (outputDirectory, typemap.FormatVersion, typemap.Map.Kind, typemap.Map.Architecture) : String.Empty;
|
||||||
Action<StreamWriter, MapEntry, bool, bool> fileGenerator;
|
Action<StreamWriter, MapEntry, bool, bool> fileGenerator;
|
||||||
|
@ -157,7 +452,7 @@ namespace tmt
|
||||||
{
|
{
|
||||||
if (firstEntry) {
|
if (firstEntry) {
|
||||||
string sep = FileFieldSeparator;
|
string sep = FileFieldSeparator;
|
||||||
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{DuplicateColumnHeader}");
|
sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLineToFile (sw,
|
WriteLineToFile (sw,
|
||||||
|
@ -180,7 +475,7 @@ namespace tmt
|
||||||
if (!full) {
|
if (!full) {
|
||||||
if (firstEntry) {
|
if (firstEntry) {
|
||||||
string sep = FileFieldSeparator;
|
string sep = FileFieldSeparator;
|
||||||
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{GenericColumnHeader}");
|
sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedGenericColumnHeader}");
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLineToFile (
|
WriteLineToFile (
|
||||||
|
@ -194,7 +489,7 @@ namespace tmt
|
||||||
|
|
||||||
if (firstEntry) {
|
if (firstEntry) {
|
||||||
string sep = FileFieldSeparator;
|
string sep = FileFieldSeparator;
|
||||||
sw.WriteLine ($"{JavaTypeColumnHeader}{sep}{ManagedTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{MVIDColumnHeader}{sep}{TokenIDColumnHeader}");
|
sw.WriteLine ($"{FormattedJavaTypeColumnHeader}{sep}{FormattedManagedTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedMVIDColumnHeader}{sep}{FormattedTokenIDColumnHeader}");
|
||||||
}
|
}
|
||||||
WriteLineToFile (
|
WriteLineToFile (
|
||||||
sw,
|
sw,
|
||||||
|
@ -238,7 +533,7 @@ namespace tmt
|
||||||
{
|
{
|
||||||
if (firstEntry) {
|
if (firstEntry) {
|
||||||
string sep = FileFieldSeparator;
|
string sep = FileFieldSeparator;
|
||||||
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{DuplicateColumnHeader}");
|
sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteLineToFile (sw,
|
WriteLineToFile (sw,
|
||||||
|
@ -262,7 +557,7 @@ namespace tmt
|
||||||
if (full) {
|
if (full) {
|
||||||
if (firstEntry) {
|
if (firstEntry) {
|
||||||
string sep = FileFieldSeparator;
|
string sep = FileFieldSeparator;
|
||||||
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{DuplicateColumnHeader}{sep}{MVIDColumnHeader}{sep}{TokenIDColumnHeader}");
|
sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedDuplicateColumnHeader}{sep}{FormattedMVIDColumnHeader}{sep}{FormattedTokenIDColumnHeader}");
|
||||||
}
|
}
|
||||||
WriteLineToFile (
|
WriteLineToFile (
|
||||||
sw,
|
sw,
|
||||||
|
@ -276,7 +571,7 @@ namespace tmt
|
||||||
} else {
|
} else {
|
||||||
if (firstEntry) {
|
if (firstEntry) {
|
||||||
string sep = FileFieldSeparator;
|
string sep = FileFieldSeparator;
|
||||||
sw.WriteLine ($"{ManagedTypeColumnHeader}{sep}{JavaTypeColumnHeader}{sep}{GenericColumnHeader}{sep}{DuplicateColumnHeader}");
|
sw.WriteLine ($"{FormattedManagedTypeColumnHeader}{sep}{FormattedJavaTypeColumnHeader}{sep}{FormattedGenericColumnHeader}{sep}{FormattedDuplicateColumnHeader}");
|
||||||
}
|
}
|
||||||
WriteLineToFile (
|
WriteLineToFile (
|
||||||
sw,
|
sw,
|
||||||
|
@ -291,9 +586,9 @@ namespace tmt
|
||||||
void ConsoleGenerateManagedToJavaRelease (MapEntry entry, bool full)
|
void ConsoleGenerateManagedToJavaRelease (MapEntry entry, bool full)
|
||||||
{
|
{
|
||||||
if (!full) {
|
if (!full) {
|
||||||
Log.Info ($" {GetManagedTypeNameDebug (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
|
Log.Info ($" {GetManagedTypeNameRelease (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
|
||||||
} else {
|
} else {
|
||||||
Log.Info ($" {GetManagedTypeNameDebug (entry)}; MVID: {entry.ManagedType.MVID}; Token ID: {TokenIdToString (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
|
Log.Info ($" {GetManagedTypeNameRelease (entry)}; MVID: {entry.ManagedType.MVID}; Token ID: {TokenIdToString (entry)} -> {entry.JavaType.Name}{GetAdditionalInfo (entry)}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
using ELFSharp.ELF.Sections;
|
||||||
|
|
||||||
namespace tmt
|
namespace tmt
|
||||||
{
|
{
|
||||||
abstract class XamarinAppDSO : ITypemap
|
abstract class XamarinAppDSO : ITypemap
|
||||||
{
|
{
|
||||||
// Corresponds to the `FORMAT_TAG` constant in src/monodroid/xamarin-app.hh
|
// Corresponds to the `FORMAT_TAG` constant in src/monodroid/xamarin-app.hh
|
||||||
protected const ulong FormatTag_V1 = 0x015E6972616D58;
|
protected const ulong FormatTag_V1 = 0x00015E6972616D58;
|
||||||
|
protected const ulong FormatTag_V2 = 0x00025E6972616D58;
|
||||||
|
|
||||||
protected const string FormatTag = "format_tag";
|
protected const string FormatTag = "format_tag";
|
||||||
|
|
||||||
|
@ -18,6 +21,7 @@ namespace tmt
|
||||||
|
|
||||||
public MapArchitecture MapArchitecture => ELF.MapArchitecture;
|
public MapArchitecture MapArchitecture => ELF.MapArchitecture;
|
||||||
public string FullPath { get; } = String.Empty;
|
public string FullPath { get; } = String.Empty;
|
||||||
|
protected abstract string LogTag { get; }
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
public abstract string FormatVersion { get; }
|
public abstract string FormatVersion { get; }
|
||||||
public abstract Map Map { get; }
|
public abstract Map Map { get; }
|
||||||
|
@ -64,87 +68,44 @@ namespace tmt
|
||||||
|
|
||||||
protected uint ReadUInt32 (byte[] data, ref ulong offset, bool packed = false)
|
protected uint ReadUInt32 (byte[] data, ref ulong offset, bool packed = false)
|
||||||
{
|
{
|
||||||
const ulong DataSize = 4;
|
return Helpers.ReadUInt32 (data, ref offset, Is64Bit, packed);
|
||||||
|
|
||||||
if ((ulong)data.Length < (offset + DataSize))
|
|
||||||
throw new InvalidOperationException ("Not enough data to read a 32-bit integer");
|
|
||||||
|
|
||||||
uint ret = BitConverter.ToUInt32 (data, (int)offset);
|
|
||||||
offset += packed ? DataSize : GetPaddedSize<uint> (offset);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ulong ReadUInt64 (byte[] data, ref ulong offset, bool packed = false)
|
protected ulong ReadUInt64 (byte[] data, ref ulong offset, bool packed = false)
|
||||||
{
|
{
|
||||||
const ulong DataSize = 8;
|
return Helpers.ReadUInt64 (data, ref offset, Is64Bit, packed);
|
||||||
|
|
||||||
if ((ulong)data.Length < (offset + DataSize))
|
|
||||||
throw new InvalidOperationException ("Not enough data to read a 64-bit integer");
|
|
||||||
|
|
||||||
ulong ret = BitConverter.ToUInt64 (data, (int)offset);
|
|
||||||
offset += packed ? DataSize : GetPaddedSize<ulong> (offset);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ulong ReadPointer (byte[] data, ref ulong offset, bool packed = false)
|
protected ulong ReadPointer (byte[] data, ref ulong offset, bool packed = false)
|
||||||
{
|
{
|
||||||
ulong ret;
|
return Helpers.ReadPointer (data, ref offset, Is64Bit, packed);
|
||||||
|
}
|
||||||
|
|
||||||
if (Is64Bit) {
|
protected ulong ReadPointer (ISymbolEntry symbol, byte[] data, ref ulong offset, bool packed = false)
|
||||||
ret = ReadUInt64 (data, ref offset, packed);
|
{
|
||||||
} else {
|
ulong prevOffset = offset;
|
||||||
ret = (ulong)ReadUInt32 (data, ref offset, packed);
|
ulong pointer = ReadPointer (data, ref offset, packed);
|
||||||
|
if (pointer == 0) {
|
||||||
|
pointer = ELF.DeterminePointerAddress (symbol, prevOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ulong ReadPointer (ulong symbolValue, byte[] data, ref ulong offset, bool packed = false)
|
||||||
|
{
|
||||||
|
ulong prevOffset = offset;
|
||||||
|
ulong pointer = ReadPointer (data, ref offset, packed);
|
||||||
|
if (pointer == 0) {
|
||||||
|
pointer = ELF.DeterminePointerAddress (symbolValue, prevOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ulong GetPaddedSize<S> (ulong sizeSoFar)
|
protected ulong GetPaddedSize<S> (ulong sizeSoFar)
|
||||||
{
|
{
|
||||||
ulong typeSize = GetTypeSize<S> ();
|
return Helpers.GetPaddedSize<S> (sizeSoFar, Is64Bit);
|
||||||
|
|
||||||
ulong modulo;
|
|
||||||
if (Is64Bit) {
|
|
||||||
modulo = typeSize < 8 ? 4u : 8u;
|
|
||||||
} else {
|
|
||||||
modulo = 4u;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong alignment = sizeSoFar % modulo;
|
|
||||||
if (alignment == 0)
|
|
||||||
return typeSize;
|
|
||||||
|
|
||||||
return typeSize + (modulo - alignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong GetTypeSize<S> ()
|
|
||||||
{
|
|
||||||
Type type = typeof(S);
|
|
||||||
|
|
||||||
if (type == typeof(string)) {
|
|
||||||
// We treat `string` as a generic pointer
|
|
||||||
return Is64Bit ? 8u : 4u;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == typeof(byte)) {
|
|
||||||
return 1u;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == typeof(bool)) {
|
|
||||||
return 1u;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == typeof(Int32) || type == typeof(UInt32)) {
|
|
||||||
return 4u;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == typeof(Int64) || type == typeof(UInt64)) {
|
|
||||||
return 8u;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException ($"Unable to map managed type {type} to native assembler type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace tmt
|
||||||
XamarinAppDebugDSO_Version? xapp;
|
XamarinAppDebugDSO_Version? xapp;
|
||||||
|
|
||||||
public override string FormatVersion => xapp?.FormatVersion ?? "0";
|
public override string FormatVersion => xapp?.FormatVersion ?? "0";
|
||||||
|
protected override string LogTag => "DebugDSO";
|
||||||
public override string Description => xapp?.Description ?? "Xamarin App Debug DSO Forwarder";
|
public override string Description => xapp?.Description ?? "Xamarin App Debug DSO Forwarder";
|
||||||
public override Map Map => XAPP.Map;
|
public override Map Map => XAPP.Map;
|
||||||
|
|
||||||
|
@ -19,6 +20,8 @@ namespace tmt
|
||||||
|
|
||||||
public override bool CanLoad (AnELF elf)
|
public override bool CanLoad (AnELF elf)
|
||||||
{
|
{
|
||||||
|
Log.Debug (LogTag, $"Checking if {elf.FilePath} is a Debug DSO");
|
||||||
|
|
||||||
xapp = null;
|
xapp = null;
|
||||||
ulong format_tag = 0;
|
ulong format_tag = 0;
|
||||||
if (elf.HasSymbol (FormatTag))
|
if (elf.HasSymbol (FormatTag))
|
||||||
|
@ -32,8 +35,13 @@ namespace tmt
|
||||||
reader = new XamarinAppDebugDSO_V1 (ManagedResolver, elf);
|
reader = new XamarinAppDebugDSO_V1 (ManagedResolver, elf);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FormatTag_V2:
|
||||||
|
format_tag = 2;
|
||||||
|
reader = new XamarinAppDebugDSO_V2 (ManagedResolver, elf);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log.Error ($"{elf.FilePath} format ({format_tag}) is not supported by this version of TMT");
|
Log.Debug (LogTag, $"{elf.FilePath} format (0x{format_tag:x}) is not supported by this version of TMT");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,180 +84,4 @@ namespace tmt
|
||||||
protected abstract bool LoadMaps ();
|
protected abstract bool LoadMaps ();
|
||||||
protected abstract bool Convert ();
|
protected abstract bool Convert ();
|
||||||
}
|
}
|
||||||
|
|
||||||
class XamarinAppDebugDSO_V1 : XamarinAppDebugDSO_Version
|
|
||||||
{
|
|
||||||
sealed class MappedType
|
|
||||||
{
|
|
||||||
public string TargetType;
|
|
||||||
public ulong DuplicateCount = 0;
|
|
||||||
|
|
||||||
public MappedType (string targetType)
|
|
||||||
{
|
|
||||||
TargetType = targetType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const string TypeMapSymbolName = "type_map";
|
|
||||||
|
|
||||||
Map? map;
|
|
||||||
SortedDictionary<string, MappedType> javaToManaged = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
|
|
||||||
SortedDictionary<string, MappedType> managedToJava = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
|
|
||||||
|
|
||||||
public override string FormatVersion => "1";
|
|
||||||
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
|
|
||||||
|
|
||||||
public XamarinAppDebugDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
|
|
||||||
: base (managedResolver, elf)
|
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
public override bool CanLoad (AnELF elf)
|
|
||||||
{
|
|
||||||
return HasSymbol (elf, TypeMapSymbolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool Convert ()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
DoConvert ();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DoConvert ()
|
|
||||||
{
|
|
||||||
var managed = new List<MapEntry> ();
|
|
||||||
foreach (var kvp in managedToJava) {
|
|
||||||
string managedName = kvp.Key;
|
|
||||||
MappedType javaType = kvp.Value;
|
|
||||||
|
|
||||||
managed.Add (
|
|
||||||
new MapEntry (
|
|
||||||
new MapManagedType (managedName) { IsDuplicate = javaType.DuplicateCount > 0 },
|
|
||||||
new MapJavaType (javaType.TargetType)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var java = new List<MapEntry> ();
|
|
||||||
foreach (var kvp in javaToManaged) {
|
|
||||||
string javaName = kvp.Key;
|
|
||||||
MappedType managedType = kvp.Value;
|
|
||||||
|
|
||||||
java.Add (
|
|
||||||
new MapEntry (
|
|
||||||
new MapManagedType (managedType.TargetType) { IsDuplicate = managedType.DuplicateCount > 0 },
|
|
||||||
new MapJavaType (javaName)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
map = MakeMap (managed, java);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool LoadMaps ()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return DoLoadMaps ();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.ExceptionError ($"{Description}: failed to load maps", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DoLoadMaps ()
|
|
||||||
{
|
|
||||||
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMap)
|
|
||||||
ulong size = 0;
|
|
||||||
size += GetPaddedSize<uint> (size); // entry_count
|
|
||||||
size += GetPaddedSize<string> (size); // assembly_name (pointer)
|
|
||||||
size += GetPaddedSize<string> (size); // data (pointer)
|
|
||||||
size += GetPaddedSize<string> (size); // java_to_managed (pointer)
|
|
||||||
size += GetPaddedSize<string> (size); // managed_to_java (pointer)
|
|
||||||
|
|
||||||
string filePath = ELF.FilePath;
|
|
||||||
byte[] mapData = ELF.GetData (TypeMapSymbolName);
|
|
||||||
if (mapData.Length == 0) {
|
|
||||||
Log.Error ($"{filePath} doesn't have a valid '{TypeMapSymbolName}' symbol");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((ulong)mapData.Length != size) {
|
|
||||||
Log.Error ($"Symbol '{TypeMapSymbolName}' in {filePath} has invalid size. Expected {size}, got {mapData.Length}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong offset = 0;
|
|
||||||
uint entry_count = ReadUInt32 (mapData, ref offset);
|
|
||||||
|
|
||||||
ReadPointer (mapData, ref offset); // assembly_name, unused in Debug mode
|
|
||||||
ReadPointer (mapData, ref offset); // data, unused in Debug mode
|
|
||||||
|
|
||||||
ulong pointer = ReadPointer (mapData, ref offset); // java_to_managed
|
|
||||||
LoadMap ("Java to Managed", pointer, entry_count, AddJavaToManaged);
|
|
||||||
|
|
||||||
pointer = ReadPointer (mapData, ref offset); // managed_to_java
|
|
||||||
LoadMap ("Managed to Java", pointer, entry_count, AddManagedToJava);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddManagedToJava (string mapFrom, string mapTo)
|
|
||||||
{
|
|
||||||
if (managedToJava.TryGetValue (mapFrom, out MappedType? entry)) {
|
|
||||||
entry.DuplicateCount++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
managedToJava.Add (mapFrom, new MappedType (mapTo));
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddJavaToManaged (string mapFrom, string mapTo)
|
|
||||||
{
|
|
||||||
if (javaToManaged.TryGetValue (mapFrom, out MappedType? entry)) {
|
|
||||||
entry.DuplicateCount++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
javaToManaged.Add (mapFrom, new MappedType (mapTo));
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoadMap (string name, ulong pointer, uint entry_count, Action<string, string> addToMap)
|
|
||||||
{
|
|
||||||
string entries = entry_count == 1 ? "entry" : "entries";
|
|
||||||
Log.Info ($" Loading {name} map: {entry_count} {entries}, please wait...");
|
|
||||||
|
|
||||||
ulong size = 0;
|
|
||||||
size += GetPaddedSize<string> (size); // from
|
|
||||||
size += GetPaddedSize<string> (size); // to
|
|
||||||
|
|
||||||
ulong mapSize = entry_count * size;
|
|
||||||
byte[] data = ELF.GetData (pointer, mapSize);
|
|
||||||
|
|
||||||
ulong offset = 0;
|
|
||||||
string mapFrom;
|
|
||||||
string mapTo;
|
|
||||||
for (uint i = 0; i < entry_count; i++) {
|
|
||||||
pointer = ReadPointer (data, ref offset);
|
|
||||||
if (pointer != 0) {
|
|
||||||
mapFrom = ELF.GetASCIIZ (pointer);
|
|
||||||
} else {
|
|
||||||
mapFrom = $"#{i}";
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer = ReadPointer (data, ref offset);
|
|
||||||
if (pointer != 0) {
|
|
||||||
mapTo = ELF.GetASCIIZ (pointer);
|
|
||||||
} else {
|
|
||||||
mapTo = $"#{i}";
|
|
||||||
}
|
|
||||||
|
|
||||||
addToMap (mapFrom, mapTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
class XamarinAppDebugDSO_V1 : XamarinAppDebugDSO_Version
|
||||||
|
{
|
||||||
|
sealed class MappedType
|
||||||
|
{
|
||||||
|
public string TargetType;
|
||||||
|
public ulong DuplicateCount = 0;
|
||||||
|
|
||||||
|
public MappedType (string targetType)
|
||||||
|
{
|
||||||
|
TargetType = targetType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const string TypeMapSymbolName = "type_map";
|
||||||
|
|
||||||
|
protected override string LogTag => "DebugDSO_V1";
|
||||||
|
|
||||||
|
Map? map;
|
||||||
|
SortedDictionary<string, MappedType> javaToManaged = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
|
||||||
|
SortedDictionary<string, MappedType> managedToJava = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
|
||||||
|
|
||||||
|
public override string FormatVersion => "1";
|
||||||
|
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
|
||||||
|
|
||||||
|
public XamarinAppDebugDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
|
||||||
|
: base (managedResolver, elf)
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
public override bool CanLoad (AnELF elf)
|
||||||
|
{
|
||||||
|
return HasSymbol (elf, TypeMapSymbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool Convert ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DoConvert ();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoConvert ()
|
||||||
|
{
|
||||||
|
var managed = new List<MapEntry> ();
|
||||||
|
foreach (var kvp in managedToJava) {
|
||||||
|
string managedName = kvp.Key;
|
||||||
|
MappedType javaType = kvp.Value;
|
||||||
|
|
||||||
|
managed.Add (
|
||||||
|
new MapEntry (
|
||||||
|
new MapManagedType (managedName) { IsDuplicate = javaType.DuplicateCount > 0 },
|
||||||
|
new MapJavaType (javaType.TargetType)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var java = new List<MapEntry> ();
|
||||||
|
foreach (var kvp in javaToManaged) {
|
||||||
|
string javaName = kvp.Key;
|
||||||
|
MappedType managedType = kvp.Value;
|
||||||
|
|
||||||
|
java.Add (
|
||||||
|
new MapEntry (
|
||||||
|
new MapManagedType (managedType.TargetType) { IsDuplicate = managedType.DuplicateCount > 0 },
|
||||||
|
new MapJavaType (javaName)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
map = MakeMap (managed, java);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool LoadMaps ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return DoLoadMaps ();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to load maps", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoLoadMaps ()
|
||||||
|
{
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMap)
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<uint> (size); // entry_count
|
||||||
|
size += GetPaddedSize<string> (size); // assembly_name (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // data (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // java_to_managed (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // managed_to_java (pointer)
|
||||||
|
|
||||||
|
string filePath = ELF.FilePath;
|
||||||
|
(byte[] mapData, _) = ELF.GetData (TypeMapSymbolName);
|
||||||
|
if (mapData.Length == 0) {
|
||||||
|
Log.Error ($"{filePath} doesn't have a valid '{TypeMapSymbolName}' symbol");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ulong)mapData.Length != size) {
|
||||||
|
Log.Error ($"Symbol '{TypeMapSymbolName}' in {filePath} has invalid size. Expected {size}, got {mapData.Length}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong offset = 0;
|
||||||
|
uint entry_count = ReadUInt32 (mapData, ref offset);
|
||||||
|
|
||||||
|
ReadPointer (mapData, ref offset); // assembly_name, unused in Debug mode
|
||||||
|
ReadPointer (mapData, ref offset); // data, unused in Debug mode
|
||||||
|
|
||||||
|
ulong pointer = ReadPointer (mapData, ref offset); // java_to_managed
|
||||||
|
LoadMap ("Java to Managed", pointer, entry_count, AddJavaToManaged);
|
||||||
|
|
||||||
|
pointer = ReadPointer (mapData, ref offset); // managed_to_java
|
||||||
|
LoadMap ("Managed to Java", pointer, entry_count, AddManagedToJava);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddManagedToJava (string mapFrom, string mapTo)
|
||||||
|
{
|
||||||
|
if (managedToJava.TryGetValue (mapFrom, out MappedType? entry)) {
|
||||||
|
entry.DuplicateCount++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
managedToJava.Add (mapFrom, new MappedType (mapTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddJavaToManaged (string mapFrom, string mapTo)
|
||||||
|
{
|
||||||
|
if (javaToManaged.TryGetValue (mapFrom, out MappedType? entry)) {
|
||||||
|
entry.DuplicateCount++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
javaToManaged.Add (mapFrom, new MappedType (mapTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadMap (string name, ulong pointer, uint entry_count, Action<string, string> addToMap)
|
||||||
|
{
|
||||||
|
string entries = entry_count == 1 ? "entry" : "entries";
|
||||||
|
Log.Info ($" Loading {name} map: {entry_count} {entries}, please wait...");
|
||||||
|
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<string> (size); // from
|
||||||
|
size += GetPaddedSize<string> (size); // to
|
||||||
|
|
||||||
|
ulong mapSize = entry_count * size;
|
||||||
|
byte[] data = ELF.GetData (pointer, mapSize);
|
||||||
|
|
||||||
|
ulong offset = 0;
|
||||||
|
string mapFrom;
|
||||||
|
string mapTo;
|
||||||
|
for (uint i = 0; i < entry_count; i++) {
|
||||||
|
pointer = ReadPointer (data, ref offset);
|
||||||
|
if (pointer != 0) {
|
||||||
|
mapFrom = ELF.GetASCIIZ (pointer);
|
||||||
|
} else {
|
||||||
|
mapFrom = $"#{i}";
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer = ReadPointer (data, ref offset);
|
||||||
|
if (pointer != 0) {
|
||||||
|
mapTo = ELF.GetASCIIZ (pointer);
|
||||||
|
} else {
|
||||||
|
mapTo = $"#{i}";
|
||||||
|
}
|
||||||
|
|
||||||
|
addToMap (mapFrom, mapTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using ELFSharp.ELF.Sections;
|
||||||
|
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
class XamarinAppDebugDSO_V2 : XamarinAppDebugDSO_Version
|
||||||
|
{
|
||||||
|
sealed class MappedType
|
||||||
|
{
|
||||||
|
public string TargetType;
|
||||||
|
public ulong DuplicateCount = 0;
|
||||||
|
|
||||||
|
public MappedType (string targetType)
|
||||||
|
{
|
||||||
|
TargetType = targetType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const string TypeMapSymbolName = "type_map";
|
||||||
|
|
||||||
|
protected override string LogTag => "DebugDSO_V2";
|
||||||
|
|
||||||
|
Map? map;
|
||||||
|
SortedDictionary<string, List<MappedType>> javaToManaged = new SortedDictionary<string, List<MappedType>> (StringComparer.Ordinal);
|
||||||
|
SortedDictionary<string, MappedType> managedToJava = new SortedDictionary<string, MappedType> (StringComparer.Ordinal);
|
||||||
|
|
||||||
|
public override string FormatVersion => "2";
|
||||||
|
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
|
||||||
|
|
||||||
|
public XamarinAppDebugDSO_V2 (ManagedTypeResolver managedResolver, AnELF elf)
|
||||||
|
: base (managedResolver, elf)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public override bool CanLoad (AnELF elf)
|
||||||
|
{
|
||||||
|
return HasSymbol (elf, TypeMapSymbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool Convert ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DoConvert ();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoConvert ()
|
||||||
|
{
|
||||||
|
var managed = new List<MapEntry> ();
|
||||||
|
foreach (var kvp in managedToJava) {
|
||||||
|
string managedName = kvp.Key;
|
||||||
|
MappedType javaType = kvp.Value;
|
||||||
|
|
||||||
|
managed.Add (
|
||||||
|
new MapEntry (
|
||||||
|
new MapManagedType (managedName) { IsDuplicate = javaType.DuplicateCount > 0 },
|
||||||
|
new MapJavaType (javaType.TargetType)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var java = new List<MapEntry> ();
|
||||||
|
foreach (var kvp in javaToManaged) {
|
||||||
|
string javaName = kvp.Key;
|
||||||
|
List<MappedType> managedTypes = kvp.Value;
|
||||||
|
var javaType = new MapJavaType (javaName);
|
||||||
|
|
||||||
|
foreach (MappedType managedType in managedTypes) {
|
||||||
|
java.Add (
|
||||||
|
new MapEntry (
|
||||||
|
new MapManagedType (managedType.TargetType) { IsDuplicate = managedType.DuplicateCount > 0 },
|
||||||
|
javaType
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map = MakeMap (managed, java);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool LoadMaps ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return DoLoadMaps ();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to load maps", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoLoadMaps ()
|
||||||
|
{
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMap)
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<uint> (size); // entry_count
|
||||||
|
size += GetPaddedSize<string> (size); // assembly_name (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // data (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // java_to_managed (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // managed_to_java (pointer)
|
||||||
|
|
||||||
|
string filePath = ELF.FilePath;
|
||||||
|
(byte[] mapData, ISymbolEntry? symbol) = ELF.GetData (TypeMapSymbolName);
|
||||||
|
if (mapData.Length == 0 || symbol == null) {
|
||||||
|
Log.Error ($"{filePath} doesn't have a valid '{TypeMapSymbolName}' symbol");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ulong)mapData.Length != size) {
|
||||||
|
Log.Error ($"Symbol '{TypeMapSymbolName}' in {filePath} has invalid size. Expected {size}, got {mapData.Length}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong offset = 0;
|
||||||
|
uint entry_count = ReadUInt32 (mapData, ref offset);
|
||||||
|
ReadPointer (symbol, mapData, ref offset); // assembly_name, unused in Debug mode
|
||||||
|
ReadPointer (symbol, mapData, ref offset); // data, unused in Debug mode
|
||||||
|
|
||||||
|
ulong pointer = ReadPointer (symbol, mapData, ref offset); // java_to_managed
|
||||||
|
LoadMap ("Java to Managed", pointer, entry_count, AddJavaToManaged);
|
||||||
|
|
||||||
|
pointer = ReadPointer (symbol, mapData, ref offset); // managed_to_java
|
||||||
|
LoadMap ("Managed to Java", pointer, entry_count, AddManagedToJava);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddManagedToJava (string mapFrom, string mapTo)
|
||||||
|
{
|
||||||
|
if (managedToJava.TryGetValue (mapFrom, out MappedType? entry)) {
|
||||||
|
entry.DuplicateCount++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
managedToJava.Add (mapFrom, new MappedType (mapTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddJavaToManaged (string mapFrom, string mapTo)
|
||||||
|
{
|
||||||
|
if (javaToManaged.TryGetValue (mapFrom, out List<MappedType>? types)) {
|
||||||
|
types.Add (new MappedType (mapTo));
|
||||||
|
for (int i = 1; i < types.Count; i++) {
|
||||||
|
MappedType entry = types[i];
|
||||||
|
entry.DuplicateCount++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
javaToManaged.Add (mapFrom, new List<MappedType> {new MappedType (mapTo)});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadMap (string name, ulong arrayPointer, uint entry_count, Action<string, string> addToMap)
|
||||||
|
{
|
||||||
|
string entries = entry_count == 1 ? "entry" : "entries";
|
||||||
|
Log.Info ($" Loading {name} map: {entry_count} {entries}");
|
||||||
|
Log.Debug (LogTag, $" pointer == 0x{arrayPointer:X}");
|
||||||
|
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<string> (size); // from
|
||||||
|
size += GetPaddedSize<string> (size); // to
|
||||||
|
|
||||||
|
ulong mapSize = entry_count * size;
|
||||||
|
byte[] data = ELF.GetDataFromPointer (arrayPointer, mapSize);
|
||||||
|
|
||||||
|
ulong offset = 0;
|
||||||
|
string mapFrom;
|
||||||
|
string mapTo;
|
||||||
|
for (uint i = 0; i < entry_count; i++) {
|
||||||
|
ulong pointer = ReadPointer (arrayPointer, data, ref offset);
|
||||||
|
Log.Debug (LogTag, $" [{i}] pointer1 == 0x{pointer:X}");
|
||||||
|
if (pointer != 0) {
|
||||||
|
mapFrom = ELF.GetASCIIZFromPointer (pointer);
|
||||||
|
} else {
|
||||||
|
mapFrom = $"#{i} <null>";
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer = ReadPointer (arrayPointer, data, ref offset);
|
||||||
|
Log.Debug (LogTag, $" [{i}] pointer2 == 0x{pointer:X}");
|
||||||
|
if (pointer != 0) {
|
||||||
|
mapTo = ELF.GetASCIIZFromPointer (pointer);
|
||||||
|
} else {
|
||||||
|
mapTo = $"#{i} <null>";
|
||||||
|
}
|
||||||
|
|
||||||
|
addToMap (mapFrom, mapTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace tmt
|
namespace tmt
|
||||||
{
|
{
|
||||||
|
@ -10,6 +8,7 @@ namespace tmt
|
||||||
XamarinAppReleaseDSO_Version? xapp;
|
XamarinAppReleaseDSO_Version? xapp;
|
||||||
|
|
||||||
public override string FormatVersion => xapp?.FormatVersion ?? "0";
|
public override string FormatVersion => xapp?.FormatVersion ?? "0";
|
||||||
|
protected override string LogTag => "ReleaseDSO";
|
||||||
public override string Description => xapp?.Description ?? "Xamarin App Release DSO Forwarder";
|
public override string Description => xapp?.Description ?? "Xamarin App Release DSO Forwarder";
|
||||||
public override Map Map => XAPP.Map;
|
public override Map Map => XAPP.Map;
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ namespace tmt
|
||||||
|
|
||||||
public override bool CanLoad (AnELF elf)
|
public override bool CanLoad (AnELF elf)
|
||||||
{
|
{
|
||||||
Log.Debug ($"Checking if {elf.FilePath} is a Release DSO");
|
Log.Debug (LogTag, $"Checking if {elf.FilePath} is a Release DSO");
|
||||||
|
|
||||||
xapp = null;
|
xapp = null;
|
||||||
ulong format_tag = 0;
|
ulong format_tag = 0;
|
||||||
|
@ -40,8 +39,13 @@ namespace tmt
|
||||||
reader = new XamarinAppReleaseDSO_V1 (ManagedResolver, elf);
|
reader = new XamarinAppReleaseDSO_V1 (ManagedResolver, elf);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case FormatTag_V2:
|
||||||
|
format_tag = 2;
|
||||||
|
reader = new XamarinAppReleaseDSO_V2 (ManagedResolver, elf);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Log.Error ($"{elf.FilePath} format ({format_tag}) is not supported by this version of TMT");
|
Log.Debug (LogTag, $"{elf.FilePath} format (0x{format_tag:x}) is not supported by this version of TMT");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,391 +94,4 @@ namespace tmt
|
||||||
protected abstract bool Convert ();
|
protected abstract bool Convert ();
|
||||||
protected abstract void SaveRaw (string baseOutputFilePath, string extension);
|
protected abstract void SaveRaw (string baseOutputFilePath, string extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
class XamarinAppReleaseDSO_V1 : XamarinAppReleaseDSO_Version
|
|
||||||
{
|
|
||||||
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
|
||||||
sealed class TypeMapModuleEntry
|
|
||||||
{
|
|
||||||
public uint type_token_id;
|
|
||||||
public uint java_map_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
|
||||||
sealed class TypeMapModule
|
|
||||||
{
|
|
||||||
public Guid module_uuid;
|
|
||||||
public uint entry_count;
|
|
||||||
public uint duplicate_count;
|
|
||||||
public List<TypeMapModuleEntry>? map;
|
|
||||||
public List<TypeMapModuleEntry>? duplicate_map;
|
|
||||||
public string assembly_name = String.Empty;
|
|
||||||
|
|
||||||
// These three aren't used, listed for completeness
|
|
||||||
public readonly object? image = null;
|
|
||||||
public readonly uint java_name_width = 0;
|
|
||||||
public readonly byte[]? java_map = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
|
|
||||||
sealed class TypeMapJava
|
|
||||||
{
|
|
||||||
public uint module_index;
|
|
||||||
public uint type_token_id;
|
|
||||||
public string java_name = String.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
const string MapModulesSymbolName = "map_modules";
|
|
||||||
const string ModuleCountSymbolName = "map_module_count";
|
|
||||||
const string JavaTypeCountSymbolName = "java_type_count";
|
|
||||||
const string JavaNameWidthSymbolName = "java_name_width";
|
|
||||||
const string MapJavaSymbolName = "map_java";
|
|
||||||
|
|
||||||
Map? map;
|
|
||||||
List<TypeMapModule>? modules;
|
|
||||||
List<TypeMapJava>? javaTypes;
|
|
||||||
|
|
||||||
public override string FormatVersion => "1";
|
|
||||||
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
|
|
||||||
|
|
||||||
public XamarinAppReleaseDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
|
|
||||||
: base (managedResolver, elf)
|
|
||||||
{}
|
|
||||||
|
|
||||||
public override bool CanLoad (AnELF elf)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
HasSymbol (elf, MapModulesSymbolName) &&
|
|
||||||
HasSymbol (elf, ModuleCountSymbolName) &&
|
|
||||||
HasSymbol (elf, JavaTypeCountSymbolName) &&
|
|
||||||
HasSymbol (elf, JavaNameWidthSymbolName) &&
|
|
||||||
HasSymbol (elf, MapJavaSymbolName);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool LoadMaps ()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
string filePath = ELF.FilePath;
|
|
||||||
modules = LoadMapModules (filePath);
|
|
||||||
javaTypes = LoadJavaTypes (filePath);
|
|
||||||
return true;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.ExceptionError ($"{Description}: failed to load maps", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void SaveRaw (string baseOutputFilePath, string extension)
|
|
||||||
{
|
|
||||||
const string indent = "\t";
|
|
||||||
|
|
||||||
if (modules == null || javaTypes == null) {
|
|
||||||
Log.Warning ($"{Description}: cannot save raw report, no data");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string outputFilePath = Utilities.GetManagedOutputFileName (baseOutputFilePath, extension);
|
|
||||||
Utilities.CreateFileDirectory (outputFilePath);
|
|
||||||
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
|
|
||||||
sw.WriteLine ("TYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_MAP_INDEX");
|
|
||||||
uint index = 0;
|
|
||||||
foreach (TypeMapModule module in modules) {
|
|
||||||
sw.WriteLine ();
|
|
||||||
sw.WriteLine ($"Module {index++:D04}: {module.assembly_name} (MVID: {module.module_uuid}; entries: {module.entry_count}; duplicates: {module.duplicate_count})");
|
|
||||||
if (module.map == null) {
|
|
||||||
sw.WriteLine ($"{indent}no map");
|
|
||||||
} else {
|
|
||||||
WriteManagedMap ("map", module.map, sw);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module.duplicate_map == null) {
|
|
||||||
if (module.duplicate_count > 0)
|
|
||||||
sw.WriteLine ($"{indent}no duplicate map, but there should be {module.duplicate_count} entries");
|
|
||||||
else
|
|
||||||
sw.WriteLine ($"{indent}no duplicates");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
WriteManagedMap ("duplicate map", module.duplicate_map, sw);
|
|
||||||
}
|
|
||||||
sw.Flush ();
|
|
||||||
}
|
|
||||||
|
|
||||||
outputFilePath = Utilities.GetJavaOutputFileName (baseOutputFilePath, extension);
|
|
||||||
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
|
|
||||||
sw.WriteLine ("MANAGED_MODULE_INDEX\tTYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_TYPE_NAME");
|
|
||||||
|
|
||||||
foreach (TypeMapJava tmj in javaTypes) {
|
|
||||||
sw.WriteLine ($"{indent}{tmj.module_index}\t{tmj.type_token_id:D08} ({tmj.type_token_id:X08})\t{tmj.java_name}");
|
|
||||||
}
|
|
||||||
sw.Flush ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteManagedMap (string name, List<TypeMapModuleEntry> map, StreamWriter sw)
|
|
||||||
{
|
|
||||||
sw.WriteLine ($"{indent}{name}:");
|
|
||||||
foreach (TypeMapModuleEntry entry in map) {
|
|
||||||
sw.WriteLine ($"{indent}{indent}{entry.type_token_id} ({entry.type_token_id:X08})\t{entry.java_map_index}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MapManagedType MakeManagedType (Guid mvid, uint tokenID, string assemblyName, string filePath, bool isGeneric, bool isDuplicate)
|
|
||||||
{
|
|
||||||
return new MapManagedType (mvid, tokenID, assemblyName, filePath) {
|
|
||||||
IsGeneric = isGeneric,
|
|
||||||
IsDuplicate = isDuplicate,
|
|
||||||
TypeName = ManagedResolver.Lookup (assemblyName, mvid, tokenID)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool Convert ()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
DoConvert ();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DoConvert ()
|
|
||||||
{
|
|
||||||
if (modules == null || javaTypes == null) {
|
|
||||||
Log.Warning ($"{Description}: cannot convert maps, no data");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string filePath = ELF.FilePath;
|
|
||||||
var managedToJava = new List<MapEntry> ();
|
|
||||||
uint index = 0;
|
|
||||||
|
|
||||||
bool somethingFailed = false;
|
|
||||||
foreach (TypeMapModule m in modules) {
|
|
||||||
ConvertManagedMap (m, EnsureMap (m), isDuplicate: false);
|
|
||||||
if (m.duplicate_map != null) {
|
|
||||||
if (!ConvertManagedMap (m, m.duplicate_map, isDuplicate: true)) {
|
|
||||||
somethingFailed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (somethingFailed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = 0;
|
|
||||||
var javaToManaged = new List<MapEntry> ();
|
|
||||||
foreach (TypeMapJava tmj in javaTypes) {
|
|
||||||
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType (tmj.module_index, tmj.type_token_id);
|
|
||||||
if (!success) {
|
|
||||||
somethingFailed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module == null) {
|
|
||||||
throw new InvalidOperationException ("module must not be null here");
|
|
||||||
}
|
|
||||||
|
|
||||||
javaToManaged.Add (
|
|
||||||
new MapEntry (
|
|
||||||
MakeManagedType (module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
|
|
||||||
new MapJavaType (tmj.java_name, filePath)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
|
|
||||||
map = MakeMap (managedToJava, javaToManaged);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
bool ConvertManagedMap (TypeMapModule module, List<TypeMapModuleEntry> map, bool isDuplicate)
|
|
||||||
{
|
|
||||||
foreach (TypeMapModuleEntry entry in map) {
|
|
||||||
TypeMapJava java;
|
|
||||||
|
|
||||||
if ((uint)javaTypes.Count <= entry.java_map_index) {
|
|
||||||
Log.Error ($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
java = javaTypes[(int)entry.java_map_index];
|
|
||||||
managedToJava.Add (
|
|
||||||
new MapEntry (
|
|
||||||
MakeManagedType (module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
|
|
||||||
new MapJavaType (java.java_name, filePath)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) FindManagedType (uint moduleIndex, uint tokenID)
|
|
||||||
{
|
|
||||||
if (moduleIndex >= (uint)modules.Count) {
|
|
||||||
Log.Error ($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
|
|
||||||
return (false, null, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeMapModule m = modules[(int)moduleIndex];
|
|
||||||
if (tokenID == 0) {
|
|
||||||
return (true, m, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (TypeMapModuleEntry entry in EnsureMap (m)) {
|
|
||||||
if (entry.type_token_id == tokenID) {
|
|
||||||
return (true, m, false, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m.duplicate_map != null) {
|
|
||||||
foreach (TypeMapModuleEntry entry in m.duplicate_map) {
|
|
||||||
if (entry.type_token_id == tokenID) {
|
|
||||||
return (true, m, false, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.Error ($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
|
|
||||||
return (false, null, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TypeMapModuleEntry> EnsureMap (TypeMapModule m)
|
|
||||||
{
|
|
||||||
if (m.map == null)
|
|
||||||
throw new InvalidOperationException ($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
|
|
||||||
return m.map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TypeMapJava> LoadJavaTypes (string filePath)
|
|
||||||
{
|
|
||||||
ulong javaTypeCount = (ulong)ELF.GetUInt32 (JavaTypeCountSymbolName);
|
|
||||||
ulong javaNameWidth = (ulong)ELF.GetUInt32 (JavaNameWidthSymbolName);
|
|
||||||
|
|
||||||
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
|
||||||
ulong size = 0;
|
|
||||||
size += GetPaddedSize<uint> (size); // module_index
|
|
||||||
size += GetPaddedSize<uint> (size); // type_token_id
|
|
||||||
size += javaNameWidth;
|
|
||||||
|
|
||||||
byte[] data = ELF.GetData (MapJavaSymbolName);
|
|
||||||
if (data.Length == 0)
|
|
||||||
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaSymbolName}' symbol");
|
|
||||||
|
|
||||||
ulong calculatedJavaTypeCount = (ulong)data.LongLength / size;
|
|
||||||
if (calculatedJavaTypeCount != javaTypeCount)
|
|
||||||
throw new InvalidOperationException ($"{filePath} has invalid '{JavaTypeCountSymbolName}' symbol value ({javaTypeCount}), '{JavaTypeCountSymbolName}' size indicates there are {calculatedJavaTypeCount} managedToJava instead");
|
|
||||||
|
|
||||||
var ret = new List<TypeMapJava> ();
|
|
||||||
ulong offset = 0;
|
|
||||||
for (ulong i = 0; i < javaTypeCount; i++) {
|
|
||||||
var javaEntry = new TypeMapJava {
|
|
||||||
module_index = ReadUInt32 (data, ref offset, packed: true),
|
|
||||||
type_token_id = ReadUInt32 (data, ref offset, packed: true),
|
|
||||||
};
|
|
||||||
|
|
||||||
javaEntry.java_name = ELF.GetASCIIZ (data, offset);
|
|
||||||
offset += javaNameWidth;
|
|
||||||
ret.Add (javaEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TypeMapModule> LoadMapModules (string filePath)
|
|
||||||
{
|
|
||||||
ulong moduleCount = (ulong)ELF.GetUInt32 (ModuleCountSymbolName);
|
|
||||||
|
|
||||||
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
|
||||||
ulong size;
|
|
||||||
|
|
||||||
size = 16; // module_uuid
|
|
||||||
size += GetPaddedSize<uint> (size); // entry_count
|
|
||||||
size += GetPaddedSize<uint> (size); // duplicate_count
|
|
||||||
size += GetPaddedSize<string> (size); // map (pointer)
|
|
||||||
size += GetPaddedSize<string> (size); // duplicate_map (pointer)
|
|
||||||
size += GetPaddedSize<string> (size); // assembly_name (pointer)
|
|
||||||
size += GetPaddedSize<string> (size); // image (pointer)
|
|
||||||
size += GetPaddedSize<uint> (size); // java_name_width
|
|
||||||
size += GetPaddedSize<string> (size); // java_map (pointer)
|
|
||||||
|
|
||||||
byte[] moduleData = ELF.GetData (MapModulesSymbolName);
|
|
||||||
if (moduleData.Length == 0)
|
|
||||||
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
|
|
||||||
|
|
||||||
ulong calculatedModuleCount = (ulong)moduleData.Length / size;
|
|
||||||
if (calculatedModuleCount != moduleCount)
|
|
||||||
throw new InvalidOperationException ($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
|
|
||||||
|
|
||||||
var ret = new List<TypeMapModule> ();
|
|
||||||
ulong offset = 0;
|
|
||||||
for (ulong i = 0; i < moduleCount; i++) {
|
|
||||||
Log.Debug ($"Module {i + 1}");
|
|
||||||
var module = new TypeMapModule ();
|
|
||||||
|
|
||||||
byte[] mvid = new byte[16];
|
|
||||||
Array.Copy (moduleData, (int)offset, mvid, 0, mvid.Length);
|
|
||||||
module.module_uuid = new Guid (mvid);
|
|
||||||
offset += (ulong)mvid.Length;
|
|
||||||
Log.Debug ($" module_uuid == {module.module_uuid}");
|
|
||||||
|
|
||||||
module.entry_count = ReadUInt32 (moduleData, ref offset);
|
|
||||||
Log.Debug ($" entry_count == {module.entry_count}");
|
|
||||||
|
|
||||||
module.duplicate_count = ReadUInt32 (moduleData, ref offset);
|
|
||||||
Log.Debug ($" duplicate_count == {module.duplicate_count}");
|
|
||||||
|
|
||||||
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
|
||||||
ulong pointer = ReadPointer (moduleData, ref offset);
|
|
||||||
size = 0;
|
|
||||||
size += GetPaddedSize<uint> (size); // type_token_id
|
|
||||||
size += GetPaddedSize<uint> (size); // java_map_index
|
|
||||||
|
|
||||||
ulong mapSize = size * module.entry_count;
|
|
||||||
byte[] data = ELF.GetData (pointer, mapSize);
|
|
||||||
|
|
||||||
module.map = new List<TypeMapModuleEntry> ();
|
|
||||||
ReadMapEntries (module.map, data, module.entry_count);
|
|
||||||
|
|
||||||
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
|
||||||
pointer = ReadPointer (moduleData, ref offset);
|
|
||||||
if (pointer != 0) {
|
|
||||||
mapSize = size * module.duplicate_count;
|
|
||||||
data = ELF.GetData (pointer, mapSize);
|
|
||||||
module.duplicate_map = new List<TypeMapModuleEntry> ();
|
|
||||||
ReadMapEntries (module.duplicate_map, data, module.duplicate_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer = ReadPointer (moduleData, ref offset);
|
|
||||||
module.assembly_name = ELF.GetASCIIZ (pointer);
|
|
||||||
Log.Debug ($" assembly_name == {module.assembly_name}");
|
|
||||||
Log.Debug ("");
|
|
||||||
|
|
||||||
// Read the values to properly adjust the offset taking padding into account
|
|
||||||
ReadPointer (moduleData, ref offset);
|
|
||||||
ReadUInt32 (moduleData, ref offset);
|
|
||||||
ReadPointer (moduleData, ref offset);
|
|
||||||
|
|
||||||
ret.Add (module);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
void ReadMapEntries (List<TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
|
|
||||||
{
|
|
||||||
ulong mapOffset = 0;
|
|
||||||
for (uint i = 0; i < entryCount; i++) {
|
|
||||||
var entry = new TypeMapModuleEntry {
|
|
||||||
type_token_id = ReadUInt32 (inputData, ref mapOffset),
|
|
||||||
java_map_index = ReadUInt32 (inputData, ref mapOffset)
|
|
||||||
};
|
|
||||||
|
|
||||||
map.Add (entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,394 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
class XamarinAppReleaseDSO_V1 : XamarinAppReleaseDSO_Version
|
||||||
|
{
|
||||||
|
protected override string LogTag => "ReleaseDSO_V1";
|
||||||
|
|
||||||
|
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
||||||
|
sealed class TypeMapModuleEntry
|
||||||
|
{
|
||||||
|
public uint type_token_id;
|
||||||
|
public uint java_map_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
||||||
|
sealed class TypeMapModule
|
||||||
|
{
|
||||||
|
public Guid module_uuid;
|
||||||
|
public uint entry_count;
|
||||||
|
public uint duplicate_count;
|
||||||
|
public List<TypeMapModuleEntry>? map;
|
||||||
|
public List<TypeMapModuleEntry>? duplicate_map;
|
||||||
|
public string assembly_name = String.Empty;
|
||||||
|
|
||||||
|
// These three aren't used, listed for completeness
|
||||||
|
public readonly object? image = null;
|
||||||
|
public readonly uint java_name_width = 0;
|
||||||
|
public readonly byte[]? java_map = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
|
||||||
|
sealed class TypeMapJava
|
||||||
|
{
|
||||||
|
public uint module_index;
|
||||||
|
public uint type_token_id;
|
||||||
|
public string java_name = String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string MapModulesSymbolName = "map_modules";
|
||||||
|
const string ModuleCountSymbolName = "map_module_count";
|
||||||
|
const string JavaTypeCountSymbolName = "java_type_count";
|
||||||
|
const string JavaNameWidthSymbolName = "java_name_width";
|
||||||
|
const string MapJavaSymbolName = "map_java";
|
||||||
|
|
||||||
|
Map? map;
|
||||||
|
List<TypeMapModule>? modules;
|
||||||
|
List<TypeMapJava>? javaTypes;
|
||||||
|
|
||||||
|
public override string FormatVersion => "1";
|
||||||
|
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
|
||||||
|
|
||||||
|
public XamarinAppReleaseDSO_V1 (ManagedTypeResolver managedResolver, AnELF elf)
|
||||||
|
: base (managedResolver, elf)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public override bool CanLoad (AnELF elf)
|
||||||
|
{
|
||||||
|
return HasSymbol (elf, MapModulesSymbolName) &&
|
||||||
|
HasSymbol (elf, ModuleCountSymbolName) &&
|
||||||
|
HasSymbol (elf, JavaTypeCountSymbolName) &&
|
||||||
|
HasSymbol (elf, JavaNameWidthSymbolName) &&
|
||||||
|
HasSymbol (elf, MapJavaSymbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool LoadMaps ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
string filePath = ELF.FilePath;
|
||||||
|
modules = LoadMapModules (filePath);
|
||||||
|
javaTypes = LoadJavaTypes (filePath);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to load maps", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SaveRaw (string baseOutputFilePath, string extension)
|
||||||
|
{
|
||||||
|
const string indent = "\t";
|
||||||
|
|
||||||
|
if (modules == null || javaTypes == null) {
|
||||||
|
Log.Warning ($"{Description}: cannot save raw report, no data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string outputFilePath = Utilities.GetManagedOutputFileName (baseOutputFilePath, extension);
|
||||||
|
Utilities.CreateFileDirectory (outputFilePath);
|
||||||
|
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
|
||||||
|
sw.WriteLine ("TYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_MAP_INDEX");
|
||||||
|
uint index = 0;
|
||||||
|
foreach (TypeMapModule module in modules) {
|
||||||
|
sw.WriteLine ();
|
||||||
|
sw.WriteLine ($"Module {index++:D04}: {module.assembly_name} (MVID: {module.module_uuid}; entries: {module.entry_count}; duplicates: {module.duplicate_count})");
|
||||||
|
if (module.map == null) {
|
||||||
|
sw.WriteLine ($"{indent}no map");
|
||||||
|
} else {
|
||||||
|
WriteManagedMap ("map", module.map, sw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.duplicate_map == null) {
|
||||||
|
if (module.duplicate_count > 0)
|
||||||
|
sw.WriteLine ($"{indent}no duplicate map, but there should be {module.duplicate_count} entries");
|
||||||
|
else
|
||||||
|
sw.WriteLine ($"{indent}no duplicates");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteManagedMap ("duplicate map", module.duplicate_map, sw);
|
||||||
|
}
|
||||||
|
sw.Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFilePath = Utilities.GetJavaOutputFileName (baseOutputFilePath, extension);
|
||||||
|
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
|
||||||
|
sw.WriteLine ("MANAGED_MODULE_INDEX\tTYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_TYPE_NAME");
|
||||||
|
|
||||||
|
foreach (TypeMapJava tmj in javaTypes) {
|
||||||
|
sw.WriteLine ($"{indent}{tmj.module_index}\t{tmj.type_token_id:D08} ({tmj.type_token_id:X08})\t{tmj.java_name}");
|
||||||
|
}
|
||||||
|
sw.Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteManagedMap (string name, List<TypeMapModuleEntry> map, StreamWriter sw)
|
||||||
|
{
|
||||||
|
sw.WriteLine ($"{indent}{name}:");
|
||||||
|
foreach (TypeMapModuleEntry entry in map) {
|
||||||
|
sw.WriteLine ($"{indent}{indent}{entry.type_token_id} ({entry.type_token_id:X08})\t{entry.java_map_index}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapManagedType MakeManagedType (Guid mvid, uint tokenID, string assemblyName, string filePath, bool isGeneric, bool isDuplicate)
|
||||||
|
{
|
||||||
|
return new MapManagedType (mvid, tokenID, assemblyName, filePath) {
|
||||||
|
IsGeneric = isGeneric,
|
||||||
|
IsDuplicate = isDuplicate,
|
||||||
|
TypeName = ManagedResolver.Lookup (assemblyName, mvid, tokenID)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool Convert ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DoConvert ();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoConvert ()
|
||||||
|
{
|
||||||
|
if (modules == null || javaTypes == null) {
|
||||||
|
Log.Warning ($"{Description}: cannot convert maps, no data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string filePath = ELF.FilePath;
|
||||||
|
var managedToJava = new List<MapEntry> ();
|
||||||
|
uint index = 0;
|
||||||
|
|
||||||
|
bool somethingFailed = false;
|
||||||
|
foreach (TypeMapModule m in modules) {
|
||||||
|
ConvertManagedMap (m, EnsureMap (m), isDuplicate: false);
|
||||||
|
if (m.duplicate_map != null) {
|
||||||
|
if (!ConvertManagedMap (m, m.duplicate_map, isDuplicate: true)) {
|
||||||
|
somethingFailed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (somethingFailed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
var javaToManaged = new List<MapEntry> ();
|
||||||
|
foreach (TypeMapJava tmj in javaTypes) {
|
||||||
|
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType (tmj.module_index, tmj.type_token_id);
|
||||||
|
if (!success) {
|
||||||
|
somethingFailed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module == null) {
|
||||||
|
throw new InvalidOperationException ("module must not be null here");
|
||||||
|
}
|
||||||
|
|
||||||
|
javaToManaged.Add (
|
||||||
|
new MapEntry (
|
||||||
|
MakeManagedType (module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
|
||||||
|
new MapJavaType (tmj.java_name, filePath)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
map = MakeMap (managedToJava, javaToManaged);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool ConvertManagedMap (TypeMapModule module, List<TypeMapModuleEntry> map, bool isDuplicate)
|
||||||
|
{
|
||||||
|
foreach (TypeMapModuleEntry entry in map) {
|
||||||
|
TypeMapJava java;
|
||||||
|
|
||||||
|
if ((uint)javaTypes.Count <= entry.java_map_index) {
|
||||||
|
Log.Error ($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
java = javaTypes[(int)entry.java_map_index];
|
||||||
|
managedToJava.Add (
|
||||||
|
new MapEntry (
|
||||||
|
MakeManagedType (module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
|
||||||
|
new MapJavaType (java.java_name, filePath)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) FindManagedType (uint moduleIndex, uint tokenID)
|
||||||
|
{
|
||||||
|
if (moduleIndex >= (uint)modules.Count) {
|
||||||
|
Log.Error ($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
|
||||||
|
return (false, null, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeMapModule m = modules[(int)moduleIndex];
|
||||||
|
if (tokenID == 0) {
|
||||||
|
return (true, m, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TypeMapModuleEntry entry in EnsureMap (m)) {
|
||||||
|
if (entry.type_token_id == tokenID) {
|
||||||
|
return (true, m, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.duplicate_map != null) {
|
||||||
|
foreach (TypeMapModuleEntry entry in m.duplicate_map) {
|
||||||
|
if (entry.type_token_id == tokenID) {
|
||||||
|
return (true, m, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Error ($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
|
||||||
|
return (false, null, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeMapModuleEntry> EnsureMap (TypeMapModule m)
|
||||||
|
{
|
||||||
|
if (m.map == null)
|
||||||
|
throw new InvalidOperationException ($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
|
||||||
|
return m.map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeMapJava> LoadJavaTypes (string filePath)
|
||||||
|
{
|
||||||
|
ulong javaTypeCount = (ulong)ELF.GetUInt32 (JavaTypeCountSymbolName);
|
||||||
|
ulong javaNameWidth = (ulong)ELF.GetUInt32 (JavaNameWidthSymbolName);
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<uint> (size); // module_index
|
||||||
|
size += GetPaddedSize<uint> (size); // type_token_id
|
||||||
|
size += javaNameWidth;
|
||||||
|
|
||||||
|
(byte[] data, _) = ELF.GetData (MapJavaSymbolName);
|
||||||
|
if (data.Length == 0)
|
||||||
|
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaSymbolName}' symbol");
|
||||||
|
|
||||||
|
ulong calculatedJavaTypeCount = (ulong)data.LongLength / size;
|
||||||
|
if (calculatedJavaTypeCount != javaTypeCount)
|
||||||
|
throw new InvalidOperationException ($"{filePath} has invalid '{JavaTypeCountSymbolName}' symbol value ({javaTypeCount}), '{JavaTypeCountSymbolName}' size indicates there are {calculatedJavaTypeCount} managedToJava instead");
|
||||||
|
|
||||||
|
var ret = new List<TypeMapJava> ();
|
||||||
|
ulong offset = 0;
|
||||||
|
for (ulong i = 0; i < javaTypeCount; i++) {
|
||||||
|
var javaEntry = new TypeMapJava {
|
||||||
|
module_index = ReadUInt32 (data, ref offset, packed: true),
|
||||||
|
type_token_id = ReadUInt32 (data, ref offset, packed: true),
|
||||||
|
};
|
||||||
|
|
||||||
|
javaEntry.java_name = ELF.GetASCIIZ (data, offset);
|
||||||
|
offset += javaNameWidth;
|
||||||
|
ret.Add (javaEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeMapModule> LoadMapModules (string filePath)
|
||||||
|
{
|
||||||
|
ulong moduleCount = (ulong)ELF.GetUInt32 (ModuleCountSymbolName);
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
||||||
|
ulong size;
|
||||||
|
|
||||||
|
size = 16; // module_uuid
|
||||||
|
size += GetPaddedSize<uint> (size); // entry_count
|
||||||
|
size += GetPaddedSize<uint> (size); // duplicate_count
|
||||||
|
size += GetPaddedSize<string> (size); // map (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // duplicate_map (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // assembly_name (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // image (pointer)
|
||||||
|
size += GetPaddedSize<uint> (size); // java_name_width
|
||||||
|
size += GetPaddedSize<string> (size); // java_map (pointer)
|
||||||
|
|
||||||
|
(byte[] moduleData, _) = ELF.GetData (MapModulesSymbolName);
|
||||||
|
if (moduleData.Length == 0)
|
||||||
|
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
|
||||||
|
|
||||||
|
ulong calculatedModuleCount = (ulong)moduleData.Length / size;
|
||||||
|
if (calculatedModuleCount != moduleCount)
|
||||||
|
throw new InvalidOperationException ($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
|
||||||
|
|
||||||
|
var ret = new List<TypeMapModule> ();
|
||||||
|
ulong offset = 0;
|
||||||
|
for (ulong i = 0; i < moduleCount; i++) {
|
||||||
|
Log.Debug ($"Module {i + 1}");
|
||||||
|
var module = new TypeMapModule ();
|
||||||
|
|
||||||
|
byte[] mvid = new byte[16];
|
||||||
|
Array.Copy (moduleData, (int)offset, mvid, 0, mvid.Length);
|
||||||
|
module.module_uuid = new Guid (mvid);
|
||||||
|
offset += (ulong)mvid.Length;
|
||||||
|
Log.Debug ($" module_uuid == {module.module_uuid}");
|
||||||
|
|
||||||
|
module.entry_count = ReadUInt32 (moduleData, ref offset);
|
||||||
|
Log.Debug ($" entry_count == {module.entry_count}");
|
||||||
|
|
||||||
|
module.duplicate_count = ReadUInt32 (moduleData, ref offset);
|
||||||
|
Log.Debug ($" duplicate_count == {module.duplicate_count}");
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
||||||
|
ulong pointer = ReadPointer (moduleData, ref offset);
|
||||||
|
size = 0;
|
||||||
|
size += GetPaddedSize<uint> (size); // type_token_id
|
||||||
|
size += GetPaddedSize<uint> (size); // java_map_index
|
||||||
|
|
||||||
|
ulong mapSize = size * module.entry_count;
|
||||||
|
byte[] data = ELF.GetData (pointer, mapSize);
|
||||||
|
|
||||||
|
module.map = new List<TypeMapModuleEntry> ();
|
||||||
|
ReadMapEntries (module.map, data, module.entry_count);
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
||||||
|
pointer = ReadPointer (moduleData, ref offset);
|
||||||
|
if (pointer != 0) {
|
||||||
|
mapSize = size * module.duplicate_count;
|
||||||
|
data = ELF.GetData (pointer, mapSize);
|
||||||
|
module.duplicate_map = new List<TypeMapModuleEntry> ();
|
||||||
|
ReadMapEntries (module.duplicate_map, data, module.duplicate_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer = ReadPointer (moduleData, ref offset);
|
||||||
|
module.assembly_name = ELF.GetASCIIZ (pointer);
|
||||||
|
Log.Debug ($" assembly_name == {module.assembly_name}");
|
||||||
|
Log.Debug ("");
|
||||||
|
|
||||||
|
// Read the values to properly adjust the offset taking padding into account
|
||||||
|
ReadPointer (moduleData, ref offset);
|
||||||
|
ReadUInt32 (moduleData, ref offset);
|
||||||
|
ReadPointer (moduleData, ref offset);
|
||||||
|
|
||||||
|
ret.Add (module);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
void ReadMapEntries (List<TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
|
||||||
|
{
|
||||||
|
ulong mapOffset = 0;
|
||||||
|
for (uint i = 0; i < entryCount; i++) {
|
||||||
|
var entry = new TypeMapModuleEntry {
|
||||||
|
type_token_id = ReadUInt32 (inputData, ref mapOffset),
|
||||||
|
java_map_index = ReadUInt32 (inputData, ref mapOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
map.Add (entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,540 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Hashing;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using ELFSharp.ELF.Sections;
|
||||||
|
using Xamarin.Android.Tasks;
|
||||||
|
|
||||||
|
namespace tmt;
|
||||||
|
|
||||||
|
class XamarinAppReleaseDSO_V2 : XamarinAppReleaseDSO_Version
|
||||||
|
{
|
||||||
|
protected override string LogTag => "ReleaseDSO_V2";
|
||||||
|
|
||||||
|
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
||||||
|
sealed class TypeMapModuleEntry
|
||||||
|
{
|
||||||
|
public uint type_token_id;
|
||||||
|
public uint java_map_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
||||||
|
sealed class TypeMapModule
|
||||||
|
{
|
||||||
|
public Guid module_uuid;
|
||||||
|
public uint entry_count;
|
||||||
|
public uint duplicate_count;
|
||||||
|
public List<TypeMapModuleEntry>? map;
|
||||||
|
public List<TypeMapModuleEntry>? duplicate_map;
|
||||||
|
public string assembly_name = String.Empty;
|
||||||
|
|
||||||
|
// These three aren't used, listed for completeness
|
||||||
|
public readonly object? image = null;
|
||||||
|
public readonly uint java_name_width = 0;
|
||||||
|
public readonly byte[]? java_map = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field names correspond to: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
|
||||||
|
sealed class TypeMapJava
|
||||||
|
{
|
||||||
|
public uint module_index;
|
||||||
|
public uint type_token_id;
|
||||||
|
public uint java_name_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string MapModulesSymbolName = "map_modules";
|
||||||
|
const string ModuleCountSymbolName = "map_module_count";
|
||||||
|
const string JavaTypeCountSymbolName = "java_type_count";
|
||||||
|
const string JavaTypeNamesSymbolName = "java_type_names";
|
||||||
|
const string MapJavaSymbolName = "map_java";
|
||||||
|
const string MapJavaHashesSymbolName = "map_java_hashes";
|
||||||
|
|
||||||
|
Map? map;
|
||||||
|
List<TypeMapModule>? modules;
|
||||||
|
List<TypeMapJava>? javaTypes;
|
||||||
|
List<string>? javaTypeNames;
|
||||||
|
List<ulong>? javaTypeNameHashes;
|
||||||
|
|
||||||
|
public override string FormatVersion => "2";
|
||||||
|
public override Map Map => map ?? throw new InvalidOperationException ("Data hasn't been loaded yet");
|
||||||
|
|
||||||
|
public XamarinAppReleaseDSO_V2 (ManagedTypeResolver managedResolver, AnELF elf)
|
||||||
|
: base (managedResolver, elf)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public override bool CanLoad (AnELF elf)
|
||||||
|
{
|
||||||
|
return HasSymbol (elf, MapModulesSymbolName) &&
|
||||||
|
HasSymbol (elf, ModuleCountSymbolName) &&
|
||||||
|
HasSymbol (elf, JavaTypeCountSymbolName) &&
|
||||||
|
HasSymbol (elf, JavaTypeNamesSymbolName) &&
|
||||||
|
HasSymbol (elf, MapJavaSymbolName) &&
|
||||||
|
HasSymbol (elf, MapJavaHashesSymbolName);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool LoadMaps ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
string filePath = ELF.FilePath;
|
||||||
|
modules = LoadMapModules (filePath);
|
||||||
|
|
||||||
|
// Order in which the entries are loaded is important. Farther loads use data gathered in the preceding ones
|
||||||
|
javaTypes = LoadJavaTypes (filePath);
|
||||||
|
javaTypeNameHashes = LoadJavaTypeNameHashes (filePath);
|
||||||
|
javaTypeNames = LoadJavaTypeNames (filePath);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to load maps", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SaveRaw (string baseOutputFilePath, string extension)
|
||||||
|
{
|
||||||
|
const string indent = "\t";
|
||||||
|
|
||||||
|
if (modules == null || javaTypes == null) {
|
||||||
|
Log.Warning ($"{Description}: cannot save raw report, no data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string outputFilePath = Utilities.GetManagedOutputFileName (baseOutputFilePath, extension);
|
||||||
|
Utilities.CreateFileDirectory (outputFilePath);
|
||||||
|
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
|
||||||
|
sw.WriteLine ("TYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_MAP_INDEX");
|
||||||
|
uint index = 0;
|
||||||
|
foreach (TypeMapModule module in modules) {
|
||||||
|
sw.WriteLine ();
|
||||||
|
sw.WriteLine ($"Module {index++:D04}: {module.assembly_name} (MVID: {module.module_uuid}; entries: {module.entry_count}; duplicates: {module.duplicate_count})");
|
||||||
|
if (module.map == null) {
|
||||||
|
sw.WriteLine ($"{indent}no map");
|
||||||
|
} else {
|
||||||
|
WriteManagedMap ("map", module.map, sw);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module.duplicate_map == null) {
|
||||||
|
if (module.duplicate_count > 0)
|
||||||
|
sw.WriteLine ($"{indent}no duplicate map, but there should be {module.duplicate_count} entries");
|
||||||
|
else
|
||||||
|
sw.WriteLine ($"{indent}no duplicates");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteManagedMap ("duplicate map", module.duplicate_map, sw);
|
||||||
|
}
|
||||||
|
sw.Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
outputFilePath = Utilities.GetJavaOutputFileName (baseOutputFilePath, extension);
|
||||||
|
using (var sw = new StreamWriter (outputFilePath, false, new UTF8Encoding (false))) {
|
||||||
|
sw.WriteLine ("MANAGED_MODULE_INDEX\tTYPE_TOKEN_DECIMAL (TYPE_TOKEN_HEXADECIMAL)\tJAVA_TYPE_NAME");
|
||||||
|
|
||||||
|
foreach (TypeMapJava tmj in javaTypes) {
|
||||||
|
sw.WriteLine ($"{indent}{tmj.module_index}\t{tmj.type_token_id:D08} ({tmj.type_token_id:X08})\t{GetJavaTypeName (tmj)}");
|
||||||
|
}
|
||||||
|
sw.Flush ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteManagedMap (string name, List<TypeMapModuleEntry> map, StreamWriter sw)
|
||||||
|
{
|
||||||
|
sw.WriteLine ($"{indent}{name}:");
|
||||||
|
foreach (TypeMapModuleEntry entry in map) {
|
||||||
|
sw.WriteLine ($"{indent}{indent}{entry.type_token_id} ({entry.type_token_id:X08})\t{entry.java_map_index}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MapManagedType MakeManagedType (Guid mvid, uint tokenID, string assemblyName, string filePath, bool isGeneric, bool isDuplicate)
|
||||||
|
{
|
||||||
|
return new MapManagedType (mvid, tokenID, assemblyName, filePath) {
|
||||||
|
IsGeneric = isGeneric,
|
||||||
|
IsDuplicate = isDuplicate,
|
||||||
|
TypeName = ManagedResolver.Lookup (assemblyName, mvid, tokenID)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool Convert ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DoConvert ();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.ExceptionError ($"{Description}: failed to convert loaded maps to common format", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoConvert ()
|
||||||
|
{
|
||||||
|
const string Present = "present";
|
||||||
|
const string Absent = "absent";
|
||||||
|
|
||||||
|
if (modules == null || javaTypes == null || javaTypeNames == null) {
|
||||||
|
Log.Warning (LogTag, $"cannot convert maps, missing data:");
|
||||||
|
Log.Warning ($" {MapModulesSymbolName}: {(modules == null ? Absent : Present)}");
|
||||||
|
Log.Warning ($" {MapJavaSymbolName}: {(javaTypes == null ? Absent : Present)}");
|
||||||
|
Log.Warning ($" {JavaTypeNamesSymbolName}: {(javaTypeNames == null ? Absent : Present)}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (javaTypes.Count != javaTypeNames.Count) {
|
||||||
|
Log.Warning (LogTag, $"Java types map has a different number of entries ({javaTypes.Count}) than the Java type names array ({javaTypeNames.Count})");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string filePath = ELF.FilePath;
|
||||||
|
var managedToJava = new List<MapEntry> ();
|
||||||
|
uint index = 0;
|
||||||
|
|
||||||
|
bool somethingFailed = false;
|
||||||
|
foreach (TypeMapModule m in modules) {
|
||||||
|
ConvertManagedMap (m, EnsureMap (m), isDuplicate: false);
|
||||||
|
if (m.duplicate_map != null) {
|
||||||
|
if (!ConvertManagedMap (m, m.duplicate_map, isDuplicate: true)) {
|
||||||
|
somethingFailed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (somethingFailed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = 0;
|
||||||
|
var javaToManaged = new List<MapEntry> ();
|
||||||
|
foreach (TypeMapJava tmj in javaTypes) {
|
||||||
|
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) = FindManagedType (tmj.module_index, tmj.type_token_id);
|
||||||
|
if (!success) {
|
||||||
|
somethingFailed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (module == null) {
|
||||||
|
throw new InvalidOperationException ("module must not be null here");
|
||||||
|
}
|
||||||
|
|
||||||
|
javaToManaged.Add (
|
||||||
|
new MapEntry (
|
||||||
|
MakeManagedType (module.module_uuid, tmj.type_token_id, module.assembly_name, filePath, isGeneric, isDuplicate),
|
||||||
|
new MapJavaType (GetJavaTypeName (tmj), filePath)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
map = MakeMap (managedToJava, javaToManaged);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool ConvertManagedMap (TypeMapModule module, List<TypeMapModuleEntry> map, bool isDuplicate)
|
||||||
|
{
|
||||||
|
foreach (TypeMapModuleEntry entry in map) {
|
||||||
|
TypeMapJava java;
|
||||||
|
|
||||||
|
if ((uint)javaTypes.Count <= entry.java_map_index) {
|
||||||
|
Log.Error ($"Managed type {entry.type_token_id} in module {module.assembly_name} ({module.module_uuid}) has invalid Java map index {entry.java_map_index}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
java = javaTypes[(int)entry.java_map_index];
|
||||||
|
managedToJava.Add (
|
||||||
|
new MapEntry (
|
||||||
|
MakeManagedType (module.module_uuid, entry.type_token_id, module.assembly_name, filePath, isGeneric: false, isDuplicate: isDuplicate),
|
||||||
|
new MapJavaType (GetJavaTypeName (java), filePath)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
(bool success, TypeMapModule? module, bool isGeneric, bool isDuplicate) FindManagedType (uint moduleIndex, uint tokenID)
|
||||||
|
{
|
||||||
|
if (moduleIndex >= (uint)modules.Count) {
|
||||||
|
Log.Error ($"Invalid module index {moduleIndex} for type token ID {tokenID} at Java map index {index}");
|
||||||
|
return (false, null, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeMapModule m = modules[(int)moduleIndex];
|
||||||
|
if (tokenID == 0) {
|
||||||
|
return (true, m, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TypeMapModuleEntry entry in EnsureMap (m)) {
|
||||||
|
if (entry.type_token_id == tokenID) {
|
||||||
|
return (true, m, false, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.duplicate_map != null) {
|
||||||
|
foreach (TypeMapModuleEntry entry in m.duplicate_map) {
|
||||||
|
if (entry.type_token_id == tokenID) {
|
||||||
|
return (true, m, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Error ($"Module {m.assembly_name} ({m.module_uuid}) at index {moduleIndex} doesn't contain an entry for managed type with token ID {tokenID}");
|
||||||
|
return (false, null, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeMapModuleEntry> EnsureMap (TypeMapModule m)
|
||||||
|
{
|
||||||
|
if (m.map == null) {
|
||||||
|
throw new InvalidOperationException ($"Module {m.module_uuid} ({m.assembly_name}) has no map?");
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ulong> LoadJavaTypeNameHashes (string filePath)
|
||||||
|
{
|
||||||
|
Log.Debug ();
|
||||||
|
Log.Debug (LogTag, "Reading Java type name hashes");
|
||||||
|
|
||||||
|
ulong size = 0;
|
||||||
|
if (Is64Bit) {
|
||||||
|
size += GetPaddedSize<ulong> (size); // hashes are 64-bit
|
||||||
|
} else {
|
||||||
|
size += GetPaddedSize<uint> (size); // hashes are 32-bit
|
||||||
|
}
|
||||||
|
|
||||||
|
(byte[] hashesData, ISymbolEntry? symbol) = ELF.GetData (MapJavaHashesSymbolName);
|
||||||
|
if (hashesData.Length == 0 || symbol == null) {
|
||||||
|
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaHashesSymbolName}' symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = new List<ulong> ();
|
||||||
|
ulong offset = 0;
|
||||||
|
for (ulong i = 0; i < (ulong)hashesData.Length / size; i++) {
|
||||||
|
ulong hash;
|
||||||
|
if (Is64Bit) {
|
||||||
|
hash = ReadUInt64 (hashesData, ref offset);
|
||||||
|
} else {
|
||||||
|
hash = (ulong)ReadUInt32 (hashesData, ref offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug (LogTag, $" [{i}] 0x{HashToHexString(hash)}");
|
||||||
|
ret.Add (hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ();
|
||||||
|
Log.Debug (LogTag, $"Java type name hashes loaded (count: {ret.Count})");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<string> LoadJavaTypeNames (string filePath)
|
||||||
|
{
|
||||||
|
Log.Debug ();
|
||||||
|
Log.Debug (LogTag, "Reading Java type names");
|
||||||
|
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<string> (size); // pointers
|
||||||
|
|
||||||
|
(byte[] namesData, ISymbolEntry? symbol) = ELF.GetData (JavaTypeNamesSymbolName);
|
||||||
|
if (namesData.Length == 0 || symbol == null) {
|
||||||
|
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{JavaTypeNamesSymbolName}' symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = new List<string> ();
|
||||||
|
ulong offset = 0;
|
||||||
|
for (ulong i = 0; i < (ulong)namesData.Length / size; i++) {
|
||||||
|
ulong pointer = ReadPointer (symbol, namesData, ref offset);
|
||||||
|
string? name;
|
||||||
|
|
||||||
|
if (pointer != 0) {
|
||||||
|
name = ELF.GetASCIIZFromPointer (pointer);
|
||||||
|
} else {
|
||||||
|
name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Add (name ?? String.Empty);
|
||||||
|
|
||||||
|
ulong hash = TypeMapHelper.HashJavaName (name ?? String.Empty, Is64Bit);
|
||||||
|
int javaTypeIndex = javaTypeNameHashes!.IndexOf (hash);
|
||||||
|
|
||||||
|
if (javaTypeIndex < 0) {
|
||||||
|
Log.Warning (LogTag, $"Hash 0x{HashToHexString(hash)} for Java type name '{name}' not found in the '{MapJavaHashesSymbolName}' array");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug (LogTag, $" [{i}] {(name ?? String.Empty)} (hash: 0x{HashToHexString(hash)})");
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ();
|
||||||
|
Log.Debug (LogTag, $"Java type names loaded (count: {ret.Count})");
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeMapJava> LoadJavaTypes (string filePath)
|
||||||
|
{
|
||||||
|
Log.Debug ();
|
||||||
|
Log.Debug (LogTag, "Reading Java types");
|
||||||
|
|
||||||
|
ulong javaTypeCount = (ulong)ELF.GetUInt32 (JavaTypeCountSymbolName);
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapJava)
|
||||||
|
ulong size = 0;
|
||||||
|
size += GetPaddedSize<uint> (size); // module_index
|
||||||
|
size += GetPaddedSize<uint> (size); // type_token_id
|
||||||
|
size += GetPaddedSize<uint> (size); // java_name_index
|
||||||
|
|
||||||
|
(byte[] data, ISymbolEntry? symbol) = ELF.GetData (MapJavaSymbolName);
|
||||||
|
if (data.Length == 0) {
|
||||||
|
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapJavaSymbolName}' symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong calculatedJavaTypeCount = (ulong)data.LongLength / size;
|
||||||
|
if (calculatedJavaTypeCount != javaTypeCount)
|
||||||
|
throw new InvalidOperationException ($"{filePath} has invalid '{JavaTypeCountSymbolName}' symbol value ({javaTypeCount}), '{JavaTypeCountSymbolName}' size indicates there are {calculatedJavaTypeCount} managedToJava instead");
|
||||||
|
|
||||||
|
var ret = new List<TypeMapJava> ();
|
||||||
|
ulong offset = 0;
|
||||||
|
for (ulong i = 0; i < javaTypeCount; i++) {
|
||||||
|
var javaEntry = new TypeMapJava {
|
||||||
|
module_index = ReadUInt32 (data, ref offset, packed: true),
|
||||||
|
type_token_id = ReadUInt32 (data, ref offset, packed: true),
|
||||||
|
java_name_index = ReadUInt32 (data, ref offset, packed: true),
|
||||||
|
};
|
||||||
|
|
||||||
|
ret.Add (javaEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug (LogTag, $"Java types loaded (count: {ret.Count})");
|
||||||
|
Log.Debug ();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TypeMapModule> LoadMapModules (string filePath)
|
||||||
|
{
|
||||||
|
Log.Debug ();
|
||||||
|
Log.Debug (LogTag, "Reading map modules");
|
||||||
|
|
||||||
|
ulong moduleCount = (ulong)ELF.GetUInt32 (ModuleCountSymbolName);
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModule)
|
||||||
|
ulong size;
|
||||||
|
|
||||||
|
size = 16; // module_uuid
|
||||||
|
size += GetPaddedSize<uint> (size); // entry_count
|
||||||
|
size += GetPaddedSize<uint> (size); // duplicate_count
|
||||||
|
size += GetPaddedSize<string> (size); // map (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // duplicate_map (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // assembly_name (pointer)
|
||||||
|
size += GetPaddedSize<string> (size); // image (pointer)
|
||||||
|
size += GetPaddedSize<uint> (size); // java_name_width
|
||||||
|
size += GetPaddedSize<string> (size); // java_map (pointer)
|
||||||
|
|
||||||
|
(byte[] moduleData, ISymbolEntry? symbol) = ELF.GetData (MapModulesSymbolName);
|
||||||
|
if (moduleData.Length == 0 || symbol == null) {
|
||||||
|
throw new InvalidOperationException ($"{filePath} doesn't have a valid '{MapModulesSymbolName}' symbol");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong calculatedModuleCount = (ulong)moduleData.Length / size;
|
||||||
|
if (calculatedModuleCount != moduleCount) {
|
||||||
|
throw new InvalidOperationException ($"{filePath} has invalid '{ModuleCountSymbolName}' symbol value ({moduleCount}), '{MapModulesSymbolName}' size indicates there are {calculatedModuleCount} managedToJava instead");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret = new List<TypeMapModule> ();
|
||||||
|
ulong offset = 0;
|
||||||
|
for (ulong i = 0; i < moduleCount; i++) {
|
||||||
|
Log.Debug ($"Module {i + 1}");
|
||||||
|
var module = new TypeMapModule ();
|
||||||
|
|
||||||
|
byte[] mvid = new byte[16];
|
||||||
|
Array.Copy (moduleData, (int)offset, mvid, 0, mvid.Length);
|
||||||
|
module.module_uuid = new Guid (mvid);
|
||||||
|
offset += (ulong)mvid.Length;
|
||||||
|
Log.Debug (LogTag, $" module_uuid == {module.module_uuid} (offset: {offset})");
|
||||||
|
|
||||||
|
module.entry_count = ReadUInt32 (moduleData, ref offset);
|
||||||
|
Log.Debug (LogTag, $" entry_count == {module.entry_count} (offset: {offset})");
|
||||||
|
|
||||||
|
module.duplicate_count = ReadUInt32 (moduleData, ref offset);
|
||||||
|
Log.Debug (LogTag, $" duplicate_count == {module.duplicate_count} (offset: {offset})");
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
||||||
|
ulong pointer = ReadPointer (symbol, moduleData, ref offset);
|
||||||
|
Log.Debug (LogTag, $" *map == 0x{pointer:x} (offset: {offset})");
|
||||||
|
|
||||||
|
if (pointer == 0) {
|
||||||
|
throw new InvalidOperationException ($"Broken typemap structure, map pointer for module {module.module_uuid} is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
size = 0;
|
||||||
|
size += GetPaddedSize<uint> (size); // type_token_id
|
||||||
|
size += GetPaddedSize<uint> (size); // java_map_index
|
||||||
|
|
||||||
|
ulong mapSize = size * module.entry_count;
|
||||||
|
byte[] data = ELF.GetDataFromPointer (pointer, mapSize);
|
||||||
|
|
||||||
|
module.map = new List<TypeMapModuleEntry> ();
|
||||||
|
ReadMapEntries (module.map, data, module.entry_count);
|
||||||
|
|
||||||
|
// MUST be kept in sync with: src/monodroid/jni/xamarin-app.hh (struct TypeMapModuleEntry)
|
||||||
|
pointer = ReadPointer (symbol, moduleData, ref offset);
|
||||||
|
|
||||||
|
if (pointer != 0) {
|
||||||
|
mapSize = size * module.duplicate_count;
|
||||||
|
data = ELF.GetDataFromPointer (pointer, mapSize);
|
||||||
|
module.duplicate_map = new List<TypeMapModuleEntry> ();
|
||||||
|
ReadMapEntries (module.duplicate_map, data, module.duplicate_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer = ReadPointer (symbol, moduleData, ref offset);
|
||||||
|
if (pointer != 0) {
|
||||||
|
module.assembly_name = ELF.GetASCIIZFromPointer (pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug ($" assembly_name == {module.assembly_name}");
|
||||||
|
Log.Debug ("");
|
||||||
|
|
||||||
|
// Read the values to properly adjust the offset taking padding into account
|
||||||
|
ReadPointer (moduleData, ref offset);
|
||||||
|
ReadUInt32 (moduleData, ref offset);
|
||||||
|
ReadPointer (moduleData, ref offset);
|
||||||
|
|
||||||
|
ret.Add (module);
|
||||||
|
|
||||||
|
// Padding
|
||||||
|
offset += offset % size;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Debug (LogTag, $"Map modules loaded (count: {ret.Count})");
|
||||||
|
Log.Debug ();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
void ReadMapEntries (List<TypeMapModuleEntry> map, byte[] inputData, uint entryCount)
|
||||||
|
{
|
||||||
|
ulong mapOffset = 0;
|
||||||
|
for (uint i = 0; i < entryCount; i++) {
|
||||||
|
var entry = new TypeMapModuleEntry {
|
||||||
|
type_token_id = ReadUInt32 (inputData, ref mapOffset),
|
||||||
|
java_map_index = ReadUInt32 (inputData, ref mapOffset)
|
||||||
|
};
|
||||||
|
|
||||||
|
map.Add (entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetJavaTypeName (TypeMapJava tmj)
|
||||||
|
{
|
||||||
|
if (javaTypeNames == null) {
|
||||||
|
return "<no Java type names data>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmj.java_name_index >= javaTypeNames.Count) {
|
||||||
|
return $"<invalid name index {tmj.java_name_index}>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return javaTypeNames[(int)tmj.java_name_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
string HashToHexString (ulong hash) => Is64Bit ? $"{hash:X16}" : $"{hash:X8}";
|
||||||
|
}
|
|
@ -150,7 +150,7 @@ namespace tmt
|
||||||
var loader = new Loader (parsedOptions.ArchFilter, parsedOptions.LoadOnlyFirst.Value);
|
var loader = new Loader (parsedOptions.ArchFilter, parsedOptions.LoadOnlyFirst.Value);
|
||||||
List<ITypemap> typemaps = loader.TryLoad (loadFrom);
|
List<ITypemap> typemaps = loader.TryLoad (loadFrom);
|
||||||
if (typemaps.Count == 0) {
|
if (typemaps.Count == 0) {
|
||||||
Log.Info ($"No type maps found in '{loadFrom}");
|
Log.Error ($"No supported type maps found in '{loadFrom}");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,15 @@
|
||||||
<RollForward>Major</RollForward>
|
<RollForward>Major</RollForward>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="../../src/Xamarin.Android.Build.Tasks/Utilities/TypeMapHelper.cs" />
|
||||||
|
<Compile Include="..\assembly-store-reader\AssemblyStore*.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" />
|
<PackageReference Include="Mono.Options" Version="$(MonoOptionsVersion)" />
|
||||||
<PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" />
|
<PackageReference Include="Mono.Cecil" Version="$(MonoCecilVersion)" />
|
||||||
|
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
|
||||||
<PackageReference Include="Xamarin.LibZipSharp" Version="$(LibZipSharpVersion)" />
|
<PackageReference Include="Xamarin.LibZipSharp" Version="$(LibZipSharpVersion)" />
|
||||||
<PackageReference Include="ELFSharp" Version="$(ELFSharpVersion)" />
|
<PackageReference Include="ELFSharp" Version="$(ELFSharpVersion)" />
|
||||||
<PackageReference Include="K4os.Compression.LZ4" Version="$(LZ4PackageVersion)" />
|
<PackageReference Include="K4os.Compression.LZ4" Version="$(LZ4PackageVersion)" />
|
||||||
|
|
Загрузка…
Ссылка в новой задаче