UNPKG

elflib

Version:
313 lines (312 loc) 16.9 kB
import { isRelocationSection, isStringSection, isSymbolSection } from './sections.js'; import { abiToString, elfFlagsToString, isaToString, elfTypeToString, sectionFlagsToString, sectionTypeToString, symbolBindingToString, symbolTypeToString, symbolVisibilityToString } from './strings.js'; import * as ELF from './types/index.js'; import { Table } from 'console-table-printer'; import { encode } from './encoding.js'; const hex = (n, prefix = '0x', pad = 0) => prefix + n.toString(16).toUpperCase().padStart(pad, '0'); export function hexdump(buf) { if (buf instanceof ArrayBuffer) buf = Buffer.from(buf, 0, buf.byteLength); const basestr = buf.toString('hex').toUpperCase(); if (!basestr) return ''; return basestr.match(/../g).join(' ').match(/(?:...?){1,16}/g).join('\n'); } function toHex(n, padamount = 0, lowercase = false) { if (n !== undefined) { const hexchars = n.toString(16); if (padamount === 0) { padamount = (typeof n === 'bigint') ? 8 : (hexchars.length <= 2) ? 2 : (hexchars.length <= 4) ? 4 : 8; } return lowercase ? `0x${hexchars.padStart(padamount, '0')}` : '0x' + `${hexchars.padStart(padamount, '0')}`.toUpperCase(); } else { return '<undefined>'; } } const DefaultDebugOptions = { printSections: false, printStrings: false, printSymbols: false, printRelocations: false }; /** Print debug information for an ELF file, similar to readelf or objdump. * @param {ELF.File} elf The parsed ELF file data to print debug info for. * @param {(boolean | DebugOptions)} [options=DefaultDebugOptions] If true, all debug info will be printed. * @returns {string} The debug info for the ELF file. */ export function debug(elf, options = DefaultDebugOptions) { let result = ''; if (options === false) options = DefaultDebugOptions; if (options === true) options = { printSections: true, printStrings: true, printSymbols: true, printRelocations: true }; if (!options.printSections && (options.printStrings || options.printSymbols || options.printRelocations)) options.printSections = true; if (elf) { const addrpad = elf.header.class ? elf.header.class * 8 : 8; result += `Class: ${elf.header.class ? elf.header.class === ELF.Class.ELF32 ? 'ELF32' : 'ELF64' : 'None'} (${elf.header.class})\n`; result += `Bits: ${elf.header.bits} bits\n`; result += `Data: ${elf.header.endian ? elf.header.endian === ELF.Endian.Big ? 'Big Endian' : 'Little Endian' : 'None'} (${elf.header.endian})\n`; result += `Version: ${elf.header.version}\n`; result += `OS/ABI: ${abiToString(elf.header.abi)} (${toHex(elf.header.abi)})\n`; result += `ABI version: ${elf.header.abiVersion}\n`; result += `Type: ${elfTypeToString(elf.header.type)} (${toHex(elf.header.type)})\n`; result += `ISA/machine: ${isaToString(elf.header.isa)} (${toHex(elf.header.isa)})\n`; result += `ISA/machine version: ${elf.header.isaVersion}\n`; result += `Entry Point: ${toHex(elf.header.entryPoint)}\n`; result += `Program header offset: ${toHex(elf.header.programHeadersOffset)}\n`; result += `Section header offset: ${toHex(elf.header.sectionHeadersOffset)}\n`; result += `Flags: ${elfFlagsToString(elf.header.isa, elf.header.flags)} (${toHex(elf.header.flags)})\n`; //TODO: result += `Program headers: ${elf.programHeaderEntrySize} bytes × ${elf.segments.length}\n`; result += `Section headers: ${elf.header.sectionHeadersEntrySize} bytes × ${elf.sections.length}\n`; result += `String table section index: ${elf.header.shstrIndex}\n`; // TODO: Segments support /*if (elf.segments.length) { result += '\n\nProgram Header Entries:\n\n'; if (elf.bits === 32) { result += ' # Type Offset VirtAddr PhysAddr FileSize MemSiz Align Flags\n'; } else { result += ' # Type Offset VirtAddr PhysAddr FileSize MemSiz Align Flags\n'; } for (const header of elf.segments) { result += ` ${header.index.toString().padEnd(3)} ` result += `${header.typeDescription.padEnd(20)} `; result += `${toHex(header.offset, addrpad)} `; result += `${toHex(header.vaddr, addrpad)} `; result += `${toHex(header.paddr, addrpad)} `; result += `${toHex(header.filesz, addrpad)} `; result += `${toHex(header.memsz, addrpad)} `; result += `${toHex(header.align, 8)} `; result += `${header.flagsDescription}\n`; } }*/ if (elf.sections.length) { result += '\n\nSections:\n\n'; if (elf.header.class === ELF.Class.ELF32) { result += ' # Name Type Address Offset Size EntSize Link Info Align Flags\n'; } else { result += ' # Name Type Address Offset Size EntSize Link Info Align Flags\n'; } for (const section of elf.sections) { result += ` ${section.index.toString().padEnd(3)} `; result += `${section.getName(elf).substr(0, 18).padEnd(18)} `; result += `${sectionTypeToString(section.type).padEnd(32)} `; result += `${toHex(section.addr, addrpad)} `; result += `${toHex(section.offset, addrpad)} `; result += `${toHex(section.size, addrpad)} `; result += `${toHex(section.entSize, addrpad)} `; result += `${(section.link || '').toString().padStart(4)} `; result += `${(section.info || '').toString().padStart(4)} `; result += `${toHex(section.addrAlign, 8)} `; result += `${sectionFlagsToString(section.flags)} (${section.flags})\n`; } } for (const section of elf.sections) { if (!options.printSections) break; result += `\n#${section.index} - ${sectionTypeToString(section.type)} section ${section.getName(elf)}:\n`; if (isSymbolSection(section) && options.printSymbols) { if (elf.header.class === ELF.Class.ELF32) { result += ' # Value Size Type Bind Visibility Name\n'; } else { result += ' # Value Info Type Bind Visibility Name\n'; } let ix = 0; for (const symbol of section.symbols) { result += ` ${(ix++).toString().padStart(5)} `; result += `${toHex(symbol.value, addrpad)} `; result += `${toHex(symbol.size, 8)} `; result += `${symbolTypeToString(symbol.type).padEnd(28)} `; result += `${symbolBindingToString(symbol.binding).padEnd(6)} `; result += `${symbolVisibilityToString(symbol.visibility).padEnd(10)} `; result += `${symbol.getName(elf)}\n`; } } else if (isStringSection(section) && options.printStrings) { for (const string in section.strings) { result += ` #${string} - ${section.strings[string]}\n`; } } else if (isRelocationSection(section) && options.printRelocations) { if (elf.header.class === ELF.Class.ELF32) { result += ' # Offset Size Type Bind Visibility Name\n'; } else { result += ' # Offset Size Type Bind Visibility Name\n'; } let ix = 0; for (const relocation of section.relocations) { result += ` ${(ix++).toString().padStart(5)} `; result += `${toHex(relocation.addr, addrpad)} `; result += `${toHex(relocation.info, addrpad)} `; result += '\n'; } } } } else { result += '<undefined>'; } return result; } export function printHeaderTable(elf) { const t = new Table({ title: 'ELF Header', rowSeparator: true, columns: [ { name: '0', alignment: 'left', color: 'white', title: 'Key' }, { name: '1', alignment: 'left', color: 'yellow', title: 'Value' }, { name: '2', alignment: 'left', color: 'cyan', title: 'Info' }, ] }); t.addRow(['Magic', hexdump(encode(elf.header.magic)), elf.header.magic.slice(1)]); t.addRow(['Class', elf.header.class, elf.header.class ? elf.header.class === ELF.Class.ELF32 ? 'ELF32' : 'ELF64' : 'None']); t.addRow(['Endian', elf.header.endian, elf.header.endian ? elf.header.endian === ELF.Endian.Little ? 'Little' : 'Big' : 'None']); t.addRow(['Version', elf.header.version, elf.header.version ? elf.header.version === ELF.Version.Current ? 'Current' : 'Unknown' : 'None']); t.addRow(['ABI', hex(elf.header.abi), abiToString(elf.header.abi)]); t.addRow(['ABI Version', hex(elf.header.abiVersion)]); t.addRow(['Type', hex(elf.header.type), elfTypeToString(elf.header.type)]); t.addRow(['ISA', hex(elf.header.isa), isaToString(elf.header.isa)]); t.addRow(['ISA Version', hex(elf.header.isaVersion)]); t.addRow(['Entrypoint', hex(elf.header.entryPoint)]); t.addRow(['Prog H. Offset', hex(elf.header.programHeadersOffset)]); t.addRow(['Sect H. Offset', hex(elf.header.sectionHeadersOffset)]); t.addRow(['Flags', hex(elf.header.flags), elfFlagsToString(elf.header.isa, elf.header.flags)]); t.addRow(['ELF Header Size', hex(elf.header.headerSize)]); t.addRow(['Prog H. size', hex(elf.header.programHeadersEntrySize)]); t.addRow(['Prog H. count', elf.header.programHeadersEntryCount]); t.addRow(['Size H. size', hex(elf.header.sectionHeadersEntrySize)]); t.addRow(['Size H. count', elf.header.sectionHeadersEntryCount]); t.addRow(['Size H. strtab Idx', elf.header.shstrIndex]); t.printTable(); } export function printSectionsTable(elf) { let shstrtab = elf.sections[elf.header.shstrIndex]; if (shstrtab?.type !== ELF.SectionType.StrTab) shstrtab = undefined; else if (shstrtab?.flags && shstrtab?.flags & ELF.SectionFlags.Compressed) shstrtab = null; const t = new Table({ title: `Number of Sections: ${elf.sections.length}`, rowSeparator: true, columns: [ { name: 'Index', alignment: 'left', color: 'white_bold' }, { name: 'Name', alignment: 'left' }, { name: 'Name Offs', alignment: 'left', color: 'yellow' }, { name: 'Type', alignment: 'left', color: 'yellow' }, { name: 'Flags', alignment: 'left', color: 'yellow' }, { name: 'Addr', alignment: 'left', color: 'yellow' }, { name: 'Offset', alignment: 'left', color: 'yellow' }, { name: 'Size', alignment: 'left', color: 'yellow' }, { name: 'Link', alignment: 'left', color: 'yellow' }, { name: 'Info', alignment: 'left', color: 'yellow' }, { name: 'Align', alignment: 'left', color: 'yellow' }, { name: 'Ent Size', alignment: 'left', color: 'yellow' }, ] }); elf.sections.forEach(section => { let color = 'white'; const name = section.getName(elf); if (section.type === ELF.SectionType.Null) color = 'red'; if (section.type === ELF.SectionType.SymTab) color = 'cyan'; if (section.type === ELF.SectionType.StrTab) color = 'blue'; if (section.type === ELF.SectionType.Rela) color = 'green'; if (section.type === ELF.SectionType.Rela) color = 'green'; if (section.type === ELF.SectionType.RPLCrcs) color = 'magenta'; if (section.type === ELF.SectionType.RPLFileInfo) color = 'magenta'; if (section.type === ELF.SectionType.NoBits) color = 'white_bold'; t.addRow({ 'Index': section.index, 'Name': shstrtab === null ? '<compressed>' : !shstrtab ? '<none>' : name, 'Name Offs': hex(section.nameOffset), 'Type': hex(section.type), 'Flags': hex(section.flags), 'Addr': hex(section.addr), 'Offset': hex(section.offset), 'Size': hex(section.size), 'Link': section.link, 'Info': section.info, 'Align': hex(section.addrAlign), 'Ent Size': hex(section.entSize) }, { color: color }); }); t.printTable(); } // TODO export function printSectionsCompressionInfoTable(elf) { let shstrtab = elf.sections[elf.header.shstrIndex]; if (shstrtab?.type !== ELF.SectionType.StrTab) shstrtab = undefined; else if (shstrtab?.flags && shstrtab?.flags & ELF.SectionFlags.Compressed) shstrtab = null; const sections = elf.sections.filter(section => section.flags & ELF.SectionFlags.Compressed); const t = new Table({ title: `Compressed Sections: ${sections.length}`, rowSeparator: true, columns: [ { name: 'Index', alignment: 'left', color: 'white_bold' }, { name: 'Name', alignment: 'left' }, { name: 'Type', alignment: 'left', color: 'yellow' }, { name: 'Offset', alignment: 'left', color: 'yellow' }, { name: 'Compressed', alignment: 'left', color: 'yellow' }, { name: 'Uncompressed', alignment: 'left', color: 'yellow' }, { name: 'Size Diff', alignment: 'left', color: 'green' }, { name: 'Compression Ratio', alignment: 'right', color: 'cyan' }, { name: 'Space Saving', alignment: 'right', color: 'blue' }, { name: 'Zlib Header', alignment: 'center', color: 'yellow' }, ] }); let data = { ratios: [0], savings: [0] }; sections.forEach(section => { let color = 'white'; if (section.type === ELF.SectionType.Null) color = 'red'; if (section.type === ELF.SectionType.SymTab) color = 'cyan'; if (section.type === ELF.SectionType.StrTab) color = 'blue'; if (section.type === ELF.SectionType.Rela) color = 'green'; if (section.type === ELF.SectionType.Rela) color = 'green'; if (section.type === ELF.SectionType.RPLCrcs) color = 'magenta'; if (section.type === ELF.SectionType.RPLFileInfo) color = 'magenta'; if (section.type === ELF.SectionType.NoBits) color = 'white_bold'; data.ratios.push(section.sizeUncompressed / section.size); data.savings.push((1 - section.size / section.sizeUncompressed) * 100); t.addRow({ 'Index': section.index, 'Name': section.getName(elf), 'Type': hex(section.type), 'Offset': hex(section.offset), 'Compressed': hex(section.size), 'Uncompressed': hex(section.sizeUncompressed), 'Size Diff': '-' + hex(section.sizeUncompressed - section.size), 'Compression Ratio': (section.sizeUncompressed / section.size).toFixed(1) + ' bytes : byte', 'Space Saving': ((1 - section.size / section.sizeUncompressed) * 100).toFixed(2) + '%', 'Zlib Header': hexdump(Buffer.from(section.data.buffer.slice(4, 6))) }, { color: color }); }); t.table.title = `Compressed Sections: ${sections.length} of ${elf.sections.length} | ` + `Avg. Compression Ratio: ${(data.ratios.reduce((x, y) => x + y) / data.ratios.length).toFixed(1)} bytes : byte | ` + `Avg. Space Saving: ${(data.savings.reduce((x, y) => x + y) / data.savings.length).toFixed(2)}% (Total: ${(data.savings.reduce((x, y) => x + y) / elf.sections.length).toFixed(2)}%)`; t.printTable(); }