mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 01:45:32 +00:00
refactor(elf): centralize symbol record decoding
This commit is contained in:
1 parent
3d565fa42a
commit
90199ff0ec
7 files changed
+154
-35
No files matched your search
@@ -0,0 +1,64 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { readElfSymbol } from "./elfSymbol.js";
|
||||
|
||||
const formats = [
|
||||
{ bits: 32, littleEndian: false },
|
||||
{ bits: 32, littleEndian: true },
|
||||
{ bits: 64, littleEndian: false },
|
||||
{ bits: 64, littleEndian: true },
|
||||
] as const;
|
||||
|
||||
it.each(formats)(
|
||||
"preserves raw ELF$bits symbols (littleEndian=$littleEndian)",
|
||||
({ bits, littleEndian }) => {
|
||||
const wide = bits === 64;
|
||||
const length = wide ? 24 : 16;
|
||||
const backing = Buffer.alloc(length + 19, 0xa5);
|
||||
const bytes = backing.subarray(7, 7 + length);
|
||||
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
const value = wide ? 0xfedcba9876543210n : 0xfedcba98n;
|
||||
const size = wide ? 0xffffffffffffffffn : 0xffffffffn;
|
||||
view.setUint32(0, 0x89abcdef, littleEndian);
|
||||
view.setUint8(wide ? 4 : 12, 0xf3);
|
||||
view.setUint8(wide ? 5 : 13, 0xee);
|
||||
view.setUint16(wide ? 6 : 14, 0xffff, littleEndian);
|
||||
if (wide) {
|
||||
view.setBigUint64(8, value, littleEndian);
|
||||
view.setBigUint64(16, size, littleEndian);
|
||||
} else {
|
||||
view.setUint32(4, Number(value), littleEndian);
|
||||
view.setUint32(8, Number(size), littleEndian);
|
||||
}
|
||||
const before = Buffer.from(backing);
|
||||
const parsed = readElfSymbol(backing.subarray(7), bits, littleEndian);
|
||||
expect(backing.equals(before)).toBe(true);
|
||||
backing.fill(0);
|
||||
expect(parsed).toEqual({
|
||||
ok: true,
|
||||
value: {
|
||||
name_offset: 0x89abcdef,
|
||||
binding: 15,
|
||||
type: 3,
|
||||
other: 0xee,
|
||||
section_index: 0xffff,
|
||||
value,
|
||||
size,
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each(formats)(
|
||||
"refuses each incomplete ELF$bits symbol (littleEndian=$littleEndian)",
|
||||
({ bits, littleEndian }) => {
|
||||
const length = bits === 64 ? 24 : 16;
|
||||
const backing = Buffer.alloc(length + 7, 0xff);
|
||||
for (let observed = 0; observed < length; observed++)
|
||||
expect(
|
||||
readElfSymbol(backing.subarray(7, 7 + observed), bits, littleEndian),
|
||||
).toEqual({
|
||||
ok: false,
|
||||
error: { kind: "malformed", message: "ELF symbol record is truncated" },
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
import { err, ok } from "./result.js";
|
||||
|
||||
/** Fixed ELF32/ELF64 symbol facts; consumers own definition, visibility and processor-flag policies. */
|
||||
export const readElfSymbol = (
|
||||
bytes: Uint8Array,
|
||||
bits: 32 | 64,
|
||||
littleEndian: boolean,
|
||||
) => {
|
||||
const wide = bits === 64;
|
||||
if (bytes.byteLength < (wide ? 24 : 16))
|
||||
return err({
|
||||
kind: "malformed" as const,
|
||||
message: "ELF symbol record is truncated",
|
||||
});
|
||||
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
const info = view.getUint8(wide ? 4 : 12);
|
||||
return ok({
|
||||
name_offset: view.getUint32(0, littleEndian),
|
||||
binding: info >>> 4,
|
||||
type: info & 15,
|
||||
other: view.getUint8(wide ? 5 : 13),
|
||||
section_index: view.getUint16(wide ? 6 : 14, littleEndian),
|
||||
value: wide
|
||||
? view.getBigUint64(8, littleEndian)
|
||||
: BigInt(view.getUint32(4, littleEndian)),
|
||||
size: wide
|
||||
? view.getBigUint64(16, littleEndian)
|
||||
: BigInt(view.getUint32(8, littleEndian)),
|
||||
});
|
||||
};
|
||||
@@ -521,3 +521,27 @@ it("keeps section admission ahead of program admission for unrepresentable raw o
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("checks raw symbol precision only after definition and requested-name selection", async () => {
|
||||
const bytes = buildElf64Image([
|
||||
{ name: "unselected", value: 64, size: 4 },
|
||||
{ name: "selected", value: 64, size: 4 },
|
||||
]);
|
||||
const expected = await readElfSymbols(bytes, ["selected"]);
|
||||
const sectionTable = Number(bytes.readBigUInt64LE(0x28));
|
||||
const symbols = Number(bytes.readBigUInt64LE(sectionTable + 64 + 0x18));
|
||||
bytes.writeBigUInt64LE(0xffffffffffffffffn, symbols + 8);
|
||||
bytes.writeBigUInt64LE(0xffffffffffffffffn, symbols + 16);
|
||||
expect(await readElfSymbols(bytes, ["selected"])).toEqual(expected);
|
||||
expect(await readElfSymbols(bytes, ["unselected"])).toEqual({
|
||||
symbols: [],
|
||||
failures: [{ reason: "Symbol unselected has unrepresentable bounds" }],
|
||||
});
|
||||
bytes.writeUInt16LE(0, symbols + 6);
|
||||
expect(await readElfSymbols(bytes, ["unselected"])).toEqual({
|
||||
symbols: [],
|
||||
failures: [
|
||||
{ reason: "symbol unselected is not in the dynamic symbol table" },
|
||||
],
|
||||
});
|
||||
});
|
||||
+16
-11
@@ -1,3 +1,4 @@
|
||||
import { readElfSymbol } from "../domain/elfSymbol.js";
|
||||
import { readElfRawTableDeclarations } from "../domain/elfRawTableDeclarations.js";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { setImmediate as yieldToEventLoop } from "node:timers/promises";
|
||||
@@ -17,12 +18,6 @@ class BoundedReader {
|
||||
return offset < this.bytes.length ? this.bytes.readUInt8(offset) : null;
|
||||
}
|
||||
|
||||
u16(offset: number): number | null {
|
||||
return offset + 2 <= this.bytes.length
|
||||
? this.bytes.readUInt16LE(offset)
|
||||
: null;
|
||||
}
|
||||
|
||||
u32(offset: number): number | null {
|
||||
return offset + 4 <= this.bytes.length
|
||||
? this.bytes.readUInt32LE(offset)
|
||||
@@ -459,8 +454,15 @@ export const readElfSymbols = async (
|
||||
signal?.throwIfAborted();
|
||||
}
|
||||
const base = table.offset + index * entrySize;
|
||||
const nameOffset = reader.u32(base)!;
|
||||
const sectionIndex = reader.u16(base + (is64 ? 6 : 14))!;
|
||||
const parsedSymbol = readElfSymbol(
|
||||
bytes.subarray(base, base + entrySize),
|
||||
is64 ? 64 : 32,
|
||||
true,
|
||||
);
|
||||
if (!parsedSymbol.ok) return failure(parsedSymbol.error.message);
|
||||
const symbol = parsedSymbol.value;
|
||||
const nameOffset = symbol.name_offset;
|
||||
const sectionIndex = symbol.section_index;
|
||||
const defined =
|
||||
sectionIndex === 0xfff1 ||
|
||||
(sectionIndex > 0 &&
|
||||
@@ -472,13 +474,16 @@ export const readElfSymbols = async (
|
||||
table.stringOffset + table.stringSize,
|
||||
);
|
||||
if (name === null || !wanted.has(name)) continue;
|
||||
const value = is64 ? reader.u64(base + 8) : reader.u32(base + 4);
|
||||
const size = is64 ? reader.u64(base + 16) : reader.u32(base + 8);
|
||||
if (value === null || size === null) {
|
||||
if (
|
||||
symbol.value > BigInt(Number.MAX_SAFE_INTEGER) ||
|
||||
symbol.size > BigInt(Number.MAX_SAFE_INTEGER)
|
||||
) {
|
||||
failures.add(`Symbol ${name} has unrepresentable bounds`);
|
||||
unresolved.add(name);
|
||||
continue;
|
||||
}
|
||||
const value = Number(symbol.value);
|
||||
const size = Number(symbol.size);
|
||||
const offset = toOffset(value, size);
|
||||
if (!offset.ok) {
|
||||
failures.add(`Symbol ${name} ${offset.error.reason}`);
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
type NativeSection,
|
||||
type NativeSectionDirectory,
|
||||
} from "../native/metadata/NativeSectionDirectory.js";
|
||||
import { readElfString, readElfSymbol } from "../native/metadata/ElfSymbol.js";
|
||||
import { readElfString } from "../native/metadata/ElfStringTable.js";
|
||||
import { readElfSymbol } from "../domain/elfSymbol.js";
|
||||
import {
|
||||
gpuCount,
|
||||
readGpuNativeDirectory,
|
||||
@@ -277,11 +278,17 @@ const cudaKernels = async (
|
||||
let extendedIndices: Buffer | null = null;
|
||||
for (let offset = 0; offset < bytes.length; offset += width) {
|
||||
index.budget.visit();
|
||||
const symbol = readElfSymbol(
|
||||
const parsedSymbol = readElfSymbol(
|
||||
bytes.subarray(offset, offset + width),
|
||||
directory.bits,
|
||||
directory.little,
|
||||
);
|
||||
if (!parsedSymbol.ok)
|
||||
throw new BinaryFormatFailure(
|
||||
parsedSymbol.error.kind,
|
||||
parsedSymbol.error.message,
|
||||
);
|
||||
const symbol = parsedSymbol.value;
|
||||
// STO_CUDA_ENTRY distinguishes launchable kernels from ordinary device functions.
|
||||
if (symbol.type !== 2 || (symbol.other & 0x10) === 0) continue;
|
||||
let sectionIndex = symbol.section_index;
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
} from "../metadata/BinaryContainer.js";
|
||||
import { architectureForElfMachine } from "../metadata/ElfImage.js";
|
||||
import { isUtf8 } from "node:buffer";
|
||||
import { readElfStringBytes, readElfSymbol } from "../metadata/ElfSymbol.js";
|
||||
import { readElfStringBytes } from "../metadata/ElfStringTable.js";
|
||||
import { readElfSymbol } from "../../domain/elfSymbol.js";
|
||||
import {
|
||||
readElfDirectoryFromBuffer,
|
||||
type NativeSection,
|
||||
@@ -548,11 +549,17 @@ const parseElfExports = (bytes: Buffer, artifact: Artifact): NativeExports => {
|
||||
const exports: ElfExport[] = [];
|
||||
for (let index = 1; index < count; index++) {
|
||||
const at = table + index * width;
|
||||
const symbol = readElfSymbol(
|
||||
const parsedSymbol = readElfSymbol(
|
||||
bytes.subarray(at, at + width),
|
||||
directory.bits,
|
||||
directory.little,
|
||||
);
|
||||
if (!parsedSymbol.ok)
|
||||
throw new BinaryFormatFailure(
|
||||
parsedSymbol.error.kind,
|
||||
parsedSymbol.error.message,
|
||||
);
|
||||
const symbol = parsedSymbol.value;
|
||||
const binding = ELF_BINDINGS[symbol.binding];
|
||||
const visibility = symbol.other & 3;
|
||||
// Undefined, local and hidden/internal symbols are not exports.
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
import { BinaryReader, BinaryFormatFailure } from "./BinaryContainer.js";
|
||||
/** Raw ELF symbol fields; GPU and other consumers own the meaning of processor-specific flags. */
|
||||
export const readElfSymbol = (
|
||||
bytes: Buffer,
|
||||
bits: 32 | 64,
|
||||
little: boolean,
|
||||
) => {
|
||||
const r = new BinaryReader(bytes, little);
|
||||
const wide = bits === 64;
|
||||
const info = r.range(wide ? 4 : 12, 1, "ELF symbol info")[0]!;
|
||||
return {
|
||||
name_offset: r.u32(0),
|
||||
binding: info >>> 4,
|
||||
type: info & 15,
|
||||
other: r.range(wide ? 5 : 13, 1, "ELF symbol other")[0]!,
|
||||
section_index: r.u16(wide ? 6 : 14),
|
||||
value: r.word(wide ? 8 : 4, bits),
|
||||
size: r.word(wide ? 16 : 8, bits),
|
||||
};
|
||||
};
|
||||
import { BinaryFormatFailure } from "./BinaryContainer.js";
|
||||
|
||||
export const readElfString = (bytes: Buffer, offset: number): string => {
|
||||
return readElfStringBytes(bytes, offset).toString("latin1");
|
||||
};
|
||||
Reference in new issue
Block a user