mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-28 07:57:43 -05:00
458 lines
15 KiB
TypeScript
458 lines
15 KiB
TypeScript
// 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 'node: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;
|
|
}
|
|
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 + Number.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;
|
|
}
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
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 = Number.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
|
|
*/
|
|
|
|
tryReadingNamedAddress(line: string) {}
|
|
|
|
/**
|
|
* Tries to match the given line to code segment information.
|
|
* Implementation specific, so this base function is empty
|
|
*/
|
|
|
|
tryReadingCodeSegmentInfo(line: string) {}
|
|
|
|
tryReadingEntryPoint(line: string) {
|
|
const matches = line.match(this.regexEntryPoint);
|
|
if (matches) {
|
|
this.entryPoint = {
|
|
segment: matches[1],
|
|
addressWithoutOffset: matches[2],
|
|
};
|
|
}
|
|
}
|
|
|
|
tryReadingPreferredAddress(line: string) {}
|
|
|
|
tryReadingLineNumbers(line: string): boolean {
|
|
return false;
|
|
}
|
|
|
|
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());
|
|
}
|
|
}
|