All files / binary101/analyzers/elf go-function-seeds.ts

100% Statements 60/60
97.05% Branches 33/34
100% Functions 3/3
100% Lines 60/60

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 611x 1x 1x 1x 1x 1x 1x 1x 12x 12x 12x 12x 12x 55x 12x 53x 53x 53x 12x 16x 12x 12x 1x 17x 17x 17x 17x 17x 17x 17x 12x 17x 17x 17x 9x 17x 17x 8x 8x 17x 17x 1x 1x 65x 65x 65x 65x 65x 65x 65x 65x 21x 22x 17x 65x 14x 14x 14x 22x 65x  
import { createFileRangeReader, type FileRangeReader } from "../file-range-reader.js";
import { parseGoPcHeader } from "../go-runtime/parser.js";
import { parseGoFunctionTable } from "../go-runtime/tables.js";
import type { GoRuntimeAddressSpace } from "../go-runtime/types.js";
import { elfFileRange } from "./relocation-reader.js";
import type { ElfSectionHeader } from "./types.js";
import type { ElfDisassemblySeedGroup } from "./disassembly-seeds-types.js";
 
function addressSpace(reader: FileRangeReader, section: ElfSectionHeader,
  text: ElfSectionHeader, pointerSize: 4 | 8): GoRuntimeAddressSpace {
  return {
    pointerSize,
    isMappedRange: (address, size) => Number.isSafeInteger(size) && size >= 0 &&
      address >= section.addr && address + BigInt(size) <= section.addr + BigInt(reader.size),
    readMapped: async (address, size) => {
      const range = elfFileRange(address - section.addr, BigInt(size), reader.size);
      return range ? reader.readBytes(range.offset, range.size) : null;
    },
    isExecutableRange: (start, end) => start >= text.addr && end > start &&
      end <= text.addr + text.size
  };
}
 
async function collectStarts(reader: FileRangeReader, section: ElfSectionHeader,
  text: ElfSectionHeader): Promise<bigint[] | null> {
  const prefix = await reader.read(0, 8);
  if (prefix.byteLength !== 8) return null;
  const pointerSize = prefix.getUint8(7);
  if (pointerSize !== 4 && pointerSize !== 8) return null;
  const image = addressSpace(reader, section, text, pointerSize);
  const header = await parseGoPcHeader(image, section.addr);
  if (!header) return null;
  // parseGoPcHeader validates exactly five ordered, positive table offsets.
  const names = header.tableOffsets[0]!;
  const functions = header.tableOffsets[4]!;
  if (functions >= BigInt(reader.size)) return null;
  return (await parseGoFunctionTable(image, header,
    { address: section.addr + names, length: Number(header.tableOffsets[1]! - names) },
    { address: section.addr + functions, length: reader.size - Number(functions) }, text.addr))
    ?.map(fn => fn.start) ?? null;
}
 
export const collectGoFunctionSeeds = async (file: File, sections: ElfSectionHeader[],
  issues: string[]): Promise<ElfDisassemblySeedGroup[]> => {
  const section = sections.find(section => section.name === ".gopclntab" ||
    section.name === ".data.rel.ro.gopclntab");
  if (!section) return [];
  // Go's ELF adapter supplies .text.Addr even when pcHeader.textStart is unrelocated.
  // https://go.dev/src/cmd/internal/objfile/elf.go
  // SHF_EXECINSTR = 4: https://gabi.xinuos.com/elf/03-sheader.html
  const text = sections.find(section => section.name === ".text" && (section.flags & 4n) !== 0n);
  const range = elfFileRange(section.offset, section.size, file.size);
  const starts = range && text && elfFileRange(text.offset, text.size, file.size) &&
    await collectStarts(createFileRangeReader(file, range.offset, range.size), section, text);
  if (!starts) {
    issues.push("Go function metadata is invalid, truncated or uses an unsupported layout/byte order.");
    return [];
  }
  return [{ source: ".gopclntab functions", vaddrs: starts }];
};