Codebase list duktape / run/d5adf042-7cb5-49a1-8165-c22db32bcb72/main debugger
run/d5adf042-7cb5-49a1-8165-c22db32bcb72/main

Tree @run/d5adf042-7cb5-49a1-8165-c22db32bcb72/main (Download .tar.gz)

=========================================
Duktape debug client and JSON debug proxy
=========================================

Overview
========

Debugger web UI which connects to the Duktape command line tool or any other
target supporting the example TCP transport (``examples/debug-trans-socket``)
on Unix and Windows.

Also provides a JSON debug proxy with a JSON mapping for the Duktape debug
protocol.

For detailed documentation of the debugger internals, see `debugger.rst`__.

__ https://github.com/svaarala/duktape/blob/master/doc/debugger.rst

Using the debugger web UI
=========================

Some prerequisites:

* You'll need Node.js v0.10.x or newer.  Older Node.js versions don't support
  the required packages.

Compile Duktape command line tool with debugger support:

* Enable ``DUK_USE_DEBUGGER_SUPPORT`` and ``DUK_USE_INTERRUPT_COUNTER`` for
  ``tools/configure.py``.

* Enable ``DUK_CMDLINE_DEBUGGER_SUPPORT`` on compiler command line for Duktape
  command line utility.

The source distributable contains a Makefile to build a "duk" command with
debugger support::

    $ cd <duktape dist directory>
    $ make -f Makefile.dukdebug

The Duktape Git repo "duk" target has debugger support enabled by default::

    $ make clean duk

Start Duktape command line tool so that it waits for a debugger connection::

    # For now we need to be in the directory containing the source files
    # executed so that the 'fileName' properties of functions will match
    # that on the debug client.

    # Using source distributable
    $ cd <duktape dist directory>
    $ ./duk --debugger mandel.js

    # Using Duktape Git repo
    $ cd <duktape checkout>/tests/ecmascript/
    $ ../../duk --debugger test-dev-mandel2-func.js

Start the web UI::

    # Must be in 'debugger' directory.

    $ cd debugger/
    $ make  # runs 'node duk_debug.js'

Once the required packages are installed, the NodeJS debug client will be
up and running.  Open the following in your browser and start debugging:

* http://localhost:9092/

The debug client automatically attaches to the debug target on startup.
If you start the debug target later, you'll need to click "Attach" in the
web UI.

Using the JSON debug proxy
==========================

There are two JSON debug proxy implementations: one implemented in DukLuv
and another in Node.js.

DukLuv JSON proxy
-----------------

