Files
rea/src/domain/elfFixedHeader.ts
T

103 lines
3.4 KiB
TypeScript

import type {
BinaryArchitecture,
RawOnlyArchitecture,
} from "./binaryTargetTypes.js";
import { err, ok, type Result } from "./result.js";
/** CPU families an ELF e_machine can name; arm64_32 is a Mach-O-only CPU type. */
type ElfArchitecture = Exclude<
BinaryArchitecture,
RawOnlyArchitecture | "arm64_32"
>;
export interface ElfFixedHeader {
readonly elfClass: 32 | 64;
readonly byteOrder: "little" | "big";
readonly headerSize: number;
readonly machine: number;
readonly type: number;
readonly flags: number;
readonly osAbi: number;
readonly architecture: ElfArchitecture | null;
}
/**
* Validate the common, fixed ELF header commitments.
*
* `bytes` may be a bounded prefix. `totalBytes` is the containing file size,
* so the declared header extent is checked against the file without requiring
* the whole declared extent to be present in this prefix.
*/
export const readElfFixedHeader = (
bytes: Buffer,
totalBytes = bytes.length,
): Result<ElfFixedHeader, string> => {
if (bytes.length >= 4 && bytes.toString("hex", 0, 4) !== "7f454c46")
return err("not an ELF image");
if (bytes.length < 16) return err("ELF identification is truncated");
const classByte = bytes[4];
if (classByte !== 1 && classByte !== 2) return err("unsupported ELF class");
const dataByte = bytes[5];
if (dataByte !== 1 && dataByte !== 2)
return err("unsupported ELF endianness");
if (bytes[6] !== 1) return err("unsupported ELF identification version");
const elfClass = classByte === 1 ? 32 : 64;
const minimumSize = elfClass === 32 ? 52 : 64;
if (
!Number.isSafeInteger(totalBytes) ||
totalBytes < bytes.length ||
totalBytes < minimumSize ||
bytes.length < minimumSize
)
return err(
`truncated ELF header: an ELF${elfClass} header needs ${minimumSize} bytes; the file has ${totalBytes}`,
);
const littleEndian = dataByte === 1;
const readU16 = (offset: number): number =>
littleEndian ? bytes.readUInt16LE(offset) : bytes.readUInt16BE(offset);
const readU32 = (offset: number): number =>
littleEndian ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
const machine = readU16(18);
// NVIDIA CUBIN uses ELFOSABI_CUDA and a vendor ABI version in e_version.
if (readU32(20) !== 1 && !(machine === 190 && bytes[7] === 0x33))
return err("unsupported ELF header version");
const headerSize = readU16(elfClass === 32 ? 40 : 52);
if (headerSize < minimumSize || headerSize > totalBytes)
return err("invalid ELF header size");
return ok({
elfClass,
byteOrder: littleEndian ? "little" : "big",
headerSize,
machine,
type: readU16(16),
flags: readU32(elfClass === 32 ? 36 : 48),
osAbi: bytes[7]!,
architecture: elfArchitecture(machine) ?? null,
});
};
export const elfArchitecture = (
machine: number,
): ElfArchitecture | undefined => {
switch (machine) {
case 3:
return "x86";
case 62:
return "x86_64";
case 8: // EM_MIPS: ISA, ABI and byte order remain separate ELF facts.
return "mips";
case 20: // EM_PPC; byte order and processor variant remain ELF declarations.
return "powerpc";
case 40:
return "arm";
case 164: // EM_HEXAGON: Qualcomm Hexagon DSP (QDSP6).
return "hexagon";
case 183:
return "arm64";
case 243: // EM_RISCV: XLEN, byte order and e_flags remain separate ELF facts.
return "riscv";
}
return undefined;
};