mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
9387282274
Parse bounded GNU file-scope FP/MSA attributes once, retain absent versus uninspected declarations, reject conflicts and unsupported argument ABIs, and include observed facts in profile identity. LE/BE filesystem and profile regressions pass; real Ghidra/MIPS behavior remains unverified. Fixes #3096 Co-authored-by: Aurelio Bernal <aurelio@bernalr.com>
116 lines
4.1 KiB
TypeScript
116 lines
4.1 KiB
TypeScript
import type { MipsGnuAttributes } from "../domain/binaryTargetTypes.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
|
|
/** Bound the section read before its declared size can drive allocation. */
|
|
export const MIPS_GNU_ATTRIBUTES_MAX_BYTES = 4096;
|
|
|
|
const FORMAT_VERSION = 0x41;
|
|
const TAG_FILE = 1;
|
|
const TAG_GNU_MIPS_ABI_FP = 4;
|
|
const TAG_GNU_MIPS_ABI_MSA = 8;
|
|
|
|
/**
|
|
* Read only GNU file-scope MIPS ABI attributes REA understands. Unknown
|
|
* vendors, scopes and tags fail closed; their declarations are not skipped.
|
|
*/
|
|
export const parseMipsGnuAttributes = (
|
|
bytes: Buffer,
|
|
littleEndian: boolean,
|
|
): Result<MipsGnuAttributes, string> => {
|
|
if (bytes.length === 0 || bytes[0] !== FORMAT_VERSION)
|
|
return err("unsupported MIPS GNU attribute section format version");
|
|
|
|
const readU32 = (offset: number): number =>
|
|
littleEndian ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
|
|
const readUleb = (
|
|
offset: number,
|
|
end: number,
|
|
): { readonly value: number; readonly next: number } | undefined => {
|
|
let value = 0;
|
|
for (let index = 0; index < 5 && offset + index < end; index += 1) {
|
|
const byte = bytes[offset + index]!;
|
|
value += (byte & 0x7f) * 2 ** (7 * index);
|
|
if ((byte & 0x80) === 0)
|
|
return value <= 0xffffffff
|
|
? { value, next: offset + index + 1 }
|
|
: undefined;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
let fpAbi: number | null = null;
|
|
let msa: number | null = null;
|
|
let vendorSeen = false;
|
|
let position = 1;
|
|
while (position < bytes.length) {
|
|
if (bytes.length - position < 4)
|
|
return err("truncated MIPS GNU attribute subsection length");
|
|
const length = readU32(position);
|
|
if (length < 4 || length > bytes.length - position)
|
|
return err(
|
|
"MIPS GNU attribute subsection length does not fit the section",
|
|
);
|
|
const end = position + length;
|
|
const vendorEnd = bytes.indexOf(0, position + 4);
|
|
if (vendorEnd < 0 || vendorEnd >= end)
|
|
return err("unterminated MIPS GNU attribute vendor name");
|
|
if (bytes.toString("latin1", position + 4, vendorEnd) !== "gnu")
|
|
return err(
|
|
"MIPS ELF declares non-GNU vendor attributes that REA does not inspect",
|
|
);
|
|
if (vendorSeen)
|
|
return err("duplicate MIPS GNU attribute vendor subsections");
|
|
vendorSeen = true;
|
|
|
|
let cursor = vendorEnd + 1;
|
|
while (cursor < end) {
|
|
const scopeStart = cursor;
|
|
const scope = readUleb(cursor, end);
|
|
if (scope === undefined)
|
|
return err("malformed MIPS GNU attribute scope tag");
|
|
if (scope.value !== TAG_FILE)
|
|
return err(
|
|
"MIPS GNU attributes scoped to sections or symbols are not inspected by REA",
|
|
);
|
|
if (end - scope.next < 4)
|
|
return err("truncated MIPS GNU attribute scope length");
|
|
const scopeLength = readU32(scope.next);
|
|
if (
|
|
scopeLength < scope.next + 4 - scopeStart ||
|
|
scopeLength > end - scopeStart
|
|
)
|
|
return err(
|
|
"MIPS GNU attribute scope length does not fit its subsection",
|
|
);
|
|
const scopeEnd = scopeStart + scopeLength;
|
|
cursor = scope.next + 4;
|
|
while (cursor < scopeEnd) {
|
|
const tag = readUleb(cursor, scopeEnd);
|
|
if (tag === undefined) return err("malformed MIPS GNU attribute tag");
|
|
if (
|
|
tag.value !== TAG_GNU_MIPS_ABI_FP &&
|
|
tag.value !== TAG_GNU_MIPS_ABI_MSA
|
|
)
|
|
return err(
|
|
`MIPS ELF declares GNU attribute tag ${tag.value}, which REA does not inspect`,
|
|
);
|
|
const value = readUleb(tag.next, scopeEnd);
|
|
if (value === undefined)
|
|
return err("malformed MIPS GNU attribute value");
|
|
if (tag.value === TAG_GNU_MIPS_ABI_FP) {
|
|
if (fpAbi !== null)
|
|
return err("duplicate MIPS GNU floating-point ABI attributes");
|
|
fpAbi = value.value;
|
|
} else {
|
|
if (msa !== null) return err("duplicate MIPS GNU MSA ABI attributes");
|
|
msa = value.value;
|
|
}
|
|
cursor = value.next;
|
|
}
|
|
cursor = scopeEnd;
|
|
}
|
|
position = end;
|
|
}
|
|
return ok({ fpAbi, msa });
|
|
};
|