tor-browser

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

browser_dbg-features-wasm.js (7013B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
      4 
      5 /**
      6 * This test covers all specifics of debugging WASM/WebAssembly files.
      7 *
      8 * WebAssembly is a binary file format for the Web.
      9 * The binary files are loaded by Gecko and machine code runs.
     10 * In order to debug them ThreadConfiguration's `observeWasm` is set to true,
     11 * only once the debugger is opened. This will set DebuggerAPI's `allowUnobservedWasm` to false.
     12 * Then, the engine will compute a different machine code with debugging instruction.
     13 * This will use a lot more memory, but allow debugger to set breakpoint and break on WASM sources.
     14 *
     15 * This behavior introduces some limitations when opening the debugger against
     16 * and already loaded page. The WASM file won't be debuggable.
     17 */
     18 
     19 "use strict";
     20 
     21 add_task(async function () {
     22  // Load the test page before opening the debugger so that WASM are built
     23  // without debugging instructions. Opening the console still doesn't enable debugging instructions.
     24  const tab = await addTab(EXAMPLE_URL + "doc-wasm-sourcemaps.html");
     25  const toolbox = await openToolboxForTab(tab, "webconsole");
     26 
     27  // Reload once again, while the console is opened.
     28  // When opening the debugger, it will still miss the source content.
     29  // To see the sources, we have to reload while the debugger has been opened.
     30  await reloadBrowser();
     31 
     32  await toolbox.selectTool("jsdebugger");
     33  const dbg = createDebuggerContext(toolbox);
     34 
     35  // When opening against an already loaded page, WASM source loading doesn't work
     36  // And because of that we don't see sourcemap/original files.
     37  await waitForSourcesInSourceTree(dbg, [
     38    "doc-wasm-sourcemaps.html",
     39    "fib.wasm",
     40  ]);
     41  is(dbg.selectors.getSourceCount(), 2, "There are only these two sources");
     42 
     43  const source = findSource(dbg, "fib.wasm");
     44  is(source.isWasm, true, "The original source is flagged as Wasm source");
     45 
     46  // Note that there is no point in asserting breakable lines,
     47  // as we aren't fetching any source.
     48  await dbg.actions.selectLocation(createLocation({ source }), {
     49    keepContext: false,
     50  });
     51  is(getEditorContent(dbg), `Please refresh to debug this module`);
     52 
     53  info("Reload and assert that WASM files are then debuggable");
     54  await reload(dbg, "doc-wasm-sourcemaps.html", "fib.wasm", "fib.c");
     55 
     56  info("After reloading, original file lines are breakable");
     57  // Ensure selecting the source before asserting breakable lines
     58  // otherwise the gutter may not be yet updated
     59  await selectSource(dbg, "fib.c");
     60  await assertLineIsBreakable(dbg, source.url, 14, true);
     61 
     62  await waitForSourcesInSourceTree(dbg, [
     63    "doc-wasm-sourcemaps.html",
     64    "fib.wasm",
     65    "fib.c",
     66  ]);
     67  is(dbg.selectors.getSourceCount(), 3, "There is all these 3 sources");
     68  // (even if errno_location.c is still not functional)
     69 
     70  // The line in the original C file, where the for() loop starts
     71  const breakpointLine = 12;
     72  assertTextContentOnLine(dbg, breakpointLine, "for (i = 0; i < n; i++) {");
     73 
     74  info("Register and trigger a breakpoint from the original source in C");
     75  await addBreakpoint(dbg, "fib.c", breakpointLine);
     76  invokeInTab("runWasm");
     77 
     78  await waitForPausedInOriginalFileAndToggleMapScopes(dbg);
     79 
     80  await assertPausedAtSourceAndLine(
     81    dbg,
     82    findSource(dbg, "fib.c").id,
     83    breakpointLine
     84  );
     85  await assertBreakpoint(dbg, breakpointLine);
     86  // Capture the generated location line, so that we can better report
     87  // when the binary code changed later in this test
     88  const frames = dbg.selectors.getCurrentThreadFrames();
     89  const generatedLine = frames[0].generatedLocation.line;
     90 
     91  assertFirstFrameTitleAndLocation(dbg, "(wasmcall)", "fib.c");
     92 
     93  await removeBreakpoint(dbg, findSource(dbg, "fib.c").id, breakpointLine);
     94  await resume(dbg);
     95 
     96  info(
     97    "Now register and trigger the same breakpoint from the binary source file"
     98  );
     99  const binarySource = findSource(dbg, "fib.wasm");
    100 
    101  // There is two lines, the hexadecimal one is the "virtual line" displayed in the gutter.
    102  // While the decimal one is the line where the line appear in CodeMirror.
    103  // So while we set the breakpoint on the decimal line in CodeMirror gutter,
    104  // internaly, the engine sets the breakpoint on the "virtual line".
    105  const virtualBinaryLine = 0x11a;
    106  is(
    107    "0x" + virtualBinaryLine.toString(16),
    108    "0x" + generatedLine.toString(16),
    109    "The hardcoded binary line (0x" +
    110      generatedLine.toString(16) +
    111      ") matches the mapped location when we set the breakpoint on the original line. If you rebuilt the binary, you may just need to update the virtualBinaryLine variable to the new location."
    112  );
    113 
    114  await dbg.actions.selectLocation(createLocation({ source: binarySource }), {
    115    keepContext: false,
    116  });
    117 
    118  const binaryLine = wasmOffsetToLine(dbg, virtualBinaryLine);
    119 
    120  // Make sure line is within viewport
    121  await scrollEditorIntoView(dbg, binaryLine, 0);
    122  await assertLineIsBreakable(dbg, binarySource.url, binaryLine, true);
    123 
    124  await addBreakpoint(dbg, binarySource, virtualBinaryLine);
    125  invokeInTab("runWasm");
    126 
    127  // We can't use waitForPaused test helper as the text content isn't displayed correctly
    128  // so only assert that we are in paused state.
    129  await waitForPaused(dbg);
    130  // We don't try to assert paused line as there is two types of line in wasm
    131  await assertPausedAtSourceAndLine(dbg, binarySource.id, virtualBinaryLine);
    132 
    133  // Switch to original source
    134  info(
    135    "Manually switch to original C source as we set the breakpoint on binary source, we paused on it"
    136  );
    137  await dbg.actions.jumpToMappedSelectedLocation();
    138 
    139  // But once we switch to original source, we should have the original text content and be able
    140  // to do all classic assertions for paused state.
    141  await waitForPausedInOriginalFileAndToggleMapScopes(dbg);
    142 
    143  await assertPausedAtSourceAndLine(
    144    dbg,
    145    findSource(dbg, "fib.c").id,
    146    breakpointLine
    147  );
    148 
    149  info("Reselect the binary source");
    150  await dbg.actions.selectLocation(createLocation({ source: binarySource }), {
    151    keepContext: false,
    152  });
    153 
    154  assertFirstFrameTitleAndLocation(dbg, "(wasmcall)", "fib.wasm");
    155 
    156  // We can't use this method as it uses internaly the breakpoint line, which isn't the line in CodeMirror
    157  // await assertPausedAtSourceAndLine(dbg, binarySource.id, binaryLine);
    158  await assertBreakpoint(dbg, binaryLine);
    159 
    160  await removeBreakpoint(dbg, binarySource.id, virtualBinaryLine);
    161  await resume(dbg);
    162 });
    163 
    164 function assertFirstFrameTitleAndLocation(dbg, title, location) {
    165  const frames = findAllElements(dbg, "frames");
    166  const firstFrameTitle = frames[0].querySelector(".title").textContent;
    167  is(firstFrameTitle, title, "First frame title is the expected one");
    168  const firstFrameLocation = frames[0].querySelector(".location").textContent;
    169  is(
    170    firstFrameLocation.includes(location),
    171    true,
    172    `First frame location '${firstFrameLocation}' includes '${location}'`
    173  );
    174 }