Mobile Development 9 min read

How to Enable and Use JavaScriptCore Debugger on iOS: A Step‑by‑Step Guide

Learn how to unlock JavaScriptCore's hidden debugger on iOS by locating the framework version, building the source with private headers, creating a custom Debugger subclass, and integrating it into an Xcode project, complete with code examples and configuration tips.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
How to Enable and Use JavaScriptCore Debugger on iOS: A Step‑by‑Step Guide

JSC's debugger is a little‑known feature, especially on iOS where the Objective‑C interface does not expose any debugger information.

Because JavaScriptCore is open source, you can locate the

JSC::Debugger

abstract class, subclass it, implement the required virtual methods, instantiate it, and attach the instance to the global object to gain debugging capabilities.

On iOS the JavaScriptCore library is delivered as a framework, so only public headers and a static library are available. To use the debugger you must:

Compile with the private headers that match the framework version and use identical compile options.

Link against the framework's internal methods.

Solution steps:

Check the JSC version inside the framework.

Find the matching source code for that version.

Build JavaScriptCore to obtain the private headers.

Create a new Xcode project and add the private headers.

Adjust macros and compile options.

Write the debugger subclass and integration code.

Check JSC Version in the Framework

The framework resides in

/System/Library/Frameworks/JavaScriptCore.framework/

. Open the

version.plist

file to read the current version (e.g., 604.4.7.1.3 in Xcode 9.2).

Download Source Code

Visit https://svn.webkit.org/repository/webkit/tags and download the source tree for the identified version. You only need the

bmalloc

,

WTF

, and

JavaScriptCore

directories.

Build JSC

Create an Xcode workspace, drag the three projects into it, and build them in order:

bmalloc

,

WTF

, then

JavaScriptCore

. Build for the macOS target (the default) to obtain the headers; you do not need an iOS build.

Create New Project

Start a new iOS project (any name) and add the private headers you copied from the built JSC project (

PrivateHeaders

and

WTF/include/wtf

) to your project’s include path.

Other C++ flags:

-std=c++14

Enable C++ runtime types:

No

System header search path:

$(PRODUCT_NAME)/

Set the required preprocessor macros (e.g.,

ENABLE_3D_TRANSFORMS

,

ENABLE_ACCELERATED_OVERFLOW_SCROLLING

, …) to match the framework build.

Write the Debugger Subclass

<code>#import &lt;JavaScriptCore/JavaScriptCore.h&gt;
#import "JavaScriptCore/HeapInlines.h"
#import "JavaScriptCore/HeapCellInlines.h"
#import "JavaScriptCore/APICast.h"
#import "JavaScriptCore/Debugger.h"
#import "JavaScriptCore/SourceProvider.h"
#import "JavaScriptCore/JSRunLoopTimer.h"
#import "JavaScriptCore/JSVirtualMachineInternal.h"

class MyDebugger : public JSC::Debugger {
public:
    MyDebugger(JSC::VM& vm) : JSC::Debugger(vm) {}
    virtual ~MyDebugger() { JSC::Debugger::~Debugger(); }
    virtual void sourceParsed(JSC::ExecState* state, JSC::SourceProvider* sourceProvider, int errorLineNumber, const WTF::String& errorMessage) {
        // Custom handling of parsed source
        NSLog(@"sourceParsed");
    }
    virtual void handleBreakpointHit(JSC::JSGlobalObject*, const JSC::Breakpoint&) {
        NSLog(@"handleBreakpointHit");
    }
    virtual void handleExceptionInBreakpointCondition(JSC::ExecState*, JSC::Exception*) const {
        NSLog(@"handleExceptionInBreakpointCondition");
    }
    virtual void handlePause(JSC::JSGlobalObject* globalObject, ReasonForPause reason) {
        NSLog(@"handlePause");
    }
    virtual void notifyDoneProcessingDebuggerEvents() {
        NSLog(@"notifyDoneProcessingDebuggerEvents");
    }
};
</code>

Use the debugger in your code:

<code>JSContext* jsContext = [[JSContext alloc] init];
JSGlobalContextRef globalContext = [jsContext JSGlobalContextRef];
JSC::ExecState* es = toJS(globalContext);
JSC::JSGlobalObject* globalObject = es->vmEntryGlobalObject();
MyDebugger* debugger = new MyDebugger(globalObject->vm());
globalObject->setDebugger(static_cast<JSC::Debugger*>(debugger));
debugger->setPauseOnNextStatement(true);
globalObject->vm().heap().acquireAccess();
debugger->activateBreakpoints();
globalObject->vm().heap().releaseAccess();
[jsContext evaluateScript:@"debugger;"];
</code>

This sequence creates a JavaScript context, retrieves the underlying JSC objects, installs the custom debugger, enables breakpoints, and finally runs a script that triggers the debugger.

iOSC++XcodeDebuggerObjective-CJavaScriptCore
Taobao Frontend Technology
Written by

Taobao Frontend Technology

The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.