Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a plugin for outputting Windows PDB files #382

Merged
merged 1 commit into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 50 additions & 38 deletions Cpp2IL.Core/Il2CppApiFunctions/BaseKeyFunctionAddresses.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Cpp2IL.Core.Logging;
using Cpp2IL.Core.Model.Contexts;
using Cpp2IL.Core.Utils;
Expand Down Expand Up @@ -49,13 +50,16 @@ public abstract class BaseKeyFunctionAddresses

public ulong AddrPInvokeLookup; //TODO Re-find this and fix name

public IEnumerable<KeyValuePair<string, ulong>> Pairs => resolvedAddressMap;

private ApplicationAnalysisContext _appContext = null!; //Always initialized before used

private readonly HashSet<ulong> resolvedAddresses = [];
private readonly Dictionary<string, ulong> resolvedAddressMap = [];
private readonly HashSet<ulong> resolvedAddressSet = [];

public bool IsKeyFunctionAddress(ulong address)
{
return address != 0 && resolvedAddresses.Contains(address);
return address != 0 && resolvedAddressSet.Contains(address);
}

private void FindExport(string name, out ulong ptr)
Expand Down Expand Up @@ -294,41 +298,49 @@ protected virtual void Init(ApplicationAnalysisContext context)

private void InitializeResolvedAddresses()
{
resolvedAddresses.Clear();
resolvedAddresses.Add(il2cpp_codegen_initialize_method);
resolvedAddresses.Add(il2cpp_codegen_initialize_runtime_metadata);
resolvedAddresses.Add(il2cpp_vm_metadatacache_initializemethodmetadata);
resolvedAddresses.Add(il2cpp_runtime_class_init_export);
resolvedAddresses.Add(il2cpp_runtime_class_init_actual);
resolvedAddresses.Add(il2cpp_object_new);
resolvedAddresses.Add(il2cpp_vm_object_new);
resolvedAddresses.Add(il2cpp_codegen_object_new);
resolvedAddresses.Add(il2cpp_array_new_specific);
resolvedAddresses.Add(il2cpp_vm_array_new_specific);
resolvedAddresses.Add(SzArrayNew);
resolvedAddresses.Add(il2cpp_type_get_object);
resolvedAddresses.Add(il2cpp_vm_reflection_get_type_object);
resolvedAddresses.Add(il2cpp_resolve_icall);
resolvedAddresses.Add(InternalCalls_Resolve);

resolvedAddresses.Add(il2cpp_string_new);
resolvedAddresses.Add(il2cpp_vm_string_new);
resolvedAddresses.Add(il2cpp_string_new_wrapper);
resolvedAddresses.Add(il2cpp_vm_string_newWrapper);
resolvedAddresses.Add(il2cpp_codegen_string_new_wrapper);

resolvedAddresses.Add(il2cpp_value_box);
resolvedAddresses.Add(il2cpp_vm_object_box);

resolvedAddresses.Add(il2cpp_object_unbox);
resolvedAddresses.Add(il2cpp_vm_object_unbox);

resolvedAddresses.Add(il2cpp_raise_exception);
resolvedAddresses.Add(il2cpp_vm_exception_raise);
resolvedAddresses.Add(il2cpp_codegen_raise_exception);

resolvedAddresses.Add(il2cpp_vm_object_is_inst);

resolvedAddresses.Add(AddrPInvokeLookup);
resolvedAddressMap.Clear();
resolvedAddressSet.Clear();

AddResolved(il2cpp_codegen_initialize_method);
AddResolved(il2cpp_codegen_initialize_runtime_metadata);
AddResolved(il2cpp_vm_metadatacache_initializemethodmetadata);
AddResolved(il2cpp_runtime_class_init_export);
AddResolved(il2cpp_runtime_class_init_actual);
AddResolved(il2cpp_object_new);
AddResolved(il2cpp_vm_object_new);
AddResolved(il2cpp_codegen_object_new);
AddResolved(il2cpp_array_new_specific);
AddResolved(il2cpp_vm_array_new_specific);
AddResolved(SzArrayNew);
AddResolved(il2cpp_type_get_object);
AddResolved(il2cpp_vm_reflection_get_type_object);
AddResolved(il2cpp_resolve_icall);
AddResolved(InternalCalls_Resolve);

AddResolved(il2cpp_string_new);
AddResolved(il2cpp_vm_string_new);
AddResolved(il2cpp_string_new_wrapper);
AddResolved(il2cpp_vm_string_newWrapper);
AddResolved(il2cpp_codegen_string_new_wrapper);

AddResolved(il2cpp_value_box);
AddResolved(il2cpp_vm_object_box);

AddResolved(il2cpp_object_unbox);
AddResolved(il2cpp_vm_object_unbox);

AddResolved(il2cpp_raise_exception);
AddResolved(il2cpp_vm_exception_raise);
AddResolved(il2cpp_codegen_raise_exception);

AddResolved(il2cpp_vm_object_is_inst);

AddResolved(AddrPInvokeLookup);

void AddResolved(ulong address, [CallerArgumentExpression(nameof(address))] string name = "")
{
resolvedAddressSet.Add(address);
resolvedAddressMap[name] = address;
}
}
}
7 changes: 6 additions & 1 deletion Cpp2IL.Core/Model/Contexts/MethodAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using LibCpp2IL;
using LibCpp2IL.Metadata;
using StableNameDotNet.Providers;
using System.Linq;

