So you’ve got a native node.js addon written in C, C++, Objective-C, C, or something else. Debugging the JS is easy — you can use WebStorm’s built-in debugger (my preferred method), node-debug, iron-node, or others. But what if you want to inspect what’s going on in your native C++ code?
I’m a native node addon and C++ beginner myself, so I recently had to learn this myself and I was a little disappointed that there weren’t many resources on how to do this.
How it works
Mac OSX comes with a built-in command line tool called LLDB that we can use. This is what Xcode uses underneath the hood in it’s integrated debugger. We’ll run node.js and pass it a JS file from within LLDB. LLDB gives us an interactive shell where can set breakpoints, inspect values, modify values, and more. For some realism, let’s pretend we’re trying to debug this excellent and highly useful native node addon, node-desktop-bg. (Quick note on that module: It’s was my first attempt at a native module and it’s probably not very good. It uses Objective-C inline with C++. NS* prefixed types are all Objective-C data structures. I know, native node addon land is crazy). Okay, let’s get started.
Let’s do it step-by-step
First, you need to do a debug build of your module.
node-gyp build --debug
Then cd
to your project’s root. We need to know the absolute path to the node executable. Run:
which node
That will return the path to node.js. Now, run:
lldb -- your/path/to/node test.js
Be sure to pass whatever node.js file you want to pass the node.js process.
LLDB will start saying that it’s current target is set to node. Excellent. We still haven’t run any code yet though.
Now, let’s say we want to set a breakpoint on line 25 of DesktopBg.mm so we can check the value of isMain
during each for-loop iteration.
breakpoint set -f DesktopBg.mm -l 25
LLDB will tell you something like “Breakpoint 1: no locations (pending).”. Great.
Go-time.
run
LLDB will tell you the process launched and will give you a little snippet of your code and where it stopped:
23
24 bool isMain = isMainScreen(screens[i]);
25
->26 NSDictionary *screenDescription = [screens[i]
deviceDescription];
27 NSNumber *displayID = [screenDescription
objectForKey:@”NSScreenNumber”];
28 int screenId = [displayID intValue];
From here, we can inspect the value of variables that are within the current scope or closure. LLDB seems to be similar to Chrome dev tools in this regard — you have access to everything in the current scope’s state.
print isMain
This will output:
$0 = true
That $0
is a reference to isMain. We can run print $0
to output it again if we wanted. We could even modify the value of it using the expression
command. Let’s go to the next line.
next
What if we want to inspect the value of some more complicated data structures? Let’s see what that screenDescription
variable is all about on line 26.
(lldb) print screenDescription
(NSDictionary *) $2 = 0x0000000102502f10
Hmm, not too helpful. Let’s try:
(lldb) po screenDescription
{
NSDeviceBitsPerSample = 8;
NSDeviceColorSpaceName = NSCalibratedRGBColorSpace;
NSDeviceIsScreen = YES;
NSDeviceResolution = “NSSize: {144, 144}”;
NSDeviceSize = “NSSize: {1440, 900}”;
NSScreenNumber = 2077750397;
}
There we go! “po” stands for “print object”.
To resume normal execution of our code, we simply run continue
, and we should see output like so:
(lldb) continue
Process 59038 resuming
(lldb) [ { filepath: ‘/Library/Desktop Pictures/El Capitan.jpg’,
isMain: true,
id: 2077750397 } ]
Process 59038 exited with status = 0 (0x00000000)
Everything ran and the process exited like normal. Then exit
to quit LLDB.
Boom. Done!
Hopefully this helps someone new to native node addons get started. I’m sure this doesn’t even scratch the surface of what LLDB is capable of. There may be other ways of debugging native node.js addons. (Let me know if there is). With the growing popularity of Electron, I think there are even more use cases for native node addons than there was before. With that comes a greater need to be able to debug native node addon code so that you can write your own native modules and contribute to other open-source ones.
Update 6/23/17
You can tell node-gyp
to output an xcodeproj
file that you can open in XCode. This is awesome, because then you can use XCode’s built-in intellisense and debugger (sort of a thin UI over lldb — all the same commands work — but has some additional UI niceness) which simplifies development quite a bit. I learned it from this video which explains it really well:
node-gyp
outputs a Makefile as well, and I think can be configured to output a vcxproj
file too, so you could probably configure CLion, VisualStudio or other C/C++ IDEs to develop node addons.