| // 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'; |
| |
| export class MapFileReader { |
| /** |
| * 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. |
| * |
| * @param {string} mapFilename |
| */ |
| constructor(mapFilename) { |
| this.mapFilename = mapFilename; |
| this.preferredLoadAddress = 0x400000; |
| this.segmentMultiplier = 0x1000; |
| this.segmentOffsets = []; |
| this.segments = []; |
| this.isegments = []; |
| this.namedAddresses = []; |
| this.entryPoint = ''; |
| |
| this.lineNumbers = []; |
| this.reconstructedSegments = []; |
| |
| this.regexEntryPoint = /^\sentry point at\s*([\da-f]*):([\da-f]*)$/i; |
| } |
| |
| /** |
| * The function to call to load a map file (not async) |
| */ |
| run() { |
| if (this.mapFilename) { |
| this.loadMap(); |
| } |
| } |
| |
| /** |
| * |
| * @param {(string|boolean)} segment |
| * @param {number} address |
| * @returns {(object | boolean)} |
| */ |
| getLineInfoByAddress(segment, address) { |
| 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 false; |
| } |
| |
| /** |
| * |
| * @param {string} segment |
| * @returns {number} |
| */ |
| getSegmentOffset(segment) { |
| 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; |
| } |
| |
| /** |
| * |
| * @param {string} segment |
| * @param {number} address |
| */ |
| setSegmentOffset(segment, address) { |
| let info = false; |
| let idx = 0; |
| |
| for (idx = 0; idx < this.segments.length; idx++) { |
| 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 (idx = 0; idx < this.segmentOffsets.length; idx++) { |
| 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, |
| }); |
| } |
| |
| /** |
| * |
| * @param {string} unitName |
| * @returns {(object | boolean)} |
| */ |
| getSegmentInfoByUnitName(unitName) { |
| for (let idx = 0; idx < this.segments.length; idx++) { |
| const info = this.segments[idx]; |
| if (info.unitName === unitName) { |
| return info; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * |
| * @param {string} unitName |
| * @returns {(object | boolean)} |
| */ |
| getICodeSegmentInfoByUnitName(unitName) { |
| for (let idx = 0; idx < this.isegments.length; idx++) { |
| const info = this.isegments[idx]; |
| if (info.unitName === unitName) { |
| return info; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * |
| * @param {string} unitName |
| * @returns {number} |
| */ |
| getSegmentIdByUnitName(unitName) { |
| const info = this.getSegmentInfoByUnitName(unitName); |
| if (info) { |
| return info.id; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Get Segment info for exact address |
| * |
| * @param {(string|boolean)} segment |
| * @param {number} address |
| * @returns {(object | boolean)} |
| */ |
| getSegmentInfoByStartingAddress(segment, address) { |
| 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 false; |
| } |
| |
| /** |
| * Get Segment info for the segment where the given address is in |
| * |
| * @param {(string|boolean)} segment |
| * @param {number} address |
| * @returns {(object | boolean)} |
| */ |
| getSegmentInfoAddressIsIn(segment, address) { |
| 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 false; |
| } |
| |
| /** |
| * Get Segment info for the segment where the given address is in |
| * |
| * @param {string} segment |
| * @param {number} address |
| * @returns {(object | boolean)} |
| */ |
| getSegmentInfoAddressWithoutOffsetIsIn(segment, address) { |
| 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 false; |
| } |
| |
| /** |
| * |
| * @param {string} segment |
| * @param {number} address |
| * @returns {(object | boolean)} |
| */ |
| getSymbolAt(segment, address) { |
| 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 false; |
| } |
| |
| /** |
| * |
| * @param {string} segment |
| * @param {number} address |
| * @returns {(object | boolean)} |
| */ |
| getSymbolBefore(segment, address) { |
| let maxNamed = false; |
| |
| 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; |
| } |
| |
| /** |
| * |
| * @param {string} name |
| * @returns {(object | boolean)} |
| */ |
| getSymbolInfoByName(name) { |
| for (let idx = 0; idx < this.namedAddresses.length; idx++) { |
| const info = this.namedAddresses[idx]; |
| if (info.displayName === name) { |
| return info; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * |
| * @param {string} segment |
| * @param {string} address |
| */ |
| addressToObject(segment, address) { |
| 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 |
| * |
| * @param {string} line |
| */ |
| // eslint-disable-next-line no-unused-vars |
| tryReadingNamedAddress(line) {} |
| |
| /** |
| * Tries to match the given line to code segment information. |
| * Implementation specific, so this base function is empty |
| * |
| * @param {string} line |
| */ |
| // eslint-disable-next-line no-unused-vars |
| tryReadingCodeSegmentInfo(line) {} |
| |
| /** |
| * |
| * @param {string} line |
| */ |
| tryReadingEntryPoint(line) { |
| const matches = line.match(this.regexEntryPoint); |
| if (matches) { |
| this.entryPoint = { |
| segment: matches[1], |
| addressWithoutOffset: matches[2], |
| }; |
| } |
| } |
| |
| /** |
| * |
| * @param {string} line |
| */ |
| // eslint-disable-next-line no-unused-vars |
| tryReadingPreferredAddress(line) {} |
| |
| /** |
| * Retreives line number references from supplied Map line |
| * |
| * @param {string} line |
| * @returns {boolean} |
| */ |
| // eslint-disable-next-line no-unused-vars |
| tryReadingLineNumbers(line) { |
| return false; |
| } |
| |
| /** |
| * |
| * @param {string} line |
| */ |
| // eslint-disable-next-line no-unused-vars |
| isStartOfLineNumbers(line) { |
| return false; |
| } |
| |
| /** |
| * Tries to reconstruct segments information from contiguous named addresses |
| */ |
| reconstructSegmentsFromNamedAddresses() { |
| let currentUnit = 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) |
| * [{startAddress: int, startAddressHex: string, endAddress: int, endAddressHex: string}] |
| * |
| * @param {string} unitName |
| */ |
| getReconstructedUnitAddressSpace(unitName) { |
| let addressSpace = []; |
| |
| 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() |
| * |
| * @param {object[]} spaces |
| * @param {number} address |
| * @param {number} size |
| */ |
| isWithinAddressSpace(spaces, address, size) { |
| 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 |
| * |
| * @param {string} contents |
| */ |
| loadMapFromString(contents) { |
| const mapLines = utils.splitLines(contents); |
| |
| let readLineNumbersMode = false; |
| |
| let lineIdx = 0; |
| while (lineIdx < mapLines.length) { |
| const line = mapLines[lineIdx]; |
| |
| if (!readLineNumbersMode) { |
| this.tryReadingPreferredAddress(line); |
| this.tryReadingEntryPoint(line); |
| this.tryReadingCodeSegmentInfo(line); |
| this.tryReadingNamedAddress(line); |
| |
| if (this.isStartOfLineNumbers(line)) { |
| readLineNumbersMode = true; |
| lineIdx++; |
| } |
| } else { |
| if (!this.tryReadingLineNumbers(line)) { |
| readLineNumbersMode = false; |
| } |
| } |
| |
| 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()); |
| } |
| } |