namespace Cpp2IL.Core.Model.Contexts;

Expand Down Expand Up @@ -67,6 +68,10 @@ public class MethodAnalysisContext : HasCustomAttributesAndName, IMethodInfoProv

public override string DefaultName => Definition?.Name ?? throw new("Subclasses of MethodAnalysisContext should override DefaultName");

public string FullName => DeclaringType == null ? Name : $"{DeclaringType.FullName}::{Name}";

public string FullNameWithSignature => $"{ReturnTypeContext.FullName} {FullName}({string.Join(", ", Parameters.Select(p => p.HumanReadableSignature))})";

public virtual MethodAttributes Attributes => Definition?.Attributes ?? throw new("Subclasses of MethodAnalysisContext should override Attributes");

public TypeAnalysisContext? InjectedReturnType { get; set; }
Expand Down Expand Up @@ -170,7 +175,7 @@ public void ReleaseAnalysisData()
ControlFlowGraph = null;
}

public override string ToString() => $"Method: {Definition?.DeclaringType!.Name}::{Definition?.Name ?? "No definition"}";
public override string ToString() => $"Method: {FullName}";

#region StableNameDot implementation

Expand Down
2 changes: 1 addition & 1 deletion Cpp2IL.Core/Model/Contexts/ParameterAnalysisContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class ParameterAnalysisContext : HasCustomAttributesAndName, IParameterIn
/// <summary>
/// The human-readable display value of the parameter type.
/// </summary>
public string ReadableTypeName => LibCpp2ILUtils.GetTypeReflectionData(ParameterType).ToString();
public string ReadableTypeName => ParameterTypeContext.FullName;

/// <summary>
/// The human-readable display value of the parameter, as it would appear in a c# method declaration.
Expand Down
18 changes: 18 additions & 0 deletions Cpp2IL.Plugin.Pdb/Cpp2IL.Plugin.Pdb.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Cpp2IL.Core\Cpp2IL.Core.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="AssetRipper.Bindings.MsPdbCore" Version="1.0.1" />
</ItemGroup>

</Project>
137 changes: 137 additions & 0 deletions Cpp2IL.Plugin.Pdb/PdbOutputFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
using System.Reflection.PortableExecutable;
using AssetRipper.Bindings.MsPdbCore;
using Cpp2IL.Core.Api;
using Cpp2IL.Core.Model.Contexts;

namespace Cpp2IL.Plugin.Pdb;

