[Lldb-commits] [lldb] [lldb] add javascript scripting support (PR #165805)
Chad Smith via lldb-commits
lldb-commits at lists.llvm.org
Thu Oct 30 16:49:14 PDT 2025
https://github.com/cs01 created https://github.com/llvm/llvm-project/pull/165805
Adds optional JavaScript scripting support to LLDB via V8. Users must install V8 separately and enable with `-DLLDB_ENABLE_JAVASCRIPT=ON`. Scripts access the LLDB API through a global `lldb` object and can run interactively or from files. Includes SWIG bindings, CMake detection for V8, and documentation. Some things are still missing things like breakpoint callback support.
To try this out save the following file
`/tmp/test.js`:
```
let dbg = lldb.SBDebugger.Create();
dbg.SetAsync(false);
let target = dbg.CreateTarget("/bin/ls");
let breakpoint = target.BreakpointCreateByName("main");
console.log("Breakpoint ID:", breakpoint.GetID());
let error = new lldb.SBError();
let proc = target.Launch(target.GetLaunchInfo(), error);
if (error.Fail()) {
console.error("Error:", error.GetCString());
} else {
console.log("PID:", proc.GetProcessID());
let thread = proc.GetSelectedThread();
console.log("Thread:", thread.GetThreadID());
console.log("Stop reason:", thread.GetStopReason());
console.log("\nStack trace:");
for (let i = 0; i < Math.min(thread.GetNumFrames(), 4); i++) {
let frame = thread.GetFrameAtIndex(i);
let pc = frame.GetPCAddress().GetLoadAddress(target);
let pcHex = "0x" + (pc >>> 0).toString(16).padStart(8, '0');
console.log("Frame #" + i + ": " + pcHex);
}
console.log("Registers:");
let frame0 = thread.GetFrameAtIndex(0);
let gprs = frame0.GetRegisters().GetValueAtIndex(0);
for (let i = 0; i < Math.min(gprs.GetNumChildren(), 10); i++) {
let reg = gprs.GetChildAtIndex(i);
console.log(reg.GetName() + " = " + reg.GetValue());
}
proc.Kill();
}
undefined;
```
then run
```
./build/bin/lldb -b -o 'settings set script-lang javascript' -o 'command script import /tmp/test.js'
```
which prints
```
bin/lldb -b -o 'settings set script-lang javascript' -o 'command script import /tmp/test_process_launch.js'
(lldb) settings set script-lang javascript
(lldb) command script import /tmp/test_process_launch.js
Breakpoint ID: 1
PID: 2351277
Thread: 2351277
Stop reason: 3
Stack trace:
Frame #0: 0x55558d80
Frame #1: 0xf7c2a610
Frame #2: 0xf7c2a6c0
Frame #3: 0x5555ab35
Registers:
rax = 0x0000555555558d80
rbx = 0x0000000000000000
rcx = 0x0000555555574f78
rdx = 0x00007fffffffcbf8
rdi = 0x0000000000000001
rsi = 0x00007fffffffcbe8
rbp = 0x0000000000000001
rsp = 0x00007fffffffcad8
r8 = 0x00007ffff7dfc330
r9 = 0x00007ffff7fcb060
>
```
>From abd0d948d8900e826c17175571f8ccc6476ffe3e Mon Sep 17 00:00:00 2001
From: Chad Smith <cssmith at fb.com>
Date: Tue, 28 Oct 2025 15:33:33 -0700
Subject: [PATCH] add javascript support
---
lldb/CMakeLists.txt | 12 +-
lldb/bindings/CMakeLists.txt | 4 +
lldb/bindings/javascript/CMakeLists.txt | 69 ++++
.../javascript/javascript-swigsafecast.swig | 8 +
.../javascript/javascript-typemaps.swig | 13 +
.../javascript/javascript-wrapper.swig | 12 +
lldb/bindings/javascript/javascript.swig | 30 ++
lldb/cmake/modules/FindV8.cmake | 71 ++++
lldb/cmake/modules/LLDBConfig.cmake | 1 +
lldb/docs/index.rst | 5 +-
lldb/docs/resources/build.rst | 2 +
lldb/docs/use/javascript-reference.md | 263 ++++++++++++
.../Interpreter/CommandOptionArgumentTable.h | 5 +
lldb/include/lldb/lldb-enumerations.h | 13 +-
lldb/source/Core/Debugger.cpp | 5 +
lldb/source/Interpreter/ScriptInterpreter.cpp | 4 +
.../Plugins/ScriptInterpreter/CMakeLists.txt | 4 +
.../JavaScript/CMakeLists.txt | 40 ++
.../JavaScript/JavaScript.cpp | 373 ++++++++++++++++++
.../ScriptInterpreter/JavaScript/JavaScript.h | 106 +++++
.../JavaScript/SWIGJavaScriptBridge.h | 53 +++
.../ScriptInterpreterJavaScript.cpp | 229 +++++++++++
.../JavaScript/ScriptInterpreterJavaScript.h | 108 +++++
23 files changed, 1426 insertions(+), 4 deletions(-)
create mode 100644 lldb/bindings/javascript/CMakeLists.txt
create mode 100644 lldb/bindings/javascript/javascript-swigsafecast.swig
create mode 100644 lldb/bindings/javascript/javascript-typemaps.swig
create mode 100644 lldb/bindings/javascript/javascript-wrapper.swig
create mode 100644 lldb/bindings/javascript/javascript.swig
create mode 100644 lldb/cmake/modules/FindV8.cmake
create mode 100644 lldb/docs/use/javascript-reference.md
create mode 100644 lldb/source/Plugins/ScriptInterpreter/JavaScript/CMakeLists.txt
create mode 100644 lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.cpp
create mode 100644 lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.h
create mode 100644 lldb/source/Plugins/ScriptInterpreter/JavaScript/SWIGJavaScriptBridge.h
create mode 100644 lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.cpp
create mode 100644 lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.h
diff --git a/lldb/CMakeLists.txt b/lldb/CMakeLists.txt
index e3b72e94d4beb..a852e581160b5 100644
--- a/lldb/CMakeLists.txt
+++ b/lldb/CMakeLists.txt
@@ -95,7 +95,7 @@ if (LLDB_ENABLE_LUA)
CACHE STRING "Path where Lua modules are installed, relative to install prefix")
endif ()
-if (LLDB_ENABLE_PYTHON OR LLDB_ENABLE_LUA)
+if (LLDB_ENABLE_PYTHON OR LLDB_ENABLE_LUA OR LLDB_ENABLE_JAVASCRIPT)
add_subdirectory(bindings)
endif ()
@@ -150,6 +150,16 @@ if (LLDB_ENABLE_LUA)
finish_swig_lua("lldb-lua" "${lldb_lua_bindings_dir}" "${LLDB_LUA_CPATH}")
endif()
+if (LLDB_ENABLE_JAVASCRIPT)
+ if(LLDB_BUILD_FRAMEWORK)
+ set(lldb_javascript_target_dir "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}/LLDB.framework/Resources/JavaScript")
+ else()
+ set(lldb_javascript_target_dir "${CMAKE_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib/javascript")
+ endif()
+ get_target_property(lldb_javascript_bindings_dir swig_wrapper_javascript BINARY_DIR)
+ finish_swig_javascript("lldb-javascript" "${lldb_javascript_bindings_dir}" "${lldb_javascript_target_dir}")
+endif()
+
set(LLDB_INCLUDE_UNITTESTS ON)
if (NOT TARGET llvm_gtest)
set(LLDB_INCLUDE_UNITTESTS OFF)
diff --git a/lldb/bindings/CMakeLists.txt b/lldb/bindings/CMakeLists.txt
index bec694e43bd7b..984614a1238aa 100644
--- a/lldb/bindings/CMakeLists.txt
+++ b/lldb/bindings/CMakeLists.txt
@@ -57,3 +57,7 @@ endif()
if (LLDB_ENABLE_LUA)
add_subdirectory(lua)
endif()
+
+if (LLDB_ENABLE_JAVASCRIPT)
+ add_subdirectory(javascript)
+endif()
diff --git a/lldb/bindings/javascript/CMakeLists.txt b/lldb/bindings/javascript/CMakeLists.txt
new file mode 100644
index 0000000000000..7356625f71a90
--- /dev/null
+++ b/lldb/bindings/javascript/CMakeLists.txt
@@ -0,0 +1,69 @@
+add_custom_command(
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/LLDBWrapJavaScript.cpp
+ DEPENDS ${SWIG_SOURCES}
+ DEPENDS ${SWIG_INTERFACES}
+ DEPENDS ${SWIG_HEADERS}
+ DEPENDS lldb-sbapi-dwarf-enums
+ COMMAND ${SWIG_EXECUTABLE}
+ ${SWIG_COMMON_FLAGS}
+ -I${CMAKE_CURRENT_SOURCE_DIR}
+ -javascript
+ -v8
+ -w503
+ -outdir ${CMAKE_CURRENT_BINARY_DIR}
+ -o ${CMAKE_CURRENT_BINARY_DIR}/LLDBWrapJavaScript.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/javascript.swig
+ VERBATIM
+ COMMENT "Building LLDB JavaScript wrapper")
+
+add_custom_target(swig_wrapper_javascript ALL DEPENDS
+ ${CMAKE_CURRENT_BINARY_DIR}/LLDBWrapJavaScript.cpp
+)
+
+function(create_javascript_package swig_target working_dir pkg_dir)
+ cmake_parse_arguments(ARG "NOINIT" "" "FILES" ${ARGN})
+ add_custom_command(TARGET ${swig_target} POST_BUILD VERBATIM
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${pkg_dir}
+ WORKING_DIRECTORY ${working_dir})
+endfunction()
+
+function(finish_swig_javascript swig_target lldb_javascript_bindings_dir lldb_javascript_target_dir)
+ add_custom_target(${swig_target} ALL VERBATIM
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${lldb_javascript_target_dir}
+ DEPENDS swig_wrapper_javascript liblldb
+ COMMENT "LLDB JavaScript API")
+ if(LLDB_BUILD_FRAMEWORK)
+ set(LIBLLDB_SYMLINK_DEST "${LLDB_FRAMEWORK_ABSOLUTE_BUILD_DIR}/LLDB.framework/LLDB")
+ else()
+ set(LIBLLDB_SYMLINK_DEST "${LLVM_SHLIB_OUTPUT_INTDIR}/liblldb${CMAKE_SHARED_LIBRARY_SUFFIX}")
+ endif()
+ if(WIN32)
+ set(LIBLLDB_SYMLINK_OUTPUT_FILE "lldb.dll")
+ else()
+ set(LIBLLDB_SYMLINK_OUTPUT_FILE "lldb.so")
+ endif()
+ create_relative_symlink(${swig_target} ${LIBLLDB_SYMLINK_DEST}
+ ${lldb_javascript_target_dir} ${LIBLLDB_SYMLINK_OUTPUT_FILE})
+ set(lldb_javascript_library_target "${swig_target}-library")
+ add_custom_target(${lldb_javascript_library_target})
+ add_dependencies(${lldb_javascript_library_target} ${swig_target})
+
+ # Ensure we do the JavaScript post-build step when building lldb.
+ add_dependencies(lldb ${swig_target})
+
+ if(LLDB_BUILD_FRAMEWORK)
+ set(LLDB_JAVASCRIPT_INSTALL_PATH ${LLDB_FRAMEWORK_INSTALL_DIR}/LLDB.framework/Resources/JavaScript)
+ else()
+ set(LLDB_JAVASCRIPT_INSTALL_PATH lib/javascript)
+ endif()
+ install(DIRECTORY ${lldb_javascript_target_dir}/
+ DESTINATION ${LLDB_JAVASCRIPT_INSTALL_PATH}
+ COMPONENT ${lldb_javascript_library_target})
+
+ set(lldb_javascript_library_install_target "install-${lldb_javascript_library_target}")
+ if (NOT LLVM_ENABLE_IDE)
+ add_llvm_install_targets(${lldb_javascript_library_install_target}
+ COMPONENT ${lldb_javascript_library_target}
+ DEPENDS ${lldb_javascript_library_target})
+ endif()
+endfunction()
diff --git a/lldb/bindings/javascript/javascript-swigsafecast.swig b/lldb/bindings/javascript/javascript-swigsafecast.swig
new file mode 100644
index 0000000000000..5d2b50f1fd249
--- /dev/null
+++ b/lldb/bindings/javascript/javascript-swigsafecast.swig
@@ -0,0 +1,8 @@
+/*
+ Safe casting for JavaScript SWIG bindings
+*/
+
+// This file provides safe type casting between LLDB types
+// Similar to lua-swigsafecast.swig and python-swigsafecast.swig
+
+// TODO: Implement safe casting functions as needed
diff --git a/lldb/bindings/javascript/javascript-typemaps.swig b/lldb/bindings/javascript/javascript-typemaps.swig
new file mode 100644
index 0000000000000..209089b4d3fcb
--- /dev/null
+++ b/lldb/bindings/javascript/javascript-typemaps.swig
@@ -0,0 +1,13 @@
+/*
+ JavaScript-specific typemaps for LLDB
+*/
+
+// Basic type mappings for JavaScript/V8
+// SWIG's JavaScript module handles most standard types automatically,
+// but we may need custom mappings for LLDB-specific types.
+
+// TODO: Add custom typemaps as needed for:
+// - File handles
+// - Callbacks
+// - Error handling
+// - Memory buffers
diff --git a/lldb/bindings/javascript/javascript-wrapper.swig b/lldb/bindings/javascript/javascript-wrapper.swig
new file mode 100644
index 0000000000000..af952201ddd05
--- /dev/null
+++ b/lldb/bindings/javascript/javascript-wrapper.swig
@@ -0,0 +1,12 @@
+/*
+ JavaScript-specific wrapper functions for LLDB
+*/
+
+// This file will contain JavaScript-specific wrapper code
+// to bridge between LLDB's C++ API and JavaScript/V8
+
+// TODO: Add wrapper functions for:
+// - Breakpoint callbacks
+// - Watchpoint callbacks
+// - Custom commands
+// - Data formatters
diff --git a/lldb/bindings/javascript/javascript.swig b/lldb/bindings/javascript/javascript.swig
new file mode 100644
index 0000000000000..098c04ad8d365
--- /dev/null
+++ b/lldb/bindings/javascript/javascript.swig
@@ -0,0 +1,30 @@
+/*
+ lldb.swig
+
+ This is the input file for SWIG, to create the appropriate C++ wrappers and
+ functions for JavaScript (V8/Node.js), to enable them to call the
+ liblldb Script Bridge functions.
+*/
+
+%module lldb
+
+%include <std_string.i>
+%include "javascript-typemaps.swig"
+%include "macros.swig"
+%include "headers.swig"
+
+%{
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "../bindings/javascript/javascript-swigsafecast.swig"
+#include "../source/Plugins/ScriptInterpreter/JavaScript/SWIGJavaScriptBridge.h"
+
+// required headers for typemaps
+#include "lldb/Host/File.h"
+
+using namespace lldb_private;
+using namespace lldb;
+%}
+
+%include "interfaces.swig"
+%include "javascript-wrapper.swig"
diff --git a/lldb/cmake/modules/FindV8.cmake b/lldb/cmake/modules/FindV8.cmake
new file mode 100644
index 0000000000000..d6ce23feddef1
--- /dev/null
+++ b/lldb/cmake/modules/FindV8.cmake
@@ -0,0 +1,71 @@
+#.rst:
+# FindV8
+# ------
+#
+# Find V8 JavaScript engine
+#
+# This module will search for V8 in standard system locations, or use
+# user-specified paths. Users can override the search by setting:
+# -DV8_INCLUDE_DIR=/path/to/v8/include
+# -DV8_LIBRARIES=/path/to/libv8.so (or libv8_monolith.a)
+#
+# The module defines:
+# V8_FOUND - System has V8
+# V8_INCLUDE_DIR - V8 include directory
+# V8_LIBRARIES - V8 libraries to link against
+
+if(V8_LIBRARIES AND V8_INCLUDE_DIR)
+ set(V8_FOUND TRUE)
+ if(NOT V8_FIND_QUIETLY)
+ message(STATUS "Found V8: ${V8_INCLUDE_DIR}")
+ message(STATUS "Found V8 library: ${V8_LIBRARIES}")
+ set(V8_FIND_QUIETLY TRUE CACHE BOOL "Suppress repeated V8 find messages" FORCE)
+ endif()
+else()
+ # Try to find system V8
+ find_path(V8_INCLUDE_DIR
+ NAMES v8.h
+ PATHS
+ # Standard system locations
+ /usr/include
+ /usr/local/include
+ /opt/v8/include
+ # Homebrew on macOS
+ /opt/homebrew/include
+ /usr/local/opt/v8/include
+ PATH_SUFFIXES
+ v8
+ DOC "V8 include directory"
+ )
+
+ find_library(V8_LIBRARIES
+ NAMES v8_monolith v8 v8_libbase v8_libplatform
+ PATHS
+ # Standard system locations
+ /usr/lib
+ /usr/local/lib
+ /opt/v8/lib
+ # Homebrew on macOS
+ /opt/homebrew/lib
+ /usr/local/opt/v8/lib
+ DOC "V8 library"
+ )
+
+ include(FindPackageHandleStandardArgs)
+ find_package_handle_standard_args(V8
+ FOUND_VAR
+ V8_FOUND
+ REQUIRED_VARS
+ V8_INCLUDE_DIR
+ V8_LIBRARIES)
+
+ if(V8_FOUND)
+ mark_as_advanced(V8_LIBRARIES V8_INCLUDE_DIR)
+ message(STATUS "Found V8: ${V8_INCLUDE_DIR}")
+ if(V8_LIBRARIES)
+ message(STATUS "Found V8 library: ${V8_LIBRARIES}")
+ else()
+ message(STATUS "V8 headers found (library may need to be built or specified manually)")
+ endif()
+ endif()
+endif()
diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake
index 4b568d27c4709..e42522e8b8765 100644
--- a/lldb/cmake/modules/LLDBConfig.cmake
+++ b/lldb/cmake/modules/LLDBConfig.cmake
@@ -62,6 +62,7 @@ add_optional_dependency(LLDB_ENABLE_CURSES "Enable curses support in LLDB" Curse
add_optional_dependency(LLDB_ENABLE_LZMA "Enable LZMA compression support in LLDB" LibLZMA LIBLZMA_FOUND)
add_optional_dependency(LLDB_ENABLE_LUA "Enable Lua scripting support in LLDB" LuaAndSwig LUAANDSWIG_FOUND)
add_optional_dependency(LLDB_ENABLE_PYTHON "Enable Python scripting support in LLDB" PythonAndSwig PYTHONANDSWIG_FOUND)
+add_optional_dependency(LLDB_ENABLE_JAVASCRIPT "Enable JavaScript scripting support in LLDB" V8 V8_FOUND)
add_optional_dependency(LLDB_ENABLE_LIBXML2 "Enable Libxml 2 support in LLDB" LibXml2 LIBXML2_FOUND VERSION ${LLDB_LIBXML2_VERSION})
add_optional_dependency(LLDB_ENABLE_FBSDVMCORE "Enable libfbsdvmcore support in LLDB" FBSDVMCore FBSDVMCore_FOUND QUIET)
diff --git a/lldb/docs/index.rst b/lldb/docs/index.rst
index a981c0ab8d6e9..bfe39dc9d5297 100644
--- a/lldb/docs/index.rst
+++ b/lldb/docs/index.rst
@@ -27,7 +27,9 @@ with GDB there is a cheat sheet listing common tasks and their LLDB equivalent
in the `GDB to LLDB command map <https://lldb.llvm.org/use/map.html>`_.
There are also multiple resources on how to script LLDB using Python: the
-:doc:`use/python-reference` is a great starting point for that.
+:doc:`use/python-reference` is a great starting point for that. LLDB also
+supports scripting with JavaScript through the V8 engine (see
+`JavaScript Reference <use/javascript-reference.html>`_).
Compiler Integration Benefits
-----------------------------
@@ -148,6 +150,7 @@ interesting areas to contribute to lldb.
use/python
use/python-reference
+ use/javascript-reference
Python API <python_api>
Python Extensions <python_extensions>
diff --git a/lldb/docs/resources/build.rst b/lldb/docs/resources/build.rst
index 0db8c92ad49d6..5d6ee39ea6164 100644
--- a/lldb/docs/resources/build.rst
+++ b/lldb/docs/resources/build.rst
@@ -66,6 +66,8 @@ CMake configuration error.
+-------------------+--------------------------------------------------------------+--------------------------+
| Lua | Lua scripting. Lua 5.3 and 5.4 are supported. | ``LLDB_ENABLE_LUA`` |
+-------------------+--------------------------------------------------------------+--------------------------+
+| JavaScript | JavaScript scripting via V8 engine. Experimental. | ``LLDB_ENABLE_JAVASCRIPT``|
++-------------------+--------------------------------------------------------------+--------------------------+
Depending on your platform and package manager, one might run any of the
commands below.
diff --git a/lldb/docs/use/javascript-reference.md b/lldb/docs/use/javascript-reference.md
new file mode 100644
index 0000000000000..a73befa02f224
--- /dev/null
+++ b/lldb/docs/use/javascript-reference.md
@@ -0,0 +1,263 @@
+# JavaScript Reference
+
+LLDB has extensive support for interacting with JavaScript through the V8
+JavaScript engine. This document describes how to use JavaScript scripting
+within LLDB and provides reference documentation for the JavaScript API.
+
+## Using JavaScript in LLDB
+
+LLDB's JavaScript support is built on top of the V8 JavaScript engine, the same
+engine that powers Node.js and Chrome. This provides full ES2020+ language
+support with modern JavaScript features.
+
+### Interactive JavaScript
+
+JavaScript can be run interactively in LLDB. First, set JavaScript as the script language, then use the `script` command:
+
+```
+(lldb) settings set script-lang javascript
+(lldb) script
+>>> let message = "Hello from JavaScript!";
+>>> console.log(message);
+Hello from JavaScript!
+>>> lldb.debugger.GetVersionString()
+lldb version 18.0.0
+```
+
+### Running JavaScript from Files
+
+You can execute JavaScript files using the `command script import` command:
+
+```
+(lldb) command script import /path/to/myscript.js
+```
+
+The JavaScript file will be executed in the current LLDB context with access
+to all LLDB APIs.
+
+### Example JavaScript Script
+
+Here's a simple example that demonstrates using the LLDB JavaScript API:
+
+```javascript
+// Get the current debugger instance
+let debugger = lldb.debugger;
+
+// Get the current target
+let target = debugger.GetSelectedTarget();
+
+// Get the current process
+let process = target.GetProcess();
+
+// Get the selected thread
+let thread = process.GetSelectedThread();
+
+// Get the selected frame
+let frame = thread.GetSelectedFrame();
+
+// Evaluate an expression
+let result = frame.EvaluateExpression("myVariable");
+console.log("Value:", result.GetValue());
+
+// Print all local variables
+let variables = frame.GetVariables(true, true, false, false);
+for (let i = 0; i < variables.GetSize(); i++) {
+ let variable = variables.GetValueAtIndex(i);
+ console.log(variable.GetName() + " = " + variable.GetValue());
+}
+```
+
+## The JavaScript API
+
+The JavaScript API provides access to all of LLDB's Script Bridge (SB) API
+classes. These classes are automatically available in the `lldb` module when
+running JavaScript within LLDB.
+
+### Global Objects
+
+* `lldb`: The main LLDB module containing all SB API classes
+* `lldb.debugger`: The current debugger instance (shortcut to avoid passing
+ debugger around)
+* `console`: Standard JavaScript console object for logging
+
+### Available Classes
+
+The JavaScript API includes all of LLDB's SB API classes, like `SBDebugger`,
+`SBTarget`, etc.
+
+For complete documentation of all classes and their methods, refer to the
+[C++ API documentation](https://lldb.llvm.org/cpp_reference/namespacelldb.html),
+as the JavaScript API mirrors the C++ API closely.
+
+### Console Output
+
+JavaScript scripts can use the standard `console` object for output:
+
+```javascript
+console.log("Informational message");
+console.error("Error message");
+console.warn("Warning message");
+```
+
+Output from `console.log()` and other console methods will be displayed in
+the LLDB command output.
+
+## Building LLDB with JavaScript Support
+
+### Prerequisites
+
+To build LLDB with JavaScript support, you need:
+
+* [V8 JavaScript Engine](https://v8.dev) (version 8.0 or later recommended)
+* [SWIG](http://swig.org/) 4 or later (for generating language bindings)
+* All standard LLDB build dependencies (see [build documentation](../resources/build.rst))
+
+### Installing V8
+
+The V8 JavaScript engine must be installed on your system. Installation methods
+vary by platform:
+
+**Ubuntu/Debian:**
+
+```bash
+$ sudo apt-get install libv8-dev
+```
+
+After installation, V8 will typically be installed in:
+- Headers: `/usr/include/v8/` or `/usr/include/`
+- Libraries: `/usr/lib/x86_64-linux-gnu/libv8.so` (or similar for your architecture)
+
+You can verify the installation with:
+```bash
+$ dpkg -L libv8-dev | grep -E '(include|lib)'
+```
+
+**macOS (using Homebrew):**
+
+```bash
+$ brew install v8
+```
+
+After installation, you can find the paths with:
+```bash
+$ brew info v8
+```
+
+Homebrew typically installs to `/opt/homebrew/` (Apple Silicon) or `/usr/local/` (Intel).
+
+**Building V8 from source:**
+
+If V8 is not available as a package for your platform, you can build it from
+source. Follow the instructions at https://v8.dev/docs/build
+
+### CMake Configuration
+
+To enable JavaScript support when building LLDB, add the following CMake
+options:
+
+```bash
+$ cmake -G Ninja \
+ -DLLDB_ENABLE_JAVASCRIPT=ON \
+ [other cmake options] \
+ /path/to/llvm-project/llvm
+```
+
+The `LLDB_ENABLE_JAVASCRIPT` flag enables JavaScript scripting support. If
+V8 is installed via a package manager in standard system locations, CMake
+should auto-detect it. If CMake cannot find V8, you can specify the paths
+manually:
+
+```bash
+$ cmake -G Ninja \
+ -DLLDB_ENABLE_JAVASCRIPT=ON \
+ -DV8_INCLUDE_DIR=/path/to/v8/include \
+ -DV8_LIBRARIES=/path/to/v8/lib/libv8.so \
+ [other cmake options] \
+ /path/to/llvm-project/llvm
+```
+
+where:
+* `V8_INCLUDE_DIR`: Path to V8 header files
+* `V8_LIBRARIES`: Path to V8 library files
+
+### Verifying JavaScript Support
+
+After building LLDB with JavaScript support, you can verify it's working:
+
+```
+$ lldb
+(lldb) settings set script-lang javascript
+(lldb) script
+>>> console.log("JavaScript is working!")
+JavaScript is working!
+>>> lldb.debugger.GetVersionString()
+lldb version 18.0.0
+```
+
+If JavaScript support is not enabled, you'll see an error message when trying
+to set the script language to JavaScript.
+
+### Build Example
+
+Here's a complete example of building LLDB with JavaScript support from scratch:
+
+```bash
+# Clone the LLVM project
+$ git clone https://github.com/llvm/llvm-project.git
+
+# Create build directory
+$ mkdir llvm-build && cd llvm-build
+
+# Configure with JavaScript support
+$ cmake -G Ninja \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DLLVM_ENABLE_PROJECTS="clang;lldb" \
+ -DLLDB_ENABLE_JAVASCRIPT=ON \
+ -DV8_INCLUDE_DIR=/usr/include/v8 \
+ -DV8_LIBRARIES=/usr/lib/x86_64-linux-gnu/libv8.so \
+ ../llvm-project/llvm
+
+# Build LLDB
+$ ninja lldb
+
+# Test JavaScript support
+$ ./bin/lldb -o "settings set script-lang javascript" -o "script -e \"console.log('Hello!')\"" -o "quit"
+```
+
+## Differences from Python API and JavaScript Environment
+
+Important differences to understand:
+
+**Not a Node.js Environment:**
+
+LLDB's JavaScript environment uses the V8 engine but is **not** Node.js. This means:
+
+* **No module system**: `import`, `require()`, and `module.exports` are not available
+* **No event loop**: Asynchronous operations like `setTimeout`, `setInterval`, `Promise.then()` callbacks are not supported
+* **Limited global APIs**: Only specific functions are implemented:
+ * `console.log()`, `console.error()`, `console.warn()` for output
+ * `lldb` global object for LLDB API access
+ * Standard JavaScript language features (ES2020+)
+
+**Module Access:**
+
+In Python, you typically import with `import lldb`. In JavaScript, `lldb`
+is automatically available as a global object without any import statement.
+
+Scripts should be written as self-contained synchronous code that directly uses the
+`lldb` global object.
+
+## Known Limitations
+
+The JavaScript support in LLDB is not as extensive as Python. The
+following features are not yet implemented:
+
+* Custom breakpoint callbacks in JavaScript
+* Custom watchpoint callbacks in JavaScript
+* Some advanced type mapping and conversions
+
+## Additional Resources
+
+* [LLDB C++ API Reference](https://lldb.llvm.org/cpp_reference/namespacelldb.html)
+* [V8 JavaScript Engine Documentation](https://v8.dev/docs)
+* [LLDB Python Reference](python-reference.html) (similar concepts apply to JavaScript)
diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
index 4face717531b1..9e4ab5d1425e6 100644
--- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
+++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h
@@ -96,6 +96,11 @@ static constexpr OptionEnumValueElement g_script_option_enumeration[] = {
"lua",
"Commands are in the Lua language.",
},
+ {
+ lldb::eScriptLanguageJavaScript,
+ "javascript",
+ "Commands are in the JavaScript language.",
+ },
{
lldb::eScriptLanguageNone,
"default",
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 1a7db8faecd94..423219063b1f5 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -227,8 +227,17 @@ enum ScriptLanguage {
eScriptLanguageNone = 0,
eScriptLanguagePython,
eScriptLanguageLua,
+ eScriptLanguageJavaScript,
eScriptLanguageUnknown,
+#if LLDB_ENABLE_PYTHON
eScriptLanguageDefault = eScriptLanguagePython
+#elif LLDB_ENABLE_JAVASCRIPT
+ eScriptLanguageDefault = eScriptLanguageJavaScript
+#elif LLDB_ENABLE_LUA
+ eScriptLanguageDefault = eScriptLanguageLua
+#else
+ eScriptLanguageDefault = eScriptLanguageNone
+#endif
};
/// Register numbering types.
@@ -314,7 +323,7 @@ enum ConnectionStatus {
eConnectionStatusNoConnection, ///< No connection
eConnectionStatusLostConnection, ///< Lost connection while connected to a
///< valid connection
- eConnectionStatusInterrupted ///< Interrupted read
+ eConnectionStatusInterrupted ///< Interrupted read
};
enum ErrorType {
@@ -1109,7 +1118,7 @@ enum PathType {
ePathTypeGlobalLLDBTempSystemDir, ///< The LLDB temp directory for this
///< system, NOT cleaned up on a process
///< exit.
- ePathTypeClangDir ///< Find path to Clang builtin headers
+ ePathTypeClangDir ///< Find path to Clang builtin headers
};
/// Kind of member function.
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index b37d9d3ed85e3..d8a6fd44c7f3f 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -147,6 +147,11 @@ static constexpr OptionEnumValueElement g_language_enumerators[] = {
"python",
"Select python as the default scripting language.",
},
+ {
+ eScriptLanguageJavaScript,
+ "javascript",
+ "Select javascript as the default scripting language.",
+ },
{
eScriptLanguageDefault,
"default",
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index ca768db1199c1..b215710a545f0 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -65,6 +65,8 @@ std::string ScriptInterpreter::LanguageToString(lldb::ScriptLanguage language) {
return "Python";
case eScriptLanguageLua:
return "Lua";
+ case eScriptLanguageJavaScript:
+ return "JavaScript";
case eScriptLanguageUnknown:
return "Unknown";
}
@@ -158,6 +160,8 @@ ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) {
return eScriptLanguagePython;
if (language.equals_insensitive(LanguageToString(eScriptLanguageLua)))
return eScriptLanguageLua;
+ if (language.equals_insensitive(LanguageToString(eScriptLanguageJavaScript)))
+ return eScriptLanguageJavaScript;
return eScriptLanguageUnknown;
}
diff --git a/lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt
index 4429b006173a7..ae74db4db31c0 100644
--- a/lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt
+++ b/lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt
@@ -8,3 +8,7 @@ endif()
if (LLDB_ENABLE_LUA)
add_subdirectory(Lua)
endif()
+
+if (LLDB_ENABLE_JAVASCRIPT)
+ add_subdirectory(JavaScript)
+endif()
diff --git a/lldb/source/Plugins/ScriptInterpreter/JavaScript/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/JavaScript/CMakeLists.txt
new file mode 100644
index 0000000000000..e7c5baffcaed5
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/JavaScript/CMakeLists.txt
@@ -0,0 +1,40 @@
+if(NOT LLDB_ENABLE_JAVASCRIPT)
+ return()
+endif()
+
+find_package(V8)
+
+if(NOT V8_FOUND)
+ message(FATAL_ERROR "V8 JavaScript engine not found. JavaScript scripting will not be available.")
+ return()
+endif()
+
+add_lldb_library(lldbPluginScriptInterpreterJavaScript PLUGIN
+ JavaScript.cpp
+ ScriptInterpreterJavaScript.cpp
+ ${CMAKE_BINARY_DIR}/tools/lldb/bindings/javascript/LLDBWrapJavaScript.cpp
+
+ LINK_LIBS
+ lldbBreakpoint
+ lldbCore
+ lldbDataFormatters
+ lldbHost
+ lldbInterpreter
+ lldbTarget
+ lldbUtility
+
+ LINK_COMPONENTS
+ Support
+
+ CLANG_LIBS
+ clangBasic
+)
+
+target_include_directories(lldbPluginScriptInterpreterJavaScript PUBLIC
+ ${V8_INCLUDE_DIR}
+)
+
+# Link against V8
+if(V8_LIBRARIES)
+ target_link_libraries(lldbPluginScriptInterpreterJavaScript PRIVATE ${V8_LIBRARIES})
+endif()
diff --git a/lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.cpp b/lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.cpp
new file mode 100644
index 0000000000000..10de1e491344a
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.cpp
@@ -0,0 +1,373 @@
+// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
+
+//===-- JavaScript.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "JavaScript.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Utility/FileSpec.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+
+#include <libplatform/libplatform.h>
+#include <v8.h>
+
+using namespace lldb_private;
+using namespace lldb;
+
+// SWIG-generated init function (SWIGV8_INIT is a macro that expands to
+// lldb_initialize)
+extern "C" void lldb_initialize(v8::Local<v8::Object> exports,
+ v8::Local<v8::Object> module);
+
+// Static V8 platform (initialized once)
+std::unique_ptr<v8::Platform> JavaScript::s_platform;
+bool JavaScript::s_platform_initialized = false;
+
+// Helper to format and write output
+static void
+WriteFormattedOutput(const v8::FunctionCallbackInfo<v8::Value> &args,
+ bool add_newline = true) {
+ v8::Isolate *isolate = args.GetIsolate();
+ v8::HandleScope handle_scope(isolate);
+
+ v8::Local<v8::Context> context = isolate->GetCurrentContext();
+ JavaScript *js_instance =
+ static_cast<JavaScript *>(context->GetAlignedPointerFromEmbedderData(1));
+
+ std::string output;
+ for (int i = 0; i < args.Length(); i++) {
+ if (i > 0)
+ output += " ";
+ v8::String::Utf8Value str(isolate, args[i]);
+ output += *str;
+ }
+ if (add_newline)
+ output += "\n";
+
+ if (js_instance) {
+ js_instance->WriteOutput(output);
+ } else {
+ printf("%s", output.c_str());
+ fflush(stdout);
+ }
+}
+
+// Console.log implementation
+static void ConsoleLog(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ WriteFormattedOutput(args, true);
+}
+
+// Console.warn implementation
+static void ConsoleWarn(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ WriteFormattedOutput(args, true);
+}
+
+// Console.error implementation
+static void ConsoleError(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ WriteFormattedOutput(args, true);
+}
+
+void JavaScript::InitializePlatform() {
+ if (s_platform_initialized)
+ return;
+
+ v8::V8::InitializeICUDefaultLocation("");
+ v8::V8::InitializeExternalStartupData("");
+ s_platform = v8::platform::NewDefaultPlatform();
+ v8::V8::InitializePlatform(s_platform.get());
+ v8::V8::Initialize();
+
+ s_platform_initialized = true;
+}
+
+JavaScript::JavaScript(lldb::FileSP output_file)
+ : m_stdout(stdout), m_stderr(stderr), m_output_file(output_file) {
+ InitializePlatform();
+
+ // Create isolate
+ v8::Isolate::CreateParams create_params;
+ create_params.array_buffer_allocator =
+ v8::ArrayBuffer::Allocator::NewDefaultAllocator();
+ m_isolate = v8::Isolate::New(create_params);
+
+ // Create context
+ v8::Isolate::Scope isolate_scope(m_isolate);
+ v8::HandleScope handle_scope(m_isolate);
+
+ v8::Local<v8::Context> context = v8::Context::New(m_isolate);
+ m_context = new v8::Global<v8::Context>(m_isolate, context);
+
+ // Initialize SWIG bindings
+ v8::Context::Scope context_scope(context);
+ v8::Local<v8::Object> lldb_module = v8::Object::New(m_isolate);
+ v8::Local<v8::Object> empty_module = v8::Object::New(m_isolate);
+
+ lldb_initialize(lldb_module, empty_module);
+
+ context->Global()
+ ->Set(context,
+ v8::String::NewFromUtf8(m_isolate, "lldb").ToLocalChecked(),
+ lldb_module)
+ .Check();
+
+ v8::Local<v8::Object> console_obj = v8::Object::New(m_isolate);
+ console_obj
+ ->Set(context, v8::String::NewFromUtf8(m_isolate, "log").ToLocalChecked(),
+ v8::Function::New(context, ConsoleLog).ToLocalChecked())
+ .Check();
+ console_obj
+ ->Set(context,
+ v8::String::NewFromUtf8(m_isolate, "warn").ToLocalChecked(),
+ v8::Function::New(context, ConsoleWarn).ToLocalChecked())
+ .Check();
+ console_obj
+ ->Set(context,
+ v8::String::NewFromUtf8(m_isolate, "error").ToLocalChecked(),
+ v8::Function::New(context, ConsoleError).ToLocalChecked())
+ .Check();
+ context->Global()
+ ->Set(context,
+ v8::String::NewFromUtf8(m_isolate, "console").ToLocalChecked(),
+ console_obj)
+ .Check();
+
+ context->SetAlignedPointerInEmbedderData(1, this);
+}
+
+JavaScript::~JavaScript() {
+ // Clear all callbacks
+ for (auto &pair : m_breakpoint_callbacks) {
+ pair.second.Reset();
+ }
+ m_breakpoint_callbacks.clear();
+
+ for (auto &pair : m_watchpoint_callbacks) {
+ pair.second.Reset();
+ }
+ m_watchpoint_callbacks.clear();
+
+ if (m_context) {
+ m_context->Reset();
+ delete m_context;
+ }
+ if (m_isolate) {
+ m_isolate->Dispose();
+ }
+}
+
+llvm::Error JavaScript::Run(llvm::StringRef code) {
+ v8::Isolate::Scope isolate_scope(m_isolate);
+ v8::HandleScope handle_scope(m_isolate);
+ v8::Local<v8::Context> context = m_context->Get(m_isolate);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(m_isolate);
+
+ // Compile
+ v8::Local<v8::String> source =
+ v8::String::NewFromUtf8(m_isolate, code.data(),
+ v8::NewStringType::kNormal, code.size())
+ .ToLocalChecked();
+
+ v8::Local<v8::Script> script;
+ if (!v8::Script::Compile(context, source).ToLocal(&script)) {
+ v8::String::Utf8Value error(m_isolate, try_catch.Exception());
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Compilation error: {0}\n", *error),
+ llvm::inconvertibleErrorCode());
+ }
+
+ // Run
+ v8::Local<v8::Value> result;
+ if (!script->Run(context).ToLocal(&result)) {
+ v8::String::Utf8Value error(m_isolate, try_catch.Exception());
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Runtime error: {0}\n", *error),
+ llvm::inconvertibleErrorCode());
+ }
+
+ // Print the result if it's not undefined (REPL behavior)
+ if (!result->IsUndefined()) {
+ v8::String::Utf8Value result_str(m_isolate, result);
+ WriteOutput(std::string(*result_str) + "\n");
+ }
+
+ return llvm::Error::success();
+}
+
+llvm::Error JavaScript::LoadModule(llvm::StringRef filename) {
+ const FileSpec file(filename);
+ if (!FileSystem::Instance().Exists(file)) {
+ return llvm::make_error<llvm::StringError>("File not found",
+ llvm::inconvertibleErrorCode());
+ }
+
+ if (file.GetFileNameExtension() != ".js") {
+ return llvm::make_error<llvm::StringError>(
+ "Invalid extension (expected .js)", llvm::inconvertibleErrorCode());
+ }
+
+ // Read file using llvm MemoryBuffer
+ auto buffer_or_error = llvm::MemoryBuffer::getFile(file.GetPath());
+ if (!buffer_or_error) {
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Failed to read file: {0}",
+ buffer_or_error.getError().message()),
+ llvm::inconvertibleErrorCode());
+ }
+
+ std::unique_ptr<llvm::MemoryBuffer> buffer = std::move(*buffer_or_error);
+ llvm::StringRef contents = buffer->getBuffer();
+
+ if (contents.empty()) {
+ return llvm::make_error<llvm::StringError>("Empty file",
+ llvm::inconvertibleErrorCode());
+ }
+
+ return Run(contents);
+}
+
+llvm::Error JavaScript::CheckSyntax(llvm::StringRef code) {
+ v8::Isolate::Scope isolate_scope(m_isolate);
+ v8::HandleScope handle_scope(m_isolate);
+ v8::Local<v8::Context> context = m_context->Get(m_isolate);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(m_isolate);
+
+ v8::Local<v8::String> source =
+ v8::String::NewFromUtf8(m_isolate, code.data(),
+ v8::NewStringType::kNormal, code.size())
+ .ToLocalChecked();
+
+ v8::Local<v8::Script> script;
+ if (!v8::Script::Compile(context, source).ToLocal(&script)) {
+ v8::String::Utf8Value error(m_isolate, try_catch.Exception());
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Syntax error: {0}\n", *error),
+ llvm::inconvertibleErrorCode());
+ }
+
+ return llvm::Error::success();
+}
+
+llvm::Error JavaScript::ChangeIO(FILE *out, FILE *err) {
+ m_stdout = out;
+ m_stderr = err;
+ return llvm::Error::success();
+}
+
+llvm::Error
+JavaScript::RegisterBreakpointCallback(void *baton,
+ const char *command_body_text) {
+ v8::Isolate::Scope isolate_scope(m_isolate);
+ v8::HandleScope handle_scope(m_isolate);
+ v8::Local<v8::Context> context = m_context->Get(m_isolate);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(m_isolate);
+
+ v8::Local<v8::String> source =
+ v8::String::NewFromUtf8(m_isolate, command_body_text,
+ v8::NewStringType::kNormal,
+ strlen(command_body_text))
+ .ToLocalChecked();
+
+ v8::Local<v8::Script> script;
+ if (!v8::Script::Compile(context, source).ToLocal(&script)) {
+ v8::String::Utf8Value error(m_isolate, try_catch.Exception());
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Failed to compile callback: {0}", *error),
+ llvm::inconvertibleErrorCode());
+ }
+
+ v8::Local<v8::Value> result;
+ if (!script->Run(context).ToLocal(&result)) {
+ v8::String::Utf8Value error(m_isolate, try_catch.Exception());
+ return llvm::make_error<llvm::StringError>(
+ llvm::formatv("Failed to evaluate callback: {0}", *error),
+ llvm::inconvertibleErrorCode());
+ }
+
+ if (!result->IsFunction()) {
+ return llvm::make_error<llvm::StringError>(
+ "Breakpoint callback must be a JavaScript function",
+ llvm::inconvertibleErrorCode());
+ }
+
+ // Store the function in our map
+ v8::Local<v8::Function> callback = result.As<v8::Function>();
+ m_breakpoint_callbacks[baton] = v8::Global<v8::Function>(m_isolate, callback);
+
+ return llvm::Error::success();
+}
+
+llvm::Expected<bool>
+JavaScript::CallBreakpointCallback(void *baton,
+ lldb::StackFrameSP stop_frame_sp,
+ lldb::BreakpointLocationSP bp_loc_sp,
+ StructuredData::ObjectSP extra_args_sp) {
+ auto it = m_breakpoint_callbacks.find(baton);
+ if (it == m_breakpoint_callbacks.end()) {
+ return llvm::make_error<llvm::StringError>(
+ "No callback registered for this baton",
+ llvm::inconvertibleErrorCode());
+ }
+
+ v8::Isolate::Scope isolate_scope(m_isolate);
+ v8::HandleScope handle_scope(m_isolate);
+ v8::Local<v8::Context> context = m_context->Get(m_isolate);
+ v8::Context::Scope context_scope(context);
+
+ v8::TryCatch try_catch(m_isolate);
+
+ v8::Local<v8::Function> callback = it->second.Get(m_isolate);
+
+ v8::Local<v8::Value> args[2] = {v8::Null(m_isolate), v8::Null(m_isolate)};
+
+ v8::Local<v8::Value> result;
+ if (!callback->Call(context, context->Global(), 2, args).ToLocal(&result)) {
+ v8::String::Utf8Value error(m_isolate, try_catch.Exception());
+ WriteOutput(
+ llvm::formatv("Breakpoint callback error: {0}\n", *error).str());
+ return false;
+ }
+
+ bool should_stop = false;
+ if (result->IsBoolean()) {
+ should_stop = result->BooleanValue(m_isolate);
+ }
+
+ return should_stop;
+}
+
+llvm::Error
+JavaScript::RegisterWatchpointCallback(void *baton,
+ const char *command_body_text) {
+ return llvm::make_error<llvm::StringError>(
+ "Watchpoint callbacks not yet implemented",
+ llvm::inconvertibleErrorCode());
+}
+
+llvm::Expected<bool> JavaScript::CallWatchpointCallback(
+ void *baton, lldb::StackFrameSP stop_frame_sp, lldb::WatchpointSP wp_sp) {
+ return false;
+}
+
+void JavaScript::WriteOutput(const std::string &text) {
+ if (m_output_callback) {
+ m_output_callback(text);
+ } else if (m_output_file && m_output_file->IsValid()) {
+ m_output_file->Printf("%s", text.c_str());
+ m_output_file->Flush();
+ } else {
+ printf("%s", text.c_str());
+ fflush(stdout);
+ }
+}
diff --git a/lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.h b/lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.h
new file mode 100644
index 0000000000000..214323300a6bd
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/JavaScript/JavaScript.h
@@ -0,0 +1,106 @@
+// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
+
+//===-- JavaScript.h --------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_JavaScript_h_
+#define liblldb_JavaScript_h_
+
+#include "lldb/Utility/StructuredData.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include <functional>
+#include <memory>
+
+// Forward declare V8 types to avoid including V8 headers here
+namespace v8 {
+class Isolate;
+template <class T> class Global;
+class Context;
+class Platform;
+class Function;
+} // namespace v8
+
+namespace lldb_private {
+
+class JavaScript {
+public:
+ JavaScript(lldb::FileSP output_file = nullptr);
+ ~JavaScript();
+
+ // Execute JavaScript code
+ llvm::Error Run(llvm::StringRef code);
+
+ // Set callback for output (used by console.log)
+ using OutputCallback = std::function<void(const std::string &)>;
+ void SetOutputCallback(OutputCallback callback) {
+ m_output_callback = callback;
+ }
+
+ // Load and execute a JavaScript module
+ llvm::Error LoadModule(llvm::StringRef filename);
+
+ // Check syntax without executing
+ llvm::Error CheckSyntax(llvm::StringRef code);
+
+ // Change IO streams
+ llvm::Error ChangeIO(FILE *out, FILE *err);
+
+ // Breakpoint callback support
+ llvm::Error RegisterBreakpointCallback(void *baton,
+ const char *command_body_text);
+
+ llvm::Expected<bool>
+ CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp,
+ lldb::BreakpointLocationSP bp_loc_sp,
+ StructuredData::ObjectSP extra_args_sp);
+
+ // Watchpoint callback support
+ llvm::Error RegisterWatchpointCallback(void *baton,
+ const char *command_body_text);
+
+ llvm::Expected<bool> CallWatchpointCallback(void *baton,
+ lldb::StackFrameSP stop_frame_sp,
+ lldb::WatchpointSP wp_sp);
+
+ // Get the V8 isolate (for advanced usage)
+ v8::Isolate *GetIsolate() { return m_isolate; }
+
+ // Get the output file (for console.log implementation)
+ lldb::FileSP GetOutputFile() { return m_output_file; }
+
+ // Set the output file (for routing console.log to the correct stream)
+ void SetOutputFile(lldb::FileSP output_file) { m_output_file = output_file; }
+
+ // Write output (used by console.log)
+ void WriteOutput(const std::string &text);
+
+private:
+ static std::unique_ptr<v8::Platform> s_platform;
+ static bool s_platform_initialized;
+
+ v8::Isolate *m_isolate;
+ v8::Global<v8::Context> *m_context;
+
+ FILE *m_stdout;
+ FILE *m_stderr;
+ lldb::FileSP m_output_file;
+ OutputCallback m_output_callback;
+
+ // Map from baton pointer to JavaScript callback function
+ std::map<void *, v8::Global<v8::Function>> m_breakpoint_callbacks;
+ std::map<void *, v8::Global<v8::Function>> m_watchpoint_callbacks;
+
+ // Initialize V8 platform (called once)
+ static void InitializePlatform();
+};
+
+} // namespace lldb_private
+
+#endif // liblldb_JavaScript_h_
diff --git a/lldb/source/Plugins/ScriptInterpreter/JavaScript/SWIGJavaScriptBridge.h b/lldb/source/Plugins/ScriptInterpreter/JavaScript/SWIGJavaScriptBridge.h
new file mode 100644
index 0000000000000..1c644cc84b3c5
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/JavaScript/SWIGJavaScriptBridge.h
@@ -0,0 +1,53 @@
+// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
+
+//===-- SWIGJavaScriptBridge.h ----------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_JAVASCRIPT_SWIGJAVASCRIPTBRIDGE_H
+#define LLDB_PLUGINS_SCRIPTINTERPRETER_JAVASCRIPT_SWIGJAVASCRIPTBRIDGE_H
+
+#include "lldb/lldb-forward.h"
+#include "llvm/Support/Error.h"
+
+namespace lldb_private {
+class StructuredDataImpl;
+} // namespace lldb_private
+
+namespace v8 {
+class Isolate;
+} // namespace v8
+
+// This will be implemented by SWIG-generated code
+extern "C" {
+void init_lldb(v8::Isolate *isolate);
+}
+
+namespace javascript {
+
+// Bridge functions for calling LLDB from JavaScript
+// These will be generated/implemented by SWIG
+namespace SWIGBridge {
+
+// TODO: Implement bridge functions
+// These are similar to LuaBridge functions but for JavaScript/V8
+
+llvm::Expected<bool> LLDBSwigJavaScriptBreakpointCallbackFunction(
+ v8::Isolate *isolate, lldb::StackFrameSP stop_frame_sp,
+ lldb::BreakpointLocationSP bp_loc_sp,
+ const lldb_private::StructuredDataImpl &extra_args_impl);
+
+llvm::Expected<bool>
+LLDBSwigJavaScriptWatchpointCallbackFunction(v8::Isolate *isolate,
+ lldb::StackFrameSP stop_frame_sp,
+ lldb::WatchpointSP wp_sp);
+
+} // namespace SWIGBridge
+
+} // namespace javascript
+
+#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_JAVASCRIPT_SWIGJAVASCRIPTBRIDGE_H
diff --git a/lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.cpp b/lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.cpp
new file mode 100644
index 0000000000000..6d7789d0e6b19
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.cpp
@@ -0,0 +1,229 @@
+// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
+
+//===-- ScriptInterpreterJavaScript.cpp ----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ScriptInterpreterJavaScript.h"
+#include "JavaScript.h"
+
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/IOHandler.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Utility/Stream.h"
+#include "lldb/Utility/StringList.h"
+#include "lldb/Utility/Timer.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+LLDB_PLUGIN_DEFINE(ScriptInterpreterJavaScript)
+
+// IOHandler for JavaScript REPL
+class IOHandlerJavaScriptInterpreter : public IOHandlerDelegate,
+ public IOHandlerEditline {
+public:
+ IOHandlerJavaScriptInterpreter(
+ Debugger &debugger, ScriptInterpreterJavaScript &script_interpreter)
+ : IOHandlerEditline(debugger, IOHandler::Type::Other, "javascript",
+ "> ", // Prompt
+ llvm::StringRef(), // No continuation prompt
+ false, // Single-line for now
+ debugger.GetUseColor(), 0, *this),
+ m_script_interpreter(script_interpreter) {
+ llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID()));
+ }
+
+ ~IOHandlerJavaScriptInterpreter() override {
+ llvm::cantFail(m_script_interpreter.LeaveSession());
+ }
+
+ void IOHandlerInputComplete(IOHandler &io_handler,
+ std::string &data) override {
+ if (data == "quit" || data == "exit") {
+ io_handler.SetIsDone(true);
+ return;
+ }
+
+ // Execute the JavaScript code
+ llvm::Error error = m_script_interpreter.GetJavaScript().Run(data);
+
+ if (error) {
+ // Print error
+ if (LockableStreamFileSP error_sp = io_handler.GetErrorStreamFileSP()) {
+ LockedStreamFile locked_stream = error_sp->Lock();
+ locked_stream << "error: " << llvm::toString(std::move(error)) << "\n";
+ }
+ }
+ }
+
+private:
+ ScriptInterpreterJavaScript &m_script_interpreter;
+};
+
+ScriptInterpreterJavaScript::ScriptInterpreterJavaScript(Debugger &debugger)
+ : ScriptInterpreter(debugger, eScriptLanguageJavaScript),
+ m_javascript(std::make_unique<JavaScript>(debugger.GetOutputFileSP())) {}
+
+ScriptInterpreterJavaScript::~ScriptInterpreterJavaScript() = default;
+
+bool ScriptInterpreterJavaScript::ExecuteOneLine(
+ llvm::StringRef command, CommandReturnObject *result,
+ const ExecuteScriptOptions &options) {
+ if (command.empty()) {
+ if (result)
+ result->AppendError("Empty command string\n");
+ return false;
+ }
+
+ // Set output callback to write console.log output to the result stream
+ if (result) {
+ m_javascript->SetOutputCallback([result](const std::string &text) {
+ result->GetOutputStream().Printf("%s", text.c_str());
+ });
+ }
+
+ llvm::Error error = m_javascript->Run(command);
+
+ // Clear the callback after execution
+ m_javascript->SetOutputCallback(nullptr);
+
+ if (error) {
+ if (result)
+ result->AppendError(llvm::toString(std::move(error)));
+ return false;
+ }
+
+ if (result)
+ result->SetStatus(eReturnStatusSuccessFinishResult);
+ return true;
+}
+
+void ScriptInterpreterJavaScript::ExecuteInterpreterLoop() {
+ LLDB_SCOPED_TIMER();
+
+ if (!m_debugger.GetInputFile().IsValid())
+ return;
+
+ IOHandlerSP io_handler_sp(
+ new IOHandlerJavaScriptInterpreter(m_debugger, *this));
+ m_debugger.RunIOHandlerAsync(io_handler_sp);
+}
+
+bool ScriptInterpreterJavaScript::LoadScriptingModule(
+ const char *filename, const LoadScriptOptions &options,
+ lldb_private::Status &error, StructuredData::ObjectSP *module_sp,
+ FileSpec extra_search_dir, lldb::TargetSP loaded_into_target_sp) {
+
+ if (!filename || filename[0] == '\0') {
+ error = Status::FromErrorString("Empty filename");
+ return false;
+ }
+
+ llvm::Error load_error = m_javascript->LoadModule(filename);
+ if (load_error) {
+ error =
+ Status::FromErrorString(llvm::toString(std::move(load_error)).c_str());
+ return false;
+ }
+
+ return true;
+}
+
+StructuredData::DictionarySP ScriptInterpreterJavaScript::GetInterpreterInfo() {
+ auto info_dict = std::make_shared<StructuredData::Dictionary>();
+ info_dict->AddStringItem("language", "javascript");
+ info_dict->AddStringItem("version", "ES2020+ (V8)");
+ return info_dict;
+}
+
+void ScriptInterpreterJavaScript::Initialize() {
+ static llvm::once_flag g_once_flag;
+ llvm::call_once(g_once_flag, []() {
+ PluginManager::RegisterPlugin(
+ GetPluginNameStatic(), GetPluginDescriptionStatic(),
+ lldb::eScriptLanguageJavaScript, CreateInstance);
+ });
+}
+
+void ScriptInterpreterJavaScript::Terminate() {
+ PluginManager::UnregisterPlugin(CreateInstance);
+}
+
+lldb::ScriptInterpreterSP
+ScriptInterpreterJavaScript::CreateInstance(Debugger &debugger) {
+ return std::make_shared<ScriptInterpreterJavaScript>(debugger);
+}
+
+llvm::StringRef ScriptInterpreterJavaScript::GetPluginDescriptionStatic() {
+ return "JavaScript script interpreter";
+}
+
+JavaScript &ScriptInterpreterJavaScript::GetJavaScript() {
+ return *m_javascript;
+}
+
+llvm::Error
+ScriptInterpreterJavaScript::EnterSession(lldb::user_id_t debugger_id) {
+ if (m_session_is_active)
+ return llvm::Error::success();
+
+ m_session_is_active = true;
+ return llvm::Error::success();
+}
+
+llvm::Error ScriptInterpreterJavaScript::LeaveSession() {
+ if (!m_session_is_active)
+ return llvm::Error::success();
+
+ m_session_is_active = false;
+ return llvm::Error::success();
+}
+
+void ScriptInterpreterJavaScript::CollectDataForBreakpointCommandCallback(
+ std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
+ CommandReturnObject &result) {
+ result.AppendError("Breakpoint callbacks not yet implemented for JavaScript");
+}
+
+void ScriptInterpreterJavaScript::CollectDataForWatchpointCommandCallback(
+ WatchpointOptions *wp_options, CommandReturnObject &result) {
+ result.AppendError("Watchpoint callbacks not yet implemented for JavaScript");
+}
+
+Status ScriptInterpreterJavaScript::SetBreakpointCommandCallback(
+ BreakpointOptions &bp_options, const char *command_body_text,
+ bool is_callback) {
+ return Status::FromErrorString(
+ "Breakpoint callbacks not yet implemented for JavaScript");
+}
+
+void ScriptInterpreterJavaScript::SetWatchpointCommandCallback(
+ WatchpointOptions *wp_options, const char *command_body_text,
+ bool is_callback) {
+ // TODO: Implement
+}
+
+Status ScriptInterpreterJavaScript::SetBreakpointCommandCallbackFunction(
+ BreakpointOptions &bp_options, const char *function_name,
+ StructuredData::ObjectSP extra_args_sp) {
+ return Status::FromErrorString(
+ "Breakpoint callbacks not yet implemented for JavaScript");
+}
+
+bool ScriptInterpreterJavaScript::BreakpointCallbackFunction(
+ void * /*baton*/, StoppointCallbackContext * /*context*/,
+ lldb::user_id_t /*break_id*/, lldb::user_id_t /*break_loc_id*/) {
+ return false;
+}
+
+bool ScriptInterpreterJavaScript::WatchpointCallbackFunction(
+ void * /*baton*/, StoppointCallbackContext * /*context*/,
+ lldb::user_id_t /*watch_id*/) {
+ return false;
+}
diff --git a/lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.h b/lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.h
new file mode 100644
index 0000000000000..5c6ebf2cd094f
--- /dev/null
+++ b/lldb/source/Plugins/ScriptInterpreter/JavaScript/ScriptInterpreterJavaScript.h
@@ -0,0 +1,108 @@
+//===-- ScriptInterpreterJavaScript.h ---------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_ScriptInterpreterJavaScript_h_
+#define liblldb_ScriptInterpreterJavaScript_h_
+
+#include <memory>
+#include <vector>
+
+#include "lldb/Breakpoint/WatchpointOptions.h"
+#include "lldb/Core/StructuredDataImpl.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Utility/Status.h"
+#include "lldb/lldb-enumerations.h"
+
+namespace v8 {
+class Isolate;
+template <class T> class Global;
+class Context;
+class Platform;
+} // namespace v8
+
+namespace lldb_private {
+
+class JavaScript;
+
+class ScriptInterpreterJavaScript : public ScriptInterpreter {
+public:
+ ScriptInterpreterJavaScript(Debugger &debugger);
+
+ ~ScriptInterpreterJavaScript() override;
+
+ bool ExecuteOneLine(
+ llvm::StringRef command, CommandReturnObject *result,
+ const ExecuteScriptOptions &options = ExecuteScriptOptions()) override;
+
+ void ExecuteInterpreterLoop() override;
+
+ bool LoadScriptingModule(const char *filename,
+ const LoadScriptOptions &options,
+ lldb_private::Status &error,
+ StructuredData::ObjectSP *module_sp = nullptr,
+ FileSpec extra_search_dir = {},
+ lldb::TargetSP loaded_into_target_sp = {}) override;
+
+ StructuredData::DictionarySP GetInterpreterInfo() override;
+
+ // Static Functions
+ static void Initialize();
+
+ static void Terminate();
+
+ static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger);
+
+ static llvm::StringRef GetPluginNameStatic() { return "script-javascript"; }
+
+ static llvm::StringRef GetPluginDescriptionStatic();
+
+ static bool BreakpointCallbackFunction(void *baton,
+ StoppointCallbackContext *context,
+ lldb::user_id_t break_id,
+ lldb::user_id_t break_loc_id);
+
+ static bool WatchpointCallbackFunction(void *baton,
+ StoppointCallbackContext *context,
+ lldb::user_id_t watch_id);
+
+ // PluginInterface protocol
+ llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
+
+ JavaScript &GetJavaScript();
+
+ llvm::Error EnterSession(lldb::user_id_t debugger_id);
+ llvm::Error LeaveSession();
+
+ void CollectDataForBreakpointCommandCallback(
+ std::vector<std::reference_wrapper<BreakpointOptions>> &bp_options_vec,
+ CommandReturnObject &result) override;
+
+ void
+ CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options,
+ CommandReturnObject &result) override;
+
+ Status SetBreakpointCommandCallback(BreakpointOptions &bp_options,
+ const char *command_body_text,
+ bool is_callback) override;
+
+ void SetWatchpointCommandCallback(WatchpointOptions *wp_options,
+ const char *command_body_text,
+ bool is_callback) override;
+
+ Status SetBreakpointCommandCallbackFunction(
+ BreakpointOptions &bp_options, const char *function_name,
+ StructuredData::ObjectSP extra_args_sp) override;
+
+private:
+ std::unique_ptr<JavaScript> m_javascript;
+ bool m_session_is_active = false;
+};
+
+} // namespace lldb_private
+
+#endif // liblldb_ScriptInterpreterJavaScript_h_
More information about the lldb-commits
mailing list