Index: lldb/CMakeLists.txt =================================================================== --- lldb/CMakeLists.txt +++ lldb/CMakeLists.txt @@ -71,7 +71,7 @@ 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_JAVA) add_subdirectory(bindings) endif () Index: lldb/bindings/CMakeLists.txt =================================================================== --- lldb/bindings/CMakeLists.txt +++ lldb/bindings/CMakeLists.txt @@ -52,3 +52,8 @@ if (LLDB_ENABLE_LUA) add_subdirectory(lua) endif() + +if (LLDB_ENABLE_JAVA) + add_subdirectory(java) +endif() + Index: lldb/bindings/java/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/bindings/java/CMakeLists.txt @@ -0,0 +1,27 @@ +FILE(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/SWIG") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/SWIG/LLDBWrapJava.cpp + DEPENDS ${SWIG_SOURCES} + DEPENDS ${SWIG_INTERFACES} + DEPENDS ${SWIG_HEADERS} + COMMAND ${SWIG_EXECUTABLE} + ${SWIG_COMMON_FLAGS} + -I${CMAKE_CURRENT_SOURCE_DIR} + -java + -package SWIG + -c++ + -outdir ${CMAKE_CURRENT_BINARY_DIR}/SWIG + -o ${CMAKE_CURRENT_BINARY_DIR}/SWIG/LLDBWrapJava.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/java.swig + VERBATIM + COMMENT "Building LLDB Java wrapper") + +add_custom_target(swig_wrapper_java ALL DEPENDS + ${CMAKE_CURRENT_BINARY_DIR}/SWIG/LLDBWrapJava.cpp +) + +FILE(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/classes") +add_custom_command(TARGET swig_wrapper_java POST_BUILD + COMMAND "${JAVAC_EXECUTABLE}" -d ${CMAKE_CURRENT_BINARY_DIR}/classes ${CMAKE_CURRENT_SOURCE_DIR}/SWIG/*.java + COMMAND ${JAR_EXECUTABLE} -cfM SWIG.jar -C ${CMAKE_CURRENT_BINARY_DIR}/classes . +) Index: lldb/bindings/java/SWIG/LldbScriptInterpreter.java =================================================================== --- /dev/null +++ lldb/bindings/java/SWIG/LldbScriptInterpreter.java @@ -0,0 +1,411 @@ +package SWIG; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URI; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import jdk.jshell.JShell; +import jdk.jshell.JShellException; +import jdk.jshell.Snippet.Status; +import jdk.jshell.SnippetEvent; + +public class LldbScriptInterpreter { + + private int debuggerID; + private String tmpDirPath; + private JavaCompiler compiler; + StandardJavaFileManager fm; + PrintWriter writer; + DiagnosticCollector diagnostics; + List options = new ArrayList<>(); + + StringWriter sw; + PrintWriter pw; + private boolean initialized = false; + private String className, methodName, methodSig; + private static Map map = new HashMap<>(); + private static boolean shellEnabled = false; + private static JShell shell; + + public LldbScriptInterpreter(int debuggerID, String swigPath) { + this.debuggerID = debuggerID; + tmpDirPath = System.getProperty("java.io.tmpdir"); + compiler = ToolProvider.getSystemJavaCompiler(); + fm = compiler.getStandardFileManager(null, null, null); + writer = new PrintWriter(new StringWriter()); + diagnostics = new DiagnosticCollector(); + + options.add("-g"); + options.add("-d"); + options.add(tmpDirPath); + options.add("-classpath"); + options.add(System.getProperty("java.class.path") + File.pathSeparator + + tmpDirPath + File.pathSeparator + swigPath); + options.add("-proc:none"); + System.loadLibrary("lldb"); + } + + public boolean runCmd(String cmd) { + + if (!initialized) { + initializeWriter(); + } + if (!cmd.startsWith("/")) { + return evalAsCode(cmd); + } + + // Might be a special command + boolean compile = cmd.startsWith("/ru") || cmd.startsWith("/co"); + boolean execute = + cmd.startsWith("/ru") || cmd.startsWith("/x") || cmd.startsWith("/ex"); + if (compile || execute) { + if (compile) { + if (!runCompile()) { + System.err.println("compile failed"); + return false; + } + } + initialized = false; + if (execute && map.containsValue(className)) { + return runMain(cmd); + } + return true; + } else if (cmd.equals("/reset")) { + initialized = false; + return true; + } else if (cmd.startsWith("/register")) { + return runRegister(cmd); + } else if (cmd.startsWith("/main") || cmd.startsWith("/bpt") || + cmd.startsWith("/wpt")) { + return buildPrologue(cmd); + } else if (cmd.equals("/init")) { + return buildContext(); + } else if (cmd.equals("/close")) { + return buildEpilogue(); + } else if (cmd.startsWith("/import")) { + return runImport(cmd); + } else if (cmd.startsWith("/shell")) { + return runShell(cmd); + } + return evalAsCode(cmd); + } + + private void initializeWriter() { + sw = new StringWriter(); + pw = new PrintWriter(sw); + initialized = true; + } + + private boolean evalAsCode(String cmd) { + if (shellEnabled || cmd.startsWith("/shell")) { + execShell(cmd); + } else { + pw.println(cmd); + } + return true; + } + + private boolean buildPrologue(String cmd) { + int randomInt = (int)(Math.random() * 1000); + className = "LLDB_" + randomInt; + methodName = cmd.startsWith("/main") ? "main" : "callback"; + methodSig = cmd.startsWith("/main") ? "void" : "boolean"; + String[] split = cmd.split(" "); + if (split.length > 1) { + map.put(split[1], className); + } + pw.println("import SWIG.*;"); + pw.println("public class " + className + " {"); + pw.print(" public static " + methodSig + " " + methodName); + if (cmd.startsWith("/main")) { + pw.println("(String args[]) {"); + return true; + } else if (cmd.startsWith("/bpt")) { + pw.println( + "(SBFrame _frame, SBBreakpointLocation loc, SBStructuredData args) {"); + return true; + } else if (cmd.startsWith("/wpt")) { + pw.println("(SBFrame _frame, SBWatchpoint wp) {"); + return true; + } + return false; + } + + private boolean buildContext() { + pw.println("SBDebugger debugger = SBDebugger.FindDebuggerWithID(" + + debuggerID + ");"); + pw.println("SBTarget target = debugger.GetSelectedTarget();"); + pw.println("SBProcess process = target.GetProcess();"); + pw.println("SBThread thread = process.GetSelectedThread();"); + pw.println("SBFrame frame = thread.GetSelectedFrame();"); + return true; + } + + private boolean buildEpilogue() { + pw.println(" }"); + pw.println("}"); + pw.close(); + return true; + } + + public boolean runCompile() { + if (!execCompile(className, sw.toString())) { + return false; + } + map.put(className, className); + return true; + } + + private boolean runMain(String cmd) { + String[] split = cmd.split(" "); + if (split.length > 1) { + if (map.containsKey(split[1])) { + className = map.get(split[1]); + } else { + System.err.println("Class for " + split[1] + " not found!"); + return false; + } + } + execMain(className); + return true; + } + + private boolean runRegister(String cmd) { + String[] split = cmd.split(" "); + if (split.length > 2) { + String x = map.get(split[2]); + if (x == null) { + System.err.println("Map does not contain " + split[2]); + System.err.println(map.keySet()); + return false; + } + map.put("LLDB_" + split[1], x); + } + return true; + } + + private boolean runImport(String cmd) { + String[] split = cmd.split(" "); + File f = new File(split[1]); + className = f.getName(); + try { + BufferedReader in = new BufferedReader(new InputStreamReader( + new FileInputStream(f), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + pw.println(line); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } + pw.close(); + if (!execCompile(className, sw.toString())) { + return false; + } + map.put(className, className); + return true; + } + + private boolean runShell(String cmd) { + String[] split = cmd.split(" "); + if (split.length > 1) { + if (split[1].equals("off")) { + shellEnabled = false; + return true; + } + cmd = cmd.substring(6); + } + if (!shellEnabled) { + shell = JShell.builder().build(); + } + shellEnabled = true; + execShell(cmd); + return true; + } + + public boolean runBreakpointCallback(String baton, long framePtr, + long bpLocPtr, long extraArgsPtr) { + SBFrame frame = framePtr == 0 ? null : new SBFrame(framePtr, false); + SBBreakpointLocation loc = + bpLocPtr == 0 ? null : new SBBreakpointLocation(framePtr, false); + SBStructuredData extraArgs = + extraArgsPtr == 0 ? null : new SBStructuredData(extraArgsPtr, false); + return execBreakpointCallback(baton, frame, loc, extraArgs); + } + + public boolean runWatchpointCallback(String baton, long framePtr, + long wpLocPtr) { + SBFrame frame = framePtr == 0 ? null : new SBFrame(framePtr, false); + SBWatchpoint wp = wpLocPtr == 0 ? null : new SBWatchpoint(framePtr, false); + return execWatchpointCallback(baton, frame, wp); + } + + public boolean runChangeIO(long outPtr, long errPtr) { + SWIGTYPE_p_std__shared_ptrT_lldb_private__File_t out = + new SWIGTYPE_p_std__shared_ptrT_lldb_private__File_t(outPtr, true); + SWIGTYPE_p_std__shared_ptrT_lldb_private__File_t err = + new SWIGTYPE_p_std__shared_ptrT_lldb_private__File_t(errPtr, true); + SBDebugger debugger = SBDebugger.FindDebuggerWithID(debuggerID); + debugger.SetOutputFile(out); + debugger.SetErrorFile(err); + return true; + } + + public boolean execCompile(String fname, String code) { + JavaFileObject file = new JavaSourceFromString(fname, code); + + List files = Arrays.asList(file); + JavaCompiler.CompilationTask task = + compiler.getTask(writer, fm, diagnostics, options, null, files); + if (!task.call()) { + System.err.println("Unexpected compilation failure:"); + System.err.println(code); + StringBuffer sb = new StringBuffer(); + for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { + sb.append("Error on line " + diagnostic.getLineNumber() + " in " + + diagnostic); + } + System.err.println(sb.toString()); + return false; + } + return true; + } + + public void execMain(String fileName) { + try { + File x = new File(tmpDirPath); + URLClassLoader classLoader = URLClassLoader.newInstance( + new URL[] {x.toURI().toURL()}); // root is path to class file + Class cls = Class.forName(fileName, true, classLoader); + cls.getDeclaredMethod("main", new Class[] {String[].class}) + .invoke(null, new Object[] {null}); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public boolean execBreakpointCallback(String batonName, SBFrame frame, + SBBreakpointLocation loc, + SBStructuredData args) { + Boolean retval = false; + String className = map.containsKey(batonName) + ? map.get(batonName) + : map.get("LLDB_" + batonName); + if (className == null) { + System.err.println("Class for " + batonName + " not found!"); + return false; + } + try { + File x = new File(tmpDirPath); + URLClassLoader classLoader = URLClassLoader.newInstance( + new URL[] {x.toURI().toURL()}); // root is path to class file + Class cls = Class.forName(className, true, classLoader); + Object ret = cls.getDeclaredMethod( + "callback", new Class[] {SBFrame.class, + SBBreakpointLocation.class, + SBStructuredData.class}) + .invoke(null, new Object[] {frame, loc, args}); + retval = (Boolean)ret; + return retval; + } catch (Exception e) { + e.printStackTrace(); + } + return retval; + } + + public boolean execWatchpointCallback(String batonName, SBFrame frame, + SBWatchpoint wp) { + Boolean retval = false; + String className = map.containsKey(batonName) + ? map.get(batonName) + : map.get("LLDB_" + batonName); + if (className == null) { + System.err.println("Class for " + batonName + " not found!"); + return false; + } + try { + File x = new File(tmpDirPath); + URLClassLoader classLoader = URLClassLoader.newInstance( + new URL[] {x.toURI().toURL()}); // root is path to class file + Class cls = Class.forName(className, true, classLoader); + Object ret = + cls.getDeclaredMethod("callback", + new Class[] {SBFrame.class, SBWatchpoint.class}) + .invoke(null, new Object[] {frame, wp}); + retval = (Boolean)ret; + return retval; + } catch (Exception e) { + e.printStackTrace(); + } + return retval; + } + + public String execShell(String shellCmd) { + List events = shell.eval(shellCmd); + String retval = ""; + for (SnippetEvent event : events) { + Status status = event.status(); + switch (status) { + case OVERWRITTEN: + retval += "OVERWRITING: "; + case VALID: + retval += event.value() + "\n"; + break; + case RECOVERABLE_DEFINED: + case RECOVERABLE_NOT_DEFINED: + case DROPPED: + case REJECTED: + case NONEXISTENT: + retval += event.toString() + "\n"; + break; + } + JShellException exception = event.exception(); + if (exception != null) { + retval += "EXC: " + exception + "\n"; + StackTraceElement[] stackTrace = exception.getStackTrace(); + for (int i = 0; i < stackTrace.length; i++) { + System.err.println(stackTrace[i].getMethodName() + ":" + + stackTrace[i].getLineNumber()); + } + } + } + System.out.print(retval); + return retval; + } + + class JavaSourceFromString extends SimpleJavaFileObject { + final String code; + + JavaSourceFromString(String name, String code) { + super(URI.create("string:///" + name.replace('.', '/') + + Kind.SOURCE.extension), + Kind.SOURCE); + this.code = code; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return code; + } + } +} Index: lldb/bindings/java/java-typemaps.swig =================================================================== --- /dev/null +++ lldb/bindings/java/java-typemaps.swig @@ -0,0 +1,18 @@ +%include +%include +%include + +%typemap(javabase) ByteArray "SWIGTYPE_p_void" +%typemap(javabody) ByteArray %{ + private long swigCPtr; // Minor bodge to work around private variable in parent + private boolean swigCMemOwn; + public $javaclassname(long cPtr, boolean cMemoryOwn) { + super(cPtr, cMemoryOwn); + this.swigCPtr = SWIGTYPE_p_void.getCPtr(this); + swigCMemOwn = cMemoryOwn; + } +%} + +%array_class(jbyte, ByteArray); + +%apply char **STRING_ARRAY { char ** } Index: lldb/bindings/java/java-wrapper.swig =================================================================== --- /dev/null +++ lldb/bindings/java/java-wrapper.swig @@ -0,0 +1,121 @@ +%header %{ + +%} + +%wrapper %{ + +// This function is called from Java::CallBreakpointCallback +SWIGEXPORT llvm::Expected +LLDBSwigJavaBreakpointCallbackFunction +( + JNIEnv *env, + void *baton, + lldb::user_id_t debugger_id, + const char * swigJarPath, + lldb::StackFrameSP stop_frame_sp, + lldb::BreakpointLocationSP bp_loc_sp, + StructuredDataImpl *extra_args_impl +) +{ + //printf("entering LLDBSwigJavaBreakpointCallbackFunction %x %x\n", stop_frame_sp, bp_loc_sp); + + lldb::SBFrame sb_frame(stop_frame_sp); + lldb::SBBreakpointLocation sb_bp_loc(bp_loc_sp); + + jclass interpreter_cls = env->FindClass("SWIG/LldbScriptInterpreter"); + if (interpreter_cls == NULL) { + printf("Failed to find 'LldbScriptInterpreter' class"); + return false; + } + + jint jid = (jint) debugger_id; + jstring jpath = env->NewStringUTF(swigJarPath); + jmethodID mid = env->GetMethodID(interpreter_cls, "", "(ILjava/lang/String;)V"); + if (mid == nullptr) { + printf("Failed to find '' function"); + return false; + } + + jobject interpreter = env->NewObject(interpreter_cls, mid, jid, jpath); + if (interpreter == NULL) { + printf("Failed to find 'interpreter' for %ld %s", debugger_id, swigJarPath); + return false; + } + + jmethodID midCallbackCmd = env->GetMethodID(interpreter_cls, "runBreakpointCallback", "(Ljava/lang/String;JJJ)Z"); + if (midCallbackCmd == NULL) { + printf("Failed to find 'runBreakpointCallback' function"); + return false; + } + + lldb::SBStructuredData *extra_args = NULL; + if (extra_args_impl) + extra_args = new lldb::SBStructuredData(extra_args_impl); + + std::string baton_str = llvm::formatv("{0}", baton); + jstring jstr = env->NewStringUTF(baton_str.c_str()); + jlong framePtr = jlong(&sb_frame); + jlong bptPtr = jlong(&sb_bp_loc); + jlong argsPtr = jlong(extra_args); + jboolean ret = (jboolean) env->CallBooleanMethod(interpreter, midCallbackCmd, jstr, framePtr, bptPtr, argsPtr); + + if (extra_args_impl) + delete extra_args; + return (bool) ret; +} + +// This function is called from Java::CallWatchpointCallback +SWIGEXPORT llvm::Expected +LLDBSwigJavaWatchpointCallbackFunction +( + JNIEnv *env, + void *baton, + lldb::user_id_t debugger_id, + const char * swigJarPath, + lldb::StackFrameSP stop_frame_sp, + lldb::WatchpointSP wp_sp +) +{ + //printf("entering LLDBSwigJavaWatchpointCallbackFunction\n"); + + lldb::SBFrame sb_frame(stop_frame_sp); + lldb::SBWatchpoint sb_wp(wp_sp); + + jclass interpreter_cls = env->FindClass("SWIG/LldbScriptInterpreter"); + if (interpreter_cls == NULL) { + printf("Failed to find 'LldbScriptInterpreter' class"); + return false; + } + + jint jid = (jint) debugger_id; + jstring jpath = env->NewStringUTF(swigJarPath); + jmethodID mid = env->GetMethodID(interpreter_cls, "", "(ILjava/lang/String;)V"); + if (mid == nullptr) { + printf("Failed to find '' function"); + return false; + } + + jobject interpreter = env->NewObject(interpreter_cls, mid, jid, jpath); + if (interpreter == NULL) { + printf("Failed to find 'interpreter' for %ld %s", debugger_id, swigJarPath); + return false; + } + + jmethodID midCallbackCmd = env->GetMethodID(interpreter_cls, "runWatchpointCallback", + "(Ljava/lang/String;JJ)Z"); + if (midCallbackCmd == NULL) { + printf("Failed to find 'runWatchpointCallback' function"); + return false; + } + + std::string baton_str = llvm::formatv("{0}", baton); + jstring jstr = env->NewStringUTF(baton_str.c_str()); + jlong framePtr = jlong(&sb_frame); + jlong wptPtr = jlong(&sb_wp); + jboolean ret = (jboolean) env->CallBooleanMethod(interpreter, midCallbackCmd, jstr, framePtr, wptPtr); + + return (bool) ret; +} + + +%} Index: lldb/bindings/java/java.swig =================================================================== --- /dev/null +++ lldb/bindings/java/java.swig @@ -0,0 +1,24 @@ +/* + lldb.swig + + This is the input file for SWIG, to create the appropriate C++ wrappers and + functions for various scripting languages, to enable them to call the + liblldb Script Bridge functions. +*/ + +%module lldb + +%include +%include "java-typemaps.swig" +%include "macros.swig" +%include "headers.swig" + +%{ +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +using namespace lldb_private; +using namespace lldb; +%} + +%include "interfaces.swig" +%include "java-wrapper.swig" Index: lldb/cmake/modules/FindJavaAndSwig.cmake =================================================================== --- /dev/null +++ lldb/cmake/modules/FindJavaAndSwig.cmake @@ -0,0 +1,44 @@ +#.rst: +# FindJavaAndSwig +# -------------- +# +# Find Java and SWIG as a whole. + +if(JAVA_LIBRARIES AND JAVA_INCLUDE_DIR AND SWIG_EXECUTABLE) + set(JAVAANDSWIG_FOUND TRUE) +else() + find_package(SWIG 3.0) + if (SWIG_FOUND) + find_package(Java 11.0) + if(JAVA_FOUND AND SWIG_FOUND) + mark_as_advanced( + JAVA_LIBRARIES + JAVA_INCLUDE_DIR + SWIG_EXECUTABLE) + endif() + else() + message(STATUS "SWIG 3 or later is required for Java support in LLDB but could not be found") + endif() + + if (DEFINED ENV{JAVA_HOME}) + set(JAVA_LIBRARIES $ENV{JAVA_HOME}/lib) + set(JAVA_SERVER_LIB $ENV{JAVA_HOME}/lib/server) + set(JAVA_INCLUDE_DIR $ENV{JAVA_HOME}/include) + set(JAVAC_EXECUTABLE $ENV{JAVA_HOME}/bin/javac) + set(JAR_EXECUTABLE $ENV{JAVA_HOME}/bin/jar) + include_directories(${JAVA_INCLUDE_DIR}) + link_directories(${JAVA_SERVER_LIB}) + set(JAVAANDSWIG_FOUND TRUE) + else() + message(STATUS "Please ensure the Environment variable JAVA_HOME has been set correctly") + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(JavaAndSwig + FOUND_VAR + JAVAANDSWIG_FOUND + REQUIRED_VARS + JAVA_LIBRARIES + JAVA_INCLUDE_DIR + SWIG_EXECUTABLE) +endif() Index: lldb/cmake/modules/LLDBConfig.cmake =================================================================== --- lldb/cmake/modules/LLDBConfig.cmake +++ lldb/cmake/modules/LLDBConfig.cmake @@ -60,6 +60,7 @@ add_optional_dependency(LLDB_ENABLE_CURSES "Enable curses support in LLDB" CursesAndPanel CURSESANDPANEL_FOUND) 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_JAVA "Enable Java scripting support in LLDB" JavaAndSwig JAVAANDSWIG_FOUND) add_optional_dependency(LLDB_ENABLE_PYTHON "Enable Python scripting support in LLDB" PythonAndSwig PYTHONANDSWIG_FOUND) add_optional_dependency(LLDB_ENABLE_LIBXML2 "Enable Libxml 2 support in LLDB" LibXml2 LIBXML2_FOUND VERSION 2.8) add_optional_dependency(LLDB_ENABLE_FBSDVMCORE "Enable libfbsdvmcore support in LLDB" FBSDVMCore FBSDVMCore_FOUND QUIET) @@ -185,6 +186,9 @@ check_cxx_compiler_flag("-Wno-vla-extension" CXX_SUPPORTS_NO_VLA_EXTENSION) append_if(CXX_SUPPORTS_NO_VLA_EXTENSION "-Wno-vla-extension" CMAKE_CXX_FLAGS) +check_cxx_compiler_flag("-fexceptions" CXX_SUPPORTS_EXCEPTIONS) +append_if(CXX_SUPPORTS_EXCEPTIONS "-fexceptions" CMAKE_CXX_FLAGS) + # Disable MSVC warnings if( MSVC ) add_definitions( Index: lldb/docs/resources/build.rst =================================================================== --- lldb/docs/resources/build.rst +++ lldb/docs/resources/build.rst @@ -64,6 +64,8 @@ +-------------------+------------------------------------------------------+--------------------------+ | Lua | Lua scripting | ``LLDB_ENABLE_LUA`` | +-------------------+------------------------------------------------------+--------------------------+ +| Java | Java scripting | ``LLDB_ENABLE_JAVA`` | ++-------------------+------------------------------------------------------+--------------------------+ Depending on your platform and package manager, one might run any of the commands below. Index: lldb/include/lldb/Core/IOHandler.h =================================================================== --- lldb/include/lldb/Core/IOHandler.h +++ lldb/include/lldb/Core/IOHandler.h @@ -55,6 +55,7 @@ ProcessIO, PythonInterpreter, LuaInterpreter, + JavaInterpreter, PythonCode, Other }; Index: lldb/include/lldb/Host/Config.h.cmake =================================================================== --- lldb/include/lldb/Host/Config.h.cmake +++ lldb/include/lldb/Host/Config.h.cmake @@ -41,6 +41,8 @@ #cmakedefine01 LLDB_ENABLE_LIBXML2 +#cmakedefine01 LLDB_ENABLE_JAVA + #cmakedefine01 LLDB_ENABLE_LUA #cmakedefine01 LLDB_ENABLE_PYTHON Index: lldb/include/lldb/lldb-enumerations.h =================================================================== --- lldb/include/lldb/lldb-enumerations.h +++ lldb/include/lldb/lldb-enumerations.h @@ -217,6 +217,7 @@ eScriptLanguageNone = 0, eScriptLanguagePython, eScriptLanguageLua, + eScriptLanguageJava, eScriptLanguageUnknown, eScriptLanguageDefault = eScriptLanguagePython }; Index: lldb/source/API/CMakeLists.txt =================================================================== --- lldb/source/API/CMakeLists.txt +++ lldb/source/API/CMakeLists.txt @@ -15,6 +15,11 @@ set(lldb_lua_wrapper ${lua_bindings_dir}/LLDBWrapLua.cpp) endif() +if(LLDB_ENABLE_JAVA) + get_target_property(java_bindings_dir swig_wrapper_java BINARY_DIR) + set(lldb_java_wrapper ${java_bindings_dir}/SWIG/LLDBWrapJava.cpp) +endif() + add_lldb_library(liblldb SHARED ${option_framework} SBAddress.cpp SBAttachInfo.cpp @@ -88,6 +93,7 @@ SystemInitializerFull.cpp ${lldb_python_wrapper} ${lldb_lua_wrapper} + ${lldb_java_wrapper} LINK_LIBS lldbBreakpoint @@ -156,6 +162,21 @@ set_source_files_properties(${lldb_lua_wrapper} PROPERTIES GENERATED ON) endif() +if(LLDB_ENABLE_JAVA) + add_dependencies(liblldb swig_wrapper_java) + target_include_directories(liblldb PRIVATE ${JAVA_INCLUDE_DIR}) + target_include_directories(liblldb PRIVATE ${JAVA_INCLUDE_DIR}/darwin) + + if (MSVC) + set_property(SOURCE ${lldb_java_wrapper} APPEND_STRING PROPERTY COMPILE_FLAGS " /W0") + else() + set_property(SOURCE ${lldb_java_wrapper} APPEND_STRING PROPERTY COMPILE_FLAGS " ") + endif() + + set_source_files_properties(${lldb_java_wrapper} PROPERTIES GENERATED ON) +endif() + + set_target_properties(liblldb PROPERTIES VERSION ${LLDB_VERSION} Index: lldb/source/API/SBDebugger.cpp =================================================================== --- lldb/source/API/SBDebugger.cpp +++ lldb/source/API/SBDebugger.cpp @@ -709,6 +709,9 @@ AddBoolConfigEntry( *config_up, "lua", LLDB_ENABLE_LUA, "A boolean value that indicates if lua support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "java", LLDB_ENABLE_JAVA, + "A boolean value that indicates if java support is enabled in LLDB"); AddBoolConfigEntry(*config_up, "fbsdvmcore", LLDB_ENABLE_FBSDVMCORE, "A boolean value that indicates if fbsdvmcore support is " "enabled in LLDB"); Index: lldb/source/API/liblldb-private.exports =================================================================== --- lldb/source/API/liblldb-private.exports +++ lldb/source/API/liblldb-private.exports @@ -5,3 +5,5 @@ init_lld* PyInit__lldb* luaopen_lldb* +Java* + Index: lldb/source/API/liblldb.exports =================================================================== --- lldb/source/API/liblldb.exports +++ lldb/source/API/liblldb.exports @@ -1,5 +1,8 @@ _ZN4lldb* _ZNK4lldb* +_ZN12lldb* +_ZNK12lldb* init_lld* PyInit__lldb* luaopen_lldb* +Java* Index: lldb/source/Commands/CommandObjectBreakpointCommand.cpp =================================================================== --- lldb/source/Commands/CommandObjectBreakpointCommand.cpp +++ lldb/source/Commands/CommandObjectBreakpointCommand.cpp @@ -41,6 +41,11 @@ "lua", "Commands are in the Lua language.", }, + { + eScriptLanguageJava, + "java", + "Commands are in the Java language.", + }, { eScriptLanguageDefault, "default-script", @@ -305,6 +310,7 @@ switch (m_script_language) { case eScriptLanguagePython: case eScriptLanguageLua: + case eScriptLanguageJava: m_use_script_language = true; break; case eScriptLanguageNone: Index: lldb/source/Commands/CommandObjectScript.cpp =================================================================== --- lldb/source/Commands/CommandObjectScript.cpp +++ lldb/source/Commands/CommandObjectScript.cpp @@ -31,6 +31,11 @@ "lua", "Lua", }, + { + eScriptLanguageJava, + "java", + "Java", + }, { eScriptLanguageNone, "default", Index: lldb/source/Commands/CommandObjectWatchpointCommand.cpp =================================================================== --- lldb/source/Commands/CommandObjectWatchpointCommand.cpp +++ lldb/source/Commands/CommandObjectWatchpointCommand.cpp @@ -41,6 +41,11 @@ "lua", "Commands are in the Lua language.", }, + { + eScriptLanguageJava, + "java", + "Commands are in the Java language.", + }, { eSortOrderByName, "default-script", @@ -336,6 +341,7 @@ switch (m_script_language) { case eScriptLanguagePython: case eScriptLanguageLua: + case eScriptLanguageJava: m_use_script_language = true; break; case eScriptLanguageNone: Index: lldb/source/Interpreter/OptionArgParser.cpp =================================================================== --- lldb/source/Interpreter/OptionArgParser.cpp +++ lldb/source/Interpreter/OptionArgParser.cpp @@ -129,6 +129,8 @@ return eScriptLanguagePython; if (s.equals_insensitive("lua")) return eScriptLanguageLua; + if (s.equals_insensitive("java")) + return eScriptLanguageJava; if (s.equals_insensitive("default")) return eScriptLanguageDefault; if (s.equals_insensitive("none")) Index: lldb/source/Interpreter/ScriptInterpreter.cpp =================================================================== --- lldb/source/Interpreter/ScriptInterpreter.cpp +++ lldb/source/Interpreter/ScriptInterpreter.cpp @@ -68,6 +68,8 @@ return "Python"; case eScriptLanguageLua: return "Lua"; + case eScriptLanguageJava: + return "Java"; case eScriptLanguageUnknown: return "Unknown"; } @@ -103,6 +105,8 @@ return eScriptLanguagePython; if (language.equals_insensitive(LanguageToString(eScriptLanguageLua))) return eScriptLanguageLua; + if (language.equals_insensitive(LanguageToString(eScriptLanguageJava))) + return eScriptLanguageJava; return eScriptLanguageUnknown; } Index: lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt =================================================================== --- lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt +++ lldb/source/Plugins/ScriptInterpreter/CMakeLists.txt @@ -6,3 +6,9 @@ if (LLDB_ENABLE_LUA) add_subdirectory(Lua) endif() + +if (LLDB_ENABLE_JAVA) + add_subdirectory(Java) +endif() + + Index: lldb/source/Plugins/ScriptInterpreter/Java/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Java/CMakeLists.txt @@ -0,0 +1,17 @@ +find_package(Java REQUIRED) + +add_lldb_library(lldbPluginScriptInterpreterJava PLUGIN + Java.cpp + ScriptInterpreterJava.cpp + + LINK_LIBS + lldbCore + lldbInterpreter + ) + +string(TOLOWER ${CMAKE_SYSTEM_NAME} LC_CMAKE_SYSTEM_NAME) +target_include_directories(lldbPluginScriptInterpreterJava PUBLIC ${JAVA_INCLUDE_DIR}) +target_include_directories(lldbPluginScriptInterpreterJava PUBLIC ${JAVA_INCLUDE_DIR}/${LC_CMAKE_SYSTEM_NAME}) +target_link_directories(lldbPluginScriptInterpreterJava PUBLIC ${JAVA_LIBRARIES}) +target_link_directories(lldbPluginScriptInterpreterJava PUBLIC ${JAVA_SERVER_LIB}) + Index: lldb/source/Plugins/ScriptInterpreter/Java/Java.h =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Java/Java.h @@ -0,0 +1,66 @@ +//===-- ScriptInterpreterJava.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_Java_h_ +#define liblldb_Java_h_ + +#include "jni.h" +#include "lldb/API/SBBreakpointLocation.h" +#include "lldb/API/SBFrame.h" +#include "lldb/Core/StructuredDataImpl.h" +#include "lldb/lldb-types.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +//#include "Java.hpp" + +#include + +namespace lldb_private { + +extern "C" { +int Javaopen_lldb(JNIEnv *env); +} + +class Java { +public: + Java(); + ~Java(); + + llvm::Error Init(lldb::user_id_t debugger_id); + llvm::Error Test(llvm::StringRef buffer); + llvm::Error Run(llvm::StringRef buffer); + llvm::Error RunError(llvm::StringRef buffer); + llvm::Error Run(const char *data); + llvm::Error RegisterBreakpointCallback(void *baton, const char *body, + bool existing); + llvm::Expected + CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp, + lldb::BreakpointLocationSP bp_loc_sp, + StructuredData::ObjectSP extra_args_sp); + llvm::Error RegisterWatchpointCallback(void *baton, const char *body, + bool existing); + llvm::Expected CallWatchpointCallback(void *baton, + lldb::StackFrameSP stop_frame_sp, + lldb::WatchpointSP wp_sp); + llvm::Error LoadModule(llvm::StringRef filename); + llvm::Error CheckSyntax(llvm::StringRef buffer); + llvm::Error ChangeIO(FILE *out, FILE *err); + +private: + JavaVM *m_vm = NULL; + JNIEnv *m_env = NULL; + jclass interpreter_cls = NULL; + jobject interpreter = NULL; + const char *swigJarPath = NULL; + lldb::user_id_t dbg_id = -1; +}; + +} // namespace lldb_private + +#endif // liblldb_Java_h_ Index: lldb/source/Plugins/ScriptInterpreter/Java/Java.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Java/Java.cpp @@ -0,0 +1,395 @@ +//===-- Java.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 "Java.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/FileSpec.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#include + +using namespace lldb_private; +using namespace lldb; +using namespace llvm; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" + +// Disable warning C4190: 'LLDBSwigPythonBreakpointCallbackFunction' has +// C-linkage specified, but returns UDT 'llvm::Expected' which is +// incompatible with C +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4190) +#endif + +extern "C" llvm::Expected LLDBSwigJavaBreakpointCallbackFunction( + JNIEnv *L, void *baton, lldb::user_id_t debugger_id, + const char *swigJarPath, lldb::StackFrameSP stop_frame_sp, + lldb::BreakpointLocationSP bp_loc_sp, StructuredDataImpl *extra_args_impl); + +extern "C" llvm::Expected LLDBSwigJavaWatchpointCallbackFunction( + JNIEnv *L, void *baton, lldb::user_id_t debugger_id, + const char *swigJarPath, lldb::StackFrameSP stop_frame_sp, + lldb::WatchpointSP wp_sp); + +#if _MSC_VER +#pragma warning(pop) +#endif + +#pragma clang diagnostic pop + +bool loadLibJVM(); +bool loadSwigJar(); + +Java::Java() { + + if (!loadLibJVM()) { + return; + } + + swigJarPath = ::getenv("CLASSPATH"); + if (swigJarPath == NULL) { + printf("Failed to find SWIG.jar in CLASSPATH=%s\n", swigJarPath); + return; + } + + JavaVMInitArgs vm_args; + JavaVMOption *options = new JavaVMOption[1]; + std::string javaClassPath = + llvm::formatv("-Djava.class.path={0}", swigJarPath).str(); + options[0].optionString = strdup(javaClassPath.c_str()); + options[0].extraInfo = NULL; + + vm_args.version = JNI_VERSION_1_2; + vm_args.nOptions = 1; + vm_args.options = options; + vm_args.ignoreUnrecognized = false; + + jint (*JNI_CreateJavaVM)(JavaVM * *p_vm, void **p_env, void *vm_args); + jint (*JNI_GetCreatedJavaVMs)(JavaVM * *vmBuf, jsize bufLen, jsize * nVMs); + + void *fn_ptr = + sys::DynamicLibrary::SearchForAddressOfSymbol("JNI_GetCreatedJavaVMs"); + JNI_GetCreatedJavaVMs = (jint(*)(JavaVM **, jsize, jsize *))fn_ptr; + fn_ptr = sys::DynamicLibrary::SearchForAddressOfSymbol("JNI_CreateJavaVM"); + JNI_CreateJavaVM = (jint(*)(JavaVM **, void **, void *))fn_ptr; + + // Construct a VM + jsize nVMs = -1; + jint res = (*JNI_GetCreatedJavaVMs)(NULL, 0, &nVMs); + if (res != JNI_OK) { + printf("Failed to get Java VM count\n"); + delete options; + return; + } + + if (nVMs > 0) { + JavaVM **buffer = new JavaVM *[nVMs]; + res = (*JNI_GetCreatedJavaVMs)(buffer, nVMs, &nVMs); + if (res != JNI_OK) { + printf("Failed to locate existing Java VM %x\n", res); + delete options; + return; + } + m_vm = buffer[0]; + m_vm->GetEnv((void **)&m_env, JNI_VERSION_1_8); + } else { + m_vm = NULL; + m_env = NULL; + // FYI: This throws SIGSEGV but seems to not be normal for JVM startup + res = (*JNI_CreateJavaVM)(&m_vm, (void **)&m_env, &vm_args); + if (res != JNI_OK) { + printf("Failed to create Java VM %x\n", res); + delete options; + return; + } + res = (*JNI_GetCreatedJavaVMs)(NULL, 0, &nVMs); + } + delete options; + + assert(m_env); +} + +Java::~Java() { + + assert(m_env); + // Shutdown the VM. + /// m_vm->DestroyJavaVM(); +} + +llvm::Error Java::Init(lldb::user_id_t debugger_id) { + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + dbg_id = debugger_id; + + interpreter_cls = m_env->FindClass("SWIG/LldbScriptInterpreter"); + if (interpreter_cls == nullptr) { + return createStringError(llvm::inconvertibleErrorCode(), + "Failed to find 'LldbScriptInterpreter' class"); + } + + jint jid = (jint)debugger_id; + jstring jstr = m_env->NewStringUTF(swigJarPath); + jmethodID mid = + m_env->GetMethodID(interpreter_cls, "", "(ILjava/lang/String;)V"); + if (mid == nullptr) { + return createStringError(llvm::inconvertibleErrorCode(), + "Failed to find function"); + } + + interpreter = m_env->NewObject(interpreter_cls, mid, jid, jstr); + return llvm::Error::success(); +} + +llvm::Error Java::Test(llvm::StringRef buffer) { + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + + if (llvm::Error e = Java::Init(dbg_id)) { + return e; + } + if (llvm::Error e = Run("/main")) { + return e; + } + if (llvm::Error e = Run(buffer.data())) { + return e; + } + if (llvm::Error e = Run("/close")) { + return e; + } + if (llvm::Error e = Run("/run")) { + return e; + } + + return llvm::Error::success(); +} + +llvm::Error Java::Run(llvm::StringRef buffer) { + + if (llvm::Error e = Run(buffer.data())) { + return e; + } + return llvm::Error::success(); +} + +llvm::Error Java::Run(const char *data) { + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + + jmethodID midRunCmd = + m_env->GetMethodID(interpreter_cls, "runCmd", "(Ljava/lang/String;)Z"); + if (midRunCmd == NULL) { + return createStringError(llvm::inconvertibleErrorCode(), + "Failed to find 'runCmd' function"); + } + + jstring jstr = m_env->NewStringUTF(data); + jboolean res = m_env->CallBooleanMethod(interpreter, midRunCmd, jstr); + if (!(bool)res) { + return createStringError(llvm::inconvertibleErrorCode(), + llvm::formatv("Failed to execute '{0}'", data)); + } + return llvm::Error::success(); +} + +llvm::Error Java::RegisterBreakpointCallback(void *baton, const char *body, + bool existing) { + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + + if (interpreter_cls == NULL) { + if (llvm::Error e = Java::Init(dbg_id)) { + return e; + } + } + + if (!existing) { + std::string baton_str = llvm::formatv("/bpt {0}", baton); + if (llvm::Error e = Run(baton_str.c_str())) + return e; + if (llvm::Error e = Run("/init")) + return e; + if (llvm::Error e = Run(body)) + return e; + if (strstr(body, "return ") == NULL) { + if (llvm::Error e = Run("return false;")) + return e; + } + if (llvm::Error e = Run("/close")) + return e; + if (llvm::Error e = Run("/compile")) + return e; + } else { + std::string baton_str = llvm::formatv("/register {0} {1}", baton, body); + if (llvm::Error e = Run(baton_str.c_str())) + return e; + } + + return llvm::Error::success(); +} + +llvm::Expected +Java::CallBreakpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp, + lldb::BreakpointLocationSP bp_loc_sp, + StructuredData::ObjectSP extra_args_sp) { + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + + auto *extra_args_impl = [&]() -> StructuredDataImpl * { + if (extra_args_sp == nullptr) + return nullptr; + auto *extra_args_impl = new StructuredDataImpl(); + extra_args_impl->SetObjectSP(extra_args_sp); + return extra_args_impl; + }(); + + return LLDBSwigJavaBreakpointCallbackFunction(m_env, baton, dbg_id, + swigJarPath, stop_frame_sp, + bp_loc_sp, extra_args_impl); +} + +llvm::Error Java::RegisterWatchpointCallback(void *baton, const char *body, + bool existing) { + + if (interpreter_cls == NULL) { + if (llvm::Error e = Java::Init(dbg_id)) { + return e; + } + } + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + + if (!existing) { + std::string baton_str = llvm::formatv("/wpt {0}", baton); + if (llvm::Error e = Run(baton_str.c_str())) + return e; + if (llvm::Error e = Run("/init")) + return e; + if (llvm::Error e = Run(body)) + return e; + if (strcmp(body, "return ") < 0) { + if (llvm::Error e = Run("return false;")) + return e; + } + if (llvm::Error e = Run("/close")) + return e; + if (llvm::Error e = Run("/compile")) + return e; + } else { + std::string baton_str = llvm::formatv("/register {0} {1}", baton, body); + if (llvm::Error e = Run(baton_str.c_str())) + return e; + } + + return llvm::Error::success(); +} + +llvm::Expected +Java::CallWatchpointCallback(void *baton, lldb::StackFrameSP stop_frame_sp, + lldb::WatchpointSP wp_sp) { + + m_vm->AttachCurrentThread((void **)&m_env, NULL); + + return LLDBSwigJavaWatchpointCallbackFunction( + m_env, baton, dbg_id, swigJarPath, stop_frame_sp, wp_sp); +} + +llvm::Error Java::CheckSyntax(llvm::StringRef buffer) { + return llvm::Error::success(); +} + +llvm::Error Java::LoadModule(llvm::StringRef filename) { + + if (interpreter_cls == NULL) { + if (llvm::Error e = Java::Init(dbg_id)) { + return e; + } + } + + FileSpec file(filename); + if (!FileSystem::Instance().Exists(file)) { + return llvm::make_error("invalid path", + llvm::inconvertibleErrorCode()); + } + + ConstString module_extension = file.GetFileNameExtension(); + if (module_extension != ".java") { + return llvm::make_error("invalid extension", + llvm::inconvertibleErrorCode()); + } + + std::string import_str = llvm::formatv("/import {0}", filename); + if (llvm::Error e = Run(import_str.c_str())) { + return e; + } + + return llvm::Error::success(); +} + +llvm::Error Java::ChangeIO(FILE *out, FILE *err) { + assert(out != nullptr); + assert(err != nullptr); + + if (interpreter_cls == NULL) { + return llvm::Error::success(); + } + + jmethodID midChangeIOCmd = + m_env->GetMethodID(interpreter_cls, "runChangeIO", "(JJ)Z"); + if (midChangeIOCmd == NULL) { + return createStringError(llvm::inconvertibleErrorCode(), + "Failed to find 'runChangeIO' function"); + } + + FileSP fout = std::make_shared(out, false); + FileSP ferr = std::make_shared(err, false); + jlong outPtr = jlong(&fout); + jlong errPtr = jlong(&ferr); + m_env->CallBooleanMethod(interpreter, midChangeIOCmd, outPtr, errPtr); + return llvm::Error::success(); +} + +llvm::Error makeError(const char *text) { + printf("makeError %s\n", text); + llvm::Error e = llvm::make_error( + llvm::formatv("{0}\n", text), llvm::inconvertibleErrorCode()); + return e; +} + +llvm::Error Java::RunError(llvm::StringRef buffer) { + + llvm::Error e = llvm::make_error( + llvm::formatv("{0}\n", buffer.data()), llvm::inconvertibleErrorCode()); + return std::move(e); +} + +bool loadLibJVM() { + +#if defined(_WIN32) + const char *Suffix = ".dll"; +#elif defined(__APPLE__) + const char *Suffix = ".dylib"; +#else + const char *Suffix = ".so"; +#endif + + const char *ldLibraryPath = ::getenv("JAVA_HOME"); + std::string err = llvm::formatv(""); + + std::string jvmPath = + llvm::formatv("{0}/lib/server/libjvm{1}", ldLibraryPath, Suffix); + sys::DynamicLibrary::LoadLibraryPermanently(jvmPath.c_str(), &err); + if (strlen(err.c_str()) == 0) { + return true; + } + + printf("Failed to find 'libjvm in %s/lib/server\n", ldLibraryPath); + return false; +} Index: lldb/source/Plugins/ScriptInterpreter/Java/ScriptInterpreterJava.h =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Java/ScriptInterpreterJava.h @@ -0,0 +1,126 @@ +//===-- ScriptInterpreterJava.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_ScriptInterpreterJava_h_ +#define liblldb_ScriptInterpreterJava_h_ + +#include + +#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 lldb_private { +class Java; +class ScriptInterpreterJava : public ScriptInterpreter { +public: + class CommandDataJava : public BreakpointOptions::CommandData { + public: + CommandDataJava() : BreakpointOptions::CommandData() { + interpreter = lldb::eScriptLanguageJava; + } + CommandDataJava(StructuredData::ObjectSP extra_args_sp) + : BreakpointOptions::CommandData(), m_extra_args_sp(extra_args_sp) { + interpreter = lldb::eScriptLanguageJava; + } + StructuredData::ObjectSP m_extra_args_sp; + }; + + ScriptInterpreterJava(Debugger &debugger); + + ~ScriptInterpreterJava() 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 = {}) override; + + // Static Functions + static void Initialize(); + + static void Terminate(); + + static lldb::ScriptInterpreterSP CreateInstance(Debugger &debugger); + + static llvm::StringRef GetPluginNameStatic() { return "script-java"; } + + static llvm::StringRef GetPluginDescriptionStatic(); + +/* + static lldb_private::ConstString GetPluginNameStatic(); + + static const char *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(); } + + Java &GetJava(); + + llvm::Error EnterSession(lldb::user_id_t debugger_id); + llvm::Error LeaveSession(); + + void CollectDataForBreakpointCommandCallback( + std::vector> &bp_options_vec, + CommandReturnObject &result) override; + + void + CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options, + CommandReturnObject &result) override; + + Status SetBreakpointCommandCallback(BreakpointOptions &bp_options, + const char *command_body_text) override; + + void SetWatchpointCommandCallback(WatchpointOptions *wp_options, + const char *command_body_text) override; + + Status SetBreakpointCommandCallbackFunction( + BreakpointOptions &bp_options, const char *function_name, + StructuredData::ObjectSP extra_args_sp) override; + + Status + SetWatchpointCommandCallbackFunction(WatchpointOptions *wp_options, + const char *function_name, + StructuredData::ObjectSP extra_args_sp); + +private: + std::unique_ptr m_java; + bool m_session_is_active = false; + + Status RegisterBreakpointCallback(BreakpointOptions &bp_options, + const char *command_body_text, + StructuredData::ObjectSP extra_args_sp, + bool existing); + + Status RegisterWatchpointCallback(WatchpointOptions *wp_options, + const char *command_body_text, + StructuredData::ObjectSP extra_args_sp, + bool existing); +}; + +} // namespace lldb_private + +#endif // liblldb_ScriptInterpreterJava_h_ Index: lldb/source/Plugins/ScriptInterpreter/Java/ScriptInterpreterJava.cpp =================================================================== --- /dev/null +++ lldb/source/Plugins/ScriptInterpreter/Java/ScriptInterpreterJava.cpp @@ -0,0 +1,406 @@ +//===-- ScriptInterpreterJava.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 "ScriptInterpreterJava.h" +#include "Java.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/StreamFile.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Utility/Stream.h" +#include "lldb/Utility/StringList.h" +#include "lldb/Utility/Timer.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatAdapters.h" +#include +#include + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(ScriptInterpreterJava) + +enum ActiveIOHandler { + eIOHandlerNone, + eIOHandlerBreakpoint, + eIOHandlerWatchpoint +}; + +class IOHandlerJavaInterpreter : public IOHandlerDelegate, + public IOHandlerEditline { +public: + IOHandlerJavaInterpreter(Debugger &debugger, + ScriptInterpreterJava &script_interpreter, + ActiveIOHandler active_io_handler = eIOHandlerNone) + : IOHandlerEditline(debugger, IOHandler::Type::JavaInterpreter, "java", + ">>> ", "..> ", true, debugger.GetUseColor(), 0, + *this, nullptr), + m_script_interpreter(script_interpreter), + m_active_io_handler(active_io_handler) { + llvm::cantFail(m_script_interpreter.EnterSession(debugger.GetID())); + llvm::cantFail(m_script_interpreter.GetJava().ChangeIO( + debugger.GetOutputFile().GetStream(), + debugger.GetErrorFile().GetStream())); + } + + ~IOHandlerJavaInterpreter() override { + llvm::cantFail(m_script_interpreter.LeaveSession()); + } + + void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { + const char *instructions = nullptr; + switch (m_active_io_handler) { + case eIOHandlerNone: + instructions = + "Enter your Java command(s). Type 'quit' to end.\n" + "Special commands include: \n" + " /main - sets up the standard prologue\n" + " /bpt, /wpt - prologue for break & watchpoint handlers (not " + "required for 'br' & 'wa')\n" + " /init - defines debugger, target, process, thread, frame\n" + " /close - completes prologue\n" + " /reset - start over\n" + " /compile - compile a function (no args == current)\n" + " /exec - execute a function\n" + " /run - compile & execute\n" + "Sample sequence: /main, /init, " + "System.out.println(frame.GetPC());, /close, /run\n"; + break; + case eIOHandlerWatchpoint: + instructions = "Enter your Java command(s). Type 'quit' to end.\n" + "The commands are compiled as the body of the following " + "Java function:\n" + " boolean callback(_frame, wp) {}\n"; + SetPrompt(llvm::StringRef("..> ")); + break; + case eIOHandlerBreakpoint: + instructions = "Enter your Java command(s). Type 'quit' to end.\n" + "The commands are compiled as the body of the following " + "Java function:\n" + " boolean callback(_frame, loc, args) {}\n"; + SetPrompt(llvm::StringRef("..> ")); + break; + } + if (instructions == nullptr) + return; + if (interactive) + *io_handler.GetOutputStreamFileSP() << instructions; + } + + bool IOHandlerIsInputComplete(IOHandler &io_handler, + StringList &lines) override { + size_t last = lines.GetSize() - 1; + if (IsQuitCommand(lines.GetStringAtIndex(last))) { + if (m_active_io_handler == eIOHandlerBreakpoint || + m_active_io_handler == eIOHandlerWatchpoint) + lines.DeleteStringAtIndex(last); + return true; + } + StreamString str; + lines.Join("\n", str); + if (llvm::Error E = + m_script_interpreter.GetJava().CheckSyntax(str.GetString())) { + std::string error_str = toString(std::move(E)); + // Java always errors out to incomplete code with '' + return error_str.find("") == std::string::npos; + } + // The breakpoint and watchpoint handler only exits with a explicit 'quit' + return m_active_io_handler != eIOHandlerBreakpoint && + m_active_io_handler != eIOHandlerWatchpoint; + } + + void IOHandlerInputComplete(IOHandler &io_handler, + std::string &data) override { + switch (m_active_io_handler) { + case eIOHandlerBreakpoint: { + auto *bp_options_vec = + static_cast> *>( + io_handler.GetUserData()); + for (BreakpointOptions &bp_options : *bp_options_vec) { + Status error = m_script_interpreter.SetBreakpointCommandCallback( + bp_options, data.c_str()); + if (error.Fail()) + *io_handler.GetErrorStreamFileSP() << error.AsCString() << '\n'; + } + io_handler.SetIsDone(true); + } break; + case eIOHandlerWatchpoint: { + auto *wp_options = + static_cast(io_handler.GetUserData()); + m_script_interpreter.SetWatchpointCommandCallback(wp_options, + data.c_str()); + io_handler.SetIsDone(true); + } break; + case eIOHandlerNone: + if (IsQuitCommand(data)) { + io_handler.SetIsDone(true); + return; + } + if (llvm::Error error = m_script_interpreter.GetJava().Run(data)) + *io_handler.GetErrorStreamFileSP() << toString(std::move(error)); + break; + } + } + +private: + ScriptInterpreterJava &m_script_interpreter; + ActiveIOHandler m_active_io_handler; + + bool IsQuitCommand(llvm::StringRef cmd) { return cmd.rtrim() == "quit"; } +}; + +ScriptInterpreterJava::ScriptInterpreterJava(Debugger &debugger) + : ScriptInterpreter(debugger, eScriptLanguageJava), + m_java(std::make_unique()) {} + +ScriptInterpreterJava::~ScriptInterpreterJava() = default; + +bool ScriptInterpreterJava::ExecuteOneLine( + llvm::StringRef command, CommandReturnObject *result, + const ExecuteScriptOptions &options) { + if (command.empty()) { + if (result) + result->AppendError("empty command passed to java\n"); + return false; + } + + llvm::Expected> + io_redirect_or_error = ScriptInterpreterIORedirect::Create( + options.GetEnableIO(), m_debugger, result); + if (!io_redirect_or_error) { + if (result) + result->AppendErrorWithFormatv( + "failed to redirect I/O: {0}\n", + llvm::fmt_consume(io_redirect_or_error.takeError())); + else + llvm::consumeError(io_redirect_or_error.takeError()); + return false; + } + + ScriptInterpreterIORedirect &io_redirect = **io_redirect_or_error; + + if (llvm::Error e = + m_java->ChangeIO(io_redirect.GetOutputFile()->GetStream(), + io_redirect.GetErrorFile()->GetStream())) { + result->AppendErrorWithFormatv("java failed to redirect I/O: {0}\n", + llvm::toString(std::move(e))); + return false; + } + + if (llvm::Error e = m_java->Test(command)) { + result->AppendErrorWithFormatv( + "java failed attempting to evaluate '{0}': {1}\n", command, + llvm::toString(std::move(e))); + return false; + } + + io_redirect.Flush(); + return true; +} + +void ScriptInterpreterJava::ExecuteInterpreterLoop() { + LLDB_SCOPED_TIMER(); + + // At the moment, the only time the debugger does not have an input file + // handle is when this is called directly from java, in which case it is + // both dangerous and unnecessary (not to mention confusing) to try to embed + // a running interpreter loop inside the already running java interpreter + // loop, so we won't do it. + if (!m_debugger.GetInputFile().IsValid()) + return; + + IOHandlerSP io_handler_sp(new IOHandlerJavaInterpreter(m_debugger, *this)); + m_debugger.RunIOHandlerAsync(io_handler_sp); +} + +bool ScriptInterpreterJava::LoadScriptingModule( + const char *filename, const LoadScriptOptions &options, + lldb_private::Status &error, StructuredData::ObjectSP *module_sp, + FileSpec extra_search_dir) { + + FileSystem::Instance().Collect(filename); + if (llvm::Error e = m_java->LoadModule(filename)) { + error.SetErrorStringWithFormatv("java failed to import '{0}': {1}\n", + filename, llvm::toString(std::move(e))); + return false; + } + return true; +} + +void ScriptInterpreterJava::Initialize() { + static llvm::once_flag g_once_flag; + + llvm::call_once(g_once_flag, []() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), + lldb::eScriptLanguageJava, CreateInstance); + }); +} + +void ScriptInterpreterJava::Terminate() {} + +llvm::Error ScriptInterpreterJava::EnterSession(user_id_t debugger_id) { + // printf("EnterSession %x\n", m_session_is_active); + if (m_session_is_active) + return llvm::Error::success(); + + m_session_is_active = true; + return m_java->Init(debugger_id); +} + +llvm::Error ScriptInterpreterJava::LeaveSession() { + if (!m_session_is_active) + return llvm::Error::success(); + + m_session_is_active = false; + return llvm::Error::success(); +} + +bool ScriptInterpreterJava::BreakpointCallbackFunction( + void *baton, StoppointCallbackContext *context, user_id_t break_id, + user_id_t break_loc_id) { + assert(context); + + ExecutionContext exe_ctx(context->exe_ctx_ref); + Target *target = exe_ctx.GetTargetPtr(); + if (target == nullptr) + return true; + + StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); + BreakpointSP breakpoint_sp = target->GetBreakpointByID(break_id); + BreakpointLocationSP bp_loc_sp(breakpoint_sp->FindLocationByID(break_loc_id)); + + Debugger &debugger = target->GetDebugger(); + ScriptInterpreterJava *java_interpreter = + static_cast( + debugger.GetScriptInterpreter(true, eScriptLanguageJava)); + Java &java = java_interpreter->GetJava(); + + CommandDataJava *bp_option_data = static_cast(baton); + llvm::Expected BoolOrErr = java.CallBreakpointCallback( + baton, stop_frame_sp, bp_loc_sp, bp_option_data->m_extra_args_sp); + if (llvm::Error E = BoolOrErr.takeError()) { + debugger.GetErrorStream() << toString(std::move(E)); + return true; + } + + return *BoolOrErr; +} + +bool ScriptInterpreterJava::WatchpointCallbackFunction( + void *baton, StoppointCallbackContext *context, user_id_t watch_id) { + assert(context); + + ExecutionContext exe_ctx(context->exe_ctx_ref); + Target *target = exe_ctx.GetTargetPtr(); + if (target == nullptr) + return true; + + StackFrameSP stop_frame_sp(exe_ctx.GetFrameSP()); + WatchpointSP wp_sp = target->GetWatchpointList().FindByID(watch_id); + + Debugger &debugger = target->GetDebugger(); + ScriptInterpreterJava *java_interpreter = + static_cast( + debugger.GetScriptInterpreter(true, eScriptLanguageJava)); + Java &java = java_interpreter->GetJava(); + + llvm::Expected BoolOrErr = + java.CallWatchpointCallback(baton, stop_frame_sp, wp_sp); + if (llvm::Error E = BoolOrErr.takeError()) { + debugger.GetErrorStream() << toString(std::move(E)); + return true; + } + + return *BoolOrErr; +} + +void ScriptInterpreterJava::CollectDataForBreakpointCommandCallback( + std::vector> &bp_options_vec, + CommandReturnObject &result) { + IOHandlerSP io_handler_sp( + new IOHandlerJavaInterpreter(m_debugger, *this, eIOHandlerBreakpoint)); + io_handler_sp->SetUserData(&bp_options_vec); + m_debugger.RunIOHandlerAsync(io_handler_sp); +} + +void ScriptInterpreterJava::CollectDataForWatchpointCommandCallback( + WatchpointOptions *wp_options, CommandReturnObject &result) { + IOHandlerSP io_handler_sp( + new IOHandlerJavaInterpreter(m_debugger, *this, eIOHandlerWatchpoint)); + io_handler_sp->SetUserData(wp_options); + m_debugger.RunIOHandlerAsync(io_handler_sp); +} + +Status ScriptInterpreterJava::SetBreakpointCommandCallbackFunction( + BreakpointOptions &bp_options, const char *function_name, + StructuredData::ObjectSP extra_args_sp) { + return RegisterBreakpointCallback(bp_options, function_name, extra_args_sp, + true); +} + +Status ScriptInterpreterJava::SetBreakpointCommandCallback( + BreakpointOptions &bp_options, const char *command_body_text) { + return RegisterBreakpointCallback(bp_options, command_body_text, {}, false); +} + +Status ScriptInterpreterJava::RegisterBreakpointCallback( + BreakpointOptions &bp_options, const char *command_body_text, + StructuredData::ObjectSP extra_args_sp, bool existing) { + Status error; + auto data_up = std::make_unique(extra_args_sp); + error = m_java->RegisterBreakpointCallback(data_up.get(), command_body_text, + existing); + if (error.Fail()) + return error; + auto baton_sp = + std::make_shared(std::move(data_up)); + bp_options.SetCallback(ScriptInterpreterJava::BreakpointCallbackFunction, + baton_sp); + return error; +} + +Status ScriptInterpreterJava::SetWatchpointCommandCallbackFunction( + WatchpointOptions *wp_options, const char *function_name, + StructuredData::ObjectSP extra_args_sp) { + return RegisterWatchpointCallback(wp_options, function_name, extra_args_sp, + true); +} + +void ScriptInterpreterJava::SetWatchpointCommandCallback( + WatchpointOptions *wp_options, const char *command_body_text) { + RegisterWatchpointCallback(wp_options, command_body_text, {}, false); +} + +Status ScriptInterpreterJava::RegisterWatchpointCallback( + WatchpointOptions *wp_options, const char *command_body_text, + StructuredData::ObjectSP extra_args_sp, bool existing) { + Status error; + auto data_up = std::make_unique(); + error = m_java->RegisterWatchpointCallback(data_up.get(), command_body_text, + existing); + if (error.Fail()) + return error; + auto baton_sp = + std::make_shared(std::move(data_up)); + wp_options->SetCallback(ScriptInterpreterJava::WatchpointCallbackFunction, + baton_sp); + return error; +} + +lldb::ScriptInterpreterSP +ScriptInterpreterJava::CreateInstance(Debugger &debugger) { + return std::make_shared(debugger); +} + +llvm::StringRef ScriptInterpreterJava::GetPluginDescriptionStatic() { + return "Java script interpreter"; +} +Java &ScriptInterpreterJava::GetJava() { return *m_java; } Index: lldb/test/CMakeLists.txt =================================================================== --- lldb/test/CMakeLists.txt +++ lldb/test/CMakeLists.txt @@ -168,6 +168,7 @@ LLDB_BUILD_INTEL_PT LLDB_ENABLE_PYTHON LLDB_ENABLE_LUA + LLDB_ENABLE_JAVA LLDB_ENABLE_LZMA LLVM_ENABLE_ZLIB LLVM_ENABLE_SHARED_LIBS Index: lldb/test/Shell/ScriptInterpreter/Java/Inputs/independent_state.in =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/Inputs/independent_state.in @@ -0,0 +1,10 @@ +script +/main +int foobar = 40 + 7; +System.out.println(foobar); +SBDebugger d = SBDebugger.Create(); +d.HandleCommand("script foobar = 40 + 2;"); +System.out.println(foobar); +d.HandleCommand("script print(foobar);"); +/close +/run Index: lldb/test/Shell/ScriptInterpreter/Java/Inputs/nested_sessions.in =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/Inputs/nested_sessions.in @@ -0,0 +1,12 @@ +script +/main +/init + System.out.println(n(target) + " " + n(debugger.GetSelectedTarget())); + debugger.SetSelectedTarget(debugger.GetTargetAtIndex(0)); + System.out.println(n(target) + " " + n(debugger.GetSelectedTarget())); + //debugger.HandleCommand("script SBDebugger debugger = SBDebugger.FindDebuggerWithID(1); SBTarget target = debugger.GetSelectedTarget(); System.out.println(target.GetExecutable().GetFilename() + \" \" + debugger.GetSelectedTarget().GetExecutable().GetFilename());"); + System.out.println(n(target) + " " + n(debugger.GetSelectedTarget())); + } + private static String n(SBTarget t) {return t.GetExecutable().GetFilename();} +} +/run Index: lldb/test/Shell/ScriptInterpreter/Java/Inputs/nested_sessions_2.in =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/Inputs/nested_sessions_2.in @@ -0,0 +1,8 @@ +script +/main +/init + System.out.println(n(target) + " " + n(debugger.GetSelectedTarget())); + } + private static String n(SBTarget t) {return t.GetExecutable().GetFilename();} +} +/run Index: lldb/test/Shell/ScriptInterpreter/Java/Inputs/testmodule.java =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/Inputs/testmodule.java @@ -0,0 +1,3 @@ +class testmodule { + public static void foo() { System.out.println("Hello World!"); } +} Index: lldb/test/Shell/ScriptInterpreter/Java/bindings.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/bindings.test @@ -0,0 +1,8 @@ +# RUN: cat %s | %lldb --script-language java 2>&1 | FileCheck %s +script +/main +SBDebugger debugger = SBDebugger.Create(); +System.err.println("debugger is valid: " + debugger.IsValid()); +/close +/run +# CHECK: debugger is valid: true Index: lldb/test/Shell/ScriptInterpreter/Java/breakpoint_callback.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/breakpoint_callback.test @@ -0,0 +1,11 @@ +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t +# RUN: %lldb -s %s --script-language java %t 2>&1 | FileCheck %s +b main +breakpoint command add -s java +int a = 123; +System.out.println(a); +return true; +quit +run +# CHECK: 123 + Index: lldb/test/Shell/ScriptInterpreter/Java/breakpoint_function_callback.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/breakpoint_function_callback.test @@ -0,0 +1,23 @@ +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t +# RUN: %lldb -s %s --script-language java %t 2>&1 | FileCheck %s +b main +script +/bpt abc +System.out.println(args); +if (args != null) { + SBStream stream = new SBStream(); + SBStructuredData val = args.GetValueForKey("foo"); + val.GetDescription(stream); + System.out.println(stream.GetData()); +} +return true; +/close +/compile +quit +breakpoint command add -s java -F abc +r +# CHECK: null +breakpoint command add -s java -F abc -k foo -v 123pizza! +r +# CHECK: "123pizza!" + Index: lldb/test/Shell/ScriptInterpreter/Java/breakpoint_oneline_callback.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/breakpoint_oneline_callback.test @@ -0,0 +1,18 @@ +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t +# RUN: %lldb -s %s --script-language java %t 2>&1 | FileCheck %s +b main +breakpoint command add -s java -o 'return false;' +run +# CHECK: Process {{[0-9]+}} exited with status = 0 +breakpoint command add -s java -o 'System.out.println("bacon");' +run +# CHECK: bacon +# CHECK: Process {{[0-9]+}} exited with status = 0 +breakpoint command add -s java -o "return true;" +run +# CHECK: Process {{[0-9]+}} stopped +breakpoint command add -s java -o 'System.out.println("my error message");' +run +# CHECK: my error message +# CHECK: Process {{[0-9]+}} exited with status = 0 + Index: lldb/test/Shell/ScriptInterpreter/Java/command_script_import.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/command_script_import.test @@ -0,0 +1,12 @@ +# RUN: %lldb --script-language java -o 'command script import %S/Inputs/testmodule.java' -o 'script testmodule.foo();' 2>&1 | FileCheck %s +# CHECK: Hello World! + +# RUN: mkdir -p %t +# RUN: cp %S/Inputs/testmodule.java %t/testmodule.notjava +# RUN: %lldb --script-language java -o 'command script import %t/testmodule.notjava' -o 'script testmodule.foo();' 2>&1 | FileCheck %s --check-prefix EXTENSION +# EXTENSION: error: module importing failed: java failed to import '{{.*}}testmodule.notjava': invalid extension +# EXTENSION-NOT: Hello World! + +# RUN: %lldb --script-language java -o 'command script import %S/Inputs/bogus' -o 'script testmodule.foo();' 2>&1 | FileCheck %s --check-prefix NONEXISTING +# NONEXISTING: error: module importing failed: java failed to import '{{.*}}bogus': invalid path +# NONEXISTING-NOT: Hello World! Index: lldb/test/Shell/ScriptInterpreter/Java/convenience_variables.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/convenience_variables.test @@ -0,0 +1,21 @@ +# UNSUPPORTED: lldb-repro +# +# This tests that the convenience variables are not nil. Given that there is no +# target we only expect the debugger to be valid. +# +# RUN: cat %s | %lldb --script-language java 2>&1 | FileCheck %s +script +/main +/init +System.out.println("debugger is valid: " + debugger.IsValid()); +System.out.println("target is valid: " + target.IsValid()); +System.out.println("process is valid: " + process.IsValid()); +System.out.println("thread is valid: " + thread.IsValid()); +System.out.println("frame is valid: " + frame.IsValid()); +/close +/run +# CHECK: debugger is valid: true +# CHECK: target is valid: false +# CHECK: process is valid: false +# CHECK: thread is valid: false +# CHECK: frame is valid: false Index: lldb/test/Shell/ScriptInterpreter/Java/fail_breakpoint_oneline.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/fail_breakpoint_oneline.test @@ -0,0 +1,4 @@ +# RUN: %lldb -s %s --script-language java 2>&1 | FileCheck %s +b main +breakpoint command add -s java -o '1234_foo' +# CHECK: error: Failed to execute '/compile' Index: lldb/test/Shell/ScriptInterpreter/Java/independent_state.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/independent_state.test @@ -0,0 +1,5 @@ +# RUN: %lldb --script-language java -s %S/Inputs/independent_state.in 2>&1 | FileCheck %s +# CHECK: 47 +# CHECK: 47 +# CHECK: 42 +# CHECK: 42 Index: lldb/test/Shell/ScriptInterpreter/Java/io.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/io.test @@ -0,0 +1,27 @@ +# UNSUPPORTED: lldb-repro +# +# RUN: rm -rf %t.stderr %t.stdout +# RUN: cat %s | %lldb --script-language java 2> %t.stderr > %t.stdout +# RUN: cat %t.stdout | FileCheck %s --check-prefix STDOUT +# RUN: cat %t.stderr | FileCheck %s --check-prefix STDERR +script +/main +/init +SBFile file = new SBFile(2, "w", false); +debugger.SetOutputFile(file); +System.out.println(95000 + 126); +/close +/run +quit +script +/main +System.err.println(95000 + 14); +/close +/run + +# STDOUT: 95126 +# STDERR: 95014 + +# RUN: rm -rf %t.stderr %t.stdout +# RUN: %lldb --script-language java -o 'script System.err.println(95000 + 126);' 2> %t.stderr > %t.stdout +# RUN: cat %t.stderr | FileCheck %s --check-prefix STDOUT Index: lldb/test/Shell/ScriptInterpreter/Java/java-python.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/java-python.test @@ -0,0 +1,20 @@ +# REQUIRES: python +# UNSUPPORTED: lldb-repro + +# RUN: mkdir -p %t +# RUN: cd %t +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o a.out +# RUN: cat %s | %lldb 2>&1 | FileCheck %s +script -l java -- +/main +/init +target = debugger.CreateTarget("a.out"); +System.out.println("target is valid: " + target.IsValid()); +debugger.SetSelectedTarget(target); +/close +/run +quit +# CHECK: target is valid: true +script -l python -- +print("selected target: {}".format(lldb.debugger.GetSelectedTarget())) +# CHECK: selected target: a.out Index: lldb/test/Shell/ScriptInterpreter/Java/java.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/java.test @@ -0,0 +1,6 @@ +# RUN: %lldb --script-language java -o 'script System.out.println(1000+100+10+1);' 2>&1 | FileCheck %s +# RUN: %lldb --script-language java -o 'script -- System.out.println(1000+100+10+1);' 2>&1 | FileCheck %s +# RUN: %lldb --script-language java -o 'script --language default -- System.out.println(1000+100+10+1);' 2>&1 | FileCheck %s +# RUN: %lldb -o 'script -l java -- System.out.println(1000+100+10+1);' 2>&1 | FileCheck %s +# RUN: %lldb -o 'script --language java -- System.out.println(1000+100+10+1);' 2>&1 | FileCheck %s +# CHECK: 1111 Index: lldb/test/Shell/ScriptInterpreter/Java/lit.local.cfg =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/lit.local.cfg @@ -0,0 +1,3 @@ +if 'java' not in config.available_features: + config.unsupported = True + Index: lldb/test/Shell/ScriptInterpreter/Java/nested_sessions.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/nested_sessions.test @@ -0,0 +1,10 @@ +# RUN: mkdir -p %t +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t/foo +# RUN: echo "int main() { return 0; }" | %clang_host -x c - -o %t/bar +# RUN: %lldb --script-language java -o "file %t/bar" -o "file %t/foo" -s %S/Inputs/nested_sessions.in -s %S/Inputs/nested_sessions_2.in 2>&1 | FileCheck %s +# CHECK: script +# CHECK: foo foo +# CHECK-NEXT: foo bar +# CHECK-NEXT: foo bar +# CHECK: script +# CHECK-NEXT: bar bar Index: lldb/test/Shell/ScriptInterpreter/Java/partial_statements.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/partial_statements.test @@ -0,0 +1,19 @@ +# RUN: %lldb -s %s --script-language java 2>&1 | FileCheck %s +script +/main +{ +int a = 123; +System.out.println(a); +} +/close +/run +/main +System.out.println(str); +} +static String str = "hello there!"; +} +/run +quit +# CHECK: 123 +# CHECK: hello there! +# CHECK-NOT: error Index: lldb/test/Shell/ScriptInterpreter/Java/persistent_state.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/persistent_state.test @@ -0,0 +1,2 @@ +# RUN: %lldb --script-language java -o 'script /shell int foo = 1010;' -o 'script /shell int bar = 101;' -o 'script /shell foo+bar;' 2>&1 | FileCheck %s +# CHECK: 1111 Index: lldb/test/Shell/ScriptInterpreter/Java/print.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/print.test @@ -0,0 +1,29 @@ +# UNSUPPORTED: lldb-repro +# +# RUN: rm -rf %t.stderr %t.stdout +# RUN: cat %s | %lldb --script-language java 2> %t.stderr > %t.stdout +# RUN: cat %t.stdout | FileCheck %s --check-prefix STDOUT +# RUN: cat %t.stderr | FileCheck %s --check-prefix STDERR +script +/main +/init +SBFile file = new SBFile(2, "w", false); +debugger.SetOutputFile(file); +System.out.println(95000 + 126); +/close +/run +quit +script +/main +/init +System.err.println("SBD@"+debugger.GetID()); +/close +/run +quit + +# STDOUT: 95126 +# STDERR: SBD@1 + +# RUN: rm -rf %t.stderr %t.stdout +# RUN: %lldb --script-language java -o 'script System.out.println(95000 + 126);' 2> %t.stderr > %t.stdout +# RUN: cat %t.stdout | FileCheck %s --check-prefix STDOUT Index: lldb/test/Shell/ScriptInterpreter/Java/quit.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/quit.test @@ -0,0 +1,9 @@ +# UNSUPPORTED: lldb-repro +# +# RUN: cat %s | %lldb --script-language java 2>&1 | FileCheck %s +script +System.out.println(95000 + 126); +quit +target list +# CHECK: 95126 +# CHECK: No targets Index: lldb/test/Shell/ScriptInterpreter/Java/watchpoint_callback.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Java/watchpoint_callback.test @@ -0,0 +1,32 @@ +# XFAIL: system-netbsd +# RUN: echo "int main() { int val = 1; val++; return 0; }" | %clang_host -x c - -g -o %t +# RUN: %lldb -s %s --script-language java %t 2>&1 | FileCheck %s +b main +r +watchpoint set variable val +watchpoint command add -s java +System.out.println("val=" + frame.FindVariable("val").GetValue()); +quit +c +# CHECK: val=1 +# CHECK: val=2 +# CHECK: Process {{[0-9]+}} exited +r +watchpoint set variable val +watchpoint modify 1 -c "(val == 1)" +watchpoint command add -s java +System.out.println("conditional watchpoint"); +SBWatchpoint wpx = target.FindWatchpointByID(wp.GetID()); +wpx.SetEnabled(false); +quit +c +# CHECK-COUNT-1: conditional watchpoint +# CHECK: Process {{[0-9]+}} exited +r +watchpoint set expr 0x00 +watchpoint command add -s java +System.out.println("never triggers"); +quit +c +# CHECK-NOT: never triggers +# CHECK: Process {{[0-9]+}} exited Index: lldb/test/Shell/lit.cfg.py =================================================================== --- lldb/test/Shell/lit.cfg.py +++ lldb/test/Shell/lit.cfg.py @@ -45,6 +45,9 @@ 'TEMP', 'TMP', 'XDG_CACHE_HOME', + 'JAVA_HOME', + 'CLASSPATH', + 'LLVM_SYMBOLIZER_PATH', ]) # Support running the test suite under the lldb-repro wrapper. This makes it @@ -118,6 +121,9 @@ if config.lldb_enable_lua: config.available_features.add('lua') +if config.lldb_enable_java: + config.available_features.add('java') + if config.lldb_enable_lzma: config.available_features.add('lzma') Index: lldb/test/Shell/lit.site.cfg.py.in =================================================================== --- lldb/test/Shell/lit.site.cfg.py.in +++ lldb/test/Shell/lit.site.cfg.py.in @@ -21,6 +21,7 @@ config.lldb_bitness = 64 if @LLDB_IS_64_BITS@ else 32 config.lldb_enable_python = @LLDB_ENABLE_PYTHON@ config.lldb_enable_lua = @LLDB_ENABLE_LUA@ +config.lldb_enable_java = @LLDB_ENABLE_JAVA@ config.lldb_build_directory = "@LLDB_TEST_BUILD_DIRECTORY@" config.lldb_system_debugserver = @LLDB_USE_SYSTEM_DEBUGSERVER@ # The shell tests use their own module caches. Index: lldb/test/Unit/lit.cfg.py =================================================================== --- lldb/test/Unit/lit.cfg.py +++ lldb/test/Unit/lit.cfg.py @@ -29,6 +29,9 @@ 'TEMP', 'TMP', 'XDG_CACHE_HOME', + 'JAVA_HOME', + 'CLASSPATH', + 'LLVM_SYMBOLIZER_PATH', ]) llvm_config.with_environment('PATH', os.path.dirname(sys.executable), Index: lldb/unittests/ScriptInterpreter/CMakeLists.txt =================================================================== --- lldb/unittests/ScriptInterpreter/CMakeLists.txt +++ lldb/unittests/ScriptInterpreter/CMakeLists.txt @@ -4,3 +4,6 @@ if (LLDB_ENABLE_LUA) add_subdirectory(Lua) endif() +if (LLDB_ENABLE_JAVA) + add_subdirectory(Java) +endif() Index: lldb/unittests/ScriptInterpreter/Java/CMakeLists.txt =================================================================== --- /dev/null +++ lldb/unittests/ScriptInterpreter/Java/CMakeLists.txt @@ -0,0 +1,12 @@ +add_lldb_unittest(ScriptInterpreterJavaTests + JavaTests.cpp + ScriptInterpreterTests.cpp + + LINK_LIBS + lldbHost + lldbPluginScriptInterpreterJava + lldbPluginPlatformLinux + LLVMTestingSupport + LINK_COMPONENTS + Support + ) Index: lldb/unittests/ScriptInterpreter/Java/JavaTests.cpp =================================================================== --- /dev/null +++ lldb/unittests/ScriptInterpreter/Java/JavaTests.cpp @@ -0,0 +1,64 @@ +//===-- JavaTests.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 "Plugins/ScriptInterpreter/Java/Java.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +extern "C" int javaopen_lldb(JNIEnv *L) { return 0; } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" + +// Disable warning C4190: 'LLDBSwigPythonBreakpointCallbackFunction' has +// C-linkage specified, but returns UDT 'llvm::Expected' which is +// incompatible with C +#if _MSC_VER +#pragma warning (push) +#pragma warning (disable : 4190) +#endif + +extern "C" llvm::Expected LLDBSwigJavaBreakpointCallbackFunction( + JNIEnv *L, lldb::StackFrameSP stop_frame_sp, + lldb::BreakpointLocationSP bp_loc_sp, StructuredDataImpl *extra_args_impl) { + return false; +} + +extern "C" llvm::Expected LLDBSwigJavaWatchpointCallbackFunction( + JNIEnv *L, lldb::StackFrameSP stop_frame_sp, lldb::WatchpointSP wp_sp) { + return false; +} + +#if _MSC_VER +#pragma warning (pop) +#endif + +#pragma clang diagnostic pop + +TEST(JavaTest, RunValid) { + Java java; + llvm::Error error = java.Test("int foo = 1;"); + EXPECT_FALSE(static_cast(error)); +} + +TEST(JavaTest, RunError) { + Java java; + llvm::Error error = java.RunError("ERROR"); + EXPECT_TRUE(static_cast(error)); + EXPECT_EQ(llvm::toString(std::move(error)),"ERROR\n"); +} + +TEST(JavaTest, RunInvalid) { + Java java; + llvm::Error error = java.Test("foo == 1"); + EXPECT_TRUE(static_cast(error)); + EXPECT_EQ(llvm::toString(std::move(error)), "Failed to execute '/run'"); +} + Index: lldb/unittests/ScriptInterpreter/Java/ScriptInterpreterTests.cpp =================================================================== --- /dev/null +++ lldb/unittests/ScriptInterpreter/Java/ScriptInterpreterTests.cpp @@ -0,0 +1,59 @@ +//===-- JavaTests.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 "Plugins/Platform/Linux/PlatformLinux.h" +#include "Plugins/ScriptInterpreter/Java/ScriptInterpreterJava.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Target/Platform.h" +#include "lldb/Utility/Reproducer.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb_private::repro; +using namespace lldb; + +namespace { +class ScriptInterpreterTest : public ::testing::Test { +public: + void SetUp() override { + llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None)); + FileSystem::Initialize(); + HostInfo::Initialize(); + + // Pretend Linux is the host platform. + platform_linux::PlatformLinux::Initialize(); + ArchSpec arch("powerpc64-pc-linux"); + Platform::SetHostPlatform( + platform_linux::PlatformLinux::CreateInstance(true, &arch)); + } + void TearDown() override { + platform_linux::PlatformLinux::Terminate(); + HostInfo::Terminate(); + FileSystem::Terminate(); + Reproducer::Terminate(); + } +}; +} // namespace + +TEST_F(ScriptInterpreterTest, ExecuteOneLine) { + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + + ScriptInterpreterJava script_interpreter(*debugger_sp); + //if (llvm::Error e = script_interpreter.EnterSession(debugger_sp->GetID())) { + // return; + //} + CommandReturnObject result(/*colors*/ false); + EXPECT_TRUE(script_interpreter.ExecuteOneLine("System.err.println(args);", &result)); + EXPECT_FALSE(script_interpreter.ExecuteOneLine("nil = foo", &result)); + EXPECT_TRUE(result.GetErrorData().startswith( + "error: java failed attempting to evaluate 'nil = foo'")); +}