385 lines
16 KiB
ReStructuredText
385 lines
16 KiB
ReStructuredText
=========================================
|
||
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.
|