--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -63,17 +63,17 @@ pref("extensions.startupScanScopes", 0);
// This is where the profiler WebExtension API will look for breakpad symbols.
// NOTE: deliberately http right now since https://symbols.mozilla.org is not supported.
pref("extensions.geckoProfiler.symbols.url", "http://symbols.mozilla.org/");
pref("extensions.geckoProfiler.acceptedExtensionIds", "geckoprofiler@mozilla.com,quantum-foxfooding@mozilla.com");
#if defined(XP_LINUX) || defined (XP_MACOSX)
pref("extensions.geckoProfiler.getSymbolRules", "localBreakpad,remoteBreakpad,nm");
#else // defined(XP_WIN)
-pref("extensions.geckoProfiler.getSymbolRules", "localBreakpad,remoteBreakpad");
+pref("extensions.geckoProfiler.getSymbolRules", "localBreakpad,remoteBreakpad,dump_syms.exe");
#endif
// Add-on content security policies.
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
#if defined(XP_WIN)
--- a/browser/components/extensions/ext-geckoProfiler.js
+++ b/browser/components/extensions/ext-geckoProfiler.js
@@ -27,17 +27,17 @@ const parseSym = data => {
worker.onmessage = (e) => {
if (e.data.error) {
reject(e.data.error);
} else {
resolve(e.data.result);
}
};
});
- worker.postMessage(data);
+ worker.postMessage(data, data.textBuffer ? [data.textBuffer.buffer] : []);
return promise;
};
class NMParser {
constructor() {
this._worker = new ChromeWorker("resource://app/modules/ParseNMSymbols-worker.js");
}
@@ -84,16 +84,28 @@ class CppFiltParser {
});
this._worker.postMessage({
finish: true,
});
return promise;
}
}
+const joinBuffers = function(buffers) {
+ const byteLengthSum =
+ buffers.reduce((accum, buffer) => accum + buffer.byteLength, 0);
+ const joinedBuffer = new Uint8Array(byteLengthSum);
+ let offset = 0;
+ for (const buffer of buffers) {
+ joinedBuffer.set(new Uint8Array(buffer), offset);
+ offset += buffer.byteLength;
+ }
+ return joinedBuffer;
+};
+
const readAllData = async function(pipe, processData) {
let data;
while ((data = await pipe.read()) && data.byteLength) {
processData(data);
}
};
const spawnProcess = async function(name, cmdArgs, processData, stdin = null) {
@@ -107,16 +119,28 @@ const spawnProcess = async function(name
const encoder = new TextEncoder("utf-8");
proc.stdin.write(encoder.encode(stdin));
proc.stdin.close();
}
await readAllData(proc.stdout, processData);
};
+const runCommandAndGetOutputAsString = async function(command, cmdArgs) {
+ const opts = {
+ command,
+ arguments: cmdArgs,
+ stderr: "pipe",
+ };
+ const proc = await Subprocess.call(opts);
+ const chunks = [];
+ await readAllData(proc.stdout, data => chunks.push(data));
+ return (new TextDecoder()).decode(joinBuffers(chunks));
+};
+
const getSymbolsFromNM = async function(path, arch) {
const parser = new NMParser();
const args = [path];
if (Services.appinfo.OS === "Darwin") {
args.unshift("-arch", arch);
} else {
// Mac's `nm` doesn't support the demangle option, so we have to
@@ -135,16 +159,93 @@ const getSymbolsFromNM = async function(
const decoder = new TextDecoder();
const symbolsJoined = decoder.decode(symbolsJoinedBuffer);
const demangler = new CppFiltParser(addresses.length);
await spawnProcess("c++filt", [], data => demangler.consume(data), symbolsJoined);
const [newIndex, newBuffer] = await demangler.finish();
return [addresses, newIndex, newBuffer];
};
+const getEnvVarCaseInsensitive = function(env, name) {
+ for (const [varname, value] of Object.entries(env)) {
+ if (varname.toLowerCase() == name.toLowerCase()) {
+ return value;
+ }
+ }
+ return undefined;
+};
+
+const findPotentialMSDIAPaths = async function(env) {
+ // dump_syms.exe needs to find msdia*.dll. This DLL is supplied by Microsoft
+ // Visual Studio. However, starting with VS2017, this DLL is no longer
+ // globally registered and needs to be loaded manually by dump_syms. And
+ // dump_syms can only load it if the DLL is somewhere in the default DLL
+ // search paths.
+ // So we append the paths where the DLL is likely to be to the PATH
+ // environment variable in order to increase the chances of dump_syms
+ // succeeding.
+ const programFilesX86Path =
+ getEnvVarCaseInsensitive(env, "ProgramFiles(x86)") ||
+ getEnvVarCaseInsensitive(env, "ProgramFiles");
+ if (programFilesX86Path) {
+ // Check if we have vswhere. This is a utilty that gets installed into a
+ // fixed path and can be used to look up the actual location of the Visual
+ // Studio installation. It's available starting with VS2017.
+ const vswherePath = OS.Path.join(programFilesX86Path,
+ "Microsoft Visual Studio", "Installer",
+ "vswhere.exe");
+ const args = [
+ "-products", "*",
+ "-latest",
+ "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
+ "-property", "installationPath",
+ "-format", "value",
+ ];
+ try {
+ const vsInstallationPath =
+ (await runCommandAndGetOutputAsString(vswherePath, args)).trim();
+ if (vsInstallationPath) {
+ const diaSDKPath = OS.Path.join(vsInstallationPath, "DIA SDK");
+ return [
+ OS.Path.join(diaSDKPath, "bin"),
+ OS.Path.join(diaSDKPath, "bin", "amd64"),
+ ];
+ }
+ } catch (e) {
+ // Something went wrong. Either Visual Studio isn't installed, or a
+ // pre-2017 version is installed, or vswhere wasn't successful.
+ // Ignore the error.
+ }
+ }
+ // Add no extra DLL search paths and hope that dump_syms.exe is going to
+ // succeed regardless.
+ return [];
+};
+
+const getSymbolsUsingWindowsDumpSyms = async function(dumpSymsPath, debugPath) {
+ const env = Subprocess.getEnvironment();
+ const extraPaths = await findPotentialMSDIAPaths(env);
+ const existingPaths = env.PATH ? env.PATH.split(";") : [];
+ env.PATH = existingPaths.concat(extraPaths).join(";");
+ const opts = {
+ command: dumpSymsPath,
+ arguments: [debugPath],
+ environment: env,
+ stderr: "pipe",
+ };
+ const proc = await Subprocess.call(opts);
+ const chunks = [];
+ await readAllData(proc.stdout, data => chunks.push(data));
+ const textBuffer = joinBuffers(chunks);
+ if (textBuffer.byteLength === 0) {
+ throw new Error("did not receive any stdout from dump_syms.exe");
+ }
+ return parseSym({textBuffer});
+};
+
const pathComponentsForSymbolFile = (debugName, breakpadId) => {
const symName = debugName.replace(/(\.pdb)?$/, ".sym");
return [debugName, breakpadId, symName];
};
const urlForSymFile = (debugName, breakpadId) => {
const profilerSymbolsURL = Services.prefs.getCharPref(PREF_SYMBOLS_URL,
"http://symbols.mozilla.org/");
@@ -175,24 +276,36 @@ const filePathForSymFileInObjDir = (bina
if (!objDirDist) {
return null;
}
return OS.Path.join(objDirDist,
"crashreporter-symbols",
...pathComponentsForSymbolFile(debugName, breakpadId));
};
+const dumpSymsPathInObjDir = binaryPath => {
+ // `dump_syms.exe` is generated by the build process
+ // at /path/to/objdir/dist/host/bin/.
+ const objDirDist = getContainingObjdirDist(binaryPath);
+ if (!objDirDist) {
+ return null;
+ }
+ return OS.Path.join(objDirDist, "host", "bin", "dump_syms.exe");
+};
+
const symbolCache = new Map();
const primeSymbolStore = libs => {
- for (const {debugName, breakpadId, path, arch} of libs) {
- symbolCache.set(urlForSymFile(debugName, breakpadId), {path, arch});
+ for (const {debugName, debugPath, breakpadId, path, arch} of libs) {
+ symbolCache.set(urlForSymFile(debugName, breakpadId), {path, debugPath, arch});
}
};
+let previouslySuccessfulDumpSymsPath = null;
+
const isRunningObserver = {
_observers: new Set(),
observe(subject, topic, data) {
switch (topic) {
case "profiler-started":
case "profiler-stopped":
// Call observer(false) or observer(true), but do it through a promise
@@ -295,16 +408,18 @@ this.geckoProfiler = class extends Exten
// We have multiple options for obtaining symbol information for the given
// binary.
// "localBreakpad" - Use existing symbol dumps stored in the object directory of a local
// Firefox build, generated using `mach buildsymbols` [requires path]
// "remoteBreakpad" - Use symbol dumps from the Mozilla symbol server [only requires
// debugName + breakpadId]
// "nm" - Use the command line tool `nm` [linux/mac only, requires path]
+ // "dump_syms.exe" - Use the tool dump_syms.exe from the object directory [Windows
+ // only, requires path]
for (const rule of symbolRules.split(",")) {
try {
switch (rule) {
case "localBreakpad":
if (haveAbsolutePath) {
const {path} = cachedLibInfo;
const filepath = filePathForSymFileInObjDir(path, debugName, breakpadId);
if (filepath) {
@@ -319,25 +434,47 @@ this.geckoProfiler = class extends Exten
const url = urlForSymFile(debugName, breakpadId);
return await parseSym({url});
case "nm":
if (haveAbsolutePath) {
const {path, arch} = cachedLibInfo;
return await getSymbolsFromNM(path, arch);
}
break;
+ case "dump_syms.exe":
+ if (haveAbsolutePath) {
+ const {path, debugPath} = cachedLibInfo;
+ let dumpSymsPath = dumpSymsPathInObjDir(path);
+ if (!dumpSymsPath && previouslySuccessfulDumpSymsPath) {
+ // We may be able to dump symbol for system libraries
+ // (which are outside the object directory, and for
+ // which dumpSymsPath will be null) using dump_syms.exe.
+ // If we know that dump_syms.exe exists, try it.
+ dumpSymsPath = previouslySuccessfulDumpSymsPath;
+ }
+ if (dumpSymsPath) {
+ const result =
+ await getSymbolsUsingWindowsDumpSyms(dumpSymsPath, debugPath);
+ previouslySuccessfulDumpSymsPath = dumpSymsPath;
+ return result;
+ }
+ }
+ break;
}
} catch (e) {
// Each of our options can go wrong for a variety of reasons, so on failure
// we will try the next one.
// "localBreakpad" will fail if this is not a local build that's running from the object
// directory or if the user hasn't run `mach buildsymbols` on it.
// "remoteBreakpad" will fail if this is not an official mozilla build (e.g. Nightly) or a
// known system library.
// "nm" will fail if `nm` is not available.
+ // "dump_syms.exe" will fail if this is not a local build that's running from the object
+ // directory, or if dump_syms.exe doesn't exist in the object directory, or if
+ // dump_syms.exe failed for other reasons.
}
}
throw new Error(`Ran out of options to get symbols from library ${debugName} ${breakpadId}.`);
},
onRunning: new EventManager(context, "geckoProfiler.onRunning", fire => {
isRunningObserver.addObserver(fire.async);