Bug 1721109 - Move ProfilerGetSymbols code out of toolkit/components/extensions and into devtools directories. r=canaltinova

Differential Revision: https://phabricator.services.mozilla.com/D120182
This commit is contained in:
Markus Stange 2021-07-29 20:28:44 +00:00
parent a058bd1bbf
commit 8b439f0bce
10 changed files with 194 additions and 193 deletions

View file

@ -79,6 +79,9 @@ devtools/client/webconsole/test/browser/code_bundle_invalidmap.js
devtools/server/tests/xpcshell/setBreakpoint*
devtools/server/tests/xpcshell/sourcemapped.js
# Ignore generated code from wasm-bindgen
devtools/client/performance-new/profiler_get_symbols.js
# Testing syntax error
devtools/client/webconsole/test/browser/test-syntaxerror-worklet.js
@ -185,9 +188,6 @@ testing/web-platform/
# toolkit/ exclusions
# Ignore generated code from wasm-bindgen
toolkit/components/extensions/profiler_get_symbols.js
# Intentionally invalid JS
toolkit/components/workerloader/tests/moduleF-syntax-error.js

View file

@ -32,8 +32,6 @@ declare namespace MockedExports {
typeof import("resource://gre/modules/osfile.jsm");
"resource://gre/modules/AppConstants.jsm":
typeof import("resource://gre/modules/AppConstants.jsm");
"resource://gre/modules/ProfilerGetSymbols.jsm":
typeof import("resource://gre/modules/ProfilerGetSymbols.jsm");
"resource:///modules/CustomizableUI.jsm":
typeof import("resource:///modules/CustomizableUI.jsm")
"resource:///modules/CustomizableWidgets.jsm":
@ -189,16 +187,6 @@ declare namespace MockedExports {
decorate: (target: object) => void;
};
const ProfilerGetSymbolsJSM: {
ProfilerGetSymbols: {
getSymbolTable: (
path: string,
debugPath: string,
breakpadId: string
) => any;
};
};
const AppConstantsJSM: {
AppConstants: {
platform: string;
@ -359,10 +347,6 @@ declare module "resource://gre/modules/AppConstants.jsm" {
export = MockedExports.AppConstantsJSM;
}
declare module "resource://gre/modules/ProfilerGetSymbols.jsm" {
export = MockedExports.ProfilerGetSymbolsJSM;
}
declare module "resource://gre/modules/WebChannel.jsm" {
export = MockedExports.WebChannelJSM;
}
@ -444,6 +428,8 @@ declare interface ChromeWindow extends Window {
) => void;
}
declare class ChromeWorker extends Worker {}
declare interface MenuListElement extends XULElement {
value: string;
disabled: boolean;

View file

@ -499,3 +499,18 @@ export interface FeatureDescription {
// This will give a reason if the feature is disabled.
disabledReason?: string;
}
export type SymbolicationWorkerError = {
name: string;
message: string;
fileName?: string;
lineNumber?: number;
};
export type SymbolicationWorkerReplyData =
| {
result: SymbolTableAsTuple;
}
| {
error: SymbolicationWorkerError;
};

View file

@ -30,8 +30,6 @@ const lazy = createLazyLoaders({
Chrome: () => require("chrome"),
Services: () => require("Services"),
OS: () => ChromeUtils.import("resource://gre/modules/osfile.jsm"),
ProfilerGetSymbols: () =>
ChromeUtils.import("resource://gre/modules/ProfilerGetSymbols.jsm"),
PerfSymbolication: () =>
ChromeUtils.import(
"resource://devtools/client/performance-new/symbolication.jsm.js"

View file

@ -14,6 +14,8 @@ DevToolsModules(
"browser.js",
"initializer.js",
"panel.js",
"profiler_get_symbols.js",
"symbolication-worker.js",
"symbolication.jsm.js",
"typescript-lazy-load.jsm.js",
"utils.js",

View file

@ -10,7 +10,7 @@
importScripts(
"resource://gre/modules/osfile.jsm",
"resource://gre/modules/profiler_get_symbols.js"
"resource://devtools/client/performance-new/profiler_get_symbols.js"
);
// This worker uses the wasm module that was generated from https://github.com/mstange/profiler-get-symbols.
@ -20,6 +20,7 @@ importScripts(
// the wasm code, and returns the symbol table or an error. Then it shuts down
// itself.
/* eslint camelcase: 0*/
const { WasmMemBuffer, get_compact_symbol_table } = wasm_bindgen;
// Read an open OS.File instance into the Uint8Array dataBuf.
@ -103,7 +104,11 @@ onmessage = async e => {
}
try {
let output = get_compact_symbol_table(binaryData, debugData, breakpadId);
const output = get_compact_symbol_table(
binaryData,
debugData,
breakpadId
);
const result = [
output.take_addr(),
output.take_index(),

View file

@ -13,14 +13,172 @@ const { createLazyLoaders } = ChromeUtils.import(
* @typedef {import("./@types/perf").PerfFront} PerfFront
* @typedef {import("./@types/perf").SymbolTableAsTuple} SymbolTableAsTuple
* @typedef {import("./@types/perf").SymbolicationService} SymbolicationService
* @typedef {import("./@types/perf").SymbolicationWorkerReplyData} SymbolicationWorkerReplyData
*/
const lazy = createLazyLoaders({
OS: () => ChromeUtils.import("resource://gre/modules/osfile.jsm"),
ProfilerGetSymbols: () =>
ChromeUtils.import("resource://gre/modules/ProfilerGetSymbols.jsm"),
});
ChromeUtils.defineModuleGetter(
this,
"setTimeout",
"resource://gre/modules/Timer.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"clearTimeout",
"resource://gre/modules/Timer.jsm"
);
/** @type {any} */
const global = this;
// This module obtains symbol tables for binaries.
// It does so with the help of a WASM module which gets pulled in from the
// internet on demand. We're doing this purely for the purposes of saving on
// code size. The contents of the WASM module are expected to be static, they
// are checked against the hash specified below.
// The WASM code is run on a ChromeWorker thread. It takes the raw byte
// contents of the to-be-dumped binary (and of an additional optional pdb file
// on Windows) as its input, and returns a set of typed arrays which make up
// the symbol table.
// Don't let the strange looking URLs and strings below scare you.
// The hash check ensures that the contents of the wasm module are what we
// expect them to be.
// The source code is at https://github.com/mstange/profiler-get-symbols/ .
// Generated from https://github.com/mstange/profiler-get-symbols/commit/90ee39f1d18d2727f07dc57bd93cff6bc73ce8a0
const WASM_MODULE_URL =
"https://zealous-rosalind-a98ce8.netlify.com/wasm/8f7ca2f70e1cd21b5a2dbe96545672752887bfbd4e7b3b9437e9fc7c3da0a3bedae4584ff734f0c9f08c642e6b66ffab.wasm";
const WASM_MODULE_INTEGRITY =
"sha384-j3yi9w4c0htaLb6WVFZydSiHv71OezuUN+n8fD2go77a5FhP9zTwyfCMZC5rZv+r";
const EXPIRY_TIME_IN_MS = 5 * 60 * 1000; // 5 minutes
/** @type {Promise<WebAssembly.Module> | null} */
let gCachedWASMModulePromise = null;
let gCachedWASMModuleExpiryTimer = 0;
// Keep active workers alive (see bug 1592227).
const gActiveWorkers = new Set();
function clearCachedWASMModule() {
gCachedWASMModulePromise = null;
gCachedWASMModuleExpiryTimer = 0;
}
function getWASMProfilerGetSymbolsModule() {
if (!gCachedWASMModulePromise) {
gCachedWASMModulePromise = (async function() {
const request = new Request(WASM_MODULE_URL, {
integrity: WASM_MODULE_INTEGRITY,
credentials: "omit",
});
return WebAssembly.compileStreaming(fetch(request));
})();
}
// Reset expiry timer.
clearTimeout(gCachedWASMModuleExpiryTimer);
gCachedWASMModuleExpiryTimer = setTimeout(
clearCachedWASMModule,
EXPIRY_TIME_IN_MS
);
return gCachedWASMModulePromise;
}
/**
* Obtain symbols for the binary at the specified location.
*
* @param {string} binaryPath The absolute path to the binary on the local
* file system.
* @param {string} debugPath The absolute path to the binary's pdb file on the
* local file system if on Windows, otherwise the same as binaryPath.
* @param {string} breakpadId The breakpadId for the binary whose symbols
* should be obtained. This is used for two purposes: 1) to locate the
* correct single-arch binary in "FatArch" files, and 2) to make sure the
* binary at the given path is actually the one that we want. If no ID match
* is found, this function throws (rejects the promise).
* @returns {Promise<SymbolTableAsTuple>} The symbol table in SymbolTableAsTuple format, see the
* documentation for nsIProfiler.getSymbolTable.
*/
async function getSymbolTableFromLocalBinary(
binaryPath,
debugPath,
breakpadId
) {
const module = await getWASMProfilerGetSymbolsModule();
return new Promise((resolve, reject) => {
const worker = new ChromeWorker(
"resource://devtools/client/performance-new/symbolication-worker.js"
);
gActiveWorkers.add(worker);
/** @param {MessageEvent<SymbolicationWorkerReplyData>} msg */
worker.onmessage = msg => {
gActiveWorkers.delete(worker);
if ("error" in msg.data) {
const error = msg.data.error;
if (error.name) {
// Turn the JSON error object into a real Error object.
const { name, message, fileName, lineNumber } = error;
const ErrorObjConstructor =
name in global && Error.isPrototypeOf(global[name])
? global[name]
: Error;
const e = new ErrorObjConstructor(message, fileName, lineNumber);
e.name = name;
reject(e);
} else {
reject(error);
}
return;
}
resolve(msg.data.result);
};
// Handle uncaught errors from the worker script. onerror is called if
// there's a syntax error in the worker script, for example, or when an
// unhandled exception is thrown, but not for unhandled promise
// rejections. Without this handler, mistakes during development such as
// syntax errors can be hard to track down.
worker.onerror = errorEvent => {
gActiveWorkers.delete(worker);
worker.terminate();
if (errorEvent instanceof ErrorEvent) {
const { message, filename, lineno } = errorEvent;
const error = new Error(`${message} at ${filename}:${lineno}`);
error.name = "WorkerError";
reject(error);
} else {
reject(new Error("Error in worker"));
}
};
// Handle errors from messages that cannot be deserialized. I'm not sure
// how to get into such a state, but having this handler seems like a good
// idea.
worker.onmessageerror = errorEvent => {
gActiveWorkers.delete(worker);
worker.terminate();
if (errorEvent instanceof ErrorEvent) {
const { message, filename, lineno } = errorEvent;
const error = new Error(`${message} at ${filename}:${lineno}`);
error.name = "WorkerMessageError";
reject(error);
} else {
reject(new Error("Error in worker"));
}
};
worker.postMessage({ binaryPath, debugPath, breakpadId, module });
});
}
/**
* @param {PerfFront} perfFront
* @param {string} path
@ -140,20 +298,21 @@ class LocalSymbolicationService {
const candidatePaths = this._getCandidatePaths(debugName, breakpadId);
// Iterate over all the paths and try to get symbols from each entry.
const { ProfilerGetSymbols } = lazy.ProfilerGetSymbols();
const errors = [];
for (const { path, debugPath } of candidatePaths) {
if (await doesFileExistAtPath(path)) {
try {
return await ProfilerGetSymbols.getSymbolTable(
return await getSymbolTableFromLocalBinary(
path,
debugPath,
breakpadId
);
} catch (e) {
// ProfilerGetSymbols.getSymbolTable was unsuccessful. So either the
// getSymbolTable was unsuccessful. So either the
// file wasn't parseable or its contents didn't match the specified
// breakpadId, or some other error occurred.
// Advance to the next candidate path.
errors.push(e);
}
}
}
@ -162,7 +321,7 @@ class LocalSymbolicationService {
`Could not obtain symbols for the library ${debugName} ${breakpadId} ` +
`because there was no matching file at any of the candidate paths: ${JSON.stringify(
candidatePaths
)}`
)}. Errors: ${errors.map(e => e.message).join(", ")}`
);
}

View file

@ -1,157 +0,0 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const EXPORTED_SYMBOLS = ["ProfilerGetSymbols"];
ChromeUtils.defineModuleGetter(
this,
"setTimeout",
"resource://gre/modules/Timer.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"clearTimeout",
"resource://gre/modules/Timer.jsm"
);
Cu.importGlobalProperties(["fetch"]);
const global = this;
// This module obtains symbol tables for binaries.
// It does so with the help of a WASM module which gets pulled in from the
// internet on demand. We're doing this purely for the purposes of saving on
// code size. The contents of the WASM module are expected to be static, they
// are checked against the hash specified below.
// The WASM code is run on a ChromeWorker thread. It takes the raw byte
// contents of the to-be-dumped binary (and of an additional optional pdb file
// on Windows) as its input, and returns a set of typed arrays which make up
// the symbol table.
// Don't let the strange looking URLs and strings below scare you.
// The hash check ensures that the contents of the wasm module are what we
// expect them to be.
// The source code is at https://github.com/mstange/profiler-get-symbols/ .
// Generated from https://github.com/mstange/profiler-get-symbols/commit/90ee39f1d18d2727f07dc57bd93cff6bc73ce8a0
const WASM_MODULE_URL =
"https://zealous-rosalind-a98ce8.netlify.com/wasm/8f7ca2f70e1cd21b5a2dbe96545672752887bfbd4e7b3b9437e9fc7c3da0a3bedae4584ff734f0c9f08c642e6b66ffab.wasm";
const WASM_MODULE_INTEGRITY =
"sha384-j3yi9w4c0htaLb6WVFZydSiHv71OezuUN+n8fD2go77a5FhP9zTwyfCMZC5rZv+r";
const EXPIRY_TIME_IN_MS = 5 * 60 * 1000; // 5 minutes
let gCachedWASMModulePromise = null;
let gCachedWASMModuleExpiryTimer = 0;
// Keep active workers alive (see bug 1592227).
let gActiveWorkers = new Set();
function clearCachedWASMModule() {
gCachedWASMModulePromise = null;
gCachedWASMModuleExpiryTimer = 0;
}
function getWASMProfilerGetSymbolsModule() {
if (!gCachedWASMModulePromise) {
gCachedWASMModulePromise = (async function() {
const request = new Request(WASM_MODULE_URL, {
integrity: WASM_MODULE_INTEGRITY,
credentials: "omit",
});
return WebAssembly.compileStreaming(fetch(request));
})();
}
// Reset expiry timer.
clearTimeout(gCachedWASMModuleExpiryTimer);
gCachedWASMModuleExpiryTimer = setTimeout(
clearCachedWASMModule,
EXPIRY_TIME_IN_MS
);
return gCachedWASMModulePromise;
}
this.ProfilerGetSymbols = {
/**
* Obtain symbols for the binary at the specified location.
*
* @param {string} binaryPath The absolute path to the binary on the local
* file system.
* @param {string} debugPath The absolute path to the binary's pdb file on the
* local file system if on Windows, otherwise the same as binaryPath.
* @param {string} breakpadId The breakpadId for the binary whose symbols
* should be obtained. This is used for two purposes: 1) to locate the
* correct single-arch binary in "FatArch" files, and 2) to make sure the
* binary at the given path is actually the one that we want. If no ID match
* is found, this function throws (rejects the promise).
* @returns {Promise} The symbol table in SymbolTableAsTuple format, see the
* documentation for nsIProfiler.getSymbolTable.
*/
async getSymbolTable(binaryPath, debugPath, breakpadId) {
const module = await getWASMProfilerGetSymbolsModule();
return new Promise((resolve, reject) => {
const worker = new ChromeWorker(
"resource://gre/modules/ProfilerGetSymbols-worker.js"
);
gActiveWorkers.add(worker);
worker.onmessage = msg => {
gActiveWorkers.delete(worker);
if (msg.data.error) {
const error = msg.data.error;
if (error.name) {
// Turn the JSON error object into a real Error object.
const { name, message, fileName, lineNumber } = error;
const ErrorObjConstructor =
name in global && Error.isPrototypeOf(global[name])
? global[name]
: Error;
const e = new ErrorObjConstructor(message, fileName, lineNumber);
e.name = name;
reject(e);
} else {
reject(error);
}
return;
}
resolve(msg.data.result);
};
// Handle uncaught errors from the worker script. onerror is called if
// there's a syntax error in the worker script, for example, or when an
// unhandled exception is thrown, but not for unhandled promise
// rejections. Without this handler, mistakes during development such as
// syntax errors can be hard to track down.
worker.onerror = errorEvent => {
gActiveWorkers.delete(worker);
worker.terminate();
const { message, filename, lineno } = errorEvent;
const error = new Error(message, filename, lineno);
error.name = "WorkerError";
reject(error);
};
// Handle errors from messages that cannot be deserialized. I'm not sure
// how to get into such a state, but having this handler seems like a good
// idea.
worker.onmessageerror = errorEvent => {
gActiveWorkers.delete(worker);
worker.terminate();
const { message, filename, lineno } = errorEvent;
const error = new Error(message, filename, lineno);
error.name = "WorkerMessageError";
reject(error);
};
worker.postMessage({ binaryPath, debugPath, breakpadId, module });
});
},
};

View file

@ -46,13 +46,6 @@ EXTRA_JS_MODULES += [
"WebNavigationFrames.jsm",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
EXTRA_JS_MODULES += [
"profiler_get_symbols.js",
"ProfilerGetSymbols-worker.js",
"ProfilerGetSymbols.jsm",
]
EXTRA_COMPONENTS += [
"extensions-toolkit.manifest",
]