// 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 {MapFileReader, Segment} from './mapfiles/map-file.js'; export class PELabelReconstructor { public readonly asmLines: string[]; private readonly addressesToLabel: string[]; private readonly dontLabelUnmappedAddresses: boolean; private readonly needsReconstruction: boolean; private readonly mapFileReader: MapFileReader; private readonly addressRegex: RegExp; private readonly jumpRegex: RegExp; private readonly callRegex: RegExp; private readonly int3Regex: RegExp; constructor( asmLines: string[], dontLabelUnmappedAddresses: boolean, mapFileReader: MapFileReader, needsReconstruction = true, ) { this.asmLines = asmLines; this.addressesToLabel = []; this.dontLabelUnmappedAddresses = dontLabelUnmappedAddresses; this.addressRegex = /^\s*([\da-f]*):/i; this.jumpRegex = /(\sj[a-z]*)(\s*)0x([\da-f]*)/i; this.callRegex = /(\scall)(\s*)0x([\da-f]*)/i; this.int3Regex = /\tcc\s*\tint3\s*$/i; this.mapFileReader = mapFileReader; this.needsReconstruction = needsReconstruction; } /** * Start reconstructing labels using the mapfile and remove unneccessary assembly * */ run(_unitName?: string) { this.mapFileReader.run(); //this.deleteEverythingBut(unitName); this.deleteSystemUnits(); this.shortenInt3s(); this.collectJumpsAndCalls(); this.insertLabels(); } /** * Remove any alignment NOP/int3 opcodes and replace them by a single line "..." */ shortenInt3s() { let lineIdx = 0; let inInt3 = false; while (lineIdx < this.asmLines.length) { const line = this.asmLines[lineIdx]; if (this.int3Regex.test(line)) { if (inInt3) { this.asmLines.splice(lineIdx, 1); lineIdx--; } else { inInt3 = true; this.asmLines[lineIdx] = '...'; } } else { inInt3 = false; } lineIdx++; } } /** * Remove any assembly or data that isn't part of the given unit */ deleteEverythingBut(unitName: string) { if (this.needsReconstruction) { const unitAddressSpaces = this.mapFileReader.getReconstructedUnitAddressSpace(unitName); for (let idx = 0; idx < this.mapFileReader.reconstructedSegments.length; idx++) { const info = this.mapFileReader.reconstructedSegments[idx]; if (info.unitName !== unitName) { if (info.segmentLength > 0) { if ( !this.mapFileReader.isWithinAddressSpace( unitAddressSpaces, info.addressInt, info.segmentLength, ) ) { this.deleteLinesBetweenAddresses(info.addressInt, info.addressInt + info.segmentLength); } } } } } else { let idx, info; for (idx = 0; idx < this.mapFileReader.segments.length; idx++) { info = this.mapFileReader.segments[idx]; if (info.unitName !== unitName) { this.deleteLinesBetweenAddresses(info.addressInt, info.addressInt + info.segmentLength); } } for (idx = 0; idx < this.mapFileReader.isegments.length; idx++) { info = this.mapFileReader.isegments[idx]; if (info.unitName !== unitName) { this.deleteLinesBetweenAddresses(info.addressInt, info.addressInt + info.segmentLength); } } } } deleteSystemUnits() { const systemUnits = new Set(['SysInit.pas', 'System.pas', 'SysUtils.pas', 'Classes.pas']); let idx, info; for (idx = 0; idx < this.mapFileReader.segments.length; idx++) { info = this.mapFileReader.segments[idx]; if (systemUnits.has(info.unitName)) { this.deleteLinesBetweenAddresses(info.addressInt, info.addressInt + info.segmentLength); } } for (idx = 0; idx < this.mapFileReader.isegments.length; idx++) { info = this.mapFileReader.isegments[idx]; if (systemUnits.has(info.unitName)) { this.deleteLinesBetweenAddresses(info.addressInt, info.addressInt + info.segmentLength); } } } deleteLinesBetweenAddresses(beginAddress: number, endAddress?: number) { let startIdx = -1; let linesRemoved = false; let lineIdx = 0; while (lineIdx < this.asmLines.length) { const line = this.asmLines[lineIdx]; const matches = line.match(this.addressRegex); if (matches) { const lineAddr = parseInt(matches[1], 16); if (startIdx === -1 && lineAddr >= beginAddress) { startIdx = lineIdx; if (line.endsWith(':') || line.endsWith('<.text>:') || line.endsWith('<.itext>:')) { startIdx++; } } else if (endAddress && lineAddr >= endAddress) { this.asmLines.splice(startIdx, lineIdx - startIdx - 1); linesRemoved = true; break; } } lineIdx++; } if (!linesRemoved && startIdx !== -1) { this.asmLines.splice(startIdx, this.asmLines.length - startIdx); } } /** * Replaces an address used in a jmp or call instruction by its label. * Does not replace an address if it has an offset. */ addAddressAsLabelAndReplaceLine(lineIdx: number, regex: RegExp) { const line = this.asmLines[lineIdx]; const matches = line.match(regex); if (matches) { const address = matches[3]; if (!address.includes('+') && !address.includes('-')) { let labelName = 'L' + address; const namedAddr = this.mapFileReader.getSymbolAt(undefined, parseInt(address, 16)); if (namedAddr && namedAddr.displayName) { labelName = namedAddr.displayName; } if (!this.dontLabelUnmappedAddresses || namedAddr) { this.addressesToLabel.push(address); this.asmLines[lineIdx] = line.replace(regex, ' ' + matches[1] + matches[2] + labelName); } } } } /** * Collects addresses that are referred to by the assembly, through jump and call instructions */ collectJumpsAndCalls() { for (let lineIdx = 0; lineIdx < this.asmLines.length; lineIdx++) { this.addAddressAsLabelAndReplaceLine(lineIdx, this.jumpRegex); this.addAddressAsLabelAndReplaceLine(lineIdx, this.callRegex); } } /** * Injects labels into the assembly where addresses are referred to * if an address doesn't have a mapped name, it is called */ insertLabels() { let currentSegment: Segment | undefined; let segmentChanged = false; let lineIdx = 0; while (lineIdx < this.asmLines.length) { const line = this.asmLines[lineIdx]; const matches = line.match(this.addressRegex); if (matches) { const addressStr = matches[1]; const address = parseInt(addressStr, 16); const segmentInfo = this.mapFileReader.getSegmentInfoByStartingAddress(undefined, address); if (segmentInfo) { currentSegment = segmentInfo; segmentChanged = true; } let namedAddr: Segment | undefined; let labelLine: string | undefined; const isReferenced = this.addressesToLabel.indexOf(addressStr); if (isReferenced === -1) { // we might have missed the reference to this address, // but if it's listed as a symbol, we should still label it. // todo: the call might be in <.itext>, should we include that part of the assembly? namedAddr = this.mapFileReader.getSymbolAt(undefined, address); if (namedAddr) { labelLine = matches[1] + ' <' + namedAddr.displayName + '>:'; this.asmLines.splice(lineIdx, 0, labelLine); lineIdx++; } } else { labelLine = matches[1] + ' :'; namedAddr = this.mapFileReader.getSymbolAt(undefined, address); if (namedAddr) { labelLine = matches[1] + ' <' + namedAddr.displayName + '>:'; } if (!this.dontLabelUnmappedAddresses || namedAddr) { this.asmLines.splice(lineIdx, 0, labelLine); lineIdx++; } } const lineInfo = this.mapFileReader.getLineInfoByAddress(undefined, address); if (lineInfo && currentSegment && currentSegment.unitName) { this.asmLines.splice(lineIdx, 0, '/app/' + currentSegment.unitName + ':' + lineInfo.lineNumber); lineIdx++; } else if (segmentChanged && currentSegment) { this.asmLines.splice(lineIdx, 0, '/app/' + currentSegment.unitName + ':0'); lineIdx++; } } lineIdx++; } } }