tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

symbolication.worker.js (10691B)


      1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim: set sts=2 sw=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 // FIXME: This file is currently not covered by TypeScript, there is no "@ts-check" comment.
      8 // We should fix this once we know how to deal with the module imports below.
      9 // (Maybe once Firefox supports worker module? Bug 1247687)
     10 
     11 "use strict";
     12 
     13 /* import-globals-from profiler_get_symbols.js */
     14 importScripts(
     15  "resource://devtools/shared/performance-new/profiler_get_symbols.js"
     16 );
     17 
     18 /**
     19 * @typedef {import("../@types/perf").SymbolicationWorkerInitialMessage} SymbolicationWorkerInitialMessage
     20 * @typedef {import("../@types/perf").FileHandle} FileHandle
     21 */
     22 
     23 // This worker uses the wasm module that was generated from https://github.com/mstange/profiler-get-symbols.
     24 // See ProfilerGetSymbols.jsm for more information.
     25 //
     26 // The worker instantiates the module, reads the binary into wasm memory, runs
     27 // the wasm code, and returns the symbol table or an error. Then it shuts down
     28 // itself.
     29 
     30 /* eslint camelcase: 0*/
     31 const { getCompactSymbolTable, queryAPI } = wasm_bindgen;
     32 
     33 // Returns a plain object that is Structured Cloneable and has name and
     34 // message properties.
     35 function createPlainErrorObject(e) {
     36  // Regular errors: just rewrap the object.
     37  // eslint-disable-next-line no-shadow
     38  const { name, message, fileName, lineNumber } = e;
     39  return { name, message, fileName, lineNumber };
     40 }
     41 
     42 /**
     43 * A FileAndPathHelper object is passed to getCompactSymbolTable, which calls
     44 * the methods `getCandidatePathsForBinaryOrPdb` and `readFile` on it.
     45 */
     46 class FileAndPathHelper {
     47  constructor(libInfoMap, objdirs) {
     48    this._libInfoMap = libInfoMap;
     49    this._objdirs = objdirs;
     50  }
     51 
     52  /**
     53   * Enumerate all paths at which we could find files with symbol information.
     54   * This method is called by wasm code (via the bindings).
     55   *
     56   * @param {LibraryInfo} libraryInfo
     57   * @returns {Array<string>}
     58   */
     59  getCandidatePathsForDebugFile(libraryInfo) {
     60    const { debugName, breakpadId } = libraryInfo;
     61    const key = `${debugName}:${breakpadId}`;
     62    const lib = this._libInfoMap.get(key);
     63    if (!lib) {
     64      throw new Error(
     65        `Could not find the library for "${debugName}", "${breakpadId}".`
     66      );
     67    }
     68 
     69    // eslint-disable-next-line no-shadow
     70    const { name, path, debugPath, arch } = lib;
     71    const candidatePaths = [];
     72 
     73    // First, try to find a binary with a matching file name and breakpadId
     74    // in one of the manually specified objdirs.
     75    // This is needed if the debuggee is a build running on a remote machine that
     76    // was compiled by the developer on *this* machine (the "host machine"). In
     77    // that case, the objdir will contain the compiled binary with full symbol and
     78    // debug information, whereas the binary on the device may not exist in
     79    // uncompressed form or may have been stripped of debug information and some
     80    // symbol information.
     81    // An objdir, or "object directory", is a directory on the host machine that's
     82    // used to store build artifacts ("object files") from the compilation process.
     83    // This only works if the binary is one of the Gecko binaries and not
     84    // a system library.
     85    for (const objdirPath of this._objdirs) {
     86      try {
     87        // Binaries are usually expected to exist at objdir/dist/bin/filename.
     88        candidatePaths.push(PathUtils.join(objdirPath, "dist", "bin", name));
     89 
     90        // Also search in the "objdir" directory itself (not just in dist/bin).
     91        // If, for some unforeseen reason, the relevant binary is not inside the
     92        // objdirs dist/bin/ directory, this provides a way out because it lets the
     93        // user specify the actual location.
     94        candidatePaths.push(PathUtils.join(objdirPath, name));
     95      } catch (e) {
     96        // PathUtils.join throws if objdirPath is not an absolute path.
     97        // Ignore those invalid objdir paths.
     98      }
     99    }
    100 
    101    // Check the absolute paths of the library last.
    102    // We do this after the objdir search because the library's path may point
    103    // to a stripped binary, which will have fewer symbols than the original
    104    // binaries in the objdir.
    105    if (debugPath !== path) {
    106      // We're on Windows, and debugPath points to a PDB file.
    107      // On non-Windows, path and debugPath are always the same.
    108 
    109      // Check the PDB file before the binary because the PDB has the symbol
    110      // information. The binary is only used as a last-ditch fallback
    111      // for things like Windows system libraries (e.g. graphics drivers).
    112      candidatePaths.push(debugPath);
    113    }
    114 
    115    // The location of the binary. If the profile was obtained on this machine
    116    // (and not, for example, on an Android device), this file should always
    117    // exist.
    118    candidatePaths.push(path);
    119 
    120    // On macOS, for system libraries, add a final fallback for the dyld shared
    121    // cache. Starting with macOS 11, most system libraries are located in this
    122    // system-wide cache file and not present as individual files.
    123    if (arch && (path.startsWith("/usr/") || path.startsWith("/System/"))) {
    124      // Use the special syntax `dyldcache:<dyldcachepath>:<librarypath>`.
    125 
    126      // Dyld cache location used on macOS 13+:
    127      candidatePaths.push(
    128        `dyldcache:/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_${arch}:${path}`
    129      );
    130      // Dyld cache location used on macOS 11 and 12:
    131      candidatePaths.push(
    132        `dyldcache:/System/Library/dyld/dyld_shared_cache_${arch}:${path}`
    133      );
    134    }
    135 
    136    return candidatePaths;
    137  }
    138 
    139  /**
    140   * Enumerate all paths at which we could find the binary which matches the
    141   * given libraryInfo, in order to disassemble machine code.
    142   * This method is called by wasm code (via the bindings).
    143   *
    144   * @param {LibraryInfo} libraryInfo
    145   * @returns {Array<string>}
    146   */
    147  getCandidatePathsForBinary(libraryInfo) {
    148    const { debugName, breakpadId } = libraryInfo;
    149    const key = `${debugName}:${breakpadId}`;
    150    const lib = this._libInfoMap.get(key);
    151    if (!lib) {
    152      throw new Error(
    153        `Could not find the library for "${debugName}", "${breakpadId}".`
    154      );
    155    }
    156 
    157    // eslint-disable-next-line no-shadow
    158    const { name, path, arch } = lib;
    159    const candidatePaths = [];
    160 
    161    // The location of the binary. If the profile was obtained on this machine
    162    // (and not, for example, on an Android device), this file should always
    163    // exist.
    164    candidatePaths.push(path);
    165 
    166    // Fall back to searching in the manually specified objdirs.
    167    // This is needed if the debuggee is a build running on a remote machine that
    168    // was compiled by the developer on *this* machine (the "host machine"). In
    169    // that case, the objdir will contain the compiled binary.
    170    for (const objdirPath of this._objdirs) {
    171      try {
    172        // Binaries are usually expected to exist at objdir/dist/bin/filename.
    173        candidatePaths.push(PathUtils.join(objdirPath, "dist", "bin", name));
    174 
    175        // Also search in the "objdir" directory itself (not just in dist/bin).
    176        // If, for some unforeseen reason, the relevant binary is not inside the
    177        // objdirs dist/bin/ directory, this provides a way out because it lets the
    178        // user specify the actual location.
    179        candidatePaths.push(PathUtils.join(objdirPath, name));
    180      } catch (e) {
    181        // PathUtils.join throws if objdirPath is not an absolute path.
    182        // Ignore those invalid objdir paths.
    183      }
    184    }
    185 
    186    // On macOS, for system libraries, add a final fallback for the dyld shared
    187    // cache. Starting with macOS 11, most system libraries are located in this
    188    // system-wide cache file and not present as individual files.
    189    if (arch && (path.startsWith("/usr/") || path.startsWith("/System/"))) {
    190      // Use the special syntax `dyldcache:<dyldcachepath>:<librarypath>`.
    191 
    192      // Dyld cache location used on macOS 13+:
    193      candidatePaths.push(
    194        `dyldcache:/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_${arch}:${path}`
    195      );
    196      // Dyld cache location used on macOS 11 and 12:
    197      candidatePaths.push(
    198        `dyldcache:/System/Library/dyld/dyld_shared_cache_${arch}:${path}`
    199      );
    200    }
    201 
    202    return candidatePaths;
    203  }
    204 
    205  /**
    206   * Asynchronously prepare the file at `path` for synchronous reading.
    207   * This method is called by wasm code (via the bindings).
    208   *
    209   * @param {string} path
    210   * @returns {FileHandle}
    211   */
    212  async readFile(path) {
    213    const info = await IOUtils.stat(path);
    214    if (info.type === "directory") {
    215      throw new Error(`Path "${path}" is a directory.`);
    216    }
    217 
    218    return IOUtils.openFileForSyncReading(path);
    219  }
    220 }
    221 
    222 /** @param {MessageEvent<SymbolicationWorkerInitialMessage>} e */
    223 onmessage = async e => {
    224  try {
    225    const { request, libInfoMap, objdirs, module } = e.data;
    226 
    227    if (!(module instanceof WebAssembly.Module)) {
    228      throw new Error("invalid WebAssembly module");
    229    }
    230 
    231    // Instantiate the WASM module.
    232    await wasm_bindgen(module);
    233 
    234    const helper = new FileAndPathHelper(libInfoMap, objdirs);
    235 
    236    switch (request.type) {
    237      case "GET_SYMBOL_TABLE": {
    238        const { debugName, breakpadId } = request;
    239        const result = await getCompactSymbolTable(
    240          debugName,
    241          breakpadId,
    242          helper
    243        );
    244        postMessage(
    245          { result },
    246          result.map(r => r.buffer)
    247        );
    248        break;
    249      }
    250      case "QUERY_SYMBOLICATION_API": {
    251        const { path, requestJson } = request;
    252        const result = await queryAPI(path, requestJson, helper);
    253        postMessage({ result });
    254        break;
    255      }
    256      default:
    257        throw new Error(`Unexpected request type ${request.type}`);
    258    }
    259  } catch (error) {
    260    postMessage({ error: createPlainErrorObject(error) });
    261  }
    262  close();
    263 };
    264 
    265 onunhandledrejection = e => {
    266  // Unhandled rejections can happen if the WASM code throws a
    267  // "RuntimeError: unreachable executed" exception, which can happen
    268  // if the Rust code panics or runs out of memory.
    269  // These panics currently are not propagated to the promise reject
    270  // callback, see https://github.com/rustwasm/wasm-bindgen/issues/2724 .
    271  // Ideally, the Rust code should never panic and handle all error
    272  // cases gracefully.
    273  e.preventDefault();
    274  postMessage({ error: createPlainErrorObject(e.reason) });
    275 };
    276 
    277 // Catch any other unhandled errors, just to be sure.
    278 onerror = e => {
    279  postMessage({ error: createPlainErrorObject(e) });
    280 };