// Copyright (c) 2018, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. import fs from 'fs'; import * as utils from '../utils.js'; type SegmentOffset = { segment: string; addressInt: number; address: string; segmentLength: number; }; export type Segment = { segment: string; addressInt: number; address: string; addressWithoutOffset: number; segmentLength: number; unitName?: string | false; id?: number; displayName?: string; }; type ReconstructedSegment = { addressInt: number; address: string; endAddress?: string; segmentLength: number; unitName?: string | false; }; type EntryPoint = { segment: string; addressWithoutOffset: string; }; type LineNumber = { addressInt: number; address: string; segment: string; addressWithoutOffset: number; lineNumber?: number; }; type AddressRangeInformation = { startAddress: number; startAddressHex: string; endAddress: number; endAddressHex: string; }; export class MapFileReader { preferredLoadAddress = 0x400000; segmentMultiplier = 0x1000; segmentOffsets: SegmentOffset[] = []; segments: Segment[] = []; isegments: Segment[] = []; namedAddresses: Segment[] = []; entryPoint: '' | EntryPoint = ''; lineNumbers: LineNumber[] = []; reconstructedSegments: ReconstructedSegment[] = []; regexEntryPoint = /^\sentry point at\s*([\da-f]*):([\da-f]*)$/i; /** * constructor of MapFileReader * Note that this is a base class and should be overriden. (see for example map-file-vs.js) * Note that this base class retains and uses state, * so when you want to read a new file you need to instantiate a new object. */ constructor(protected readonly mapFilename: string) {} /** * The function to call to load a map file (not async) */ run() { if (this.mapFilename) { this.loadMap(); } } getLineInfoByAddress(segment: string | undefined, address: number): LineNumber | undefined { for (let idx = 0; idx < this.lineNumbers.length; idx++) { const lineInfo = this.lineNumbers[idx]; if (!segment && lineInfo.addressInt === address) { return lineInfo; } else if (segment === lineInfo.segment && lineInfo.addressWithoutOffset === address) { return lineInfo; } } return undefined; } getSegmentOffset(segment: string): number { if (this.segmentOffsets.length > 0) { for (let idx = 0; idx < this.segmentOffsets.length; idx++) { const info = this.segmentOffsets[idx]; if (info.segment === segment) { return info.addressInt; } } } return this.preferredLoadAddress + parseInt(segment, 16) * this.segmentMultiplier; } setSegmentOffset(segment: string, address: number) { for (let idx = 0; idx < this.segments.length; idx++) { const info = this.segments[idx]; if (info.segment === segment) { this.segments[idx].addressInt = address; this.segments[idx].address = address.toString(16); } } if (this.segmentOffsets.length > 0) { for (let idx = 0; idx < this.segmentOffsets.length; idx++) { const info = this.segmentOffsets[idx]; if (info.segment === segment) { this.segmentOffsets[idx].addressInt = address; this.segmentOffsets[idx].address = address.toString(16); return; } } } this.segmentOffsets.push({ segment: segment, addressInt: address, address: address.toString(16), segmentLength: 0, }); } getSegmentInfoByUnitName(unitName: string): Segment | undefined { for (let idx = 0; idx < this.segments.length; idx++) { const info = this.segments[idx]; if (info.unitName === unitName) { return info; } } return undefined; } getICodeSegmentInfoByUnitName(unitName: string): Segment | undefined { for (let idx = 0; idx < this.isegments.length; idx++) { const info = this.isegments[idx]; if (info.unitName === unitName) { return info; } } return undefined; } getSegmentIdByUnitName(unitName: string): number | undefined { const info = this.getSegmentInfoByUnitName(unitName); if (info) { return info.id; } return 0; } /** * Get Segment info for exact address */ getSegmentInfoByStartingAddress(segment: string | undefined, address: number): Segment | undefined { for (let idx = 0; idx < this.segments.length; idx++) { const info = this.segments[idx]; if (!segment && info.addressInt === address) { return info; } else if (info.segment === segment && info.addressWithoutOffset === address) { return info; } } return undefined; } /** * Get Segment info for the segment where the given address is in */ getSegmentInfoAddressIsIn(segment: string | undefined, address: number): Segment | undefined { for (let idx = 0; idx < this.segments.length; idx++) { const info = this.segments[idx]; if (!segment && address >= info.addressInt && address < info.addressInt + info.segmentLength) { return info; } else if ( segment === info.segment && address >= info.addressWithoutOffset && address < info.addressWithoutOffset + info.segmentLength ) { return info; } } return undefined; } /** * Get Segment info for the segment where the given address is in */ getSegmentInfoAddressWithoutOffsetIsIn(segment: string, address: number): Segment | undefined { for (let idx = 0; idx < this.segments.length; idx++) { const info = this.segments[idx]; if ( segment === info.segment && address >= info.addressWithoutOffset && address < info.addressWithoutOffset + info.segmentLength ) { return info; } } return undefined; } getSymbolAt(segment: string | undefined, address: number): Segment | undefined { for (let idx = 0; idx < this.namedAddresses.length; idx++) { const info = this.namedAddresses[idx]; if (!segment && info.addressInt === address) { return info; } else if (segment === info.segment && info.addressWithoutOffset === address) { return info; } } return undefined; } getSymbolBefore(segment: string, address: number): Segment | undefined { let maxNamed: Segment | undefined; for (let idx = 0; idx < this.namedAddresses.length; idx++) { const info = this.namedAddresses[idx]; if (!segment && info.addressInt <= address) { if (!maxNamed || info.addressInt > maxNamed.addressInt) { maxNamed = info; } } else if (segment === info.segment && info.addressWithoutOffset <= address) { if (!maxNamed || info.addressInt > maxNamed.addressInt) { maxNamed = info; } } } return maxNamed; } getSymbolInfoByName(name: string): Segment | undefined { for (let idx = 0; idx < this.namedAddresses.length; idx++) { const info = this.namedAddresses[idx]; if (info.displayName === name) { return info; } } return undefined; } addressToObject(segment: string, address: string): LineNumber { const addressWithoutOffset = parseInt(address, 16); const addressWithOffset = this.getSegmentOffset(segment) + addressWithoutOffset; return { segment: segment, addressWithoutOffset: addressWithoutOffset, addressInt: addressWithOffset, address: addressWithOffset.toString(16), }; } /** * Try to match information about the address where a symbol is */ // eslint-disable-next-line no-unused-vars tryReadingNamedAddress(line: string) {} /** * Tries to match the given line to code segment information. * Implementation specific, so this base function is empty */ // eslint-disable-next-line no-unused-vars tryReadingCodeSegmentInfo(line: string) {} tryReadingEntryPoint(line: string) { const matches = line.match(this.regexEntryPoint); if (matches) { this.entryPoint = { segment: matches[1], addressWithoutOffset: matches[2], }; } } // eslint-disable-next-line no-unused-vars tryReadingPreferredAddress(line: string) {} // eslint-disable-next-line no-unused-vars tryReadingLineNumbers(line: string): boolean { return false; } // eslint-disable-next-line no-unused-vars isStartOfLineNumbers(line: string) { return false; } /** * Tries to reconstruct segments information from contiguous named addresses */ reconstructSegmentsFromNamedAddresses() { let currentUnit: false | string | undefined = false; let addressStart = 0; for (let idxSymbol = 0; idxSymbol < this.namedAddresses.length; ++idxSymbol) { const symbolObject = this.namedAddresses[idxSymbol]; if (symbolObject.addressInt < this.preferredLoadAddress) continue; if (!currentUnit) { addressStart = symbolObject.addressInt; currentUnit = symbolObject.unitName; } else if (symbolObject.unitName !== currentUnit) { const segmentLen = symbolObject.addressInt - addressStart; this.reconstructedSegments.push({ addressInt: addressStart, address: addressStart.toString(16), endAddress: (addressStart + segmentLen).toString(16), segmentLength: segmentLen, unitName: currentUnit, }); addressStart = symbolObject.addressInt; currentUnit = symbolObject.unitName; } if (idxSymbol === this.namedAddresses.length - 1) { this.reconstructedSegments.push({ addressInt: addressStart, address: addressStart.toString(16), segmentLength: -1, unitName: symbolObject.unitName, }); } } } /** * Returns an array of objects with address range information for a given unit (filename) */ getReconstructedUnitAddressSpace(unitName: string): AddressRangeInformation[] { const addressSpace: AddressRangeInformation[] = []; for (let idxSegment = 0; idxSegment < this.reconstructedSegments.length; ++idxSegment) { const segment = this.reconstructedSegments[idxSegment]; if (segment.unitName === unitName) { addressSpace.push({ startAddress: segment.addressInt, startAddressHex: segment.addressInt.toString(16), endAddress: segment.addressInt + segment.segmentLength, endAddressHex: (segment.addressInt + segment.segmentLength).toString(16), }); } } return addressSpace; } /** * Returns true if the address (and every address until address+size) is within * one of the given spaces. Spaces should be of format returned by getReconstructedUnitAddressSpace() */ isWithinAddressSpace(spaces: AddressRangeInformation[], address: number, size: number) { for (const spaceAddress of spaces) { if ( (address >= spaceAddress.startAddress && address < spaceAddress.endAddress) || (address < spaceAddress.startAddress && address + size >= spaceAddress.startAddress) ) { return true; } } return false; } /** * Retreives information from Map file contents */ loadMapFromString(contents: string) { const mapLines = utils.splitLines(contents); let readLineNumbersMode = false; let lineIdx = 0; while (lineIdx < mapLines.length) { const line = mapLines[lineIdx]; if (readLineNumbersMode) { if (!this.tryReadingLineNumbers(line)) { readLineNumbersMode = false; } } else { this.tryReadingPreferredAddress(line); this.tryReadingEntryPoint(line); this.tryReadingCodeSegmentInfo(line); this.tryReadingNamedAddress(line); if (this.isStartOfLineNumbers(line)) { readLineNumbersMode = true; lineIdx++; } } lineIdx++; } this.reconstructSegmentsFromNamedAddresses(); } /** * Reads the actual mapfile from disk synchronously and load it into this class */ loadMap() { const data = fs.readFileSync(this.mapFilename); this.loadMapFromString(data.toString()); } }