blob: 246d8de1c52e3665311362aceb1d8b53a6295df8 [file] [log] [blame] [raw]
// 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.
"use strict";
var fs = require("fs"),
utils = require("./utils");
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*([0-9a-f]*):([0-9a-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 (var idx = 0; idx < this.lineNumbers.length; idx++) {
var 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
*/
tryReadingNamedAddress() {
}
/**
* Tries to match the given line to code segment information.
* Implementation specific, so this base function is empty
* @param {string} line
*/
tryReadingCodeSegmentInfo() {
}
/**
*
* @param {string} line
*/
tryReadingEntryPoint(line) {
const matches = line.match(this.regexEntryPoint);
if (matches) {
this.entryPoint = {
segment: matches[1],
addressWithoutOffset: matches[2]
};
}
}
/**
*
* @param {string} line
*/
tryReadingPreferredAddress() {
}
/**
* Retreives line number references from supplied Map line
* @param {string} line
* @returns {boolean}
*/
tryReadingLineNumbers() {
return false;
}
/**
*
* @param {string} line
*/
isStartOfLineNumbers() {
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 {array of objects} spaces
* @param {int} address
* @param {int} size
*/
isWithinAddressSpace(spaces, address, size) {
for (let idxSpace = 0; idxSpace < spaces.length; ++idxSpace) {
const spaceAddress = spaces[idxSpace];
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());
}
}
exports.MapFileReader = MapFileReader;