fix(macho): preserve leading spaces in export identities
This commit is contained in:
1 parent
0781c3e99c
commit
54fbfacfb1
3 files changed
+62
-13
No files matched your search
@@ -17,7 +17,11 @@ import {
|
||||
MACHO_ARCHITECTURE_POLICIES,
|
||||
selectMachoArchitectureName,
|
||||
} from "./MachoArchitectureSelection.js";
|
||||
import { parseDyldExports, parseDyldImports } from "./parsers/dyldInfo.js";
|
||||
import {
|
||||
parseDyldExportAddressRow,
|
||||
parseDyldExports,
|
||||
parseDyldImports,
|
||||
} from "./parsers/dyldInfo.js";
|
||||
import { parseLipoCapture, type LipoArchitecture } from "./parsers/lipo.js";
|
||||
import { withoutEchoedPathHeader } from "./parsers/echoedPath.js";
|
||||
import { parseOtoolLoadCommands } from "./parsers/otool.js";
|
||||
@@ -476,7 +480,7 @@ const namesEveryExport = (
|
||||
const rows = parseDyldExports(line, null, names);
|
||||
// The parser withholds a row only when nm names both of its readings.
|
||||
if (rows.length === 0)
|
||||
return names.has(/^0x[\da-f]+\s+(.+)$/isu.exec(line)?.[1] ?? "");
|
||||
return names.has(parseDyldExportAddressRow(line)?.text ?? "");
|
||||
return rows.length === 1 && names.has(rows[0]!.name);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -103,6 +103,16 @@ export const parseDyldImports = (
|
||||
return { items, needsNameInventory };
|
||||
};
|
||||
|
||||
/** Apple prints two spaces after the address; one-space captures remain readable. */
|
||||
export const parseDyldExportAddressRow = (
|
||||
line: string,
|
||||
): { readonly address: string; readonly text: string } | null => {
|
||||
const match = /^(0x[\da-f]+)(?: {2}|[ \t])(.+)$/isu.exec(line);
|
||||
return match?.[1] === undefined || match[2] === undefined
|
||||
? null
|
||||
: { address: match[1], text: match[2] };
|
||||
};
|
||||
|
||||
/** Decode Apple dyld export rows, projecting export offsets to VM addresses. */
|
||||
export const parseDyldExports = (
|
||||
output: string,
|
||||
@@ -133,29 +143,31 @@ export const parseDyldExports = (
|
||||
: { sourceSymbol: reexport.sourceSymbol }),
|
||||
},
|
||||
];
|
||||
const exported = /^(0x[\da-f]+)\s+(.+?)(?:\s+\[([^\]]+)\])?$/isu.exec(line);
|
||||
if (exported?.[1] === undefined || exported[2] === undefined) return [];
|
||||
const rawName = /^0x[\da-f]+\s+(.+)$/isu.exec(line)?.[1];
|
||||
const literalName = rawName !== undefined && knownExportNames.has(rawName);
|
||||
const row = parseDyldExportAddressRow(line);
|
||||
if (row === null) return [];
|
||||
const exported = /^(.+?)(?:\s+\[([^\]]+)\])?$/su.exec(row.text);
|
||||
if (exported?.[1] === undefined) return [];
|
||||
const rawName = row.text;
|
||||
const literalName = knownExportNames.has(rawName);
|
||||
// If nm establishes both interpretations, the textual row cannot bind an address or flags.
|
||||
// Let the caller retain the exact nm names without guessing which one owns this row.
|
||||
if (
|
||||
literalName &&
|
||||
exported[3] !== undefined &&
|
||||
knownExportNames.has(exported[2])
|
||||
exported[2] !== undefined &&
|
||||
knownExportNames.has(exported[1])
|
||||
)
|
||||
return [];
|
||||
const annotation = literalName ? undefined : exported[3];
|
||||
const annotation = literalName ? undefined : exported[2];
|
||||
const absolute = /\babsolute\b/u.test(annotation ?? "");
|
||||
const address =
|
||||
offsets && !absolute
|
||||
? imageBase === null
|
||||
? null
|
||||
: `0x${(BigInt(imageBase) + BigInt(exported[1])).toString(16)}`
|
||||
: canonicalHex(exported[1]);
|
||||
: `0x${(BigInt(imageBase) + BigInt(row.address)).toString(16)}`
|
||||
: canonicalHex(row.address);
|
||||
return [
|
||||
{
|
||||
name: literalName ? rawName : exported[2],
|
||||
name: literalName ? rawName : exported[1],
|
||||
address,
|
||||
weak: /\bweak-def\b/u.test(annotation ?? "") ? true : null,
|
||||
reexport: false,
|
||||
@@ -185,7 +197,7 @@ const parseReexportRow = (
|
||||
line: string,
|
||||
knownNames: ReadonlySet<string>,
|
||||
): ReexportRow | null => {
|
||||
const prefix = /^\[re-export\]\s+/u.exec(line);
|
||||
const prefix = /^\[re-export\] /u.exec(line);
|
||||
if (prefix === null) return null;
|
||||
const rest = line.slice(prefix[0].length);
|
||||
if (rest.length === 0) return null;
|
||||
|
||||
@@ -977,6 +977,39 @@ it("reads exports from the symbol table without llvm-nm's dyld-info pass", async
|
||||
expect(nmCommands(execution.value.result)).toEqual(nmArguments);
|
||||
});
|
||||
|
||||
it("binds leading-space exports to their exact nm identities", async () => {
|
||||
// Real clang dylib: .globl " _spaced"; " _spaced": ret; .globl _spaced; _spaced: ret.
|
||||
// dyld_info separates the address from the literal symbol with two spaces.
|
||||
const names = " _spaced\n_spaced\n";
|
||||
const { runner, nmArguments } = symbolTableRunner(
|
||||
{
|
||||
"dyld_info:-exports": [
|
||||
"fixture [arm64]:",
|
||||
" -exports:",
|
||||
" offset symbol",
|
||||
" 0x000002A0 _spaced",
|
||||
" 0x000002A4 _spaced",
|
||||
"",
|
||||
].join("\n"),
|
||||
},
|
||||
{ symbolTable: names, withDyldInfo: names },
|
||||
);
|
||||
const execution = await new NativeMacOSProvider({}, runner, "darwin")
|
||||
.createClient(machoTarget("/private/fixture"))
|
||||
.execute("inspect_macho", {});
|
||||
if (!execution.ok) throw execution.error;
|
||||
expect(nmArguments).toHaveLength(1);
|
||||
const exported = (
|
||||
execution.value.result as {
|
||||
exports: { items: { name: string; address: string | null }[] };
|
||||
}
|
||||
).exports.items;
|
||||
expect(exported).toEqual([
|
||||
expect.objectContaining({ name: " _spaced", address: "0x1000002a0" }),
|
||||
expect.objectContaining({ name: "_spaced", address: "0x1000002a4" }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps an export name ending in a carriage return distinct", async () => {
|
||||
// Captured from a clang executable exporting "trailing\r" and "trailing".
|
||||
const names = "__mh_execute_header\n_main\ntrailing\ntrailing\r\n";
|
||||
|
||||
Reference in new issue
Block a user