It’s been recently that the old V8 debugger API has been removed from V8’s source code in favor of the more modern Inspector API.
This Inspector API is great, and allows me to debug my embedded Android V8 code using Chrome dev tools directly from the browser, or by using an standalone version of them. Profiling, memory dumps, source maps, breakpoints, all works like a charm (except minor bugs here and there mainly related to chrome version though). Unfortunately, there’s not much documentation on this Inspector integration from the embedder point of view.
Inspector integration process
The first to note about the Inspector is that inspection is per Isolate
. One single Inspector
object instance will be enough to debug all your javascript Context
s. The Isolate
is thread dependent, and as such you must keep your isolate in scope Isolate::Scope
when necessary. That said, the elements that will conform your inspection code are very simple:
InspectorClient
This object will be used to select what Context
we are currently debugging, but more importantly, it will handle runMessageLoopOnPause
and quitMessageLoopOnPause
methods. These two methods are called by V8 debugging internals when you are breaking into js
code from Dev Tools. While runMessageLoopOnPause
is being called, you must synchronously consume all front end (Dev Tools) debugging messages. If not, you will not get all context information of the code you are debugging. Once V8 knows it has no more inspector messages pending, it will call quitMessageLoopOnPause
automatically.
The InspectorClient could do the debugging initialisation process like this:
// create a v8 inspector client: // InspectorClientImpl : public v8_inspector::V8InspectorClient v8_inspector::InspectorClient = new InspectorClientImpl(); // create a v8 inspector instance. v8_inspector::V8Inspector inspector_ = v8_inspector::V8Inspector.create( isolate, inspectorClient ); // create a v8 channel. // ChannelImpl : public v8_inspector::V8Inspector::Channel v8_inspector::V8Inspector::Channel channel_ = new ChannelImpl(); v8_inspector::StringView view( ... ) // Create a debugging session by connecting the V8Inspector // instance to the channel v8_inspector::V8InspectorSession session_ = inspector_.connect( 1, channel, view); v8_inspector::StringView ctx_name( /*ctx_name*/ ) // make sure you register Context objects in the V8Inspector. // ctx_name will be shown in CDT/console. Call this for each context // your app creates. Normally just one btw. inspector_->contextCreated( v8_inspector::V8ContextInfo( context, 1, ctx_name);
That’s pretty much it. After this, you’ll have a valid debugging session. How do you, as a dev, interact with each of these elements: V8InspectorClient
, V8Inspector
, V8Inspector::Channel
, V8InspectorSession
? Well, to answer this question, first we call all this code happening in our V8-enabled app the debugging backend, which implicitly means we should have a debugging front-end.
V8InspectorSession
Ideally, the debugging front-end would be Chrome Dev Tools. CDT opens a WebSocket
to communicate with the debugging back-end. You can make this happen in Chrome with something like this:
chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:20000/backend
This causes chrome to open a dev tools only tab, w/o most DOM specific stuff. In my case, the 20000 is a forwarded port from my android app to a local port(adb forward tcp:20000 tcp:20000)
and /backend
in the url is a mount point on the backend WebSocket
listener. All front end inspector messages will be received on the backend websocket listening code, and must be forwarded to the debug session:
// msg is a std::string with whatever front sent to back. // normally a json object with sequence and payload. v8::internal::Vector<const char> v(msg.c_str(), msg.length()); // inspector session requires a v8_inspector::StringView v8_inspector::StringView message_view( reinterpret_cast<const uint8_t *>(v.start()), v.length()); // let the magic happen: session_->dispatchProtocolMessage( message_view );
The V8InspectorSession object is full of inspection love. I recommend you having a look at v8-inspector.h
header file. While all interaction happens from CDT front end, you’ll recognise a lot of functionality there like breakProgram, pause or resume
methods.
V8Inspector::Channel
All inspector protocol handling happens automagically. You don’t have to worry about front end message id sequences, or their responses. The only missing part is forward inspector session message results from backend to front end. Responses happen in the custom v8_inspector::V8Inspector::Channel
object implementation. Both methods:
sendProtocolResponse( int callId, const v8_inspector::StringView& msg); void sendProtocolNotification( const v8_inspector::StringView& msg);
will handle inspector protocol responses from commands received from inspection front end. Just convert msg
from StringView
to std::string (or whatever your code requires) and send to front end
Diagram
This is a small diagram of how things work:

Result
At the end of the process, you’ll get a full browser-enabled remote v8 debugging session. Here’s an screenshot of a sample app. All objects but console
are custom bound native objects. In this sample screenshot, the host application OS is Android.

Also note, this inspector-over-devtools integration also works on the Android emulator. In my Mac, I have an android emulator, running my app with embedded v8 and connected to dev tools on chrome to debug javascript… what a time to be alive !
Hello,
that’s a very nice article and I appreciate it. But how do you exactly load this InspectorClient in d8? Where do you put it? You wouldn’t have a working example somewhere on github, would you?
LikeLike
let’s move the conversation to private.
Thanks.
LikeLike
Which WebSocket library are you using for cross-platform support (you mentioned Android and Mac)? Any chance you’ll consider open sourcing your implementation? Thanks!
LikeLike
I am using a random websockets provider for android. Anyone will do.
I will consider open sourcing all my v8 related stuff. The inspector is just one piece of it.
LikeLike
Can you make a minimal example to github ? Thanks. Great article.
LikeLike
Yeah. I think I could do that.
Basically I ripped this code: https://source.chromium.org/chromium/chromium/src/+/master:v8/test/inspector/inspector-test.cc
and adapted it to android. It’s being working mostly untouched since I first compiled v8 5.2.
LikeLike
Hi,
Can I call make a call to js callback method from within runMessageLoopOnPause method ? ( so, calling JS method from C++ side via v8 api )
or i can only make a call to session_->dispatchProtocolMessage method and forward the messages received from frontend ?
LikeLike
When you run from runMessageLoopOnPause, v8 is synchronously calling to the outside world, primarily to dev tools front end. Javascript execution is paused, and the thread holding the isolate is waiting for a response, so no javascript can run at this point.
V8 is only expecting you to call into v8 using the backend debugging session channel (dispatchProtocolMessage).
Since the thread holding the isolate is locked, you won’t be able to call into javascript at all. Bear also in mind, you should consider runMessageLoopOnPause to be reentrant.
The information expected to be exchanged during this dialog between front/back end endopoints, are things like closure state, stack trace, variable values, etc. Whatever the frontend needs to show a proper debugging state.
Also, your JS thread will keep locked until v8 says otherwise (quitMessageLoopOnPause). E.g., when resuming execution. Or temporarily, when executing step-in/out.
Hope that helps.
LikeLike