92 lines
4.2 KiB
JavaScript
Executable file
92 lines
4.2 KiB
JavaScript
Executable file
// Resolves relocation targets the compare step could not name, using the
|
|
// symbol database's addresses. A call to an undecompiled function compiles to
|
|
// a reference to e.g. `?Foo@@YAXXZ`; the original points at 0x401230, which the
|
|
// database knows is fn_00042. If nothing contradicts it, that reference is
|
|
// correct and `?Foo@@YAXXZ` is learned as fn_00042's decorated name on merge.
|
|
|
|
import { STATUS } from './compare.js';
|
|
import { hex } from './annotations.js';
|
|
|
|
/**
|
|
* @param {object} check result of checkUnits
|
|
* @param {Map<string,object>} symbols ID -> symbol (with addr for the current version)
|
|
* @returns {{learned: Array<{decorated, id, addr}>, unknown: Array<{decorated, addr}>, conflicts: string[]}}
|
|
*/
|
|
export function reconcile(check, symbols) {
|
|
const byAddr = new Map();
|
|
const byDecorated = new Map();
|
|
for (const s of symbols.values()) {
|
|
if (s.addr != null) byAddr.set(s.addr, s);
|
|
if (s.decorated) byDecorated.set(s.decorated, s);
|
|
}
|
|
|
|
const claims = new Map(); // decorated -> addr
|
|
const conflicts = [];
|
|
for (const u of check.units) {
|
|
for (const [decorated, id] of Object.entries(u.bindings)) {
|
|
const s = symbols.get(id);
|
|
if (s?.addr != null) addClaim(claims, conflicts, decorated, s.addr);
|
|
}
|
|
for (const x of u.inferred) addClaim(claims, conflicts, x.symbol, x.addr);
|
|
}
|
|
|
|
const learned = [];
|
|
const learnedAddresses = [];
|
|
const unknown = [];
|
|
const ok = new Set();
|
|
for (const [decorated, addr] of claims) {
|
|
const atAddr = byAddr.get(addr);
|
|
const named = byDecorated.get(decorated);
|
|
if (named && named.addr == null) {
|
|
// A known symbol without an address in this version, referenced by matching code: the
|
|
// original's target is its address here (unless another symbol already holds it).
|
|
ok.add(decorated);
|
|
if (!atAddr) learnedAddresses.push({ id: named.id, decorated, addr });
|
|
} else if (named && named.addr !== addr) {
|
|
// Definitive: the name is known to live elsewhere, so the reference is wrong.
|
|
conflicts.push({ symbol: decorated, hard: true, message: `${decorated} is ${named.id} at 0x${hex(named.addr)}, but the original references 0x${hex(addr)}` });
|
|
} else if (atAddr && atAddr.decorated && atAddr.decorated !== decorated) {
|
|
// Another name at a known address: the linker folded identical functions or data into one.
|
|
// Accepted, but not learned (the address keeps the name it has).
|
|
ok.add(decorated);
|
|
} else if (atAddr) {
|
|
ok.add(decorated);
|
|
if (!atAddr.decorated) learned.push({ decorated, id: atAddr.id, addr });
|
|
} else {
|
|
ok.add(decorated);
|
|
unknown.push({ decorated, addr });
|
|
}
|
|
}
|
|
|
|
const conflictSymbols = new Set(conflicts.map((c) => c.symbol));
|
|
const wrongSymbols = new Set(conflicts.filter((c) => c.hard).map((c) => c.symbol));
|
|
|
|
// Upgrade functions whose only problem was unnamed relocation targets.
|
|
for (const u of check.units) {
|
|
for (const f of u.functions) {
|
|
if (f.status !== STATUS.UNRESOLVED) continue;
|
|
const pending = (f.relocations ?? []).filter((r) => r.unresolved);
|
|
const allKnown = pending.every((r) => ok.has(r.symbol) && byAddr.has(r.actual));
|
|
// An ambiguous name only means it cannot be learned; the function's own bytes and
|
|
// references were fine, so it stays unresolved (reviewers see why) instead of failing.
|
|
const conflicted = pending.filter((r) => conflictSymbols.has(r.symbol)).map((r) => r.symbol);
|
|
if (conflicted.some((s) => wrongSymbols.has(s))) f.status = STATUS.MISMATCH;
|
|
else if (conflicted.length) f.conflictedReferences = conflicted;
|
|
else if (allKnown) f.status = STATUS.EXACT;
|
|
}
|
|
}
|
|
// Ambiguous names are not learned.
|
|
return {
|
|
learned: learned.filter((l) => !conflictSymbols.has(l.decorated)),
|
|
learnedAddresses: learnedAddresses.filter((l) => !conflictSymbols.has(l.decorated)),
|
|
unknown: unknown.filter((u) => !conflictSymbols.has(u.decorated)),
|
|
conflicts,
|
|
conflictSymbols,
|
|
};
|
|
}
|
|
|
|
function addClaim(claims, conflicts, decorated, addr) {
|
|
const prev = claims.get(decorated);
|
|
if (prev === undefined) claims.set(decorated, addr);
|
|
else if (prev !== addr) conflicts.push({ symbol: decorated, message: `${decorated} is referenced as both 0x${hex(prev)} and 0x${hex(addr)}` });
|
|
}
|