fix(artifacts): preserve decode coverage and cleanup ownership
This commit is contained in:
parent
c74142e6bd
commit
a73a55f53c
|
|
@ -213,19 +213,15 @@ export const inventoryFirmwareOutput = async (
|
|||
});
|
||||
if (handle === undefined) continue;
|
||||
const names: string[] = [];
|
||||
try {
|
||||
for await (const entry of handle) {
|
||||
throwIfFirmwareInventoryCancelled(signal, operation);
|
||||
count++;
|
||||
if (count > maxEntries)
|
||||
throw new AnalysisOutputError(
|
||||
operation,
|
||||
`Staging entry budget exceeded (${maxEntries})`,
|
||||
);
|
||||
names.push(entry.name);
|
||||
}
|
||||
} finally {
|
||||
await handle.close().catch(() => undefined);
|
||||
for await (const entry of handle) {
|
||||
throwIfFirmwareInventoryCancelled(signal, operation);
|
||||
count++;
|
||||
if (count > maxEntries)
|
||||
throw new AnalysisOutputError(
|
||||
operation,
|
||||
`Staging entry budget exceeded (${maxEntries})`,
|
||||
);
|
||||
names.push(entry.name);
|
||||
}
|
||||
for (const name of names.sort()) {
|
||||
const path = join(directory, name);
|
||||
|
|
|
|||
|
|
@ -42,14 +42,62 @@ export const pushDispatchCoverage = (input: {
|
|||
vtables,
|
||||
swift,
|
||||
} = input;
|
||||
const classOwnedMethods = result.objc_dispatch_implementations.filter(
|
||||
({ category }) => category === undefined,
|
||||
);
|
||||
const classRecords = [
|
||||
...result.objc_classes.flatMap((record) =>
|
||||
record.decode?.status === "decoded"
|
||||
? []
|
||||
: [
|
||||
`class ${record.name}: ${record.decode?.reason ?? record.decode?.status ?? "decode_status_missing"}`,
|
||||
],
|
||||
),
|
||||
...result.objc_protocol_records.flatMap((record) =>
|
||||
record.decode.status === "decoded"
|
||||
? []
|
||||
: [
|
||||
`protocol ${record.name}: ${record.decode.reason ?? record.decode.status}`,
|
||||
],
|
||||
),
|
||||
...classOwnedMethods.flatMap((record) =>
|
||||
record.decode.status === "decoded"
|
||||
? []
|
||||
: [
|
||||
`method ${record.class_name} ${record.method_type} ${record.selector}: ${record.decode.reason ?? record.decode.status}`,
|
||||
],
|
||||
),
|
||||
...result.objc_ivars.flatMap((record) =>
|
||||
record.decode.status === "decoded"
|
||||
? []
|
||||
: [
|
||||
`ivar ${record.class_name}.${record.name}: ${record.decode.reason ?? record.decode.status}`,
|
||||
],
|
||||
),
|
||||
];
|
||||
const classReasons = [
|
||||
...failures,
|
||||
...classRecords,
|
||||
...(truncated ? ["max_records_reached"] : []),
|
||||
];
|
||||
const categoryMethods = result.objc_dispatch_implementations.filter(
|
||||
({ category }) => category !== undefined,
|
||||
);
|
||||
const categoryMethodReasons = categoryMethods.flatMap((record) =>
|
||||
record.decode.status === "decoded"
|
||||
? []
|
||||
: [
|
||||
`category method ${record.category} ${record.class_name} ${record.method_type} ${record.selector}: ${record.decode.reason ?? record.decode.status}`,
|
||||
],
|
||||
);
|
||||
result.coverage.push({
|
||||
facet: "objc_class_method_ivar_metadata",
|
||||
status: failures.length > 0 || truncated ? "partial" : "complete",
|
||||
reason:
|
||||
[...failures, ...(truncated ? ["max_records_reached"] : [])].join("; ") ||
|
||||
null,
|
||||
status: classReasons.length > 0 ? "partial" : "complete",
|
||||
reason: classReasons.join("; ") || null,
|
||||
examined,
|
||||
decoded: result.objc_classes.length,
|
||||
decoded: result.objc_classes.filter(
|
||||
({ decode }) => decode?.status === "decoded",
|
||||
).length,
|
||||
});
|
||||
result.coverage.push({
|
||||
facet: "binary_relative_pointers",
|
||||
|
|
@ -79,13 +127,18 @@ export const pushDispatchCoverage = (input: {
|
|||
status:
|
||||
truncated ||
|
||||
categoryFailures.length > 0 ||
|
||||
result.objc_categories.some(({ decode }) => decode.status !== "decoded")
|
||||
result.objc_categories.some(
|
||||
({ decode }) => decode.status !== "decoded",
|
||||
) ||
|
||||
categoryMethodReasons.length > 0
|
||||
? "partial"
|
||||
: "complete",
|
||||
reason:
|
||||
[...categoryFailures, ...(truncated ? ["max_records_reached"] : [])].join(
|
||||
"; ",
|
||||
) || null,
|
||||
[
|
||||
...categoryFailures,
|
||||
...categoryMethodReasons,
|
||||
...(truncated ? ["max_records_reached"] : []),
|
||||
].join("; ") || null,
|
||||
examined: categoriesExamined,
|
||||
decoded: result.objc_categories.filter(
|
||||
({ decode }) => decode.status === "decoded",
|
||||
|
|
@ -110,7 +163,7 @@ export const pushDispatchCoverage = (input: {
|
|||
: "complete",
|
||||
reason: fixupReason.length > 0 ? fixupReason : null,
|
||||
examined: fixups.examined,
|
||||
decoded: fixups.issues.length > 0 ? 0 : fixups.examined,
|
||||
decoded: fixups.decoded,
|
||||
});
|
||||
result.coverage.push({
|
||||
facet: "swift_generic_resilient_witnesses_overrides_async_coroutines",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { describe, expect, it } from "vitest";
|
|||
import { decodeAppleDispatchMetadata } from "./AppleDispatchMetadata.js";
|
||||
|
||||
const base = 0x100000000n;
|
||||
const fixture = (relative = false) => {
|
||||
const fixture = (
|
||||
relative = false,
|
||||
category?: "partial-method" | "unmapped-pointer",
|
||||
) => {
|
||||
const bytes = Buffer.alloc(4096);
|
||||
const u32 = (offset: number, value: number) =>
|
||||
bytes.writeUInt32LE(value, offset);
|
||||
|
|
@ -11,19 +14,26 @@ const fixture = (relative = false) => {
|
|||
u32(0, 0xfeedfacf);
|
||||
u32(4, 0x0100000c);
|
||||
u32(16, 1);
|
||||
u32(20, 152);
|
||||
u32(20, category === undefined ? 152 : 232);
|
||||
u32(32, 0x19);
|
||||
u32(36, 152);
|
||||
u32(36, category === undefined ? 152 : 232);
|
||||
bytes.write("__DATA", 40);
|
||||
ptr(56, 0);
|
||||
bytes.writeBigUInt64LE(4096n, 64);
|
||||
bytes.writeBigUInt64LE(4096n, 80);
|
||||
u32(92, 5);
|
||||
u32(96, 1);
|
||||
u32(96, category === undefined ? 1 : 2);
|
||||
bytes.write("__objc_classlist", 104);
|
||||
bytes.write("__DATA", 120);
|
||||
ptr(136, 0x180);
|
||||
bytes.writeBigUInt64LE(8n, 144);
|
||||
if (category !== undefined) {
|
||||
bytes.write("__objc_catlist", 184);
|
||||
bytes.write("__DATA", 200);
|
||||
ptr(216, 0xb00);
|
||||
bytes.writeBigUInt64LE(8n, 224);
|
||||
u32(232, 0xb00);
|
||||
}
|
||||
u32(152, 0x180);
|
||||
ptr(0x180, 0x200);
|
||||
ptr(0x200, 0x280);
|
||||
|
|
@ -86,10 +96,58 @@ const fixture = (relative = false) => {
|
|||
bytes.write("FixtureProtocol\0", 0x900);
|
||||
bytes.write("requiredAction:\0", 0x920);
|
||||
bytes.write("optionalAction:\0", 0x940);
|
||||
if (category !== undefined) {
|
||||
if (category === "unmapped-pointer") {
|
||||
bytes.writeBigUInt64LE(0xffffffffffffffffn, 0xb00);
|
||||
} else {
|
||||
ptr(0xb00, 0xb40);
|
||||
ptr(0xb40, 0xbc0);
|
||||
ptr(0xb48, 0x200);
|
||||
ptr(0xb50, 0xb80);
|
||||
u32(0xb80, 24);
|
||||
u32(0xb84, 1);
|
||||
ptr(0xb88, 0x460);
|
||||
ptr(0xb90, 0x5c0);
|
||||
bytes.writeBigUInt64LE(0xffffffffffffffffn, 0xb98);
|
||||
bytes.write("FixtureCategory\0", 0xbc0);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
const provenance = { path: "/fixture/app", sha256: "a".repeat(64) };
|
||||
describe("chained Apple dispatch binary metadata", () => {
|
||||
describe("Apple dispatch pointer fixup metadata", () => {
|
||||
it("keeps decoded bindings and their coverage when an independent stream fails", () => {
|
||||
const bytes = fixture();
|
||||
bytes.writeUInt32LE(2, 16);
|
||||
bytes.writeUInt32LE(200, 20);
|
||||
bytes.writeUInt32LE(0x80000022, 184);
|
||||
bytes.writeUInt32LE(48, 188);
|
||||
const bindings = Buffer.from([
|
||||
0x40,
|
||||
...Buffer.from("_OBJC_CLASS_$_NSObject\0"),
|
||||
0x70,
|
||||
0x88,
|
||||
0x04,
|
||||
0x90,
|
||||
0x00,
|
||||
]);
|
||||
bindings.copy(bytes, 0xc00);
|
||||
bytes[0xc80] = 0xd0;
|
||||
bytes.writeUInt32LE(0xc00, 200);
|
||||
bytes.writeUInt32LE(bindings.length, 204);
|
||||
bytes.writeUInt32LE(0xc80, 208);
|
||||
bytes.writeUInt32LE(1, 212);
|
||||
|
||||
const result = decodeAppleDispatchMetadata(bytes, 100, provenance);
|
||||
expect(result.objc_classes[0]).toMatchObject({
|
||||
name: "Fixture",
|
||||
super_class: "NSObject",
|
||||
});
|
||||
expect(
|
||||
result.coverage.find(({ facet }) => facet === "pointer_fixups"),
|
||||
).toMatchObject({ status: "partial", examined: 2, decoded: 1 });
|
||||
});
|
||||
|
||||
it("keeps absolute metadata pointers between declared chained fixups", () => {
|
||||
const bytes = fixture();
|
||||
bytes.writeUInt32LE(2, 16);
|
||||
|
|
@ -120,7 +178,7 @@ describe("chained Apple dispatch binary metadata", () => {
|
|||
});
|
||||
expect(
|
||||
result.coverage.find(({ facet }) => facet === "pointer_fixups"),
|
||||
).toMatchObject({ status: "complete" });
|
||||
).toMatchObject({ status: "complete", examined: 1, decoded: 1 });
|
||||
});
|
||||
});
|
||||
describe("Apple dispatch binary metadata", () => {
|
||||
|
|
@ -221,6 +279,9 @@ describe("Apple dispatch binary metadata", () => {
|
|||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Apple dispatch coverage ownership", () => {
|
||||
it("keeps unsupported pointers and record truncation explicit", () => {
|
||||
const bytes = fixture();
|
||||
bytes.writeBigUInt64LE(0xffffffffffffffffn, 0x498);
|
||||
|
|
@ -229,6 +290,14 @@ describe("Apple dispatch binary metadata", () => {
|
|||
implementation_address: null,
|
||||
decode: { status: "partial" },
|
||||
});
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_class_method_ivar_metadata",
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "partial",
|
||||
reason: expect.stringContaining("method Fixture instance performAction:"),
|
||||
});
|
||||
const truncated = decodeAppleDispatchMetadata(fixture(), 1, provenance);
|
||||
expect(truncated.coverage[0]?.reason).toContain("max_records_reached");
|
||||
expect(
|
||||
|
|
@ -237,6 +306,67 @@ describe("Apple dispatch binary metadata", () => {
|
|||
),
|
||||
).toMatchObject({ status: "partial", reason: "max_records_reached" });
|
||||
});
|
||||
it("keeps partial category methods in category coverage only", () => {
|
||||
const baseline = decodeAppleDispatchMetadata(fixture(), 100, provenance);
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
fixture(false, "partial-method"),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
|
||||
expect(result.objc_dispatch_implementations).toContainEqual(
|
||||
expect.objectContaining({
|
||||
category: "FixtureCategory",
|
||||
decode: {
|
||||
status: "partial",
|
||||
reason: "implementation_pointer_unmapped_or_encoded",
|
||||
},
|
||||
}),
|
||||
);
|
||||
const baselineClassCoverage = baseline.coverage.find(
|
||||
({ facet }) => facet === "objc_class_method_ivar_metadata",
|
||||
);
|
||||
const classCoverage = result.coverage.find(
|
||||
({ facet }) => facet === "objc_class_method_ivar_metadata",
|
||||
);
|
||||
expect(baselineClassCoverage).toBeDefined();
|
||||
expect(classCoverage).toEqual(baselineClassCoverage);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_properties_categories",
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "partial",
|
||||
reason: expect.stringContaining("category method FixtureCategory"),
|
||||
});
|
||||
});
|
||||
it("keeps malformed category pointers out of class failure coverage", () => {
|
||||
const baseline = decodeAppleDispatchMetadata(fixture(), 100, provenance);
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
fixture(false, "unmapped-pointer"),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
const baselineClassCoverage = baseline.coverage.find(
|
||||
({ facet }) => facet === "objc_class_method_ivar_metadata",
|
||||
);
|
||||
const classCoverage = result.coverage.find(
|
||||
({ facet }) => facet === "objc_class_method_ivar_metadata",
|
||||
);
|
||||
expect(baselineClassCoverage).toBeDefined();
|
||||
expect(classCoverage).toEqual(baselineClassCoverage);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_properties_categories",
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "partial",
|
||||
reason: expect.stringContaining("category_decode_failed"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("malformed Apple dispatch metadata", () => {
|
||||
it("rejects malformed command and section boundaries", () => {
|
||||
const bytes = fixture();
|
||||
bytes.writeUInt32LE(7, 36);
|
||||
|
|
|
|||
|
|
@ -149,6 +149,64 @@ const SEGMENTS: Segment[] = [
|
|||
{ address: BASE + 0x4000n, size: 0x4000n, offset: 0x4000, executable: false },
|
||||
];
|
||||
|
||||
describe("independent chained segment coverage", () => {
|
||||
it("counts successfully decoded chained segments alongside an unsupported segment", () => {
|
||||
const starts = 28;
|
||||
const startsSize = 4 + 2 * 4;
|
||||
const firstInfo = starts + startsSize;
|
||||
const secondInfo = firstInfo + 24;
|
||||
const imports = secondInfo + 24;
|
||||
const data = Buffer.alloc(0x3000);
|
||||
data.writeUInt32LE(starts, 4);
|
||||
data.writeUInt32LE(imports, 8);
|
||||
data.writeUInt32LE(imports, 12);
|
||||
data.writeUInt32LE(1, 20);
|
||||
data.writeUInt32LE(2, starts);
|
||||
data.writeUInt32LE(firstInfo - starts, starts + 4);
|
||||
data.writeUInt32LE(secondInfo - starts, starts + 8);
|
||||
for (const [info, format, segmentOffset] of [
|
||||
[firstInfo, 6, 0],
|
||||
[secondInfo, 99, 0x1000],
|
||||
] as const) {
|
||||
data.writeUInt32LE(24, info);
|
||||
data.writeUInt16LE(0x1000, info + 4);
|
||||
data.writeUInt16LE(format, info + 6);
|
||||
data.writeBigUInt64LE(BigInt(segmentOffset), info + 8);
|
||||
data.writeUInt16LE(1, info + 20);
|
||||
data.writeUInt16LE(8, info + 22);
|
||||
}
|
||||
const segments: Segment[] = [
|
||||
{ address: BASE, size: 0x1000n, offset: 0x1000, executable: false },
|
||||
{
|
||||
address: BASE + 0x1000n,
|
||||
size: 0x1000n,
|
||||
offset: 0x2000,
|
||||
executable: false,
|
||||
},
|
||||
];
|
||||
const fixups = parsePointerFixups(
|
||||
data,
|
||||
{
|
||||
chained: { offset: 0, size: imports },
|
||||
binds: [],
|
||||
rebases: [],
|
||||
dylibs: [],
|
||||
},
|
||||
segments,
|
||||
BASE,
|
||||
);
|
||||
|
||||
expect(fixups).toMatchObject({ examined: 2, decoded: 1 });
|
||||
expect(fixups.issues).toMatchObject([
|
||||
{ code: "unsupported_pointer_format" },
|
||||
]);
|
||||
expect(fixups.decode(BASE + 8n, 0n)).toEqual({
|
||||
kind: "rebase",
|
||||
target: BASE,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("pointer fixup tables", () => {
|
||||
it("reports unsupported pointer formats even when no pointer is visited", () => {
|
||||
const data = chainedData(1);
|
||||
|
|
@ -196,20 +254,27 @@ describe("pointer fixup tables", () => {
|
|||
{
|
||||
chained: { offset: 0, size: 128 },
|
||||
binds: [],
|
||||
rebases: [],
|
||||
rebases: [{ offset: 8, size: 12 }],
|
||||
dylibs: [],
|
||||
},
|
||||
SEGMENTS,
|
||||
BASE,
|
||||
);
|
||||
expect(fixups).toMatchObject({
|
||||
examined: 1,
|
||||
decoded: 1,
|
||||
unreadRebaseBytes: 12,
|
||||
});
|
||||
expect(fixups.decode(BASE + 0x4008n, field(1n, 63))).toMatchObject({
|
||||
kind: "bind",
|
||||
symbol: "_OBJC_CLASS_$_NSObject",
|
||||
});
|
||||
expect(fixups.issues).toEqual([]);
|
||||
expect(fixups.decoded).toBe(1);
|
||||
expect(fixups.decode(BASE + 0x4008n, 1n | field(1n, 63))).toMatchObject({
|
||||
kind: "unsupported",
|
||||
});
|
||||
expect(fixups.decoded).toBe(0);
|
||||
expect(fixups.issues).toMatchObject([
|
||||
{ code: "chained_pointer_decode_failed" },
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ export interface PointerFixups {
|
|||
readonly issues: readonly DecodeIssue[];
|
||||
/** Fixup segments or bind streams this reader walked. */
|
||||
readonly examined: number;
|
||||
/** Fully decoded fixup segments or bind streams, in the same units as `examined`. */
|
||||
readonly decoded: number;
|
||||
/** Rebase opcode bytes present in the load command and not decoded. */
|
||||
readonly unreadRebaseBytes: number;
|
||||
/** Decode the pointer stored at `address` whose raw 64-bit value is `raw`. */
|
||||
|
|
@ -260,6 +262,7 @@ const readChainedPage = (
|
|||
const chainedFixups = (
|
||||
image: FixupImage,
|
||||
range: { readonly offset: number; readonly size: number },
|
||||
unreadRebaseBytes: number,
|
||||
): PointerFixups => {
|
||||
const { bytes, segments, dylibs, baseAddress } = image;
|
||||
const base = range.offset;
|
||||
|
|
@ -400,19 +403,25 @@ const chainedFixups = (
|
|||
]
|
||||
: [],
|
||||
);
|
||||
const examinedViews = new Set<string>();
|
||||
for (const chain of ranges) {
|
||||
const examinedViews = new Map<string, boolean>();
|
||||
const decodedRanges = new Set<number>();
|
||||
for (const [chainIndex, chain] of ranges.entries()) {
|
||||
// Unknown formats retain their compact starts table; only a queried page
|
||||
// needs its NONE/presence value. Do not expand unsupported declarations.
|
||||
if (POINTER_FORMATS[chain.format] === undefined) continue;
|
||||
const view = `${chain.pageStarts}:${chain.segment.offset}:${chain.segment.size}:${chain.segment.address}`;
|
||||
if (examinedViews.has(view)) continue;
|
||||
examinedViews.add(view);
|
||||
const view = `${chain.pageStarts}:${chain.segment.offset}:${chain.segment.size}:${chain.segment.address}:${chain.format}:${chain.pageSize}:${chain.pageCount}`;
|
||||
const prior = examinedViews.get(view);
|
||||
if (prior !== undefined) {
|
||||
if (prior) decodedRanges.add(chainIndex);
|
||||
continue;
|
||||
}
|
||||
let complete = true;
|
||||
for (let page = 0; page < chain.pageCount; page++) {
|
||||
const start = bytes.readUInt16LE(chain.pageStarts + page * 2);
|
||||
if (start === 0xffff) continue;
|
||||
const facts = pageFacts(chain, page, start);
|
||||
if (facts.failure !== undefined) {
|
||||
complete = false;
|
||||
issues.push(
|
||||
issue(
|
||||
"pointer_fixups",
|
||||
|
|
@ -427,6 +436,8 @@ const chainedFixups = (
|
|||
break;
|
||||
}
|
||||
}
|
||||
examinedViews.set(view, complete);
|
||||
if (complete) decodedRanges.add(chainIndex);
|
||||
}
|
||||
const failures = issues.map(({ message }) => message);
|
||||
return {
|
||||
|
|
@ -435,13 +446,17 @@ const chainedFixups = (
|
|||
failures,
|
||||
issues,
|
||||
examined: Math.max(ranges.length, 1),
|
||||
unreadRebaseBytes: 0,
|
||||
get decoded() {
|
||||
return ranges.length === 0 ? 1 : decodedRanges.size;
|
||||
},
|
||||
unreadRebaseBytes,
|
||||
decode(address, raw) {
|
||||
const chain = ranges.find(
|
||||
const chainIndex = ranges.findIndex(
|
||||
({ segment }) =>
|
||||
address >= segment.address &&
|
||||
address < segment.address + segment.size,
|
||||
);
|
||||
const chain = ranges[chainIndex];
|
||||
// Pointers outside fixup segments, such as in __TEXT, are stored plainly.
|
||||
if (chain === undefined) return { kind: "rebase", target: raw };
|
||||
const within = Number(address - chain.segment.address);
|
||||
|
|
@ -486,6 +501,7 @@ const chainedFixups = (
|
|||
);
|
||||
failures.push(reason);
|
||||
}
|
||||
decodedRanges.delete(chainIndex);
|
||||
return { kind: "unsupported", reason };
|
||||
}
|
||||
},
|
||||
|
|
@ -684,10 +700,7 @@ export const parsePointerFixups = (
|
|||
0,
|
||||
);
|
||||
if (commands.chained !== null) {
|
||||
const chained = chainedFixups(image, commands.chained);
|
||||
return unreadRebaseBytes === 0
|
||||
? chained
|
||||
: { ...chained, unreadRebaseBytes };
|
||||
return chainedFixups(image, commands.chained, unreadRebaseBytes);
|
||||
}
|
||||
if (commands.binds.length === 0)
|
||||
return {
|
||||
|
|
@ -696,14 +709,17 @@ export const parsePointerFixups = (
|
|||
failures: [],
|
||||
issues: [],
|
||||
examined: 0,
|
||||
decoded: 0,
|
||||
unreadRebaseBytes,
|
||||
decode: (_address, raw) => ({ kind: "rebase", target: raw }),
|
||||
};
|
||||
const binds: BindRun[] = [];
|
||||
const failures: string[] = [];
|
||||
let decoded = 0;
|
||||
for (const stream of commands.binds)
|
||||
try {
|
||||
readBindStream(image, stream, binds);
|
||||
decoded++;
|
||||
} catch (cause: unknown) {
|
||||
failures.push(
|
||||
`${stream.stream} bind opcodes: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
|
|
@ -717,6 +733,7 @@ export const parsePointerFixups = (
|
|||
issue("pointer_fixups", "bind_stream_decode_failed", message),
|
||||
),
|
||||
examined: commands.binds.length,
|
||||
decoded,
|
||||
unreadRebaseBytes,
|
||||
decode(address, raw) {
|
||||
let match: BindRun | undefined;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ export const decodeObjcCategories = (input: {
|
|||
readonly sections: readonly Section[];
|
||||
readonly read: ObjcDispatchReaders;
|
||||
readonly result: ObjcSwiftMetadata;
|
||||
readonly failures: string[];
|
||||
readonly issues: DecodeIssue[];
|
||||
readonly admit: () => boolean;
|
||||
/** Decode a method list into implementations attributed to the category. */
|
||||
|
|
@ -37,7 +36,7 @@ export const decodeObjcCategories = (input: {
|
|||
/** Name of a class defined in this image, from its class_ro_t. */
|
||||
readonly localClassName: (address: bigint) => string;
|
||||
}): number => {
|
||||
const { read, result, failures } = input;
|
||||
const { read, result } = input;
|
||||
let examined = 0;
|
||||
for (const section of input.sections.filter(
|
||||
({ name }) => name === "__objc_catlist",
|
||||
|
|
@ -93,7 +92,6 @@ export const decodeObjcCategories = (input: {
|
|||
});
|
||||
} catch (cause: unknown) {
|
||||
const message = cause instanceof Error ? cause.message : String(cause);
|
||||
failures.push(`Category at 0x${field.toString(16)}: ${message}`);
|
||||
input.issues.push(
|
||||
issue(
|
||||
"objc_properties_categories",
|
||||
|
|
|
|||
|
|
@ -335,7 +335,6 @@ export const decodeObjcDispatchFacets = (input: {
|
|||
sections,
|
||||
read,
|
||||
result,
|
||||
failures,
|
||||
issues: categoryIssues,
|
||||
admit: () => budget.admit(),
|
||||
methods,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ describe("Swift vtable coverage facts: unresolved Swift dispatch slots", () => {
|
|||
issues: [],
|
||||
decode: (_address, raw) => ({ kind: "rebase", target: raw }),
|
||||
examined: 0,
|
||||
decoded: 0,
|
||||
unreadRebaseBytes: 0,
|
||||
},
|
||||
swift: {
|
||||
|
|
@ -181,6 +182,7 @@ describe("Swift vtable coverage facts: unsupported pointer fixup coverage", () =
|
|||
reason: "Unsupported chained pointer format 99",
|
||||
}),
|
||||
examined: 0,
|
||||
decoded: 0,
|
||||
unreadRebaseBytes: 0,
|
||||
},
|
||||
swift: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue