diff --git a/docs/binary-diagnostics.md b/docs/binary-diagnostics.md index 53e10642ab..84c4abe3b0 100644 --- a/docs/binary-diagnostics.md +++ b/docs/binary-diagnostics.md @@ -334,11 +334,12 @@ count and SHA-256. `.gnu.version_r`, and the `hidden` flag from `.gnu.version`. `"unknown"` means the symbol-version table could not be read. A hidden version is not the default version that a by-name `dlsym` lookup selects. + Definition names take precedence when both tables name the same index. Unreadable names, out-of-range name ordinals and unreadable forwarders are listed in `issues`, their entries are not reported, and coverage is `partial`. Unresolved symbol versions keep their rows: the version name is null when no -unambiguous definition or requirement names the index, and `version` is +definition or requirement names the index, and `version` is `"unknown"` when the version table cannot be read; coverage is likewise `partial`. A dynamic symbol table without its null entry is malformed. An ELF without section headers is `unsupported`: diff --git a/src/native/exports/NativeExportParser.test.ts b/src/native/exports/NativeExportParser.test.ts index c8d62d3832..1237dc4382 100644 --- a/src/native/exports/NativeExportParser.test.ts +++ b/src/native/exports/NativeExportParser.test.ts @@ -340,7 +340,7 @@ describe("ELF version requirements", () => { }); }); - it("keeps conflicting names for one version index unknown", () => { + it("uses a definition name when a requirement shares its index", () => { const result = parse( elfExportImage({ symbols: [{ name: "ambiguous", version: 2 }], @@ -350,10 +350,13 @@ describe("ELF version requirements", () => { ); expect(result.exports).toMatchObject([ - { name: "ambiguous", version: { index: 2, name: null, hidden: false } }, + { + name: "ambiguous", + version: { index: 2, name: "DEFINITION", hidden: false }, + }, ]); - expect(result.coverage.status).toBe("partial"); - expect(result.issues).toMatchObject([{ code: "version_unresolved" }]); + expect(result.coverage.status).toBe("complete"); + expect(result.issues).toEqual([]); }); it("retains exports and reports a truncated version-requirement chain", () => { diff --git a/src/native/exports/NativeExportParser.ts b/src/native/exports/NativeExportParser.ts index e127d45fc9..d2d3eb9c55 100644 --- a/src/native/exports/NativeExportParser.ts +++ b/src/native/exports/NativeExportParser.ts @@ -290,76 +290,8 @@ const readVersionNames = ( ): Map => { const namesByIndex = new Map(); const addName = (index: number, name: string): void => { - const previous = namesByIndex.get(index); - if (previous === undefined) namesByIndex.set(index, name || null); - else if (previous !== name) namesByIndex.set(index, null); + namesByIndex.set(index, name || null); }; - const verdef = sections.find(({ type }) => type === SHT_GNU_VERDEF); - if (verdef !== undefined) { - try { - const { table, names } = versionSectionData( - reader, - sections, - verdef, - "Version definitions", - ); - let offset = 0; - for (let index = 0; index < verdef.info; index++) { - if (offset + 20 > table.size) - throw new BinaryFormatFailure( - "malformed", - "Version definition chain leaves its section", - ); - const at = table.offset + offset; - const versionIndex = reader.u16(at + 4) & 0x7fff; - const aux = reader.u32(at + 12); - const next = reader.u32(at + 16); - const auxiliaryCount = reader.u16(at + 6); - if (auxiliaryCount === 0 || aux < 20 || offset + aux + 8 > table.size) - throw new BinaryFormatFailure( - "malformed", - "Version definition name entry leaves its section", - ); - let auxiliary = offset + aux; - const versionName = readElfString( - names, - reader.u32(table.offset + auxiliary), - ); - addName(versionIndex, versionName); - for (let item = 0; item < auxiliaryCount; item++) { - if (auxiliary + 8 > table.size) - throw new BinaryFormatFailure( - "malformed", - "Version definition auxiliary chain leaves its section", - ); - if (item + 1 < auxiliaryCount) { - const auxiliaryNext = reader.u32(table.offset + auxiliary + 4); - if (auxiliaryNext < 8) - throw new BinaryFormatFailure( - "malformed", - "Version definition auxiliary chain ends before its declared count", - ); - auxiliary += auxiliaryNext; - } - } - if (index + 1 < verdef.info) { - if (next < 20) - throw new BinaryFormatFailure( - "malformed", - "Version definition chain ends before its declared count", - ); - offset += next; - } - } - } catch (cause) { - issues.push({ - code: "version_unresolved", - location: hex(verdef.offset), - message: `Version definitions: ${failureMessage(cause)}`, - }); - } - } - const verneed = sections.find(({ type }) => type === SHT_GNU_VERNEED); if (verneed !== undefined) { try { @@ -430,6 +362,73 @@ const readVersionNames = ( }); } } + // GNU lookup installs requirement names first, then definition names. + const verdef = sections.find(({ type }) => type === SHT_GNU_VERDEF); + if (verdef !== undefined) { + try { + const { table, names } = versionSectionData( + reader, + sections, + verdef, + "Version definitions", + ); + let offset = 0; + for (let index = 0; index < verdef.info; index++) { + if (offset + 20 > table.size) + throw new BinaryFormatFailure( + "malformed", + "Version definition chain leaves its section", + ); + const at = table.offset + offset; + const versionIndex = reader.u16(at + 4) & 0x7fff; + const aux = reader.u32(at + 12); + const next = reader.u32(at + 16); + const auxiliaryCount = reader.u16(at + 6); + if (auxiliaryCount === 0 || aux < 20 || offset + aux + 8 > table.size) + throw new BinaryFormatFailure( + "malformed", + "Version definition name entry leaves its section", + ); + let auxiliary = offset + aux; + const versionName = readElfString( + names, + reader.u32(table.offset + auxiliary), + ); + addName(versionIndex, versionName); + for (let item = 0; item < auxiliaryCount; item++) { + if (auxiliary + 8 > table.size) + throw new BinaryFormatFailure( + "malformed", + "Version definition auxiliary chain leaves its section", + ); + if (item + 1 < auxiliaryCount) { + const auxiliaryNext = reader.u32(table.offset + auxiliary + 4); + if (auxiliaryNext < 8) + throw new BinaryFormatFailure( + "malformed", + "Version definition auxiliary chain ends before its declared count", + ); + auxiliary += auxiliaryNext; + } + } + if (index + 1 < verdef.info) { + if (next < 20) + throw new BinaryFormatFailure( + "malformed", + "Version definition chain ends before its declared count", + ); + offset += next; + } + } + } catch (cause) { + issues.push({ + code: "version_unresolved", + location: hex(verdef.offset), + message: `Version definitions: ${failureMessage(cause)}`, + }); + } + } + return namesByIndex; };