mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 16:28:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			234 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
	
		
			6.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | |
| import { AsyncShutdown } from "resource://gre/modules/AsyncShutdown.sys.mjs";
 | |
| 
 | |
| // Set to true if the application is quitting
 | |
| var gQuitting = false;
 | |
| 
 | |
| // Tracks all the running instances of the minidump-analyzer
 | |
| var gRunningProcesses = new Set();
 | |
| 
 | |
| /**
 | |
|  * Run the minidump-analyzer with the given options unless we're already
 | |
|  * shutting down or the main process has been instructed to shut down in the
 | |
|  * case a content process crashes. Minidump analysis can take a while so we
 | |
|  * don't want to block shutdown waiting for it.
 | |
|  */
 | |
| async function maybeRunMinidumpAnalyzer(minidumpPath, allThreads) {
 | |
|   let shutdown = Services.env.exists("MOZ_CRASHREPORTER_SHUTDOWN");
 | |
| 
 | |
|   if (gQuitting || shutdown) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   await runMinidumpAnalyzer(minidumpPath, allThreads).catch(e =>
 | |
|     console.error(e)
 | |
|   );
 | |
| }
 | |
| 
 | |
| function getMinidumpAnalyzerPath() {
 | |
|   const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
 | |
|   const exeName = "minidump-analyzer" + binSuffix;
 | |
| 
 | |
|   let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
 | |
|   exe.append(exeName);
 | |
| 
 | |
|   return exe;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Run the minidump analyzer tool to gather stack traces from the minidump. The
 | |
|  * stack traces will be stored in the .extra file under the StackTraces= entry.
 | |
|  *
 | |
|  * @param minidumpPath {string} The path to the minidump file
 | |
|  * @param allThreads {bool} Gather stack traces for all threads, not just the
 | |
|  *                   crashing thread.
 | |
|  *
 | |
|  * @returns {Promise} A promise that gets resolved once minidump analysis has
 | |
|  *          finished.
 | |
|  */
 | |
| function runMinidumpAnalyzer(minidumpPath, allThreads) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     try {
 | |
|       let exe = getMinidumpAnalyzerPath();
 | |
|       let args = [minidumpPath];
 | |
|       let process = Cc["@mozilla.org/process/util;1"].createInstance(
 | |
|         Ci.nsIProcess
 | |
|       );
 | |
|       process.init(exe);
 | |
|       process.startHidden = true;
 | |
|       process.noShell = true;
 | |
| 
 | |
|       if (allThreads) {
 | |
|         args.unshift("--full");
 | |
|       }
 | |
| 
 | |
|       process.runAsync(args, args.length, (subject, topic) => {
 | |
|         switch (topic) {
 | |
|           case "process-finished":
 | |
|             gRunningProcesses.delete(process);
 | |
|             resolve();
 | |
|             break;
 | |
|           case "process-failed":
 | |
|             gRunningProcesses.delete(process);
 | |
|             resolve();
 | |
|             break;
 | |
|           default:
 | |
|             reject(new Error("Unexpected topic received " + topic));
 | |
|             break;
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       gRunningProcesses.add(process);
 | |
|     } catch (e) {
 | |
|       reject(e);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Computes the SHA256 hash of a minidump file
 | |
|  *
 | |
|  * @param minidumpPath {string} The path to the minidump file
 | |
|  *
 | |
|  * @returns {Promise} A promise that resolves to the hash value of the
 | |
|  *          minidump.
 | |
|  */
 | |
| function computeMinidumpHash(minidumpPath) {
 | |
|   return (async function () {
 | |
|     try {
 | |
|       let minidumpData = await IOUtils.read(minidumpPath);
 | |
|       let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
 | |
|         Ci.nsICryptoHash
 | |
|       );
 | |
|       hasher.init(hasher.SHA256);
 | |
|       hasher.update(minidumpData, minidumpData.length);
 | |
| 
 | |
|       let hashBin = hasher.finish(false);
 | |
|       let hash = "";
 | |
| 
 | |
|       for (let i = 0; i < hashBin.length; i++) {
 | |
|         // Every character in the hash string contains a byte of the hash data
 | |
|         hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
 | |
|       }
 | |
| 
 | |
|       return hash;
 | |
|     } catch (e) {
 | |
|       console.error(e);
 | |
|       return null;
 | |
|     }
 | |
|   })();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Process the given .extra file and return the annotations it contains in an
 | |
|  * object.
 | |
|  *
 | |
|  * @param extraPath {string} The path to the .extra file
 | |
|  *
 | |
|  * @return {Promise} A promise that resolves to an object holding the crash
 | |
|  *         annotations.
 | |
|  */
 | |
| function processExtraFile(extraPath) {
 | |
|   return (async function () {
 | |
|     try {
 | |
|       let decoder = new TextDecoder();
 | |
|       let extraData = await IOUtils.read(extraPath);
 | |
| 
 | |
|       return JSON.parse(decoder.decode(extraData));
 | |
|     } catch (e) {
 | |
|       console.error(e);
 | |
|       return {};
 | |
|     }
 | |
|   })();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This component makes crash data available throughout the application.
 | |
|  *
 | |
|  * It is a service because some background activity will eventually occur.
 | |
|  */
 | |
| export function CrashService() {
 | |
|   Services.obs.addObserver(this, "quit-application");
 | |
| }
 | |
| 
 | |
| CrashService.prototype = Object.freeze({
 | |
|   classID: Components.ID("{92668367-1b17-4190-86b2-1061b2179744}"),
 | |
|   QueryInterface: ChromeUtils.generateQI(["nsICrashService", "nsIObserver"]),
 | |
| 
 | |
|   async addCrash(processType, crashType, id) {
 | |
|     if (processType === Ci.nsIXULRuntime.PROCESS_TYPE_IPDLUNITTEST) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     processType = Services.crashmanager.processTypes[processType];
 | |
| 
 | |
|     let allThreads = false;
 | |
| 
 | |
|     switch (crashType) {
 | |
|       case Ci.nsICrashService.CRASH_TYPE_CRASH:
 | |
|         crashType = Services.crashmanager.CRASH_TYPE_CRASH;
 | |
|         break;
 | |
|       case Ci.nsICrashService.CRASH_TYPE_HANG:
 | |
|         crashType = Services.crashmanager.CRASH_TYPE_HANG;
 | |
|         allThreads = true;
 | |
|         break;
 | |
|       default:
 | |
|         throw new Error("Unrecognized CRASH_TYPE: " + crashType);
 | |
|     }
 | |
| 
 | |
|     let minidumpPath = Services.appinfo.getMinidumpForID(id).path;
 | |
|     let extraPath = Services.appinfo.getExtraFileForID(id).path;
 | |
|     let metadata = {};
 | |
|     let hash = null;
 | |
| 
 | |
|     await maybeRunMinidumpAnalyzer(minidumpPath, allThreads);
 | |
|     metadata = await processExtraFile(extraPath);
 | |
|     hash = await computeMinidumpHash(minidumpPath);
 | |
| 
 | |
|     if (hash) {
 | |
|       metadata.MinidumpSha256Hash = hash;
 | |
|     }
 | |
| 
 | |
|     let blocker = Services.crashmanager.addCrash(
 | |
|       processType,
 | |
|       crashType,
 | |
|       id,
 | |
|       new Date(),
 | |
|       metadata
 | |
|     );
 | |
| 
 | |
|     AsyncShutdown.profileBeforeChange.addBlocker(
 | |
|       "CrashService waiting for content crash ping to be sent",
 | |
|       blocker
 | |
|     );
 | |
| 
 | |
|     blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));
 | |
| 
 | |
|     await blocker;
 | |
|   },
 | |
| 
 | |
|   observe(subject, topic) {
 | |
|     switch (topic) {
 | |
|       case "profile-after-change":
 | |
|         // Side-effect is the singleton is instantiated.
 | |
|         Services.crashmanager;
 | |
|         break;
 | |
|       case "quit-application":
 | |
|         gQuitting = true;
 | |
|         gRunningProcesses.forEach(process => {
 | |
|           try {
 | |
|             process.kill();
 | |
|           } catch (e) {
 | |
|             // If the process has already quit then kill() fails, but since
 | |
|             // this failure is benign it is safe to silently ignore it.
 | |
|           }
 | |
|           Services.obs.notifyObservers(null, "test-minidump-analyzer-killed");
 | |
|         });
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| });
 | 
