Remote Debugging Protocol: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(Rename page to Remote Debugging Protocol; the full protocol documentation will be here.)
 
(Rewrite protocol documentation.)
Line 1: Line 1:
Mozilla will support remote debugging with a JSON-based protocol, roughly modeled after the [http://code.google.com/p/v8/wiki/DebuggerProtocol V8 Debugger Protocol].
<i>(Note: this page is a draft design of work not yet completed. It is written in the present tense to be easily promoted to documentation when implemented, and also to simplify the grammar.)</i>


The remote protocol operates at the JavaScript level, not at the C++ or
The Mozilla debugging protocol allows a debugger to connect to a browser, discover what sorts of things are present to debug or inspect, select JavaScript threads to watch, and observe and modify their execution. The protocol provides a unified view of JavaScript, DOM nodes, CSS rules, and the other technologies used in client-side web applications. The protocol is meant to be sufficiently general to be extended for use with other sorts of clients (profilers, say) and servers (mail readers; random XULrunner applications).
machine level. It assumes that the JavaScript implementation itself is
healthy and responsive: the JavaScript program being executed may have gone
wrong, but the JavaScript implementation's internal state must not be
corrupt. Bugs in the implementation may cause the debugger to fail; bugs in
the interpreted program must not.


= Debugging States =
All communication between debugger (client) and browser (server) is in the form of JSON objects. This makes the protocol directly readable by humans, capable of graceful evolution, and easy to implement using stock libraries. In particular, it should be easy to create mock implementations for testing and experimentation.


When a debugger client first makes a connection to a Mozilla process, no
The protocol operates at the JavaScript level, not at the C++ or machine level, and assumes that the JavaScript implementation itself is healthy and responsive. The JavaScript program being executed may well have gone wrong, but the JavaScript implementation's internal state must not be corrupt. Bugs in the implementation may cause the debugger to fail; bugs in the interpreted program must not.
debugging sphere has yet been selected, and the server uses the js::dbg2
sphere discovery facilities to answer client requests about what is
available to be debugged in that process:


[[File:Initial-connection.png]]
= Actors =


Selecting a debugging sphere establishes event handlers which invoke the
The Mozilla debugging protocol is an actor-oriented protocol. An "actor" is something on the server that receives and replies to JSON packets from the client. Every packet from the client specifies the actor to which it is directed, and every packet sent in reply indicates the actor that sent it.
debug server to communicate the news to the debugger. Assuming the selected
sphere runs on the main thread, the stack looks like this while the event
is being reported:


[[File:main-thread-event.png]]
Each server has a root actor, with which the client first interacts. The root actor can explain what sort of thing the server represents (browser; mail reader; etc.), and enumerate things available to debug: tabs, chrome, and so on. Each of these, in turn, is an actor to which requests can be addressed.


When the debugger asks the debuggee to continue, the frames for the
For example, a debugger might connect to a browser, ask the root actor to list the browser's tabs, and present this list to the developer. If the developer chooses some tabs to debug, then the debugger sends requests to begin debugging to the actors representing those tabs. JavaScript objects, stack frames, breakpoints, and other entities encountered in the course of debugging are all actors with which the debugger can interact.
js::dbg2 dispatch facilities simply return, and the code that generated the
event resumes execution.


If the user asks the debugger to evaluate an expression that requires
Each actor (other than the root) has a parent actor; closing communications with the parent closes communications with all its descendants. This establishes a lifetime for actor names, which allows the server to free associated storage, remove breakpoints, and so on. The root actor has no owner, and lives as long as the underlying connection to the client does.
evaluating JavaScript code (like <tt>e.x()</tt>), then that evaluation
takes place without leaving the dynamic scope of the trap handlers, but in
the static scope in which the event was generated, using js::dbg2's
evaluateInFrame facility:


[[File:Expression-evaluation.png]]
= Packets =


If evaluation of the expression throws an exception or hits a breakpoint,
The protocol is carried by a reliable, bi-directional byte stream; data sent in both directions consists of JSON objects, called packets. A packet is a top-level JSON object, not contained inside any other value.
then the result is a matter of user interface. Either we abandon evaluation
of the expression, and control returns to the original debug protocol
server frames, or we treat the event as something to be investigated, just
as if it had occurred in the debuggee's normal course of execution,
resulting in a nested debug server invocation:


[[File:Expression-nested-exception.png]]
Every packet sent from the client has the form:


If the debugger elects to debug a worker thread, the main thread acts as a
  { "to": <i>actor</i>, "type": <i>type</i>, ... }
proxy, relaying messages to the worker thread's server:


[[File:worker-thread-event.png]]
where <i>actor</i> is a JSON integer naming the actor to whom the packet is directed, and <i>type</i> is a string specifying what sort of packet it is. Additional properties may be present, depending on <i>type</i>.


(I am not sure whether we want to keep things this way, or have the
Every packet sent from the server has the form:
debugger connect directly to its debuggees. Being able to debug many
threads with a single socket connection seems like a win, and we still get
the inter-thread synchronization benefits; but latency might affect the
debuggers' users' experience. Let's code it and see!)


If Mozilla puts content and chrome in separate processes, then the remote
  { "from": <i>actor</i>, ... }
protocol can be readily used to continue to support content debugging:


[[File:content-process.png]]
where <i>actor</i> is a JSON integer naming the actor that sent it. Additional properties may be present, depending on the situation.
 
= Requests and Replies =
 
In this protocol description, a "request" is a packet sent from the client which always elicits a single packet from the recipient, the "reply". These terms indicate a simple communications state where either the client or actor is permitted to send a packet, but never both.
 
The client's communication with each actor is treated separately: a client may send a request to one actor, and then send a request to another before receiving a reply to the first.
 
Packets not described as "requests" or "replies" are part of some more complicated interaction, which should be spelled out in more detail.
 
= Thread Interactions =
 
Actors representing threads of JavaScript execution follow a more complicated communication pattern. A thread is always in one of the following states:
 
* <b>Detached</b>: the thread is running freely, and not presently interacting with the debugger. Detached threads run, encounter errors, and exit without exchanging any sort of messages with the debugger. A debugger can attach to a thread, putting it in the "Running" state. Or, a detached thread may exit on its own, entering the "Exited" state.
 
* <b>Running</b>: the thread is running under the debugger's observation, executing JavaScript code or possibly blocked waiting for input. It will report exceptions, breakpoint hits, watchpoint hits, and other interesting events to the client, and enter the "paused" state. The debugger can also interrupt a running thread; this elicits a response and puts the thread in the "paused" state. A running thread may also exit, entering the "exited" state.
 
* <b>Paused</b>: the thread has reported a pause to the client and is awaiting further instructions. In this state, a thread can accept requests and send replies. If the client asks the thread to continue or step, it returns to the "running" state.
 
* <b>Exited</b>: the thread has ceased execution, and will disappear. The resources of the underlying thread may have been freed; this state really indicates that the actor's name is not yet available for reuse.
 
[[File:thread-states.png]]
 
These interactions are meant to have certain properties:
 
* At no point may either client or server send an unbounded number of packets without receiving a packet from its counterpart. This avoids deadlock without requiring either side to buffer an arbitrary number of packets per actor.
 
* In states where a transition can be initiated by either the debugger or the thread, it is always clear to the debugger which state the thread actually entered, and for what reason.<p>For example, if the debugger interrupts a running thread, it cannot be sure whether the thread stopped because of the interruption, paused of its own accord (to report a watchpoint hit, say), or exited. However, the next packet the debugger receives will either be "interrupted", "paused", or "exited", resolving the ambiguity.</p><p>Similarly, when the debugger attaches to a thread, it cannot be sure whether it has succeeded in attaching to the thread, or whether the thread exited before the attachment request arrived. However, in either case the debugger can expect a disambiguating response: if the attach suceeded, it receives an "attached" packet; and in the second case, it receives an "exit" packet.</p><p>To support this property, the thread ignores certain debugger packets in some states (the "interrupt" packet in the "Paused" and "Exited" states, for exmple). These cases all handle situations where the ignored packet was preempted by some thread action.</p>
 
Some more post-list text.

Revision as of 23:03, 15 July 2010

(Note: this page is a draft design of work not yet completed. It is written in the present tense to be easily promoted to documentation when implemented, and also to simplify the grammar.)

The Mozilla debugging protocol allows a debugger to connect to a browser, discover what sorts of things are present to debug or inspect, select JavaScript threads to watch, and observe and modify their execution. The protocol provides a unified view of JavaScript, DOM nodes, CSS rules, and the other technologies used in client-side web applications. The protocol is meant to be sufficiently general to be extended for use with other sorts of clients (profilers, say) and servers (mail readers; random XULrunner applications).

All communication between debugger (client) and browser (server) is in the form of JSON objects. This makes the protocol directly readable by humans, capable of graceful evolution, and easy to implement using stock libraries. In particular, it should be easy to create mock implementations for testing and experimentation.

The protocol operates at the JavaScript level, not at the C++ or machine level, and assumes that the JavaScript implementation itself is healthy and responsive. The JavaScript program being executed may well have gone wrong, but the JavaScript implementation's internal state must not be corrupt. Bugs in the implementation may cause the debugger to fail; bugs in the interpreted program must not.

Actors

The Mozilla debugging protocol is an actor-oriented protocol. An "actor" is something on the server that receives and replies to JSON packets from the client. Every packet from the client specifies the actor to which it is directed, and every packet sent in reply indicates the actor that sent it.

Each server has a root actor, with which the client first interacts. The root actor can explain what sort of thing the server represents (browser; mail reader; etc.), and enumerate things available to debug: tabs, chrome, and so on. Each of these, in turn, is an actor to which requests can be addressed.

For example, a debugger might connect to a browser, ask the root actor to list the browser's tabs, and present this list to the developer. If the developer chooses some tabs to debug, then the debugger sends requests to begin debugging to the actors representing those tabs. JavaScript objects, stack frames, breakpoints, and other entities encountered in the course of debugging are all actors with which the debugger can interact.

Each actor (other than the root) has a parent actor; closing communications with the parent closes communications with all its descendants. This establishes a lifetime for actor names, which allows the server to free associated storage, remove breakpoints, and so on. The root actor has no owner, and lives as long as the underlying connection to the client does.

Packets

The protocol is carried by a reliable, bi-directional byte stream; data sent in both directions consists of JSON objects, called packets. A packet is a top-level JSON object, not contained inside any other value.

Every packet sent from the client has the form:

 { "to": actor, "type": type, ... }

where actor is a JSON integer naming the actor to whom the packet is directed, and type is a string specifying what sort of packet it is. Additional properties may be present, depending on type.

Every packet sent from the server has the form:

 { "from": actor, ... }

where actor is a JSON integer naming the actor that sent it. Additional properties may be present, depending on the situation.

Requests and Replies

In this protocol description, a "request" is a packet sent from the client which always elicits a single packet from the recipient, the "reply". These terms indicate a simple communications state where either the client or actor is permitted to send a packet, but never both.

The client's communication with each actor is treated separately: a client may send a request to one actor, and then send a request to another before receiving a reply to the first.

Packets not described as "requests" or "replies" are part of some more complicated interaction, which should be spelled out in more detail.

Thread Interactions

Actors representing threads of JavaScript execution follow a more complicated communication pattern. A thread is always in one of the following states:

  • Detached: the thread is running freely, and not presently interacting with the debugger. Detached threads run, encounter errors, and exit without exchanging any sort of messages with the debugger. A debugger can attach to a thread, putting it in the "Running" state. Or, a detached thread may exit on its own, entering the "Exited" state.
  • Running: the thread is running under the debugger's observation, executing JavaScript code or possibly blocked waiting for input. It will report exceptions, breakpoint hits, watchpoint hits, and other interesting events to the client, and enter the "paused" state. The debugger can also interrupt a running thread; this elicits a response and puts the thread in the "paused" state. A running thread may also exit, entering the "exited" state.
  • Paused: the thread has reported a pause to the client and is awaiting further instructions. In this state, a thread can accept requests and send replies. If the client asks the thread to continue or step, it returns to the "running" state.
  • Exited: the thread has ceased execution, and will disappear. The resources of the underlying thread may have been freed; this state really indicates that the actor's name is not yet available for reuse.

Thread-states.png

These interactions are meant to have certain properties:

  • At no point may either client or server send an unbounded number of packets without receiving a packet from its counterpart. This avoids deadlock without requiring either side to buffer an arbitrary number of packets per actor.
  • In states where a transition can be initiated by either the debugger or the thread, it is always clear to the debugger which state the thread actually entered, and for what reason.

    For example, if the debugger interrupts a running thread, it cannot be sure whether the thread stopped because of the interruption, paused of its own accord (to report a watchpoint hit, say), or exited. However, the next packet the debugger receives will either be "interrupted", "paused", or "exited", resolving the ambiguity.

    Similarly, when the debugger attaches to a thread, it cannot be sure whether it has succeeded in attaching to the thread, or whether the thread exited before the attachment request arrived. However, in either case the debugger can expect a disambiguating response: if the attach suceeded, it receives an "attached" packet; and in the second case, it receives an "exit" packet.

    To support this property, the thread ignores certain debugger packets in some states (the "interrupt" packet in the "Paused" and "Exited" states, for exmple). These cases all handle situations where the ignored packet was preempted by some thread action.

Some more post-list text.