fix(il2cpp): track unresolved ELF relocation widths

Fixes #6701
This commit is contained in:
morluto committed 2026-10-23 21:28:10 +08:00
1 parent d31e42699f
commit bdf13a63c4
5 files changed
+84 -25

No files matched your search

+1 -1
View File
@@ -298,7 +298,7 @@ Format and selection failures name `binary_path`; capacity refusals remain typed
Scans yield cooperatively in bounded batches. Name-address retention and reverse-trie construction are each limited to65,536 entries/nodes and fallback module-array candidates to64 (`native-index-capacity`); exceeding capacity does not prove absence. Rendered type text has a64MiB UTF-8 cache-workspace guard, separate from the64MiB encoded-report admission, and64 nested rendering frames (`native-type-text-capacity` / `type-nesting-capacity`). Genuine cycles are malformed; unsupported type kinds are explicit coverage failures. Native row/text admission runs before allocation or retention. Unused generic parameter names are read on demand from the same stable metadata snapshot. There is no cross-inspection file cache; validated immutable Evidence is reused for session views.
ELF RELATIVE writes into zero-fill are kept as a private virtual-pointer overlay. Unresolved supported-width symbol relocations, including RELA PLT entries, remain unknown; stored zeros never establish no-body. COPY, TLS descriptor, REL, packed APS2 and other unsupported relocation encodings fail closed. Input bytes are unchanged.
ELF RELATIVE writes into zero-fill are kept as a private virtual-pointer overlay. Unresolved supported-width symbol relocations, including RELA PLT entries, remain unknown only for the bytes their ELF ABI relocation type covers; an adjacent 32-bit relocation does not make a separate 64-bit method slot unknown. Stored zeros never establish no-body. COPY, TLS descriptor, REL, packed APS2 and other unsupported relocation encodings fail closed. Input bytes are unchanged.
Native address queries batch exact64-bit hexadecimal `addresses` and `states` (`code`, `no-body`, `unknown`), intersecting indices and pagination. Numerically equal address spellings match; original row indices and every shared-body alternative survive:
+37 -13
View File
@@ -21,7 +21,7 @@ const unsupported = (message: string) =>
interface RelocatedImage {
readonly bytes: Buffer;
readonly unresolved: Set<bigint>;
readonly unresolved: Map<bigint, number>;
readonly zeroFillPointers: Map<bigint, bigint>;
}
interface RelocationSource {
@@ -29,7 +29,7 @@ interface RelocationSource {
readonly mappings: readonly FileMapping[];
readonly zeroFills: readonly ZeroFillMapping[];
readonly relative: number;
readonly unknownPointerKinds: ReadonlySet<number>;
readonly unknownPointerWidths: ReadonlyMap<number, number>;
}
/** Standard dynamic relocations whose destination spans at most eight bytes.
@@ -41,8 +41,19 @@ const RELOCATION_KINDS = new Map([
183,
{
relative: 1027,
unknownPointerKinds: new Set([
257, 258, 259, 260, 261, 262, 1025, 1026, 1028, 1029, 1030, 1032,
unknownPointerWidths: new Map([
[257, 8],
[258, 4],
[259, 2],
[260, 8],
[261, 4],
[262, 2],
[1025, 8],
[1026, 8],
[1028, 8],
[1029, 8],
[1030, 8],
[1032, 8],
]),
},
],
@@ -50,7 +61,19 @@ const RELOCATION_KINDS = new Map([
62,
{
relative: 8,
unknownPointerKinds: new Set([1, 2, 6, 7, 10, 11, 16, 17, 18, 24, 37]),
unknownPointerWidths: new Map([
[1, 8],
[2, 4],
[6, 8],
[7, 8],
[10, 4],
[11, 4],
[16, 8],
[17, 8],
[18, 8],
[24, 8],
[37, 8],
]),
},
],
]);
@@ -148,19 +171,20 @@ function* applyRelaTable(
kind = Number(info & 0xffffffffn);
if (kind === 0) continue;
const destination = source.image.readBigUInt64LE(at);
const target = fileOffset(source.mappings, destination, 8);
if (kind !== source.relative || info >> 32n !== 0n) {
if (!source.unknownPointerKinds.has(kind))
const width = source.unknownPointerWidths.get(kind);
if (width === undefined)
throw unsupported(`ELF relocation kind ${kind} is not decoded`);
if (!output.unresolved.has(destination)) {
admitOverlay(output);
output.unresolved.add(destination);
}
const priorWidth = output.unresolved.get(destination);
if (priorWidth === undefined) admitOverlay(output);
output.unresolved.set(destination, Math.max(priorWidth ?? 0, width));
// Registration scans read this private image directly. Clear unknown
// fields so their stored bytes cannot masquerade as resolved pointers.
if (target !== null) output.bytes.fill(0, target, target + 8);
const target = fileOffset(source.mappings, destination, width);
if (target !== null) output.bytes.fill(0, target, target + width);
continue;
}
const target = fileOffset(source.mappings, destination, 8);
const value = source.image.readBigInt64LE(at + 16) & 0xffffffffffffffffn;
if (target !== null) output.bytes.writeBigUInt64LE(value, target);
else if (
@@ -196,7 +220,7 @@ export function* applyElfRelativeRelocationsSteps(
yield;
const output: RelocatedImage = {
bytes: Buffer.from(image),
unresolved: new Set(),
unresolved: new Map(),
zeroFillPointers: new Map(),
};
const source = { image, mappings, zeroFills, ...kinds };
+7 -7
View File
@@ -66,7 +66,7 @@ export interface NativeImage {
readonly bytes: Buffer;
readonly container: BinaryContainer;
readonly imageBase: bigint;
readonly unresolvedPointerSlots: ReadonlySet<bigint>;
readonly unresolvedPointerSlots: ReadonlyMap<bigint, number>;
readonly zeroFillPointers: ReadonlyMap<bigint, bigint>;
}
@@ -109,7 +109,7 @@ export function* openImageSteps(
let relocated:
| {
bytes: Buffer;
unresolved: ReadonlySet<bigint>;
unresolved: ReadonlyMap<bigint, number>;
zeroFillPointers: ReadonlyMap<bigint, bigint>;
}
| undefined;
@@ -151,7 +151,7 @@ export function* openImageSteps(
bytes: image,
container,
imageBase,
unresolvedPointerSlots: relocated?.unresolved ?? new Set<bigint>(),
unresolvedPointerSlots: relocated?.unresolved ?? new Map<bigint, number>(),
zeroFillPointers: relocated?.zeroFillPointers ?? new Map<bigint, bigint>(),
};
}
@@ -295,10 +295,10 @@ const hasUnresolvedPointer = (
size = 8,
): boolean => {
if (image.unresolvedPointerSlots.size === 0) return false;
// Unsupported relocation widths are unknown; conservatively cover any
// overlap with the pointer rather than infer a zero or trust stored bytes.
for (let delta = -7; delta < size; delta++)
if (image.unresolvedPointerSlots.has(address + BigInt(delta))) return true;
for (let delta = -7; delta < size; delta++) {
const width = image.unresolvedPointerSlots.get(address + BigInt(delta));
if (width !== undefined && delta + width > 0) return true;
}
return false;
};
@@ -511,6 +511,29 @@ it("reports a symbol-relocated method slot as unknown rather than no-body", () =
expect(linked.method_addresses[0]).toEqual(expectedAddresses[0]);
});
it.each([
["PC32", 2],
["ABS32", 10],
] as const)(
"keeps a method pointer beside a non-overlapping %s relocation",
(_, kind) => {
const binary = createIl2cppBinaryFixture();
const adjacent = createElfFixture(binary, {
machine: 62,
unresolvedRelocations: [{ offset: 0x2fc, kind }],
});
expect(link(adjacent).method_addresses).toEqual(expectedAddresses);
const overlapping = createElfFixture(binary, {
machine: 62,
unresolvedRelocations: [{ offset: 0x300, kind }],
});
expect(link(overlapping).method_addresses[1]).toEqual(
expectedAddress("unknown"),
);
},
);
it("reads a relative relocation into a zero-filled pointer array as a known value", () => {
const bytes = createElfFixture(createIl2cppBinaryFixture(), {
zeroFilledTable: true,
+16 -4
View File
@@ -223,6 +223,11 @@ export interface ElfFixtureOptions {
readonly dynamic?: boolean;
/** Give every relocation a symbol index, so none is RELATIVE-only. */
readonly symbolRelocations?: boolean;
/** Add unresolved dynamic relocations at offsets relative to the load base. */
readonly unresolvedRelocations?: readonly {
readonly offset: number;
readonly kind: number;
}[];
/** Write DT_RELA twice. */
readonly duplicateRela?: boolean;
/** Override DT_RELASZ or DT_RELAENT. */
@@ -243,7 +248,7 @@ export const createElfFixture = (
): Buffer => {
const body = Buffer.from(image);
body.fill(0, 0, 0x100);
const relocations: [slot: bigint, value: bigint][] = [];
const relocations: [slot: bigint, value: bigint, kind?: number][] = [];
for (let at = 0x100; at + 8 <= SIZE; at += 8) {
const value = body.readBigUInt64LE(at);
if (
@@ -254,6 +259,12 @@ export const createElfFixture = (
relocations.push([FIXTURE_BINARY_BASE + BigInt(at), value]);
body.writeBigUInt64LE(0n, at);
}
for (const relocation of options.unresolvedRelocations ?? [])
relocations.push([
FIXTURE_BINARY_BASE + BigInt(relocation.offset),
0n,
relocation.kind,
]);
const tagCount =
4 +
(options.duplicateRela === true ? 1 : 0) +
@@ -266,11 +277,12 @@ export const createElfFixture = (
entry[1] = FIXTURE_BINARY_BASE + BigInt(fileSize);
}
const rela = Buffer.alloc(relocations.length * 24);
relocations.forEach(([slot, value], index) => {
relocations.forEach(([slot, value, kind], index) => {
rela.writeBigUInt64LE(slot, index * 24);
rela.writeBigUInt64LE(
(options.symbolRelocations === true ? 1n << 32n : 0n) |
(options.machine === 62 ? 8n : 1027n),
(options.symbolRelocations === true || kind !== undefined
? 1n << 32n
: 0n) | BigInt(kind ?? (options.machine === 62 ? 8 : 1027)),
index * 24 + 8,
);
rela.writeBigInt64LE(value, index * 24 + 16);