Debug Interface for the mozjs, how to start/stop/pause/continue the js engine

74 views
Skip to first unread message

lemmel

unread,
Apr 5, 2020, 3:54:41 PM4/5/20
to dev-tech-...@lists.mozilla.org
Hi !

Once upon a time I used QtScript and QScriptEngineDebugger; both tools gave me the ability to:
- run JS script
- have a debug interface: start/stop/pause/continue/breakpoint/stackwalker/ etc
for a teaching tool: a learner could code, run the code, and debug it.

Then Qt stopped QtScript and migrated to QJSEngine, a library that does not provide a debug interface.
Then I used ChakraCore ... then Microsoft switched to Chrome ...

As of now, I'm evaluating several solutions, and I test spidermonkey engine.

So I'm able to have some test code running, but the core need remains the debug interface, but as of now, I'm not able to understand how to do anything.
The mozjs library seems synchronous (so no start/stop/pause call), I saw something about a Debug Protocol (but mozjs doesn't seems to open ports), I saw the Debugger API (but I don't see how to even pause the execution).

Is there some docs[1] ? Example ? Or a project that includes a debugger ? Or could you give me some hints/explanations ?

Thanks for your attention.


[1] Already found:
- https://developer.mozilla.org/en-US/docs/Tools/Debugger-API/Debugger
- https://mozilla-spidermonkey.github.io/
- https://github.com/mozilla-spidermonkey/spidermonkey-embedding-examples





Nicolas B. Pierron

unread,
Apr 6, 2020, 10:19:49 AM4/6/20
to
Hi,

SpiderMonkey does have a Debugger interface. This interface is available from JavaScript code to inspect other code which is running in the same engine.

I am no expert in the Debugger, but I would reading the documentation[1] to find which function seems interesting and checking the test suite[2] to find example of usage.

Firefox uses SpiderMonkey and uses this interface to implement its Debugger. However it adds extra complexity to it, in order to handle Debug from a different process. You can find some examples using the Debugger API [3] but they would contains extra code which is specific to Firefox.

The Debugger API works using callbacks. Thus if you want to stop at a given location, you register a callback using `Debugger.Script.setBreakpoint`, which would gives you a `Debugger.Frame` as argument of the `hit` function. Then you can register the `onStep` callback there and continue this way. I am sure some improvement could be made to make this API more async-await friendly. However, Generators and Promises did not exists at the time.

[1] https://developer.mozilla.org/en-US/docs/Tools/Debugger-API
[2] https://searchfox.org/mozilla-central/source/js/src/jit-test/tests/debug
[3] https://developer.mozilla.org/en-US/docs/Tools/Debugger-API/Tutorial-Breakpoint

Steve Fink

unread,
Apr 6, 2020, 10:55:51 AM4/6/20
to dev-tech-...@lists.mozilla.org
Yes, using the Debugger interface directly requires wrapping your brain
around what it means to have your code and debugger code running on the
same stack. Hitting a `debugger;` statement or a breakpoint means
invoking the debugger script as if the application code called it.
Resuming execution means returning from the debugger code.

There is an example debugger that works with the JS shell in the Gecko
repository at `js/examples/jorendb.js`. As the comment at the top says:

/*
 * jorendb is a simple command-line debugger for shell-js programs. It is
 * intended as a demo of the Debugger object (as there are no shell js
programs
 * to speak of).
 *
 * To run it: $JS -d path/to/this/file/jorendb.js
 * To run some JS code under it, try:
 *    (jorendb) print load("my-script-to-debug.js")
 * Execution will stop at debugger statements and you'll get a jorendb
prompt.
 */

It is bare-bones, and as nbp said it was written before Promises and
associated async/await syntax existed. It still works, more or less, but
it's not a supported or maintained tool. (It has precisely one user --
me -- who attempts to use it for anything "real".) The author, Jason
Orendorff (hence the name), only wrote it as a demonstration and does
not use it. It doesn't support breakpoints -- or rather, it doesn't
support *setting* breakpoints because the APIs for enumerating available
scripts in which to set breakpoints were problematic for a long time and
changed a bunch.

That said, if you want to decipher how one would write an in-process
debugger with the Debugger API, it should provide a good example.

lemmel

unread,
Apr 6, 2020, 3:20:21 PM4/6/20
to dev-tech-...@lists.mozilla.org
Thanks for your input !

So if I read you correctly for the following simple scenario :
- A) user gives me code
- B) the code is executed in mozjs
- C) while executing, the user press pause
- D) the code is paused in mozjs
- E) the user « step over »

B) several steps:
- I should initialize the engine, register all my utility functions (e.g. those managing events) and set the debugger (with code like the one in https://developer.mozilla.org/en-US/docs/Tools/Debugger-API/Tutorial-Breakpoint) through a call to JS::Evaluate
- then I give the user code to the engine through a call to JS::Evaluate
- immediately a "onNewScript" event is triggered (in ./Debugger.md)

C) normally my « callback » on onStep (through a onEnterFrame like in js/src/jit-test/tests/debug/Frame-onStep-01.js) ignore the "steps"
D) but after the user order, I block the mozjs library by not returning from my callback ? Is it the trick ?
In js/src/doc/Debugger/Debugger-API.md, it is said that "Both the debuggee and the code using `Debugger` to observe it must run in the same thread" (the code "using the Debugger" is mine I suppose), so no risks of corruption.
E) and so on