DukLuv (https://github.com/creationix/dukluv) is a small and portable event
loop based on LibUV and Duktape with MIT license (like Duktape).  As such it's
easy to embed in a custom debug client: you just include the DukLuv executable
and the JSON proxy source file in your debug client.

Install DukLuv:

* Ensure ``cmake`` is installed

* ``git clone https://github.com/creationix/dukluv.git``

* ``cd dukluv``

* ``git submodule init; git submodule update``

* ``make``

* Binary should appear in:

  - ``./build/dukluv`` on Linux

  - ``.\build\Debug\dukluv.exe`` on Windows

Run the proxy::

    # Using Makefile; autogenerates duk_debug_meta.json
    # (You may need to edit DUKLUV in Makefile to point to your DukLuv)
    $ make runproxydukluv

    # Manually: see "dukluv duk_debug_proxy.js --help" for help
    $ .../path/to/dukluv duk_debug_proxy.js

Start Duktape command line (or whatever your target is)::

    $ cd <duktape checkout>/tests/ecmascript/
    $ ../../duk --debugger test-dev-mandel2-func.js

Now connect to the proxy using e.g. telnet::

    $ telnet localhost 9093

The proxy will then connect to the target and you can start issuing commands::

    $ telnet localhost 9093
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    {"notify":"_TargetConnecting","args":["127.0.0.1",9091]}
    {"notify":"_TargetConnected","args":["1 10499 v1.4.0-140-gc9a6c7c duk command built from Duktape repo"]}
    {"notify":"Status","command":1,"args":[1,"test-dev-mandel2-func.js","global",58,0]}
    {"request":"BasicInfo"}
    {"reply":true,"args":[10499,"v1.4.0-140-gc9a6c7c","duk command built from Duktape repo",1]}
    {"request":"Eval","args":["print('Hello world!'); 123;"]}
    {"notify":"Print","command":2,"args":["Hello world!\n"]}
    {"reply":true,"args":[0,{"type":"number","data":"405ec00000000000"}]}
    [...]

The proxy log provides dumps both JSON and dvalue binary traffic which is
quite useful in development::

    $ make runproxydukluv
    Running Dukluv based debug proxy
    "dukluv" duk_debug_proxy.js --log-level 2 --metadata duk_debug_meta.json
    2016-02-17T13:59:42.308Z INF Proxy: Read proxy metadata from duk_debug_meta.json
    2016-02-17T13:59:42.325Z INF Proxy: Listening for incoming JSON debug connection on 0.0.0.0:9093, target is 127.0.0.1:9091
    2016-02-17T13:59:47.994Z INF Proxy: JSON proxy client connected
    2016-02-17T13:59:47.994Z INF Proxy: Connecting to debug target at 127.0.0.1:9091
    2016-02-17T13:59:47.994Z INF Proxy: PROXY --> CLIENT: {"notify":"_TargetConnecting","args":["127.0.0.1",9091]}
    2016-02-17T13:59:47.994Z INF Proxy: Connected to debug target at 127.0.0.1:9091
    2016-02-17T13:59:48.003Z INF Proxy: PROXY --> CLIENT: {"notify":"_TargetConnected","args":["1 10499 v1.4.0-140-gc9a6c7c duk command built from Duktape repo"]}
    2016-02-17T13:59:48.003Z INF Proxy: Target handshake: {"line":"1 10499 v1.4.0-140-gc9a6c7c duk command built from Duktape repo","protocolVersion":1,"text":"10499 v1.4.0-140-gc9a6c7c duk command built from Duktape repo","dukVersion":"1","dukGitDescribe":"10499","targetString":"v1.4.0-140-gc9a6c7c"}
    2016-02-17T13:59:48.151Z INF Proxy: PROXY <-- TARGET: |04|
    2016-02-17T13:59:48.152Z INF Proxy: PROXY <-- TARGET: |81|
    2016-02-17T13:59:48.152Z INF Proxy: PROXY <-- TARGET: |81|
    2016-02-17T13:59:48.160Z INF Proxy: PROXY <-- TARGET: |78746573742d6465762d6d616e64656c322d66756e632e6a73|
    2016-02-17T13:59:48.161Z INF Proxy: PROXY <-- TARGET: |66676c6f62616c|
    2016-02-17T13:59:48.165Z INF Proxy: PROXY <-- TARGET: |ba|
    2016-02-17T13:59:48.165Z INF Proxy: PROXY <-- TARGET: |80|
    2016-02-17T13:59:48.165Z INF Proxy: PROXY <-- TARGET: |00|
    2016-02-17T13:59:48.165Z INF Proxy: PROXY --> CLIENT: {"notify":"Status","command":1,"args":[1,"test-dev-mandel2-func.js","global",58,0]}
    2016-02-17T13:59:51.289Z INF Proxy: PROXY <-- CLIENT: {"request":"BasicInfo"}
    2016-02-17T13:59:51.289Z INF Proxy: PROXY --> TARGET: |01|
    2016-02-17T13:59:51.289Z INF Proxy: PROXY --> TARGET: |90|
    2016-02-17T13:59:51.289Z INF Proxy: PROXY --> TARGET: |00|
    2016-02-17T13:59:51.291Z INF Proxy: PROXY <-- TARGET: |02|
    2016-02-17T13:59:51.291Z INF Proxy: PROXY <-- TARGET: |e903|
    2016-02-17T13:59:51.292Z INF Proxy: PROXY <-- TARGET: |7376312e342e302d3134302d6763396136633763|
    2016-02-17T13:59:51.293Z INF Proxy: PROXY <-- TARGET: |12002364756b20636f6d6d616e64206275696c742066726f6d2044756b74617065207265706f|
    2016-02-17T13:59:51.293Z INF Proxy: PROXY <-- TARGET: |81|
    2016-02-17T13:59:51.293Z INF Proxy: PROXY <-- TARGET: |00|
    2016-02-17T13:59:51.293Z INF Proxy: PROXY --> CLIENT: {"reply":true,"args":[10499,"v1.4.0-140-gc9a6c7c","duk command built from Duktape repo",1]}
    2016-02-17T14:00:06.105Z INF Proxy: PROXY <-- CLIENT: {"request":"Eval","args":["print('Hello world!'); 123;"]}
    2016-02-17T14:00:06.105Z INF Proxy: PROXY --> TARGET: |01|
    2016-02-17T14:00:06.105Z INF Proxy: PROXY --> TARGET: |9e|
    2016-02-17T14:00:06.105Z INF Proxy: PROXY --> TARGET: |7b7072696e74282748656c6c6f20776f726c642127293b203132333b|
    2016-02-17T14:00:06.105Z INF Proxy: PROXY --> TARGET: |00|
    2016-02-17T14:00:06.167Z INF Proxy: PROXY <-- TARGET: |04|
    2016-02-17T14:00:06.167Z INF Proxy: PROXY <-- TARGET: |82|
    2016-02-17T14:00:06.167Z INF Proxy: PROXY <-- TARGET: |6d48656c6c6f20776f726c64210a|
    2016-02-17T14:00:06.168Z INF Proxy: PROXY <-- TARGET: |00|
    2016-02-17T14:00:06.168Z INF Proxy: PROXY --> CLIENT: {"notify":"Print","command":2,"args":["Hello world!\n"]}
    2016-02-17T14:00:06.171Z INF Proxy: PROXY <-- TARGET: |02|
    2016-02-17T14:00:06.171Z INF Proxy: PROXY <-- TARGET: |80|
    2016-02-17T14:00:06.173Z INF Proxy: PROXY <-- TARGET: |1a405ec00000000000|
    2016-02-17T14:00:06.173Z INF Proxy: PROXY <-- TARGET: |00|
    2016-02-17T14:00:06.174Z INF Proxy: PROXY --> CLIENT: {"reply":true,"args":[0,{"type":"number","data":"405ec00000000000"}]}
    [...]

Node.js JSON proxy
------------------

A Node.js-based JSON debug proxy is also provided by ``duk_debug.js``::

    # Same prerequisites as for running the debug client
    $ make runproxynodejs

Start Duktape command line (or whatever your target is)::

    $ cd <duktape checkout>/tests/ecmascript/
    $ ../../duk --debugger test-dev-mandel2-func.js

You can then connect to localhost:9093 and interact with the proxy.
Here's an example session using telnet and manually typed in commands
The ``-->`` (send) and ``<--`` (receiver) markers have been added for
readability and are not part of the stream::

    $ telnet localhost 9093
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    <-- {"notify":"_TargetConnected","args":["1 10199 v1.1.0-275-gbd4d610-dirty duk command built from Duktape repo"]}
    <-- {"notify":"Status","command":1,"args":[1,"test-dev-mandel2-func.js","global",58,0]}
    --> {"request":"BasicInfo"}
    <-- {"reply":true,"args":[10199,"v1.1.0-275-gbd4d610-dirty","duk command built from Duktape repo",1]}
    --> {"request":"Eval", "args":[ "print(Math.PI)" ]}
    <-- {"notify":"Print","command":2,"args":["3.141592653589793\n"]}
    <-- {"reply":true,"args":[0,{"type":"undefined"}]}
    --> {"request":"Resume"}
    <-- {"reply":true,"args":[]}
    <-- {"notify":"Status","command":1,"args":[0,"test-dev-mandel2-func.js","global",58,0]}
    <-- {"notify":"Status","command":1,"args":[0,"test-dev-mandel2-func.js","global",58,0]}
    <-- {"notify":"Print","command":2,"args":["................................................................................\n"]}
    <-- {"notify":"Print","command":2,"args":["................................................................................\n"]}
    <-- {"notify":"Print","command":2,"args":["................................................................................\n"]}
    [...]
    <-- {"notify":"_Disconnecting"}

A telnet connection allows you to experiment with debug commands by simply
copy-pasting debug commands to the telnet session.  This is useful even if
you decide to implement the binary protocol directly.

The debug target used by the proxy can be configured with ``duk_debug.js``
command line options.

Source search path
==================

The NodeJS debug client needs to be able to find source code files matching
code running on the target ("duk" command line).  **The filenames used on the
target and on the debug client must match exactly**, because e.g. breakpoints
are targeted based on the 'fileName' property of Function objects.

The search path can be set using the ``--source-dirs`` option given to
``duk_debug.js``, with the default search paths including only
``../tests/ecmascript/``.

The default search path means that if a function on the target has fileName
``foo/bar.js`` it would be loaded from (relative to the duk_debug.js working
directory, ``debugger/``)::

    ../tests/ecmascript/foo/bar.js

Similarly, if the filesystem contained::

    ../tests/ecmascript/baz/quux.js

the web UI dropdown would show ``baz/quux.js``.  If you selected that file
and added a breakpoint, the breakpoint fileName sent to the debug target
would be ``baz/quux.js``.

.. note:: There's much to improve in the search path.  For instance, it'd
          be nice to add a certain path to search but exclude files based
          on paths and patterns, etc.

Architecture
============

::

    +-------------------+
    | Web browser       |  [debug UI]
    +-------------------+
          |
          | http (port 9092)
          | socket.io
          v
    +-------------------+
    | duk_debug.js      |  [debug client]
    +-------------------+
          |          /\
          |          ||
          +----------||---- [example tcp transport] (port 9091)
          |          ||     (application provides concrete transport)
          |          ||
          |          ||---- [debug protocol stream]
          |          ||     (between debug client and Duktape)
          |          ||
    + - - | - - - - -|| - - +
    :     v          ||     :
    :  +-------------||-+   :  [target]
    :  | application || |   :
    :  +-------------||-+   :
    :     ^          ||     :
    :     |          ||     :   [debug API]
    :     +----------||-------- debug transport callbacks
    :     |          ||     :   (read, write, peek, read/write flush)
    :     |          ||     :   implemented by application
    :     |          \/     :
    :  +----------------+   :
    :  | Duktape        |   :
    :  +----------------+   :
    + - - - - - - - - - - - +

The debug transport is application specific:

* Duktape command line ("duk") and this debug client use an **example** TCP
  transport as a concrete example.

* It is entirely up to the application to come up with the most suitable
  transport for its environment.  Different mechanisms will be needed for
  Wi-Fi, serial, etc.

The debug protocol running inside the transport is transport independent:

* The debug protocol is documented in ``doc/debugger.rst``.

* This debug client provides further concrete examples and clarifications
  on how the protocol can be used.

Using a custom transport
========================

Quite possibly your target device cannot use the example TCP transport and
you need to implement your own transport.  You'll need to implement your
custom transport both for the target device and for the debug client.

Target device
-------------

Implement the debug transport callbacks needed by ``duk_debugger_attach()``.

See ``doc/debugger.rst`` for details and ``examples/debug-trans-socket``
for example running code for a TCP transport.

Debug client alternative 1: duk_debug.js + custom TCP proxy
-----------------------------------------------------------

If you don't want to change ``duk_debug.js`` you can implement a TCP proxy
which accepts a TCP connection from ``duk_debug.js`` and then uses your
custom transport to talk to the target::

   +--------------+   TCP   +-------+   custom   +--------+
   | duk_debug.js | ------> | proxy | ---------> | target |
   +--------------+         +-------+            +--------+

This is a straightforward option and a proxy can be used with other debug
clients too (perhaps custom scripts talking to the target etc).

You could also use netcat and implement your proxy so that it talks to
``duk_debug.js`` using stdin/stdout.

Debug client alternative 2: duk_debug.js + custom NodeJS stream
---------------------------------------------------------------

To make ``duk_debug.js`` use a custom transport you need to:

* Implement your own transport as NodeJS stream.  You can add it directly to
  ``duk_debug.js`` but it's probably easiest to use a separate module so that
  the diff to ``duk_debug.js`` stays minimal.

* Change ``duk_debug.js`` to use the custom transport instead of a TCP
  stream.  Search for "CUSTOMTRANSPORT" in ``duk_debug.js``.

See:

* http://nodejs.org/api/stream.html

* https://github.com/substack/stream-handbook

Debug client alternative 3: custom debug client
-----------------------------------------------

You can also implement your own debug client and debug UI with support for
your custom transport.

You'll also need to implement the client part of the Duktape debugger
protocol.  See ``doc/debugger.rst`` for the specification and ``duk_debug.js``
for example running code which should illustrate the protocol in more detail.

The JSON debug proxy allows you to implement a debug client without needing
to implement the Duktape binary debug protocol.  The JSON protocol provides
a roughly 1:1 mapping to the binary protocol but with an easier syntax.