Hacking Tips

These tips were archived from MDN: This may be out of date!

This is archived here because it captures valuable documentation that even if potentially out of date, provides inspiration.


This page lists a few tips to help you investigate issues related to SpiderMonkey. All tips listed here are dealing with the JavaScript shell obtained at the end of the build documentation of SpiderMonkey. It is separated in 2 parts, one section related to debugging and another section related to drafting optimizations. Many of these tips only apply to debug builds of the JS shell; they will not function in a release build.

Tools

Here are some debugging tools above and beyond your standard debugger that might help you:

  • rr is a record-and-replay deterministic debugger for Linux

  • Pernosco takes an rr recording and adds omniscient debugging tools to help you

    • It is a paid service with a free trial

    • Mozilla has a license for internal developers; you can contact Matthew Gaudet mgaudet@mozilla.com for details

Debugging Tips

Getting help (from JS shell)

Use the help function to get the list of all primitive functions of the shell with their description. Note that some functions have been moved under an ‘os’ object, and help(os) will give brief help on just the members of that “namespace”.

You can also use help(/Regex/) to get help for members of the global namespace that match the given regular expression.

Getting the bytecode of a function (from JS shell)

The shell has a small function named dis to dump the bytecode of a function with its source notes. Without arguments, it will dump the bytecode of its caller.

js> function f () {
  return 1;
}
js> dis(f);
flags:
loc     op
-----   --
main:
00000:  one
00001:  return
00002:  stop

Source notes:
 ofs  line    pc  delta desc     args
---- ---- ----- ------ -------- ------
  0:    1     0 [   0] newline
  1:    2     0 [   0] colspan 2
  3:    2     2 [   2] colspan 9

Getting the bytecode of a function (from gdb)

In jsopcode.cpp, a function named js::DisassembleAtPC can print the bytecode of a script. Some variants of this function, such as js::DumpScript etc., are convenient for debugging.

Printing the JS stack (from gdb)

In jsobj.cpp, a function named js::DumpBacktrace prints a backtrace à la gdb for the JS stack. The backtrace contains in the following order, the stack depth, the interpreter frame pointer (see js/src/vm/Stack.h, StackFrame class) or (nil) if compiled with IonMonkey, the file and line number of the call location and under parentheses, the JSScript pointer and the jsbytecode pointer (pc) executed.

$ gdb --args js
[…]
(gdb) b js::ReportOverRecursed
(gdb) r
js> function f(i) {
  if (i % 2) f(i + 1);
  else f(i + 3);
}
js> f(0)

Breakpoint 1, js::ReportOverRecursed (maybecx=0xfdca70) at /home/nicolas/mozilla/ionmonkey/js/src/jscntxt.cpp:495
495         if (maybecx)
(gdb) call js::DumpBacktrace(maybecx)
#0          (nil)   typein:2 (0x7fffef1231c0 @ 0)
#1          (nil)   typein:2 (0x7fffef1231c0 @ 24)
#2          (nil)   typein:3 (0x7fffef1231c0 @ 47)
#3          (nil)   typein:2 (0x7fffef1231c0 @ 24)
#4          (nil)   typein:3 (0x7fffef1231c0 @ 47)
[…]
#25157 0x7fffefbbc250   typein:2 (0x7fffef1231c0 @ 24)
#25158 0x7fffefbbc1c8   typein:3 (0x7fffef1231c0 @ 47)
#25159 0x7fffefbbc140   typein:2 (0x7fffef1231c0 @ 24)
#25160 0x7fffefbbc0b8   typein:3 (0x7fffef1231c0 @ 47)
#25161 0x7fffefbbc030   typein:5 (0x7fffef123280 @ 9)

Note, you can do the exact same exercise above using lldb (necessary on OSX after Apple removed gdb) by running lldb -f js then following the remaining steps.

Since SpiderMonkey 48, we have a gdb unwinder. This unwinder is able to read the frames created by the JIT, and to display the frames which are after these JIT frames.

$ gdb --args out/dist/bin/js ./foo.js
[…]
SpiderMonkey unwinder is disabled by default, to enable it type:
        enable unwinder .* SpiderMonkey