Did I understand correctly ?
> _______________________________________________
> dev-tech-js-engine mailing list
> dev-tech-...@lists.mozilla.org
> https://lists.mozilla.org/listinfo/dev-tech-js-engine
>




Nicolas B. Pierron

unread,
Apr 7, 2020, 5:48:13 AM4/7/20
to
On Monday, April 6, 2020 at 9:20:21 PM UTC+2, lemmel wrote:
> - C) while executing, the user press pause

The problem you will have is that the user pressing pause will run concurrently with the JavaScript execution. Also, you do not want to execute `onStep` for each instruction as this would be terribly slow.

Instead, what you can do from C++ is to trigger an Interruption callback, this breaks the semantic of JavaScript in many ways … but this gives you the opportunity to "pause" the execution by setting up the `onStep` callback on `Debugger.getNewestFrame()`. Once resuming from the interruption callback, the `onStep` callback should be called.

For information about interruption callback, I will forward you to the code, because I do not know if we have any good documentation on the subject. The JS Shell has some builtins to register a callback[1], and it can be triggered asynchronously using `JS_RequestInterruptCallback` [2].

[1] https://searchfox.org/mozilla-central/source/js/src/shell/js.cpp#4588
[2] https://searchfox.org/mozilla-central/source/js/src/jsapi.cpp#4093,4097

lemmel

unread,
Apr 7, 2020, 3:12:31 PM4/7/20
to dev-tech-...@lists.mozilla.org
Thanks, I think that I have now some ideas about how to proceed; probably this week-end.


PS: When I send a message to the list I receive a message (spam ?!) from "Support TheFork <not...@thefork.com> (TheFork, a TripAdvisor Company)"; I joined the mail as attachment, and give below the content:

-----
Hello ,
Thanks for your email. We have received your request 04464655 and it is being processed by our Support team.
To leave additional comments, please reply to this email.
Best,
TheFork team
-----

lemmel

unread,
Apr 11, 2020, 12:39:36 PM4/11/20
to dev-tech-...@lists.mozilla.org
Hi !

I did a lot of work, and I'm now able to:
- run arbitrary JS code through a call to JS::Evaluate (and I know now that the JSContext - obtained via JS_NewContext - must stick to only one thread) any number of time I want
- retrieve an error generated from the code (in this order: JS_IsExceptionPending, JS_GetPendingException, js::ErrorReport, JS_ClearPendingException)
- "pause" the engine execution (registered my callback with JS_AddInterruptCallback and triggered the pause via JS_RequestInterruptCallback)
- stop all graciously

But I don't know how to set a debugger (the object Debugger is not defined):
- can't achieve to load jsdebugger.jsm (JS::Evaluate of Components.utils.import("resource://gre/modules/jsdebugger.jsm"); does nothing as there is no Component object)
- even copy/path of the content of jsdebugger.jsm failed (JS::Evaluate of > const init = Cc["@mozilla.org/jsdebugger;1"].createInstance(Ci.IJSDebugger); < failed on Cc that is unknown)


Could someone give me a hint ?

Thanks for your attention.

PS: I took the liberty to describe what I did for other people that might be looking for information

Steve Fink

unread,
Apr 11, 2020, 12:45:34 PM4/11/20
to dev-tech-...@lists.mozilla.org
On 4/11/20 9:39 AM, lemmel wrote:
> Hi !
>
> I did a lot of work, and I'm now able to:
> - run arbitrary JS code through a call to JS::Evaluate (and I know now that the JSContext - obtained via JS_NewContext - must stick to only one thread) any number of time I want
> - retrieve an error generated from the code (in this order: JS_IsExceptionPending, JS_GetPendingException, js::ErrorReport, JS_ClearPendingException)
> - "pause" the engine execution (registered my callback with JS_AddInterruptCallback and triggered the pause via JS_RequestInterruptCallback)
> - stop all graciously
>
> But I don't know how to set a debugger (the object Debugger is not defined):
> - can't achieve to load jsdebugger.jsm (JS::Evaluate of Components.utils.import("resource://gre/modules/jsdebugger.jsm"); does nothing as there is no Component object)
> - even copy/path of the content of jsdebugger.jsm failed (JS::Evaluate of > const init = Cc["@mozilla.org/jsdebugger;1"].createInstance(Ci.IJSDebugger); < failed on Cc that is unknown)

jsdebugger.jsm requires the full browser. If you're only embedding
mozjs, you will need to use the Debugger interface directly.
https://developer.mozilla.org/en-US/docs/Tools/Debugger-API


lemmel

unread,
Apr 11, 2020, 1:22:56 PM4/11/20
to dev-tech-...@lists.mozilla.org
Thanks for your answer !

Ok for the jsdebugger.jsm being a browser only tool.

But how to make available the Debugger in the mozjs ?
Currently any use of Debbuger[1], trigger an exception like this one : Exception at line 1 column 11 from file ><, errorMessage=ReferenceError: Debugger is not defined



[1] through a call JS::Evaluate with :
-------------------------
var dbg = new Debugger(root);
dbg.onEnterFrame = function (frame) {
frame.onStep = function () {
onStepCallback();
};
}
-------------------------

Steve Fink

unread,
Apr 11, 2020, 2:25:02 PM4/11/20
to dev-tech-...@lists.mozilla.org
It looks like you need to call JS_DefineDebuggerObject() during your
startup.
Reply all
Reply to author
Forward
0 new messages