|  | // Copyright 2015 the V8 project authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | import { PROF_COLS, UNICODE_BLOCK } from "../src/constants"; | 
|  | import { SelectionBroker } from "../src/selection-broker"; | 
|  | import { TextView } from "../src/text-view"; | 
|  | import { MySelection } from "./selection"; | 
|  | import { anyToString, interpolate } from "./util"; | 
|  | import { InstructionSelectionHandler } from "./selection-handler"; | 
|  |  | 
|  | const toolboxHTML = `<div id="disassembly-toolbox"> | 
|  | <form> | 
|  | <label><input id="show-instruction-address" type="checkbox" name="instruction-address">Show addresses</label> | 
|  | <label><input id="show-instruction-binary" type="checkbox" name="instruction-binary">Show binary literal</label> | 
|  | <label><input id="highlight-gap-instructions" type="checkbox" name="instruction-binary">Highlight gap instructions</label> | 
|  | </form> | 
|  | </div>`; | 
|  |  | 
|  | export class DisassemblyView extends TextView { | 
|  | SOURCE_POSITION_HEADER_REGEX: any; | 
|  | addrEventCounts: any; | 
|  | totalEventCounts: any; | 
|  | maxEventCounts: any; | 
|  | posLines: Array<any>; | 
|  | instructionSelectionHandler: InstructionSelectionHandler; | 
|  | offsetSelection: MySelection; | 
|  | showInstructionAddressHandler: () => void; | 
|  | showInstructionBinaryHandler: () => void; | 
|  | highlightGapInstructionsHandler: () => void; | 
|  |  | 
|  | createViewElement() { | 
|  | const pane = document.createElement('div'); | 
|  | pane.setAttribute('id', "disassembly"); | 
|  | pane.innerHTML = | 
|  | `<pre id='disassembly-text-pre' class='prettyprint prettyprinted'> | 
|  | <ul class='disassembly-list nolinenums noindent'> | 
|  | </ul> | 
|  | </pre>`; | 
|  |  | 
|  | return pane; | 
|  | } | 
|  |  | 
|  | constructor(parentId, broker: SelectionBroker) { | 
|  | super(parentId, broker); | 
|  | const view = this; | 
|  | const ADDRESS_STYLE = { | 
|  | associateData: (text, fragment: HTMLElement) => { | 
|  | const matches = text.match(/(?<address>0?x?[0-9a-fA-F]{8,16})(?<addressSpace>\s+)(?<offset>[0-9a-f]+)(?<offsetSpace>\s*)/); | 
|  | const offset = Number.parseInt(matches.groups["offset"], 16); | 
|  | const instructionKind = view.sourceResolver.getInstructionKindForPCOffset(offset); | 
|  | fragment.dataset.instructionKind = instructionKind; | 
|  | fragment.title = view.sourceResolver.instructionKindToReadableName(instructionKind); | 
|  | const blockIds = view.sourceResolver.getBlockIdsForOffset(offset); | 
|  | const blockIdElement = document.createElement("SPAN"); | 
|  | blockIdElement.className = "block-id com linkable-text"; | 
|  | blockIdElement.innerText = ""; | 
|  | if (blockIds && blockIds.length > 0) { | 
|  | blockIds.forEach(blockId => view.addHtmlElementForBlockId(blockId, fragment)); | 
|  | blockIdElement.innerText = `B${blockIds.join(",")}:`; | 
|  | blockIdElement.dataset.blockId = `${blockIds.join(",")}`; | 
|  | } | 
|  | fragment.appendChild(blockIdElement); | 
|  | const addressElement = document.createElement("SPAN"); | 
|  | addressElement.className = "instruction-address"; | 
|  | addressElement.innerText = matches.groups["address"]; | 
|  | const offsetElement = document.createElement("SPAN"); | 
|  | offsetElement.innerText = matches.groups["offset"]; | 
|  | fragment.appendChild(addressElement); | 
|  | fragment.appendChild(document.createTextNode(matches.groups["addressSpace"])); | 
|  | fragment.appendChild(offsetElement); | 
|  | fragment.appendChild(document.createTextNode(matches.groups["offsetSpace"])); | 
|  | fragment.classList.add('tag'); | 
|  |  | 
|  | if (!Number.isNaN(offset)) { | 
|  | let pcOffset = view.sourceResolver.getKeyPcOffset(offset); | 
|  | if (pcOffset == -1) pcOffset = Number(offset); | 
|  | fragment.dataset.pcOffset = `${pcOffset}`; | 
|  | addressElement.classList.add('linkable-text'); | 
|  | offsetElement.classList.add('linkable-text'); | 
|  | } | 
|  | return true; | 
|  | } | 
|  | }; | 
|  | const UNCLASSIFIED_STYLE = { | 
|  | css: 'com' | 
|  | }; | 
|  | const NUMBER_STYLE = { | 
|  | css: ['instruction-binary', 'lit'] | 
|  | }; | 
|  | const COMMENT_STYLE = { | 
|  | css: 'com' | 
|  | }; | 
|  | const OPCODE_ARGS = { | 
|  | associateData: function (text, fragment) { | 
|  | fragment.innerHTML = text; | 
|  | const replacer = (match, hexOffset) => { | 
|  | const offset = Number.parseInt(hexOffset, 16); | 
|  | let keyOffset = view.sourceResolver.getKeyPcOffset(offset); | 
|  | if (keyOffset == -1) keyOffset = Number(offset); | 
|  | const blockIds = view.sourceResolver.getBlockIdsForOffset(offset); | 
|  | let block = ""; | 
|  | let blockIdData = ""; | 
|  | if (blockIds && blockIds.length > 0) { | 
|  | block = `B${blockIds.join(",")} `; | 
|  | blockIdData = `data-block-id="${blockIds.join(",")}"`; | 
|  | } | 
|  | return `<span class="tag linkable-text" data-pc-offset="${keyOffset}" ${blockIdData}>${block}${match}</span>`; | 
|  | }; | 
|  | const html = text.replace(/<.0?x?([0-9a-fA-F]+)>/g, replacer); | 
|  | fragment.innerHTML = html; | 
|  | return true; | 
|  | } | 
|  | }; | 
|  | const OPCODE_STYLE = { | 
|  | css: 'kwd' | 
|  | }; | 
|  | const BLOCK_HEADER_STYLE = { | 
|  | associateData: function (text, fragment) { | 
|  | if (view.sourceResolver.hasBlockStartInfo()) return false; | 
|  | const matches = /\d+/.exec(text); | 
|  | if (!matches) return true; | 
|  | const blockId = matches[0]; | 
|  | fragment.dataset.blockId = blockId; | 
|  | fragment.innerHTML = text; | 
|  | fragment.className = "com block"; | 
|  | return true; | 
|  | } | 
|  | }; | 
|  | const SOURCE_POSITION_HEADER_STYLE = { | 
|  | css: 'com' | 
|  | }; | 
|  | view.SOURCE_POSITION_HEADER_REGEX = /^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/; | 
|  | const patterns = [ | 
|  | [ | 
|  | [/^0?x?[0-9a-fA-F]{8,16}\s+[0-9a-f]+\s+/, ADDRESS_STYLE, 1], | 
|  | [view.SOURCE_POSITION_HEADER_REGEX, SOURCE_POSITION_HEADER_STYLE, -1], | 
|  | [/^\s+-- B\d+ start.*/, BLOCK_HEADER_STYLE, -1], | 
|  | [/^.*/, UNCLASSIFIED_STYLE, -1] | 
|  | ], | 
|  | [ | 
|  | [/^\s*[0-9a-f]+\s+/, NUMBER_STYLE, 2], | 
|  | [/^\s*[0-9a-f]+\s+[0-9a-f]+\s+/, NUMBER_STYLE, 2], | 
|  | [/^.*/, null, -1] | 
|  | ], | 
|  | [ | 
|  | [/^REX.W \S+\s+/, OPCODE_STYLE, 3], | 
|  | [/^\S+\s+/, OPCODE_STYLE, 3], | 
|  | [/^\S+$/, OPCODE_STYLE, -1], | 
|  | [/^.*/, null, -1] | 
|  | ], | 
|  | [ | 
|  | [/^\s+/, null], | 
|  | [/^[^;]+$/, OPCODE_ARGS, -1], | 
|  | [/^[^;]+/, OPCODE_ARGS, 4], | 
|  | [/^;/, COMMENT_STYLE, 5] | 
|  | ], | 
|  | [ | 
|  | [/^.+$/, COMMENT_STYLE, -1] | 
|  | ] | 
|  | ]; | 
|  | view.setPatterns(patterns); | 
|  |  | 
|  | const linkHandler = (e: MouseEvent) => { | 
|  | if (!(e.target instanceof HTMLElement)) return; | 
|  | const offsetAsString = typeof e.target.dataset.pcOffset != "undefined" ? e.target.dataset.pcOffset : e.target.parentElement.dataset.pcOffset; | 
|  | const offset = Number.parseInt(offsetAsString, 10); | 
|  | if ((typeof offsetAsString) != "undefined" && !Number.isNaN(offset)) { | 
|  | view.offsetSelection.select([offset], true); | 
|  | const nodes = view.sourceResolver.nodesForPCOffset(offset)[0]; | 
|  | if (nodes.length > 0) { | 
|  | e.stopPropagation(); | 
|  | if (!e.shiftKey) { | 
|  | view.selectionHandler.clear(); | 
|  | } | 
|  | view.selectionHandler.select(nodes, true); | 
|  | } else { | 
|  | view.updateSelection(); | 
|  | } | 
|  | } | 
|  | return undefined; | 
|  | }; | 
|  | view.divNode.addEventListener('click', linkHandler); | 
|  |  | 
|  | const linkHandlerBlock = e => { | 
|  | const blockId = e.target.dataset.blockId; | 
|  | if (typeof blockId != "undefined") { | 
|  | const blockIds = blockId.split(","); | 
|  | if (!e.shiftKey) { | 
|  | view.selectionHandler.clear(); | 
|  | } | 
|  | view.blockSelectionHandler.select(blockIds, true); | 
|  | } | 
|  | }; | 
|  | view.divNode.addEventListener('click', linkHandlerBlock); | 
|  |  | 
|  | this.offsetSelection = new MySelection(anyToString); | 
|  | const instructionSelectionHandler = { | 
|  | clear: function () { | 
|  | view.offsetSelection.clear(); | 
|  | view.updateSelection(); | 
|  | broker.broadcastClear(instructionSelectionHandler); | 
|  | }, | 
|  | select: function (instructionIds, selected) { | 
|  | view.offsetSelection.select(instructionIds, selected); | 
|  | view.updateSelection(); | 
|  | broker.broadcastBlockSelect(instructionSelectionHandler, instructionIds, selected); | 
|  | }, | 
|  | brokeredInstructionSelect: function (instructionIds, selected) { | 
|  | const firstSelect = view.offsetSelection.isEmpty(); | 
|  | const keyPcOffsets = view.sourceResolver.instructionsToKeyPcOffsets(instructionIds); | 
|  | view.offsetSelection.select(keyPcOffsets, selected); | 
|  | view.updateSelection(firstSelect); | 
|  | }, | 
|  | brokeredClear: function () { | 
|  | view.offsetSelection.clear(); | 
|  | view.updateSelection(); | 
|  | } | 
|  | }; | 
|  | this.instructionSelectionHandler = instructionSelectionHandler; | 
|  | broker.addInstructionHandler(instructionSelectionHandler); | 
|  |  | 
|  | const toolbox = document.createElement("div"); | 
|  | toolbox.id = "toolbox-anchor"; | 
|  | toolbox.innerHTML = toolboxHTML; | 
|  | view.divNode.insertBefore(toolbox, view.divNode.firstChild); | 
|  | const instructionAddressInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-address"); | 
|  | const lastShowInstructionAddress = window.sessionStorage.getItem("show-instruction-address"); | 
|  | instructionAddressInput.checked = lastShowInstructionAddress == 'true'; | 
|  | const showInstructionAddressHandler = () => { | 
|  | window.sessionStorage.setItem("show-instruction-address", `${instructionAddressInput.checked}`); | 
|  | for (const el of view.divNode.querySelectorAll(".instruction-address")) { | 
|  | el.classList.toggle("invisible", !instructionAddressInput.checked); | 
|  | } | 
|  | }; | 
|  | instructionAddressInput.addEventListener("change", showInstructionAddressHandler); | 
|  | this.showInstructionAddressHandler = showInstructionAddressHandler; | 
|  |  | 
|  | const instructionBinaryInput: HTMLInputElement = view.divNode.querySelector("#show-instruction-binary"); | 
|  | const lastShowInstructionBinary = window.sessionStorage.getItem("show-instruction-binary"); | 
|  | instructionBinaryInput.checked = lastShowInstructionBinary == 'true'; | 
|  | const showInstructionBinaryHandler = () => { | 
|  | window.sessionStorage.setItem("show-instruction-binary", `${instructionBinaryInput.checked}`); | 
|  | for (const el of view.divNode.querySelectorAll(".instruction-binary")) { | 
|  | el.classList.toggle("invisible", !instructionBinaryInput.checked); | 
|  | } | 
|  | }; | 
|  | instructionBinaryInput.addEventListener("change", showInstructionBinaryHandler); | 
|  | this.showInstructionBinaryHandler = showInstructionBinaryHandler; | 
|  |  | 
|  | const highlightGapInstructionsInput: HTMLInputElement = view.divNode.querySelector("#highlight-gap-instructions"); | 
|  | const lastHighlightGapInstructions = window.sessionStorage.getItem("highlight-gap-instructions"); | 
|  | highlightGapInstructionsInput.checked = lastHighlightGapInstructions == 'true'; | 
|  | const highlightGapInstructionsHandler = () => { | 
|  | window.sessionStorage.setItem("highlight-gap-instructions", `${highlightGapInstructionsInput.checked}`); | 
|  | view.divNode.classList.toggle("highlight-gap-instructions", highlightGapInstructionsInput.checked); | 
|  | }; | 
|  |  | 
|  | highlightGapInstructionsInput.addEventListener("change", highlightGapInstructionsHandler); | 
|  | this.highlightGapInstructionsHandler = highlightGapInstructionsHandler; | 
|  | } | 
|  |  | 
|  | updateSelection(scrollIntoView: boolean = false) { | 
|  | super.updateSelection(scrollIntoView); | 
|  | const keyPcOffsets = this.sourceResolver.nodesToKeyPcOffsets(this.selection.selectedKeys()); | 
|  | if (this.offsetSelection) { | 
|  | for (const key of this.offsetSelection.selectedKeys()) { | 
|  | keyPcOffsets.push(Number(key)); | 
|  | } | 
|  | } | 
|  | for (const keyPcOffset of keyPcOffsets) { | 
|  | const elementsToSelect = this.divNode.querySelectorAll(`[data-pc-offset='${keyPcOffset}']`); | 
|  | for (const el of elementsToSelect) { | 
|  | el.classList.toggle("selected", true); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | initializeCode(sourceText, sourcePosition: number = 0) { | 
|  | const view = this; | 
|  | view.addrEventCounts = null; | 
|  | view.totalEventCounts = null; | 
|  | view.maxEventCounts = null; | 
|  | view.posLines = new Array(); | 
|  | // Comment lines for line 0 include sourcePosition already, only need to | 
|  | // add sourcePosition for lines > 0. | 
|  | view.posLines[0] = sourcePosition; | 
|  | if (sourceText && sourceText != "") { | 
|  | const base = sourcePosition; | 
|  | let current = 0; | 
|  | const sourceLines = sourceText.split("\n"); | 
|  | for (let i = 1; i < sourceLines.length; i++) { | 
|  | // Add 1 for newline character that is split off. | 
|  | current += sourceLines[i - 1].length + 1; | 
|  | view.posLines[i] = base + current; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | initializePerfProfile(eventCounts) { | 
|  | const view = this; | 
|  | if (eventCounts !== undefined) { | 
|  | view.addrEventCounts = eventCounts; | 
|  |  | 
|  | view.totalEventCounts = {}; | 
|  | view.maxEventCounts = {}; | 
|  | for (const evName in view.addrEventCounts) { | 
|  | if (view.addrEventCounts.hasOwnProperty(evName)) { | 
|  | const keys = Object.keys(view.addrEventCounts[evName]); | 
|  | const values = keys.map(key => view.addrEventCounts[evName][key]); | 
|  | view.totalEventCounts[evName] = values.reduce((a, b) => a + b); | 
|  | view.maxEventCounts[evName] = values.reduce((a, b) => Math.max(a, b)); | 
|  | } | 
|  | } | 
|  | } else { | 
|  | view.addrEventCounts = null; | 
|  | view.totalEventCounts = null; | 
|  | view.maxEventCounts = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | showContent(data): void { | 
|  | console.time("disassembly-view"); | 
|  | super.initializeContent(data, null); | 
|  | this.showInstructionAddressHandler(); | 
|  | this.showInstructionBinaryHandler(); | 
|  | this.highlightGapInstructionsHandler(); | 
|  | console.timeEnd("disassembly-view"); | 
|  | } | 
|  |  | 
|  | // Shorten decimals and remove trailing zeroes for readability. | 
|  | humanize(num) { | 
|  | return num.toFixed(3).replace(/\.?0+$/, "") + "%"; | 
|  | } | 
|  |  | 
|  | processLine(line) { | 
|  | const view = this; | 
|  | let fragments = super.processLine(line); | 
|  |  | 
|  | // Add profiling data per instruction if available. | 
|  | if (view.totalEventCounts) { | 
|  | const matches = /^(0x[0-9a-fA-F]+)\s+\d+\s+[0-9a-fA-F]+/.exec(line); | 
|  | if (matches) { | 
|  | const newFragments = []; | 
|  | for (const event in view.addrEventCounts) { | 
|  | if (!view.addrEventCounts.hasOwnProperty(event)) continue; | 
|  | const count = view.addrEventCounts[event][matches[1]]; | 
|  | let str = " "; | 
|  | const cssCls = "prof"; | 
|  | if (count !== undefined) { | 
|  | const perc = count / view.totalEventCounts[event] * 100; | 
|  |  | 
|  | let col = { r: 255, g: 255, b: 255 }; | 
|  | for (let i = 0; i < PROF_COLS.length; i++) { | 
|  | if (perc === PROF_COLS[i].perc) { | 
|  | col = PROF_COLS[i].col; | 
|  | break; | 
|  | } else if (perc > PROF_COLS[i].perc && perc < PROF_COLS[i + 1].perc) { | 
|  | const col1 = PROF_COLS[i].col; | 
|  | const col2 = PROF_COLS[i + 1].col; | 
|  |  | 
|  | const val = perc - PROF_COLS[i].perc; | 
|  | const max = PROF_COLS[i + 1].perc - PROF_COLS[i].perc; | 
|  |  | 
|  | col.r = Math.round(interpolate(val, max, col1.r, col2.r)); | 
|  | col.g = Math.round(interpolate(val, max, col1.g, col2.g)); | 
|  | col.b = Math.round(interpolate(val, max, col1.b, col2.b)); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | str = UNICODE_BLOCK; | 
|  |  | 
|  | const fragment = view.createFragment(str, cssCls); | 
|  | fragment.title = event + ": " + view.humanize(perc) + " (" + count + ")"; | 
|  | fragment.style.color = "rgb(" + col.r + ", " + col.g + ", " + col.b + ")"; | 
|  |  | 
|  | newFragments.push(fragment); | 
|  | } else { | 
|  | newFragments.push(view.createFragment(str, cssCls)); | 
|  | } | 
|  | } | 
|  | fragments = newFragments.concat(fragments); | 
|  | } | 
|  | } | 
|  | return fragments; | 
|  | } | 
|  |  | 
|  | detachSelection() { return null; } | 
|  |  | 
|  | public searchInputAction(searchInput: HTMLInputElement, e: Event, onlyVisible: boolean): void { | 
|  | throw new Error("Method not implemented."); | 
|  | } | 
|  | } |