(gdb) b js::math_cos
(gdb) run
[…]
#0  js::math_cos (cx=0x14f2640, argc=1, vp=0x7fffffff6a88) at js/src/jsmath.cpp:338
338         CallArgs args = CallArgsFromVp(argc, vp);
(gdb) enable unwinder .* SpiderMonkey
(gdb) backtrace 10
#0  0x0000000000f89979 in js::math_cos(JSContext*, unsigned int, JS::Value*) (cx=0x14f2640, argc=1, vp=0x7fffffff6a88) at js/src/jsmath.cpp:338
#1  0x0000000000ca9c6e in js::CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) (cx=0x14f2640, native=0xf89960 , args=...) at js/src/jscntxtinlines.h:235
#2  0x0000000000c87625 in js::Invoke(JSContext*, JS::CallArgs const&, js::MaybeConstruct) (cx=0x14f2640, args=..., construct=js::NO_CONSTRUCT) at js/src/vm/Interpreter.cpp:476
#3  0x000000000069bdcf in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICCall_Fallback*, uint32_t, JS::Value*, JS::MutableHandleValue) (cx=0x14f2640, frame=0x7fffffff6ad8, stub_=0x1798838, argc=1, vp=0x7fffffff6a88, res=JSVAL_VOID) at js/src/jit/BaselineIC.cpp:6113
#4  0x00007ffff7f41395 in

Note, when you enable the unwinder, the current version of gdb (7.10.1) does not flush the backtrace. Therefore, the JIT frames do not appear until you settle on the next breakpoint. To work-around this issue you can use the recording feature of gdb, to step one instruction, and settle back to where you came from with the following set of gdb commands:

(gdb) record full
(gdb) si
(gdb) record goto 0
(gdb) record stop

If you have a core file, you can use the gdb unwinder the same way, or do everything from the command line as follows:

$ gdb -ex 'enable unwinder .* SpiderMonkey' -ex 'bt 0' -ex 'thread apply all backtrace' -ex 'quit' out/dist/bin/js corefile

The gdb unwinder is supposed to be loaded by dist/bin/js-gdb.py and load python scripts which are located in js/src/gdb/mozilla under gdb. If gdb does not load the unwinder by default, you can force it to, by using the source command with the js-gdb.py file.

Setting a breakpoint in the generated code (from gdb, x86 / x86-64, arm)

To set a breakpoint in the generated code of a specific JSScript compiled with IonMonkey, set a breakpoint on the instruction you are interested in. If you have no precise idea which function you are looking at, you can set a breakpoint on the js::ion::CodeGenerator::visitStart function. Optionally, a condition on the ins->id() of the LIR instruction can be added to select precisely the instruction you are looking for. Once the breakpoint is on the CodeGenerator function of the LIR instruction, add a command to generate a static breakpoint in the generated code.

$ gdb --args js
[…]
(gdb) b js::ion::CodeGenerator::visitStart
(gdb) command
>call masm.breakpoint()
>continue
>end
(gdb) r
js> function f(a, b) { return a + b; }
js> for (var  i = 0; i < 100000; i++) f(i, i + 1);

Breakpoint 1, js::ion::CodeGenerator::visitStart (this=0x101ed20, lir=0x10234e0)
    at /home/nicolas/mozilla/ionmonkey/js/src/ion/CodeGenerator.cpp:609
609     }

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00007ffff7fb165a in ?? ()
(gdb)

Once you hit the generated breakpoint, you can replace it by a gdb breakpoint to make it conditional. The procedure is to first replace the generated breakpoint by a nop instruction, and to set a breakpoint at the address of the nop.

(gdb) x /5i $pc - 1
   0x7ffff7fb1659:      int3
=> 0x7ffff7fb165a:      mov    0x28(%rsp),%rax
   0x7ffff7fb165f:      mov    %eax,%ecx
   0x7ffff7fb1661:      mov    0x30(%rsp),%rdx
   0x7ffff7fb1666:      mov    %edx,%ebx

(gdb) # replace the int3 by a nop
(gdb) set *(unsigned char *) ($pc - 1) = 0x90
(gdb) x /1i $pc - 1
   0x7ffff7fb1659:      nop

(gdb) # set a breakpoint at the previous location
(gdb) b *0x7ffff7fb1659
Breakpoint 2 at 0x7ffff7fb1659

Printing Ion generated assembly code (from gdb)

If you want to look at the assembly code generated by IonMonkey, you can follow this procedure:

  1. Place a breakpoint at CodeGenerator.cpp on the CodeGenerator::link method.

  2. Step next a few times, so that the “code” variable gets generated

  3. Print code->code_, which is the address of the code

  4. Disassemble code read at this address (using x/Ni address, where N is the number of instructions you would like to see)

Here is an example. It might be simpler to use the CodeGenerator::link lineno instead of the full qualified name to put the breakpoint. Let’s say that the line number of this function is 4780, for instance:

(gdb) b CodeGenerator.cpp:4780
Breakpoint 1 at 0x84cade0: file /home/code/mozilla-central/js/src/ion/CodeGenerator.cpp, line 4780.
(gdb) r
Starting program: /home/code/mozilla-central/js/src/32-release/js -f /home/code/jaeger.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0xf7903b40 (LWP 12563)]
[New Thread 0xf6bdeb40 (LWP 12564)]
Run#0

