Skip to content

Commit edbb994

Browse files
authored
Wasm name section output format (#388)
1 parent 9c6a962 commit edbb994

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

Cpp2IL.Core/Cpp2IlCorePlugin.cs

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public override void OnLoad()
4949
OutputFormatRegistry.Register<DiffableCsOutputFormat>();
5050
OutputFormatRegistry.Register<IsilDumpOutputFormat>();
5151
OutputFormatRegistry.Register<WasmMappingOutputFormat>();
52+
OutputFormatRegistry.Register<WasmNameSectionOutputFormat>();
5253

5354
Logger.VerboseNewline("\tRegistering built-in processing layers", "Core Plugin");
5455

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Text;
5+
using Cpp2IL.Core.Api;
6+
using Cpp2IL.Core.Logging;
7+
using Cpp2IL.Core.Model.Contexts;
8+
using Cpp2IL.Core.Utils;
9+
using LibCpp2IL;
10+
using LibCpp2IL.Wasm;
11+
12+
namespace Cpp2IL.Core.OutputFormats;
13+
14+
public class WasmNameSectionOutputFormat : Cpp2IlOutputFormat
15+
{
16+
public override string OutputFormatId => "wasm_name_section";
17+
public override string OutputFormatName => "Webassembly name section";
18+
19+
public override void DoOutput(ApplicationAnalysisContext context, string outputRoot)
20+
{
21+
// Outputs a binary file matching the standardized "name" WebAssembly custom section.
22+
// If the game's binary doesn't already have the same type of section (which is practically always),
23+
// all that's needed to combine the two is a simple `cat main.wasm section.dat > out.wasm`.
24+
// Using this modified binary in place of the original one provides extra information for debuggers.
25+
26+
// The spec can be found here: https://webassembly.github.io/spec/core/appendix/custom.html#name-section
27+
// Some additional subsections have been proposed (already implemented in v8 & spidermonkey), but they don't seem to be helpful as of now.
28+
// The proposal overview can be found here: https://github.com/WebAssembly/extended-name-section/blob/main/proposals/extended-name-section/Overview.md
29+
30+
if (context.Binary.GetType() != typeof(WasmFile))
31+
throw new Exception("This output format only works with WebAssembly files");
32+
33+
if (!Directory.Exists(outputRoot))
34+
Directory.CreateDirectory(outputRoot);
35+
36+
var outputFile = File.Create(Path.Combine(outputRoot, "namesection.dat"));
37+
38+
var section =
39+
new MemoryStream(); // This stream is separate from outputFile because we need to know the size of the section before writing it
40+
section.WriteName("name"); // The section's name (`name`)
41+
42+
var moduleNameSubsection = new MemoryStream();
43+
moduleNameSubsection.WriteName("Unity");
44+
section.WriteSizedData(moduleNameSubsection, 0x0); // Subsection id 0
45+
moduleNameSubsection.Dispose();
46+
47+
var paramSuccessCount = 0;
48+
var paramFailCount = 0;
49+
50+
var functionData = context.AllTypes.SelectMany(t => t.Methods)
51+
.Select(method => (method, definition: WasmUtils.TryGetWasmDefinition(method)))
52+
.Where(v => v.definition is not null)
53+
.Select(v =>
54+
{
55+
var trueParamCount = v.definition!.GetType((WasmFile)LibCpp2IlMain.Binary!).ParamTypes.Length;
56+
57+
// Also see WasmUtils.BuildSignature
58+
var parameters = v.method.Parameters.Select(param => param.Name).ToList();
59+
60+
if (!v.method.IsStatic)
61+
parameters.Insert(0, "this");
62+
63+
if (v.method.ReturnTypeContext is
64+
{ IsValueType: true, IsPrimitive: true, Definition: null or { Size: > 8 } })
65+
parameters.Insert(0, "out");
66+
67+
parameters.Add("methodInfo"); // Only for some methods...?
68+
69+
if (trueParamCount != parameters.Count)
70+
{
71+
// Logger.WarnNewline($"Failed param matching for {v.method.FullNameWithSignature}, calculated {parameters.Count} with there actually being {trueParamCount} ({string.Join(" ", parameters)})");
72+
parameters.Clear();
73+
paramFailCount++;
74+
}
75+
else
76+
{
77+
paramSuccessCount++;
78+
}
79+
80+
return (
81+
Index: v.definition!.FunctionTableIndex,
82+
Name: v.method.FullName,
83+
Parameters: parameters
84+
);
85+
})
86+
.GroupBy(v => v.Index)
87+
.OrderBy(grouping => grouping.Key)
88+
.ToDictionary(
89+
grouping => grouping.Key,
90+
grouping => grouping.Select(v => (v.Name, v.Parameters)).ToList()
91+
);
92+
93+
Logger.InfoNewline(
94+
$"Estimated parameter naming success rate: {(float)paramSuccessCount / (paramSuccessCount + paramFailCount) * 100:N2}%");
95+
96+
var functionNameSubsection = new MemoryStream();
97+
functionNameSubsection.WriteLEB128Unsigned((ulong)functionData.Count); // vector length
98+
99+
var localNameSubsection = new MemoryStream();
100+
localNameSubsection.WriteLEB128Unsigned((ulong)functionData.Count); // vector length
101+
102+
foreach (var (idx, data) in functionData)
103+
{
104+
functionNameSubsection.WriteLEB128Unsigned((ulong)idx);
105+
localNameSubsection.WriteLEB128Unsigned((ulong)idx);
106+
107+
functionNameSubsection.WriteName(data.Count == 1 ? data[0].Name : $"multiple_{data.Count}_{data[0].Name}");
108+
109+
localNameSubsection.WriteLEB128Unsigned((ulong)data[0].Parameters.Count); // vector length
110+
for (var i = 0; i < data[0].Parameters.Count; i++)
111+
{
112+
localNameSubsection.WriteLEB128Unsigned((ulong)i);
113+
// Possible to include type here, but may make names excessively long
114+
localNameSubsection.WriteName(data.Count == 1
115+
? data[0].Parameters[i]
116+
: $"multiple_{data.Count}_{data[0].Parameters[i]}");
117+
}
118+
}
119+
120+
section.WriteSizedData(functionNameSubsection, 0x1); // Subsection id 1
121+
section.WriteSizedData(localNameSubsection, 0x2); // Subsection id 2
122+
123+
outputFile.WriteSizedData(section, 0x0); // Section id 0 - custom
124+
}
125+
}
126+
127+
public static class Extensions
128+
{
129+
public static void WriteName(this Stream memoryStream, string name)
130+
{
131+
var bytes = Encoding.Default.GetBytes(name);
132+
memoryStream.WriteLEB128Unsigned((ulong)bytes.Length);
133+
memoryStream.Write(bytes, 0, bytes.Length);
134+
}
135+
136+
public static void WriteSizedData(this Stream memoryStream, MemoryStream data, byte? prependByte = null)
137+
{
138+
if (prependByte.HasValue)
139+
{
140+
memoryStream.WriteByte(prependByte.Value);
141+
}
142+
143+
memoryStream.WriteLEB128Unsigned((ulong)data.Length);
144+
data.WriteTo(memoryStream);
145+
}
146+
}

0 commit comments

Comments
 (0)