forked from ppy/osu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPatchElfPageSize.targets
More file actions
274 lines (239 loc) · 9.95 KB
/
Copy pathPatchElfPageSize.targets
File metadata and controls
274 lines (239 loc) · 9.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
<!--
PatchElfPageSize.targets
========================
Patches Android native libraries from NuGet packages that still ship with 4 KB
ELF LOAD-segment alignment so they satisfy the Android 16 (API 36+) requirement
for 16 KB page sizes.
The task rewrites the ELF file in-place: it inserts padding between LOAD segments
so that file_offset % 0x4000 == virtual_address % 0x4000 and sets each LOAD
segment's p_align to 0x4000. The patched file is byte-identical on a second run
(idempotent).
Import this file from any Android .props / .csproj that needs the fix.
-->
<Project>
<UsingTask TaskName="PatchElfPageSize" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
<ParameterGroup>
<FilePath ParameterType="System.String" Required="true" />
<TargetPageSize ParameterType="System.Int32" Required="false" />
<WasPatched ParameterType="System.Boolean" Output="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs"><![CDATA[
WasPatched = false;
int pageSize = TargetPageSize > 0 ? TargetPageSize : 0x4000; // 16 KB default
if (!System.IO.File.Exists(FilePath))
return true;
byte[] data = System.IO.File.ReadAllBytes(FilePath);
// ── Validate ELF magic ──────────────────────────────────────────────
if (data.Length < 64 || data[0] != 0x7F || data[1] != (byte)'E' || data[2] != (byte)'L' || data[3] != (byte)'F')
return true;
int eiClass = data[4]; // 1 = 32-bit, 2 = 64-bit
bool is64 = eiClass == 2;
bool isLE = data[5] == 1;
// Only 64-bit little-endian ELFs need 16 KB alignment (Android arm64/x64).
if (!is64 || !isLE)
return true;
// ── Helper lambdas ──────────────────────────────────────────────────
System.Func<byte[], int, ulong> RU64 = (b, o) =>
(ulong)b[o] | ((ulong)b[o+1] << 8) | ((ulong)b[o+2] << 16) | ((ulong)b[o+3] << 24) |
((ulong)b[o+4] << 32) | ((ulong)b[o+5] << 40) | ((ulong)b[o+6] << 48) | ((ulong)b[o+7] << 56);
System.Func<byte[], int, uint> RU32 = (b, o) =>
(uint)b[o] | ((uint)b[o+1] << 8) | ((uint)b[o+2] << 16) | ((uint)b[o+3] << 24);
System.Action<byte[], int, ulong> WU64 = (b, o, v) =>
{
b[o] = (byte)(v); b[o+1] = (byte)(v >> 8);
b[o+2] = (byte)(v >> 16); b[o+3] = (byte)(v >> 24);
b[o+4] = (byte)(v >> 32); b[o+5] = (byte)(v >> 40);
b[o+6] = (byte)(v >> 48); b[o+7] = (byte)(v >> 56);
};
// ── Parse ELF header ────────────────────────────────────────────────
ulong e_phoff = RU64(data, 32);
ulong e_shoff = RU64(data, 40);
int e_phentsize = (int)(data[54] | (data[55] << 8));
int e_phnum = (int)(data[56] | (data[57] << 8));
int e_shentsize = (int)(data[58] | (data[59] << 8));
int e_shnum = (int)(data[60] | (data[61] << 8));
// ── Collect LOAD segments ───────────────────────────────────────────
const uint PT_LOAD = 1;
bool alreadyAligned = true;
var loadIndices = new System.Collections.Generic.List<int>();
for (int i = 0; i < e_phnum; i++)
{
int hdr = (int)e_phoff + i * e_phentsize;
if (RU32(data, hdr) == PT_LOAD)
{
loadIndices.Add(i);
if (RU64(data, hdr + 48) < (ulong)pageSize)
alreadyAligned = false;
}
}
if (alreadyAligned)
return true;
// ── Build a new file with proper padding ────────────────────────────
var segInfos = new System.Collections.Generic.List<System.Tuple<int, ulong, ulong, ulong>>();
foreach (int idx in loadIndices)
{
int hdr = (int)e_phoff + idx * e_phentsize;
segInfos.Add(System.Tuple.Create(idx,
RU64(data, hdr + 8), // p_offset
RU64(data, hdr + 32), // p_filesz
RU64(data, hdr + 16) // p_vaddr
));
}
segInfos.Sort((a, b) => a.Item2.CompareTo(b.Item2));
var ms = new System.IO.MemoryStream(data.Length + pageSize * loadIndices.Count);
int currentOld = 0;
var deltas = new System.Collections.Generic.List<System.Tuple<ulong, long>>();
foreach (var seg in segInfos)
{
int oldOff = (int)seg.Item2;
ulong vaddr = seg.Item4;
int filesz = (int)seg.Item3;
if (oldOff > currentOld)
ms.Write(data, currentOld, oldOff - currentOld);
long newOff = ms.Position;
ulong requiredMod = vaddr % (ulong)pageSize;
ulong currentMod = (ulong)newOff % (ulong)pageSize;
if (currentMod != requiredMod)
{
long pad = (requiredMod >= currentMod)
? (long)(requiredMod - currentMod)
: (long)((ulong)pageSize - currentMod + requiredMod);
for (long p = 0; p < pad; p++) ms.WriteByte(0);
newOff = ms.Position;
}
deltas.Add(System.Tuple.Create((ulong)oldOff, newOff - oldOff));
ms.Write(data, oldOff, filesz);
currentOld = oldOff + filesz;
}
if (currentOld < data.Length)
ms.Write(data, currentOld, data.Length - currentOld);
byte[] newData = ms.ToArray();
// ── Offset translation helper ───────────────────────────────────────
System.Func<ulong, ulong> translate = (ulong old) =>
{
long d = 0;
foreach (var t in deltas)
{
if (old >= t.Item1) d = t.Item2;
else break;
}
return (ulong)((long)old + d);
};
// ── Patch ELF header: e_shoff ───────────────────────────────────────
ulong newShoff = translate(e_shoff);
WU64(newData, 40, newShoff);
// ── Patch program headers ───────────────────────────────────────────
for (int i = 0; i < e_phnum; i++)
{
int oldHdr = (int)e_phoff + i * e_phentsize;
int newHdr = (int)translate((ulong)oldHdr);
WU64(newData, newHdr + 8, translate(RU64(data, oldHdr + 8))); // p_offset
if (RU32(data, oldHdr) == PT_LOAD)
WU64(newData, newHdr + 48, (ulong)pageSize); // p_align
}
// ── Patch section headers ───────────────────────────────────────────
for (int i = 0; i < e_shnum; i++)
{
int oldSh = (int)e_shoff + i * e_shentsize;
int newSh = (int)newShoff + i * e_shentsize;
WU64(newData, newSh + 24, translate(RU64(data, oldSh + 24))); // sh_offset
}
// ── Write patched file (with lock to handle parallel builds) ────────
string lockFile = FilePath + ".patch-lock";
System.IO.FileStream lockStream = null;
try
{
// Acquire an exclusive lock file to prevent concurrent writes.
// Retry up to 30 times with 1-second delays (handles parallel MSBuild nodes).
for (int attempt = 0; attempt < 30; attempt++)
{
try
{
lockStream = new System.IO.FileStream(lockFile,
System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite,
System.IO.FileShare.None);
break;
}
catch (System.IO.IOException)
{
System.Threading.Thread.Sleep(1000);
}
}
if (lockStream == null)
{
Log.LogMessage(MessageImportance.High,
"PatchElfPageSize: could not acquire lock for {0}, skipping (another build node may be patching it)",
System.IO.Path.GetFileName(FilePath));
return true;
}
// Re-read and re-check: another node may have already patched while we waited.
byte[] freshData = System.IO.File.ReadAllBytes(FilePath);
bool stillNeeds = false;
if (freshData.Length >= 64 && freshData[0] == 0x7F && freshData[4] == 2 && freshData[5] == 1)
{
ulong freshPhoff = RU64(freshData, 32);
int freshPhentsz = (int)(freshData[54] | (freshData[55] << 8));
int freshPhnum = (int)(freshData[56] | (freshData[57] << 8));
for (int i = 0; i < freshPhnum; i++)
{
int h = (int)freshPhoff + i * freshPhentsz;
if (RU32(freshData, h) == PT_LOAD && RU64(freshData, h + 48) < (ulong)pageSize)
{
stillNeeds = true;
break;
}
}
}
if (!stillNeeds)
{
Log.LogMessage(MessageImportance.Low,
"PatchElfPageSize: {0} was already patched by another build node",
System.IO.Path.GetFileName(FilePath));
return true;
}
// Even with a lock file, the actual .so may be locked for reading by another MSBuild process.
// Retry writing the actual file.
for (int attempt = 0; attempt < 30; attempt++)
{
try
{
System.IO.File.WriteAllBytes(FilePath, newData);
WasPatched = true;
break;
}
catch (System.IO.IOException)
{
if (attempt == 29) throw;
System.Threading.Thread.Sleep(1000);
}
}
WasPatched = true;
Log.LogMessage(MessageImportance.High,
"PatchElfPageSize: patched {0} (align 0x1000 -> 0x{1:X}, +{2} bytes)",
System.IO.Path.GetFileName(FilePath), pageSize, newData.Length - data.Length);
}
finally
{
if (lockStream != null)
{
lockStream.Dispose();
try { System.IO.File.Delete(lockFile); } catch { }
}
}
]]></Code>
</Task>
</UsingTask>
<!--
Target: PatchAndroidNativeLibPageSize
Runs early in the build, before the page-size validation.
Patches any 64-bit .so in the NuGet cache that has sub-16 KB LOAD alignment.
The patch is idempotent — already-aligned files are skipped.
-->
<Target Name="PatchAndroidNativeLibPageSize" BeforeTargets="Build">
<ItemGroup>
<_NuGetNativeLibs Include="$(NuGetPackageRoot)/**/runtimes/android-*/native/*.so" />
</ItemGroup>
<PatchElfPageSize FilePath="%(Identity)" Condition="'@(_NuGetNativeLibs)' != ''" />
</Target>
</Project>