internal unsafe class PdbOutputFormat : Cpp2IlOutputFormat
{
public override string OutputFormatId => "pdb_windows";

public override string OutputFormatName => "Windows PDB generation";

public override void DoOutput(ApplicationAnalysisContext context, string outputRoot)
{
if (!Directory.Exists(outputRoot))
Directory.CreateDirectory(outputRoot);

using var peReader = new PEReader(new MemoryStream(context.Binary.GetRawBinaryContent()));

var pdbFilePath = Path.Combine(outputRoot, "GameAssembly.pdb");
MsPdbCore.PDBOpen2W(pdbFilePath, "w", out var err, out var openError, out var pdb);

MsPdbCore.PDBOpenDBI(pdb, "w", "", out var dbi);

MsPdbCore.DBIOpenModW(dbi, "__Globals", "__Globals", out var mod);

ushort secNum = 1;
ushort i2cs = 1;
foreach (var sectionHeader in peReader.PEHeaders.SectionHeaders)
{
if (sectionHeader.Name == "il2cpp")
i2cs = secNum;
MsPdbCore.DBIAddSec(dbi, secNum++, 0 /* TODO? */, sectionHeader.VirtualAddress, sectionHeader.VirtualSize);
}

Dictionary<string, ulong> keyFunctions = [];

foreach ((var name, var address) in context.GetOrCreateKeyFunctionAddresses().Pairs)
{
keyFunctions[name] = address;
}

foreach ((var name, var address) in context.Binary.GetExportedFunctions())
{
keyFunctions[name] = address;
}

foreach ((var name, var address) in keyFunctions)
{
if (address == 0)
continue;

GetSectionInformation(peReader, (long)context.Binary.GetRva(address), out var targetSection, out var offset);
MsPdbCore.ModAddPublic2(mod, name, targetSection, offset, CV_PUBSYMFLAGS_e.Function);
}

foreach ((var virtualAddress, var list) in context.MethodsByAddress)
{
if (virtualAddress <= 0)
continue;

GetSectionInformation(peReader, (long)context.Binary.GetRva(virtualAddress), out var targetSection, out var offset);

foreach (var method in list)
{
if (method is NativeMethodAnalysisContext nativeMethod)
{
continue; // Skip native methods
}
MsPdbCore.ModAddPublic2(mod, method.FullName, targetSection, offset, CV_PUBSYMFLAGS_e.Function);
}
}

MsPdbCore.ModClose(mod);
MsPdbCore.DBIClose(dbi);

MsPdbCore.PDBCommit(pdb);

MsPdbCore.PDBQuerySignature2(pdb, out var wrongGuid);

MsPdbCore.PDBClose(pdb);

// Hack: manually replace guid and age in generated .pdb, because there's no API on mspdbcore to set them manually
var targetDebugInfo = peReader.ReadCodeViewDebugDirectoryData(peReader.ReadDebugDirectory()
.Single(it => it.Type == DebugDirectoryEntryType.CodeView));

var wrongGuidBytes = wrongGuid.ToByteArray();
var allPdbBytes = File.ReadAllBytes(pdbFilePath);

var patchTarget = IndexOfBytes(allPdbBytes, wrongGuidBytes);
targetDebugInfo.Guid.TryWriteBytes(allPdbBytes.AsSpan(patchTarget));

Console.WriteLine(targetDebugInfo.Guid);
Console.WriteLine(targetDebugInfo.Age);

BitConverter.TryWriteBytes(allPdbBytes.AsSpan(patchTarget - 4), targetDebugInfo.Age);
File.WriteAllBytes(pdbFilePath, allPdbBytes);
}

private static void GetSectionInformation(PEReader peReader, long virtualAddress, out ushort targetSection, out int offset)
{
targetSection = 0;
long tsva = 0;
ushort sc = 1;
foreach (var sectionHeader in peReader.PEHeaders.SectionHeaders)
{
if (virtualAddress > sectionHeader.VirtualAddress)
{
targetSection = sc;
tsva = sectionHeader.VirtualAddress;
}
else
break;

sc++;
}

if (targetSection == 0)
throw new ApplicationException("Bad segment");

offset = (int)(virtualAddress - tsva);
}

private static int IndexOfBytes(ReadOnlySpan<byte> haystack, ReadOnlySpan<byte> needle)
{
for (var i = 0; i <= haystack.Length - needle.Length; i++)
{
if (haystack.Slice(i, needle.Length).SequenceEqual(needle))
{
return i;
}
}

return -1;
}
}
19 changes: 19 additions & 0 deletions Cpp2IL.Plugin.Pdb/PdbOutputPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Cpp2IL.Core.Api;
using Cpp2IL.Core.Attributes;
using Cpp2IL.Plugin.Pdb;

