Debugging Node.js with MDB

September 20, 2016 - by Wyatt Lyon Preul

The modular debugger (MDB) is a vital tool to have at your disposal for debugging issues in your Node.js application, no matter if they are trivial or complex, like that of the infamous Node.js memory leak. Unfortunately, setting up an environment to run MDB is often too much of a hurdle for developers to consider even using it. This post explains how to run MDB as a service to make debugging Node.js applications easier.

Over the years, Joyent has shared information on how to use MDB to debug core dumps generated from Node.js applications. The information in those posts is still valid, but there are new dcmds that are worth a demonstration. This post demonstrates some of the new commands. Additionally, it assumes that you don't have and don't plan to setup SmartOS. The only prerequisite is that you sign up for a free Manta account and install the Manta CLI.

Copying the core dump and node binary

To use MDB with a Node.js core dump, you will need to pass in the Node.js binary used to run the process that generated the core dump. And, of course, you will also need the core dump file itself. Once you have these files saved locally, you can upload them into Manta using the mput command. Below is an example of uploading the Node.js binary and a core dump to Manta.

mput -f node ~~/stor/
mput -f core.dump ~~/stor/

In addition to the above two files, you will also need the mdb_v8 binary file. Fortunately, this file is in Manta in a public location. As a result, you can use mln to create a SnapLink to the file. Below is an example of setting up a SnapLink named v8_amd64.so from the public file located at /Joyent_Dev/public/mdb_v8/v1.1.3/mdb_v8_amd64.so.

mln /Joyent_Dev/public/mdb_v8/v1.1.3/mdb_v8_amd64.so ~~/stor/v8_amd64.so

Launching a Manta session and running MDB

Now that all of the files required are in Manta, you can create a new interactive session and start debugging with MDB. To create a new session use the mlogin command specifying the two assets along with the main core file. Below is an example of running this command.

mlogin ~~/stor/core.dump -s ~~/stor/v8_amd64.so -s ~~/stor/node

mlogin connects you to Manta with the v8_amd64.so and node files available in the /assets/{username}/stor/ folder. Next, load MDB by passing in the path to the Node.js binary and the core dump file, below is what this command should look like, replacing {username} with your Manta username.

mdb /assets/{username}/stor/node $MANTA_INPUT_FILE

Load the v8_amd64.so file into MDB in order to gain access to the Node.js dcmds using the ::load command.

> ::load /assets/{username}/stor/v8_amd64.so

Everything should be loaded and ready. To verify that this is the case and to see some basic information about the process that created the core dump run the ::status command.

> ::status
debugging core file of node (64-bit) from smartos
file: /assets/{username}/stor/node
initial argv: node --abort-on-uncaught-exception server.js
threading model: raw lwps
status: process terminated by SIGILL (Illegal Instruction), addr=f401e9

V8 dcmds

To see a listing of all of the available dcmds, execute ::dcmds. If you want to see more information on a particular dcmd run ::help followed by the command. For example, to get the help information on the ::nodebuffer command execute the following.

> ::help nodebuffer

NAME
  nodebuffer - print details about the given Node Buffer

SYNOPSIS
  addr ::nodebuffer [-a]

ATTRIBUTES

  Target: proc
  Module: v8_amd64
  Interface Stability: Unstable

For the most part, ::findjsobjects is the command to become the most familiar with, as it will be what you will use to look for the JavaScript objects in memory. For example, if you want to see all of the requests to your process at the time the core dump was created you can use ::findjsobjects with the constructor flag as shown below.

> ::findjsobjects -c IncomingMessage

The ::findjsobjects command executed above, outputs the address for any object that has the constructor IncomingMessage. You can pipe this result to ::jsprint to see what the object values are.

> ::findjsobjects -c IncomingMessage | ::jsprint
{
    "_readableState": {
        "objectMode": false,
        "highWaterMark": 16384,
        "buffer": [...],

To see a listing of the functions that were in memory, use the ::jsfunctions command, as shown below.

> ::jsfunctions
            FUNC   #FUNCS NAME                                     FROM
    378381117f51        1 <anonymous> (as req.once)                /server.js line 11
    378381117be1        1 <anonymous> (as req.on)                  /server.js line 7
    378381116091        1 resOnFinish                              _http_server.js position 15138

If you are curious about what the actual source code of a function is, you can use the ::jssource command. For example, to print the source of resOnFinish shown above, the command would be the following.

> 378381116091::jssource
file: _http_server.js

  491     }
  492
  493     // When we're finished writing the response, check if this is the last
  494     // response, if so destroy the socket.
  495     res.on('finish', resOnFinish);
  496     function resOnFinish() {
  497       // Usually the first incoming element should be our request.  it may
  498       // be that in the case abortIncoming() was called that the incoming
  499       // array will be empty.
  500       assert(incoming.length === 0 || incoming[0] === req);

Similarly, to know what variables a function has a reference to, use the ::jsclosures command, as shown below.

> 378381116091::jsclosure
    "req": 3783811141d9: {
        "_readableState": 3783811142c9: [...],
        "readable": 1e40ce504299: false,
        "domain": 1e40ce504101: null,

When finished using MDB execute the ::quit command and exit the Manta session using exit.

Thoth

In addition to running MDB directly, there is a utility named Thoth that can be used to upload and analyze a core dump by using Manta Functions. Use npm to install manta-thoth globally (npm -g manta-thoth). Once installed, you can use Thoth to upload and issue commands into MDB. For example, the following shows how to upload a core into Manta and retrieve basic information about it, similar to running the ::status command previously.

$ thoth upload core.dump
thoth: creating 2bcc33c390eda0f5b106041f3bdb5910
...

$ thoth info 2bcc33c390eda0f5b106041f3bdb5910
{
    "name": "/{username}/stor/thoth/2bcc33c390eda0f5b106041f3bdb5910",
    "dump": "/{username}/stor/thoth/2bcc33c390eda0f5b106041f3bdb5910/core.dump",
    "pid": "50187",
    "cmd": "node",
    "psargs": "node --abort-on-uncaught-exception server.js",
    "platform": "joyent_20160622T234741Z",
    "node": "smartos",
    "version": "1",
    "time": 1473781733,
    "stack": []
}

Summary

It should be clear that there are many powerful commands available in MDB when using the mdb_v8 binaries. Over the years many commands have been added as well as support for debugging Linux core dumps. Additionally, there isn't even a need setup a local or hosted instance of SmartOS, as Manta is all you need for debugging purposes. Therefore, there hasn't been a better time to use MDB with Manta for post-mortem Node.js debugging.