Breakpoint 1, js::ion::CodeGenerator::link (this=0x86badf8)
    at /home/code/mozilla-central/js/src/ion/CodeGenerator.cpp:4780
4780    {
(gdb) n
4781        JSContext *cx = GetIonContext()->cx;
(gdb) n
4783        Linker linker(masm);
(gdb) n
4784        IonCode *code = linker.newCode(cx, JSC::ION*CODE);
(gdb) n
4785        if (!code)
(gdb) p code->code*
$1 = (uint8_t \*) 0xf7fd25a8 "\201", <incomplete sequence \354\200>
(gdb) x/2i 0xf7fd25a8
   0xf7fd25a8:    sub    $0x80,%esp
   0xf7fd25ae:    mov    0x94(%esp),%ecx

On arm, the compiled JS code will always be ARM machine code, whereas SpiderMonkey itself is frequently Thumb2. Since there isn’t debug info for the JIT’d code, you will need to tell gdb that you are looking at ARM code:

(gdb) set arm force-mode arm

Or you can wrap the x command in your own command:

def xi
    set arm force-mode arm
    eval "x/%di %d", $arg0, $arg1
    set arm force-mode auto
end

Printing wasm generated assembly code (from gdb)

  • Set a breakpoint on js::wasm::Instance::callExport (defined in WasmInstance.cpp as of November 18th 2016). This will trigger for any wasm call, so you should find a way to set this breakpoint for the only generated codes you want to look at.

  • Run the program.

  • Do next in gdb until you reach the definition of the funcPtr:

// Call the per-exported-function trampoline created by GenerateEntry.
auto funcPtr = JS*DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase() + func.entryOffset());
if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), &tlsData*))
    return false;
  • After it’s set, x/64i funcPtr will show you the trampoline code. There should be a call to an address at some point; that’s what we’re targeting. Copy that address.

   0x7ffff7ff6000:    push   %r15
   0x7ffff7ff6002:    push   %r14
   0x7ffff7ff6004:    push   %r13
   0x7ffff7ff6006:    push   %r12
   0x7ffff7ff6008:    push   %rbp
   0x7ffff7ff6009:    push   %rbx
   0x7ffff7ff600a:    movabs $0xea4f80,%r10
   0x7ffff7ff6014:    mov    0x178(%r10),%r10
   0x7ffff7ff601b:    mov    %rsp,0x40(%r10)
   0x7ffff7ff601f:    mov    (%rsi),%r15
   0x7ffff7ff6022:    mov    %rdi,%r10
   0x7ffff7ff6025:    push   %r10
   0x7ffff7ff6027:    test   $0xf,%spl
   0x7ffff7ff602b:    je     0x7ffff7ff6032
   0x7ffff7ff6031:    int3
   0x7ffff7ff6032:    callq  0x7ffff7ff5000     <------ right here
  • x/64i address (in this case: x/64i 0x7ffff7ff6032).

  • If you want to put a breakpoint at the function’s entry, you can do: b *address (for instance here, b* 0x7ffff7ff6032). Then you can display the instructions around pc with x/20i $pc, and execute instruction by instruction with stepi.

Finding the script of Ion generated assembly (from gdb)

When facing a bug in which you are in the middle of IonMonkey generated code, the first thing to note is that gdb’s backtrace is not reliable, because the generated code does not keep a frame pointer. To figure it out, you have to read the stack to infer the IonMonkey frame.

(gdb) x /64a $sp
[…]
0x7fffffff9838: 0x7ffff7fad2da  0x141
0x7fffffff9848: 0x7fffef134d40  0x2
[…]
(gdb) p (*(JSFunction**) 0x7fffffff9848)->u.i.script_->lineno
$1 = 1
(gdb) p (*(JSFunction**) 0x7fffffff9848)->u.i.script_->filename
$2 = 0xff92d1 "typein"

The stack is ordered as defined in js/src/ion/IonFrames-x86-shared.h. It is composed of the return address, a descriptor (a small value), the JSFunction (if it is even) or a JSScript (if it is odd; remove it to dereference the pointer) and the frame ends with the number of actual arguments (a small value too). If you want to know at which LIR the code is failing at, the js::ion::CodeGenerator::generateBody function can be instrumented to dump the LIR id before each instruction.

for (; iter != current->end(); iter++) {
    IonSpew(IonSpew_Codegen, "instruction %s", iter->opName());
    […]

    masm.store16(Imm32(iter->id()), Address(StackPointer, -8)); // added
    if (!iter->accept(this))
        return false;

This modification will add an instruction which abuses the