[assembly: RegisterCpp2IlPlugin(typeof(PdbOutputPlugin))]

namespace Cpp2IL.Plugin.Pdb;

public class PdbOutputPlugin : Cpp2IlPlugin
{
public override string Name => "PDB Output Plugin";

public override string Description => "Adds an output format which generates debug symbols";

public override void OnLoad()
{
OutputFormatRegistry.Register<PdbOutputFormat>();
}
}
10 changes: 8 additions & 2 deletions Cpp2IL.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
do-release.ps1 = do-release.ps1
.github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml
.github\FUNDING.yml = .github\FUNDING.yml
global.json = global.json
LICENSE = LICENSE
nuget.config = nuget.config
README.md = README.md
LibCpp2Il\README.md = LibCpp2Il\README.md
Cpp2IL.Core\README_CORE.md = Cpp2IL.Core\README_CORE.md
global.json = global.json
do-release.ps1 = do-release.ps1
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibCpp2ILTests", "LibCpp2ILTests\LibCpp2ILTests.csproj", "{EB3CFC80-2125-48D2-AA2F-548F5AA58342}"
Expand All @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cpp2IL.Plugin.StrippedCodeR
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cpp2IL.Plugin.ControlFlowGraph", "Cpp2IL.Plugin.ControlFlowGraph\Cpp2IL.Plugin.ControlFlowGraph.csproj", "{29683659-D4B0-444B-8D35-F35309535EE6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cpp2IL.Plugin.Pdb", "Cpp2IL.Plugin.Pdb\Cpp2IL.Plugin.Pdb.csproj", "{ED407C97-534B-476C-8CCC-BF9D2FB5458A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -95,6 +97,10 @@ Global
{29683659-D4B0-444B-8D35-F35309535EE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29683659-D4B0-444B-8D35-F35309535EE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29683659-D4B0-444B-8D35-F35309535EE6}.Release|Any CPU.Build.0 = Release|Any CPU
{ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ED407C97-534B-476C-8CCC-BF9D2FB5458A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
5 changes: 5 additions & 0 deletions LibCpp2IL/Elf/ElfFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,11 @@ public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)]
}
}

public override IEnumerable<KeyValuePair<string, ulong>> GetExportedFunctions()
{
return _exportNameTable.Select(kv => new KeyValuePair<string, ulong>(kv.Key, kv.Value.VirtualAddress));
}

public override ulong GetVirtualAddressOfPrimaryExecutableSection() => _elfSectionHeaderEntries.FirstOrDefault(s => s.Name == ".text")?.VirtualAddress ?? 0;

public override byte[] GetEntirePrimaryExecutableSection()
Expand Down
2 changes: 2 additions & 0 deletions LibCpp2IL/Il2CppBinary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,8 @@ public virtual bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)] o
return false;
}

public virtual IEnumerable<KeyValuePair<string, ulong>> GetExportedFunctions() => [];

public abstract byte[] GetEntirePrimaryExecutableSection();

public abstract ulong GetVirtualAddressOfPrimaryExecutableSection();
Expand Down
5 changes: 5 additions & 0 deletions LibCpp2IL/MachO/MachOFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ public override bool TryGetExportedFunctionName(ulong addr, [NotNullWhen(true)]
return _exportNamesDict.TryGetValue((long)addr, out name);
}

public override IEnumerable<KeyValuePair<string, ulong>> GetExportedFunctions()
{
return _exportAddressesDict.Select(pair => new KeyValuePair<string, ulong>(pair.Key, (ulong)pair.Value));
}

private MachOSection GetTextSection64()
{
var textSection = Sections64.FirstOrDefault(s => s.SectionName == "__text");
Expand Down
Loading
Loading