diff options
Diffstat (limited to 'lldb')
559 files changed, 20831 insertions, 4545 deletions
diff --git a/lldb/CMakeLists.txt b/lldb/CMakeLists.txt index 01b5546..0736e6b 100644 --- a/lldb/CMakeLists.txt +++ b/lldb/CMakeLists.txt @@ -62,11 +62,16 @@ if (LLDB_ENABLE_PYTHON) set(cachestring_LLDB_PYTHON_EXT_SUFFIX "Filename extension for native code python modules") + if (LLDB_ENABLE_PYTHON_LIMITED_API) + set(stable_abi "--stable-abi") + endif() + foreach(var LLDB_PYTHON_RELATIVE_PATH LLDB_PYTHON_EXE_RELATIVE_PATH LLDB_PYTHON_EXT_SUFFIX) if(NOT DEFINED ${var} AND NOT CMAKE_CROSSCOMPILING) execute_process( COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings/python/get-python-config.py + ${stable_abi} ${var} OUTPUT_VARIABLE value OUTPUT_STRIP_TRAILING_WHITESPACE) diff --git a/lldb/bindings/interface/SBFrameExtensions.i b/lldb/bindings/interface/SBFrameExtensions.i index 38d03ab..0c79b98 100644 --- a/lldb/bindings/interface/SBFrameExtensions.i +++ b/lldb/bindings/interface/SBFrameExtensions.i @@ -24,6 +24,13 @@ STRING_EXTENSION_OUTSIDE(SBFrame) else: return SBFrame() + def get_child_frame(self): + child_idx = self.idx - 1 + if child_idx >= 0: + return self.thread.frame[child_idx] + else: + return SBFrame() + def get_arguments(self): return self.GetVariables(True,False,False,False) @@ -92,6 +99,7 @@ STRING_EXTENSION_OUTSIDE(SBFrame) register = property(get_registers_access, None, doc='''A read only property that returns an helper object providing a flattened indexable view of the CPU registers for this stack frame.''') reg = property(get_registers_access, None, doc='''A read only property that returns an helper object providing a flattened indexable view of the CPU registers for this stack frame''') parent = property(get_parent_frame, None, doc='''A read only property that returns the parent (caller) frame of the current frame.''') + child = property(get_child_frame, None, doc='''A read only property that returns the child (callee) frame of the current frame.''') %} #endif } diff --git a/lldb/bindings/interface/SBFrameListExtensions.i b/lldb/bindings/interface/SBFrameListExtensions.i new file mode 100644 index 0000000..1c6ac8d --- /dev/null +++ b/lldb/bindings/interface/SBFrameListExtensions.i @@ -0,0 +1,41 @@ +%extend lldb::SBFrameList { + +#ifdef SWIGPYTHON + %nothreadallow; +#endif + std::string lldb::SBFrameList::__str__ (){ + lldb::SBStream description; + if (!$self->GetDescription(description)) + return std::string("<empty> lldb.SBFrameList()"); + const char *desc = description.GetData(); + size_t desc_len = description.GetSize(); + if (desc_len > 0 && (desc[desc_len-1] == '\n' || desc[desc_len-1] == '\r')) + --desc_len; + return std::string(desc, desc_len); + } +#ifdef SWIGPYTHON + %clearnothreadallow; +#endif + +#ifdef SWIGPYTHON + %pythoncode %{ + def __iter__(self): + '''Iterate over all frames in a lldb.SBFrameList object.''' + return lldb_iter(self, 'GetSize', 'GetFrameAtIndex') + + def __len__(self): + return int(self.GetSize()) + + def __getitem__(self, key): + if type(key) is not int: + return None + if key < 0: + count = len(self) + if -count <= key < count: + key %= count + + frame = self.GetFrameAtIndex(key) + return frame if frame.IsValid() else None + %} +#endif +} diff --git a/lldb/bindings/interface/SBSectionDocstrings.i b/lldb/bindings/interface/SBSectionDocstrings.i index 231e9e8..9c9cb813 100644 --- a/lldb/bindings/interface/SBSectionDocstrings.i +++ b/lldb/bindings/interface/SBSectionDocstrings.i @@ -4,7 +4,7 @@ SBSection supports iteration through its subsection, represented as SBSection as well. For example, :: - for sec in exe_module: + for sec in exe_module.section_iter(): if sec.GetName() == '__TEXT': print sec break diff --git a/lldb/bindings/interface/SBTargetExtensions.i b/lldb/bindings/interface/SBTargetExtensions.i index 43125d8..ef1093b 100644 --- a/lldb/bindings/interface/SBTargetExtensions.i +++ b/lldb/bindings/interface/SBTargetExtensions.i @@ -190,6 +190,7 @@ STRING_EXTENSION_LEVEL_OUTSIDE(SBTarget, lldb::eDescriptionLevelBrief) byte_order = property(GetByteOrder, None, doc='''A read only property that returns an lldb enumeration value (lldb.eByteOrderLittle, lldb.eByteOrderBig, lldb.eByteOrderInvalid) that represents the byte order for this target.''') addr_size = property(GetAddressByteSize, None, doc='''A read only property that returns the size in bytes of an address for this target.''') triple = property(GetTriple, None, doc='''A read only property that returns the target triple (arch-vendor-os) for this target as a string.''') + arch_name = property(GetArchName, None, doc='''A read only property that returns the architecture name for this target as a string.''') data_byte_size = property(GetDataByteSize, None, doc='''A read only property that returns the size in host bytes of a byte in the data address space for this target.''') code_byte_size = property(GetCodeByteSize, None, doc='''A read only property that returns the size in host bytes of a byte in the code address space for this target.''') platform = property(GetPlatform, None, doc='''A read only property that returns the platform associated with with this target.''') diff --git a/lldb/bindings/interface/SBThreadExtensions.i b/lldb/bindings/interface/SBThreadExtensions.i index 4ec9f10..c9ae410 100644 --- a/lldb/bindings/interface/SBThreadExtensions.i +++ b/lldb/bindings/interface/SBThreadExtensions.i @@ -41,7 +41,8 @@ STRING_EXTENSION_OUTSIDE(SBThread) def get_thread_frames(self): '''An accessor function that returns a list() that contains all frames in a lldb.SBThread object.''' frames = [] - for frame in self: + frame_list = self.GetFrames() + for frame in frame_list: frames.append(frame) return frames diff --git a/lldb/bindings/interfaces.swig b/lldb/bindings/interfaces.swig index b3d4497..fddbedf 100644 --- a/lldb/bindings/interfaces.swig +++ b/lldb/bindings/interfaces.swig @@ -119,6 +119,7 @@ %include "lldb/API/SBFileSpecList.h" %include "lldb/API/SBFormat.h" %include "lldb/API/SBFrame.h" +%include "lldb/API/SBFrameList.h" %include "lldb/API/SBFunction.h" %include "lldb/API/SBHostOS.h" %include "lldb/API/SBInstruction.h" @@ -193,6 +194,7 @@ %include "./interface/SBFileSpecExtensions.i" %include "./interface/SBFileSpecListExtensions.i" %include "./interface/SBFrameExtensions.i" +%include "./interface/SBFrameListExtensions.i" %include "./interface/SBFunctionExtensions.i" %include "./interface/SBInstructionExtensions.i" %include "./interface/SBInstructionListExtensions.i" diff --git a/lldb/bindings/lua/lua-typemaps.swig b/lldb/bindings/lua/lua-typemaps.swig index f2a74014..51a9247 100644 --- a/lldb/bindings/lua/lua-typemaps.swig +++ b/lldb/bindings/lua/lua-typemaps.swig @@ -122,9 +122,9 @@ LLDB_NUMBER_TYPEMAP(enum SWIGTYPE); } // Disable default type checking for this method to avoid SWIG dispatch issues. -// +// // Problem: SBThread::GetStopDescription has two overloads: -// 1. GetStopDescription(char* dst_or_null, size_t dst_len) +// 1. GetStopDescription(char* dst_or_null, size_t dst_len) // 2. GetStopDescription(lldb::SBStream& stream) // // SWIG generates a dispatch function to select the correct overload based on argument types. @@ -132,9 +132,9 @@ LLDB_NUMBER_TYPEMAP(enum SWIGTYPE); // However, this dispatcher doesn't consider typemaps that transform function signatures. // // In lua, our typemap converts GetStopDescription(char*, size_t) to GetStopDescription(int). -// The dispatcher still checks against the original (char*, size_t) signature instead of +// The dispatcher still checks against the original (char*, size_t) signature instead of // the transformed (int) signature, causing type matching to fail. -// This only affects SBThread::GetStopDescription since the type check also matches +// This only affects SBThread::GetStopDescription since the type check also matches // the argument name, which is unique to this function. %typemap(typecheck, precedence=SWIG_TYPECHECK_POINTER) (char *dst_or_null, size_t dst_len) "" @@ -251,7 +251,8 @@ LLDB_NUMBER_TYPEMAP(enum SWIGTYPE); %typemap(in) lldb::FileSP { luaL_Stream *p = (luaL_Stream *)luaL_checkudata(L, $input, LUA_FILEHANDLE); lldb::FileSP file_sp; - file_sp = std::make_shared<lldb_private::NativeFile>(p->f, false); + file_sp = std::make_shared<lldb_private::NativeFile>( + p->f, lldb_private::NativeFile::eOpenOptionWriteOnly, false); if (!file_sp->IsValid()) return luaL_error(L, "Invalid file"); $1 = file_sp; diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt index ef6def3..2ebcf5a 100644 --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -60,8 +60,10 @@ endfunction() function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_target_dir) # Add a Post-Build Event to copy over Python files and create the symlink to # liblldb.so for the Python API(hardlink on Windows). + # Note that Swig-generated code is located one level deeper in the `native` + # module, in order to avoid cyclic importing. add_custom_target(${swig_target} ALL VERBATIM - COMMAND ${CMAKE_COMMAND} -E make_directory ${lldb_python_target_dir} + COMMAND ${CMAKE_COMMAND} -E make_directory ${lldb_python_target_dir}/native/ DEPENDS ${lldb_python_bindings_dir}/lldb.py COMMENT "Python script sym-linking LLDB Python API") @@ -75,6 +77,8 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar "${LLDB_SOURCE_DIR}/source/Interpreter/embedded_interpreter.py" "${lldb_python_target_dir}") + create_python_package(${swig_target} ${lldb_python_target_dir} "native" FILES) + # Distribute the examples as python packages. create_python_package( ${swig_target} @@ -107,6 +111,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar "plugins" FILES "${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py" + "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py" "${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py" @@ -142,7 +147,7 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar endif() set(LIBLLDB_SYMLINK_OUTPUT_FILE "_lldb${LLDB_PYTHON_EXT_SUFFIX}") create_relative_symlink(${swig_target} ${LIBLLDB_SYMLINK_DEST} - ${lldb_python_target_dir} ${LIBLLDB_SYMLINK_OUTPUT_FILE}) + ${lldb_python_target_dir}/native/ ${LIBLLDB_SYMLINK_OUTPUT_FILE}) if (NOT WIN32) diff --git a/lldb/bindings/python/get-python-config.py b/lldb/bindings/python/get-python-config.py index ae84cbb..bf8cc48 100755 --- a/lldb/bindings/python/get-python-config.py +++ b/lldb/bindings/python/get-python-config.py @@ -18,6 +18,9 @@ def relpath_nodots(path, base): def main(): parser = argparse.ArgumentParser(description="extract cmake variables from python") parser.add_argument("variable_name") + parser.add_argument( + "--stable-abi", action="store_true", help="Target the Stable C ABI" + ) args = parser.parse_args() if args.variable_name == "LLDB_PYTHON_RELATIVE_PATH": # LLDB_PYTHON_RELATIVE_PATH is the relative path from lldb's prefix @@ -68,7 +71,10 @@ def main(): print("sys.prefix:", sys.prefix, file=sys.stderr) sys.exit(1) elif args.variable_name == "LLDB_PYTHON_EXT_SUFFIX": - print(sysconfig.get_config_var("EXT_SUFFIX")) + if args.stable_abi: + print(".abi3%s" % sysconfig.get_config_var("SHLIB_SUFFIX")) + else: + print(sysconfig.get_config_var("EXT_SUFFIX")) else: parser.error(f"unknown variable {args.variable_name}") diff --git a/lldb/bindings/python/python-swigsafecast.swig b/lldb/bindings/python/python-swigsafecast.swig index 3ea24f1..a86dc44 100644 --- a/lldb/bindings/python/python-swigsafecast.swig +++ b/lldb/bindings/python/python-swigsafecast.swig @@ -37,6 +37,11 @@ PythonObject SWIGBridge::ToSWIGWrapper(lldb::ThreadPlanSP thread_plan_sp) { SWIGTYPE_p_lldb__SBThreadPlan); } +PythonObject SWIGBridge::ToSWIGWrapper(lldb::StackFrameListSP frames_sp) { + return ToSWIGHelper(new lldb::SBFrameList(std::move(frames_sp)), + SWIGTYPE_p_lldb__SBFrameList); +} + PythonObject SWIGBridge::ToSWIGWrapper(lldb::BreakpointSP breakpoint_sp) { return ToSWIGHelper(new lldb::SBBreakpoint(std::move(breakpoint_sp)), SWIGTYPE_p_lldb__SBBreakpoint); diff --git a/lldb/bindings/python/python-typemaps.h b/lldb/bindings/python/python-typemaps.h deleted file mode 100644 index 8a533e8..0000000 --- a/lldb/bindings/python/python-typemaps.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef LLDB_BINDINGS_PYTHON_PYTHON_TYPEMAPS_H -#define LLDB_BINDINGS_PYTHON_PYTHON_TYPEMAPS_H - -#include <Python.h> - -// Defined here instead of a .swig file because SWIG 2 doesn't support -// explicit deleted functions. -struct Py_buffer_RAII { - Py_buffer buffer = {}; - Py_buffer_RAII(){}; - Py_buffer &operator=(const Py_buffer_RAII &) = delete; - Py_buffer_RAII(const Py_buffer_RAII &) = delete; - ~Py_buffer_RAII() { - if (buffer.obj) - PyBuffer_Release(&buffer); - } -}; - -#endif // LLDB_BINDINGS_PYTHON_PYTHON_TYPEMAPS_H diff --git a/lldb/bindings/python/python-typemaps.swig b/lldb/bindings/python/python-typemaps.swig index 4d3a957..072e688 100644 --- a/lldb/bindings/python/python-typemaps.swig +++ b/lldb/bindings/python/python-typemaps.swig @@ -6,12 +6,6 @@ AND call SWIG_fail at the same time, because it will result in a double free. */ -%inline %{ - -#include "../bindings/python/python-typemaps.h" - -%} - %typemap(in) char ** { /* Check if is a list */ if (PythonList::Check($input)) { @@ -634,49 +628,34 @@ template <> bool SetNumberFromPyObject<double>(double &number, PyObject *obj) { } } -// These two pybuffer macros are copied out of swig/Lib/python/pybuffer.i, -// and fixed so they will not crash if PyObject_GetBuffer fails. -// https://github.com/swig/swig/issues/1640 -// -// I've also moved the call to PyBuffer_Release to the end of the SWIG wrapper, -// doing it right away is not legal according to the python buffer protocol. - -%define %pybuffer_mutable_binary(TYPEMAP, SIZE) -%typemap(in) (TYPEMAP, SIZE) (Py_buffer_RAII view) { - int res; - Py_ssize_t size = 0; - void *buf = 0; - res = PyObject_GetBuffer($input, &view.buffer, PyBUF_WRITABLE); - if (res < 0) { - PyErr_Clear(); - %argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum); - } - size = view.buffer.len; - buf = view.buffer.buf; - $1 = ($1_ltype)buf; - $2 = ($2_ltype)(size / sizeof($*1_type)); -} -%enddef - -%define %pybuffer_binary(TYPEMAP, SIZE) -%typemap(in) (TYPEMAP, SIZE) (Py_buffer_RAII view) { - int res; - Py_ssize_t size = 0; - const void *buf = 0; - res = PyObject_GetBuffer($input, &view.buffer, PyBUF_CONTIG_RO); - if (res < 0) { - PyErr_Clear(); - %argument_fail(res, "(TYPEMAP, SIZE)", $symname, $argnum); + +// Typemap for SBFile::Write. +%typemap(in) (const uint8_t *buf, size_t num_bytes) { + if (PythonByteArray::Check($input)) { + PythonByteArray bytearray(PyRefType::Borrowed, $input); + $1 = (uint8_t *)bytearray.GetBytes().data(); + $2 = bytearray.GetSize(); + } else if (PythonBytes::Check($input)) { + PythonBytes bytes(PyRefType::Borrowed, $input); + $1 = (uint8_t *)bytes.GetBytes().data(); + $2 = bytes.GetSize(); + } else { + PyErr_SetString(PyExc_ValueError, "Expecting a bytes or bytearray object"); + SWIG_fail; } - size = view.buffer.len; - buf = view.buffer.buf; - $1 = ($1_ltype)buf; - $2 = ($2_ltype)(size / sizeof($*1_type)); } -%enddef -%pybuffer_binary(const uint8_t *buf, size_t num_bytes); -%pybuffer_mutable_binary(uint8_t *buf, size_t num_bytes); +// Typemap for SBFile::Read. +%typemap(in) (uint8_t *buf, size_t num_bytes) { + if (PythonByteArray::Check($input)) { + PythonByteArray bytearray(PyRefType::Borrowed, $input); + $1 = (uint8_t *)bytearray.GetBytes().data(); + $2 = bytearray.GetSize(); + } else { + PyErr_SetString(PyExc_ValueError, "Expecting a bytearray"); + SWIG_fail; + } +} %typemap(in) (const char **symbol_name, uint32_t num_names) { using namespace lldb_private; diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index e7acba5..0ba1521 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -128,8 +128,11 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallTypeScript( PyObject *pfunc_impl = nullptr; - if (pyfunct_wrapper && *pyfunct_wrapper && - PyFunction_Check(*pyfunct_wrapper)) { + if (pyfunct_wrapper && *pyfunct_wrapper +#ifndef Py_LIMITED_API + && PyFunction_Check(*pyfunct_wrapper) +#endif + ) { pfunc_impl = (PyObject *)(*pyfunct_wrapper); if (pfunc_impl->ob_refcnt == 1) { Py_XDECREF(pfunc_impl); @@ -422,6 +425,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBBreakpoint(PyObject * return sb_ptr; } +void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject * data) { + lldb::SBThread *sb_ptr = nullptr; + + int valid_cast = + SWIG_ConvertPtr(data, (void **)&sb_ptr, SWIGTYPE_p_lldb__SBThread, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject * data) { lldb::SBFrame *sb_ptr = nullptr; @@ -556,6 +571,18 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext(PyOb return sb_ptr; } +void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data) { + lldb::SBFrameList *sb_ptr = NULL; + + int valid_cast = SWIG_ConvertPtr(data, (void **)&sb_ptr, + SWIGTYPE_p_lldb__SBFrameList, 0); + + if (valid_cast == -1) + return NULL; + + return sb_ptr; +} + bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommand( const char *python_function_name, const char *session_dictionary_name, lldb::DebuggerSP debugger, const char *args, diff --git a/lldb/bindings/python/python.swig b/lldb/bindings/python/python.swig index 4a5a39d..3d2caa6 100644 --- a/lldb/bindings/python/python.swig +++ b/lldb/bindings/python/python.swig @@ -50,7 +50,12 @@ Older swig versions will simply ignore this setting. import $module except ImportError: # Relative import should work if we are being loaded by Python. - from . import $module" + # The cpython module built by swig is pushed one level down into + # the native submodule, because at this point the interpreter + # is still constructing the lldb module itself. + # Simply importing anything using `from . import` constitutes + # a cyclic importing. + from .native import $module" %enddef // The name of the module to be created. @@ -59,6 +64,11 @@ except ImportError: // Parameter types will be used in the autodoc string. %feature("autodoc", "1"); +// Include lldb-python first as it sets Py_LIMITED_API. +%begin %{ +#include "../source/Plugins/ScriptInterpreter/Python/lldb-python.h" +%} + %pythoncode%{ import uuid import re diff --git a/lldb/cmake/modules/AddLLDB.cmake b/lldb/cmake/modules/AddLLDB.cmake index 5d58abf..6493df2 100644 --- a/lldb/cmake/modules/AddLLDB.cmake +++ b/lldb/cmake/modules/AddLLDB.cmake @@ -172,6 +172,7 @@ function(add_lldb_executable name) if(NOT LIBLLDB_INDEX EQUAL -1) if (MSVC) target_link_options(${name} PRIVATE "/DELAYLOAD:$<TARGET_FILE_NAME:liblldb>") + target_link_libraries(${name} PRIVATE delayimp) elseif (MINGW AND LINKER_IS_LLD) # LLD can delay load just by passing a --delayload flag, as long as the import # library is a short type import library (which LLD and MS link.exe produce). diff --git a/lldb/cmake/modules/LLDBConfig.cmake b/lldb/cmake/modules/LLDBConfig.cmake index 4b568d2..0d62c32 100644 --- a/lldb/cmake/modules/LLDBConfig.cmake +++ b/lldb/cmake/modules/LLDBConfig.cmake @@ -180,13 +180,35 @@ if (LLDB_ENABLE_PYTHON) "Path to use as PYTHONHOME in lldb. If a relative path is specified, it will be resolved at runtime relative to liblldb directory.") endif() - if (SWIG_VERSION VERSION_GREATER_EQUAL "4.2" AND NOT LLDB_EMBED_PYTHON_HOME) + # Enable targeting the Python Limited C API. + set(PYTHON_LIMITED_API_MIN_SWIG_VERSION "4.2") + if (SWIG_VERSION VERSION_EQUAL "4.4.0" AND Python3_VERSION VERSION_GREATER_EQUAL "3.13") + set(AFFECTED_BY_SWIG_BUG TRUE) + else() + set(AFFECTED_BY_SWIG_BUG FALSE) + endif() + + if (SWIG_VERSION VERSION_GREATER_EQUAL PYTHON_LIMITED_API_MIN_SWIG_VERSION + AND NOT LLDB_EMBED_PYTHON_HOME AND NOT AFFECTED_BY_SWIG_BUG) set(default_enable_python_limited_api ON) else() set(default_enable_python_limited_api OFF) endif() + option(LLDB_ENABLE_PYTHON_LIMITED_API "Force LLDB to only use the Python Limited API (requires SWIG 4.2 or later)" ${default_enable_python_limited_api}) + + # Diagnose unsupported configurations. + if (LLDB_ENABLE_PYTHON_LIMITED_API AND AFFECTED_BY_SWIG_BUG) + message(SEND_ERROR "LLDB_ENABLE_PYTHON_LIMITED_API is not compatible with SWIG 4.4.0 and Python >= 3.13 due to a bug in SWIG: https://github.com/swig/swig/issues/3283") + endif() + if (LLDB_ENABLE_PYTHON_LIMITED_API AND LLDB_EMBED_PYTHON_HOME) + message(SEND_ERROR "LLDB_ENABLE_PYTHON_LIMITED_API is not compatible with LLDB_EMBED_PYTHON_HOME") + endif() + if (LLDB_ENABLE_PYTHON_LIMITED_API AND SWIG_VERSION VERSION_LESS PYTHON_LIMITED_API_MIN_SWIG_VERSION) + message(SEND_ERROR "LLDB_ENABLE_PYTHON_LIMITED_API is not compatible with SWIG ${SWIG_VERSION} (requires SWIG ${PYTHON_LIMITED_API_MIN_SWIG_VERSION})") + endif() + else() # Even if Python scripting is disabled, we still need a Python interpreter to # build, for example to generate SBLanguages.h. diff --git a/lldb/docs/CMakeLists.txt b/lldb/docs/CMakeLists.txt index f1664a6..bbecf60 100644 --- a/lldb/docs/CMakeLists.txt +++ b/lldb/docs/CMakeLists.txt @@ -28,13 +28,14 @@ if (LLDB_ENABLE_PYTHON AND SPHINX_FOUND) add_custom_target(lldb-python-doc-package COMMAND "${CMAKE_COMMAND}" -E copy "${lldb_bindings_dir}/lldb.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/__init__.py" COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins" + COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_frame_provider.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMAND "${CMAKE_COMMAND}" -E copy "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_thread_plan.py" "${CMAKE_CURRENT_BINARY_DIR}/lldb/plugins/" COMMENT "Copying lldb.py to pretend its a Python package.") - add_dependencies(lldb-python-doc-package swig_wrapper_python lldb-python) + add_dependencies(lldb-python-doc-package swig_wrapper_python) # FIXME: Don't treat Sphinx warnings as errors. The files generated by # automodapi are full of warnings (partly caused by SWIG, our documentation diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf index 70eda3b..5fabdd4 100644 --- a/lldb/docs/dil-expr-lang.ebnf +++ b/lldb/docs/dil-expr-lang.ebnf @@ -3,12 +3,15 @@ (* This is currently a subset of the final DIL Language, matching the current DIL implementation. *) -expression = unary_expression ; +expression = cast_expression; + +cast_expression = unary_expression + | "(" type_id ")" cast_expression; unary_expression = postfix_expression - | unary_operator expression ; + | unary_operator cast_expression ; -unary_operator = "*" | "&" ; +unary_operator = "*" | "&" | "+" | "-"; postfix_expression = primary_expression | postfix_expression "[" integer_literal "]" @@ -44,10 +47,28 @@ nested_name_specifier = type_name "::" | namespace_name '::' | nested_name_specifier identifier "::" ; +type_id = type_specifier_seq [abstract_declarator] ; + +type_specifier_seq = type_specifier [type_specifier]; + +type_specifier = ["::"] [nested_name_specifier] type_name + | builtin_typename ; + +nested_name_specifier = type_name "::" + | namespace_name "::" + | nested_name_specifier identifier "::" ; + +abstract_declarator = ptr_operator [abstract_declarator] ; + +ptr_operator = "*" + | "&"; + type_name = class_name | enum_name | typedef_name; +builtin_typename = identifier_seq; + class_name = identifier ; enum_name = identifier ; @@ -56,6 +77,7 @@ typedef_name = identifier ; namespace_name = identifier ; - +identifier_seq = identifier + | identifier identifier_seq; diff --git a/lldb/docs/python_extensions.rst b/lldb/docs/python_extensions.rst index 7e5f1ba..8420187 100644 --- a/lldb/docs/python_extensions.rst +++ b/lldb/docs/python_extensions.rst @@ -14,6 +14,14 @@ Operating System Thread Plugins :skip: ScriptedThread :no-inheritance-diagram: +Scripted Frame Provider Plugins +------------------------------- + +.. automodapi:: lldb.plugins.scripted_frame_provider + :no-heading: + :skip: ABCMeta + :no-inheritance-diagram: + Scripted Process Plugins ------------------------------- diff --git a/lldb/docs/resources/build.rst b/lldb/docs/resources/build.rst index 2eb1677..9f76b3a 100644 --- a/lldb/docs/resources/build.rst +++ b/lldb/docs/resources/build.rst @@ -104,7 +104,7 @@ Build Requirements Please follow the steps below if you only want to **build** lldb. -1. Install `Visual Studio <https://visualstudio.microsoft.com>` with the +1. Install `Visual Studio <https://visualstudio.microsoft.com>`_ with the "Desktop Development with C++" workload. Make sure that the latest Windows SDK and the Active Template Library (ATL) are installed. 2. Install `Git Bash <https://git-scm.com/install/windows>`_ and add diff --git a/lldb/docs/resources/contributing.rst b/lldb/docs/resources/contributing.rst index 7e84df4..8da8768 100644 --- a/lldb/docs/resources/contributing.rst +++ b/lldb/docs/resources/contributing.rst @@ -90,19 +90,19 @@ dependency graph manageable, we still have some rules to govern these relationships: * All dependencies between plugins of the same kind must flow in the same - direction (if plugin `A1` depends on plugin `B1`, then `B2` must not depend on - `A2`) + direction (if plugin ``A1`` depends on plugin ``B1``, then ``B2`` must not depend on + ``A2``) * Dependency graph of plugin kinds must not contain loops (dependencies like - `A1->B1`, `B2->C2` and `C3->A3` are forbidden because they induce a cycle in + ``A1->B1``, ``B2->C2`` and ``C3->A3`` are forbidden because they induce a cycle in the plugin kind graph even though the plugins themselves are acyclical) The first of these rules is checked via CMake scripts (using the -`LLDB_ACCEPTABLE_PLUGIN_DEPENDENCIES` property). Dependencies in this category +``LLDB_ACCEPTABLE_PLUGIN_DEPENDENCIES`` property). Dependencies in this category are expected and permitted (subject to other constraints such as that dependency making sense for the particular pair of plugins). Unfortunately, due to historic reasons, not all plugin dependencies follow this rule, which is why we have -another category called `LLDB_TOLERATED_PLUGIN_DEPENDENCIES`. New dependencies +another category called ``LLDB_TOLERATED_PLUGIN_DEPENDENCIES``. New dependencies are forbidden (even though they are accepted by CMake) and existing ones should be removed wherever possible. diff --git a/lldb/docs/resources/lldbgdbremote.md b/lldb/docs/resources/lldbgdbremote.md index 032edb6..fdd9b05 100644 --- a/lldb/docs/resources/lldbgdbremote.md +++ b/lldb/docs/resources/lldbgdbremote.md @@ -2508,7 +2508,7 @@ stack traces. Get the value of a Wasm global variable for the given frame index at the given variable index. The indexes are encoded as base 10. The result is a hex-encoded -address from where to read the value. +little-endian value of the global. ``` send packet: $qWasmGlobal:0;2#cb @@ -2523,7 +2523,7 @@ variables. Get the value of a Wasm function argument or local variable for the given frame index at the given variable index. The indexes are encoded as base 10. The -result is a hex-encoded address from where to read the value. +result is a hex-encoded little-endian value of the local. ``` @@ -2539,7 +2539,7 @@ variables. Get the value of a Wasm local variable from the Wasm operand stack, for the given frame index at the given variable index. The indexes are encoded as base -10. The result is a hex-encoded address from where to read value. +10. The result is a hex-encoded little-endian value from the stack at the given index. ``` send packet: $qWasmStackValue:0;2#cb diff --git a/lldb/docs/use/map.rst b/lldb/docs/use/map.rst index da566e7..1221546 100644 --- a/lldb/docs/use/map.rst +++ b/lldb/docs/use/map.rst @@ -802,7 +802,7 @@ Print the dynamic type of the result of an expression LLDB does this automatically if determining the dynamic type does not require running the target (in C++, running the target is never needed). This default is -controlled by the `target.prefer-dynamic-value` setting. If that is disabled, it +controlled by the ``target.prefer-dynamic-value`` setting. If that is disabled, it can be re-enabled on a per-command basis: .. code-block:: shell @@ -812,7 +812,7 @@ can be re-enabled on a per-command basis: (lldb) expr -d no-run-target -- someCPPObjectPtr Note that printing of the dynamic type of references is not possible with the -`expr` command. The workaround is to take the address of the reference and +``expr`` command. The workaround is to take the address of the reference and instruct lldb to print the children of the resulting pointer. .. code-block:: shell diff --git a/lldb/docs/use/tutorials/creating-custom-breakpoints.md b/lldb/docs/use/tutorials/creating-custom-breakpoints.md index 04673c3..b31a600 100644 --- a/lldb/docs/use/tutorials/creating-custom-breakpoints.md +++ b/lldb/docs/use/tutorials/creating-custom-breakpoints.md @@ -20,7 +20,7 @@ happens when a location triggers and includes the commands, conditions, ignore counts, etc. Stop options are common between all breakpoint types, so for our purposes only the Searcher and Resolver are relevant. -### Breakpoint Searcher +## Breakpoint Searcher The Searcher's job is to traverse in a structured way the code in the current target. It proceeds from the Target, to search all the Modules in the Target, @@ -36,7 +36,7 @@ based search filter. If neither of these is specified, the breakpoint will have a no-op search filter, so all parts of the program are searched and all locations accepted. -### Breakpoint Resolver +## Breakpoint Resolver The Resolver has two functions: @@ -72,7 +72,7 @@ you add to the breakpoint yourself. Note that the Breakpoint takes care of deduplicating equal addresses in AddLocation, so you shouldn't need to worry about that anyway. -### Scripted Breakpoint Resolver +## Scripted Breakpoint Resolver At present, when adding a ScriptedBreakpoint type, you can only provide a custom Resolver, not a custom SearchFilter. @@ -127,7 +127,7 @@ of Modules and the list of CompileUnits that will make up the SearchFilter. If you pass in empty lists, the breakpoint will use the default "search everywhere,accept everything" filter. -### Providing Facade Locations: +## Providing Facade Locations: The breakpoint resolver interface also allows you to present a separate set of locations for the breakpoint than the ones that actually implement the diff --git a/lldb/docs/use/tutorials/custom-frame-recognizers.md b/lldb/docs/use/tutorials/custom-frame-recognizers.md index 17bf963..e1f859ba 100644 --- a/lldb/docs/use/tutorials/custom-frame-recognizers.md +++ b/lldb/docs/use/tutorials/custom-frame-recognizers.md @@ -33,14 +33,14 @@ It's important to restrict the recognizer to the libc library (which is `libsystem_kernel.dylib` on macOS) to avoid matching functions with the same name in other modules: -```c++ +``` (lldb) command script import .../fd_recognizer.py (lldb) frame recognizer add -l fd_recognizer.LibcFdRecognizer -n read -s libsystem_kernel.dylib ``` When the program is stopped at the beginning of the 'read' function in libc, we can view the recognizer arguments in 'frame variable': -```c++ +``` (lldb) b read (lldb) r Process 1234 stopped diff --git a/lldb/docs/use/tutorials/implementing-standalone-scripts.md b/lldb/docs/use/tutorials/implementing-standalone-scripts.md index b1a3441..8143bb89 100644 --- a/lldb/docs/use/tutorials/implementing-standalone-scripts.md +++ b/lldb/docs/use/tutorials/implementing-standalone-scripts.md @@ -1,6 +1,6 @@ # Implementing Standalone Scripts -### Configuring `PYTHONPATH` +## Configuring `PYTHONPATH` LLDB has all of its core code built into a shared library which gets used by the `lldb` command line application. @@ -30,7 +30,7 @@ $ export PYTHONPATH=`lldb -P` Alternatively, you can append the LLDB Python directory to the sys.path list directly in your Python code before importing the lldb module. -### Initialization +## Initialization The standard test for `__main__`, like many python modules do, is useful for creating scripts that can be run from the command line. However, for command @@ -56,7 +56,7 @@ if __name__ == '__main__': lldb.SBDebugger.Terminate() ``` -### Example +## Example Now your python scripts are ready to import the lldb module. Below is a python script that will launch a program from the current working directory called @@ -133,7 +133,7 @@ if target: print(symbol) ``` -### Expected Output +## Expected Output Exact output varies by system, but you should see something like this: @@ -148,7 +148,7 @@ a.out[0x714]: mov w0, #0x0 ; =0 a.out[0x718]: ret ``` -### Troubleshooting +## Troubleshooting You can use all the usual Python tools to debug scripts, and on top of that you can enable LLDB's log channels. To do this in the script shown above, add diff --git a/lldb/docs/use/tutorials/script-driven-debugging.md b/lldb/docs/use/tutorials/script-driven-debugging.md index 55b90b1..4fe3ef8 100644 --- a/lldb/docs/use/tutorials/script-driven-debugging.md +++ b/lldb/docs/use/tutorials/script-driven-debugging.md @@ -12,7 +12,7 @@ This document will show how to do some of these things by going through an example, explaining how to use Python scripting to find a bug in a program that searches for text in a large binary tree. -### The Test Program and Input +## The Test Program and Input We have a simple C program ([dictionary.c](https://github.com/llvm/llvm-project/blob/main/lldb/examples/scripting/dictionary.c)) that reads in a text file, and stores all the words from the file in a @@ -24,7 +24,7 @@ the word in the tree. The input text file we are using to test our program contains the text for William Shakespeare's famous tragedy "Romeo and Juliet". -### The Bug +## The Bug When we try running our program, we find there is a problem. While it successfully finds some of the words we would expect to find, such as @@ -44,7 +44,7 @@ Enter search word: ^D $ ``` -### Using Depth First Search +## Using Depth First Search Our first job is to determine if the word "Romeo" actually got inserted into the tree or not. Since "Romeo and Juliet" has thousands of words, @@ -86,7 +86,7 @@ later explanations: 25: return DFS (right_child_ptr, word, cur_path) ``` -### Accessing & Manipulating Program Variables +## Accessing & Manipulating Program Variables Before we can call any Python function on any of our program's variables, we need to get the variable into a form that Python can @@ -126,7 +126,7 @@ information or children values out of SBValues. For complete information, see the header file SBValue.h. The `SBValue` methods that we use in our DFS function are `GetChildMemberWithName()`, `GetSummary()`, and `GetValue()`. -### Explaining DFS Script in Detail +## Explaining DFS Script in Detail Before diving into the details of this code, it would be best to give a high-level overview of what it does. The nodes in our binary search tree were @@ -166,13 +166,13 @@ start all over. Therefore we recommend doing as we have done: Writing your longer, more complicated script functions in a separate file (in this case tree_utils.py) and then importing it into your LLDB Python interpreter. -### The DFS Script in Action +## The DFS Script in Action At this point we are ready to use the DFS function to see if the word "Romeo" is in our tree or not. To actually use it in LLDB on our dictionary program, you would do something like this: -```c++ +``` $ lldb (lldb) process attach -n "dictionary" Architecture set to: x86_64. @@ -261,7 +261,7 @@ From this we can see that the word "Romeo" was indeed found in the tree, and the path from the root of the tree to the node containing "Romeo" is left-left-right-right-left. -### Using Breakpoint Command Scripts +## Using Breakpoint Command Scripts We are halfway to figuring out what the problem is. We know the word we are looking for is in the binary tree, and we know exactly where it is in the @@ -282,7 +282,7 @@ being hit. But if the decision differs from what the path says it should be, then the script prints out a message and does NOT resume execution, leaving the user sitting at the first point where a wrong decision is being made. -### Python Breakpoint Command Scripts Are Not What They Seem +## Python Breakpoint Command Scripts Are Not What They Seem What do we mean by that? When you enter a Python breakpoint command in LLDB, it appears that you are entering one or more plain lines of Python. BUT LLDB then @@ -305,7 +305,7 @@ automatically have a frame and a bp_loc variable. The variables are pre-loaded by LLDB with the correct context for the breakpoint. You do not have to use these variables, but they are there if you want them. -### The Decision Point Breakpoint Commands +## The Decision Point Breakpoint Commands This is what the Python breakpoint command script would look like for the decision to go right: @@ -358,7 +358,7 @@ execution. We allow the breakpoint to remain stopped (by doing nothing), and we print an informational message telling the user we have found the problem, and what the problem is. -### Actually Using The Breakpoint Commands +## Actually Using The Breakpoint Commands Now we will look at what happens when we actually use these breakpoint commands on our program. Doing a source list -n find_word shows us the function @@ -423,7 +423,7 @@ Enter your Python command(s). Type 'DONE' to end. > print "Here is the problem. Going right, should go left!" > DONE ``` -```c++ +``` (lldb) continue Process 696 resuming Here is the problem. Going right, should go left! @@ -480,7 +480,7 @@ case conversion problem somewhere in our program (we do). This is the end of our example on how you might use Python scripting in LLDB to help you find bugs in your program. -### Sources +## Sources The complete code for the Dictionary program (with case-conversion bug), the DFS function and other Python script examples used for this example are diff --git a/lldb/docs/use/tutorials/writing-custom-commands.md b/lldb/docs/use/tutorials/writing-custom-commands.md index d53b7e4..1ec6edf 100644 --- a/lldb/docs/use/tutorials/writing-custom-commands.md +++ b/lldb/docs/use/tutorials/writing-custom-commands.md @@ -1,6 +1,6 @@ # Writing Custom Commands -### Create a new command using a Python function +## Create a new command using a Python function Python functions can be used to create new LLDB command interpreter commands, which will work like all the natively defined lldb commands. This provides a @@ -51,7 +51,7 @@ command definition form can't do the right thing. | `result` | `lldb.SBCommandReturnObject` | A return object which encapsulates success/failure information for the command and output text that needs to be printed as a result of the command. The plain Python "print" command also works but text won't go in the result by default (it is useful as a temporary logging facility). | | `internal_dict` | `python dict object` | The dictionary for the current embedded script session which contains all variables and functions. | -### Create a new command using a Python class +## Create a new command using a Python class Since lldb 3.7, Python commands can also be implemented by means of a class which should implement the following interface: @@ -103,7 +103,7 @@ print("my command does lots of cool stuff", file=result) `SBCommandReturnObject` and `SBStream` both support this file-like behavior by providing `write()` and `flush()` calls at the Python layer. -### Parsed Commands +## Parsed Commands The commands that are added using this class definition are what lldb calls "raw" commands. The command interpreter doesn't attempt to parse the command, @@ -207,7 +207,7 @@ Mostly useful for handle_completion where you get passed the long option. """ ``` -### Completion +## Completion lldb will handle completing your option names, and all your enum values automatically. If your option or argument types have associated built-in completers, @@ -280,7 +280,7 @@ You can optionally include a "descriptions" key, whose value is a parallel array of description strings, and the completion will show the description next to each completion. -### Loading Commands +## Loading Commands One other handy convenience when defining lldb command-line commands is the command "command script import" which will import a module specified by file @@ -309,7 +309,7 @@ def goodstuff(debugger, command, ctx, result, internal_dict): # Command Implementation code goes here ``` -### Examples +## Examples Now we can create a module called ls.py in the file ~/ls.py that will implement a function that can be used by LLDB's python command code: @@ -413,7 +413,7 @@ you may want a `pofoo` X command, that equates po [ModifyString(X) capitalizedString]. The following debugger interaction shows how to achieve that goal: -```python3 +``` (lldb) script Python Interactive Interpreter. To exit, type 'quit()', 'exit()' or Ctrl-D. >>> def pofoo_funct(debugger, command, result, internal_dict): diff --git a/lldb/docs/use/variable.rst b/lldb/docs/use/variable.rst index 212e5c5..b23b78c 100644 --- a/lldb/docs/use/variable.rst +++ b/lldb/docs/use/variable.rst @@ -367,7 +367,7 @@ Initially, we will focus on summary strings, and then describe the Python binding mechanism. Summary Format Matching On Pointers ----------------------- +----------------------------------- A summary formatter for a type ``T`` might or might not be appropriate to use for pointers to that type. If the formatter is only appropriate for the type and @@ -972,13 +972,13 @@ Being more specific, in case of exceptions, LLDB might assume that the given object has no children or it might skip printing some children, as they are printed one by one. -[1] The `max_children` argument is optional (since lldb 3.8.0) and indicates the +[1] The ``max_children`` argument is optional (since lldb 3.8.0) and indicates the maximum number of children that lldb is interested in (at this moment). If the computation of the number of children is expensive (for example, requires traversing a linked list to determine its size) your implementation may return -`max_children` rather than the actual number. If the computation is cheap (e.g., the +``max_children`` rather than the actual number. If the computation is cheap (e.g., the number is stored as a field of the object), then you can always return the true -number of children (that is, ignore the `max_children` argument). +number of children (that is, ignore the ``max_children`` argument). [2] This method is optional. Also, a boolean value must be returned (since lldb 3.1.0). If ``False`` is returned, then whenever the process reaches a new stop, diff --git a/lldb/examples/python/templates/scripted_frame_provider.py b/lldb/examples/python/templates/scripted_frame_provider.py new file mode 100644 index 0000000..7a72f1a --- /dev/null +++ b/lldb/examples/python/templates/scripted_frame_provider.py @@ -0,0 +1,160 @@ +from abc import ABCMeta, abstractmethod + +import lldb + + +class ScriptedFrameProvider(metaclass=ABCMeta): + """ + The base class for a scripted frame provider. + + A scripted frame provider allows you to provide custom stack frames for a + thread, which can be used to augment or replace the standard unwinding + mechanism. This is useful for: + + - Providing frames for custom calling conventions or languages + - Reconstructing missing frames from crash dumps or core files + - Adding diagnostic or synthetic frames for debugging + - Visualizing state machines or async execution contexts + + Most of the base class methods are `@abstractmethod` that need to be + overwritten by the inheriting class. + + Example usage: + + .. code-block:: python + + # Attach a frame provider to a thread + thread = process.GetSelectedThread() + error = thread.SetScriptedFrameProvider( + "my_module.MyFrameProvider", + lldb.SBStructuredData() + ) + """ + + @staticmethod + def applies_to_thread(thread): + """Determine if this frame provider should be used for a given thread. + + This static method is called before creating an instance of the frame + provider to determine if it should be applied to a specific thread. + Override this method to provide custom filtering logic. + + Args: + thread (lldb.SBThread): The thread to check. + + Returns: + bool: True if this frame provider should be used for the thread, + False otherwise. The default implementation returns True for + all threads. + + Example: + + .. code-block:: python + + @staticmethod + def applies_to_thread(thread): + # Only apply to thread 1 + return thread.GetIndexID() == 1 + """ + return True + + @staticmethod + @abstractmethod + def get_description(): + """Get a description of this frame provider. + + This method should return a human-readable string describing what + this frame provider does. The description is used for debugging + and display purposes. + + Returns: + str: A description of the frame provider. + + Example: + + .. code-block:: python + + def get_description(self): + return "Crash log frame provider for thread 1" + """ + pass + + def __init__(self, input_frames, args): + """Construct a scripted frame provider. + + Args: + input_frames (lldb.SBFrameList): The frame list to use as input. + This allows you to access frames by index. The frames are + materialized lazily as you access them. + args (lldb.SBStructuredData): A Dictionary holding arbitrary + key/value pairs used by the scripted frame provider. + """ + self.input_frames = None + self.args = None + self.thread = None + self.target = None + self.process = None + + if isinstance(input_frames, lldb.SBFrameList) and input_frames.IsValid(): + self.input_frames = input_frames + self.thread = input_frames.GetThread() + if self.thread and self.thread.IsValid(): + self.process = self.thread.GetProcess() + if self.process and self.process.IsValid(): + self.target = self.process.GetTarget() + + if isinstance(args, lldb.SBStructuredData) and args.IsValid(): + self.args = args + + @abstractmethod + def get_frame_at_index(self, index): + """Get a single stack frame at the given index. + + This method is called lazily when a specific frame is needed in the + thread's backtrace (e.g., via the 'bt' command). Each frame is + requested individually as needed. + + Args: + index (int): The frame index to retrieve (0 for youngest/top frame). + + Returns: + Dict or None: A frame dictionary describing the stack frame, or None + if no frame exists at this index. The dictionary should contain: + + Required fields: + - idx (int): The synthetic frame index (0 for youngest/top frame) + - pc (int): The program counter address for the synthetic frame + + Alternatively, you can return: + - A ScriptedFrame object for full control over frame behavior + - An integer representing an input frame index to reuse + - None to indicate no more frames exist + + Example: + + .. code-block:: python + + def get_frame_at_index(self, index): + # Return None when there are no more frames + if index >= self.total_frames: + return None + + # Re-use an input frame by returning its index + if self.should_use_input_frame(index): + return index # Returns input frame at this index + + # Or create a custom frame dictionary + if index == 0: + return { + "idx": 0, + "pc": 0x100001234, + } + + return None + + Note: + The frames are indexed from 0 (youngest/top) to N (oldest/bottom). + This method will be called repeatedly with increasing indices until + None is returned. + """ + pass diff --git a/lldb/examples/python/templates/scripted_process.py b/lldb/examples/python/templates/scripted_process.py index 49059d5..24aa981 100644 --- a/lldb/examples/python/templates/scripted_process.py +++ b/lldb/examples/python/templates/scripted_process.py @@ -35,9 +35,7 @@ class ScriptedProcess(metaclass=ABCMeta): target = exe_ctx.target if isinstance(target, lldb.SBTarget) and target.IsValid(): self.target = target - triple = self.target.triple - if triple: - self.arch = triple.split("-")[0] + self.arch = target.arch_name self.dbg = target.GetDebugger() if isinstance(args, lldb.SBStructuredData) and args.IsValid(): self.args = args @@ -245,6 +243,7 @@ class ScriptedThread(metaclass=ABCMeta): key/value pairs used by the scripted thread. """ self.target = None + self.arch = None self.originating_process = None self.process = None self.args = None @@ -266,6 +265,9 @@ class ScriptedThread(metaclass=ABCMeta): and process.IsValid() ): self.target = process.target + triple = self.target.triple + if triple: + self.arch = triple.split("-")[0] self.originating_process = process self.process = self.target.GetProcess() self.get_register_info() @@ -352,17 +354,14 @@ class ScriptedThread(metaclass=ABCMeta): def get_register_info(self): if self.register_info is None: self.register_info = dict() - if "x86_64" in self.originating_process.arch: + if "x86_64" in self.arch: self.register_info["sets"] = ["General Purpose Registers"] self.register_info["registers"] = INTEL64_GPR - elif ( - "arm64" in self.originating_process.arch - or self.originating_process.arch == "aarch64" - ): + elif "arm64" in self.arch or self.arch == "aarch64": self.register_info["sets"] = ["General Purpose Registers"] self.register_info["registers"] = ARM64_GPR else: - raise ValueError("Unknown architecture", self.originating_process.arch) + raise ValueError("Unknown architecture", self.arch) return self.register_info @abstractmethod @@ -405,11 +404,12 @@ class ScriptedFrame(metaclass=ABCMeta): """Construct a scripted frame. Args: - thread (ScriptedThread): The thread owning this frame. + thread (ScriptedThread/lldb.SBThread): The thread owning this frame. args (lldb.SBStructuredData): A Dictionary holding arbitrary key/value pairs used by the scripted frame. """ self.target = None + self.arch = None self.originating_thread = None self.thread = None self.args = None @@ -419,15 +419,17 @@ class ScriptedFrame(metaclass=ABCMeta): self.register_ctx = {} self.variables = [] - if ( - isinstance(thread, ScriptedThread) - or isinstance(thread, lldb.SBThread) - and thread.IsValid() + if isinstance(thread, ScriptedThread) or ( + isinstance(thread, lldb.SBThread) and thread.IsValid() ): - self.target = thread.target self.process = thread.process + self.target = self.process.target + triple = self.target.triple + if triple: + self.arch = triple.split("-")[0] + tid = thread.tid if isinstance(thread, ScriptedThread) else thread.id self.originating_thread = thread - self.thread = self.process.GetThreadByIndexID(thread.tid) + self.thread = self.process.GetThreadByIndexID(tid) self.get_register_info() @abstractmethod @@ -508,7 +510,18 @@ class ScriptedFrame(metaclass=ABCMeta): def get_register_info(self): if self.register_info is None: - self.register_info = self.originating_thread.get_register_info() + if isinstance(self.originating_thread, ScriptedThread): + self.register_info = self.originating_thread.get_register_info() + elif isinstance(self.originating_thread, lldb.SBThread): + self.register_info = dict() + if "x86_64" in self.arch: + self.register_info["sets"] = ["General Purpose Registers"] + self.register_info["registers"] = INTEL64_GPR + elif "arm64" in self.arch or self.arch == "aarch64": + self.register_info["sets"] = ["General Purpose Registers"] + self.register_info["registers"] = ARM64_GPR + else: + raise ValueError("Unknown architecture", self.arch) return self.register_info @abstractmethod @@ -642,12 +655,12 @@ class PassthroughScriptedThread(ScriptedThread): # TODO: Passthrough stop reason from driving process if self.driving_thread.GetStopReason() != lldb.eStopReasonNone: - if "arm64" in self.originating_process.arch: + if "arm64" in self.arch: stop_reason["type"] = lldb.eStopReasonException stop_reason["data"]["desc"] = ( self.driving_thread.GetStopDescription(100) ) - elif self.originating_process.arch == "x86_64": + elif self.arch == "x86_64": stop_reason["type"] = lldb.eStopReasonSignal stop_reason["data"]["signal"] = signal.SIGTRAP else: diff --git a/lldb/include/lldb/API/LLDB.h b/lldb/include/lldb/API/LLDB.h index 6485f35..6ac35bb 100644 --- a/lldb/include/lldb/API/LLDB.h +++ b/lldb/include/lldb/API/LLDB.h @@ -37,6 +37,7 @@ #include "lldb/API/SBFileSpecList.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBFrame.h" +#include "lldb/API/SBFrameList.h" #include "lldb/API/SBFunction.h" #include "lldb/API/SBHostOS.h" #include "lldb/API/SBInstruction.h" diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h index 85f6bbe..5fcc685 100644 --- a/lldb/include/lldb/API/SBDefines.h +++ b/lldb/include/lldb/API/SBDefines.h @@ -76,6 +76,7 @@ class LLDB_API SBFileSpec; class LLDB_API SBFileSpecList; class LLDB_API SBFormat; class LLDB_API SBFrame; +class LLDB_API SBFrameList; class LLDB_API SBFunction; class LLDB_API SBHostOS; class LLDB_API SBInstruction; diff --git a/lldb/include/lldb/API/SBFile.h b/lldb/include/lldb/API/SBFile.h index ebdc560..8cf4fe1 100644 --- a/lldb/include/lldb/API/SBFile.h +++ b/lldb/include/lldb/API/SBFile.h @@ -27,7 +27,10 @@ public: SBFile(FileSP file_sp); #ifndef SWIG SBFile(const SBFile &rhs); + LLDB_DEPRECATED_FIXME("Use the constructor that specifies mode instead", + "SBFile(FILE*, const char*, bool)") SBFile(FILE *file, bool transfer_ownership); + SBFile(FILE *file, const char *mode, bool transfer_ownership); #endif SBFile(int fd, const char *mode, bool transfer_ownership); ~SBFile(); diff --git a/lldb/include/lldb/API/SBFrame.h b/lldb/include/lldb/API/SBFrame.h index 92917e5..5283cdfe 100644 --- a/lldb/include/lldb/API/SBFrame.h +++ b/lldb/include/lldb/API/SBFrame.h @@ -222,6 +222,7 @@ public: protected: friend class SBBlock; friend class SBExecutionContext; + friend class SBFrameList; friend class SBInstruction; friend class SBThread; friend class SBValue; diff --git a/lldb/include/lldb/API/SBFrameList.h b/lldb/include/lldb/API/SBFrameList.h new file mode 100644 index 0000000..0039ffb --- /dev/null +++ b/lldb/include/lldb/API/SBFrameList.h @@ -0,0 +1,96 @@ +//===----------------------------------------------------------------------===// +// +// 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_API_SBFRAMELIST_H +#define LLDB_API_SBFRAMELIST_H + +#include "lldb/API/SBDefines.h" + +namespace lldb_private { +class ScriptInterpreter; +namespace python { +class SWIGBridge; +} +namespace lua { +class SWIGBridge; +} +} // namespace lldb_private + +namespace lldb { + +/// Represents a list of SBFrame objects. +/// +/// SBFrameList provides a way to iterate over stack frames lazily, +/// materializing frames on-demand as they are accessed. This is more +/// efficient than eagerly creating all frames upfront. +class LLDB_API SBFrameList { +public: + SBFrameList(); + + SBFrameList(const lldb::SBFrameList &rhs); + + ~SBFrameList(); + + const lldb::SBFrameList &operator=(const lldb::SBFrameList &rhs); + + explicit operator bool() const; + + bool IsValid() const; + + /// Returns the number of frames in the list. + uint32_t GetSize() const; + + /// Returns the frame at the given index. + /// + /// \param[in] idx + /// The index of the frame to retrieve (0-based). + /// + /// \return + /// An SBFrame object for the frame at the specified index. + /// Returns an invalid SBFrame if idx is out of range. + lldb::SBFrame GetFrameAtIndex(uint32_t idx) const; + + /// Get the thread associated with this frame list. + /// + /// \return + /// An SBThread object representing the thread. + lldb::SBThread GetThread() const; + + /// Clear all frames from this list. + void Clear(); + + /// Get a description of this frame list. + /// + /// \param[in] description + /// The stream to write the description to. + /// + /// \return + /// True if the description was successfully written. + bool GetDescription(lldb::SBStream &description) const; + +protected: + friend class SBThread; + + friend class lldb_private::python::SWIGBridge; + friend class lldb_private::lua::SWIGBridge; + friend class lldb_private::ScriptInterpreter; + +private: + SBFrameList(const lldb::StackFrameListSP &frame_list_sp); + + void SetFrameList(const lldb::StackFrameListSP &frame_list_sp); + + // This needs to be a shared_ptr since an SBFrameList can be passed to + // scripting affordances like ScriptedFrameProviders but also out of + // convenience because Thread::GetStackFrameList returns a StackFrameListSP. + lldb::StackFrameListSP m_opaque_sp; +}; + +} // namespace lldb + +#endif // LLDB_API_SBFRAMELIST_H diff --git a/lldb/include/lldb/API/SBModuleSpec.h b/lldb/include/lldb/API/SBModuleSpec.h index 8d1ecfe..b80a52b 100644 --- a/lldb/include/lldb/API/SBModuleSpec.h +++ b/lldb/include/lldb/API/SBModuleSpec.h @@ -87,6 +87,16 @@ public: bool GetDescription(lldb::SBStream &description); + lldb::SBTarget GetTarget(); + + /// Set the target to be used when resolving a module. + /// + /// A target can help locate a module specified by a SBModuleSpec. The + /// target settings, like the executable and debug info search paths, can + /// be essential. The target's platform can also be used to locate or download + /// the specified module. + void SetTarget(lldb::SBTarget target); + private: friend class SBModuleSpecList; friend class SBModule; diff --git a/lldb/include/lldb/API/SBStream.h b/lldb/include/lldb/API/SBStream.h index d230da6..21f9d21 100644 --- a/lldb/include/lldb/API/SBStream.h +++ b/lldb/include/lldb/API/SBStream.h @@ -81,6 +81,7 @@ protected: friend class SBFileSpec; friend class SBFileSpecList; friend class SBFrame; + friend class SBFrameList; friend class SBFunction; friend class SBInstruction; friend class SBInstructionList; diff --git a/lldb/include/lldb/API/SBStructuredData.h b/lldb/include/lldb/API/SBStructuredData.h index dfd8ec0..05b9ef4 100644 --- a/lldb/include/lldb/API/SBStructuredData.h +++ b/lldb/include/lldb/API/SBStructuredData.h @@ -114,11 +114,11 @@ public: /// the previous data. void SetValueForKey(const char *key, SBStructuredData &value); - /// Change the type to unsigned interger and overwrite the previous data with + /// Change the type to unsigned integer and overwrite the previous data with /// the new value. void SetUnsignedIntegerValue(uint64_t value); - /// Change the type to signed interger and overwrite the previous data with + /// Change the type to signed integer and overwrite the previous data with /// the new value. void SetSignedIntegerValue(int64_t value); diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h index 173fd05..0318492 100644 --- a/lldb/include/lldb/API/SBTarget.h +++ b/lldb/include/lldb/API/SBTarget.h @@ -19,6 +19,7 @@ #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBStatisticsOptions.h" #include "lldb/API/SBSymbolContextList.h" +#include "lldb/API/SBThreadCollection.h" #include "lldb/API/SBType.h" #include "lldb/API/SBValue.h" #include "lldb/API/SBWatchpoint.h" @@ -44,6 +45,7 @@ public: eBroadcastBitWatchpointChanged = (1 << 3), eBroadcastBitSymbolsLoaded = (1 << 4), eBroadcastBitSymbolsChanged = (1 << 5), + eBroadcastBitNewTargetCreated = (1 << 6), }; // Constructors @@ -64,6 +66,10 @@ public: static lldb::SBTarget GetTargetFromEvent(const lldb::SBEvent &event); + /// For eBroadcastBitNewTargetCreated events, returns the newly created + /// target. For other event types, returns an invalid SBTarget. + static lldb::SBTarget GetCreatedTargetFromEvent(const lldb::SBEvent &event); + static uint32_t GetNumModulesFromEvent(const lldb::SBEvent &event); static lldb::SBModule GetModuleAtIndexFromEvent(const uint32_t idx, @@ -353,6 +359,8 @@ public: const char *GetTriple(); + const char *GetArchName(); + const char *GetABIName(); const char *GetLabel() const; @@ -365,6 +373,16 @@ public: /// LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID if the target is invalid. lldb::user_id_t GetGloballyUniqueID() const; + /// Get the target session name for this target. + /// + /// The target session name provides a meaningful name for IDEs or tools to + /// display to help the user identify the origin and purpose of the target. + /// + /// \return + /// The target session name for this target, or nullptr if the target is + /// invalid or has no target session name. + const char *GetTargetSessionName() const; + SBError SetLabel(const char *label); /// Architecture opcode byte size width accessor @@ -986,6 +1004,35 @@ public: lldb::SBMutex GetAPIMutex() const; + /// Register a scripted frame provider for this target. + /// If a scripted frame provider with the same name and same argument + /// dictionary is already registered on this target, it will be overwritten. + /// + /// \param[in] class_name + /// The name of the Python class that implements the frame provider. + /// + /// \param[in] args_dict + /// A dictionary of arguments to pass to the frame provider class. + /// + /// \param[out] error + /// An error object indicating success or failure. + /// + /// \return + /// A unique identifier for the frame provider descriptor that was + /// registered. 0 if the registration failed. + uint32_t RegisterScriptedFrameProvider(const char *class_name, + lldb::SBStructuredData args_dict, + lldb::SBError &error); + + /// Remove a scripted frame provider from this target by name. + /// + /// \param[in] provider_id + /// The id of the frame provider class to remove. + /// + /// \return + /// An error object indicating success or failure. + lldb::SBError RemoveScriptedFrameProvider(uint32_t provider_id); + protected: friend class SBAddress; friend class SBAddressRange; @@ -999,6 +1046,7 @@ protected: friend class SBFunction; friend class SBInstruction; friend class SBModule; + friend class SBModuleSpec; friend class SBPlatform; friend class SBProcess; friend class SBSection; diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h index 2411dfd..639e7a0 100644 --- a/lldb/include/lldb/API/SBThread.h +++ b/lldb/include/lldb/API/SBThread.h @@ -186,6 +186,8 @@ public: lldb::SBFrame GetFrameAtIndex(uint32_t idx); + lldb::SBFrameList GetFrames(); + lldb::SBFrame GetSelectedFrame(); lldb::SBFrame SetSelectedFrame(uint32_t frame_idx); @@ -244,6 +246,7 @@ private: friend class SBSaveCoreOptions; friend class SBExecutionContext; friend class SBFrame; + friend class SBFrameList; friend class SBProcess; friend class SBDebugger; friend class SBValue; @@ -253,6 +256,7 @@ private: friend class SBThreadPlan; friend class SBTrace; + friend class lldb_private::ScriptInterpreter; friend class lldb_private::python::SWIGBridge; SBThread(const lldb::ThreadSP &lldb_object_sp); diff --git a/lldb/include/lldb/API/SBThreadCollection.h b/lldb/include/lldb/API/SBThreadCollection.h index 5a052e6..d13dea0 100644 --- a/lldb/include/lldb/API/SBThreadCollection.h +++ b/lldb/include/lldb/API/SBThreadCollection.h @@ -46,6 +46,7 @@ protected: void SetOpaque(const lldb::ThreadCollectionSP &threads); private: + friend class SBTarget; friend class SBProcess; friend class SBThread; friend class SBSaveCoreOptions; diff --git a/lldb/include/lldb/API/SBTrace.h b/lldb/include/lldb/API/SBTrace.h index ce95595..d5368b2 100644 --- a/lldb/include/lldb/API/SBTrace.h +++ b/lldb/include/lldb/API/SBTrace.h @@ -39,7 +39,7 @@ public: SBTraceCursor CreateNewCursor(SBError &error, SBThread &thread); /// Save the trace to the specified directory, which will be created if - /// needed. This will also create a file \a <directory>/trace.json with the + /// needed. This will also create a file <directory>/trace.json with the /// main properties of the trace session, along with others files which /// contain the actual trace data. The trace.json file can be used later as /// input for the "trace load" command to load the trace in LLDB, or for the diff --git a/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h b/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h index 124cb55..57acb82 100644 --- a/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h +++ b/lldb/include/lldb/Breakpoint/BreakpointLocationCollection.h @@ -32,7 +32,8 @@ public: ~BreakpointLocationCollection(); - BreakpointLocationCollection &operator=(const BreakpointLocationCollection &rhs); + BreakpointLocationCollection & + operator=(const BreakpointLocationCollection &rhs); /// Add the breakpoint \a bp_loc_sp to the list. /// @@ -172,17 +173,18 @@ private: lldb::break_id_t break_loc_id) const; collection m_break_loc_collection; - mutable std::mutex m_collection_mutex; + mutable std::recursive_mutex m_collection_mutex; /// These are used if we're preserving breakpoints in this list: const bool m_preserving_bkpts = false; std::map<std::pair<lldb::break_id_t, lldb::break_id_t>, lldb::BreakpointSP> m_preserved_bps; public: - typedef llvm::iterator_range<collection::const_iterator> + typedef LockingAdaptedIterable<std::recursive_mutex, collection> BreakpointLocationCollectionIterable; BreakpointLocationCollectionIterable BreakpointLocations() { - return BreakpointLocationCollectionIterable(m_break_loc_collection); + return BreakpointLocationCollectionIterable(m_break_loc_collection, + m_collection_mutex); } }; } // namespace lldb_private diff --git a/lldb/include/lldb/Breakpoint/BreakpointSite.h b/lldb/include/lldb/Breakpoint/BreakpointSite.h index a935b24..e189ed7 100644 --- a/lldb/include/lldb/Breakpoint/BreakpointSite.h +++ b/lldb/include/lldb/Breakpoint/BreakpointSite.h @@ -156,6 +156,10 @@ public: /// would be valid for this thread, false otherwise. bool ValidForThisThread(Thread &thread); + /// Returns true if at least one constituent is both public and valid for + /// `thread`. + bool ContainsUserBreakpointForThread(Thread &thread); + /// Print a description of this breakpoint site to the stream \a s. /// GetDescription tells you about the breakpoint site's constituents. Use /// BreakpointSite::Dump(Stream *) to get information about the breakpoint diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h index ead2ed3..a39413c 100644 --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -107,6 +107,9 @@ public: static void Destroy(lldb::DebuggerSP &debugger_sp); + /// Get the build configuration as structured data. + static StructuredData::DictionarySP GetBuildConfiguration(); + static lldb::DebuggerSP FindDebuggerWithID(lldb::user_id_t id); static lldb::DebuggerSP diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h index db186dd..fb91907 100644 --- a/lldb/include/lldb/Core/Disassembler.h +++ b/lldb/include/lldb/Core/Disassembler.h @@ -167,6 +167,8 @@ public: virtual bool IsLoad() = 0; + virtual bool IsBarrier() = 0; + virtual bool IsAuthenticated() = 0; bool CanSetBreakpoint(); @@ -297,6 +299,10 @@ public: lldb::InstructionSP GetInstructionAtIndex(size_t idx) const; + llvm::ArrayRef<lldb::InstructionSP> Instructions() const { + return m_instructions; + } + /// Get the instruction at the given address. /// /// \return @@ -363,6 +369,8 @@ public: bool IsLoad() override; + bool IsBarrier() override; + bool IsAuthenticated() override; void CalculateMnemonicOperandsAndComment( @@ -566,24 +574,40 @@ private: const Disassembler &operator=(const Disassembler &) = delete; }; +/// Structured data for a single variable annotation. +struct VariableAnnotation { + std::string variable_name; + /// Location description (e.g., "r15", "undef", "const_0"). + std::string location_description; + /// Whether variable is live at this instruction. + bool is_live; + /// Register numbering scheme for location interpretation. + lldb::RegisterKind register_kind; + /// Where this annotation is valid. + std::optional<lldb_private::AddressRange> address_range; + /// Source file where variable was declared. + std::optional<std::string> decl_file; + /// Line number where variable was declared. + std::optional<uint32_t> decl_line; + /// Variable's type name. + std::optional<std::string> type_name; +}; + /// Tracks live variable annotations across instructions and produces /// per-instruction "events" like `name = RDI` or `name = <undef>`. class VariableAnnotator { - struct VarState { - /// Display name. - std::string name; - /// Last printed location (empty means <undef>). - std::string last_loc; - }; // Live state from the previous instruction, keyed by Variable::GetID(). - llvm::DenseMap<lldb::user_id_t, VarState> Live_; + llvm::DenseMap<lldb::user_id_t, VariableAnnotation> m_live_vars; public: - /// Compute annotation strings for a single instruction and update `Live_`. - /// Returns only the events that should be printed *at this instruction*. - std::vector<std::string> annotate(Instruction &inst, Target &target, - const lldb::ModuleSP &module_sp); + /// Compute annotation strings for a single instruction and update + /// `m_live_vars`. Returns only the events that should be printed *at this + /// instruction*. + std::vector<std::string> Annotate(Instruction &inst); + + /// Returns structured data for all variables relevant at this instruction. + std::vector<VariableAnnotation> AnnotateStructured(Instruction &inst); }; } // namespace lldb_private diff --git a/lldb/include/lldb/Core/EmulateInstruction.h b/lldb/include/lldb/Core/EmulateInstruction.h index a9fd454..ff1386c 100644 --- a/lldb/include/lldb/Core/EmulateInstruction.h +++ b/lldb/include/lldb/Core/EmulateInstruction.h @@ -385,8 +385,8 @@ public: const RegisterInfo *reg_info, const RegisterValue ®_value); - // Type to represent the condition of an instruction. The UINT32 value is - // reserved for the unconditional case and all other value can be used in an + // Type to represent the condition of an instruction. The UINT32_MAX value is + // reserved for the unconditional case and all other values can be used in an // architecture dependent way. typedef uint32_t InstructionCondition; static const InstructionCondition UnconditionalCondition = UINT32_MAX; diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h index 40916dc..107c30a 100644 --- a/lldb/include/lldb/Core/FormatEntity.h +++ b/lldb/include/lldb/Core/FormatEntity.h @@ -81,6 +81,7 @@ struct Entry { FrameRegisterByName, FrameIsArtificial, FrameKind, + FrameBorrowedInfo, ScriptFrame, FunctionID, FunctionDidChange, diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h index 8513e14..40ce23e 100644 --- a/lldb/include/lldb/Core/Module.h +++ b/lldb/include/lldb/Core/Module.h @@ -298,23 +298,15 @@ public: /// matches. void FindCompileUnits(const FileSpec &path, SymbolContextList &sc_list); - /// Find functions by lookup info. + /// Find functions by a vector of lookup infos. /// /// If the function is an inlined function, it will have a block, /// representing the inlined function, and the function will be the /// containing function. If it is not inlined, then the block will be NULL. - /// - /// \param[in] lookup_info - /// The lookup info of the function we are looking for. - /// - /// \param[out] sc_list - /// A symbol context list that gets filled in with all of the - /// matches. - void FindFunctions(const LookupInfo &lookup_info, + void FindFunctions(llvm::ArrayRef<LookupInfo> lookup_infos, const CompilerDeclContext &parent_decl_ctx, const ModuleFunctionSearchOptions &options, SymbolContextList &sc_list); - /// Find functions by name. /// /// If the function is an inlined function, it will have a block, @@ -917,8 +909,29 @@ public: public: LookupInfo() = default; - LookupInfo(ConstString name, lldb::FunctionNameType name_type_mask, - lldb::LanguageType language); + /// Creates a vector of lookup infos for function name resolution. + /// + /// \param[in] name + /// The function name to search for. This can be a simple name like + /// "foo" or a qualified name like "Class::method". + /// + /// \param[in] name_type_mask + /// A bitmask specifying what types of names to search for + /// (e.g., eFunctionNameTypeFull, eFunctionNameTypeBase, + /// eFunctionNameTypeMethod, eFunctionNameTypeAuto). Multiple types + /// can be combined with bitwise OR. + /// + /// \param[in] lang_type + /// The language to create lookups for. If eLanguageTypeUnknown is + /// passed, creates one LookupInfo for each language plugin currently + /// available in LLDB. If a specific language is provided, creates only + // a single LookupInfo for that language. + /// + /// \return + /// A vector of LookupInfo objects, one per relevant language. + static std::vector<LookupInfo> + MakeLookupInfos(ConstString name, lldb::FunctionNameType name_type_mask, + lldb::LanguageType lang_type); ConstString GetName() const { return m_name; } @@ -959,6 +972,10 @@ public: /// If \b true, then demangled names that match will need to contain /// "m_name" in order to be considered a match bool m_match_name_after_lookup = false; + + private: + LookupInfo(ConstString name, lldb::FunctionNameType name_type_mask, + lldb::LanguageType lang_type); }; /// Get a unique hash for this module. diff --git a/lldb/include/lldb/Core/ModuleList.h b/lldb/include/lldb/Core/ModuleList.h index e71f3b2..1ef90b4 100644 --- a/lldb/include/lldb/Core/ModuleList.h +++ b/lldb/include/lldb/Core/ModuleList.h @@ -476,9 +476,9 @@ public: static Status GetSharedModule(const ModuleSpec &module_spec, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, - bool *did_create_ptr, bool always_create = false); + bool *did_create_ptr, bool always_create = false, + bool invoke_locate_callback = true); static bool RemoveSharedModule(lldb::ModuleSP &module_sp); @@ -511,6 +511,12 @@ public: /// Atomically swaps the contents of this module list with \a other. void Swap(ModuleList &other); + /// For each module in this ModuleList, preload its symbols. + /// + /// \param[in] parallelize + /// If true, all modules will be preloaded in parallel. + void PreloadSymbols(bool parallelize) const; + protected: // Class typedefs. typedef std::vector<lldb::ModuleSP> diff --git a/lldb/include/lldb/Core/ModuleSpec.h b/lldb/include/lldb/Core/ModuleSpec.h index 86be038..acbc85b 100644 --- a/lldb/include/lldb/Core/ModuleSpec.h +++ b/lldb/include/lldb/Core/ModuleSpec.h @@ -16,9 +16,11 @@ #include "lldb/Utility/Iterable.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/UUID.h" +#include "lldb/lldb-forward.h" #include "llvm/Support/Chrono.h" +#include <memory> #include <mutex> #include <vector> @@ -126,6 +128,16 @@ public: lldb::DataBufferSP GetData() const { return m_data; } + lldb::TargetSP GetTargetSP() const { return m_target_wp.lock(); } + + /// Set the target to be used when resolving a module. + /// + /// A target can help locate a module specified by a ModuleSpec. The target + /// settings, like the executable and debug info search paths, can be + /// essential. The target's platform can also be used to locate or download + /// the specified module. + void SetTarget(std::shared_ptr<Target> target) { m_target_wp = target; } + void Clear() { m_file.Clear(); m_platform_file.Clear(); @@ -137,6 +149,7 @@ public: m_object_size = 0; m_source_mappings.Clear(false); m_object_mod_time = llvm::sys::TimePoint<>(); + m_target_wp.reset(); } explicit operator bool() const { @@ -265,6 +278,11 @@ protected: ArchSpec m_arch; UUID m_uuid; ConstString m_object_name; + /// The target used when resolving a module. A target can help locate a module + /// specified by a ModuleSpec. The target settings, like the executable and + /// debug info search paths, can be essential. The target's platform can also + /// be used to locate or download the specified module. + std::weak_ptr<Target> m_target_wp; uint64_t m_object_offset = 0; uint64_t m_object_size = 0; llvm::sys::TimePoint<> m_object_mod_time; diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index aa60b7c..ab2ca58 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -356,6 +356,24 @@ public: GetScriptInterpreterForLanguage(lldb::ScriptLanguage script_lang, Debugger &debugger); + // SyntheticFrameProvider + static bool + RegisterPlugin(llvm::StringRef name, llvm::StringRef description, + SyntheticFrameProviderCreateInstance create_native_callback, + ScriptedFrameProviderCreateInstance create_scripted_callback); + + static bool + UnregisterPlugin(SyntheticFrameProviderCreateInstance create_callback); + + static bool + UnregisterPlugin(ScriptedFrameProviderCreateInstance create_callback); + + static SyntheticFrameProviderCreateInstance + GetSyntheticFrameProviderCreateCallbackForPluginName(llvm::StringRef name); + + static ScriptedFrameProviderCreateInstance + GetScriptedFrameProviderCreateCallbackAtIndex(uint32_t idx); + // StructuredDataPlugin /// Register a StructuredDataPlugin class along with optional diff --git a/lldb/include/lldb/Core/Section.h b/lldb/include/lldb/Core/Section.h index f0f5a0b..3c5586c 100644 --- a/lldb/include/lldb/Core/Section.h +++ b/lldb/include/lldb/Core/Section.h @@ -46,6 +46,8 @@ public: /// Create an empty list. SectionList() = default; + SectionList(const SectionList &lhs); + SectionList &operator=(const SectionList &rhs); size_t AddSection(const lldb::SectionSP §ion_sp); @@ -96,6 +98,17 @@ public: /// information. uint64_t GetDebugInfoSize() const; + // Callback to decide which of two matching sections should be used in the + // merged output. + using MergeCallback = + std::function<lldb::SectionSP(lldb::SectionSP, lldb::SectionSP)>; + + // Function that merges two different sections into a new output list. All + // unique sections will be checked for conflict and resolved using the + // supplied merging callback. + static SectionList Merge(SectionList &lhs, SectionList &rhs, + MergeCallback filter); + protected: collection m_sections; }; @@ -273,6 +286,9 @@ public: /// return true. bool ContainsOnlyDebugInfo() const; + /// Returns true if this is a global offset table section. + bool IsGOTSection() const; + protected: ObjectFile *m_obj_file; // The object file that data for this section should // be read from diff --git a/lldb/include/lldb/Core/SourceManager.h b/lldb/include/lldb/Core/SourceManager.h index 83dc747..5a7b515 100644 --- a/lldb/include/lldb/Core/SourceManager.h +++ b/lldb/include/lldb/Core/SourceManager.h @@ -11,6 +11,7 @@ #include "lldb/Utility/Checksum.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/SupportFile.h" #include "lldb/lldb-defines.h" #include "lldb/lldb-forward.h" @@ -38,8 +39,8 @@ public: const SourceManager::File &rhs); public: - File(lldb::SupportFileSP support_file_sp, lldb::TargetSP target_sp); - File(lldb::SupportFileSP support_file_sp, lldb::DebuggerSP debugger_sp); + File(SupportFileNSP support_file_nsp, lldb::TargetSP target_sp); + File(SupportFileNSP support_file_nsp, lldb::DebuggerSP debugger_sp); bool ModificationTimeIsStale() const; bool PathRemappingIsStale() const; @@ -57,9 +58,9 @@ public: bool LineIsValid(uint32_t line); - lldb::SupportFileSP GetSupportFile() const { - assert(m_support_file_sp && "SupportFileSP must always be valid"); - return m_support_file_sp; + SupportFileNSP GetSupportFile() const { + assert(m_support_file_nsp && "SupportFileNSP must always be valid"); + return m_support_file_nsp; } uint32_t GetSourceMapModificationID() const { return m_source_map_mod_id; } @@ -80,13 +81,13 @@ public: protected: /// Set file and update modification time. - void SetSupportFile(lldb::SupportFileSP support_file_sp); + void SetSupportFile(SupportFileNSP support_file_nsp); bool CalculateLineOffsets(uint32_t line = UINT32_MAX); /// The support file. If the target has source mappings, this might be /// different from the original support file passed to the constructor. - lldb::SupportFileSP m_support_file_sp; + SupportFileNSP m_support_file_nsp; /// Keep track of the on-disk checksum. Checksum m_checksum; @@ -107,9 +108,9 @@ public: lldb::TargetWP m_target_wp; private: - void CommonInitializer(lldb::SupportFileSP support_file_sp, + void CommonInitializer(SupportFileNSP support_file_nsp, lldb::TargetSP target_sp); - void CommonInitializerImpl(lldb::SupportFileSP support_file_sp, + void CommonInitializerImpl(SupportFileNSP support_file_nsp, lldb::TargetSP target_sp); }; @@ -156,13 +157,13 @@ public: ~SourceManager(); - FileSP GetLastFile() { return GetFile(m_last_support_file_sp); } + FileSP GetLastFile() { return GetFile(m_last_support_file_nsp); } bool AtLastLine(bool reverse) { return m_last_line == UINT32_MAX || (reverse && m_last_line == 1); } size_t DisplaySourceLinesWithLineNumbers( - lldb::SupportFileSP support_file_sp, uint32_t line, uint32_t column, + SupportFileNSP support_file_nsp, uint32_t line, uint32_t column, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs = nullptr); @@ -176,31 +177,30 @@ public: size_t DisplayMoreWithLineNumbers(Stream *s, uint32_t count, bool reverse, const SymbolContextList *bp_locs = nullptr); - bool SetDefaultFileAndLine(lldb::SupportFileSP support_file_sp, - uint32_t line); + bool SetDefaultFileAndLine(SupportFileNSP support_file_nsp, uint32_t line); struct SupportFileAndLine { - lldb::SupportFileSP support_file_sp; + SupportFileNSP support_file_nsp; uint32_t line; - SupportFileAndLine(lldb::SupportFileSP support_file_sp, uint32_t line) - : support_file_sp(support_file_sp), line(line) {} + SupportFileAndLine(SupportFileNSP support_file_nsp, uint32_t line) + : support_file_nsp(support_file_nsp), line(line) {} }; std::optional<SupportFileAndLine> GetDefaultFileAndLine(); bool DefaultFileAndLineSet() { - return (GetFile(m_last_support_file_sp).get() != nullptr); + return (GetFile(m_last_support_file_nsp).get() != nullptr); } - void FindLinesMatchingRegex(lldb::SupportFileSP support_file_sp, + void FindLinesMatchingRegex(SupportFileNSP support_file_nsp, RegularExpression ®ex, uint32_t start_line, uint32_t end_line, std::vector<uint32_t> &match_lines); - FileSP GetFile(lldb::SupportFileSP support_file_sp); + FileSP GetFile(SupportFileNSP support_file_nsp); protected: - lldb::SupportFileSP m_last_support_file_sp; + SupportFileNSP m_last_support_file_nsp; uint32_t m_last_line; uint32_t m_last_count; bool m_default_set; diff --git a/lldb/include/lldb/Expression/DiagnosticManager.h b/lldb/include/lldb/Expression/DiagnosticManager.h index fc49349..c7e02d8 100644 --- a/lldb/include/lldb/Expression/DiagnosticManager.h +++ b/lldb/include/lldb/Expression/DiagnosticManager.h @@ -12,7 +12,7 @@ #include "lldb/lldb-defines.h" #include "lldb/lldb-types.h" -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/Status.h" diff --git a/lldb/include/lldb/Host/File.h b/lldb/include/lldb/Host/File.h index 7402a22..590c9fa5 100644 --- a/lldb/include/lldb/Host/File.h +++ b/lldb/include/lldb/Host/File.h @@ -66,6 +66,9 @@ public: LLVM_MARK_AS_BITMASK_ENUM(/* largest_value= */ eOpenOptionInvalid) }; + static constexpr OpenOptions OpenOptionsModeMask = + eOpenOptionReadOnly | eOpenOptionWriteOnly | eOpenOptionReadWrite; + static mode_t ConvertOpenOptionsForPOSIXOpen(OpenOptions open_options); static llvm::Expected<OpenOptions> GetOptionsFromMode(llvm::StringRef mode); static bool DescriptorIsValid(int descriptor) { return descriptor >= 0; }; @@ -384,7 +387,7 @@ public: NativeFile(); - NativeFile(FILE *fh, bool transfer_ownership); + NativeFile(FILE *fh, OpenOptions options, bool transfer_ownership); NativeFile(int fd, OpenOptions options, bool transfer_ownership); diff --git a/lldb/include/lldb/Host/FileAction.h b/lldb/include/lldb/Host/FileAction.h index d3166c1..b2cc8be 100644 --- a/lldb/include/lldb/Host/FileAction.h +++ b/lldb/include/lldb/Host/FileAction.h @@ -39,8 +39,6 @@ public: int GetActionArgument() const { return m_arg; } - llvm::StringRef GetPath() const; - const FileSpec &GetFileSpec() const; void Dump(Stream &stream) const; diff --git a/lldb/include/lldb/Host/StreamFile.h b/lldb/include/lldb/Host/StreamFile.h index e37661a..8b01eea 100644 --- a/lldb/include/lldb/Host/StreamFile.h +++ b/lldb/include/lldb/Host/StreamFile.h @@ -81,7 +81,8 @@ public: LockableStreamFile(StreamFile &stream_file, Mutex &mutex) : m_file_sp(stream_file.GetFileSP()), m_mutex(mutex) {} LockableStreamFile(FILE *fh, bool transfer_ownership, Mutex &mutex) - : m_file_sp(std::make_shared<NativeFile>(fh, transfer_ownership)), + : m_file_sp(std::make_shared<NativeFile>(fh, File::eOpenOptionWriteOnly, + transfer_ownership)), m_mutex(mutex) {} LockableStreamFile(std::shared_ptr<File> file_sp, Mutex &mutex) : m_file_sp(file_sp), m_mutex(mutex) {} diff --git a/lldb/include/lldb/Utility/DiagnosticsRendering.h b/lldb/include/lldb/Host/common/DiagnosticsRendering.h index dd33d671..dd33d671 100644 --- a/lldb/include/lldb/Utility/DiagnosticsRendering.h +++ b/lldb/include/lldb/Host/common/DiagnosticsRendering.h diff --git a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h index 81aea5b..553263e2 100644 --- a/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h +++ b/lldb/include/lldb/Host/windows/ProcessLauncherWindows.h @@ -11,6 +11,7 @@ #include "lldb/Host/ProcessLauncher.h" #include "lldb/Host/windows/windows.h" +#include "llvm/Support/ErrorOr.h" namespace lldb_private { @@ -23,6 +24,36 @@ public: protected: HANDLE GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd); + + /// Get the list of Windows handles that should be inherited by the child + /// process and update `STARTUPINFOEXW` with the handle list. + /// + /// If no handles need to be inherited, an empty vector is returned. + /// + /// Otherwise, the function populates the + /// `PROC_THREAD_ATTRIBUTE_HANDLE_LIST` attribute in `startupinfoex` with the + /// collected handles using `UpdateProcThreadAttribute`. On success, the + /// vector of inherited handles is returned. + /// + /// \param launch_info + /// The process launch configuration. + /// + /// \param startupinfoex + /// The extended STARTUPINFO structure for the process being created. + /// + /// \param stdout_handle + /// \param stderr_handle + /// \param stdin_handle + /// Optional explicit standard stream handles to use for the child process. + /// + /// \returns + /// `std::vector<HANDLE>` containing all handles that the child must + /// inherit. + llvm::ErrorOr<std::vector<HANDLE>> + GetInheritedHandles(const ProcessLaunchInfo &launch_info, + STARTUPINFOEXW &startupinfoex, + HANDLE stdout_handle = NULL, HANDLE stderr_handle = NULL, + HANDLE stdin_handle = NULL); }; } diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h index 4face71..701491b 100644 --- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h +++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h @@ -154,6 +154,27 @@ static constexpr OptionEnumValueElement g_running_mode[] = { "Run only this thread while stepping"}, }; +static constexpr OptionEnumValueElement g_exception_stage[] = { + {lldb::eExceptionStageThrow, "throw", "Stop when the exception is thrown."}, + {lldb::eExceptionStageReThrow, "re-throw", + "Stop when the exception is re-thrown."}, + {lldb::eExceptionStageCatch, "catch", "Stop when the exception is caught."}, +}; + +static constexpr OptionEnumValueElement g_name_match_style[] = { + {lldb::eNameMatchStyleAuto, "auto", + "Match against the leaf nodes of the identifier, or against methods or " + "selectors."}, + {lldb::eNameMatchStyleFull, "full", "Match the full identifier name."}, + {lldb::eNameMatchStyleBase, "base", + "Match against the leaf node of the identifier."}, + {lldb::eNameMatchStyleMethod, "method", "Match only against method names."}, + {lldb::eNameMatchStyleSelector, "selector", + "Match only against selector names."}, + {lldb::eNameMatchStyleRegex, "regex", + "Match the identifier using a regular expression."}, +}; + static constexpr OptionEnumValueElement g_completion_type[] = { {lldb::eNoCompletion, "none", "No completion."}, {lldb::eSourceFileCompletion, "source-file", "Completes to a source file."}, @@ -316,6 +337,8 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = { { lldb::eArgTypeCPUFeatures, "cpu-features", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The CPU feature string." }, { lldb::eArgTypeManagedPlugin, "managed-plugin", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "Plugins managed by the PluginManager" }, { lldb::eArgTypeProtocol, "protocol", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of the protocol." }, + { lldb::eArgTypeExceptionStage, "exception-stage", lldb::CompletionType::eNoCompletion, g_exception_stage, { nullptr, false }, "Specify at which stage of the exception raise to stop." }, + { lldb::eArgTypeNameMatchStyle, "match-style", lldb::CompletionType::eNoCompletion, g_name_match_style, { nullptr, false }, "Specify the kind of match to use when looking up names." }, // clang-format on }; diff --git a/lldb/include/lldb/Interpreter/CommandReturnObject.h b/lldb/include/lldb/Interpreter/CommandReturnObject.h index d53aeb8..f6e6084 100644 --- a/lldb/include/lldb/Interpreter/CommandReturnObject.h +++ b/lldb/include/lldb/Interpreter/CommandReturnObject.h @@ -10,7 +10,7 @@ #define LLDB_INTERPRETER_COMMANDRETURNOBJECT_H #include "lldb/Host/StreamFile.h" -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/StreamTee.h" #include "lldb/Utility/StructuredData.h" @@ -129,8 +129,6 @@ public: void AppendError(llvm::StringRef in_string); - void AppendRawError(llvm::StringRef in_string); - void AppendErrorWithFormat(const char *format, ...) __attribute__((format(printf, 2, 3))); diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h new file mode 100644 index 0000000..49b6013 --- /dev/null +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H +#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H + +#include "lldb/lldb-private.h" + +#include "ScriptedInterface.h" + +namespace lldb_private { +class ScriptedFrameProviderInterface : public ScriptedInterface { +public: + virtual bool AppliesToThread(llvm::StringRef class_name, + lldb::ThreadSP thread_sp) { + return true; + } + + virtual llvm::Expected<StructuredData::GenericSP> + CreatePluginObject(llvm::StringRef class_name, + lldb::StackFrameListSP input_frames, + StructuredData::DictionarySP args_sp) = 0; + + /// Get a description string for the frame provider. + /// + /// This is called by the descriptor to fetch a description from the + /// scripted implementation. Implementations should call a static method + /// on the scripting class to retrieve the description. + /// + /// \param class_name The name of the scripting class implementing the + /// provider. + /// + /// \return A string describing what this frame provider does, or an + /// empty string if no description is available. + virtual std::string GetDescription(llvm::StringRef class_name) { return {}; } + + virtual StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) { + return {}; + } +}; +} // namespace lldb_private + +#endif // LLDB_INTERPRETER_INTERFACES_SCRIPTEDFRAMEPROVIDERINTERFACE_H diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h index a3dc52c..8ace909 100644 --- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h +++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h @@ -39,6 +39,10 @@ public: virtual llvm::SmallVector<AbstractMethodRequirement> GetAbstractMethodRequirements() const = 0; + virtual llvm::Expected<FileSpec> GetScriptedModulePath() { + return llvm::make_error<UnimplementedError>(); + } + llvm::SmallVector<llvm::StringLiteral> const GetAbstractMethods() const { llvm::SmallVector<llvm::StringLiteral> abstract_methods; llvm::transform(GetAbstractMethodRequirements(), abstract_methods.begin(), diff --git a/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h b/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h index 70f035d..e2b84f9 100644 --- a/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h +++ b/lldb/include/lldb/Interpreter/OptionValueFileColonLine.h @@ -41,6 +41,10 @@ public: m_column_number = LLDB_INVALID_COLUMN_NUMBER; } + void SetFile(const FileSpec &file_spec) { m_file_spec = file_spec; } + void SetLine(uint32_t line) { m_line_number = line; } + void SetColumn(uint32_t column) { m_column_number = column; } + void AutoComplete(CommandInterpreter &interpreter, CompletionRequest &request) override; diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index edb80dc..0b91d67 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -16,10 +16,12 @@ #include "lldb/API/SBError.h" #include "lldb/API/SBEvent.h" #include "lldb/API/SBExecutionContext.h" +#include "lldb/API/SBFrameList.h" #include "lldb/API/SBLaunchInfo.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBSymbolContext.h" +#include "lldb/API/SBThread.h" #include "lldb/Breakpoint/BreakpointOptions.h" #include "lldb/Core/PluginInterface.h" #include "lldb/Core/SearchFilter.h" @@ -28,6 +30,7 @@ #include "lldb/Host/StreamFile.h" #include "lldb/Interpreter/Interfaces/OperatingSystemInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedPlatformInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h" #include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h" @@ -537,6 +540,11 @@ public: return {}; } + virtual lldb::ScriptedFrameProviderInterfaceSP + CreateScriptedFrameProviderInterface() { + return {}; + } + virtual lldb::ScriptedThreadPlanInterfaceSP CreateScriptedThreadPlanInterface() { return {}; @@ -573,6 +581,8 @@ public: lldb::StreamSP GetOpaqueTypeFromSBStream(const lldb::SBStream &stream) const; + lldb::ThreadSP GetOpaqueTypeFromSBThread(const lldb::SBThread &exe_ctx) const; + lldb::StackFrameSP GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const; SymbolContext @@ -596,6 +606,9 @@ public: lldb::ExecutionContextRefSP GetOpaqueTypeFromSBExecutionContext( const lldb::SBExecutionContext &exe_ctx) const; + lldb::StackFrameListSP + GetOpaqueTypeFromSBFrameList(const lldb::SBFrameList &exe_ctx) const; + protected: Debugger &m_debugger; lldb::ScriptLanguage m_script_lang; diff --git a/lldb/include/lldb/Symbol/CompileUnit.h b/lldb/include/lldb/Symbol/CompileUnit.h index c5bb080..bb95946 100644 --- a/lldb/include/lldb/Symbol/CompileUnit.h +++ b/lldb/include/lldb/Symbol/CompileUnit.h @@ -93,7 +93,7 @@ public: /// \param[in] user_data /// User data where the SymbolFile parser can store data. /// - /// \param[in] support_file_sp + /// \param[in] support_file_nsp /// The file specification for the source file of this compile /// unit. /// @@ -118,7 +118,7 @@ public: /// An rvalue list of already parsed support files. /// \see lldb::LanguageType CompileUnit(const lldb::ModuleSP &module_sp, void *user_data, - lldb::SupportFileSP support_file_sp, lldb::user_id_t uid, + SupportFileNSP support_file_nsp, lldb::user_id_t uid, lldb::LanguageType language, lldb_private::LazyBool is_optimized, SupportFileList &&support_files = {}); @@ -230,12 +230,12 @@ public: /// Return the primary source spec associated with this compile unit. const FileSpec &GetPrimaryFile() const { - return m_primary_support_file_sp->GetSpecOnly(); + return m_primary_support_file_nsp->GetSpecOnly(); } /// Return the primary source file associated with this compile unit. - lldb::SupportFileSP GetPrimarySupportFile() const { - return m_primary_support_file_sp; + SupportFileNSP GetPrimarySupportFile() const { + return m_primary_support_file_nsp; } /// Get the line table for the compile unit. @@ -430,7 +430,7 @@ protected: /// compile unit. std::vector<SourceModule> m_imported_modules; /// The primary file associated with this compile unit. - lldb::SupportFileSP m_primary_support_file_sp; + SupportFileNSP m_primary_support_file_nsp; /// Files associated with this compile unit's line table and declarations. SupportFileList m_support_files; /// Line table that will get parsed on demand. diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h index 21b3f9a..57bd509 100644 --- a/lldb/include/lldb/Symbol/Function.h +++ b/lldb/include/lldb/Symbol/Function.h @@ -469,12 +469,12 @@ public: /// /// \param[out] line_no /// The line number. - void GetStartLineSourceInfo(lldb::SupportFileSP &source_file_sp, + void GetStartLineSourceInfo(SupportFileNSP &source_file_sp, uint32_t &line_no); using SourceRange = Range<uint32_t, uint32_t>; /// Find the file and line number range of the function. - llvm::Expected<std::pair<lldb::SupportFileSP, SourceRange>> GetSourceInfo(); + llvm::Expected<std::pair<SupportFileNSP, SourceRange>> GetSourceInfo(); /// Get the outgoing call edges from this function, sorted by their return /// PC addresses (in increasing order). diff --git a/lldb/include/lldb/Symbol/LineEntry.h b/lldb/include/lldb/Symbol/LineEntry.h index 8da59cf..adf2e98 100644 --- a/lldb/include/lldb/Symbol/LineEntry.h +++ b/lldb/include/lldb/Symbol/LineEntry.h @@ -136,11 +136,15 @@ struct LineEntry { /// The section offset address range for this line entry. AddressRange range; + /// This gets set for LineEntries created without a valid address range. + /// When set, `LineEntry::IsValid` doesn't check the `range` validity. + bool synthetic = false; + /// The source file, possibly mapped by the target.source-map setting. - lldb::SupportFileSP file_sp; + SupportFileNSP file_sp; /// The original source file, from debug info. - lldb::SupportFileSP original_file_sp; + SupportFileNSP original_file_sp; /// The source line number, or LLDB_INVALID_LINE_NUMBER if there is no line /// number information. diff --git a/lldb/include/lldb/Symbol/ObjectFile.h b/lldb/include/lldb/Symbol/ObjectFile.h index 1b9ae1f..993650b 100644 --- a/lldb/include/lldb/Symbol/ObjectFile.h +++ b/lldb/include/lldb/Symbol/ObjectFile.h @@ -18,6 +18,7 @@ #include "lldb/Utility/Endian.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/FileSpecList.h" +#include "lldb/Utility/NonNullSharedPtr.h" #include "lldb/Utility/StructuredData.h" #include "lldb/Utility/UUID.h" #include "lldb/lldb-private.h" @@ -418,7 +419,7 @@ public: /// Attempts to parse the object header. /// /// This function is used as a test to see if a given plug-in instance can - /// parse the header data already contained in ObjectFile::m_data. If an + /// parse the header data already contained in ObjectFile::m_data_nsp. If an /// object file parser does not recognize that magic bytes in a header, /// false should be returned and the next plug-in can attempt to parse an /// object file. @@ -758,6 +759,12 @@ public: return false; } + /// Returns true if the section is a global offset table section. + virtual bool IsGOTSection(const lldb_private::Section §ion) const { + assert(section.GetObjectFile() == this && "Wrong object file!"); + return false; + } + /// Get a hash that can be used for caching object file releated information. /// /// Data for object files can be cached between runs of debug sessions and @@ -771,6 +778,8 @@ public: std::string GetObjectName() const; protected: + typedef NonNullSharedPtr<lldb_private::DataExtractor> DataExtractorNSP; + // Member variables. FileSpec m_file; Type m_type; @@ -780,8 +789,10 @@ protected: lldb::addr_t m_length; ///< The length of this object file if it is known (can ///be zero if length is unknown or can't be ///determined). - DataExtractor - m_data; ///< The data for this object file so things can be parsed lazily. + DataExtractorNSP m_data_nsp; ///< The data for this object file so things + ///< can be parsed lazily. This shared pointer + ///< will always have a DataExtractor object, + ///< although it may only be default-constructed. lldb::ProcessWP m_process_wp; /// Set if the object file only exists in memory. const lldb::addr_t m_memory_addr; diff --git a/lldb/include/lldb/Symbol/SymbolContext.h b/lldb/include/lldb/Symbol/SymbolContext.h index af2f694e..0834825 100644 --- a/lldb/include/lldb/Symbol/SymbolContext.h +++ b/lldb/include/lldb/Symbol/SymbolContext.h @@ -231,6 +231,20 @@ public: lldb::LanguageType GetLanguage() const; + /// Compares the two symbol contexts, considering that the symbol may or may + /// not be present. If both symbols are present, compare them, if one of the + /// symbols is not present, consider the symbol contexts as equal as long as + /// the other fields are equal. + /// + /// This function exists because SymbolContexts are often created without the + /// symbol, which is filled in later on, after its creation. + static bool CompareConsideringPossiblyNullSymbol(const SymbolContext &lhs, + const SymbolContext &rhs); + + /// Compares the two symbol contexts, except for the symbol field. + static bool CompareWithoutSymbol(const SymbolContext &lhs, + const SymbolContext &rhs); + /// Find a block that defines the function represented by this symbol /// context. /// diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h index 3b4d7bc..9982852c 100644 --- a/lldb/include/lldb/Symbol/SymbolFile.h +++ b/lldb/include/lldb/Symbol/SymbolFile.h @@ -309,6 +309,9 @@ public: virtual void FindFunctions(const Module::LookupInfo &lookup_info, const CompilerDeclContext &parent_decl_ctx, bool include_inlines, SymbolContextList &sc_list); + virtual void FindFunctions(llvm::ArrayRef<Module::LookupInfo> lookup_infos, + const CompilerDeclContext &parent_decl_ctx, + bool include_inlines, SymbolContextList &sc_list); virtual void FindFunctions(const RegularExpression ®ex, bool include_inlines, SymbolContextList &sc_list); diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h index 25b208a..99ea058 100644 --- a/lldb/include/lldb/Symbol/TypeSystem.h +++ b/lldb/include/lldb/Symbol/TypeSystem.h @@ -411,6 +411,18 @@ public: GetIntegralTemplateArgument(lldb::opaque_compiler_type_t type, size_t idx, bool expand_pack); + // DIL + + /// Checks if the type is eligible for integral promotion. + virtual bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type); + + /// Perform integral promotion on a given type. + /// This promotes eligible types (boolean, integers, unscoped enumerations) + /// to a larger integer type according to type system rules. + /// \returns Promoted type. + virtual llvm::Expected<CompilerType> + DoIntegralPromotion(CompilerType from, ExecutionContextScope *exe_scope); + // Dumping types #ifndef NDEBUG diff --git a/lldb/include/lldb/Target/BorrowedStackFrame.h b/lldb/include/lldb/Target/BorrowedStackFrame.h new file mode 100644 index 0000000..72e7777 --- /dev/null +++ b/lldb/include/lldb/Target/BorrowedStackFrame.h @@ -0,0 +1,146 @@ +//===----------------------------------------------------------------------===// +// +// 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_TARGET_BORROWEDSTACKFRAME_H +#define LLDB_TARGET_BORROWEDSTACKFRAME_H + +#include "lldb/Target/StackFrame.h" + +namespace lldb_private { + +/// \class BorrowedStackFrame BorrowedStackFrame.h +/// "lldb/Target/BorrowedStackFrame.h" +/// +/// A wrapper around an existing StackFrame that supersedes its frame indices. +/// +/// This class is useful when you need to present an existing stack frame +/// with a different index, such as when creating synthetic frame views or +/// renumbering frames without copying all the underlying data. +/// +/// All methods delegate to the borrowed frame except for GetFrameIndex() +/// & GetConcreteFrameIndex() which uses the overridden indices. +class BorrowedStackFrame : public StackFrame { +public: + /// Construct a BorrowedStackFrame that wraps an existing frame. + /// + /// \param [in] borrowed_frame_sp + /// The existing StackFrame to borrow from. This frame's data will be + /// used for all operations except frame index queries. + /// + /// \param [in] new_frame_index + /// The frame index to report instead of the borrowed frame's index. + /// + /// \param [in] new_concrete_frame_index + /// Optional concrete frame index. If not provided, defaults to + /// new_frame_index. + BorrowedStackFrame( + lldb::StackFrameSP borrowed_frame_sp, uint32_t new_frame_index, + std::optional<uint32_t> new_concrete_frame_index = std::nullopt); + + ~BorrowedStackFrame() override = default; + + uint32_t GetFrameIndex() const override; + void SetFrameIndex(uint32_t index); + + /// Get the concrete frame index for this borrowed frame. + /// + /// Returns the overridden concrete frame index provided at construction, + /// or LLDB_INVALID_FRAME_ID if the borrowed frame represents an inlined + /// function, since this would require some computation if we chain inlined + /// borrowed stack frames. + /// + /// \return + /// The concrete frame index, or LLDB_INVALID_FRAME_ID for inline frames. + uint32_t GetConcreteFrameIndex() override; + + StackID &GetStackID() override; + + const Address &GetFrameCodeAddress() override; + + Address GetFrameCodeAddressForSymbolication() override; + + bool ChangePC(lldb::addr_t pc) override; + + const SymbolContext & + GetSymbolContext(lldb::SymbolContextItem resolve_scope) override; + + llvm::Error GetFrameBaseValue(Scalar &value) override; + + DWARFExpressionList *GetFrameBaseExpression(Status *error_ptr) override; + + Block *GetFrameBlock() override; + + lldb::RegisterContextSP GetRegisterContext() override; + + VariableList *GetVariableList(bool get_file_globals, + Status *error_ptr) override; + + lldb::VariableListSP + GetInScopeVariableList(bool get_file_globals, + bool must_have_valid_location = false) override; + + lldb::ValueObjectSP GetValueForVariableExpressionPath( + llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic, + uint32_t options, lldb::VariableSP &var_sp, Status &error) override; + + bool HasDebugInformation() override; + + const char *Disassemble() override; + + lldb::ValueObjectSP + GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp, + lldb::DynamicValueType use_dynamic) override; + + bool IsInlined() override; + + bool IsSynthetic() const override; + + bool IsHistorical() const override; + + bool IsArtificial() const override; + + bool IsHidden() override; + + const char *GetFunctionName() override; + + const char *GetDisplayFunctionName() override; + + lldb::ValueObjectSP FindVariable(ConstString name) override; + + SourceLanguage GetLanguage() override; + + SourceLanguage GuessLanguage() override; + + lldb::ValueObjectSP GuessValueForAddress(lldb::addr_t addr) override; + + lldb::ValueObjectSP GuessValueForRegisterAndOffset(ConstString reg, + int64_t offset) override; + + StructuredData::ObjectSP GetLanguageSpecificData() override; + + lldb::RecognizedStackFrameSP GetRecognizedFrame() override; + + /// Get the underlying borrowed frame. + lldb::StackFrameSP GetBorrowedFrame() const; + + bool isA(const void *ClassID) const override; + static bool classof(const StackFrame *obj); + +private: + lldb::StackFrameSP m_borrowed_frame_sp; + uint32_t m_new_frame_index; + uint32_t m_new_concrete_frame_index; + static char ID; + + BorrowedStackFrame(const BorrowedStackFrame &) = delete; + const BorrowedStackFrame &operator=(const BorrowedStackFrame &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_BORROWEDSTACKFRAME_H diff --git a/lldb/include/lldb/Target/DynamicLoader.h b/lldb/include/lldb/Target/DynamicLoader.h index 75bb6cb..4131a57 100644 --- a/lldb/include/lldb/Target/DynamicLoader.h +++ b/lldb/include/lldb/Target/DynamicLoader.h @@ -352,6 +352,7 @@ public: protected: // Utility methods for derived classes + /// Find a module in the target that matches the given file. lldb::ModuleSP FindModuleViaTarget(const FileSpec &file); /// Checks to see if the target module has changed, updates the target diff --git a/lldb/include/lldb/Target/ExecutionContext.h b/lldb/include/lldb/Target/ExecutionContext.h index fe8bce7..8637234 100644 --- a/lldb/include/lldb/Target/ExecutionContext.h +++ b/lldb/include/lldb/Target/ExecutionContext.h @@ -268,7 +268,10 @@ public: m_tid = LLDB_INVALID_THREAD_ID; } - void ClearFrame() { m_stack_id.Clear(); } + void ClearFrame() { + m_stack_id.Clear(); + m_frame_list_wp.reset(); + } protected: // Member variables @@ -279,7 +282,14 @@ protected: ///< object refers to in case the /// backing object changes StackID m_stack_id; ///< The stack ID that this object refers to in case the - ///backing object changes + ///< backing object changes + mutable lldb::StackFrameListWP + m_frame_list_wp; ///< Weak reference to the + ///< frame list that contains + ///< this frame. If we can create a valid + ///< StackFrameListSP from it, we must use it to resolve + ///< the StackID, otherwise, we should ask the Thread's + ///< StackFrameList. }; /// \class ExecutionContext ExecutionContext.h diff --git a/lldb/include/lldb/Target/Language.h b/lldb/include/lldb/Target/Language.h index 9958b6e..d59ea9f 100644 --- a/lldb/include/lldb/Target/Language.h +++ b/lldb/include/lldb/Target/Language.h @@ -165,6 +165,8 @@ public: static Language *FindPlugin(lldb::LanguageType language, llvm::StringRef file_path); + static llvm::Expected<lldb::LanguageType> + GetExceptionLanguageForLanguage(llvm::StringRef lang_name); // return false from callback to stop iterating static void ForEach(llvm::function_ref<IterationAction(Language *)> callback); @@ -318,7 +320,9 @@ public: /// /// This function should only return true if there is a high confidence /// that the name actually belongs to this language. - virtual bool SymbolNameFitsToLanguage(Mangled name) const { return false; } + virtual bool SymbolNameFitsToLanguage(const Mangled &name) const { + return false; + } /// An individual data formatter may apply to several types and cross language /// boundaries. Each of those languages may want to customize the display of diff --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h index 35ffdab..1104722 100644 --- a/lldb/include/lldb/Target/Platform.h +++ b/lldb/include/lldb/Target/Platform.h @@ -127,8 +127,7 @@ public: /// Returns \b true if this Platform plug-in was able to find /// a suitable executable, \b false otherwise. virtual Status ResolveExecutable(const ModuleSpec &module_spec, - lldb::ModuleSP &exe_module_sp, - const FileSpecList *module_search_paths_ptr); + lldb::ModuleSP &exe_module_sp); /// Find a symbol file given a symbol file module specification. /// @@ -304,10 +303,11 @@ public: /// \return /// The Status object for any errors found while searching for /// the binary. - virtual Status GetSharedModule( - const ModuleSpec &module_spec, Process *process, - lldb::ModuleSP &module_sp, const FileSpecList *module_search_paths_ptr, - llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr); + virtual Status + GetSharedModule(const ModuleSpec &module_spec, Process *process, + lldb::ModuleSP &module_sp, + llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, + bool *did_create_ptr); void CallLocateModuleCallbackIfSet(const ModuleSpec &module_spec, lldb::ModuleSP &module_sp, @@ -1039,8 +1039,8 @@ protected: /// predefined trap handlers, this method may be a no-op. virtual void CalculateTrapHandlerSymbolNames() = 0; - Status GetCachedExecutable(ModuleSpec &module_spec, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr); + Status GetCachedExecutable(ModuleSpec &module_spec, + lldb::ModuleSP &module_sp); virtual Status DownloadModuleSlice(const FileSpec &src_file_spec, const uint64_t src_offset, diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index c1f9785..8e6c16c 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -2534,6 +2534,28 @@ void PruneThreadPlans(); void CalculateExecutionContext(ExecutionContext &exe_ctx) override; + /// Associates a file descriptor with the process' STDIO handling + /// and configures an asynchronous reading of that descriptor. + /// + /// This method installs a ConnectionFileDescriptor for the passed file + /// descriptor and starts a dedicated read thread. If the read thread starts + /// successfully, the method also ensures that an IOHandlerProcessSTDIO is + /// created to manage user input to the process. + /// + /// The descriptor's ownership is transferred to the underlying + /// ConnectionFileDescriptor. + /// + /// When data is successfully read from the file descriptor, it is stored in + /// m_stdout_data. There is no differentiation between stdout and stderr. + /// + /// \param[in] fd + /// The file descriptor to use for process STDIO communication. It's + /// assumed to be valid and will be managed by the newly created + /// connection. + /// + /// \see lldb_private::Process::STDIOReadThreadBytesReceived() + /// \see lldb_private::IOHandlerProcessSTDIO + /// \see lldb_private::ConnectionFileDescriptor void SetSTDIOFileDescriptor(int file_descriptor); // Add a permanent region of memory that should never be read or written to. diff --git a/lldb/include/lldb/Target/RemoteAwarePlatform.h b/lldb/include/lldb/Target/RemoteAwarePlatform.h index fb2eecf..de13b18 100644 --- a/lldb/include/lldb/Target/RemoteAwarePlatform.h +++ b/lldb/include/lldb/Target/RemoteAwarePlatform.h @@ -20,10 +20,8 @@ class RemoteAwarePlatform : public Platform { public: using Platform::Platform; - virtual Status - ResolveExecutable(const ModuleSpec &module_spec, - lldb::ModuleSP &exe_module_sp, - const FileSpecList *module_search_paths_ptr) override; + virtual Status ResolveExecutable(const ModuleSpec &module_spec, + lldb::ModuleSP &exe_module_sp) override; bool GetModuleSpec(const FileSpec &module_file_spec, const ArchSpec &arch, ModuleSpec &module_spec) override; diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h index cdbe8ae..4692244 100644 --- a/lldb/include/lldb/Target/StackFrame.h +++ b/lldb/include/lldb/Target/StackFrame.h @@ -43,6 +43,13 @@ namespace lldb_private { class StackFrame : public ExecutionContextScope, public std::enable_shared_from_this<StackFrame> { public: + /// LLVM RTTI support. + /// \{ + static char ID; + virtual bool isA(const void *ClassID) const { return ClassID == &ID; } + static bool classof(const StackFrame *obj) { return obj->isA(&ID); } + /// \} + enum ExpressionPathOption { eExpressionPathOptionCheckPtrVsMember = (1u << 0), eExpressionPathOptionsNoFragileObjcIvar = (1u << 1), @@ -127,7 +134,7 @@ public: lldb::ThreadSP GetThread() const { return m_thread_wp.lock(); } - StackID &GetStackID(); + virtual StackID &GetStackID(); /// Get an Address for the current pc value in this StackFrame. /// @@ -135,7 +142,7 @@ public: /// /// \return /// The Address object set to the current PC value. - const Address &GetFrameCodeAddress(); + virtual const Address &GetFrameCodeAddress(); /// Get the current code Address suitable for symbolication, /// may not be the same as GetFrameCodeAddress(). @@ -153,7 +160,7 @@ public: /// /// \return /// The Address object set to the current PC value. - Address GetFrameCodeAddressForSymbolication(); + virtual Address GetFrameCodeAddressForSymbolication(); /// Change the pc value for a given thread. /// @@ -165,7 +172,7 @@ public: /// \return /// true if the pc was changed. false if this failed -- possibly /// because this frame is not a live StackFrame. - bool ChangePC(lldb::addr_t pc); + virtual bool ChangePC(lldb::addr_t pc); /// Provide a SymbolContext for this StackFrame's current pc value. /// @@ -181,7 +188,8 @@ public: /// \return /// A SymbolContext reference which includes the types of information /// requested by resolve_scope, if they are available. - const SymbolContext &GetSymbolContext(lldb::SymbolContextItem resolve_scope); + virtual const SymbolContext & + GetSymbolContext(lldb::SymbolContextItem resolve_scope); /// Return the Canonical Frame Address (DWARF term) for this frame. /// @@ -199,7 +207,7 @@ public: /// \return /// If there is an error determining the CFA address, return an error /// explaining the failure. Success otherwise. - llvm::Error GetFrameBaseValue(Scalar &value); + virtual llvm::Error GetFrameBaseValue(Scalar &value); /// Get the DWARFExpressionList corresponding to the Canonical Frame Address. /// @@ -211,7 +219,7 @@ public: /// /// \return /// Returns the corresponding DWARF expression, or NULL. - DWARFExpressionList *GetFrameBaseExpression(Status *error_ptr); + virtual DWARFExpressionList *GetFrameBaseExpression(Status *error_ptr); /// Get the current lexical scope block for this StackFrame, if possible. /// @@ -221,7 +229,7 @@ public: /// \return /// A pointer to the current Block. nullptr is returned if this can /// not be provided. - Block *GetFrameBlock(); + virtual Block *GetFrameBlock(); /// Get the RegisterContext for this frame, if possible. /// @@ -235,7 +243,7 @@ public: /// /// \return /// The RegisterContext shared point for this frame. - lldb::RegisterContextSP GetRegisterContext(); + virtual lldb::RegisterContextSP GetRegisterContext(); const lldb::RegisterContextSP &GetRegisterContextSP() const { return m_reg_context_sp; @@ -261,7 +269,8 @@ public: /// /// \return /// A pointer to a list of variables. - VariableList *GetVariableList(bool get_file_globals, Status *error_ptr); + virtual VariableList *GetVariableList(bool get_file_globals, + Status *error_ptr); /// Retrieve the list of variables that are in scope at this StackFrame's /// pc. @@ -280,7 +289,7 @@ public: /// StackFrame's pc. /// \return /// A pointer to a list of variables. - lldb::VariableListSP + virtual lldb::VariableListSP GetInScopeVariableList(bool get_file_globals, bool must_have_valid_location = false); @@ -309,7 +318,7 @@ public: /// /// \return /// A shared pointer to the ValueObject described by var_expr. - lldb::ValueObjectSP GetValueForVariableExpressionPath( + virtual lldb::ValueObjectSP GetValueForVariableExpressionPath( llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic, uint32_t options, lldb::VariableSP &var_sp, Status &error); @@ -318,14 +327,14 @@ public: /// \return /// true if debug information is available for this frame (function, /// compilation unit, block, etc.) - bool HasDebugInformation(); + virtual bool HasDebugInformation(); /// Return the disassembly for the instructions of this StackFrame's /// function as a single C string. /// /// \return /// C string with the assembly instructions for this function. - const char *Disassemble(); + virtual const char *Disassemble(); /// Print a description of this frame using the provided frame format. /// @@ -337,9 +346,9 @@ public: /// /// \return /// \b true if and only if dumping with the given \p format worked. - bool DumpUsingFormat(Stream &strm, - const lldb_private::FormatEntity::Entry *format, - llvm::StringRef frame_marker = {}); + virtual bool DumpUsingFormat(Stream &strm, + const lldb_private::FormatEntity::Entry *format, + llvm::StringRef frame_marker = {}); /// Print a description for this frame using the frame-format formatter /// settings. If the current frame-format settings are invalid, then the @@ -353,8 +362,8 @@ public: /// /// \param [in] frame_marker /// Optional string that will be prepended to the frame output description. - void DumpUsingSettingsFormat(Stream *strm, bool show_unique = false, - const char *frame_marker = nullptr); + virtual void DumpUsingSettingsFormat(Stream *strm, bool show_unique = false, + const char *frame_marker = nullptr); /// Print a description for this frame using a default format. /// @@ -366,7 +375,7 @@ public: /// /// \param [in] show_fullpaths /// Whether to print the full source paths or just the file base name. - void Dump(Stream *strm, bool show_frame_index, bool show_fullpaths); + virtual void Dump(Stream *strm, bool show_frame_index, bool show_fullpaths); /// Print a description of this stack frame and/or the source /// context/assembly for this stack frame. @@ -389,8 +398,9 @@ public: /// /// \return /// Returns true if successful. - bool GetStatus(Stream &strm, bool show_frame_info, bool show_source, - bool show_unique = false, const char *frame_marker = nullptr); + virtual bool GetStatus(Stream &strm, bool show_frame_info, bool show_source, + bool show_unique = false, + const char *frame_marker = nullptr); /// Query whether this frame is a concrete frame on the call stack, or if it /// is an inlined frame derived from the debug information and presented by @@ -401,10 +411,10 @@ public: virtual bool IsInlined(); /// Query whether this frame is synthetic. - bool IsSynthetic() const; + virtual bool IsSynthetic() const; /// Query whether this frame is part of a historical backtrace. - bool IsHistorical() const; + virtual bool IsHistorical() const; /// Query whether this frame is artificial (e.g a synthesized result of /// inferring missing tail call frames from a backtrace). Artificial frames @@ -419,7 +429,7 @@ public: /// Language plugins can use this API to report language-specific /// runtime information about this compile unit, such as additional /// language version details or feature flags. - StructuredData::ObjectSP GetLanguageSpecificData(); + virtual StructuredData::ObjectSP GetLanguageSpecificData(); /// Get the frame's demangled name. /// @@ -439,9 +449,9 @@ public: /// \return /// StackFrame index 0 indicates the currently-executing function. Inline /// frames are included in this frame index count. - uint32_t GetFrameIndex() const; + virtual uint32_t GetFrameIndex() const; - /// Set this frame's synthetic frame index. + /// Set this frame's frame index. void SetFrameIndex(uint32_t index) { m_frame_index = index; } /// Query this frame to find what frame it is in this Thread's @@ -452,7 +462,7 @@ public: /// frames are not included in this frame index count; their concrete /// frame index will be the same as the concrete frame that they are /// derived from. - uint32_t GetConcreteFrameIndex() const { return m_concrete_frame_index; } + virtual uint32_t GetConcreteFrameIndex() { return m_concrete_frame_index; } /// Create a ValueObject for a given Variable in this StackFrame. /// @@ -466,7 +476,7 @@ public: /// /// \return /// A ValueObject for this variable. - lldb::ValueObjectSP + virtual lldb::ValueObjectSP GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp, lldb::DynamicValueType use_dynamic); @@ -474,11 +484,11 @@ public: /// parsing expressions given the execution context. /// /// \return The language of the frame if known. - SourceLanguage GetLanguage(); + virtual SourceLanguage GetLanguage(); /// Similar to GetLanguage(), but is allowed to take a potentially incorrect /// guess if exact information is not available. - SourceLanguage GuessLanguage(); + virtual SourceLanguage GuessLanguage(); /// Attempt to econstruct the ValueObject for a given raw address touched by /// the current instruction. The ExpressionPath should indicate how to get @@ -489,7 +499,7 @@ public: /// /// \return /// The ValueObject if found. If valid, it has a valid ExpressionPath. - lldb::ValueObjectSP GuessValueForAddress(lldb::addr_t addr); + virtual lldb::ValueObjectSP GuessValueForAddress(lldb::addr_t addr); /// Attempt to reconstruct the ValueObject for the address contained in a /// given register plus an offset. The ExpressionPath should indicate how @@ -503,8 +513,8 @@ public: /// /// \return /// The ValueObject if found. If valid, it has a valid ExpressionPath. - lldb::ValueObjectSP GuessValueForRegisterAndOffset(ConstString reg, - int64_t offset); + virtual lldb::ValueObjectSP GuessValueForRegisterAndOffset(ConstString reg, + int64_t offset); /// Attempt to reconstruct the ValueObject for a variable with a given \a name /// from within the current StackFrame, within the current block. The search @@ -517,7 +527,7 @@ public: /// /// \return /// The ValueObject if found. - lldb::ValueObjectSP FindVariable(ConstString name); + virtual lldb::ValueObjectSP FindVariable(ConstString name); // lldb::ExecutionContextScope pure virtual functions lldb::TargetSP CalculateTarget() override; @@ -530,10 +540,25 @@ public: void CalculateExecutionContext(ExecutionContext &exe_ctx) override; - lldb::RecognizedStackFrameSP GetRecognizedFrame(); + virtual lldb::RecognizedStackFrameSP GetRecognizedFrame(); + + /// Get the StackFrameList that contains this frame. + /// + /// Returns the StackFrameList that contains this frame, allowing + /// frames to resolve execution contexts without calling + /// Thread::GetStackFrameList(), which can cause circular dependencies + /// during frame provider initialization. + /// + /// \return + /// The StackFrameList that contains this frame, or nullptr if not set. + virtual lldb::StackFrameListSP GetContainingStackFrameList() const { + return m_frame_list_wp.lock(); + } protected: + friend class BorrowedStackFrame; friend class StackFrameList; + friend class SyntheticStackFrameList; void SetSymbolContextScope(SymbolContextScope *symbol_scope); @@ -574,6 +599,7 @@ protected: /// well as any other frame with the same trait. bool m_behaves_like_zeroth_frame; lldb::VariableListSP m_variable_list_sp; + lldb::StackFrameListWP m_frame_list_wp; /// Value objects for each variable in m_variable_list_sp. ValueObjectList m_variable_list_value_objects; std::optional<lldb::RecognizedStackFrameSP> m_recognized_frame_sp; diff --git a/lldb/include/lldb/Target/StackFrameList.h b/lldb/include/lldb/Target/StackFrameList.h index ea9aab8..539c070 100644 --- a/lldb/include/lldb/Target/StackFrameList.h +++ b/lldb/include/lldb/Target/StackFrameList.h @@ -20,13 +20,13 @@ namespace lldb_private { class ScriptedThread; -class StackFrameList { +class StackFrameList : public std::enable_shared_from_this<StackFrameList> { public: // Constructors and Destructors StackFrameList(Thread &thread, const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames); - ~StackFrameList(); + virtual ~StackFrameList(); /// Get the number of visible frames. Frames may be created if \p can_create /// is true. Synthetic (inline) frames expanded from the concrete frame #0 @@ -101,8 +101,12 @@ public: /// Returns whether we have currently fetched all the frames of a stack. bool WereAllFramesFetched() const; + /// Get the thread associated with this frame list. + Thread &GetThread() const { return m_thread; } + protected: friend class Thread; + friend class ScriptedFrameProvider; friend class ScriptedThread; /// Use this API to build a stack frame list (used for scripted threads, for @@ -208,19 +212,23 @@ protected: /// Whether or not to show synthetic (inline) frames. Immutable. const bool m_show_inlined_frames; + /// Returns true if fetching frames was interrupted, false otherwise. + virtual bool FetchFramesUpTo(uint32_t end_idx, + InterruptionControl allow_interrupt); + private: uint32_t SetSelectedFrameNoLock(lldb_private::StackFrame *frame); lldb::StackFrameSP GetFrameAtIndexNoLock(uint32_t idx, std::shared_lock<std::shared_mutex> &guard); + /// @{ /// These two Fetch frames APIs and SynthesizeTailCallFrames are called in /// GetFramesUpTo, they are the ones that actually add frames. They must be /// called with the writer end of the list mutex held. - - /// Returns true if fetching frames was interrupted, false otherwise. - bool FetchFramesUpTo(uint32_t end_idx, InterruptionControl allow_interrupt); + /// /// Not currently interruptible so returns void. + /// }@ void FetchOnlyConcreteFramesUpTo(uint32_t end_idx); void SynthesizeTailCallFrames(StackFrame &next_frame); @@ -228,6 +236,27 @@ private: const StackFrameList &operator=(const StackFrameList &) = delete; }; +/// A StackFrameList that wraps another StackFrameList and uses a +/// SyntheticFrameProvider to lazily provide frames from either the provider +/// or the underlying real stack frame list. +class SyntheticStackFrameList : public StackFrameList { +public: + SyntheticStackFrameList(Thread &thread, lldb::StackFrameListSP input_frames, + const lldb::StackFrameListSP &prev_frames_sp, + bool show_inline_frames); + +protected: + /// Override FetchFramesUpTo to lazily return frames from the provider + /// or from the actual stack frame list. + bool FetchFramesUpTo(uint32_t end_idx, + InterruptionControl allow_interrupt) override; + +private: + /// The input stack frame list that the provider transforms. + /// This could be a real StackFrameList or another SyntheticStackFrameList. + lldb::StackFrameListSP m_input_frames; +}; + } // namespace lldb_private #endif // LLDB_TARGET_STACKFRAMELIST_H diff --git a/lldb/include/lldb/Target/StackID.h b/lldb/include/lldb/Target/StackID.h index 1846153..3f6a83b 100644 --- a/lldb/include/lldb/Target/StackID.h +++ b/lldb/include/lldb/Target/StackID.h @@ -52,6 +52,7 @@ public: protected: friend class StackFrame; + friend class SyntheticStackFrameList; void SetPC(lldb::addr_t pc, Process *process); void SetCFA(lldb::addr_t cfa, Process *process); diff --git a/lldb/include/lldb/Target/SyntheticFrameProvider.h b/lldb/include/lldb/Target/SyntheticFrameProvider.h new file mode 100644 index 0000000..2d5330c --- /dev/null +++ b/lldb/include/lldb/Target/SyntheticFrameProvider.h @@ -0,0 +1,174 @@ +//===----------------------------------------------------------------------===// +// +// 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_TARGET_SYNTHETICFRAMEPROVIDER_H +#define LLDB_TARGET_SYNTHETICFRAMEPROVIDER_H + +#include "lldb/Core/PluginInterface.h" +#include "lldb/Target/StackFrameList.h" +#include "lldb/Target/ThreadSpec.h" +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" + +#include <optional> +#include <vector> + +namespace lldb_private { + +/// This struct contains the metadata needed to instantiate a frame provider +/// and optional filters to control which threads it applies to. +struct ScriptedFrameProviderDescriptor { + /// Metadata for instantiating the provider (e.g. script class name and args). + lldb::ScriptedMetadataSP scripted_metadata_sp; + + /// Interface for calling static methods on the provider class. + lldb::ScriptedFrameProviderInterfaceSP interface_sp; + + /// Optional list of thread specifications to which this provider applies. + /// If empty, the provider applies to all threads. A thread matches if it + /// satisfies ANY of the specs in this vector (OR logic). + std::vector<ThreadSpec> thread_specs; + + ScriptedFrameProviderDescriptor() = default; + + ScriptedFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp) + : scripted_metadata_sp(metadata_sp) {} + + ScriptedFrameProviderDescriptor(lldb::ScriptedMetadataSP metadata_sp, + const std::vector<ThreadSpec> &specs) + : scripted_metadata_sp(metadata_sp), thread_specs(specs) {} + + /// Get the name of this descriptor (the scripted class name). + llvm::StringRef GetName() const { + return scripted_metadata_sp ? scripted_metadata_sp->GetClassName() : ""; + } + + /// Get the description of this frame provider. + /// + /// \return A string describing what this frame provider does, or an + /// empty string if no description is available. + std::string GetDescription() const; + + /// Check if this descriptor applies to the given thread. + bool AppliesToThread(Thread &thread) const { + // If no thread specs specified, applies to all threads. + if (thread_specs.empty()) + return true; + + // Check if the thread matches any of the specs (OR logic). + for (const auto &spec : thread_specs) { + if (spec.ThreadPassesBasicTests(thread)) + return true; + } + return false; + } + + /// Check if this descriptor has valid metadata for script-based providers. + bool IsValid() const { return scripted_metadata_sp != nullptr; } + + /// Get a unique identifier for this descriptor based on its contents. + /// The ID is computed from the class name and arguments dictionary, + /// not from the pointer address, so two descriptors with the same + /// contents will have the same ID. + uint32_t GetID() const; + + /// Dump a description of this descriptor to the given stream. + void Dump(Stream *s) const; +}; + +/// Base class for all synthetic frame providers. +/// +/// Synthetic frame providers allow modifying or replacing the stack frames +/// shown for a thread. This is useful for: +/// - Providing frames for custom calling conventions or languages. +/// - Reconstructing missing frames from crash dumps or core files. +/// - Adding diagnostic or synthetic frames for debugging. +/// - Visualizing state machines or async execution contexts. +class SyntheticFrameProvider : public PluginInterface { +public: + /// Try to create a SyntheticFrameProvider instance for the given input + /// frames and descriptor. + /// + /// This method iterates through all registered SyntheticFrameProvider + /// plugins and returns the first one that can handle the given descriptor. + /// + /// \param[in] input_frames + /// The input stack frame list that this provider will transform. + /// This could be real unwound frames or output from another provider. + /// + /// \param[in] descriptor + /// The descriptor containing metadata for the provider. + /// + /// \return + /// A shared pointer to a SyntheticFrameProvider if one could be created, + /// otherwise an \a llvm::Error. + static llvm::Expected<lldb::SyntheticFrameProviderSP> + CreateInstance(lldb::StackFrameListSP input_frames, + const ScriptedFrameProviderDescriptor &descriptor); + + /// Try to create a SyntheticFrameProvider instance for the given input + /// frames using a specific C++ plugin. + /// + /// This method directly invokes a specific SyntheticFrameProvider plugin + /// by name, bypassing the descriptor-based plugin iteration. This is useful + /// for C++ plugins that don't require scripted metadata. + /// + /// \param[in] input_frames + /// The input stack frame list that this provider will transform. + /// This could be real unwound frames or output from another provider. + /// + /// \param[in] plugin_name + /// The name of the plugin to use for creating the provider. + /// + /// \param[in] thread_specs + /// Optional list of thread specifications to which this provider applies. + /// If empty, the provider applies to all threads. + /// + /// \return + /// A shared pointer to a SyntheticFrameProvider if one could be created, + /// otherwise an \a llvm::Error. + static llvm::Expected<lldb::SyntheticFrameProviderSP> + CreateInstance(lldb::StackFrameListSP input_frames, + llvm::StringRef plugin_name, + const std::vector<ThreadSpec> &thread_specs = {}); + + ~SyntheticFrameProvider() override; + + virtual std::string GetDescription() const = 0; + + /// Get a single stack frame at the specified index. + /// + /// This method is called lazily - frames are only created when requested. + /// The provider can access its input frames via GetInputFrames() if needed. + /// + /// \param[in] idx + /// The index of the frame to create. + /// + /// \return + /// An Expected containing the StackFrameSP if successful. Returns an + /// error when the index is beyond the last frame to signal the end of + /// the frame list. + virtual llvm::Expected<lldb::StackFrameSP> GetFrameAtIndex(uint32_t idx) = 0; + + /// Get the thread associated with this provider. + Thread &GetThread() { return m_input_frames->GetThread(); } + + /// Get the input frames that this provider transforms. + lldb::StackFrameListSP GetInputFrames() const { return m_input_frames; } + +protected: + SyntheticFrameProvider(lldb::StackFrameListSP input_frames); + + lldb::StackFrameListSP m_input_frames; +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_SYNTHETICFRAMEPROVIDER_H diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 40f9c9b..812a638 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -32,6 +32,7 @@ #include "lldb/Target/PathMappingList.h" #include "lldb/Target/SectionLoadHistory.h" #include "lldb/Target/Statistics.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/ThreadSpec.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/Broadcaster.h" @@ -537,6 +538,7 @@ public: eBroadcastBitWatchpointChanged = (1 << 3), eBroadcastBitSymbolsLoaded = (1 << 4), eBroadcastBitSymbolsChanged = (1 << 5), + eBroadcastBitNewTargetCreated = (1 << 6), }; // These two functions fill out the Broadcaster interface: @@ -556,6 +558,13 @@ public: TargetEventData(const lldb::TargetSP &target_sp, const ModuleList &module_list); + // Constructor for eBroadcastBitNewTargetCreated events. For this event + // type: + // - target_sp is the parent target (the subject/broadcaster of the event) + // - created_target_sp is the newly created target + TargetEventData(const lldb::TargetSP &target_sp, + const lldb::TargetSP &created_target_sp); + ~TargetEventData() override; static llvm::StringRef GetFlavorString(); @@ -570,14 +579,23 @@ public: static lldb::TargetSP GetTargetFromEvent(const Event *event_ptr); + // For eBroadcastBitNewTargetCreated events, returns the newly created + // target. For other event types, returns an invalid target. + static lldb::TargetSP GetCreatedTargetFromEvent(const Event *event_ptr); + static ModuleList GetModuleListFromEvent(const Event *event_ptr); const lldb::TargetSP &GetTarget() const { return m_target_sp; } + const lldb::TargetSP &GetCreatedTarget() const { + return m_created_target_sp; + } + const ModuleList &GetModuleList() const { return m_module_list; } private: lldb::TargetSP m_target_sp; + lldb::TargetSP m_created_target_sp; ModuleList m_module_list; TargetEventData(const TargetEventData &) = delete; @@ -622,6 +640,30 @@ public: /// requirements. llvm::Error SetLabel(llvm::StringRef label); + /// Get the target session name for this target. + /// + /// Provides a meaningful name for IDEs or tools to display for dynamically + /// created targets. Defaults to "Session {ID}" based on the globally unique + /// ID. + /// + /// \return + /// The target session name for this target. + llvm::StringRef GetTargetSessionName() { return m_target_session_name; } + + /// Set the target session name for this target. + /// + /// This should typically be set along with the event + /// eBroadcastBitNewTargetCreated. Useful for scripts or triggers that + /// automatically create targets and want to provide meaningful names that + /// IDEs or other tools can display to help users identify the origin and + /// purpose of each target. + /// + /// \param[in] target_session_name + /// The target session name to set for this target. + void SetTargetSessionName(llvm::StringRef target_session_name) { + m_target_session_name = target_session_name.str(); + } + /// Find a binary on the system and return its Module, /// or return an existing Module that is already in the Target. /// @@ -629,13 +671,20 @@ public: /// or identify a matching Module already present in the Target, /// and return a shared pointer to it. /// + /// Note that this function previously also preloaded the module's symbols + /// depending on a setting. This function no longer does any module + /// preloading because that can potentially cause deadlocks when called in + /// parallel with this function. + /// /// \param[in] module_spec /// The criteria that must be matched for the binary being loaded. /// e.g. UUID, architecture, file path. /// /// \param[in] notify /// If notify is true, and the Module is new to this Target, - /// Target::ModulesDidLoad will be called. + /// Target::ModulesDidLoad will be called. See note in + /// Target::ModulesDidLoad about thread-safety with + /// Target::GetOrCreateModule. /// If notify is false, it is assumed that the caller is adding /// multiple Modules and will call ModulesDidLoad with the /// full list at the end. @@ -697,6 +746,36 @@ public: Status Attach(ProcessAttachInfo &attach_info, Stream *stream); // Optional stream to receive first stop info + /// Add or update a scripted frame provider descriptor for this target. + /// All new threads in this target will check if they match any descriptors + /// to create their frame providers. + /// + /// \param[in] descriptor + /// The descriptor to add or update. + /// + /// \return + /// The descriptor identifier if the registration succeeded, otherwise an + /// llvm::Error. + llvm::Expected<uint32_t> AddScriptedFrameProviderDescriptor( + const ScriptedFrameProviderDescriptor &descriptor); + + /// Remove a scripted frame provider descriptor by id. + /// + /// \param[in] id + /// The id of the descriptor to remove. + /// + /// \return + /// True if a descriptor was removed, false if no descriptor with that + /// id existed. + bool RemoveScriptedFrameProviderDescriptor(uint32_t id); + + /// Clear all scripted frame provider descriptors for this target. + void ClearScriptedFrameProviderDescriptors(); + + /// Get all scripted frame provider descriptors for this target. + const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> & + GetScriptedFrameProviderDescriptors() const; + // This part handles the breakpoints. BreakpointList &GetBreakpointList(bool internal = false); @@ -931,6 +1010,13 @@ public: // the address of its previous instruction and return that address. lldb::addr_t GetBreakableLoadAddress(lldb::addr_t addr); + /// This call may preload module symbols, and may do so in parallel depending + /// on the following target settings: + /// - TargetProperties::GetPreloadSymbols() + /// - TargetProperties::GetParallelModuleLoad() + /// + /// Warning: if preloading is active and this is called in parallel with + /// Target::GetOrCreateModule, this may result in a ABBA deadlock situation. void ModulesDidLoad(ModuleList &module_list); void ModulesDidUnload(ModuleList &module_list, bool delete_locations); @@ -1689,6 +1775,13 @@ protected: PathMappingList m_image_search_paths; TypeSystemMap m_scratch_type_system_map; + /// Map of scripted frame provider descriptors for this target. + /// Keys are the provider descriptors ids, values are the descriptors. + /// Used to initialize frame providers for new threads. + llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> + m_frame_provider_descriptors; + mutable std::recursive_mutex m_frame_provider_descriptors_mutex; + typedef std::map<lldb::LanguageType, lldb::REPLSP> REPLMap; REPLMap m_repl_map; @@ -1705,8 +1798,11 @@ protected: bool m_is_dummy_target; unsigned m_next_persistent_variable_index = 0; lldb::user_id_t m_target_unique_id = - LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; /// The globally unique ID + LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; ///< The globally unique ID /// assigned to this target + std::string m_target_session_name; ///< The target session name for this + /// target, used to name debugging + /// sessions in DAP. /// An optional \a lldb_private::Trace object containing processor trace /// information of this target. lldb::TraceSP m_trace_sp; diff --git a/lldb/include/lldb/Target/TargetList.h b/lldb/include/lldb/Target/TargetList.h index 8827251..d7ff639 100644 --- a/lldb/include/lldb/Target/TargetList.h +++ b/lldb/include/lldb/Target/TargetList.h @@ -216,6 +216,11 @@ private: llvm::StringRef triple_str, LoadDependentFiles load_dependent_files, const OptionGroupPlatform *platform_options, lldb::TargetSP &target_sp); + // Create Target Internal does not modify any state directly, and should not + // be called under the target list mutex. Instead any state changes should + // call into methods which themselves are protected by the target list mutex. + // We need to do this so the locate module call back doesn't cause a re-entry + // dead lock when creating the target. static Status CreateTargetInternal(Debugger &debugger, llvm::StringRef user_exe_path, const ArchSpec &arch, diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index 688c056..46ce192 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1295,6 +1295,17 @@ public: /// an empty std::optional is returned in that case. std::optional<lldb::addr_t> GetPreviousFrameZeroPC(); + lldb::StackFrameListSP GetStackFrameList(); + + llvm::Error + LoadScriptedFrameProvider(const ScriptedFrameProviderDescriptor &descriptor); + + void ClearScriptedFrameProvider(); + + lldb::SyntheticFrameProviderSP GetFrameProvider() const { + return m_frame_provider_sp; + } + protected: friend class ThreadPlan; friend class ThreadList; @@ -1336,8 +1347,6 @@ protected: return StructuredData::ObjectSP(); } - lldb::StackFrameListSP GetStackFrameList(); - void SetTemporaryResumeState(lldb::StateType new_state) { m_temporary_resume_state = new_state; } @@ -1400,6 +1409,9 @@ protected: /// The Thread backed by this thread, if any. lldb::ThreadWP m_backed_thread; + /// The Scripted Frame Provider, if any. + lldb::SyntheticFrameProviderSP m_frame_provider_sp; + private: bool m_extended_info_fetched; // Have we tried to retrieve the m_extended_info // for this thread? diff --git a/lldb/include/lldb/Target/ThreadSpec.h b/lldb/include/lldb/Target/ThreadSpec.h index 7c7c832..63f8f8b 100644 --- a/lldb/include/lldb/Target/ThreadSpec.h +++ b/lldb/include/lldb/Target/ThreadSpec.h @@ -34,6 +34,8 @@ class ThreadSpec { public: ThreadSpec(); + ThreadSpec(Thread &thread); + static std::unique_ptr<ThreadSpec> CreateFromStructuredData(const StructuredData::Dictionary &data_dict, Status &error); diff --git a/lldb/include/lldb/Target/UnixSignals.h b/lldb/include/lldb/Target/UnixSignals.h index a1807d6..590e4d1 100644 --- a/lldb/include/lldb/Target/UnixSignals.h +++ b/lldb/include/lldb/Target/UnixSignals.h @@ -31,6 +31,8 @@ public: llvm::StringRef GetSignalAsStringRef(int32_t signo) const; + llvm::StringRef GetSignalNumberDescription(int32_t signo) const; + std::string GetSignalDescription(int32_t signo, std::optional<int32_t> code = std::nullopt, diff --git a/lldb/include/lldb/Utility/DataExtractor.h b/lldb/include/lldb/Utility/DataExtractor.h index b4960f5..db85b44 100644 --- a/lldb/include/lldb/Utility/DataExtractor.h +++ b/lldb/include/lldb/Utility/DataExtractor.h @@ -334,7 +334,8 @@ public: /// \return /// A pointer to the bytes in this object's data if the offset /// and length are valid, or nullptr otherwise. - const void *GetData(lldb::offset_t *offset_ptr, lldb::offset_t length) const { + virtual const void *GetData(lldb::offset_t *offset_ptr, + lldb::offset_t length) const { const uint8_t *ptr = PeekData(*offset_ptr, length); if (ptr) *offset_ptr += length; @@ -609,17 +610,17 @@ public: /// The extracted uint8_t value. uint8_t GetU8(lldb::offset_t *offset_ptr) const; - uint8_t GetU8_unchecked(lldb::offset_t *offset_ptr) const { + virtual uint8_t GetU8_unchecked(lldb::offset_t *offset_ptr) const { uint8_t val = m_start[*offset_ptr]; *offset_ptr += 1; return val; } - uint16_t GetU16_unchecked(lldb::offset_t *offset_ptr) const; + virtual uint16_t GetU16_unchecked(lldb::offset_t *offset_ptr) const; - uint32_t GetU32_unchecked(lldb::offset_t *offset_ptr) const; + virtual uint32_t GetU32_unchecked(lldb::offset_t *offset_ptr) const; - uint64_t GetU64_unchecked(lldb::offset_t *offset_ptr) const; + virtual uint64_t GetU64_unchecked(lldb::offset_t *offset_ptr) const; /// Extract \a count uint8_t values from \a *offset_ptr. /// /// Extract \a count uint8_t values from the binary data at the offset @@ -829,7 +830,8 @@ public: /// A non-nullptr data pointer if \a offset is a valid offset and /// there are \a length bytes available at that offset, nullptr /// otherwise. - const uint8_t *PeekData(lldb::offset_t offset, lldb::offset_t length) const { + virtual const uint8_t *PeekData(lldb::offset_t offset, + lldb::offset_t length) const { if (ValidOffsetForDataOfSize(offset, length)) return m_start + offset; return nullptr; diff --git a/lldb/include/lldb/Utility/FileSpecList.h b/lldb/include/lldb/Utility/FileSpecList.h index d091a92..b565c2a 100644 --- a/lldb/include/lldb/Utility/FileSpecList.h +++ b/lldb/include/lldb/Utility/FileSpecList.h @@ -41,7 +41,7 @@ public: bool AppendIfUnique(const FileSpec &file); size_t GetSize() const { return m_files.size(); } const FileSpec &GetFileSpecAtIndex(size_t idx) const; - lldb::SupportFileSP GetSupportFileAtIndex(size_t idx) const; + SupportFileNSP GetSupportFileAtIndex(size_t idx) const; size_t FindFileIndex(size_t idx, const FileSpec &file, bool full) const; /// Find a compatible file index. /// diff --git a/lldb/include/lldb/Utility/LLDBLog.h b/lldb/include/lldb/Utility/LLDBLog.h index 18e4a3c..ac360bf 100644 --- a/lldb/include/lldb/Utility/LLDBLog.h +++ b/lldb/include/lldb/Utility/LLDBLog.h @@ -50,7 +50,8 @@ enum class LLDBLog : Log::MaskType { OnDemand = Log::ChannelFlag<31>, Source = Log::ChannelFlag<32>, Disassembler = Log::ChannelFlag<33>, - LLVM_MARK_AS_BITMASK_ENUM(Disassembler), + InstrumentationRuntime = Log::ChannelFlag<34>, + LLVM_MARK_AS_BITMASK_ENUM(InstrumentationRuntime), }; LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); diff --git a/lldb/include/lldb/Utility/NonNullSharedPtr.h b/lldb/include/lldb/Utility/NonNullSharedPtr.h new file mode 100644 index 0000000..e0da293 --- /dev/null +++ b/lldb/include/lldb/Utility/NonNullSharedPtr.h @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// 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_UTILITY_NONNULLSHAREDPTR_H +#define LLDB_UTILITY_NONNULLSHAREDPTR_H + +#include <memory> +#include <utility> + +namespace lldb_private { + +/// A non-nullable shared pointer that always holds a valid object. +/// +/// NonNullSharedPtr is a smart pointer wrapper around std::shared_ptr that +/// guarantees the pointer is never null. +/// +/// This class is used for enforcing invariants at the type level and +/// eliminating entire classes of null pointer bugs. +/// +/// @tparam T The type of object to manage. Must be default-constructible. +template <typename T> class NonNullSharedPtr : private std::shared_ptr<T> { + using Base = std::shared_ptr<T>; + +public: + NonNullSharedPtr(const std::shared_ptr<T> &t) + : Base(t ? t : std::make_shared<T>()) { + assert(t && "NonNullSharedPtr constructed from nullptr"); + } + + NonNullSharedPtr(std::shared_ptr<T> &&t) : Base(std::move(t)) { + const auto b = static_cast<bool>(*this); + assert(b && "NonNullSharedPtr constructed from nullptr"); + if (!b) + Base::operator=(std::make_shared<T>()); + } + + NonNullSharedPtr(const NonNullSharedPtr &other) : Base(other) {} + + NonNullSharedPtr(NonNullSharedPtr &&other) : Base(std::move(other)) {} + + NonNullSharedPtr &operator=(const NonNullSharedPtr &other) { + Base::operator=(other); + return *this; + } + + NonNullSharedPtr &operator=(NonNullSharedPtr &&other) { + Base::operator=(std::move(other)); + return *this; + } + + using Base::operator*; + using Base::operator->; + using Base::get; + using Base::use_count; + using Base::operator bool; + + void swap(NonNullSharedPtr &other) { Base::swap(other); } + + /// Explicitly deleted operations that could introduce nullptr. + /// @{ + void reset() = delete; + void reset(T *ptr) = delete; + /// @} +}; + +} // namespace lldb_private + +/// Specialized swap function for NonNullSharedPtr to enable argument-dependent +/// lookup (ADL) and efficient swapping. +template <typename T> +void swap(lldb_private::NonNullSharedPtr<T> &lhs, + lldb_private::NonNullSharedPtr<T> &rhs) { + lhs.swap(rhs); +} + +#endif diff --git a/lldb/include/lldb/Utility/RangeMap.h b/lldb/include/lldb/Utility/RangeMap.h index e701ae1..24ed4a5 100644 --- a/lldb/include/lldb/Utility/RangeMap.h +++ b/lldb/include/lldb/Utility/RangeMap.h @@ -465,6 +465,10 @@ public: RangeDataVector(Compare compare = Compare()) : m_compare(compare) {} + RangeDataVector(std::initializer_list<AugmentedEntry> entries, + Compare compare = Compare()) + : m_entries(entries), m_compare(compare) {} + ~RangeDataVector() = default; void Append(const Entry &entry) { m_entries.emplace_back(entry); } diff --git a/lldb/include/lldb/Utility/RegisterValue.h b/lldb/include/lldb/Utility/RegisterValue.h index 49aaf68..baf984c 100644 --- a/lldb/include/lldb/Utility/RegisterValue.h +++ b/lldb/include/lldb/Utility/RegisterValue.h @@ -46,7 +46,8 @@ public: eTypeUInt16, eTypeUInt32, eTypeUInt64, - eTypeUInt128, + eTypeUIntN, /// < This value is used when the (integer) register is larger + /// than 64-bits. eTypeFloat, eTypeDouble, eTypeLongDouble, @@ -69,7 +70,7 @@ public: m_scalar = inst; } - explicit RegisterValue(llvm::APInt inst) : m_type(eTypeUInt128) { + explicit RegisterValue(llvm::APInt inst) : m_type(eTypeUIntN) { m_scalar = llvm::APInt(std::move(inst)); } @@ -178,7 +179,7 @@ public: } void operator=(llvm::APInt uint) { - m_type = eTypeUInt128; + m_type = eTypeUIntN; m_scalar = llvm::APInt(std::move(uint)); } @@ -217,8 +218,8 @@ public: m_scalar = uint; } - void SetUInt128(llvm::APInt uint) { - m_type = eTypeUInt128; + void SetUIntN(llvm::APInt uint) { + m_type = eTypeUIntN; m_scalar = std::move(uint); } diff --git a/lldb/include/lldb/Utility/ScriptedMetadata.h b/lldb/include/lldb/Utility/ScriptedMetadata.h index 69c83ed..8523c95 100644 --- a/lldb/include/lldb/Utility/ScriptedMetadata.h +++ b/lldb/include/lldb/Utility/ScriptedMetadata.h @@ -10,7 +10,9 @@ #define LLDB_INTERPRETER_SCRIPTEDMETADATA_H #include "lldb/Utility/ProcessInfo.h" +#include "lldb/Utility/StreamString.h" #include "lldb/Utility/StructuredData.h" +#include "llvm/ADT/Hashing.h" namespace lldb_private { class ScriptedMetadata { @@ -27,11 +29,36 @@ public: } } + ScriptedMetadata(const ScriptedMetadata &other) + : m_class_name(other.m_class_name), m_args_sp(other.m_args_sp) {} + explicit operator bool() const { return !m_class_name.empty(); } llvm::StringRef GetClassName() const { return m_class_name; } StructuredData::DictionarySP GetArgsSP() const { return m_args_sp; } + /// Get a unique identifier for this metadata based on its contents. + /// The ID is computed from the class name and arguments dictionary, + /// not from the pointer address, so two metadata objects with the same + /// contents will have the same ID. + uint32_t GetID() const { + if (m_class_name.empty()) + return 0; + + // Hash the class name. + llvm::hash_code hash = llvm::hash_value(m_class_name); + + // Hash the arguments dictionary if present. + if (m_args_sp) { + StreamString ss; + m_args_sp->GetDescription(ss); + hash = llvm::hash_combine(hash, llvm::hash_value(ss.GetData())); + } + + // Return the lower 32 bits of the hash. + return static_cast<uint32_t>(hash); + } + private: std::string m_class_name; StructuredData::DictionarySP m_args_sp; diff --git a/lldb/include/lldb/Utility/SupportFile.h b/lldb/include/lldb/Utility/SupportFile.h index c389edf..edc327d 100644 --- a/lldb/include/lldb/Utility/SupportFile.h +++ b/lldb/include/lldb/Utility/SupportFile.h @@ -11,6 +11,7 @@ #include "lldb/Utility/Checksum.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/NonNullSharedPtr.h" namespace lldb_private { @@ -76,6 +77,8 @@ protected: const Checksum m_checksum; }; +typedef NonNullSharedPtr<lldb_private::SupportFile> SupportFileNSP; + } // namespace lldb_private #endif // LLDB_UTILITY_SUPPORTFILE_H diff --git a/lldb/include/lldb/Utility/VirtualDataExtractor.h b/lldb/include/lldb/Utility/VirtualDataExtractor.h new file mode 100644 index 0000000..e430dd8 --- /dev/null +++ b/lldb/include/lldb/Utility/VirtualDataExtractor.h @@ -0,0 +1,75 @@ +//===----------------------------------------------------------------------===// +// +// 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_UTILITY_VIRTUALDATAEXTRACTOR_H +#define LLDB_UTILITY_VIRTUALDATAEXTRACTOR_H + +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/RangeMap.h" +#include "lldb/lldb-types.h" + +namespace lldb_private { + +/// A DataExtractor subclass that allows reading data at virtual addresses +/// using a lookup table that maps virtual address ranges to physical offsets. +/// +/// This class maintains a lookup table where each entry contains: +/// - base: starting virtual address for this entry +/// - size: size of this entry in bytes +/// - data: physical offset in the underlying data buffer +/// +/// Reads are translated from virtual addresses to physical offsets using +/// this lookup table. Reads cannot cross entry boundaries and this is +/// enforced with assertions. +class VirtualDataExtractor : public DataExtractor { +public: + /// Type alias for the range map used internally. + /// Maps virtual addresses (base) to physical offsets (data). + using LookupTable = + RangeDataVector<lldb::offset_t, lldb::offset_t, lldb::offset_t>; + + VirtualDataExtractor() = default; + + VirtualDataExtractor(const void *data, lldb::offset_t data_length, + lldb::ByteOrder byte_order, uint32_t addr_size, + LookupTable lookup_table); + + VirtualDataExtractor(const lldb::DataBufferSP &data_sp, + lldb::ByteOrder byte_order, uint32_t addr_size, + LookupTable lookup_table); + + const void *GetData(lldb::offset_t *offset_ptr, + lldb::offset_t length) const override; + + const uint8_t *PeekData(lldb::offset_t offset, + lldb::offset_t length) const override; + + /// Unchecked overrides + /// @{ + uint8_t GetU8_unchecked(lldb::offset_t *offset_ptr) const override; + uint16_t GetU16_unchecked(lldb::offset_t *offset_ptr) const override; + uint32_t GetU32_unchecked(lldb::offset_t *offset_ptr) const override; + uint64_t GetU64_unchecked(lldb::offset_t *offset_ptr) const override; + /// @} + +protected: + /// Find the lookup entry that contains the given virtual address. + const LookupTable::Entry *FindEntry(lldb::offset_t virtual_addr) const; + + /// Validate that a read at a virtual address is within bounds and + /// does not cross entry boundaries. + bool ValidateVirtualRead(lldb::offset_t virtual_addr, + lldb::offset_t length) const; + +private: + LookupTable m_lookup_table; +}; + +} // namespace lldb_private + +#endif // LLDB_UTILITY_VIRTUALDATAEXTRACTOR_H diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h index 0f05d75..9fda0c7 100644 --- a/lldb/include/lldb/ValueObject/DILAST.h +++ b/lldb/include/lldb/ValueObject/DILAST.h @@ -21,6 +21,7 @@ enum class NodeKind { eArraySubscriptNode, eBitExtractionNode, eBooleanLiteralNode, + eCastNode, eErrorNode, eFloatLiteralNode, eIdentifierNode, @@ -33,6 +34,16 @@ enum class NodeKind { enum class UnaryOpKind { AddrOf, // "&" Deref, // "*" + Minus, // "-" + Plus, // "+" +}; + +/// The type casts allowed by DIL. +enum class CastKind { + eEnumeration, ///< Casting from a scalar to an enumeration type + eNullptr, ///< Casting to a nullptr type + eReference, ///< Casting to a reference type + eNone, ///< Type promotion casting }; /// Forward declaration, for use in DIL AST nodes. Definition is at the very @@ -244,6 +255,29 @@ private: bool m_value; }; +class CastNode : public ASTNode { +public: + CastNode(uint32_t location, CompilerType type, ASTNodeUP operand, + CastKind kind) + : ASTNode(location, NodeKind::eCastNode), m_type(type), + m_operand(std::move(operand)), m_cast_kind(kind) {} + + llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override; + + CompilerType GetType() const { return m_type; } + ASTNode *GetOperand() const { return m_operand.get(); } + CastKind GetCastKind() const { return m_cast_kind; } + + static bool classof(const ASTNode *node) { + return node->GetKind() == NodeKind::eCastNode; + } + +private: + CompilerType m_type; + ASTNodeUP m_operand; + CastKind m_cast_kind; +}; + /// This class contains one Visit method for each specialized type of /// DIL AST node. The Visit methods are used to dispatch a DIL AST node to /// the correct function in the DIL expression evaluator for evaluating that @@ -267,6 +301,7 @@ public: Visit(const FloatLiteralNode *node) = 0; virtual llvm::Expected<lldb::ValueObjectSP> Visit(const BooleanLiteralNode *node) = 0; + virtual llvm::Expected<lldb::ValueObjectSP> Visit(const CastNode *node) = 0; }; } // namespace lldb_private::dil diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h index eab3218..2db45a7 100644 --- a/lldb/include/lldb/ValueObject/DILEval.h +++ b/lldb/include/lldb/ValueObject/DILEval.h @@ -60,7 +60,12 @@ private: Visit(const FloatLiteralNode *node) override; llvm::Expected<lldb::ValueObjectSP> Visit(const BooleanLiteralNode *node) override; + llvm::Expected<lldb::ValueObjectSP> Visit(const CastNode *node) override; + /// Perform usual unary conversions on a value. At the moment this + /// includes array-to-pointer and integral promotion for eligible types. + llvm::Expected<lldb::ValueObjectSP> + UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location); llvm::Expected<CompilerType> PickIntegerType(lldb::TypeSystemSP type_system, std::shared_ptr<ExecutionContextScope> ctx, diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h index d17ed66..dd5c3fb 100644 --- a/lldb/include/lldb/ValueObject/DILParser.h +++ b/lldb/include/lldb/ValueObject/DILParser.h @@ -9,8 +9,8 @@ #ifndef LLDB_VALUEOBJECT_DILPARSER_H #define LLDB_VALUEOBJECT_DILPARSER_H +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Target/ExecutionContextScope.h" -#include "lldb/Utility/DiagnosticsRendering.h" #include "lldb/Utility/Status.h" #include "lldb/ValueObject/DILAST.h" #include "lldb/ValueObject/DILLexer.h" @@ -101,6 +101,12 @@ private: ASTNodeUP ParseFloatingPointLiteral(); ASTNodeUP ParseBooleanLiteral(); + ASTNodeUP ParseCastExpression(); + std::optional<CompilerType> ParseBuiltinType(); + std::optional<CompilerType> ParseTypeId(); + CompilerType ResolveTypeDeclarators(CompilerType type, + const std::vector<Token> &ptr_operators); + void BailOut(const std::string &error, uint32_t loc, uint16_t err_len); void Expect(Token::Kind kind); diff --git a/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h b/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h index 063d796..1a82fd7 100644 --- a/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h +++ b/lldb/include/lldb/ValueObject/ValueObjectSynthetic.h @@ -123,6 +123,11 @@ public: void SetLanguageFlags(uint64_t flags) override; + void + GetExpressionPath(Stream &stream, + GetExpressionPathFormat epformat = + eGetExpressionPathFormatDereferencePointers) override; + protected: bool UpdateValue() override; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 1a7db8f..4cbbabb 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -542,6 +542,7 @@ enum InstrumentationRuntimeType { eInstrumentationRuntimeTypeMainThreadChecker = 0x0003, eInstrumentationRuntimeTypeSwiftRuntimeReporting = 0x0004, eInstrumentationRuntimeTypeLibsanitizersAsan = 0x0005, + eInstrumentationRuntimeTypeBoundsSafety = 0x0006, eNumInstrumentationRuntimeTypes }; @@ -670,6 +671,8 @@ enum CommandArgumentType { eArgTypeCPUFeatures, eArgTypeManagedPlugin, eArgTypeProtocol, + eArgTypeExceptionStage, + eArgTypeNameMatchStyle, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; @@ -1401,6 +1404,22 @@ enum StopDisassemblyType { eStopDisassemblyTypeAlways }; +enum ExceptionStage { + eExceptionStageCreate = (1 << 0), + eExceptionStageThrow = (1 << 1), + eExceptionStageReThrow = (1 << 2), + eExceptionStageCatch = (1 << 3) +}; + +enum NameMatchStyle { + eNameMatchStyleAuto = eFunctionNameTypeAuto, + eNameMatchStyleFull = eFunctionNameTypeFull, + eNameMatchStyleBase = eFunctionNameTypeBase, + eNameMatchStyleMethod = eFunctionNameTypeMethod, + eNameMatchStyleSelector = eFunctionNameTypeSelector, + eNameMatchStyleRegex = eFunctionNameTypeSelector << 1 +}; + } // namespace lldb #endif // LLDB_LLDB_ENUMERATIONS_H diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h index af5656b..ccfe5ef 100644 --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -188,6 +188,7 @@ class Scalar; class ScriptInterpreter; class ScriptInterpreterLocker; class ScriptedFrameInterface; +class ScriptedFrameProviderInterface; class ScriptedMetadata; class ScriptedBreakpointInterface; class ScriptedPlatformInterface; @@ -235,6 +236,7 @@ class SymbolVendor; class Symtab; class SyntheticChildren; class SyntheticChildrenFrontEnd; +class SyntheticFrameProvider; class SystemRuntime; class Progress; class Target; @@ -340,6 +342,7 @@ typedef std::shared_ptr<lldb_private::CompileUnit> CompUnitSP; typedef std::shared_ptr<lldb_private::DataBuffer> DataBufferSP; typedef std::shared_ptr<lldb_private::WritableDataBuffer> WritableDataBufferSP; typedef std::shared_ptr<lldb_private::DataExtractor> DataExtractorSP; +typedef std::unique_ptr<lldb_private::DataExtractor> DataExtractorUP; typedef std::shared_ptr<lldb_private::Debugger> DebuggerSP; typedef std::weak_ptr<lldb_private::Debugger> DebuggerWP; typedef std::shared_ptr<lldb_private::Disassembler> DisassemblerSP; @@ -411,6 +414,10 @@ typedef std::shared_ptr<lldb_private::ScriptSummaryFormat> typedef std::shared_ptr<lldb_private::ScriptInterpreter> ScriptInterpreterSP; typedef std::shared_ptr<lldb_private::ScriptedFrameInterface> ScriptedFrameInterfaceSP; +typedef std::shared_ptr<lldb_private::ScriptedFrameProviderInterface> + ScriptedFrameProviderInterfaceSP; +typedef std::shared_ptr<lldb_private::SyntheticFrameProvider> + SyntheticFrameProviderSP; typedef std::shared_ptr<lldb_private::ScriptedMetadata> ScriptedMetadataSP; typedef std::unique_ptr<lldb_private::ScriptedPlatformInterface> ScriptedPlatformInterfaceUP; @@ -433,6 +440,7 @@ typedef std::unique_ptr<lldb_private::SourceManager> SourceManagerUP; typedef std::shared_ptr<lldb_private::StackFrame> StackFrameSP; typedef std::weak_ptr<lldb_private::StackFrame> StackFrameWP; typedef std::shared_ptr<lldb_private::StackFrameList> StackFrameListSP; +typedef std::weak_ptr<lldb_private::StackFrameList> StackFrameListWP; typedef std::shared_ptr<lldb_private::StackFrameRecognizer> StackFrameRecognizerSP; typedef std::unique_ptr<lldb_private::StackFrameRecognizerManager> @@ -487,7 +495,6 @@ typedef std::shared_ptr<lldb_private::TypeSummaryImpl> TypeSummaryImplSP; typedef std::shared_ptr<lldb_private::TypeSummaryOptions> TypeSummaryOptionsSP; typedef std::shared_ptr<lldb_private::ScriptedSyntheticChildren> ScriptedSyntheticChildrenSP; -typedef std::shared_ptr<lldb_private::SupportFile> SupportFileSP; typedef std::shared_ptr<lldb_private::UnixSignals> UnixSignalsSP; typedef std::weak_ptr<lldb_private::UnixSignals> UnixSignalsWP; typedef std::shared_ptr<lldb_private::UnwindAssembly> UnwindAssemblySP; diff --git a/lldb/include/lldb/lldb-private-interfaces.h b/lldb/include/lldb/lldb-private-interfaces.h index 249b25c..52806ee 100644 --- a/lldb/include/lldb/lldb-private-interfaces.h +++ b/lldb/include/lldb/lldb-private-interfaces.h @@ -15,6 +15,7 @@ #include "lldb/lldb-types.h" #include <memory> #include <set> +#include <vector> namespace llvm { namespace json { @@ -25,6 +26,7 @@ class Value; namespace lldb_private { class ScriptedInterfaceUsages; +struct ScriptedFrameProviderDescriptor; typedef lldb::ABISP (*ABICreateInstance)(lldb::ProcessSP process_sp, const ArchSpec &arch); typedef std::unique_ptr<Architecture> (*ArchitectureCreateInstance)( @@ -86,6 +88,14 @@ typedef lldb::RegisterTypeBuilderSP (*RegisterTypeBuilderCreateInstance)( Target &target); typedef lldb::ScriptInterpreterSP (*ScriptInterpreterCreateInstance)( Debugger &debugger); +typedef llvm::Expected<lldb::SyntheticFrameProviderSP> ( + *ScriptedFrameProviderCreateInstance)( + lldb::StackFrameListSP input_frames, + const lldb_private::ScriptedFrameProviderDescriptor &descriptor); +typedef llvm::Expected<lldb::SyntheticFrameProviderSP> ( + *SyntheticFrameProviderCreateInstance)( + lldb::StackFrameListSP input_frames, + const std::vector<lldb_private::ThreadSpec> &thread_specs); typedef SymbolFile *(*SymbolFileCreateInstance)(lldb::ObjectFileSP objfile_sp); typedef SymbolVendor *(*SymbolVendorCreateInstance)( const lldb::ModuleSP &module_sp, diff --git a/lldb/include/lldb/lldb-private-types.h b/lldb/include/lldb/lldb-private-types.h index b82a2b8..185467e 100644 --- a/lldb/include/lldb/lldb-private-types.h +++ b/lldb/include/lldb/lldb-private-types.h @@ -102,13 +102,18 @@ struct RegisterSet { /// A type-erased pair of llvm::dwarf::SourceLanguageName and version. struct SourceLanguage { SourceLanguage() = default; - SourceLanguage(lldb::LanguageType language_type); + explicit SourceLanguage(lldb::LanguageType language_type); + SourceLanguage(uint16_t name, uint32_t version) : name(name), version(version) {} - SourceLanguage(std::optional<std::pair<uint16_t, uint32_t>> name_vers) + + explicit SourceLanguage( + std::optional<std::pair<uint16_t, uint32_t>> name_vers) : name(name_vers ? name_vers->first : 0), version(name_vers ? name_vers->second : 0) {} - operator bool() const { return name > 0; } + + explicit operator bool() const { return name > 0; } + lldb::LanguageType AsLanguageType() const; llvm::StringRef GetDescription() const; bool IsC() const; diff --git a/lldb/packages/Python/lldbsuite/test/builders/builder.py b/lldb/packages/Python/lldbsuite/test/builders/builder.py index 96c7b39..024c9f1 100644 --- a/lldb/packages/Python/lldbsuite/test/builders/builder.py +++ b/lldb/packages/Python/lldbsuite/test/builders/builder.py @@ -258,6 +258,7 @@ class Builder: "gmodules": {"MAKE_DSYM": "NO", "MAKE_GMODULES": "YES"}, "debug_names": {"MAKE_DEBUG_NAMES": "YES"}, "dwp": {"MAKE_DSYM": "NO", "MAKE_DWP": "YES"}, + "pdb": {"MAKE_PDB": "YES"}, } # Collect all flags, with later options overriding earlier ones diff --git a/lldb/packages/Python/lldbsuite/test/builders/darwin.py b/lldb/packages/Python/lldbsuite/test/builders/darwin.py index a023bda..eebe0ef 100644 --- a/lldb/packages/Python/lldbsuite/test/builders/darwin.py +++ b/lldb/packages/Python/lldbsuite/test/builders/darwin.py @@ -60,7 +60,7 @@ def get_triple_str(arch, vendor, os, version, env): component = [arch, vendor, os + version] if env: - components.append(env) + component.append(env) return "-".join(component) diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py index 454196e..7311b17 100644 --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -647,6 +647,31 @@ def skipIfOutOfTreeDebugserver(func): return skipTestIfFn(is_out_of_tree_debugserver)(func) +def skipIfOutOfTreeLibunwind(func): + """Decorate the item to skip tests if libunwind was not built in-tree.""" + + def is_out_of_tree_libunwind(): + if not configuration.llvm_tools_dir: + return "out-of-tree libunwind" + + # llvm_tools_dir is typically <build>/bin, so lib is a sibling. + llvm_lib_dir = os.path.join( + os.path.dirname(configuration.llvm_tools_dir), "lib" + ) + + if not os.path.isdir(llvm_lib_dir): + return "out-of-tree libunwind" + + # Check for libunwind library (any extension). + for filename in os.listdir(llvm_lib_dir): + if filename.startswith("libunwind.") or filename.startswith("unwind."): + return None + + return "out-of-tree libunwind" + + return skipTestIfFn(is_out_of_tree_libunwind)(func) + + def skipIfRemote(func): """Decorate the item to skip tests if testing remotely.""" return unittest.skipIf(lldb.remote_platform, "skip on remote platform")(func) @@ -1034,6 +1059,16 @@ def skipUnlessAddressSanitizer(func): return skipTestIfFn(is_compiler_with_address_sanitizer)(func) +def skipUnlessBoundsSafety(func): + """Decorate the item to skip test unless Clang -fbounds-safety is supported.""" + + def is_compiler_with_bounds_safety(): + if not _compiler_supports(lldbplatformutil.getCompiler(), "-fbounds-safety"): + return "Compiler cannot compile with -fbounds-safety" + return None + + return skipTestIfFn(is_compiler_with_bounds_safety)(func) + def skipIfAsan(func): """Skip this test if the environment is set up to run LLDB *itself* under ASAN.""" return skipTestIfFn(is_running_under_asan)(func) diff --git a/lldb/packages/Python/lldbsuite/test/dotest.py b/lldb/packages/Python/lldbsuite/test/dotest.py index 63f7df4..f280bd2 100644 --- a/lldb/packages/Python/lldbsuite/test/dotest.py +++ b/lldb/packages/Python/lldbsuite/test/dotest.py @@ -1108,11 +1108,7 @@ def run_suite(): checkDAPSupport() skipped_categories_list = ", ".join(configuration.skip_categories) - print( - "Skipping the following test categories: {}".format( - configuration.skip_categories - ) - ) + print(f"Skipping the following test categories: {skipped_categories_list}") for testdir in configuration.testdirs: for dirpath, dirnames, filenames in os.walk(testdir): diff --git a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py index bd2fdc0..4c40299 100644 --- a/lldb/packages/Python/lldbsuite/test/gdbclientutils.py +++ b/lldb/packages/Python/lldbsuite/test/gdbclientutils.py @@ -264,31 +264,31 @@ class MockGDBServerResponder: return self.other(packet) - def qsProcessInfo(self): + def qsProcessInfo(self) -> str: return "E04" - def qfProcessInfo(self, packet): + def qfProcessInfo(self, packet) -> str: return "E04" - def jGetLoadedDynamicLibrariesInfos(self, packet): + def jGetLoadedDynamicLibrariesInfos(self, packet) -> str: return "" - def qGetWorkingDir(self): + def qGetWorkingDir(self) -> str: return "2f" - def qOffsets(self): + def qOffsets(self) -> str: return "" - def qProcessInfo(self): + def qProcessInfo(self) -> str: return "" - def qHostInfo(self): + def qHostInfo(self) -> str: return "ptrsize:8;endian:little;" - def qEcho(self, num: int): + def qEcho(self, num: int) -> str: return "E04" - def qQueryGDBServer(self): + def qQueryGDBServer(self) -> str: return "E04" def interrupt(self): @@ -300,10 +300,10 @@ class MockGDBServerResponder: def vCont(self, packet): raise self.UnexpectedPacketException() - def A(self, packet): + def A(self, packet) -> str: return "" - def D(self, packet): + def D(self, packet) -> str: return "OK" def readRegisters(self) -> str: @@ -312,40 +312,40 @@ class MockGDBServerResponder: def readRegister(self, register: int) -> str: return "00000000" - def writeRegisters(self, registers_hex): + def writeRegisters(self, registers_hex) -> str: return "OK" - def writeRegister(self, register, value_hex): + def writeRegister(self, register, value_hex) -> str: return "OK" - def readMemory(self, addr, length): + def readMemory(self, addr, length) -> str: return "00" * length - def x(self, addr, length): + def x(self, addr, length) -> str: return "" - def writeMemory(self, addr, data_hex): + def writeMemory(self, addr, data_hex) -> str: return "OK" - def qSymbol(self, symbol_args): + def qSymbol(self, symbol_args) -> str: return "OK" - def qSupported(self, client_supported): + def qSupported(self, client_supported) -> str: return "qXfer:features:read+;PacketSize=3fff;QStartNoAckMode+" - def qfThreadInfo(self): + def qfThreadInfo(self) -> str: return "l" - def qsThreadInfo(self): + def qsThreadInfo(self) -> str: return "l" - def qC(self): + def qC(self) -> str: return "QC0" - def QEnableErrorStrings(self): + def QEnableErrorStrings(self) -> str: return "OK" - def haltReason(self): + def haltReason(self) -> str: # SIGINT is 2, return type is 2 digit hex string return "S02" @@ -360,50 +360,50 @@ class MockGDBServerResponder: def vAttach(self, pid): raise self.UnexpectedPacketException() - def selectThread(self, op, thread_id): + def selectThread(self, op, thread_id) -> str: return "OK" - def setBreakpoint(self, packet): + def setBreakpoint(self, packet) -> str: raise self.UnexpectedPacketException() - def threadStopInfo(self, threadnum): + def threadStopInfo(self, threadnum) -> str: return "" - def other(self, packet): + def other(self, packet) -> str: # empty string means unsupported return "" - def QThreadSuffixSupported(self): + def QThreadSuffixSupported(self) -> str: return "" - def QListThreadsInStopReply(self): + def QListThreadsInStopReply(self) -> str: return "" - def qMemoryRegionInfo(self, addr): + def qMemoryRegionInfo(self, addr) -> str: return "" - def qPathComplete(self): + def qPathComplete(self) -> str: return "" - def vFile(self, packet): + def vFile(self, packet) -> str: return "" - def vRun(self, packet): + def vRun(self, packet) -> str: return "" def qLaunchGDBServer(self, host): raise self.UnexpectedPacketException() - def qLaunchSuccess(self): + def qLaunchSuccess(self) -> str: return "" - def QEnvironment(self, packet): + def QEnvironment(self, packet) -> str: return "OK" - def QEnvironmentHexEncoded(self, packet): + def QEnvironmentHexEncoded(self, packet) -> str: return "OK" - def qRegisterInfo(self, num): + def qRegisterInfo(self, num) -> str: return "" def k(self): diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index b92de94..8c1eea9 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -1791,6 +1791,11 @@ class LLDBTestCaseFactory(type): if can_replicate ] + # PDB is off by default, because it has a lot of failures right now. + # See llvm.org/pr149498 + if original_testcase.TEST_WITH_PDB_DEBUG_INFO: + dbginfo_categories.append("pdb") + xfail_for_debug_info_cat_fn = getattr( attrvalue, "__xfail_for_debug_info_cat_fn__", no_reason ) @@ -1878,6 +1883,13 @@ class TestBase(Base, metaclass=LLDBTestCaseFactory): # test multiple times with various debug info types. NO_DEBUG_INFO_TESTCASE = False + TEST_WITH_PDB_DEBUG_INFO = False + """ + Subclasses can set this to True to test with PDB in addition to the other debug info + types. This id off by default because many tests will fail due to missing functionality in PDB. + See llvm.org/pr149498. + """ + def generateSource(self, source): template = source + ".template" temp = os.path.join(self.getSourceDir(), template) diff --git a/lldb/packages/Python/lldbsuite/test/lldbutil.py b/lldb/packages/Python/lldbsuite/test/lldbutil.py index b8a78b7..e3f64fb 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbutil.py +++ b/lldb/packages/Python/lldbsuite/test/lldbutil.py @@ -319,13 +319,27 @@ def sort_stopped_threads( # Utility functions for setting breakpoints # ================================================== +g_use_break_add = True + + +def set_use_break_add(use_it): + global g_use_break_add + g_use_break_add = use_it + + +def get_use_break_add(): + global g_use_break_add + return g_use_break_add def run_break_set_by_script( test, class_name, extra_options=None, num_expected_locations=1 ): """Set a scripted breakpoint. Check that it got the right number of locations.""" test.assertTrue(class_name is not None, "Must pass in a class name.") - command = "breakpoint set -P " + class_name + if get_use_break_add(): + command = f"breakpoint add scripted -P {class_name}" + else: + command = "breakpoint set -P " + class_name if extra_options is not None: command += " " + extra_options @@ -333,7 +347,6 @@ def run_break_set_by_script( check_breakpoint_result(test, break_results, num_locations=num_expected_locations) return get_bpno_from_match(break_results) - def run_break_set_by_file_and_line( test, file_name, @@ -353,10 +366,16 @@ def run_break_set_by_file_and_line( If loc_exact is true, we check that there is one location, and that location must be at the input file and line number. """ - if file_name is None: - command = "breakpoint set -l %d" % (line_number) + if get_use_break_add(): + if file_name is None: + command = f"breakpoint add file {line_number} " + else: + command = f"breakpoint add file -f {file_name} -l {line_number} " else: - command = 'breakpoint set -f "%s" -l %d' % (file_name, line_number) + if file_name is None: + command = "breakpoint set -l %d" % (line_number) + else: + command = 'breakpoint set -f "%s" -l %d' % (file_name, line_number) if module_name: command += " --shlib '%s'" % (module_name) @@ -395,7 +414,10 @@ def run_break_set_by_symbol( If sym_exact is true, then the output symbol must match the input exactly, otherwise we do a substring match. """ - command = 'breakpoint set -n "%s"' % (symbol) + if get_use_break_add(): + command = f"breakpoint add name" + else: + command = 'breakpoint set -n "%s"' % (symbol) if module_name: command += " --shlib '%s'" % (module_name) @@ -403,6 +425,9 @@ def run_break_set_by_symbol( if extra_options: command += " " + extra_options + if get_use_break_add(): + command += f" -- '{symbol}'" + break_results = run_break_set_command(test, command) if num_expected_locations == 1 and sym_exact: @@ -426,7 +451,10 @@ def run_break_set_by_selector( ): """Set a breakpoint by selector. Common options are the same as run_break_set_by_file_and_line.""" - command = 'breakpoint set -S "%s"' % (selector) + if get_use_break_add(): + command = f"breakpoint add name --match-style selector '{selector}'" + else: + command = 'breakpoint set -S "%s"' % (selector) if module_name: command += ' --shlib "%s"' % (module_name) @@ -458,7 +486,10 @@ def run_break_set_by_regexp( ): """Set a breakpoint by regular expression match on symbol name. Common options are the same as run_break_set_by_file_and_line.""" - command = 'breakpoint set -r "%s"' % (regexp) + if get_use_break_add(): + command = f"breakpoint add name --match-style regex '{regexp}'" + else: + command = 'breakpoint set -r "%s"' % (regexp) if extra_options: command += " " + extra_options @@ -473,10 +504,16 @@ def run_break_set_by_source_regexp( test, regexp, extra_options=None, num_expected_locations=-1 ): """Set a breakpoint by source regular expression. Common options are the same as run_break_set_by_file_and_line.""" - command = 'breakpoint set -p "%s"' % (regexp) + if get_use_break_add(): + command = "breakpoint add pattern" + else: + command = 'breakpoint set -p "%s"' % (regexp) if extra_options: command += " " + extra_options + if get_use_break_add(): + command += f" -- {regexp}" + break_results = run_break_set_command(test, command) check_breakpoint_result(test, break_results, num_locations=num_expected_locations) @@ -493,7 +530,11 @@ def run_break_set_by_file_colon_line( extra_options=None, num_expected_locations=-1, ): - command = 'breakpoint set -y "%s"' % (specifier) + if get_use_break_add(): + command = f"breakpoint add file '{specifier}'" + else: + command = 'breakpoint set -y "%s"' % (specifier) + if extra_options: command += " " + extra_options diff --git a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules index 28cae54..55dbd39 100644 --- a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules +++ b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules @@ -249,6 +249,10 @@ ifeq ($(CC_TYPE), clang) MODULE_DEBUG_INFO_FLAGS += -gmodules endif +ifeq "$(MAKE_PDB)" "YES" + DEBUG_INFO_FLAG ?= -g -gcodeview +endif + # If the OS is Windows, we need to pass -gdwarf to clang, otherwise it will build # with codeview by default but all the tests rely on dwarf. ifeq "$(OS)" "Windows_NT" @@ -290,6 +294,11 @@ ifeq "$(MAKE_DEBUG_NAMES)" "YES" CFLAGS += -gpubnames endif +# Enable GNU POSIX extensions (e.g. kill(), usleep(), getpgid(), ...) +ifeq "$(OS)" "Linux" + CFLAGS += -D_DEFAULT_SOURCE +endif + ifeq "$(USE_PRIVATE_MODULE_CACHE)" "YES" THE_CLANG_MODULE_CACHE_DIR := $(BUILDDIR)/private-module-cache else @@ -330,9 +339,11 @@ endif # library to make ASAN tests work for most users, including the bots. ifeq "$(OS)" "Darwin" ifneq "$(ASAN_OPTIONS)" "" -LD_FLAGS += -Wl,-lto_library -Wl,$(shell dirname $(shell xcrun -find clang))/../lib/libLTO.dylib +ASAN_LDFLAGS = -Wl,-lto_library -Wl,$(shell dirname $(shell xcrun -find clang))/../lib/libLTO.dylib endif endif +LDFLAGS += $(ASAN_LDFLAGS) + OBJECTS = EXE ?= a.out diff --git a/lldb/packages/Python/lldbsuite/test/test_categories.py b/lldb/packages/Python/lldbsuite/test/test_categories.py index 1f6e8a7..b8a764f 100644 --- a/lldb/packages/Python/lldbsuite/test/test_categories.py +++ b/lldb/packages/Python/lldbsuite/test/test_categories.py @@ -12,7 +12,13 @@ from lldbsuite.support import gmodules # Key: Category name # Value: should be used in lldbtest's debug-info replication -debug_info_categories = {"dwarf": True, "dwo": True, "dsym": True, "gmodules": False} +debug_info_categories = { + "dwarf": True, + "dwo": True, + "dsym": True, + "pdb": False, + "gmodules": False, +} all_categories = { "basic_process": "Basic process execution sniff tests.", @@ -34,6 +40,7 @@ all_categories = { "lldb-dap": "Tests for the Debug Adapter Protocol with lldb-dap", "llgs": "Tests for the gdb-server functionality of lldb-server", "msvcstl": "Test for MSVC STL data formatters", + "pdb": "Tests that can be run with PDB debug information", "pexpect": "Tests requiring the pexpect library to be available", "objc": "Tests related to the Objective-C programming language support", "pyapi": "Tests related to the Python API", @@ -65,6 +72,8 @@ def is_supported_on_platform(category, platform, compiler_path): if platform not in ["darwin", "macosx", "ios", "watchos", "tvos", "bridgeos"]: return False return gmodules.is_compiler_clang_with_gmodules(compiler_path) + elif category == "pdb": + return platform == "windows" return True diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index d892c01..7a9d5a8 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -32,6 +32,10 @@ from typing import ( # timeout by a factor of 10 if ASAN is enabled. DEFAULT_TIMEOUT = 10 * (10 if ("ASAN_OPTIONS" in os.environ) else 1) +# See lldbtest.Base.spawnSubprocess, which should help ensure any processes +# created by the DAP client are terminated correctly when the test ends. +SpawnHelperCallback = Callable[[str, List[str], List[str]], subprocess.Popen] + ## DAP type references @@ -187,18 +191,25 @@ class NotSupportedError(KeyError): class DebugCommunication(object): + @property + def is_stopped(self) -> bool: + """Returns True if the debuggee is stopped, otherwise False.""" + return len(self.thread_stop_reasons) > 0 or self.exit_status is not None + def __init__( self, recv: BinaryIO, send: BinaryIO, - init_commands: list[str], - log_file: Optional[TextIO] = None, + init_commands: Optional[List[str]] = None, + log_file: Optional[str] = None, + spawn_helper: Optional[SpawnHelperCallback] = None, ): # For debugging test failures, try setting `trace_file = sys.stderr`. self.trace_file: Optional[TextIO] = None self.log_file = log_file self.send = send self.recv = recv + self.spawn_helper = spawn_helper # Packets that have been received and processed but have not yet been # requested by a test case. @@ -211,7 +222,7 @@ class DebugCommunication(object): self._recv_thread = threading.Thread(target=self._read_packet_thread) # session state - self.init_commands = init_commands + self.init_commands = init_commands if init_commands else [] self.exit_status: Optional[int] = None self.capabilities: Dict = {} self.initialized: bool = False @@ -310,11 +321,6 @@ class DebugCommunication(object): output += self.get_output(category, clear=clear) return output - def _enqueue_recv_packet(self, packet: Optional[ProtocolMessage]): - with self.recv_condition: - self.recv_packets.append(packet) - self.recv_condition.notify() - def _handle_recv_packet(self, packet: Optional[ProtocolMessage]) -> bool: """Handles an incoming packet. @@ -385,7 +391,7 @@ class DebugCommunication(object): with self._recv_condition: for packet in self._recv_packets: if packet and ("seq" not in packet or packet["seq"] == 0): - warnings.warn( + raise ValueError( f"received a malformed packet, expected 'seq != 0' for {packet!r}" ) # Handle events that may modify any stateful properties of @@ -460,22 +466,11 @@ class DebugCommunication(object): self.reverse_requests.append(request) arguments = request.get("arguments") if request["command"] == "runInTerminal" and arguments is not None: - in_shell = arguments.get("argsCanBeInterpretedByShell", False) - print("spawning...", arguments["args"]) - proc = subprocess.Popen( - arguments["args"], - env=arguments.get("env", {}), - cwd=arguments.get("cwd", None), - stdin=subprocess.DEVNULL, - stdout=sys.stderr, - stderr=sys.stderr, - shell=in_shell, - ) - body = {} - if in_shell: - body["shellProcessId"] = proc.pid - else: - body["processId"] = proc.pid + assert self.spawn_helper is not None, "Not configured to spawn subprocesses" + [exe, *args] = arguments["args"] + env = [f"{k}={v}" for k, v in arguments.get("env", {}).items()] + proc = self.spawn_helper(exe, args, env) + body = {"processId": proc.pid} self.send_packet( { "type": "response", @@ -790,6 +785,8 @@ class DebugCommunication(object): *, program: Optional[str] = None, pid: Optional[int] = None, + debuggerId: Optional[int] = None, + targetId: Optional[int] = None, waitFor=False, initCommands: Optional[list[str]] = None, preRunCommands: Optional[list[str]] = None, @@ -809,6 +806,10 @@ class DebugCommunication(object): args_dict["pid"] = pid if program is not None: args_dict["program"] = program + if debuggerId is not None: + args_dict["debuggerId"] = debuggerId + if targetId is not None: + args_dict["targetId"] = targetId if waitFor: args_dict["waitFor"] = waitFor args_dict["initCommands"] = self.init_commands @@ -870,7 +871,17 @@ class DebugCommunication(object): response = self._send_recv(command_dict) if response: self.configuration_done_sent = True + stopped_on_entry = self.is_stopped self.request_threads() + if not stopped_on_entry: + # Drop the initial cached threads if we did not stop-on-entry. + # In VSCode, immediately following 'configurationDone', a + # 'threads' request is made to get the initial set of threads, + # specifically the main threads id and name. + # We issue the threads request to mimic this pattern but in our + # tests we don't want to cache the result unless the process is + # actually stopped. + self.threads = None return response def _process_stopped(self): @@ -982,15 +993,25 @@ class DebugCommunication(object): } return self._send_recv(command_dict) - def request_evaluate(self, expression, frameIndex=0, threadId=None, context=None): + def request_evaluate( + self, + expression, + frameIndex=0, + threadId=None, + context=None, + is_hex: Optional[bool] = None, + ): stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId) if stackFrame is None: return [] args_dict = { "expression": expression, - "context": context, "frameId": stackFrame["id"], } + if context: + args_dict["context"] = context + if is_hex is not None: + args_dict["format"] = {"hex": is_hex} command_dict = { "command": "evaluate", "type": "request", @@ -1244,16 +1265,18 @@ class DebugCommunication(object): return response def request_dataBreakpointInfo( - self, variablesReference, name, frameIndex=0, threadId=None + self, variablesReference, name, size=None, frameIndex=0, threadId=None ): stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId) if stackFrame is None: return [] - args_dict = { - "variablesReference": variablesReference, - "name": name, - "frameId": stackFrame["id"], - } + args_dict = {"name": name} + if size is None: + args_dict["variablesReference"] = variablesReference + args_dict["frameId"] = stackFrame["id"] + else: + args_dict["asAddress"] = True + args_dict["bytes"] = size command_dict = { "command": "dataBreakpointInfo", "type": "request", @@ -1501,12 +1524,14 @@ class DebugCommunication(object): class DebugAdapterServer(DebugCommunication): def __init__( self, + *, executable: Optional[str] = None, connection: Optional[str] = None, - init_commands: list[str] = [], - log_file: Optional[TextIO] = None, - env: Optional[dict[str, str]] = None, - additional_args: list[str] = [], + init_commands: Optional[list[str]] = None, + log_file: Optional[str] = None, + env: Optional[Dict[str, str]] = None, + additional_args: Optional[List[str]] = None, + spawn_helper: Optional[SpawnHelperCallback] = None, ): self.process = None self.connection = None @@ -1532,13 +1557,21 @@ class DebugAdapterServer(DebugCommunication): s = socket.create_connection((host.strip("[]"), int(port))) else: raise ValueError("invalid connection: {}".format(connection)) - DebugCommunication.__init__( - self, s.makefile("rb"), s.makefile("wb"), init_commands, log_file + super().__init__( + s.makefile("rb"), + s.makefile("wb"), + init_commands, + log_file, + spawn_helper, ) self.connection = connection else: - DebugCommunication.__init__( - self, self.process.stdout, self.process.stdin, init_commands, log_file + super().__init__( + self.process.stdout, + self.process.stdin, + init_commands, + log_file, + spawn_helper, ) @classmethod @@ -1546,14 +1579,14 @@ class DebugAdapterServer(DebugCommunication): cls, *, executable: str, - env: Optional[dict[str, str]] = None, - log_file: Optional[TextIO] = None, + env: Optional[Dict[str, str]] = None, + log_file: Optional[str] = None, connection: Optional[str] = None, connection_timeout: Optional[int] = None, - additional_args: list[str] = [], + additional_args: Optional[List[str]] = None, ) -> tuple[subprocess.Popen, Optional[str]]: adapter_env = os.environ.copy() - if env is not None: + if env: adapter_env.update(env) if log_file: @@ -1561,7 +1594,8 @@ class DebugAdapterServer(DebugCommunication): args = [executable] # Add additional arguments first (like --no-lldbinit) - args.extend(additional_args) + if additional_args: + args.extend(additional_args) if connection is not None: args.append("--connection") diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index c6c4a3e..c7d302c 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -39,6 +39,7 @@ class DAPTestCaseBase(TestBase): log_file=log_file_path, env=lldbDAPEnv, additional_args=additional_args or [], + spawn_helper=self.spawnSubprocess, ) def build_and_create_debug_adapter( @@ -168,6 +169,7 @@ class DAPTestCaseBase(TestBase): if ( body["reason"] != "breakpoint" and body["reason"] != "instruction breakpoint" + and body["reason"] != "data breakpoint" ): continue if "hitBreakpointIds" not in body: diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt index ce59ee5..ac47580 100644 --- a/lldb/source/API/CMakeLists.txt +++ b/lldb/source/API/CMakeLists.txt @@ -69,6 +69,7 @@ add_lldb_library(liblldb SHARED ${option_framework} SBFileSpecList.cpp SBFormat.cpp SBFrame.cpp + SBFrameList.cpp SBFunction.cpp SBHostOS.cpp SBInstruction.cpp diff --git a/lldb/source/API/SBCommandReturnObject.cpp b/lldb/source/API/SBCommandReturnObject.cpp index e78e213a..da7e288 100644 --- a/lldb/source/API/SBCommandReturnObject.cpp +++ b/lldb/source/API/SBCommandReturnObject.cpp @@ -15,6 +15,7 @@ #include "lldb/API/SBValue.h" #include "lldb/API/SBValueList.h" #include "lldb/Core/StructuredDataImpl.h" +#include "lldb/Host/File.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Instrumentation.h" @@ -275,14 +276,16 @@ void SBCommandReturnObject::SetImmediateErrorFile(FILE *fh) { void SBCommandReturnObject::SetImmediateOutputFile(FILE *fh, bool transfer_ownership) { LLDB_INSTRUMENT_VA(this, fh, transfer_ownership); - FileSP file = std::make_shared<NativeFile>(fh, transfer_ownership); + FileSP file = std::make_shared<NativeFile>(fh, File::eOpenOptionWriteOnly, + transfer_ownership); ref().SetImmediateOutputFile(file); } void SBCommandReturnObject::SetImmediateErrorFile(FILE *fh, bool transfer_ownership) { LLDB_INSTRUMENT_VA(this, fh, transfer_ownership); - FileSP file = std::make_shared<NativeFile>(fh, transfer_ownership); + FileSP file = std::make_shared<NativeFile>(fh, File::eOpenOptionWriteOnly, + transfer_ownership); ref().SetImmediateErrorFile(file); } diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp index 5c4c653..3f34e7a 100644 --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -179,7 +179,7 @@ void SBDebugger::Initialize() { lldb::SBError SBDebugger::InitializeWithErrorHandling() { LLDB_INSTRUMENT(); - SBError error; + SBError error((Status())); if (auto e = g_debugger_lifetime->Initialize( std::make_unique<SystemInitializerFull>())) { error.SetError(Status::FromError(std::move(e))); @@ -327,8 +327,8 @@ void SBDebugger::SkipAppInitFiles(bool b) { void SBDebugger::SetInputFileHandle(FILE *fh, bool transfer_ownership) { LLDB_INSTRUMENT_VA(this, fh, transfer_ownership); if (m_opaque_sp) - m_opaque_sp->SetInputFile( - (FileSP)std::make_shared<NativeFile>(fh, transfer_ownership)); + m_opaque_sp->SetInputFile((FileSP)std::make_shared<NativeFile>( + fh, File::eOpenOptionReadOnly, transfer_ownership)); } SBError SBDebugger::SetInputString(const char *data) { @@ -385,7 +385,8 @@ SBError SBDebugger::SetOutputFile(FileSP file_sp) { void SBDebugger::SetOutputFileHandle(FILE *fh, bool transfer_ownership) { LLDB_INSTRUMENT_VA(this, fh, transfer_ownership); - SetOutputFile((FileSP)std::make_shared<NativeFile>(fh, transfer_ownership)); + SetOutputFile((FileSP)std::make_shared<NativeFile>( + fh, File::eOpenOptionWriteOnly, transfer_ownership)); } SBError SBDebugger::SetOutputFile(SBFile file) { @@ -405,7 +406,8 @@ SBError SBDebugger::SetOutputFile(SBFile file) { void SBDebugger::SetErrorFileHandle(FILE *fh, bool transfer_ownership) { LLDB_INSTRUMENT_VA(this, fh, transfer_ownership); - SetErrorFile((FileSP)std::make_shared<NativeFile>(fh, transfer_ownership)); + SetErrorFile((FileSP)std::make_shared<NativeFile>( + fh, File::eOpenOptionWriteOnly, transfer_ownership)); } SBError SBDebugger::SetErrorFile(FileSP file_sp) { @@ -576,8 +578,10 @@ void SBDebugger::HandleProcessEvent(const SBProcess &process, FILE *err) { LLDB_INSTRUMENT_VA(this, process, event, out, err); - FileSP outfile = std::make_shared<NativeFile>(out, false); - FileSP errfile = std::make_shared<NativeFile>(err, false); + FileSP outfile = + std::make_shared<NativeFile>(out, File::eOpenOptionWriteOnly, false); + FileSP errfile = + std::make_shared<NativeFile>(err, File::eOpenOptionWriteOnly, false); return HandleProcessEvent(process, event, outfile, errfile); } @@ -705,61 +709,11 @@ const char *SBDebugger::StateAsCString(StateType state) { return lldb_private::StateAsCString(state); } -static void AddBoolConfigEntry(StructuredData::Dictionary &dict, - llvm::StringRef name, bool value, - llvm::StringRef description) { - auto entry_up = std::make_unique<StructuredData::Dictionary>(); - entry_up->AddBooleanItem("value", value); - entry_up->AddStringItem("description", description); - dict.AddItem(name, std::move(entry_up)); -} - -static void AddLLVMTargets(StructuredData::Dictionary &dict) { - auto array_up = std::make_unique<StructuredData::Array>(); -#define LLVM_TARGET(target) \ - array_up->AddItem(std::make_unique<StructuredData::String>(#target)); -#include "llvm/Config/Targets.def" - auto entry_up = std::make_unique<StructuredData::Dictionary>(); - entry_up->AddItem("value", std::move(array_up)); - entry_up->AddStringItem("description", "A list of configured LLVM targets."); - dict.AddItem("targets", std::move(entry_up)); -} - SBStructuredData SBDebugger::GetBuildConfiguration() { LLDB_INSTRUMENT(); - auto config_up = std::make_unique<StructuredData::Dictionary>(); - AddBoolConfigEntry( - *config_up, "xml", XMLDocument::XMLEnabled(), - "A boolean value that indicates if XML support is enabled in LLDB"); - AddBoolConfigEntry( - *config_up, "curl", LLVM_ENABLE_CURL, - "A boolean value that indicates if CURL support is enabled in LLDB"); - AddBoolConfigEntry( - *config_up, "curses", LLDB_ENABLE_CURSES, - "A boolean value that indicates if curses support is enabled in LLDB"); - AddBoolConfigEntry( - *config_up, "editline", LLDB_ENABLE_LIBEDIT, - "A boolean value that indicates if editline support is enabled in LLDB"); - AddBoolConfigEntry(*config_up, "editline_wchar", LLDB_EDITLINE_USE_WCHAR, - "A boolean value that indicates if editline wide " - "characters support is enabled in LLDB"); - AddBoolConfigEntry( - *config_up, "lzma", LLDB_ENABLE_LZMA, - "A boolean value that indicates if lzma support is enabled in LLDB"); - AddBoolConfigEntry( - *config_up, "python", LLDB_ENABLE_PYTHON, - "A boolean value that indicates if python support is enabled in LLDB"); - AddBoolConfigEntry( - *config_up, "lua", LLDB_ENABLE_LUA, - "A boolean value that indicates if lua support is enabled in LLDB"); - AddBoolConfigEntry(*config_up, "fbsdvmcore", LLDB_ENABLE_FBSDVMCORE, - "A boolean value that indicates if fbsdvmcore support is " - "enabled in LLDB"); - AddLLVMTargets(*config_up); - SBStructuredData data; - data.m_impl_up->SetObjectSP(std::move(config_up)); + data.m_impl_up->SetObjectSP(Debugger::GetBuildConfiguration()); return data; } diff --git a/lldb/source/API/SBFile.cpp b/lldb/source/API/SBFile.cpp index 2ae4b14..5690992 100644 --- a/lldb/source/API/SBFile.cpp +++ b/lldb/source/API/SBFile.cpp @@ -39,7 +39,22 @@ SBFile::SBFile() { LLDB_INSTRUMENT_VA(this); } SBFile::SBFile(FILE *file, bool transfer_ownership) { LLDB_INSTRUMENT_VA(this, file, transfer_ownership); - m_opaque_sp = std::make_shared<NativeFile>(file, transfer_ownership); + // For backwards comptability, this defaulted to ReadOnly previously. + m_opaque_sp = std::make_shared<NativeFile>(file, File::eOpenOptionReadOnly, + transfer_ownership); +} + +SBFile::SBFile(FILE *file, const char *mode, bool transfer_ownership) { + LLDB_INSTRUMENT_VA(this, file, transfer_ownership); + + auto options = File::GetOptionsFromMode(mode); + if (!options) { + llvm::consumeError(options.takeError()); + return; + } + + m_opaque_sp = + std::make_shared<NativeFile>(file, options.get(), transfer_ownership); } SBFile::SBFile(int fd, const char *mode, bool transfer_owndership) { diff --git a/lldb/source/API/SBFrameList.cpp b/lldb/source/API/SBFrameList.cpp new file mode 100644 index 0000000..d5fa955 --- /dev/null +++ b/lldb/source/API/SBFrameList.cpp @@ -0,0 +1,97 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/API/SBFrameList.h" +#include "lldb/API/SBFrame.h" +#include "lldb/API/SBStream.h" +#include "lldb/API/SBThread.h" +#include "lldb/Target/StackFrameList.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Instrumentation.h" + +using namespace lldb; +using namespace lldb_private; + +SBFrameList::SBFrameList() : m_opaque_sp() { LLDB_INSTRUMENT_VA(this); } + +SBFrameList::SBFrameList(const SBFrameList &rhs) + : m_opaque_sp(rhs.m_opaque_sp) { + LLDB_INSTRUMENT_VA(this, rhs); +} + +SBFrameList::~SBFrameList() = default; + +const SBFrameList &SBFrameList::operator=(const SBFrameList &rhs) { + LLDB_INSTRUMENT_VA(this, rhs); + + if (this != &rhs) + m_opaque_sp = rhs.m_opaque_sp; + return *this; +} + +SBFrameList::SBFrameList(const lldb::StackFrameListSP &frame_list_sp) + : m_opaque_sp(frame_list_sp) {} + +void SBFrameList::SetFrameList(const lldb::StackFrameListSP &frame_list_sp) { + m_opaque_sp = frame_list_sp; +} + +SBFrameList::operator bool() const { + LLDB_INSTRUMENT_VA(this); + + return m_opaque_sp.get() != nullptr; +} + +bool SBFrameList::IsValid() const { + LLDB_INSTRUMENT_VA(this); + return this->operator bool(); +} + +uint32_t SBFrameList::GetSize() const { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + return m_opaque_sp->GetNumFrames(); + return 0; +} + +SBFrame SBFrameList::GetFrameAtIndex(uint32_t idx) const { + LLDB_INSTRUMENT_VA(this, idx); + + SBFrame sb_frame; + if (m_opaque_sp) + sb_frame.SetFrameSP(m_opaque_sp->GetFrameAtIndex(idx)); + return sb_frame; +} + +SBThread SBFrameList::GetThread() const { + LLDB_INSTRUMENT_VA(this); + + SBThread sb_thread; + if (m_opaque_sp) + sb_thread.SetThread(m_opaque_sp->GetThread().shared_from_this()); + return sb_thread; +} + +void SBFrameList::Clear() { + LLDB_INSTRUMENT_VA(this); + + if (m_opaque_sp) + m_opaque_sp->Clear(); +} + +bool SBFrameList::GetDescription(SBStream &description) const { + LLDB_INSTRUMENT_VA(this, description); + + if (!m_opaque_sp) + return false; + + Stream &strm = description.ref(); + m_opaque_sp->Dump(&strm); + return true; +} diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp index 6755089..5921511 100644 --- a/lldb/source/API/SBInstruction.cpp +++ b/lldb/source/API/SBInstruction.cpp @@ -10,8 +10,8 @@ #include "lldb/Utility/Instrumentation.h" #include "lldb/API/SBAddress.h" -#include "lldb/API/SBFrame.h" #include "lldb/API/SBFile.h" +#include "lldb/API/SBFrame.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBTarget.h" @@ -268,7 +268,8 @@ bool SBInstruction::GetDescription(lldb::SBStream &s) { void SBInstruction::Print(FILE *outp) { LLDB_INSTRUMENT_VA(this, outp); - FileSP out = std::make_shared<NativeFile>(outp, /*take_ownership=*/false); + FileSP out = std::make_shared<NativeFile>(outp, File::eOpenOptionWriteOnly, + /*take_ownership=*/false); Print(out); } diff --git a/lldb/source/API/SBLineEntry.cpp b/lldb/source/API/SBLineEntry.cpp index 0f4936f..2257294 100644 --- a/lldb/source/API/SBLineEntry.cpp +++ b/lldb/source/API/SBLineEntry.cpp @@ -132,6 +132,8 @@ void SBLineEntry::SetLine(uint32_t line) { LLDB_INSTRUMENT_VA(this, line); ref().line = line; + if (!ref().range.IsValid()) + ref().synthetic = true; } void SBLineEntry::SetColumn(uint32_t column) { diff --git a/lldb/source/API/SBModule.cpp b/lldb/source/API/SBModule.cpp index 5a57f45..32067ac 100644 --- a/lldb/source/API/SBModule.cpp +++ b/lldb/source/API/SBModule.cpp @@ -37,8 +37,8 @@ SBModule::SBModule(const SBModuleSpec &module_spec) { LLDB_INSTRUMENT_VA(this, module_spec); ModuleSP module_sp; - Status error = ModuleList::GetSharedModule( - *module_spec.m_opaque_up, module_sp, nullptr, nullptr, nullptr); + Status error = ModuleList::GetSharedModule(*module_spec.m_opaque_up, + module_sp, nullptr, nullptr); if (module_sp) SetSP(module_sp); } diff --git a/lldb/source/API/SBModuleSpec.cpp b/lldb/source/API/SBModuleSpec.cpp index fbbcfea..031ba12 100644 --- a/lldb/source/API/SBModuleSpec.cpp +++ b/lldb/source/API/SBModuleSpec.cpp @@ -9,6 +9,7 @@ #include "lldb/API/SBModuleSpec.h" #include "Utils.h" #include "lldb/API/SBStream.h" +#include "lldb/API/SBTarget.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Host/Host.h" @@ -174,6 +175,18 @@ void SBModuleSpec::SetObjectSize(uint64_t object_size) { m_opaque_up->SetObjectSize(object_size); } +SBTarget SBModuleSpec::GetTarget() { + LLDB_INSTRUMENT_VA(this); + + return SBTarget(m_opaque_up->GetTargetSP()); +} + +void SBModuleSpec::SetTarget(SBTarget target) { + LLDB_INSTRUMENT_VA(this, target); + + m_opaque_up->SetTarget(target.GetSP()); +} + SBModuleSpecList::SBModuleSpecList() : m_opaque_up(new ModuleSpecList()) { LLDB_INSTRUMENT_VA(this); } diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp index d4be64b..14aa943 100644 --- a/lldb/source/API/SBProcess.cpp +++ b/lldb/source/API/SBProcess.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "lldb/API/SBProcess.h" +#include "lldb/Host/File.h" #include "lldb/Utility/Instrumentation.h" #include <cinttypes> @@ -310,7 +311,8 @@ void SBProcess::ReportEventState(const SBEvent &event, SBFile out) const { void SBProcess::ReportEventState(const SBEvent &event, FILE *out) const { LLDB_INSTRUMENT_VA(this, event, out); - FileSP outfile = std::make_shared<NativeFile>(out, false); + FileSP outfile = + std::make_shared<NativeFile>(out, File::eOpenOptionWriteOnly, false); return ReportEventState(event, outfile); } diff --git a/lldb/source/API/SBStream.cpp b/lldb/source/API/SBStream.cpp index fc8f09a..2fc5fcf 100644 --- a/lldb/source/API/SBStream.cpp +++ b/lldb/source/API/SBStream.cpp @@ -116,7 +116,8 @@ void SBStream::RedirectToFile(const char *path, bool append) { void SBStream::RedirectToFileHandle(FILE *fh, bool transfer_fh_ownership) { LLDB_INSTRUMENT_VA(this, fh, transfer_fh_ownership); - FileSP file = std::make_unique<NativeFile>(fh, transfer_fh_ownership); + FileSP file = std::make_unique<NativeFile>(fh, File::eOpenOptionReadWrite, + transfer_fh_ownership); return RedirectToFile(file); } diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp index 98d10aa..78c2d49 100644 --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -23,6 +23,7 @@ #include "lldb/API/SBStringList.h" #include "lldb/API/SBStructuredData.h" #include "lldb/API/SBSymbolContextList.h" +#include "lldb/API/SBThreadCollection.h" #include "lldb/API/SBTrace.h" #include "lldb/Breakpoint/BreakpointID.h" #include "lldb/Breakpoint/BreakpointIDList.h" @@ -39,6 +40,7 @@ #include "lldb/Core/Section.h" #include "lldb/Core/StructuredDataImpl.h" #include "lldb/Host/Host.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Symbol/DeclVendor.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/SymbolFile.h" @@ -50,6 +52,7 @@ #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" #include "lldb/Target/StackFrame.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/Target.h" #include "lldb/Target/TargetList.h" #include "lldb/Utility/ArchSpec.h" @@ -59,6 +62,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/ValueObject/ValueObjectConstResult.h" #include "lldb/ValueObject/ValueObjectList.h" #include "lldb/ValueObject/ValueObjectVariable.h" @@ -128,6 +132,12 @@ SBTarget SBTarget::GetTargetFromEvent(const SBEvent &event) { return Target::TargetEventData::GetTargetFromEvent(event.get()); } +SBTarget SBTarget::GetCreatedTargetFromEvent(const SBEvent &event) { + LLDB_INSTRUMENT_VA(event); + + return Target::TargetEventData::GetCreatedTargetFromEvent(event.get()); +} + uint32_t SBTarget::GetNumModulesFromEvent(const SBEvent &event) { LLDB_INSTRUMENT_VA(event); @@ -1614,6 +1624,19 @@ const char *SBTarget::GetTriple() { return nullptr; } +const char *SBTarget::GetArchName() { + LLDB_INSTRUMENT_VA(this); + + if (TargetSP target_sp = GetSP()) { + llvm::StringRef arch_name = + target_sp->GetArchitecture().GetTriple().getArchName(); + ConstString const_arch_name(arch_name); + + return const_arch_name.GetCString(); + } + return nullptr; +} + const char *SBTarget::GetABIName() { LLDB_INSTRUMENT_VA(this); @@ -1641,6 +1664,14 @@ lldb::user_id_t SBTarget::GetGloballyUniqueID() const { return LLDB_INVALID_GLOBALLY_UNIQUE_TARGET_ID; } +const char *SBTarget::GetTargetSessionName() const { + LLDB_INSTRUMENT_VA(this); + + if (TargetSP target_sp = GetSP()) + return ConstString(target_sp->GetTargetSessionName()).AsCString(); + return nullptr; +} + SBError SBTarget::SetLabel(const char *label) { LLDB_INSTRUMENT_VA(this, label); @@ -2408,3 +2439,81 @@ lldb::SBMutex SBTarget::GetAPIMutex() const { return lldb::SBMutex(target_sp); return lldb::SBMutex(); } + +uint32_t +SBTarget::RegisterScriptedFrameProvider(const char *class_name, + lldb::SBStructuredData args_dict, + lldb::SBError &error) { + LLDB_INSTRUMENT_VA(this, class_name, args_dict, error); + + TargetSP target_sp = GetSP(); + if (!target_sp) { + error.SetErrorString("invalid target"); + return 0; + } + + if (!class_name || !class_name[0]) { + error.SetErrorString("invalid class name"); + return 0; + } + + // Extract the dictionary from SBStructuredData. + StructuredData::DictionarySP dict_sp; + if (args_dict.IsValid() && args_dict.m_impl_up) { + StructuredData::ObjectSP obj_sp = args_dict.m_impl_up->GetObjectSP(); + if (obj_sp && obj_sp->GetType() != lldb::eStructuredDataTypeDictionary) { + error.SetErrorString("SBStructuredData argument isn't a dictionary"); + return 0; + } + dict_sp = std::make_shared<StructuredData::Dictionary>(obj_sp); + } + + // Create the ScriptedMetadata. + ScriptedMetadataSP metadata_sp = + std::make_shared<ScriptedMetadata>(class_name, dict_sp); + + // Create the interface for calling static methods. + ScriptedFrameProviderInterfaceSP interface_sp = + target_sp->GetDebugger() + .GetScriptInterpreter() + ->CreateScriptedFrameProviderInterface(); + + // Create a descriptor (applies to all threads by default). + ScriptedFrameProviderDescriptor descriptor(metadata_sp); + descriptor.interface_sp = interface_sp; + + llvm::Expected<uint32_t> descriptor_id_or_err = + target_sp->AddScriptedFrameProviderDescriptor(descriptor); + if (!descriptor_id_or_err) { + error.SetErrorString( + llvm::toString(descriptor_id_or_err.takeError()).c_str()); + return 0; + } + + // Register the descriptor with the target. + return *descriptor_id_or_err; +} + +lldb::SBError SBTarget::RemoveScriptedFrameProvider(uint32_t provider_id) { + LLDB_INSTRUMENT_VA(this, provider_id); + + SBError error; + TargetSP target_sp = GetSP(); + if (!target_sp) { + error.SetErrorString("invalid target"); + return error; + } + + if (!provider_id) { + error.SetErrorString("invalid provider id"); + return error; + } + + if (!target_sp->RemoveScriptedFrameProviderDescriptor(provider_id)) { + error.SetErrorStringWithFormat("no frame provider named '%u' found", + provider_id); + return error; + } + + return {}; +} diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp index f58a1b5..f32c5c5 100644 --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -14,6 +14,7 @@ #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBFrame.h" +#include "lldb/API/SBFrameList.h" #include "lldb/API/SBProcess.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStructuredData.h" @@ -1102,6 +1103,26 @@ SBFrame SBThread::GetFrameAtIndex(uint32_t idx) { return sb_frame; } +lldb::SBFrameList SBThread::GetFrames() { + LLDB_INSTRUMENT_VA(this); + + SBFrameList sb_frame_list; + llvm::Expected<StoppedExecutionContext> exe_ctx = + GetStoppedExecutionContext(m_opaque_sp); + if (!exe_ctx) { + LLDB_LOG_ERROR(GetLog(LLDBLog::API), exe_ctx.takeError(), "{0}"); + return SBFrameList(); + } + + if (exe_ctx->HasThreadScope()) { + StackFrameListSP frame_list_sp = + exe_ctx->GetThreadPtr()->GetStackFrameList(); + sb_frame_list.SetFrameList(frame_list_sp); + } + + return sb_frame_list; +} + lldb::SBFrame SBThread::GetSelectedFrame() { LLDB_INSTRUMENT_VA(this); diff --git a/lldb/source/Breakpoint/BreakpointLocation.cpp b/lldb/source/Breakpoint/BreakpointLocation.cpp index f25209c..25285be 100644 --- a/lldb/source/Breakpoint/BreakpointLocation.cpp +++ b/lldb/source/Breakpoint/BreakpointLocation.cpp @@ -251,7 +251,7 @@ bool BreakpointLocation::ConditionSaysStop(ExecutionContext &exe_ctx, } m_user_expression_sp.reset(GetTarget().GetUserExpressionForLanguage( - condition.GetText(), llvm::StringRef(), language, + condition.GetText(), llvm::StringRef(), SourceLanguage{language}, Expression::eResultTypeAny, EvaluateExpressionOptions(), nullptr, error)); if (error.Fail()) { diff --git a/lldb/source/Breakpoint/BreakpointLocationCollection.cpp b/lldb/source/Breakpoint/BreakpointLocationCollection.cpp index 9771583..adff429 100644 --- a/lldb/source/Breakpoint/BreakpointLocationCollection.cpp +++ b/lldb/source/Breakpoint/BreakpointLocationCollection.cpp @@ -24,7 +24,7 @@ BreakpointLocationCollection::BreakpointLocationCollection(bool preserving) BreakpointLocationCollection::~BreakpointLocationCollection() = default; void BreakpointLocationCollection::Add(const BreakpointLocationSP &bp_loc) { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); BreakpointLocationSP old_bp_loc = FindByIDPair(bp_loc->GetBreakpoint().GetID(), bp_loc->GetID()); if (!old_bp_loc.get()) { @@ -44,7 +44,7 @@ void BreakpointLocationCollection::Add(const BreakpointLocationSP &bp_loc) { bool BreakpointLocationCollection::Remove(lldb::break_id_t bp_id, lldb::break_id_t bp_loc_id) { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); collection::iterator pos = GetIDPairIterator(bp_id, bp_loc_id); // Predicate if (pos != m_break_loc_collection.end()) { if (m_preserving_bkpts) { @@ -117,7 +117,7 @@ const BreakpointLocationSP BreakpointLocationCollection::FindByIDPair( } BreakpointLocationSP BreakpointLocationCollection::GetByIndex(size_t i) { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); BreakpointLocationSP stop_sp; if (i < m_break_loc_collection.size()) stop_sp = m_break_loc_collection[i]; @@ -127,7 +127,7 @@ BreakpointLocationSP BreakpointLocationCollection::GetByIndex(size_t i) { const BreakpointLocationSP BreakpointLocationCollection::GetByIndex(size_t i) const { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); BreakpointLocationSP stop_sp; if (i < m_break_loc_collection.size()) stop_sp = m_break_loc_collection[i]; @@ -168,7 +168,7 @@ bool BreakpointLocationCollection::ShouldStop( } bool BreakpointLocationCollection::ValidForThisThread(Thread &thread) { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); collection::iterator pos, begin = m_break_loc_collection.begin(), end = m_break_loc_collection.end(); @@ -180,7 +180,7 @@ bool BreakpointLocationCollection::ValidForThisThread(Thread &thread) { } bool BreakpointLocationCollection::IsInternal() const { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); collection::const_iterator pos, begin = m_break_loc_collection.begin(), end = m_break_loc_collection.end(); @@ -197,7 +197,7 @@ bool BreakpointLocationCollection::IsInternal() const { void BreakpointLocationCollection::GetDescription( Stream *s, lldb::DescriptionLevel level) { - std::lock_guard<std::mutex> guard(m_collection_mutex); + std::lock_guard<std::recursive_mutex> guard(m_collection_mutex); collection::iterator pos, begin = m_break_loc_collection.begin(), end = m_break_loc_collection.end(); @@ -212,8 +212,10 @@ BreakpointLocationCollection &BreakpointLocationCollection::operator=( const BreakpointLocationCollection &rhs) { if (this != &rhs) { std::lock(m_collection_mutex, rhs.m_collection_mutex); - std::lock_guard<std::mutex> lhs_guard(m_collection_mutex, std::adopt_lock); - std::lock_guard<std::mutex> rhs_guard(rhs.m_collection_mutex, std::adopt_lock); + std::lock_guard<std::recursive_mutex> lhs_guard(m_collection_mutex, + std::adopt_lock); + std::lock_guard<std::recursive_mutex> rhs_guard(rhs.m_collection_mutex, + std::adopt_lock); m_break_loc_collection = rhs.m_break_loc_collection; } return *this; diff --git a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp index a94e9e2..cef1ef1 100644 --- a/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp +++ b/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp @@ -139,7 +139,7 @@ void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list) { if (!sc.block) continue; - SupportFileSP file_sp; + SupportFileNSP file_sp = std::make_shared<SupportFile>(); uint32_t line; const Block *inline_block = sc.block->GetContainingInlinedBlock(); if (inline_block) { diff --git a/lldb/source/Breakpoint/BreakpointResolverName.cpp b/lldb/source/Breakpoint/BreakpointResolverName.cpp index 4f252f9..2025f59 100644 --- a/lldb/source/Breakpoint/BreakpointResolverName.cpp +++ b/lldb/source/Breakpoint/BreakpointResolverName.cpp @@ -218,19 +218,22 @@ StructuredData::ObjectSP BreakpointResolverName::SerializeToStructuredData() { void BreakpointResolverName::AddNameLookup(ConstString name, FunctionNameType name_type_mask) { - - Module::LookupInfo lookup(name, name_type_mask, m_language); - m_lookups.emplace_back(lookup); + std::vector<Module::LookupInfo> infos = + Module::LookupInfo::MakeLookupInfos(name, name_type_mask, m_language); + llvm::append_range(m_lookups, infos); auto add_variant_funcs = [&](Language *lang) { for (Language::MethodNameVariant variant : lang->GetMethodNameVariants(name)) { // FIXME: Should we be adding variants that aren't of type Full? if (variant.GetType() & lldb::eFunctionNameTypeFull) { - Module::LookupInfo variant_lookup(name, variant.GetType(), - lang->GetLanguageType()); - variant_lookup.SetLookupName(variant.GetName()); - m_lookups.emplace_back(variant_lookup); + std::vector<Module::LookupInfo> variant_lookups = + Module::LookupInfo::MakeLookupInfos(name, variant.GetType(), + lang->GetLanguageType()); + llvm::for_each(variant_lookups, [&](auto &variant_lookup) { + variant_lookup.SetLookupName(variant.GetName()); + }); + llvm::append_range(m_lookups, variant_lookups); } } return IterationAction::Continue; @@ -401,14 +404,22 @@ void BreakpointResolverName::GetDescription(Stream *s) { if (m_match_type == Breakpoint::Regexp) s->Printf("regex = '%s'", m_regex.GetText().str().c_str()); else { - size_t num_names = m_lookups.size(); - if (num_names == 1) - s->Printf("name = '%s'", m_lookups[0].GetName().GetCString()); + // Since there may be many lookups objects for the same name breakpoint (one + // per language available), unique them by name, and operate on those unique + // names. + std::vector<ConstString> unique_lookups; + for (auto &lookup : m_lookups) { + if (!llvm::is_contained(unique_lookups, lookup.GetName())) + unique_lookups.push_back(lookup.GetName()); + } + if (unique_lookups.size() == 1) + s->Printf("name = '%s'", unique_lookups[0].GetCString()); else { + size_t num_names = unique_lookups.size(); s->Printf("names = {"); for (size_t i = 0; i < num_names; i++) { s->Printf("%s'%s'", (i == 0 ? "" : ", "), - m_lookups[i].GetName().GetCString()); + unique_lookups[i].GetCString()); } s->Printf("}"); } diff --git a/lldb/source/Breakpoint/BreakpointSite.cpp b/lldb/source/Breakpoint/BreakpointSite.cpp index fd7666b..8639379 100644 --- a/lldb/source/Breakpoint/BreakpointSite.cpp +++ b/lldb/source/Breakpoint/BreakpointSite.cpp @@ -168,6 +168,22 @@ bool BreakpointSite::ValidForThisThread(Thread &thread) { return m_constituents.ValidForThisThread(thread); } +bool BreakpointSite::ContainsUserBreakpointForThread(Thread &thread) { + if (ThreadSP backed_thread = thread.GetBackedThread()) + return ContainsUserBreakpointForThread(*backed_thread); + + std::lock_guard<std::recursive_mutex> guard(m_constituents_mutex); + for (const BreakpointLocationSP &bp_loc : + m_constituents.BreakpointLocations()) { + const Breakpoint &bp = bp_loc->GetBreakpoint(); + if (bp.IsInternal()) + continue; + if (bp_loc->ValidForThisThread(thread)) + return true; + } + return false; +} + void BreakpointSite::BumpHitCounts() { std::lock_guard<std::recursive_mutex> guard(m_constituents_mutex); for (BreakpointLocationSP loc_sp : m_constituents.BreakpointLocations()) { diff --git a/lldb/source/Commands/CMakeLists.txt b/lldb/source/Commands/CMakeLists.txt index 69e4c45..f2e6224 100644 --- a/lldb/source/Commands/CMakeLists.txt +++ b/lldb/source/Commands/CMakeLists.txt @@ -58,6 +58,9 @@ add_lldb_library(lldbCommands NO_PLUGIN_DEPENDENCIES lldbUtility lldbValueObject lldbVersion + CLANG_LIBS + clangFrontend + clangSerialization ) add_dependencies(lldbCommands LLDBOptionsGen) diff --git a/lldb/source/Commands/CommandObjectBreakpoint.cpp b/lldb/source/Commands/CommandObjectBreakpoint.cpp index 5a55126..75dc890 100644 --- a/lldb/source/Commands/CommandObjectBreakpoint.cpp +++ b/lldb/source/Commands/CommandObjectBreakpoint.cpp @@ -45,6 +45,38 @@ static void AddBreakpointDescription(Stream *s, Breakpoint *bp, s->EOL(); } +static bool GetDefaultFile(Target &target, StackFrame *cur_frame, + FileSpec &file, CommandReturnObject &result) { + // First use the Source Manager's default file. Then use the current stack + // frame's file. + if (auto maybe_file_and_line = + target.GetSourceManager().GetDefaultFileAndLine()) { + file = maybe_file_and_line->support_file_nsp->GetSpecOnly(); + return true; + } + + if (cur_frame == nullptr) { + result.AppendError("No selected frame to use to find the default file."); + return false; + } + if (!cur_frame->HasDebugInformation()) { + result.AppendError("Cannot use the selected frame to find the default " + "file, it has no debug info."); + return false; + } + + const SymbolContext &sc = + cur_frame->GetSymbolContext(eSymbolContextLineEntry); + if (sc.line_entry.GetFile()) { + file = sc.line_entry.GetFile(); + } else { + result.AppendError("Can't find the file for the selected frame to " + "use as the default file."); + return false; + } + return true; +} + // Modifiable Breakpoint Options #pragma mark Modify::CommandOptions #define LLDB_OPTIONS_breakpoint_modify @@ -200,6 +232,53 @@ public: BreakpointOptions m_bp_opts; }; +// This is the Breakpoint Names option group - used to add Names to breakpoints +// while making them. Not to be confused with the "Breakpoint Name" option +// group which is the common options of various "breakpoint name" commands. +#define LLDB_OPTIONS_breakpoint_names +#include "CommandOptions.inc" + +class BreakpointNamesOptionGroup : public OptionGroup { +public: + BreakpointNamesOptionGroup() = default; + + ~BreakpointNamesOptionGroup() override = default; + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return g_breakpoint_names_options; + } + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'N': + if (BreakpointID::StringIsBreakpointName(option_value, error)) + m_breakpoint_names.push_back(std::string(option_value)); + else + error = Status::FromError( + CreateOptionParsingError(option_value, short_option, long_option, + "Invalid breakpoint name")); + break; + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_breakpoint_names.clear(); + } + + const std::vector<std::string> &GetBreakpointNames() { + return m_breakpoint_names; + } + +protected: + std::vector<std::string> m_breakpoint_names; +}; + #define LLDB_OPTIONS_breakpoint_dummy #include "CommandOptions.inc" @@ -237,6 +316,1190 @@ public: bool m_use_dummy; }; +#pragma mark AddAddress::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_address +#include "CommandOptions.inc" + +#pragma mark Add Address + +static bool CopyOverBreakpointOptions(BreakpointSP bp_sp, + BreakpointOptionGroup &bp_opts, + const std::vector<std::string> &bp_names, + CommandReturnObject &result) { + assert(bp_sp && "CopyOverBreakpointOptions called with no breakpoint"); + + bp_sp->GetOptions().CopyOverSetOptions(bp_opts.GetBreakpointOptions()); + Target &target = bp_sp->GetTarget(); + if (!bp_names.empty()) { + Status name_error; + for (auto name : bp_names) { + target.AddNameToBreakpoint(bp_sp, name.c_str(), name_error); + if (name_error.Fail()) { + result.AppendErrorWithFormat("Invalid breakpoint name: %s", + name.c_str()); + target.RemoveBreakpointByID(bp_sp->GetID()); + return false; + } + } + } + return true; +} + +static llvm::Expected<LanguageType> +GetExceptionLanguageForLanguage(llvm::StringRef lang_name, + char short_option = '\0', + llvm::StringRef long_option = {}) { + llvm::Expected<LanguageType> exception_language = + Language::GetExceptionLanguageForLanguage(lang_name); + if (!exception_language) { + std::string error_msg = llvm::toString(exception_language.takeError()); + return CreateOptionParsingError(lang_name, short_option, long_option, + error_msg); + } + return exception_language; +} + +static Status CompleteLineEntry(ExecutionContext &exe_ctx, + OptionValueFileColonLine &line_entry) { + Status error; + uint32_t line_num = line_entry.GetLineNumber(); + if (!line_entry.GetFileSpec()) { + FileSpec default_file_spec; + std::string error_msg; + Target *target = exe_ctx.GetTargetPtr(); + if (!target) { + error.FromErrorString("Can't complete a line entry with no " + "target"); + return error; + } + Debugger &dbg = target->GetDebugger(); + CommandReturnObject result(dbg.GetUseColor()); + if (!GetDefaultFile(*target, exe_ctx.GetFramePtr(), default_file_spec, + result)) { + error.FromErrorStringWithFormatv("{0}/nCouldn't get default file for " + "line {1}: {2}", + result.GetErrorString(), line_num, + error_msg); + return error; + } + line_entry.SetFile(default_file_spec); + } + return error; +} + +class CommandObjectBreakpointAddAddress : public CommandObjectParsed { +public: + CommandObjectBreakpointAddAddress(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint add address", + "Add breakpoints by raw address", nullptr) { + CommandArgumentData bp_id_arg; + + // Define the first (and only) variant of this arg. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Finalize(); + + AddSimpleArgumentList(eArgTypeAddress, eArgRepeatPlus); + } + + ~CommandObjectBreakpointAddAddress() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'H': + m_hardware = true; + break; + + case 's': + if (m_modules.GetSize() == 0) + m_modules.AppendIfUnique(FileSpec(option_arg)); + else + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + "Only one shared library can be " + "specified for address breakpoints.")); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_modules.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_address_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; // FIXME - this can go in the "modify" options. + FileSpecList m_modules; + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + // We've already asserted that there can only be one entry in m_modules: + const ExecutionContext &exe_ctx = m_interpreter.GetExecutionContext(); + // We don't set address breakpoints in the dummy target. + if (!exe_ctx.HasTargetScope() || exe_ctx.GetTargetPtr()->IsDummyTarget()) { + result.AppendError( + "can't set address breakpoints without a real target."); + return; + } + // Commands can't set internal breakpoints: + const bool internal = false; + + Target &target = exe_ctx.GetTargetRef(); + + FileSpec module_spec; + bool has_module = false; + if (m_options.m_modules.GetSize() != 0) { + has_module = true; + module_spec = m_options.m_modules.GetFileSpecAtIndex(0); + } + BreakpointSP bp_sp; + // Let's process the arguments first so we can short-circuit if there are + // any errors: + std::vector<lldb::addr_t> bp_addrs; + for (const Args::ArgEntry &arg_entry : command) { + Address bp_address; + Status error; + lldb::addr_t bp_load_addr = OptionArgParser::ToAddress( + &exe_ctx, arg_entry.ref(), LLDB_INVALID_ADDRESS, &error); + if (error.Fail()) { + result.AppendErrorWithFormatv("invalid argument value '{0}': {1}", + arg_entry.ref(), error); + return; + } + bp_addrs.push_back(bp_load_addr); + } + for (auto bp_addr : bp_addrs) { + if (has_module) + bp_sp = target.CreateAddressInModuleBreakpoint( + bp_addr, internal, module_spec, m_options.m_hardware); + else + // ENHANCEMENT: we should see if bp_addr is in a single loaded module, + // and pass that module in if it is. + bp_sp = + target.CreateBreakpoint(bp_addr, internal, m_options.m_hardware); + } + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/false); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddException::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_exception +#include "CommandOptions.inc" + +#pragma mark Add Exception + +class CommandObjectBreakpointAddException : public CommandObjectParsed { +public: + CommandObjectBreakpointAddException(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add exception", + "Add breakpoints on language exceptions. If no language is " + "specified, break on exceptions for all supported languages", + nullptr) { + // Define the first (and only) variant of this arg. + AddSimpleArgumentList(eArgTypeLanguage, eArgRepeatStar); + + // Next add all the options. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddException() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + + switch (short_option) { + case 'E': { + uint32_t this_val = (uint32_t)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eExceptionStageThrow, error); + if (error.Fail()) + return error; + m_exception_stage |= this_val; + } break; + case 'H': + m_hardware = true; + break; + + case 'O': + m_exception_extra_args.AppendArgument("-O"); + m_exception_extra_args.AppendArgument(option_arg); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_exception_extra_args.Clear(); + m_exception_stage = eExceptionStageThrow; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_exception_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; // FIXME - this can go in the "modify" options. + Args m_exception_extra_args; + uint32_t m_exception_stage = eExceptionStageThrow; + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + BreakpointSP bp_sp; + LanguageType exception_language = eLanguageTypeUnknown; + + if (command.size() == 0) { + result.AppendError("no languages specified"); + } else if (command.size() > 1) { + result.AppendError( + "can only set exception breakpoints on one language at a time"); + } else { + llvm::Expected<LanguageType> language = + GetExceptionLanguageForLanguage(command[0].ref()); + if (language) + exception_language = *language; + else { + result.SetError(language.takeError()); + return; + } + } + Status precond_error; + const bool internal = false; + bool catch_bp = (m_options.m_exception_stage & eExceptionStageCatch) != 0; + bool throw_bp = (m_options.m_exception_stage & eExceptionStageThrow) != 0; + bp_sp = target.CreateExceptionBreakpoint( + exception_language, catch_bp, throw_bp, internal, + &m_options.m_exception_extra_args, &precond_error); + if (precond_error.Fail()) { + result.AppendErrorWithFormat( + "Error setting extra exception arguments: %s", + precond_error.AsCString()); + target.RemoveBreakpointByID(bp_sp->GetID()); + return; + } + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/false); + // Note, we don't print a "got no locations" warning for exception + // breakpoints. They can get set in the dummy target, and we won't know + // how to actually set the breakpoint till we know what version of the + // relevant LanguageRuntime gets loaded. + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddFile::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_file +#include "CommandOptions.inc" + +#pragma mark Add File + +class CommandObjectBreakpointAddFile : public CommandObjectParsed { +public: + CommandObjectBreakpointAddFile(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add file", + "Add breakpoints on lines in specified source files", nullptr) { + CommandArgumentEntry arg1; + CommandArgumentData linespec_arg; + CommandArgumentData no_arg; + + // Any number of linespecs in group 1: + linespec_arg.arg_type = eArgTypeFileLineColumn; + linespec_arg.arg_repetition = eArgRepeatPlus; + linespec_arg.arg_opt_set_association = LLDB_OPT_SET_1; + + arg1.push_back(linespec_arg); + + // Leave arg2 empty, there are no arguments to this variant. + CommandArgumentEntry arg2; + no_arg.arg_type = eArgTypeNone; + no_arg.arg_repetition = eArgRepeatOptional; + no_arg.arg_opt_set_association = LLDB_OPT_SET_2; + + arg2.push_back(linespec_arg); + + // Push the data for the first argument into the m_arguments vector. + m_arguments.push_back(arg1); + m_arguments.push_back(arg2); + + // Define the first (and only) variant of this arg. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddFile() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'f': + m_cur_value.SetFile(FileSpec(option_arg)); + break; + case 'l': + uint32_t line_num; + if (option_arg.getAsInteger(0, line_num)) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_int_parsing_error_message)); + else { + // The line number is the only required part of the options for a + // specifying the location - since we will fill in the file with the + // default file. So when we see a new line, the old line entry we + // were building is done. If we haven't gotten a file, try to fill + // in the default file, and then finish up this linespec and start + // the next one. + if (m_cur_value.GetLineNumber() != LLDB_INVALID_LINE_NUMBER) { + // FIXME: It should be possible to create a breakpoint with a list + // of file, line, column values. But for now we can only create + // one, so return an error here. The commented out code is what we + // will do when I come back to add that capability. + return Status::FromErrorString("Can only specify one file and line " + "pair at a time."); +#if 0 // This code will be appropriate once we have a resolver that can take + // more than one linespec at a time. + error = CompleteLineEntry(*execution_context, m_cur_value); + if (error.Fail()) + return error; + + m_line_specs.push_back(m_cur_value); + m_cur_value.Clear(); +#endif + } + m_cur_value.SetLine(line_num); + } + break; + case 'u': + uint32_t column_num; + if (option_arg.getAsInteger(0, column_num)) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_int_parsing_error_message)); + else + m_cur_value.SetColumn(column_num); + break; + case 'K': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (value) + m_skip_prologue = eLazyBoolYes; + else + m_skip_prologue = eLazyBoolNo; + + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + } break; + case 'm': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (value) + m_move_to_nearest_code = eLazyBoolYes; + else + m_move_to_nearest_code = eLazyBoolNo; + + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + } break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + case 'S': { + lldb::addr_t tmp_offset_addr; + tmp_offset_addr = OptionArgParser::ToAddress(execution_context, + option_arg, 0, &error); + if (error.Success()) + m_offset_addr = tmp_offset_addr; + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_line_specs.clear(); + m_cur_value.Clear(); + m_skip_prologue = eLazyBoolCalculate; + m_modules.Clear(); + m_move_to_nearest_code = eLazyBoolCalculate; + m_offset_addr = 0; + } + + Status OptionParsingFinished(ExecutionContext *execution_context) override { + // We were supplied at least a line from the options, so fill in the + // default file if needed. + if (m_cur_value.GetLineNumber() != LLDB_INVALID_LINE_NUMBER) { + Status error = CompleteLineEntry(*execution_context, m_cur_value); + if (error.Fail()) + return error; + m_line_specs.push_back(m_cur_value); + m_cur_value.Clear(); + } + return {}; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_file_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; // FIXME - this can go in the "modify" options. + std::vector<OptionValueFileColonLine> m_line_specs; + LazyBool m_skip_prologue = eLazyBoolCalculate; + OptionValueFileColonLine m_cur_value; + FileSpecList m_modules; + LazyBool m_move_to_nearest_code = eLazyBoolCalculate; + lldb::addr_t m_offset_addr = 0; + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + bool internal = false; + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + // FIXME: At present we can only make file & line breakpoints for one file + // and line pair. It wouldn't be hard to extend that, but I'm not adding + // features at this point so I'll leave that for a future patch. For now, + // flag this as an error. + + // I'm leaving this as a loop since that's how it should be when we can + // do more than one linespec at a time. + FileSpec default_file; + for (const Args::ArgEntry &this_arg : command) { + OptionValueFileColonLine value; + uint32_t line_value = LLDB_INVALID_LINE_NUMBER; + if (!this_arg.ref().getAsInteger(0, line_value)) { + // The argument is a plain number. Treat that as a line number, and + // allow it if we can find a default file & line. + std::string error_msg; + if (!GetDefaultFile(target, m_exe_ctx.GetFramePtr(), default_file, + result)) { + result.AppendErrorWithFormatv("Couldn't find default file for line " + "input: {0} - {1}", + line_value, error_msg); + return; + } + value.SetLine(line_value); + value.SetFile(default_file); + } else { + Status error = value.SetValueFromString(this_arg.c_str()); + if (error.Fail()) { + result.AppendErrorWithFormatv("Failed to parse linespec: {0}", error); + return; + } + } + m_options.m_line_specs.push_back(value); + } + + if (m_options.m_line_specs.size() != 1) { + result.AppendError("Can only make file and line breakpoints with one " + "specification at a time."); + return; + } + + BreakpointSP bp_sp; + // Only check for inline functions if + LazyBool check_inlines = eLazyBoolCalculate; + + OptionValueFileColonLine &this_spec = m_options.m_line_specs[0]; + bp_sp = target.CreateBreakpoint( + &(m_options.m_modules), this_spec.GetFileSpec(), + this_spec.GetLineNumber(), this_spec.GetColumnNumber(), + m_options.m_offset_addr, check_inlines, m_options.m_skip_prologue, + internal, m_options.m_hardware, m_options.m_move_to_nearest_code); + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddName::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_name +#include "CommandOptions.inc" + +#pragma mark Add Name + +class CommandObjectBreakpointAddName : public CommandObjectParsed { +public: + CommandObjectBreakpointAddName(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "breakpoint add name", + "Add breakpoints matching function or symbol names", + nullptr) { + // FIXME: Add a completer that's aware of the name match style. + // Define the first (and only) variant of this arg. + AddSimpleArgumentList(eArgTypeFunctionOrSymbol, eArgRepeatPlus); + + // Now add all the options groups. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddName() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'f': + m_files.AppendIfUnique(FileSpec(option_arg)); + break; + case 'K': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + else { + if (value) + m_skip_prologue = eLazyBoolYes; + else + m_skip_prologue = eLazyBoolNo; + } + } break; + case 'L': { + m_language = Language::GetLanguageTypeFromString(option_arg); + if (m_language == eLanguageTypeUnknown) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_language_parsing_error_message)); + } break; + case 'm': { + uint32_t this_val = (uint32_t)OptionArgParser::ToOptionEnum( + option_arg, GetDefinitions()[option_idx].enum_values, + eNameMatchStyleAuto, error); + if (error.Fail()) + return error; + m_lookup_style = (NameMatchStyle)this_val; + } break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + case 'S': { + lldb::addr_t tmp_offset_addr; + tmp_offset_addr = OptionArgParser::ToAddress(execution_context, + option_arg, 0, &error); + if (error.Success()) + m_offset_addr = tmp_offset_addr; + } break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_skip_prologue = eLazyBoolCalculate; + m_files.Clear(); + m_language = eLanguageTypeUnknown; + m_modules.Clear(); + m_offset_addr = 0; + m_lookup_style = eNameMatchStyleAuto; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_name_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; // FIXME - this can go in the "modify" options. + LazyBool m_skip_prologue = eLazyBoolCalculate; + FileSpecList m_modules; + LanguageType m_language = eLanguageTypeUnknown; + FileSpecList m_files; + LazyBool m_move_to_nearest_code = eLazyBoolCalculate; + lldb::addr_t m_offset_addr = 0; + NameMatchStyle m_lookup_style = eNameMatchStyleAuto; + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + const bool internal = false; + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + // Parse the argument list - this is a simple list of names. + std::vector<std::string> func_names; + for (const Args::ArgEntry &this_arg : command) { + func_names.push_back(this_arg.ref().str()); + } + BreakpointSP bp_sp; + if (!(m_options.m_lookup_style & eNameMatchStyleRegex)) + bp_sp = target.CreateBreakpoint( + &m_options.m_modules, &m_options.m_files, func_names, + (FunctionNameType)m_options.m_lookup_style, m_options.m_language, + m_options.m_offset_addr, m_options.m_skip_prologue, internal, + m_options.m_hardware); + else { + if (func_names.size() != 1) { + result.AppendError("Can only set function regular expression " + "breakpoints on one regex at a time."); + return; + } + std::string &func_regexp = func_names[0]; + RegularExpression regexp(func_regexp); + if (llvm::Error err = regexp.GetError()) { + result.AppendErrorWithFormat( + "Function name regular expression could not be compiled: %s", + llvm::toString(std::move(err)).c_str()); + // Check if the incorrect regex looks like a globbing expression and + // warn the user about it. + if (!func_regexp.empty()) { + if (func_regexp[0] == '*' || func_regexp[0] == '?') + result.AppendWarning( + "Function name regex does not accept glob patterns."); + } + return; + } + + bp_sp = target.CreateFuncRegexBreakpoint( + &(m_options.m_modules), &(m_options.m_files), std::move(regexp), + m_options.m_language, m_options.m_skip_prologue, internal, + m_options.m_hardware); + } + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddPattern::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_pattern +#include "CommandOptions.inc" + +#pragma mark Add Pattern + +class CommandObjectBreakpointAddPattern : public CommandObjectRaw { +public: + CommandObjectBreakpointAddPattern(CommandInterpreter &interpreter) + : CommandObjectRaw(interpreter, "breakpoint add pattern", + "Add breakpoints matching patterns in the source text", + "breakpoint add pattern [options] -- <pattern>") { + AddSimpleArgumentList(eArgTypeRegularExpression, eArgRepeatPlain); + // Now add all the options groups. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddPattern() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + const char *long_option = GetDefinitions()[option_idx].long_option; + + switch (short_option) { + case 'a': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + else + m_all_files = value; + } break; + case 'f': + m_files.AppendIfUnique(FileSpec(option_arg)); + break; + case 'm': { + bool success; + bool value; + value = OptionArgParser::ToBoolean(option_arg, true, &success); + if (!success) + error = Status::FromError( + CreateOptionParsingError(option_arg, short_option, long_option, + g_bool_parsing_error_message)); + else { + if (value) + m_move_to_nearest_code = eLazyBoolYes; + else + m_move_to_nearest_code = eLazyBoolNo; + } + } break; + case 'n': + m_func_names.insert(option_arg.str()); + break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_skip_prologue = eLazyBoolCalculate; + m_modules.Clear(); + m_files.Clear(); + m_func_names.clear(); + m_all_files = false; + m_move_to_nearest_code = eLazyBoolCalculate; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_pattern_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; // FIXME - this can go in the "modify" options. + LazyBool m_skip_prologue = eLazyBoolCalculate; + FileSpecList m_modules; + FileSpecList m_files; + std::unordered_set<std::string> m_func_names; + bool m_all_files = false; + LazyBool m_move_to_nearest_code = eLazyBoolCalculate; + }; + +protected: + void DoExecute(llvm::StringRef command, + CommandReturnObject &result) override { + const bool internal = false; + ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext(); + m_all_options.NotifyOptionParsingStarting(&exe_ctx); + + if (command.empty()) { + result.AppendError("no pattern to seek."); + return; + } + + OptionsWithRaw args(command); + + if (args.HasArgs()) { + if (!ParseOptionsAndNotify(args.GetArgs(), result, m_all_options, + exe_ctx)) + return; + } + llvm::StringRef pattern = args.GetRawPart(); + if (pattern.empty()) { + result.AppendError("no pattern to seek"); + return; + } + + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + + BreakpointSP bp_sp; + const size_t num_files = m_options.m_files.GetSize(); + + if (num_files == 0 && !m_options.m_all_files) { + FileSpec file; + if (!GetDefaultFile(target, m_exe_ctx.GetFramePtr(), file, result)) { + result.AppendError( + "No files provided and could not find default file."); + return; + } else { + m_options.m_files.Append(file); + } + } + + RegularExpression regexp(pattern); + if (llvm::Error err = regexp.GetError()) { + result.AppendErrorWithFormat( + "Source text regular expression could not be compiled: \"%s\"", + llvm::toString(std::move(err)).c_str()); + return; + } + bp_sp = target.CreateSourceRegexBreakpoint( + &(m_options.m_modules), &(m_options.m_files), m_options.m_func_names, + std::move(regexp), internal, m_options.m_hardware, + m_options.m_move_to_nearest_code); + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("Breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark AddScripted::CommandOptions +#define LLDB_OPTIONS_breakpoint_add_scripted +#include "CommandOptions.inc" + +#pragma mark Add Scripted + +class CommandObjectBreakpointAddScripted : public CommandObjectParsed { +public: + CommandObjectBreakpointAddScripted(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "breakpoint add scripted", + "Add breakpoints using a scripted breakpoint resolver.", nullptr), + m_python_class_options("scripted breakpoint", true, 'P') { + // We're picking up all the normal options, commands and disable. + m_all_options.Append(&m_python_class_options, + LLDB_OPT_SET_1 | LLDB_OPT_SET_2, LLDB_OPT_SET_1); + // Define the first (and only) variant of this arg. + m_all_options.Append(&m_bp_opts, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_name_opts); + m_all_options.Append(&m_dummy_options, LLDB_OPT_SET_ALL, LLDB_OPT_SET_1); + m_all_options.Append(&m_options); + m_all_options.Finalize(); + } + + ~CommandObjectBreakpointAddScripted() override = default; + + Options *GetOptions() override { return &m_all_options; } + + class CommandOptions : public OptionGroup { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = GetDefinitions()[option_idx].short_option; + + switch (short_option) { + case 'f': + m_files.Append(FileSpec(option_arg)); + break; + case 's': + m_modules.AppendIfUnique(FileSpec(option_arg)); + break; + case 'H': + m_hardware = true; + break; + + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_hardware = false; + m_files.Clear(); + m_modules.Clear(); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + return llvm::ArrayRef(g_breakpoint_add_scripted_options); + } + + // Instance variables to hold the values for command options. + bool m_hardware = false; // FIXME - this can go in the "modify" options. + FileSpecList m_files; + FileSpecList m_modules; + }; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = + m_dummy_options.m_use_dummy ? GetDummyTarget() : GetTarget(); + + BreakpointSP bp_sp; + Status error; + bp_sp = target.CreateScriptedBreakpoint( + m_python_class_options.GetName().c_str(), &(m_options.m_modules), + &(m_options.m_files), false, m_options.m_hardware, + m_python_class_options.GetStructuredData(), &error); + if (error.Fail()) { + result.AppendErrorWithFormat( + "error setting extra exception arguments: %s", error.AsCString()); + target.RemoveBreakpointByID(bp_sp->GetID()); + return; + } + + if (bp_sp) { + CopyOverBreakpointOptions(bp_sp, m_bp_opts, + m_name_opts.GetBreakpointNames(), result); + Stream &output_stream = result.GetOutputStream(); + bp_sp->GetDescription(&output_stream, lldb::eDescriptionLevelInitial, + /*show_locations=*/false); + if (&target == &GetDummyTarget()) + output_stream.Printf("Breakpoint set in dummy target, will get copied " + "into future targets.\n"); + else { + // Don't print out this warning for exception breakpoints. They can + // get set before the target is set, but we won't know how to actually + // set the breakpoint till we run. + if (bp_sp->GetNumLocations() == 0) { + output_stream.Printf("WARNING: Unable to resolve breakpoint to any " + "actual locations.\n"); + } + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("breakpoint creation failed: No breakpoint created."); + } + } + +private: + BreakpointOptionGroup m_bp_opts; + BreakpointNamesOptionGroup m_name_opts; + BreakpointDummyOptionGroup m_dummy_options; + OptionGroupPythonClassWithDict m_python_class_options; + CommandOptions m_options; + OptionGroupOptions m_all_options; +}; + +#pragma mark Add::CommandOptions +#define LLDB_OPTIONS_breakpoint_add +#include "CommandOptions.inc" + +#pragma mark Add + +class CommandObjectBreakpointAdd : public CommandObjectMultiword { +public: + CommandObjectBreakpointAdd(CommandInterpreter &interpreter) + : CommandObjectMultiword(interpreter, "add", + "Commands to add breakpoints of various types") { + SetHelpLong( + R"( +Access the breakpoint search kernels built into lldb. Along with specifying the +search kernel, each breakpoint add operation can specify a common set of +"reaction" options for each breakpoint. The reaction options can also be +modified after breakpoint creation using the "breakpoint modify" command. + )"); + CommandObjectSP address_command_object( + new CommandObjectBreakpointAddAddress(interpreter)); + CommandObjectSP exception_command_object( + new CommandObjectBreakpointAddException(interpreter)); + CommandObjectSP file_command_object( + new CommandObjectBreakpointAddFile(interpreter)); + CommandObjectSP name_command_object( + new CommandObjectBreakpointAddName(interpreter)); + CommandObjectSP pattern_command_object( + new CommandObjectBreakpointAddPattern(interpreter)); + CommandObjectSP scripted_command_object( + new CommandObjectBreakpointAddScripted(interpreter)); + + LoadSubCommand("address", address_command_object); + LoadSubCommand("exception", exception_command_object); + LoadSubCommand("file", file_command_object); + LoadSubCommand("name", name_command_object); + LoadSubCommand("pattern", pattern_command_object); + LoadSubCommand("scripted", scripted_command_object); + } +}; + #define LLDB_OPTIONS_breakpoint_set #include "CommandOptions.inc" @@ -313,42 +1576,12 @@ public: break; case 'E': { - LanguageType language = Language::GetLanguageTypeFromString(option_arg); - - llvm::StringRef error_context; - switch (language) { - case eLanguageTypeC89: - case eLanguageTypeC: - case eLanguageTypeC99: - case eLanguageTypeC11: - m_exception_language = eLanguageTypeC; - break; - case eLanguageTypeC_plus_plus: - case eLanguageTypeC_plus_plus_03: - case eLanguageTypeC_plus_plus_11: - case eLanguageTypeC_plus_plus_14: - m_exception_language = eLanguageTypeC_plus_plus; - break; - case eLanguageTypeObjC_plus_plus: - error_context = - "Set exception breakpoints separately for c++ and objective-c"; - break; - case eLanguageTypeUnknown: - error_context = "Unknown language type for exception breakpoint"; - break; - default: - if (Language *languagePlugin = Language::FindPlugin(language)) { - if (languagePlugin->SupportsExceptionBreakpointsOnThrow() || - languagePlugin->SupportsExceptionBreakpointsOnCatch()) { - m_exception_language = language; - break; - } - } - error_context = "Unsupported language type for exception breakpoint"; - } - if (!error_context.empty()) - error = Status::FromError(CreateOptionParsingError( - option_arg, short_option, long_option, error_context)); + llvm::Expected<LanguageType> language = GetExceptionLanguageForLanguage( + option_arg, short_option, long_option); + if (language) + m_exception_language = *language; + else + error = Status::FromError(language.takeError()); } break; case 'f': @@ -608,8 +1841,8 @@ protected: FileSpec file; const size_t num_files = m_options.m_filenames.GetSize(); if (num_files == 0) { - if (!GetDefaultFile(target, file, result)) { - result.AppendError("no file supplied and no default file available"); + if (!GetDefaultFile(target, m_exe_ctx.GetFramePtr(), file, result)) { + result.AppendError("no file supplied and no default file available."); return; } } else if (num_files > 1) { @@ -694,7 +1927,7 @@ protected: if (num_files == 0 && !m_options.m_all_files) { FileSpec file; - if (!GetDefaultFile(target, file, result)) { + if (!GetDefaultFile(target, m_exe_ctx.GetFramePtr(), file, result)) { result.AppendError( "No files provided and could not find default file."); return; @@ -789,40 +2022,6 @@ protected: } private: - bool GetDefaultFile(Target &target, FileSpec &file, - CommandReturnObject &result) { - // First use the Source Manager's default file. Then use the current stack - // frame's file. - if (auto maybe_file_and_line = - target.GetSourceManager().GetDefaultFileAndLine()) { - file = maybe_file_and_line->support_file_sp->GetSpecOnly(); - return true; - } - - StackFrame *cur_frame = m_exe_ctx.GetFramePtr(); - if (cur_frame == nullptr) { - result.AppendError( - "No selected frame to use to find the default file."); - return false; - } - if (!cur_frame->HasDebugInformation()) { - result.AppendError("Cannot use the selected frame to find the default " - "file, it has no debug info."); - return false; - } - - const SymbolContext &sc = - cur_frame->GetSymbolContext(eSymbolContextLineEntry); - if (sc.line_entry.GetFile()) { - file = sc.line_entry.GetFile(); - } else { - result.AppendError("Can't find the file for the selected frame to " - "use as the default file."); - return false; - } - return true; - } - BreakpointOptionGroup m_bp_opts; BreakpointDummyOptionGroup m_dummy_options; OptionGroupPythonClassWithDict m_python_class_options; @@ -2408,6 +3607,8 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( new CommandObjectBreakpointDelete(interpreter)); CommandObjectSP set_command_object( new CommandObjectBreakpointSet(interpreter)); + CommandObjectSP add_command_object( + new CommandObjectBreakpointAdd(interpreter)); CommandObjectSP command_command_object( new CommandObjectBreakpointCommand(interpreter)); CommandObjectSP modify_command_object( @@ -2425,6 +3626,7 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( clear_command_object->SetCommandName("breakpoint clear"); delete_command_object->SetCommandName("breakpoint delete"); set_command_object->SetCommandName("breakpoint set"); + add_command_object->SetCommandName("breakpoint add"); command_command_object->SetCommandName("breakpoint command"); modify_command_object->SetCommandName("breakpoint modify"); name_command_object->SetCommandName("breakpoint name"); @@ -2437,6 +3639,7 @@ CommandObjectMultiwordBreakpoint::CommandObjectMultiwordBreakpoint( LoadSubCommand("clear", clear_command_object); LoadSubCommand("delete", delete_command_object); LoadSubCommand("set", set_command_object); + LoadSubCommand("add", add_command_object); LoadSubCommand("command", command_command_object); LoadSubCommand("modify", modify_command_object); LoadSubCommand("name", name_command_object); diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp index 0d9eb45..40f00c9 100644 --- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp +++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp @@ -95,9 +95,9 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command, StackFrame *frame = m_exe_ctx.GetFramePtr(); // Either the language was explicitly specified, or we check the frame. - lldb::LanguageType language = m_expr_options.language; - if (language == lldb::eLanguageTypeUnknown && frame) - language = frame->GuessLanguage().AsLanguageType(); + SourceLanguage language{m_expr_options.language}; + if (!language && frame) + language = frame->GuessLanguage(); // Add a hint if object description was requested, but no description // function was implemented. @@ -119,8 +119,8 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command, "^<\\S+: 0x[[:xdigit:]]{5,}>\\s*$"); if (GetDebugger().GetShowDontUsePoHint() && target_ptr && - (language == lldb::eLanguageTypeSwift || - language == lldb::eLanguageTypeObjC) && + (language.AsLanguageType() == lldb::eLanguageTypeSwift || + language.IsObjC()) && std::regex_match(output.data(), swift_class_regex)) { result.AppendNote( @@ -193,7 +193,8 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command, // Second, try `expr` as a persistent variable. if (expr.starts_with("$")) - if (auto *state = target.GetPersistentExpressionStateForLanguage(language)) + if (auto *state = target.GetPersistentExpressionStateForLanguage( + language.AsLanguageType())) if (auto var_sp = state->GetVariable(expr)) if (auto valobj_sp = var_sp->GetValueObject()) { dump_val_object(*valobj_sp); diff --git a/lldb/source/Commands/CommandObjectExpression.cpp b/lldb/source/Commands/CommandObjectExpression.cpp index 197bffe9..4919bd3 100644 --- a/lldb/source/Commands/CommandObjectExpression.cpp +++ b/lldb/source/Commands/CommandObjectExpression.cpp @@ -13,6 +13,7 @@ #include "lldb/Expression/UserExpression.h" #include "lldb/Host/OptionParser.h" #include "lldb/Host/StreamFile.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandOptionArgumentTable.h" #include "lldb/Interpreter/CommandReturnObject.h" @@ -21,7 +22,6 @@ #include "lldb/Target/Process.h" #include "lldb/Target/StackFrame.h" #include "lldb/Target/Target.h" -#include "lldb/Utility/DiagnosticsRendering.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-enumerations.h" diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp index 88a02dc..9133359 100644 --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -265,6 +265,29 @@ public: Options *GetOptions() override { return &m_options; } +private: + void SkipHiddenFrames(Thread &thread, uint32_t frame_idx) { + uint32_t candidate_idx = frame_idx; + const unsigned max_depth = 12; + for (unsigned num_try = 0; num_try < max_depth; ++num_try) { + if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) { + candidate_idx = UINT32_MAX; + break; + } + candidate_idx += *m_options.relative_frame_offset; + if (auto candidate_sp = thread.GetStackFrameAtIndex(candidate_idx)) { + if (candidate_sp->IsHidden()) + continue; + // Now candidate_idx is the first non-hidden frame. + break; + } + candidate_idx = UINT32_MAX; + break; + }; + if (candidate_idx != UINT32_MAX) + m_options.relative_frame_offset = candidate_idx - frame_idx; + } + protected: void DoExecute(Args &command, CommandReturnObject &result) override { // No need to check "thread" for validity as eCommandRequiresThread ensures @@ -278,28 +301,13 @@ protected: if (frame_idx == UINT32_MAX) frame_idx = 0; - // If moving up/down by one, skip over hidden frames. - if (*m_options.relative_frame_offset == 1 || - *m_options.relative_frame_offset == -1) { - uint32_t candidate_idx = frame_idx; - const unsigned max_depth = 12; - for (unsigned num_try = 0; num_try < max_depth; ++num_try) { - if (candidate_idx == 0 && *m_options.relative_frame_offset == -1) { - candidate_idx = UINT32_MAX; - break; - } - candidate_idx += *m_options.relative_frame_offset; - if (auto candidate_sp = thread->GetStackFrameAtIndex(candidate_idx)) { - if (candidate_sp->IsHidden()) - continue; - // Now candidate_idx is the first non-hidden frame. - break; - } - candidate_idx = UINT32_MAX; - break; - }; - if (candidate_idx != UINT32_MAX) - m_options.relative_frame_offset = candidate_idx - frame_idx; + // If moving up/down by one, skip over hidden frames, unless we started + // in a hidden frame. + if ((*m_options.relative_frame_offset == 1 || + *m_options.relative_frame_offset == -1)) { + if (auto current_frame_sp = thread->GetStackFrameAtIndex(frame_idx); + !current_frame_sp->IsHidden()) + SkipHiddenFrames(*thread, frame_idx); } if (*m_options.relative_frame_offset < 0) { diff --git a/lldb/source/Commands/CommandObjectMultiword.cpp b/lldb/source/Commands/CommandObjectMultiword.cpp index a369557..e08b33c 100644 --- a/lldb/source/Commands/CommandObjectMultiword.cpp +++ b/lldb/source/Commands/CommandObjectMultiword.cpp @@ -205,7 +205,7 @@ void CommandObjectMultiword::Execute(const char *args_string, .str()); } error_msg.append("\n"); - result.AppendRawError(error_msg.c_str()); + result.AppendError(error_msg); } std::string CommandObjectMultiword::GetSubcommandsHintText() { diff --git a/lldb/source/Commands/CommandObjectProcess.cpp b/lldb/source/Commands/CommandObjectProcess.cpp index 7d326404..c17f12f 100644 --- a/lldb/source/Commands/CommandObjectProcess.cpp +++ b/lldb/source/Commands/CommandObjectProcess.cpp @@ -1603,8 +1603,8 @@ public: Options *GetOptions() override { return &m_options; } void PrintSignalHeader(Stream &str) { - str.Printf("NAME PASS STOP NOTIFY\n"); - str.Printf("=========== ===== ===== ======\n"); + str.Printf("NAME PASS STOP NOTIFY DESCRIPTION\n"); + str.Printf("=========== ===== ===== ====== ===================\n"); } void PrintSignal(Stream &str, int32_t signo, llvm::StringRef sig_name, @@ -1615,9 +1615,16 @@ public: str.Format("{0, -11} ", sig_name); if (signals_sp->GetSignalInfo(signo, suppress, stop, notify)) { - bool pass = !suppress; + const bool pass = !suppress; str.Printf("%s %s %s", (pass ? "true " : "false"), (stop ? "true " : "false"), (notify ? "true " : "false")); + + const llvm::StringRef sig_description = + signals_sp->GetSignalNumberDescription(signo); + if (!sig_description.empty()) { + str.PutCString(" "); + str.PutCString(sig_description); + } } str.Printf("\n"); } diff --git a/lldb/source/Commands/CommandObjectSource.cpp b/lldb/source/Commands/CommandObjectSource.cpp index 0b4599b..c9835e7 100644 --- a/lldb/source/Commands/CommandObjectSource.cpp +++ b/lldb/source/Commands/CommandObjectSource.cpp @@ -777,7 +777,7 @@ protected: if (sc.function) { Target &target = GetTarget(); - SupportFileSP start_file = std::make_shared<SupportFile>(); + SupportFileNSP start_file = std::make_shared<SupportFile>(); uint32_t start_line; uint32_t end_line; FileSpec end_file; @@ -1194,7 +1194,7 @@ protected: // file(s) will be found and assigned to // sc.comp_unit->GetPrimarySupportFile, which is NOT what we want to // print. Instead, we want to print the one from the line entry. - lldb::SupportFileSP found_file_sp = sc.line_entry.file_sp; + SupportFileNSP found_file_sp = sc.line_entry.file_sp; target.GetSourceManager().DisplaySourceLinesWithLineNumbers( found_file_sp, m_options.start_line, column, 0, diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 8de6521..322dd6c 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -51,6 +51,7 @@ #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StructuredData.h" @@ -60,6 +61,7 @@ #include "lldb/lldb-forward.h" #include "lldb/lldb-private-enumerations.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" @@ -5121,6 +5123,15 @@ public: : CommandObjectParsed(interpreter, "target stop-hook delete", "Delete a stop-hook.", "target stop-hook delete [<idx>]") { + SetHelpLong( + R"( +Deletes the stop hook by index. + +At any given stop, all enabled stop hooks that pass the stop filter will +get a chance to run. That means if one stop-hook deletes another stop hook +while executing, the deleted stop hook will still fire for the stop at which +it was deleted. + )"); AddSimpleArgumentList(eArgTypeStopHookID, eArgRepeatStar); } @@ -5392,6 +5403,200 @@ public: ~CommandObjectTargetDump() override = default; }; +#pragma mark CommandObjectTargetFrameProvider + +#define LLDB_OPTIONS_target_frame_provider_register +#include "CommandOptions.inc" + +class CommandObjectTargetFrameProviderRegister : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderRegister(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider register", + "Register frame provider for all threads in this target.", nullptr, + eCommandRequiresTarget), + + m_class_options("target frame-provider", true, 'C', 'k', 'v', 0) { + m_all_options.Append(&m_class_options, LLDB_OPT_SET_1 | LLDB_OPT_SET_2, + LLDB_OPT_SET_ALL); + m_all_options.Finalize(); + } + + ~CommandObjectTargetFrameProviderRegister() override = default; + + Options *GetOptions() override { return &m_all_options; } + + std::optional<std::string> GetRepeatCommand(Args ¤t_command_args, + uint32_t index) override { + return std::string(""); + } + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + ScriptedMetadataSP metadata_sp = std::make_shared<ScriptedMetadata>( + m_class_options.GetName(), m_class_options.GetStructuredData()); + + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) + target = &GetDebugger().GetDummyTarget(); + + // Create the interface for calling static methods. + ScriptedFrameProviderInterfaceSP interface_sp = + GetDebugger() + .GetScriptInterpreter() + ->CreateScriptedFrameProviderInterface(); + + // Create a descriptor from the metadata (applies to all threads by + // default). + ScriptedFrameProviderDescriptor descriptor(metadata_sp); + descriptor.interface_sp = interface_sp; + + auto id_or_err = target->AddScriptedFrameProviderDescriptor(descriptor); + if (!id_or_err) { + result.SetError(id_or_err.takeError()); + return; + } + + result.AppendMessageWithFormat( + "successfully registered scripted frame provider '%s' for target\n", + m_class_options.GetName().c_str()); + } + + OptionGroupPythonClassWithDict m_class_options; + OptionGroupOptions m_all_options; +}; + +class CommandObjectTargetFrameProviderClear : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderClear(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider clear", + "Clear all registered frame providers from this target.", nullptr, + eCommandRequiresTarget) {} + + ~CommandObjectTargetFrameProviderClear() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) { + result.AppendError("invalid target"); + return; + } + + target->ClearScriptedFrameProviderDescriptors(); + + result.SetStatus(eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectTargetFrameProviderList : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderList(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider list", + "List all registered frame providers for the target.", nullptr, + eCommandRequiresTarget) {} + + ~CommandObjectTargetFrameProviderList() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) + target = &GetDebugger().GetDummyTarget(); + + const auto &descriptors = target->GetScriptedFrameProviderDescriptors(); + if (descriptors.empty()) { + result.AppendMessage("no frame providers registered for this target."); + result.SetStatus(eReturnStatusSuccessFinishResult); + return; + } + + result.AppendMessageWithFormat("%u frame provider(s) registered:\n\n", + descriptors.size()); + + for (const auto &entry : descriptors) { + const ScriptedFrameProviderDescriptor &descriptor = entry.second; + descriptor.Dump(&result.GetOutputStream()); + result.GetOutputStream().PutChar('\n'); + } + + result.SetStatus(eReturnStatusSuccessFinishResult); + } +}; + +class CommandObjectTargetFrameProviderRemove : public CommandObjectParsed { +public: + CommandObjectTargetFrameProviderRemove(CommandInterpreter &interpreter) + : CommandObjectParsed( + interpreter, "target frame-provider remove", + "Remove a registered frame provider from the target by id.", + "target frame-provider remove <provider-id>", + eCommandRequiresTarget) { + AddSimpleArgumentList(eArgTypeUnsignedInteger, eArgRepeatPlus); + } + + ~CommandObjectTargetFrameProviderRemove() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + Target *target = m_exe_ctx.GetTargetPtr(); + if (!target) + target = &GetDebugger().GetDummyTarget(); + + std::vector<uint32_t> removed_provider_ids; + for (size_t i = 0; i < command.GetArgumentCount(); i++) { + uint32_t provider_id = 0; + if (!llvm::to_integer(command[i].ref(), provider_id)) { + result.AppendError("target frame-provider remove requires integer " + "provider id argument"); + return; + } + + if (!target->RemoveScriptedFrameProviderDescriptor(provider_id)) { + result.AppendErrorWithFormat( + "no frame provider named '%u' found in target\n", provider_id); + return; + } + removed_provider_ids.push_back(provider_id); + } + + if (size_t num_removed_providers = removed_provider_ids.size()) { + result.AppendMessageWithFormat( + "Successfully removed %zu frame-providers.\n", num_removed_providers); + result.SetStatus(eReturnStatusSuccessFinishNoResult); + } else { + result.AppendError("0 frame providers removed.\n"); + } + } +}; + +class CommandObjectTargetFrameProvider : public CommandObjectMultiword { +public: + CommandObjectTargetFrameProvider(CommandInterpreter &interpreter) + : CommandObjectMultiword( + interpreter, "target frame-provider", + "Commands for registering and viewing frame providers for the " + "target.", + "target frame-provider [<sub-command-options>] ") { + LoadSubCommand("register", + CommandObjectSP(new CommandObjectTargetFrameProviderRegister( + interpreter))); + LoadSubCommand("clear", + CommandObjectSP( + new CommandObjectTargetFrameProviderClear(interpreter))); + LoadSubCommand( + "list", + CommandObjectSP(new CommandObjectTargetFrameProviderList(interpreter))); + LoadSubCommand( + "remove", CommandObjectSP( + new CommandObjectTargetFrameProviderRemove(interpreter))); + } + + ~CommandObjectTargetFrameProvider() override = default; +}; + #pragma mark CommandObjectMultiwordTarget // CommandObjectMultiwordTarget @@ -5407,6 +5612,9 @@ CommandObjectMultiwordTarget::CommandObjectMultiwordTarget( CommandObjectSP(new CommandObjectTargetDelete(interpreter))); LoadSubCommand("dump", CommandObjectSP(new CommandObjectTargetDump(interpreter))); + LoadSubCommand( + "frame-provider", + CommandObjectSP(new CommandObjectTargetFrameProvider(interpreter))); LoadSubCommand("list", CommandObjectSP(new CommandObjectTargetList(interpreter))); LoadSubCommand("select", diff --git a/lldb/source/Commands/CommandObjectVersion.cpp b/lldb/source/Commands/CommandObjectVersion.cpp index f13ec18..fb7e399 100644 --- a/lldb/source/Commands/CommandObjectVersion.cpp +++ b/lldb/source/Commands/CommandObjectVersion.cpp @@ -8,13 +8,21 @@ #include "CommandObjectVersion.h" +#include "lldb/Core/Debugger.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Version/Version.h" +#include "llvm/ADT/StringExtras.h" using namespace lldb; using namespace lldb_private; -// CommandObjectVersion +#define LLDB_OPTIONS_version +#include "CommandOptions.inc" + +llvm::ArrayRef<OptionDefinition> +CommandObjectVersion::CommandOptions::GetDefinitions() { + return llvm::ArrayRef(g_version_options); +} CommandObjectVersion::CommandObjectVersion(CommandInterpreter &interpreter) : CommandObjectParsed(interpreter, "version", @@ -22,7 +30,45 @@ CommandObjectVersion::CommandObjectVersion(CommandInterpreter &interpreter) CommandObjectVersion::~CommandObjectVersion() = default; +// Dump the array values on a single line. +static void dump(const StructuredData::Array &array, Stream &s) { + std::vector<std::string> values; + array.ForEach([&](StructuredData::Object *object) -> bool { + values.emplace_back(object->GetStringValue().str()); + return true; + }); + + s << '[' << llvm::join(values, ", ") << ']'; +} + +// The default dump output is too verbose. +static void dump(const StructuredData::Dictionary &config, Stream &s) { + config.ForEach( + [&](llvm::StringRef key, StructuredData::Object *object) -> bool { + assert(object); + + StructuredData::Dictionary *value_dict = object->GetAsDictionary(); + assert(value_dict); + + StructuredData::ObjectSP value_sp = value_dict->GetValueForKey("value"); + assert(value_sp); + + s << " " << key << ": "; + if (StructuredData::Boolean *boolean = value_sp->GetAsBoolean()) + s << (boolean ? "yes" : "no"); + else if (StructuredData::Array *array = value_sp->GetAsArray()) + dump(*array, s); + s << '\n'; + + return true; + }); +} + void CommandObjectVersion::DoExecute(Args &args, CommandReturnObject &result) { result.AppendMessageWithFormat("%s\n", lldb_private::GetVersion()); + + if (m_options.verbose) + dump(*Debugger::GetBuildConfiguration(), result.GetOutputStream()); + result.SetStatus(eReturnStatusSuccessFinishResult); } diff --git a/lldb/source/Commands/CommandObjectVersion.h b/lldb/source/Commands/CommandObjectVersion.h index 4ba081b..ea2741c 100644 --- a/lldb/source/Commands/CommandObjectVersion.h +++ b/lldb/source/Commands/CommandObjectVersion.h @@ -9,20 +9,56 @@ #ifndef LLDB_SOURCE_COMMANDS_COMMANDOBJECTVERSION_H #define LLDB_SOURCE_COMMANDS_COMMANDOBJECTVERSION_H +#include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandObject.h" +#include "lldb/Interpreter/Options.h" namespace lldb_private { -// CommandObjectVersion - class CommandObjectVersion : public CommandObjectParsed { public: CommandObjectVersion(CommandInterpreter &interpreter); ~CommandObjectVersion() override; + class CommandOptions : public Options { + public: + CommandOptions() = default; + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'v': + verbose = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + verbose = false; + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override; + + bool verbose; + }; + + Options *GetOptions() override { return &m_options; } + protected: void DoExecute(Args &args, CommandReturnObject &result) override; + +private: + CommandOptions m_options; }; } // namespace lldb_private diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index ed06131..d96354a 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -173,6 +173,14 @@ let Command = "breakpoint dummy" in { "is provided, which prime new targets.">; } +let Command = "breakpoint names" in { + def breakpoint_name_option_names : + Option<"breakpoint-name", "N">, Group<1>, + Arg<"BreakpointName">, + Desc<"Adds this name to the list of names for this breakpoint. " + "Can be specified more than once.">; +} + let Command = "breakpoint set" in { def breakpoint_set_shlib : Option<"shlib", "s">, @@ -357,6 +365,124 @@ let Command = "breakpoint set" in { */ } +let Command = "breakpoint add address" in { + def breakpoint_add_address_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Set the breakpoint at an address relative to sections in this shared" + " library.">; + def breakpoint_add_address_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; +} + +let Command = "breakpoint add exception" in { + def breakpoint_add_exception_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_exception_typename : Option<"exception-typename", "O">, + Arg<"TypeName">, Desc<"The breakpoint will only stop if an " + "exception Object of this type is thrown. Can be repeated multiple times " + "to stop for multiple object types">; + def breakpoint_add_exception_stage : Option<"exception-stage", "E">, + Arg<"ExceptionStage">, + Desc<"Stop only at the specified exception stage. Can be specified more " + "than once to create a mask of stages.">; +} + +let Command = "breakpoint add file" in { + def breakpoint_add_file_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_file_line : Option<"line", "l">, Group<2>, Arg<"LineNum">, + Required, + Desc<"Specifies the line number on which to set this breakpoint.">; + def breakpoint_add_file_filename : Option<"filename", "f">, Group<2>, + Arg<"Filename">, Completion<"SourceFile">, Desc<"The file in which to seek " + "the specified source line.">; + def breakpoint_add_file_column : Option<"column", "u">, Arg<"ColumnNum">, Group<2>, + Desc<"Specifies the column number on which to set this breakpoint.">; + def breakpoint_add_file_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Set the breakpoint only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_add_file_move_to_nearest_code : Option<"move-to-nearest-code", "m">, + Arg<"Boolean">, + Desc<"Move breakpoints to nearest code. If not set the " + "target.move-to-nearest-code setting is used.">; + def breakpoint_add_file_address_slide : Option<"address-slide", "S">, + Arg<"Offset">, + Desc<"Add the specified offset to whatever address(es) the breakpoint " + "resolves to. At present this applies the offset directly as given, and " + "doesn't try to align it to instruction boundaries.">; + def breakpoint_add_file_skip_prologue : Option<"skip-prologue", "K">, + Arg<"Boolean">, + Desc<"Skip the prologue if the breakpoint is at the beginning of a " + "function. If not set the target.skip-prologue setting is used.">; +} + +let Command = "breakpoint add name" in { + def breakpoint_add_name_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_name_address_slide : Option<"address-slide", "S">, + Arg<"Offset">, + Desc<"Add the specified offset to whatever address(es) the breakpoint " + "resolves to. At present this applies the offset directly as given, and " + "doesn't try to align it to instruction boundaries.">; + def breakpoint_add_name_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Search for names only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_add_name_filename : Option<"filename", "f">, + Arg<"Filename">, Completion<"SourceFile">, Desc<"Only search for functions " + "defined in the given source file. Can be specified more than once.">; + def breakpoint_add_name_skip_prologue : Option<"skip-prologue", "K">, + Arg<"Boolean">, + Desc<"Skip the prologue if the breakpoint is at the beginning of a " + "function. If not set the target.skip-prologue setting is used.">; + def breakpoint_add_name_match_style : Option<"match-style", "m">, + Arg<"NameMatchStyle">, + Desc<"The style of matching to do when looking for candidate symbols - " + "auto if not specified.">; + def breakpoint_add_name_language : Option<"name-language", "L">, + Arg<"Language">, + Desc<"Only consider indentifiers from the given language when looking for " + "match candidates.">; +} + +let Command = "breakpoint add pattern" in { + def breakpoint_add_pattern_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_pattern_move_to_nearest_code : Option<"move-to-nearest-code", "m">, + Arg<"Boolean">, + Desc<"Move breakpoints to nearest code. If not set the " + "target.move-to-nearest-code setting is used.">; + def breakpoint_add_pattern_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"Search for pattern matches only in this shared library. Can repeat this " + "option multiple times to specify multiple shared libraries.">; + def breakpoint_add_pattern_filename : Option<"filename", "f">, Group<1>, + Arg<"Filename">, Completion<"SourceFile">, Desc<"Limit the pattern search " + "to the specified source file. Can be specified more than once.">; + def breakpoint_add_pattern_name : Option<"name", "n">, Arg<"FunctionName">, + Completion<"Symbol">, Group<1>, + Desc<"Search for pattern matches only in functions matching the specified " + "function name using the 'auto' match style. Can be specified more than " + "once, and composes with the filename option.">; + def breakpoint_add_pattern_all_files : Option<"all-files", "a">, Group<2>, + Desc<"All files are searched for source pattern matches, limited by the " + "shlib argument.">; +} + +let Command = "breakpoint add scripted" in { + def breakpoint_add_scripted_hardware : Option<"hardware", "H">, + Desc<"Require the breakpoint to use hardware breakpoints.">; + def breakpoint_add_scripted_filename : Option<"filename", "f">, + Arg<"Filename">, Completion<"SourceFile">, Desc<"The files in which to apply " + "the scripted resolver callback. Can repeat the option multiple times.">; + def breakpoint_add_scripted_shlib : Option<"shlib", "s">, Arg<"ShlibName">, + Completion<"Module">, + Desc<"The module in which to apply the scripted resolver callback. Can " + "repeat this option multiple times to specify multiple shared libraries.">; +} + + let Command = "breakpoint clear" in { def breakpoint_clear_file : Option<"file", "f">, Group<1>, @@ -2432,3 +2558,8 @@ let Command = "statistics dump" in { "enabled state. Defaults to true for both summary and default " "mode.">; } + +let Command = "version" in { + def version_verbose : Option<"verbose", "v">, + Desc<"Include build configuration in version output.">; +} diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td index 1be911c..a54d553 100644 --- a/lldb/source/Core/CoreProperties.td +++ b/lldb/source/Core/CoreProperties.td @@ -59,7 +59,7 @@ let Definition = "debugger" in { Desc<"The default disassembly format string to use when disassembling instruction sequences.">; def FrameFormat: Property<"frame-format", "FormatEntity">, Global, - DefaultStringValue<"frame #${frame.index}: ${ansi.fg.cyan}${frame.pc}${ansi.normal}{ ${module.file.basename}{`${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, + DefaultStringValue<"frame #${frame.index}: {${ansi.fg.cyan}${frame.pc}${ansi.normal}}{ ${module.file.basename}{`}}{${function.name-with-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, Desc<"The default frame format string to use when displaying stack frame information for threads.">; def NotiftVoid: Property<"notify-void", "Boolean">, Global, @@ -235,7 +235,7 @@ let Definition = "debugger" in { Desc<"If true, LLDB will automatically escape non-printable and escape characters when formatting strings.">; def FrameFormatUnique: Property<"frame-format-unique", "FormatEntity">, Global, - DefaultStringValue<"frame #${frame.index}: ${ansi.fg.cyan}${frame.pc}${ansi.normal}{ ${module.file.basename}{`${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, + DefaultStringValue<"frame #${frame.index}: {${ansi.fg.cyan}${frame.pc}${ansi.normal}}{ ${module.file.basename}{`}}{${function.name-without-args}{${frame.no-debug}${function.pc-offset}}}{ at ${ansi.fg.cyan}${line.file.basename}${ansi.normal}:${ansi.fg.yellow}${line.number}${ansi.normal}{:${ansi.fg.yellow}${line.column}${ansi.normal}}}${frame.kind}{${function.is-optimized} [opt]}{${function.is-inlined} [inlined]}{${frame.is-artificial} [artificial]}\\\\n">, Desc<"The default frame format string to use when displaying stack frame information for threads from thread backtrace unique.">; def ShowAutosuggestion: Property<"show-autosuggestion", "Boolean">, Global, diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp index b37d9d3..99f4a72 100644 --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -21,12 +21,14 @@ #include "lldb/Core/Telemetry.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/Expression/REPL.h" +#include "lldb/Host/Config.h" #include "lldb/Host/File.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/HostInfo.h" #include "lldb/Host/StreamFile.h" #include "lldb/Host/Terminal.h" #include "lldb/Host/ThreadLauncher.h" +#include "lldb/Host/XML.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Interpreter/OptionValue.h" @@ -965,7 +967,8 @@ llvm::StringRef Debugger::GetStaticBroadcasterClass() { Debugger::Debugger(lldb::LogOutputCallback log_callback, void *baton) : UserID(g_unique_id++), Properties(std::make_shared<OptionValueProperties>()), - m_input_file_sp(std::make_shared<NativeFile>(stdin, NativeFile::Unowned)), + m_input_file_sp(std::make_shared<NativeFile>( + stdin, File::eOpenOptionReadOnly, NativeFile::Unowned)), m_output_stream_sp(std::make_shared<LockableStreamFile>( stdout, NativeFile::Unowned, m_output_mutex)), m_error_stream_sp(std::make_shared<LockableStreamFile>( @@ -1172,7 +1175,8 @@ Status Debugger::SetInputString(const char *data) { return result; } - SetInputFile((FileSP)std::make_shared<NativeFile>(commands_file, true)); + SetInputFile((FileSP)std::make_shared<NativeFile>( + commands_file, File::eOpenOptionReadOnly, true)); return result; } @@ -1378,7 +1382,8 @@ void Debugger::AdoptTopIOHandlerFilesIfInvalid(FileSP &in, in = GetInputFileSP(); // If there is nothing, use stdin if (!in) - in = std::make_shared<NativeFile>(stdin, NativeFile::Unowned); + in = std::make_shared<NativeFile>(stdin, File::eOpenOptionReadOnly, + NativeFile::Unowned); } // If no STDOUT has been set, then set it appropriately if (!out || !out->GetUnlockedFile().IsValid()) { @@ -2439,3 +2444,56 @@ llvm::ThreadPoolInterface &Debugger::GetThreadPool() { "Debugger::GetThreadPool called before Debugger::Initialize"); return *g_thread_pool; } + +static void AddBoolConfigEntry(StructuredData::Dictionary &dict, + llvm::StringRef name, bool value, + llvm::StringRef description) { + auto entry_up = std::make_unique<StructuredData::Dictionary>(); + entry_up->AddBooleanItem("value", value); + entry_up->AddStringItem("description", description); + dict.AddItem(name, std::move(entry_up)); +} + +static void AddLLVMTargets(StructuredData::Dictionary &dict) { + auto array_up = std::make_unique<StructuredData::Array>(); +#define LLVM_TARGET(target) \ + array_up->AddItem(std::make_unique<StructuredData::String>(#target)); +#include "llvm/Config/Targets.def" + auto entry_up = std::make_unique<StructuredData::Dictionary>(); + entry_up->AddItem("value", std::move(array_up)); + entry_up->AddStringItem("description", "A list of configured LLVM targets."); + dict.AddItem("targets", std::move(entry_up)); +} + +StructuredData::DictionarySP Debugger::GetBuildConfiguration() { + auto config_up = std::make_unique<StructuredData::Dictionary>(); + AddBoolConfigEntry( + *config_up, "xml", XMLDocument::XMLEnabled(), + "A boolean value that indicates if XML support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "curl", LLVM_ENABLE_CURL, + "A boolean value that indicates if CURL support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "curses", LLDB_ENABLE_CURSES, + "A boolean value that indicates if curses support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "editline", LLDB_ENABLE_LIBEDIT, + "A boolean value that indicates if editline support is enabled in LLDB"); + AddBoolConfigEntry(*config_up, "editline_wchar", LLDB_EDITLINE_USE_WCHAR, + "A boolean value that indicates if editline wide " + "characters support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "lzma", LLDB_ENABLE_LZMA, + "A boolean value that indicates if lzma support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "python", LLDB_ENABLE_PYTHON, + "A boolean value that indicates if python support is enabled in LLDB"); + AddBoolConfigEntry( + *config_up, "lua", LLDB_ENABLE_LUA, + "A boolean value that indicates if lua support is enabled in LLDB"); + AddBoolConfigEntry(*config_up, "fbsdvmcore", LLDB_ENABLE_FBSDVMCORE, + "A boolean value that indicates if fbsdvmcore support is " + "enabled in LLDB"); + AddLLVMTargets(*config_up); + return config_up; +} diff --git a/lldb/source/Core/DemangledNameInfo.cpp b/lldb/source/Core/DemangledNameInfo.cpp index 76f8987..16fbfda 100644 --- a/lldb/source/Core/DemangledNameInfo.cpp +++ b/lldb/source/Core/DemangledNameInfo.cpp @@ -16,7 +16,7 @@ bool TrackingOutputBuffer::shouldTrack() const { if (!isPrintingTopLevelFunctionType()) return false; - if (isGtInsideTemplateArgs()) + if (isInsideTemplateArgs()) return false; if (NameInfo.ArgumentsRange.first > 0) @@ -29,7 +29,7 @@ bool TrackingOutputBuffer::canFinalize() const { if (!isPrintingTopLevelFunctionType()) return false; - if (isGtInsideTemplateArgs()) + if (isInsideTemplateArgs()) return false; if (NameInfo.ArgumentsRange.first == 0) diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp index f2ed1f7..2d73df1 100644 --- a/lldb/source/Core/Disassembler.cpp +++ b/lldb/source/Core/Disassembler.cpp @@ -208,7 +208,7 @@ Disassembler::GetFunctionDeclLineEntry(const SymbolContext &sc) { return {}; LineEntry prologue_end_line = sc.line_entry; - SupportFileSP func_decl_file_sp; + SupportFileNSP func_decl_file_sp = std::make_shared<SupportFile>(); uint32_t func_decl_line; sc.function->GetStartLineSourceInfo(func_decl_file_sp, func_decl_line); @@ -286,6 +286,18 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine( return false; } +static constexpr const llvm::StringLiteral kUndefLocation = "undef"; +static constexpr const llvm::StringLiteral kUndefLocationFormatted = "<undef>"; +static void +AddVariableAnnotationToVector(std::vector<VariableAnnotation> &annotations, + VariableAnnotation annotation_entity, + const bool is_live) { + annotation_entity.is_live = is_live; + if (!is_live) + annotation_entity.location_description = kUndefLocation; + annotations.push_back(std::move(annotation_entity)); +} + // For each instruction, this block attempts to resolve in-scope variables // and determine if the current PC falls within their // DWARF location entry. If so, it prints a simplified annotation using the @@ -299,17 +311,38 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine( // The goal is to give users helpful live variable hints alongside the // disassembled instruction stream, similar to how debug information // enhances source-level debugging. -std::vector<std::string> -VariableAnnotator::annotate(Instruction &inst, Target &target, - const lldb::ModuleSP &module_sp) { +std::vector<std::string> VariableAnnotator::Annotate(Instruction &inst) { + std::vector<VariableAnnotation> structured_annotations = + AnnotateStructured(inst); + std::vector<std::string> events; + events.reserve(structured_annotations.size()); + + for (const VariableAnnotation &annotation : structured_annotations) { + const llvm::StringRef location = + (annotation.location_description == kUndefLocation + ? llvm::StringRef(kUndefLocationFormatted) + : llvm::StringRef(annotation.location_description)); + + events.push_back( + llvm::formatv("{0} = {1}", annotation.variable_name, location).str()); + } + + return events; +} + +std::vector<VariableAnnotation> +VariableAnnotator::AnnotateStructured(Instruction &inst) { + std::vector<VariableAnnotation> annotations; + + auto module_sp = inst.GetAddress().GetModule(); - // If we lost module context, everything becomes <undef>. + // If we lost module context, mark all live variables as UndefLocation. if (!module_sp) { - for (const auto &KV : Live_) - events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str()); - Live_.clear(); - return events; + for (const auto &KV : m_live_vars) + AddVariableAnnotationToVector(annotations, KV.second, false); + m_live_vars.clear(); + return annotations; } // Resolve function/block at this *file* address. @@ -319,13 +352,13 @@ VariableAnnotator::annotate(Instruction &inst, Target &target, if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) || !sc.function) { // No function context: everything dies here. - for (const auto &KV : Live_) - events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str()); - Live_.clear(); - return events; + for (const auto &KV : m_live_vars) + AddVariableAnnotationToVector(annotations, KV.second, false); + m_live_vars.clear(); + return annotations; } - // Collect in-scope variables for this instruction into Current. + // Collect in-scope variables for this instruction into current_vars. VariableList var_list; // Innermost block containing iaddr. if (Block *B = sc.block) { @@ -341,7 +374,7 @@ VariableAnnotator::annotate(Instruction &inst, Target &target, const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress(); // ABI from Target (pretty reg names if plugin exists). Safe to be null. - lldb::ABISP abi_sp = ABI::FindPlugin(nullptr, target.GetArchitecture()); + lldb::ABISP abi_sp = ABI::FindPlugin(nullptr, module_sp->GetArchitecture()); ABI *abi = abi_sp.get(); llvm::DIDumpOptions opts; @@ -349,7 +382,7 @@ VariableAnnotator::annotate(Instruction &inst, Target &target, // Prefer "register-only" output when we have an ABI. opts.PrintRegisterOnly = static_cast<bool>(abi_sp); - llvm::DenseMap<lldb::user_id_t, VarState> Current; + llvm::DenseMap<lldb::user_id_t, VariableAnnotation> current_vars; for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) { lldb::VariableSP v = var_list.GetVariableAtIndex(i); @@ -376,35 +409,50 @@ VariableAnnotator::annotate(Instruction &inst, Target &target, if (loc.empty()) continue; - Current.try_emplace(v->GetID(), - VarState{std::string(name), std::string(loc)}); + std::optional<std::string> decl_file; + std::optional<uint32_t> decl_line; + std::optional<std::string> type_name; + + const Declaration &decl = v->GetDeclaration(); + if (decl.GetFile()) { + decl_file = decl.GetFile().GetFilename().AsCString(); + if (decl.GetLine() > 0) + decl_line = decl.GetLine(); + } + + if (Type *type = v->GetType()) + if (const char *type_str = type->GetName().AsCString()) + type_name = type_str; + + current_vars.try_emplace( + v->GetID(), + VariableAnnotation{std::string(name), std::string(loc), true, + entry.expr->GetRegisterKind(), entry.file_range, + decl_file, decl_line, type_name}); } - // Diff Live_ → Current. + // Diff m_live_vars → current_vars. - // 1) Starts/changes: iterate Current and compare with Live_. - for (const auto &KV : Current) { - auto it = Live_.find(KV.first); - if (it == Live_.end()) { + // 1) Starts/changes: iterate current_vars and compare with m_live_vars. + for (const auto &KV : current_vars) { + auto it = m_live_vars.find(KV.first); + if (it == m_live_vars.end()) // Newly live. - events.emplace_back( - llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str()); - } else if (it->second.last_loc != KV.second.last_loc) { + AddVariableAnnotationToVector(annotations, KV.second, true); + else if (it->second.location_description != KV.second.location_description) // Location changed. - events.emplace_back( - llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str()); - } + AddVariableAnnotationToVector(annotations, KV.second, true); } - // 2) Ends: anything that was live but is not in Current becomes <undef>. - for (const auto &KV : Live_) { - if (!Current.count(KV.first)) - events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str()); - } + // 2) Ends: anything that was live but is not in current_vars becomes + // UndefLocation. + for (const auto &KV : m_live_vars) + if (!current_vars.count(KV.first)) + AddVariableAnnotationToVector(annotations, KV.second, false); // Commit new state. - Live_ = std::move(Current); - return events; + m_live_vars = std::move(current_vars); + return annotations; } void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, @@ -539,7 +587,8 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, LineEntry prologue_end_line = sc.line_entry; if (!ElideMixedSourceAndDisassemblyLine(exe_ctx, sc, prologue_end_line)) { - SupportFileSP func_decl_file_sp; + SupportFileNSP func_decl_file_sp = + std::make_shared<SupportFile>(); uint32_t func_decl_line; sc.function->GetStartLineSourceInfo(func_decl_file_sp, func_decl_line); @@ -676,7 +725,7 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, address_text_size); if ((options & eOptionVariableAnnotations) && target_sp) { - auto annotations = annot.annotate(*inst, *target_sp, module_sp); + auto annotations = annot.Annotate(*inst); if (!annotations.empty()) { const size_t annotation_column = 100; inst_line.FillLastLineToColumn(annotation_column, ' '); @@ -1340,6 +1389,11 @@ bool PseudoInstruction::DoesBranch() { return false; } +bool PseudoInstruction::IsBarrier() { + // This is NOT a valid question for a pseudo instruction. + return false; +} + bool PseudoInstruction::HasDelaySlot() { // This is NOT a valid question for a pseudo instruction. return false; diff --git a/lldb/source/Core/DynamicLoader.cpp b/lldb/source/Core/DynamicLoader.cpp index 7580b15..31d277b 100644 --- a/lldb/source/Core/DynamicLoader.cpp +++ b/lldb/source/Core/DynamicLoader.cpp @@ -165,7 +165,8 @@ ModuleSP DynamicLoader::FindModuleViaTarget(const FileSpec &file) { if (ModuleSP module_sp = target.GetImages().FindFirstModule(module_spec)) return module_sp; - if (ModuleSP module_sp = target.GetOrCreateModule(module_spec, false)) + if (ModuleSP module_sp = + target.GetOrCreateModule(module_spec, /*notify=*/false)) return module_sp; return nullptr; @@ -227,6 +228,7 @@ ModuleSP DynamicLoader::LoadBinaryWithUUIDAndAddress( } } ModuleSpec module_spec; + module_spec.SetTarget(target.shared_from_this()); module_spec.GetUUID() = uuid; FileSpec name_filespec(name); if (FileSystem::Instance().Exists(name_filespec)) @@ -238,8 +240,8 @@ ModuleSP DynamicLoader::LoadBinaryWithUUIDAndAddress( // Has lldb already seen a module with this UUID? // Or have external lookup enabled in DebugSymbols on macOS. if (!module_sp) - error = ModuleList::GetSharedModule(module_spec, module_sp, nullptr, - nullptr, nullptr); + error = + ModuleList::GetSharedModule(module_spec, module_sp, nullptr, nullptr); // Can lldb's symbol/executable location schemes // find an executable and symbol file. diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp index 491f5c6..faafb5e 100644 --- a/lldb/source/Core/FormatEntity.cpp +++ b/lldb/source/Core/FormatEntity.cpp @@ -27,6 +27,7 @@ #include "lldb/Symbol/Symbol.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Symbol/VariableList.h" +#include "lldb/Target/BorrowedStackFrame.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/ExecutionContextScope.h" #include "lldb/Target/Language.h" @@ -109,6 +110,7 @@ constexpr Definition g_frame_child_entries[] = { g_string_entry), Definition("is-artificial", EntryType::FrameIsArtificial), Definition("kind", EntryType::FrameKind), + Definition("borrowed-info", EntryType::FrameBorrowedInfo), }; constexpr Definition g_function_child_entries[] = { @@ -382,6 +384,7 @@ const char *FormatEntity::Entry::TypeToCString(Type t) { ENUM_TO_CSTR(FrameRegisterByName); ENUM_TO_CSTR(FrameIsArtificial); ENUM_TO_CSTR(FrameKind); + ENUM_TO_CSTR(FrameBorrowedInfo); ENUM_TO_CSTR(ScriptFrame); ENUM_TO_CSTR(FunctionID); ENUM_TO_CSTR(FunctionDidChange); @@ -1681,10 +1684,9 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, StackFrame *frame = exe_ctx->GetFramePtr(); if (frame) { const Address &pc_addr = frame->GetFrameCodeAddress(); - if (pc_addr.IsValid() || frame->IsSynthetic()) { + if (pc_addr.IsValid()) if (DumpAddressAndContent(s, sc, exe_ctx, pc_addr, false)) return true; - } } } return false; @@ -1761,6 +1763,22 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, return false; } + case Entry::Type::FrameBorrowedInfo: { + if (exe_ctx) + if (StackFrame *frame = exe_ctx->GetFramePtr()) { + if (BorrowedStackFrame *borrowed_frame = + llvm::dyn_cast<BorrowedStackFrame>(frame)) { + if (lldb::StackFrameSP borrowed_from_sp = + borrowed_frame->GetBorrowedFrame()) { + s.Printf(" [borrowed from frame #%u]", + borrowed_from_sp->GetFrameIndex()); + return true; + } + } + } + return false; + } + case Entry::Type::ScriptFrame: if (exe_ctx) { StackFrame *frame = exe_ctx->GetFramePtr(); @@ -1789,70 +1807,91 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, return initial_function; case Entry::Type::FunctionName: { - if (!sc) - return false; + if (sc) { + Language *language_plugin = nullptr; + bool language_plugin_handled = false; + StreamString ss; - Language *language_plugin = nullptr; - bool language_plugin_handled = false; - StreamString ss; + if (sc->function) + language_plugin = Language::FindPlugin(sc->function->GetLanguage()); + else if (sc->symbol) + language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); - if (sc->function) - language_plugin = Language::FindPlugin(sc->function->GetLanguage()); - else if (sc->symbol) - language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); + if (language_plugin) + language_plugin_handled = language_plugin->GetFunctionDisplayName( + *sc, exe_ctx, Language::FunctionNameRepresentation::eName, ss); - if (language_plugin) - language_plugin_handled = language_plugin->GetFunctionDisplayName( - *sc, exe_ctx, Language::FunctionNameRepresentation::eName, ss); + if (language_plugin_handled) { + s << ss.GetString(); + return true; + } - if (language_plugin_handled) { - s << ss.GetString(); - return true; + const char *name = sc->GetPossiblyInlinedFunctionName() + .GetName(Mangled::NamePreference::ePreferDemangled) + .AsCString(); + if (name) { + s.PutCString(name); + return true; + } } - const char *name = sc->GetPossiblyInlinedFunctionName() - .GetName(Mangled::NamePreference::ePreferDemangled) - .AsCString(); - if (!name) - return false; - - s.PutCString(name); - - return true; + // Fallback to frame methods if available. + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + const char *name = frame->GetFunctionName(); + if (name) { + s.PutCString(name); + return true; + } + } + } + return false; } case Entry::Type::FunctionNameNoArgs: { - if (!sc) - return false; - - Language *language_plugin = nullptr; - bool language_plugin_handled = false; - StreamString ss; - if (sc->function) - language_plugin = Language::FindPlugin(sc->function->GetLanguage()); - else if (sc->symbol) - language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); - - if (language_plugin) - language_plugin_handled = language_plugin->GetFunctionDisplayName( - *sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithNoArgs, - ss); + if (sc) { + Language *language_plugin = nullptr; + bool language_plugin_handled = false; + StreamString ss; + if (sc->function) + language_plugin = Language::FindPlugin(sc->function->GetLanguage()); + else if (sc->symbol) + language_plugin = Language::FindPlugin(sc->symbol->GetLanguage()); + + if (language_plugin) + language_plugin_handled = language_plugin->GetFunctionDisplayName( + *sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithNoArgs, + ss); + + if (language_plugin_handled) { + s << ss.GetString(); + return true; + } - if (language_plugin_handled) { - s << ss.GetString(); - return true; + const char *name = + sc->GetPossiblyInlinedFunctionName() + .GetName( + Mangled::NamePreference::ePreferDemangledWithoutArguments) + .AsCString(); + if (name) { + s.PutCString(name); + return true; + } } - const char *name = - sc->GetPossiblyInlinedFunctionName() - .GetName(Mangled::NamePreference::ePreferDemangledWithoutArguments) - .AsCString(); - if (!name) - return false; - - s.PutCString(name); - - return true; + // Fallback to frame methods if available. + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + const char *name = frame->GetFunctionName(); + if (name) { + s.PutCString(name); + return true; + } + } + } + return false; } case Entry::Type::FunctionPrefix: @@ -1879,13 +1918,26 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, } case Entry::Type::FunctionNameWithArgs: { - if (!sc) - return false; + if (sc) { + if (FormatFunctionNameForLanguage(s, exe_ctx, sc)) + return true; - if (FormatFunctionNameForLanguage(s, exe_ctx, sc)) - return true; + if (HandleFunctionNameWithArgs(s, exe_ctx, *sc)) + return true; + } - return HandleFunctionNameWithArgs(s, exe_ctx, *sc); + // Fallback to frame methods if available. + if (exe_ctx) { + StackFrame *frame = exe_ctx->GetFramePtr(); + if (frame) { + const char *name = frame->GetDisplayFunctionName(); + if (name) { + s.PutCString(name); + return true; + } + } + } + return false; } case Entry::Type::FunctionMangledName: { if (!sc) @@ -1927,12 +1979,11 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, case Entry::Type::FunctionPCOffset: if (exe_ctx) { StackFrame *frame = exe_ctx->GetFramePtr(); - if (frame) { + if (frame) if (DumpAddressOffsetFromFunction(s, sc, exe_ctx, frame->GetFrameCodeAddress(), false, false, false)) return true; - } } return false; @@ -1956,11 +2007,8 @@ bool FormatEntity::Format(const Entry &entry, Stream &s, case Entry::Type::LineEntryFile: if (sc && sc->line_entry.IsValid()) { - Module *module = sc->module_sp.get(); - if (module) { - if (DumpFile(s, sc->line_entry.GetFile(), (FileKind)entry.number)) - return true; - } + if (DumpFile(s, sc->line_entry.GetFile(), (FileKind)entry.number)) + return true; } return false; diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp index f27a95d..da2c188 100644 --- a/lldb/source/Core/Module.cpp +++ b/lldb/source/Core/Module.cpp @@ -52,9 +52,6 @@ #include "lldb/Host/windows/PosixApi.h" #endif -#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h" -#include "Plugins/Language/ObjC/ObjCLanguage.h" - #include "llvm/ADT/STLExtras.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/DJB.h" @@ -646,26 +643,13 @@ void Module::FindCompileUnits(const FileSpec &path, Module::LookupInfo::LookupInfo(ConstString name, FunctionNameType name_type_mask, - LanguageType language) - : m_name(name), m_lookup_name(name), m_language(language) { + LanguageType lang_type) + : m_name(name), m_lookup_name(name), m_language(lang_type) { std::optional<ConstString> basename; - - std::vector<Language *> languages; - { - std::vector<LanguageType> lang_types; - if (language != eLanguageTypeUnknown) - lang_types.push_back(language); - else - lang_types = {eLanguageTypeObjC, eLanguageTypeC_plus_plus}; - - for (LanguageType lang_type : lang_types) { - if (Language *lang = Language::FindPlugin(lang_type)) - languages.push_back(lang); - } - } + Language *lang = Language::FindPlugin(lang_type); if (name_type_mask & eFunctionNameTypeAuto) { - for (Language *lang : languages) { + if (lang) { auto info = lang->GetFunctionNameInfo(name); if (info.first != eFunctionNameTypeNone) { m_name_type_mask |= info.first; @@ -682,7 +666,7 @@ Module::LookupInfo::LookupInfo(ConstString name, } else { m_name_type_mask = name_type_mask; - for (Language *lang : languages) { + if (lang) { auto info = lang->GetFunctionNameInfo(name); if (info.first & m_name_type_mask) { // If the user asked for FunctionNameTypes that aren't possible, @@ -691,14 +675,12 @@ Module::LookupInfo::LookupInfo(ConstString name, // ObjC) m_name_type_mask &= info.first; basename = info.second; - break; - } - // Still try and get a basename in case someone specifies a name type mask - // of eFunctionNameTypeFull and a name like "A::func" - if (name_type_mask & eFunctionNameTypeFull && - info.first != eFunctionNameTypeNone && !basename && info.second) { + } else if (name_type_mask & eFunctionNameTypeFull && + info.first != eFunctionNameTypeNone && !basename && + info.second) { + // Still try and get a basename in case someone specifies a name type + // mask of eFunctionNameTypeFull and a name like "A::func" basename = info.second; - break; } } } @@ -714,6 +696,36 @@ Module::LookupInfo::LookupInfo(ConstString name, } } +std::vector<Module::LookupInfo> +Module::LookupInfo::MakeLookupInfos(ConstString name, + lldb::FunctionNameType name_type_mask, + lldb::LanguageType lang_type) { + std::vector<LanguageType> lang_types; + if (lang_type != eLanguageTypeUnknown) { + lang_types.push_back(lang_type); + } else { + // If the language type was not specified, look up in every language + // available. + Language::ForEach([&](Language *lang) { + auto lang_type = lang->GetLanguageType(); + if (!llvm::is_contained(lang_types, lang_type)) + lang_types.push_back(lang_type); + return IterationAction::Continue; + }); + + if (lang_types.empty()) + lang_types = {eLanguageTypeObjC, eLanguageTypeC_plus_plus}; + } + + std::vector<Module::LookupInfo> infos; + infos.reserve(lang_types.size()); + for (LanguageType lang_type : lang_types) { + Module::LookupInfo info(name, name_type_mask, lang_type); + infos.push_back(info); + } + return infos; +} + bool Module::LookupInfo::NameMatchesLookupInfo( ConstString function_name, LanguageType language_type) const { // We always keep unnamed symbols @@ -808,22 +820,21 @@ void Module::LookupInfo::Prune(SymbolContextList &sc_list, } } -void Module::FindFunctions(const Module::LookupInfo &lookup_info, +void Module::FindFunctions(llvm::ArrayRef<Module::LookupInfo> lookup_infos, const CompilerDeclContext &parent_decl_ctx, const ModuleFunctionSearchOptions &options, SymbolContextList &sc_list) { - // Find all the functions (not symbols, but debug information functions... - if (SymbolFile *symbols = GetSymbolFile()) { + for (auto &lookup_info : lookup_infos) { + SymbolFile *symbols = GetSymbolFile(); + if (!symbols) + continue; + symbols->FindFunctions(lookup_info, parent_decl_ctx, options.include_inlines, sc_list); - // Now check our symbol table for symbols that are code symbols if - // requested - if (options.include_symbols) { - if (Symtab *symtab = symbols->GetSymtab()) { + if (options.include_symbols) + if (Symtab *symtab = symbols->GetSymtab()) symtab->FindFunctionSymbols(lookup_info.GetLookupName(), lookup_info.GetNameTypeMask(), sc_list); - } - } } } @@ -832,13 +843,16 @@ void Module::FindFunctions(ConstString name, FunctionNameType name_type_mask, const ModuleFunctionSearchOptions &options, SymbolContextList &sc_list) { - const size_t old_size = sc_list.GetSize(); - LookupInfo lookup_info(name, name_type_mask, eLanguageTypeUnknown); - FindFunctions(lookup_info, parent_decl_ctx, options, sc_list); - if (name_type_mask & eFunctionNameTypeAuto) { - const size_t new_size = sc_list.GetSize(); - if (old_size < new_size) - lookup_info.Prune(sc_list, old_size); + std::vector<LookupInfo> lookup_infos = + LookupInfo::MakeLookupInfos(name, name_type_mask, eLanguageTypeUnknown); + for (auto &lookup_info : lookup_infos) { + const size_t old_size = sc_list.GetSize(); + FindFunctions(lookup_info, parent_decl_ctx, options, sc_list); + if (name_type_mask & eFunctionNameTypeAuto) { + const size_t new_size = sc_list.GetSize(); + if (old_size < new_size) + lookup_info.Prune(sc_list, old_size); + } } } diff --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp index c40612c..be6ff72 100644 --- a/lldb/source/Core/ModuleList.cpp +++ b/lldb/source/Core/ModuleList.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "lldb/Core/ModuleList.h" +#include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" @@ -19,6 +20,8 @@ #include "lldb/Symbol/SymbolContext.h" #include "lldb/Symbol/TypeList.h" #include "lldb/Symbol/VariableList.h" +#include "lldb/Target/Platform.h" +#include "lldb/Target/Target.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/ConstString.h" #include "lldb/Utility/FileSpecList.h" @@ -26,6 +29,7 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/UUID.h" #include "lldb/lldb-defines.h" +#include "llvm/Support/ThreadPool.h" #if defined(_WIN32) #include "lldb/Host/windows/PosixApi.h" @@ -449,21 +453,22 @@ void ModuleList::FindFunctions(ConstString name, FunctionNameType name_type_mask, const ModuleFunctionSearchOptions &options, SymbolContextList &sc_list) const { - const size_t old_size = sc_list.GetSize(); - if (name_type_mask & eFunctionNameTypeAuto) { - Module::LookupInfo lookup_info(name, name_type_mask, eLanguageTypeUnknown); - + std::vector<Module::LookupInfo> lookup_infos = + Module::LookupInfo::MakeLookupInfos(name, name_type_mask, + eLanguageTypeUnknown); std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); - for (const ModuleSP &module_sp : m_modules) { - module_sp->FindFunctions(lookup_info, CompilerDeclContext(), options, - sc_list); - } - - const size_t new_size = sc_list.GetSize(); + for (const auto &lookup_info : lookup_infos) { + const size_t old_size = sc_list.GetSize(); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindFunctions(lookup_info, CompilerDeclContext(), options, + sc_list); + } - if (old_size < new_size) - lookup_info.Prune(sc_list, old_size); + const size_t new_size = sc_list.GetSize(); + if (old_size < new_size) + lookup_info.Prune(sc_list, old_size); + } } else { std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); for (const ModuleSP &module_sp : m_modules) { @@ -476,21 +481,24 @@ void ModuleList::FindFunctions(ConstString name, void ModuleList::FindFunctionSymbols(ConstString name, lldb::FunctionNameType name_type_mask, SymbolContextList &sc_list) { - const size_t old_size = sc_list.GetSize(); - if (name_type_mask & eFunctionNameTypeAuto) { - Module::LookupInfo lookup_info(name, name_type_mask, eLanguageTypeUnknown); + std::vector<Module::LookupInfo> lookup_infos = + Module::LookupInfo::MakeLookupInfos(name, name_type_mask, + eLanguageTypeUnknown); std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); - for (const ModuleSP &module_sp : m_modules) { - module_sp->FindFunctionSymbols(lookup_info.GetLookupName(), - lookup_info.GetNameTypeMask(), sc_list); - } + for (const auto &lookup_info : lookup_infos) { + const size_t old_size = sc_list.GetSize(); + for (const ModuleSP &module_sp : m_modules) { + module_sp->FindFunctionSymbols(lookup_info.GetLookupName(), + lookup_info.GetNameTypeMask(), sc_list); + } - const size_t new_size = sc_list.GetSize(); + const size_t new_size = sc_list.GetSize(); - if (old_size < new_size) - lookup_info.Prune(sc_list, old_size); + if (old_size < new_size) + lookup_info.Prune(sc_list, old_size); + } } else { std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); for (const ModuleSP &module_sp : m_modules) { @@ -1038,9 +1046,9 @@ size_t ModuleList::RemoveOrphanSharedModules(bool mandatory) { Status ModuleList::GetSharedModule(const ModuleSpec &module_spec, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, - bool *did_create_ptr, bool always_create) { + bool *did_create_ptr, bool always_create, + bool invoke_locate_callback) { SharedModuleList &shared_module_list = GetSharedModuleList(); std::lock_guard<std::recursive_mutex> guard(shared_module_list.GetMutex()); char path[PATH_MAX]; @@ -1095,6 +1103,22 @@ ModuleList::GetSharedModule(const ModuleSpec &module_spec, ModuleSP &module_sp, if (module_sp) return error; + // Try target's platform locate module callback before second attempt. + if (invoke_locate_callback) { + TargetSP target_sp = module_spec.GetTargetSP(); + if (target_sp && target_sp->IsValid()) { + if (PlatformSP platform_sp = target_sp->GetPlatform()) { + FileSpec symbol_file_spec; + platform_sp->CallLocateModuleCallbackIfSet( + module_spec, module_sp, symbol_file_spec, did_create_ptr); + if (module_sp) { + // The callback found a module. + return error; + } + } + } + } + module_sp = std::make_shared<Module>(module_spec); // Make sure there are a module and an object file since we can specify a // valid file path with an architecture that might not be in that file. By @@ -1122,10 +1146,16 @@ ModuleList::GetSharedModule(const ModuleSpec &module_spec, ModuleSP &module_sp, module_sp.reset(); } - if (module_search_paths_ptr) { - const auto num_directories = module_search_paths_ptr->GetSize(); + // Get module search paths from the target if available. + lldb::TargetSP target_sp = module_spec.GetTargetSP(); + FileSpecList module_search_paths; + if (target_sp) + module_search_paths = target_sp->GetExecutableSearchPaths(); + + if (!module_search_paths.IsEmpty()) { + const auto num_directories = module_search_paths.GetSize(); for (size_t idx = 0; idx < num_directories; ++idx) { - auto search_path_spec = module_search_paths_ptr->GetFileSpecAtIndex(idx); + auto search_path_spec = module_search_paths.GetFileSpecAtIndex(idx); FileSystem::Instance().Resolve(search_path_spec); namespace fs = llvm::sys::fs; if (!FileSystem::Instance().IsDirectory(search_path_spec)) @@ -1357,3 +1387,21 @@ void ModuleList::Swap(ModuleList &other) { m_modules_mutex, other.m_modules_mutex); m_modules.swap(other.m_modules); } + +void ModuleList::PreloadSymbols(bool parallelize) const { + std::lock_guard<std::recursive_mutex> guard(m_modules_mutex); + + if (!parallelize) { + for (const ModuleSP &module_sp : m_modules) + module_sp->PreloadSymbols(); + return; + } + + llvm::ThreadPoolTaskGroup task_group(Debugger::GetThreadPool()); + for (const ModuleSP &module_sp : m_modules) + task_group.async([module_sp] { + if (module_sp) + module_sp->PreloadSymbols(); + }); + task_group.wait(); +} diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index 5887367..4e3563c 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -1300,6 +1300,61 @@ PluginManager::GetScriptInterpreterForLanguage(lldb::ScriptLanguage script_lang, return none_instance(debugger); } +#pragma mark SyntheticFrameProvider + +typedef PluginInstance<SyntheticFrameProviderCreateInstance> + SyntheticFrameProviderInstance; +typedef PluginInstance<ScriptedFrameProviderCreateInstance> + ScriptedFrameProviderInstance; +typedef PluginInstances<SyntheticFrameProviderInstance> + SyntheticFrameProviderInstances; +typedef PluginInstances<ScriptedFrameProviderInstance> + ScriptedFrameProviderInstances; + +static SyntheticFrameProviderInstances &GetSyntheticFrameProviderInstances() { + static SyntheticFrameProviderInstances g_instances; + return g_instances; +} + +static ScriptedFrameProviderInstances &GetScriptedFrameProviderInstances() { + static ScriptedFrameProviderInstances g_instances; + return g_instances; +} + +bool PluginManager::RegisterPlugin( + llvm::StringRef name, llvm::StringRef description, + SyntheticFrameProviderCreateInstance create_native_callback, + ScriptedFrameProviderCreateInstance create_scripted_callback) { + if (create_native_callback) + return GetSyntheticFrameProviderInstances().RegisterPlugin( + name, description, create_native_callback); + else if (create_scripted_callback) + return GetScriptedFrameProviderInstances().RegisterPlugin( + name, description, create_scripted_callback); + return false; +} + +bool PluginManager::UnregisterPlugin( + SyntheticFrameProviderCreateInstance create_callback) { + return GetSyntheticFrameProviderInstances().UnregisterPlugin(create_callback); +} + +bool PluginManager::UnregisterPlugin( + ScriptedFrameProviderCreateInstance create_callback) { + return GetScriptedFrameProviderInstances().UnregisterPlugin(create_callback); +} + +SyntheticFrameProviderCreateInstance +PluginManager::GetSyntheticFrameProviderCreateCallbackForPluginName( + llvm::StringRef name) { + return GetSyntheticFrameProviderInstances().GetCallbackForName(name); +} + +ScriptedFrameProviderCreateInstance +PluginManager::GetScriptedFrameProviderCreateCallbackAtIndex(uint32_t idx) { + return GetScriptedFrameProviderInstances().GetCallbackAtIndex(idx); +} + #pragma mark StructuredDataPlugin struct StructuredDataPluginInstance diff --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp index 02d9d86..f16035b 100644 --- a/lldb/source/Core/Section.cpp +++ b/lldb/source/Core/Section.cpp @@ -471,8 +471,14 @@ bool Section::ContainsOnlyDebugInfo() const { return false; } +bool Section::IsGOTSection() const { + return GetObjectFile()->IsGOTSection(*this); +} + #pragma mark SectionList +SectionList::SectionList(const SectionList &rhs) : m_sections(rhs.m_sections) {} + SectionList &SectionList::operator=(const SectionList &rhs) { if (this != &rhs) m_sections = rhs.m_sections; @@ -683,6 +689,33 @@ uint64_t SectionList::GetDebugInfoSize() const { return debug_info_size; } +SectionList SectionList::Merge(SectionList &lhs, SectionList &rhs, + MergeCallback filter) { + SectionList output_list; + + // Iterate through all the sections in lhs and see if we have matches in + // the rhs list. + for (const auto &lhs_section : lhs) { + auto rhs_section = rhs.FindSectionByName(lhs_section->GetName()); + if (rhs_section) + output_list.AddSection(filter(lhs_section, rhs_section)); + else + output_list.AddSection(lhs_section); + } + + // Now that we've visited all possible duplicates, we can iterate over + // the rhs and take any values not in lhs. + for (const auto &rhs_section : rhs) { + auto lhs_section = lhs.FindSectionByName(rhs_section->GetName()); + // Because we already visited everything overlapping between rhs + // and lhs, any section not in lhs is unique and can be output. + if (!lhs_section) + output_list.AddSection(rhs_section); + } + + return output_list; +} + namespace llvm { namespace json { diff --git a/lldb/source/Core/SourceManager.cpp b/lldb/source/Core/SourceManager.cpp index 097173f..c60288c 100644 --- a/lldb/source/Core/SourceManager.cpp +++ b/lldb/source/Core/SourceManager.cpp @@ -30,6 +30,7 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/RegularExpression.h" #include "lldb/Utility/Stream.h" +#include "lldb/Utility/SupportFile.h" #include "lldb/lldb-enumerations.h" #include "llvm/ADT/Twine.h" @@ -69,22 +70,20 @@ static std::string toString(const Checksum &checksum) { // SourceManager constructor SourceManager::SourceManager(const TargetSP &target_sp) - : m_last_support_file_sp(std::make_shared<SupportFile>()), m_last_line(0), + : m_last_support_file_nsp(std::make_shared<SupportFile>()), m_last_line(0), m_last_count(0), m_default_set(false), m_target_wp(target_sp), m_debugger_wp(target_sp->GetDebugger().shared_from_this()) {} SourceManager::SourceManager(const DebuggerSP &debugger_sp) - : m_last_support_file_sp(std::make_shared<SupportFile>()), m_last_line(0), + : m_last_support_file_nsp(std::make_shared<SupportFile>()), m_last_line(0), m_last_count(0), m_default_set(false), m_target_wp(), m_debugger_wp(debugger_sp) {} // Destructor SourceManager::~SourceManager() = default; -SourceManager::FileSP SourceManager::GetFile(SupportFileSP support_file_sp) { - assert(support_file_sp && "SupportFileSP must be valid"); - - FileSpec file_spec = support_file_sp->GetSpecOnly(); +SourceManager::FileSP SourceManager::GetFile(SupportFileNSP support_file_nsp) { + FileSpec file_spec = support_file_nsp->GetSpecOnly(); if (!file_spec) return {}; @@ -97,8 +96,8 @@ SourceManager::FileSP SourceManager::GetFile(SupportFileSP support_file_sp) { LLDB_LOG(log, "Source file caching disabled: creating new source file: {0}", file_spec); if (target_sp) - return std::make_shared<File>(support_file_sp, target_sp); - return std::make_shared<File>(support_file_sp, debugger_sp); + return std::make_shared<File>(support_file_nsp, target_sp); + return std::make_shared<File>(support_file_nsp, debugger_sp); } ProcessSP process_sp = target_sp ? target_sp->GetProcessSP() : ProcessSP(); @@ -159,9 +158,9 @@ SourceManager::FileSP SourceManager::GetFile(SupportFileSP support_file_sp) { // (Re)create the file. if (target_sp) - file_sp = std::make_shared<File>(support_file_sp, target_sp); + file_sp = std::make_shared<File>(support_file_nsp, target_sp); else - file_sp = std::make_shared<File>(support_file_sp, debugger_sp); + file_sp = std::make_shared<File>(support_file_nsp, debugger_sp); // Add the file to the debugger and process cache. If the file was // invalidated, this will overwrite it. @@ -325,12 +324,12 @@ size_t SourceManager::DisplaySourceLinesWithLineNumbersUsingLastFile( } size_t SourceManager::DisplaySourceLinesWithLineNumbers( - lldb::SupportFileSP support_file_sp, uint32_t line, uint32_t column, + SupportFileNSP support_file_nsp, uint32_t line, uint32_t column, uint32_t context_before, uint32_t context_after, const char *current_line_cstr, Stream *s, const SymbolContextList *bp_locs) { - assert(support_file_sp && "SupportFile must be valid"); - FileSP file_sp(GetFile(support_file_sp)); + assert(support_file_nsp && "SupportFile must be valid"); + FileSP file_sp(GetFile(support_file_nsp)); uint32_t start_line; uint32_t count = context_before + context_after + 1; @@ -343,7 +342,7 @@ size_t SourceManager::DisplaySourceLinesWithLineNumbers( if (last_file_sp.get() != file_sp.get()) { if (line == 0) m_last_line = 0; - m_last_support_file_sp = support_file_sp; + m_last_support_file_nsp = support_file_nsp; } return DisplaySourceLinesWithLineNumbersUsingLastFile( @@ -389,15 +388,15 @@ size_t SourceManager::DisplayMoreWithLineNumbers( return 0; } -bool SourceManager::SetDefaultFileAndLine(lldb::SupportFileSP support_file_sp, +bool SourceManager::SetDefaultFileAndLine(SupportFileNSP support_file_nsp, uint32_t line) { - assert(support_file_sp && "SupportFile must be valid"); + assert(support_file_nsp && "SupportFile must be valid"); m_default_set = true; - if (FileSP file_sp = GetFile(support_file_sp)) { + if (FileSP file_sp = GetFile(support_file_nsp)) { m_last_line = line; - m_last_support_file_sp = support_file_sp; + m_last_support_file_nsp = support_file_nsp; return true; } @@ -407,7 +406,7 @@ bool SourceManager::SetDefaultFileAndLine(lldb::SupportFileSP support_file_sp, std::optional<SourceManager::SupportFileAndLine> SourceManager::GetDefaultFileAndLine() { if (FileSP last_file_sp = GetLastFile()) - return SupportFileAndLine(m_last_support_file_sp, m_last_line); + return SupportFileAndLine(m_last_support_file_nsp, m_last_line); if (!m_default_set) { TargetSP target_sp(m_target_wp.lock()); @@ -446,36 +445,36 @@ SourceManager::GetDefaultFileAndLine() { return std::nullopt; } -void SourceManager::FindLinesMatchingRegex(SupportFileSP support_file_sp, +void SourceManager::FindLinesMatchingRegex(SupportFileNSP support_file_nsp, RegularExpression ®ex, uint32_t start_line, uint32_t end_line, std::vector<uint32_t> &match_lines) { match_lines.clear(); - FileSP file_sp = GetFile(support_file_sp); + FileSP file_sp = GetFile(support_file_nsp); if (!file_sp) return; return file_sp->FindLinesMatchingRegex(regex, start_line, end_line, match_lines); } -SourceManager::File::File(SupportFileSP support_file_sp, +SourceManager::File::File(SupportFileNSP support_file_nsp, lldb::DebuggerSP debugger_sp) - : m_support_file_sp(std::make_shared<SupportFile>()), m_checksum(), + : m_support_file_nsp(std::make_shared<SupportFile>()), m_checksum(), m_mod_time(), m_debugger_wp(debugger_sp), m_target_wp(TargetSP()) { - CommonInitializer(support_file_sp, {}); + CommonInitializer(support_file_nsp, {}); } -SourceManager::File::File(SupportFileSP support_file_sp, TargetSP target_sp) - : m_support_file_sp(std::make_shared<SupportFile>()), m_checksum(), +SourceManager::File::File(SupportFileNSP support_file_nsp, TargetSP target_sp) + : m_support_file_nsp(std::make_shared<SupportFile>()), m_checksum(), m_mod_time(), m_debugger_wp(target_sp ? target_sp->GetDebugger().shared_from_this() : DebuggerSP()), m_target_wp(target_sp) { - CommonInitializer(support_file_sp, target_sp); + CommonInitializer(support_file_nsp, target_sp); } -void SourceManager::File::CommonInitializer(SupportFileSP support_file_sp, +void SourceManager::File::CommonInitializer(SupportFileNSP support_file_nsp, TargetSP target_sp) { // It might take a while to read a source file, for example because it's // coming from a virtual file system that's fetching the data on demand. When @@ -484,23 +483,23 @@ void SourceManager::File::CommonInitializer(SupportFileSP support_file_sp, static constexpr auto g_progress_delay = std::chrono::milliseconds(500); std::future<void> future = std::async(std::launch::async, [=]() { - CommonInitializerImpl(support_file_sp, target_sp); + CommonInitializerImpl(support_file_nsp, target_sp); }); std::optional<Progress> progress; if (future.wait_for(g_progress_delay) == std::future_status::timeout) { Debugger *debugger = target_sp ? &target_sp->GetDebugger() : nullptr; progress.emplace("Loading source file", - support_file_sp->GetSpecOnly().GetFilename().GetString(), + support_file_nsp->GetSpecOnly().GetFilename().GetString(), 1, debugger); } future.wait(); } -void SourceManager::File::CommonInitializerImpl(SupportFileSP support_file_sp, +void SourceManager::File::CommonInitializerImpl(SupportFileNSP support_file_nsp, TargetSP target_sp) { // Set the file and update the modification time. - SetSupportFile(support_file_sp); + SetSupportFile(support_file_nsp); // Always update the source map modification ID if we have a target. if (target_sp) @@ -511,7 +510,7 @@ void SourceManager::File::CommonInitializerImpl(SupportFileSP support_file_sp, if (target_sp) { // If this is just a file name, try finding it in the target. { - FileSpec file_spec = support_file_sp->GetSpecOnly(); + FileSpec file_spec = support_file_nsp->GetSpecOnly(); if (!file_spec.GetDirectory() && file_spec.GetFilename()) { bool check_inlines = false; SymbolContextList sc_list; @@ -548,7 +547,7 @@ void SourceManager::File::CommonInitializerImpl(SupportFileSP support_file_sp, // Try remapping the file if it doesn't exist. { - FileSpec file_spec = support_file_sp->GetSpecOnly(); + FileSpec file_spec = support_file_nsp->GetSpecOnly(); if (!FileSystem::Instance().Exists(file_spec)) { // Check target specific source remappings (i.e., the // target.source-map setting), then fall back to the module @@ -561,7 +560,7 @@ void SourceManager::File::CommonInitializerImpl(SupportFileSP support_file_sp, } if (remapped) SetSupportFile(std::make_shared<SupportFile>( - *remapped, support_file_sp->GetChecksum())); + *remapped, support_file_nsp->GetChecksum())); } } } @@ -570,16 +569,16 @@ void SourceManager::File::CommonInitializerImpl(SupportFileSP support_file_sp, // If the file exists, read in the data. if (m_mod_time != llvm::sys::TimePoint<>()) { m_data_sp = FileSystem::Instance().CreateDataBuffer( - m_support_file_sp->GetSpecOnly()); + m_support_file_nsp->GetSpecOnly()); m_checksum = llvm::MD5::hash(m_data_sp->GetData()); } } -void SourceManager::File::SetSupportFile(lldb::SupportFileSP support_file_sp) { - FileSpec file_spec = support_file_sp->GetSpecOnly(); +void SourceManager::File::SetSupportFile(SupportFileNSP support_file_nsp) { + FileSpec file_spec = support_file_nsp->GetSpecOnly(); resolve_tilde(file_spec); - m_support_file_sp = - std::make_shared<SupportFile>(file_spec, support_file_sp->GetChecksum()); + m_support_file_nsp = + std::make_shared<SupportFile>(file_spec, support_file_nsp->GetChecksum()); m_mod_time = FileSystem::Instance().GetModificationTime(file_spec); } @@ -654,7 +653,7 @@ bool SourceManager::File::ModificationTimeIsStale() const { // source cache and only update when we determine a file has been updated. // For now we check each time we want to display info for the file. auto curr_mod_time = FileSystem::Instance().GetModificationTime( - m_support_file_sp->GetSpecOnly()); + m_support_file_nsp->GetSpecOnly()); return curr_mod_time != llvm::sys::TimePoint<>() && m_mod_time != curr_mod_time; } diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp index bfbd190..922aada 100644 --- a/lldb/source/Core/Statusline.cpp +++ b/lldb/source/Core/Statusline.cpp @@ -91,7 +91,7 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) { if (!stream_sp) return; - const unsigned reduced_scroll_window = m_terminal_height - 1; + const unsigned reduced_scroll_rows = m_terminal_height - 1; LockedStreamFile locked_stream = stream_sp->Lock(); switch (mode) { @@ -101,13 +101,14 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) { locked_stream.Printf(ANSI_UP_ROWS, 1); // Reduce the scroll window. locked_stream << ANSI_SAVE_CURSOR; - locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_window); + locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_rows); locked_stream << ANSI_RESTORE_CURSOR; break; case DisableStatusline: // Reset the scroll window. locked_stream << ANSI_SAVE_CURSOR; - locked_stream.Printf(ANSI_SET_SCROLL_ROWS, 0); + locked_stream.Printf(ANSI_SET_SCROLL_ROWS, + static_cast<unsigned>(m_terminal_height)); locked_stream << ANSI_RESTORE_CURSOR; // Clear the screen below to hide the old statusline. locked_stream << ANSI_CLEAR_BELOW; @@ -116,7 +117,7 @@ void Statusline::UpdateScrollWindow(ScrollWindowMode mode) { // Clear the screen and update the scroll window. // FIXME: Find a better solution (#146919). locked_stream << ANSI_CLEAR_SCREEN; - locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_window); + locked_stream.Printf(ANSI_SET_SCROLL_ROWS, reduced_scroll_rows); break; } diff --git a/lldb/source/Expression/CMakeLists.txt b/lldb/source/Expression/CMakeLists.txt index 08dc536..515289c 100644 --- a/lldb/source/Expression/CMakeLists.txt +++ b/lldb/source/Expression/CMakeLists.txt @@ -24,6 +24,7 @@ add_lldb_library(lldbExpression NO_PLUGIN_DEPENDENCIES LINK_COMPONENTS Core + DebugInfoDWARF ExecutionEngine Support LINK_LIBS diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp index 4f9d6eb..364b2ec 100644 --- a/lldb/source/Expression/DWARFExpression.cpp +++ b/lldb/source/Expression/DWARFExpression.cpp @@ -861,32 +861,130 @@ ResolveLoadAddress(ExecutionContext *exe_ctx, lldb::ModuleSP &module_sp, return load_addr; } -static llvm::Error Evaluate_DW_OP_deref(DWARFExpression::Stack &stack, - ExecutionContext *exe_ctx, - lldb::ModuleSP module_sp, - Process *process) { +/// @brief Helper function to load sized data from a uint8_t buffer. +/// +/// @param addr_bytes The buffer containing raw data. +/// @param size_addr_bytes How large is the underlying raw data. +/// @param byte_order What is the byte order of the underlying data. +/// @param size How much of the underlying data we want to use. +/// @return The underlying data converted into a Scalar. +static Scalar DerefSizeExtractDataHelper(uint8_t *addr_bytes, + size_t size_addr_bytes, + ByteOrder byte_order, size_t size) { + DataExtractor addr_data(addr_bytes, size_addr_bytes, byte_order, size); + + lldb::offset_t addr_data_offset = 0; + if (size <= 8) + return addr_data.GetMaxU64(&addr_data_offset, size); + return addr_data.GetAddress(&addr_data_offset); +} + +static llvm::Error Evaluate_DW_OP_deref_size( + DWARFExpression::Stack &stack, ExecutionContext *exe_ctx, + lldb::ModuleSP module_sp, Process *process, Target *target, uint8_t size, + size_t size_addr_bytes, + LocationDescriptionKind &dwarf4_location_description_kind) { if (stack.empty()) - return llvm::createStringError("expression stack empty for DW_OP_deref"); + return llvm::createStringError( + "expression stack empty for DW_OP_deref_size"); - const Value::ValueType value_type = stack.back().GetValueType(); + if (size > 8) + return llvm::createStringError( + "Invalid address size for DW_OP_deref_size: %d\n", size); + + // Deref a register or implicit location and truncate the value to `size` + // bytes. See the corresponding comment in DW_OP_deref for more details on + // why we deref these locations this way. + if (dwarf4_location_description_kind == Register || + dwarf4_location_description_kind == Implicit) { + // Reset context to default values. + dwarf4_location_description_kind = Memory; + stack.back().ClearContext(); + + // Truncate the value on top of the stack to *size* bytes then + // extend to the size of an address (e.g. generic type). + Scalar scalar = stack.back().GetScalar(); + scalar.TruncOrExtendTo(size * 8, /*sign=*/false); + scalar.TruncOrExtendTo(size_addr_bytes * 8, + /*sign=*/false); + stack.back().GetScalar() = scalar; + return llvm::Error::success(); + } + + Value::ValueType value_type = stack.back().GetValueType(); switch (value_type) { case Value::ValueType::HostAddress: { void *src = (void *)stack.back().GetScalar().ULongLong(); intptr_t ptr; ::memcpy(&ptr, src, sizeof(void *)); + // I can't decide whether the size operand should apply to the bytes in + // their lldb-host endianness or the target endianness.. I doubt this'll + // ever come up but I'll opt for assuming big endian regardless. + switch (size) { + case 1: + ptr = ptr & 0xff; + break; + case 2: + ptr = ptr & 0xffff; + break; + case 3: + ptr = ptr & 0xffffff; + break; + case 4: + ptr = ptr & 0xffffffff; + break; + // The casts are added to work around the case where intptr_t is a 32-bit + // quantity. Presumably we won't hit the 5..7 cases if (void*) is 32-bits in + // this program. + case 5: + ptr = (intptr_t)ptr & 0xffffffffffULL; + break; + case 6: + ptr = (intptr_t)ptr & 0xffffffffffffULL; + break; + case 7: + ptr = (intptr_t)ptr & 0xffffffffffffffULL; + break; + default: + break; + } stack.back().GetScalar() = ptr; stack.back().ClearContext(); } break; case Value::ValueType::FileAddress: { auto file_addr = stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); Address so_addr; - auto maybe_load_addr = ResolveLoadAddress(exe_ctx, module_sp, "DW_OP_deref", - file_addr, so_addr); + auto maybe_load_addr = ResolveLoadAddress( + exe_ctx, module_sp, "DW_OP_deref_size", file_addr, so_addr, + /*check_sectionoffset=*/true); + if (!maybe_load_addr) return maybe_load_addr.takeError(); - stack.back().GetScalar() = *maybe_load_addr; + + addr_t load_addr = *maybe_load_addr; + + if (load_addr == LLDB_INVALID_ADDRESS && so_addr.IsSectionOffset()) { + uint8_t addr_bytes[8]; + Status error; + + if (!target || target->ReadMemory(so_addr, &addr_bytes, size, error, + /*force_live_memory=*/false) != size) + return llvm::createStringError( + "failed to dereference pointer for DW_OP_deref_size: " + "%s\n", + error.AsCString()); + + ObjectFile *objfile = module_sp->GetObjectFile(); + + stack.back().GetScalar() = DerefSizeExtractDataHelper( + addr_bytes, size, objfile->GetByteOrder(), size); + stack.back().ClearContext(); + break; + } + stack.back().GetScalar() = load_addr; // Fall through to load address promotion code below. } + [[fallthrough]]; case Value::ValueType::Scalar: // Promote Scalar to LoadAddress and fall through. @@ -894,51 +992,34 @@ static llvm::Error Evaluate_DW_OP_deref(DWARFExpression::Stack &stack, [[fallthrough]]; case Value::ValueType::LoadAddress: { if (!exe_ctx) - return llvm::createStringError("NULL execution context for DW_OP_deref"); + return llvm::createStringError( + "no execution context for DW_OP_deref_size"); if (!process) - return llvm::createStringError("NULL process for DW_OP_deref"); + return llvm::createStringError("no process for DW_OP_deref_size"); + lldb::addr_t pointer_addr = stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); + uint8_t addr_bytes[sizeof(lldb::addr_t)]; Status error; - lldb::addr_t pointer_value = - process->ReadPointerFromMemory(pointer_addr, error); - if (pointer_value == LLDB_INVALID_ADDRESS) - return llvm::joinErrors( - llvm::createStringError( - "Failed to dereference pointer from 0x%" PRIx64 - " for DW_OP_deref", - pointer_addr), - error.takeError()); - stack.back().GetScalar() = pointer_value; + + if (process->ReadMemory(pointer_addr, &addr_bytes, size, error) != size) + return llvm::createStringError( + "failed to dereference pointer from 0x%" PRIx64 + " for DW_OP_deref_size: %s\n", + pointer_addr, error.AsCString()); + + stack.back().GetScalar() = DerefSizeExtractDataHelper( + addr_bytes, sizeof(addr_bytes), process->GetByteOrder(), size); stack.back().ClearContext(); } break; + case Value::ValueType::Invalid: - return llvm::createStringError("invalid value type for DW_OP_deref"); + return llvm::createStringError("invalid value for DW_OP_deref_size"); } return llvm::Error::success(); } -/// Helper function to move common code used to load sized data from a uint8_t -/// buffer. -/// -/// \param addr_bytes uint8_t buffer containg raw data -/// \param size_addr_bytes how large is the underlying raw data -/// \param byte_order what is the byter order of the underlyig data -/// \param size How much of the underlying data we want to use -/// \return The underlying data converted into a Scalar -static Scalar DerefSizeExtractDataHelper(uint8_t *addr_bytes, - size_t size_addr_bytes, - ByteOrder byte_order, size_t size) { - DataExtractor addr_data(addr_bytes, size_addr_bytes, byte_order, size); - - lldb::offset_t addr_data_offset = 0; - if (size <= 8) - return addr_data.GetMaxU64(&addr_data_offset, size); - else - return addr_data.GetAddress(&addr_data_offset); -} - llvm::Expected<Value> DWARFExpression::Evaluate( ExecutionContext *exe_ctx, RegisterContext *reg_ctx, lldb::ModuleSP module_sp, const DataExtractor &opcodes, @@ -1079,8 +1160,10 @@ llvm::Expected<Value> DWARFExpression::Evaluate( // retrieved from the dereferenced address is the size of an address on the // target machine. case DW_OP_deref: { - if (llvm::Error err = - Evaluate_DW_OP_deref(stack, exe_ctx, module_sp, process)) + size_t size = opcodes.GetAddressByteSize(); + if (llvm::Error err = Evaluate_DW_OP_deref_size( + stack, exe_ctx, module_sp, process, target, size, size, + dwarf4_location_description_kind)) return err; } break; @@ -1097,131 +1180,11 @@ llvm::Expected<Value> DWARFExpression::Evaluate( // the size of an address on the target machine before being pushed on the // expression stack. case DW_OP_deref_size: { - if (stack.empty()) { - return llvm::createStringError( - "expression stack empty for DW_OP_deref_size"); - } - uint8_t size = opcodes.GetU8(&offset); - if (size > 8) { - return llvm::createStringError( - "Invalid address size for DW_OP_deref_size: %d\n", size); - } - Value::ValueType value_type = stack.back().GetValueType(); - switch (value_type) { - case Value::ValueType::HostAddress: { - void *src = (void *)stack.back().GetScalar().ULongLong(); - intptr_t ptr; - ::memcpy(&ptr, src, sizeof(void *)); - // I can't decide whether the size operand should apply to the bytes in - // their - // lldb-host endianness or the target endianness.. I doubt this'll ever - // come up but I'll opt for assuming big endian regardless. - switch (size) { - case 1: - ptr = ptr & 0xff; - break; - case 2: - ptr = ptr & 0xffff; - break; - case 3: - ptr = ptr & 0xffffff; - break; - case 4: - ptr = ptr & 0xffffffff; - break; - // the casts are added to work around the case where intptr_t is a 32 - // bit quantity; - // presumably we won't hit the 5..7 cases if (void*) is 32-bits in this - // program. - case 5: - ptr = (intptr_t)ptr & 0xffffffffffULL; - break; - case 6: - ptr = (intptr_t)ptr & 0xffffffffffffULL; - break; - case 7: - ptr = (intptr_t)ptr & 0xffffffffffffffULL; - break; - default: - break; - } - stack.back().GetScalar() = ptr; - stack.back().ClearContext(); - } break; - case Value::ValueType::FileAddress: { - auto file_addr = - stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); - Address so_addr; - auto maybe_load_addr = ResolveLoadAddress( - exe_ctx, module_sp, "DW_OP_deref_size", file_addr, so_addr, - /*check_sectionoffset=*/true); - - if (!maybe_load_addr) - return maybe_load_addr.takeError(); - - addr_t load_addr = *maybe_load_addr; - - if (load_addr == LLDB_INVALID_ADDRESS && so_addr.IsSectionOffset()) { - uint8_t addr_bytes[8]; - Status error; - - if (target && - target->ReadMemory(so_addr, &addr_bytes, size, error, - /*force_live_memory=*/false) == size) { - ObjectFile *objfile = module_sp->GetObjectFile(); - - stack.back().GetScalar() = DerefSizeExtractDataHelper( - addr_bytes, size, objfile->GetByteOrder(), size); - stack.back().ClearContext(); - break; - } else { - return llvm::createStringError( - "Failed to dereference pointer for DW_OP_deref_size: " - "%s\n", - error.AsCString()); - } - } - stack.back().GetScalar() = load_addr; - // Fall through to load address promotion code below. - } - - [[fallthrough]]; - case Value::ValueType::Scalar: - case Value::ValueType::LoadAddress: - if (exe_ctx) { - if (process) { - lldb::addr_t pointer_addr = - stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS); - uint8_t addr_bytes[sizeof(lldb::addr_t)]; - Status error; - if (process->ReadMemory(pointer_addr, &addr_bytes, size, error) == - size) { - - stack.back().GetScalar() = - DerefSizeExtractDataHelper(addr_bytes, sizeof(addr_bytes), - process->GetByteOrder(), size); - stack.back().ClearContext(); - } else { - return llvm::createStringError( - "Failed to dereference pointer from 0x%" PRIx64 - " for DW_OP_deref: %s\n", - pointer_addr, error.AsCString()); - } - } else { - - return llvm::createStringError("NULL process for DW_OP_deref_size"); - } - } else { - return llvm::createStringError( - "NULL execution context for DW_OP_deref_size"); - } - break; - - case Value::ValueType::Invalid: - - return llvm::createStringError("invalid value for DW_OP_deref_size"); - } - + size_t size = opcodes.GetU8(&offset); + if (llvm::Error err = Evaluate_DW_OP_deref_size( + stack, exe_ctx, module_sp, process, target, size, + opcodes.GetAddressByteSize(), dwarf4_location_description_kind)) + return err; } break; // OPCODE: DW_OP_xderef_size diff --git a/lldb/source/Expression/ObjectFileJIT.cpp b/lldb/source/Expression/ObjectFileJIT.cpp index e4a6135..46ceb75 100644 --- a/lldb/source/Expression/ObjectFileJIT.cpp +++ b/lldb/source/Expression/ObjectFileJIT.cpp @@ -73,8 +73,8 @@ ObjectFileJIT::ObjectFileJIT(const lldb::ModuleSP &module_sp, : ObjectFile(module_sp, nullptr, 0, 0, DataBufferSP(), 0), m_delegate_wp() { if (delegate_sp) { m_delegate_wp = delegate_sp; - m_data.SetByteOrder(delegate_sp->GetByteOrder()); - m_data.SetAddressByteSize(delegate_sp->GetAddressByteSize()); + m_data_nsp->SetByteOrder(delegate_sp->GetByteOrder()); + m_data_nsp->SetAddressByteSize(delegate_sp->GetAddressByteSize()); } } @@ -85,12 +85,14 @@ bool ObjectFileJIT::ParseHeader() { return false; } -ByteOrder ObjectFileJIT::GetByteOrder() const { return m_data.GetByteOrder(); } +ByteOrder ObjectFileJIT::GetByteOrder() const { + return m_data_nsp->GetByteOrder(); +} bool ObjectFileJIT::IsExecutable() const { return false; } uint32_t ObjectFileJIT::GetAddressByteSize() const { - return m_data.GetAddressByteSize(); + return m_data_nsp->GetAddressByteSize(); } void ObjectFileJIT::ParseSymtab(Symtab &symtab) { diff --git a/lldb/source/Expression/REPL.cpp b/lldb/source/Expression/REPL.cpp index 92017d2..c6cf6d8 100644 --- a/lldb/source/Expression/REPL.cpp +++ b/lldb/source/Expression/REPL.cpp @@ -615,6 +615,6 @@ Status REPL::RunLoop() { // Restore the default file and line if (default_file_line) m_target.GetSourceManager().SetDefaultFileAndLine( - default_file_line->support_file_sp, default_file_line->line); + default_file_line->support_file_nsp, default_file_line->line); return error; } diff --git a/lldb/source/Expression/UserExpression.cpp b/lldb/source/Expression/UserExpression.cpp index af4b477..5563eba 100644 --- a/lldb/source/Expression/UserExpression.cpp +++ b/lldb/source/Expression/UserExpression.cpp @@ -246,7 +246,7 @@ UserExpression::Evaluate(ExecutionContext &exe_ctx, // language in the target's properties if specified, else default to the // langage for the frame. if (!language) { - if (target->GetLanguage() != lldb::eLanguageTypeUnknown) + if (target->GetLanguage()) language = target->GetLanguage(); else if (StackFrame *frame = exe_ctx.GetFramePtr()) language = frame->GetLanguage(); diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt index c9e8afe..3184d3a 100644 --- a/lldb/source/Host/CMakeLists.txt +++ b/lldb/source/Host/CMakeLists.txt @@ -17,6 +17,7 @@ macro(add_host_subdirectory group) endmacro() add_host_subdirectory(common + common/DiagnosticsRendering.cpp common/FileAction.cpp common/FileCache.cpp common/File.cpp diff --git a/lldb/source/Utility/DiagnosticsRendering.cpp b/lldb/source/Host/common/DiagnosticsRendering.cpp index 8c21e661..f2cd396 100644 --- a/lldb/source/Utility/DiagnosticsRendering.cpp +++ b/lldb/source/Host/common/DiagnosticsRendering.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include <cstdint> using namespace lldb_private; diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp index 1b1922e..e2995b3 100644 --- a/lldb/source/Host/common/Editline.cpp +++ b/lldb/source/Host/common/Editline.cpp @@ -1626,6 +1626,9 @@ bool Editline::GetLine(std::string &line, bool &interrupted) { m_editor_status = EditorStatus::Editing; m_revert_cursor_index = -1; + lldbassert(m_output_stream_sp); + fprintf(m_locked_output->GetFile().GetStream(), "\r" ANSI_CLEAR_RIGHT); + int count; auto input = el_wgets(m_editline, &count); diff --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp index 65b75bd..4fad93f 100644 --- a/lldb/source/Host/common/File.cpp +++ b/lldb/source/Host/common/File.cpp @@ -249,8 +249,8 @@ uint32_t File::GetPermissions(Status &error) const { NativeFile::NativeFile() = default; -NativeFile::NativeFile(FILE *fh, bool transfer_ownership) - : m_stream(fh), m_own_stream(transfer_ownership) { +NativeFile::NativeFile(FILE *fh, OpenOptions options, bool transfer_ownership) + : m_stream(fh), m_options(options), m_own_stream(transfer_ownership) { #ifdef _WIN32 // In order to properly display non ASCII characters in Windows, we need to // use Windows APIs to print to the console. This is only required if the @@ -258,6 +258,26 @@ NativeFile::NativeFile(FILE *fh, bool transfer_ownership) int fd = _fileno(fh); is_windows_console = ::GetFileType((HANDLE)::_get_osfhandle(fd)) == FILE_TYPE_CHAR; +#else +#ifndef NDEBUG + int fd = fileno(fh); + if (fd != -1) { + int required_mode = ConvertOpenOptionsForPOSIXOpen(options) & O_ACCMODE; + int mode = fcntl(fd, F_GETFL); + if (mode != -1) { + mode &= O_ACCMODE; + // Check that the file is open with a valid subset of the requested file + // access mode, e.g. if we expected the file to be writable then ensure it + // was opened with O_WRONLY or O_RDWR. + assert( + (required_mode == O_RDWR && mode == O_RDWR) || + (required_mode == O_RDONLY && (mode == O_RDWR || mode == O_RDONLY) || + (required_mode == O_WRONLY && + (mode == O_RDWR || mode == O_WRONLY))) && + "invalid file access mode"); + } + } +#endif #endif } @@ -274,7 +294,8 @@ NativeFile::NativeFile(int fd, OpenOptions options, bool transfer_ownership) } bool NativeFile::IsValid() const { - std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, m_stream_mutex); + std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, + m_stream_mutex); return DescriptorIsValidUnlocked() || StreamIsValidUnlocked(); } @@ -343,7 +364,8 @@ FILE *NativeFile::GetStream() { } Status NativeFile::Close() { - std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, m_stream_mutex); + std::scoped_lock<std::mutex, std::mutex> lock(m_descriptor_mutex, + m_stream_mutex); Status error; @@ -548,6 +570,10 @@ Status NativeFile::Sync() { Status NativeFile::Read(void *buf, size_t &num_bytes) { Status error; + // Ensure the file is open for reading. + if ((m_options & File::OpenOptionsModeMask) == eOpenOptionWriteOnly) + return Status(std::make_error_code(std::errc::bad_file_descriptor)); + #if defined(MAX_READ_SIZE) if (num_bytes > MAX_READ_SIZE) { uint8_t *p = (uint8_t *)buf; @@ -612,6 +638,10 @@ Status NativeFile::Read(void *buf, size_t &num_bytes) { Status NativeFile::Write(const void *buf, size_t &num_bytes) { Status error; + // Ensure the file is open for writing. + if ((m_options & File::OpenOptionsModeMask) == File::eOpenOptionReadOnly) + return Status(std::make_error_code(std::errc::bad_file_descriptor)); + #if defined(MAX_WRITE_SIZE) if (num_bytes > MAX_WRITE_SIZE) { const uint8_t *p = (const uint8_t *)buf; @@ -776,8 +806,8 @@ Status NativeFile::Write(const void *buf, size_t &num_bytes, off_t &offset) { int fd = GetDescriptor(); if (fd != kInvalidDescriptor) { #ifndef _WIN32 - ssize_t bytes_written = - llvm::sys::RetryAfterSignal(-1, ::pwrite, m_descriptor, buf, num_bytes, offset); + ssize_t bytes_written = llvm::sys::RetryAfterSignal( + -1, ::pwrite, m_descriptor, buf, num_bytes, offset); if (bytes_written < 0) { num_bytes = 0; error = Status::FromErrno(); diff --git a/lldb/source/Host/common/FileAction.cpp b/lldb/source/Host/common/FileAction.cpp index e1c3e14..ec271f7 100644 --- a/lldb/source/Host/common/FileAction.cpp +++ b/lldb/source/Host/common/FileAction.cpp @@ -25,10 +25,6 @@ void FileAction::Clear() { m_file_spec.Clear(); } -llvm::StringRef FileAction::GetPath() const { - return m_file_spec.GetPathAsConstString().AsCString(); -} - const FileSpec &FileAction::GetFileSpec() const { return m_file_spec; } bool FileAction::Open(int fd, const FileSpec &file_spec, bool read, diff --git a/lldb/source/Host/common/StreamFile.cpp b/lldb/source/Host/common/StreamFile.cpp index 099980a..131412d 100644 --- a/lldb/source/Host/common/StreamFile.cpp +++ b/lldb/source/Host/common/StreamFile.cpp @@ -27,7 +27,8 @@ StreamFile::StreamFile(int fd, bool transfer_ownership) : Stream() { } StreamFile::StreamFile(FILE *fh, bool transfer_ownership) : Stream() { - m_file_sp = std::make_shared<NativeFile>(fh, transfer_ownership); + m_file_sp = std::make_shared<NativeFile>(fh, File::eOpenOptionWriteOnly, + transfer_ownership); } StreamFile::StreamFile(const char *path, File::OpenOptions options, diff --git a/lldb/source/Host/macosx/objcxx/Host.mm b/lldb/source/Host/macosx/objcxx/Host.mm index 96a282c..16bca0f1 100644 --- a/lldb/source/Host/macosx/objcxx/Host.mm +++ b/lldb/source/Host/macosx/objcxx/Host.mm @@ -1013,20 +1013,29 @@ static Status LaunchProcessXPC(const char *exe_path, xpc_dictionary_set_int64(message, LauncherXPCServicePosixspawnFlagsKey, GetPosixspawnFlags(launch_info)); const FileAction *file_action = launch_info.GetFileActionForFD(STDIN_FILENO); - if (file_action && !file_action->GetPath().empty()) { + std::string file_action_path; + if (file_action) + file_action_path = file_action->GetFileSpec().GetPath(); + + if (!file_action_path.empty()) xpc_dictionary_set_string(message, LauncherXPCServiceStdInPathKeyKey, - file_action->GetPath().str().c_str()); - } + file_action_path.c_str()); + file_action = launch_info.GetFileActionForFD(STDOUT_FILENO); - if (file_action && !file_action->GetPath().empty()) { + if (file_action) + file_action_path = file_action->GetFileSpec().GetPath(); + + if (!file_action_path.empty()) xpc_dictionary_set_string(message, LauncherXPCServiceStdOutPathKeyKey, - file_action->GetPath().str().c_str()); - } + file_action_path.c_str()); + file_action = launch_info.GetFileActionForFD(STDERR_FILENO); - if (file_action && !file_action->GetPath().empty()) { + if (file_action) + file_action_path = file_action->GetFileSpec().GetPath(); + + if (!file_action_path.empty()) xpc_dictionary_set_string(message, LauncherXPCServiceStdErrPathKeyKey, - file_action->GetPath().str().c_str()); - } + file_action_path.c_str()); xpc_object_t reply = xpc_connection_send_message_with_reply_sync(conn, message); @@ -1135,16 +1144,16 @@ static bool AddPosixSpawnFileAction(void *_file_actions, const FileAction *info, if (oflag & O_CREAT) mode = 0640; - error = Status(::posix_spawn_file_actions_addopen( - file_actions, info->GetFD(), - info->GetPath().str().c_str(), oflag, mode), - eErrorTypePOSIX); + const std::string file_path(info->GetFileSpec().GetPath()); + error = Status( + ::posix_spawn_file_actions_addopen(file_actions, info->GetFD(), + file_path.c_str(), oflag, mode), + eErrorTypePOSIX); if (error.Fail()) LLDB_LOG(log, "error: {0}, posix_spawn_file_actions_addopen (action={1}, " "fd={2}, path='{3}', oflag={4}, mode={5})", - error, file_actions, info->GetFD(), info->GetPath(), oflag, - mode); + error, file_actions, info->GetFD(), file_path, oflag, mode); } break; } diff --git a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp index 15a8097..a5f5cc5 100644 --- a/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp +++ b/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp @@ -229,8 +229,8 @@ struct ForkLaunchInfo { // End of code running in the child process. ForkFileAction::ForkFileAction(const FileAction &act) - : action(act.GetAction()), fd(act.GetFD()), path(act.GetPath().str()), - arg(act.GetActionArgument()) {} + : action(act.GetAction()), fd(act.GetFD()), + path(act.GetFileSpec().GetPath()), arg(act.GetActionArgument()) {} static std::vector<ForkFileAction> MakeForkActions(const ProcessLaunchInfo &info) { diff --git a/lldb/source/Host/windows/ProcessLauncherWindows.cpp b/lldb/source/Host/windows/ProcessLauncherWindows.cpp index f5adada..e983c52 100644 --- a/lldb/source/Host/windows/ProcessLauncherWindows.cpp +++ b/lldb/source/Host/windows/ProcessLauncherWindows.cpp @@ -14,6 +14,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Program.h" +#include "llvm/Support/WindowsError.h" #include <string> #include <vector> @@ -21,42 +22,63 @@ using namespace lldb; using namespace lldb_private; -static void CreateEnvironmentBuffer(const Environment &env, - std::vector<char> &buffer) { - // The buffer is a list of null-terminated UTF-16 strings, followed by an - // extra L'\0' (two bytes of 0). An empty environment must have one - // empty string, followed by an extra L'\0'. +/// Create a UTF-16 environment block to use with CreateProcessW. +/// +/// The buffer is a sequence of null-terminated UTF-16 strings, followed by an +/// extra L'\0' (two bytes of 0). An empty environment must have one +/// empty string, followed by an extra L'\0'. +/// +/// The keys are sorted to comply with the CreateProcess API calling convention. +/// +/// Ensure that the resulting buffer is used in conjunction with +/// CreateProcessW and be sure that dwCreationFlags includes +/// CREATE_UNICODE_ENVIRONMENT. +/// +/// \param env The Environment object to convert. +/// \returns The sorted sequence of environment variables and their values, +/// separated by null terminators. The vector is guaranteed to never be empty. +static std::vector<wchar_t> CreateEnvironmentBufferW(const Environment &env) { + std::vector<std::wstring> env_entries; for (const auto &KV : env) { - std::wstring warg; - if (llvm::ConvertUTF8toWide(Environment::compose(KV), warg)) { - buffer.insert( - buffer.end(), reinterpret_cast<const char *>(warg.c_str()), - reinterpret_cast<const char *>(warg.c_str() + warg.size() + 1)); - } + std::wstring wentry; + if (llvm::ConvertUTF8toWide(Environment::compose(KV), wentry)) + env_entries.push_back(std::move(wentry)); } - // One null wchar_t (to end the block) is two null bytes - buffer.push_back(0); - buffer.push_back(0); - // Insert extra two bytes, just in case the environment was empty. - buffer.push_back(0); - buffer.push_back(0); + std::sort(env_entries.begin(), env_entries.end(), + [](const std::wstring &a, const std::wstring &b) { + return _wcsicmp(a.c_str(), b.c_str()) < 0; + }); + + std::vector<wchar_t> buffer; + for (const auto &env_entry : env_entries) { + buffer.insert(buffer.end(), env_entry.begin(), env_entry.end()); + buffer.push_back(L'\0'); + } + + if (buffer.empty()) + buffer.push_back(L'\0'); // If there are no environment variables, we have + // to ensure there are 4 zero bytes in the buffer. + buffer.push_back(L'\0'); + + return buffer; } -static bool GetFlattenedWindowsCommandString(Args args, std::wstring &command) { +/// Flattens an Args object into a Windows command-line wide string. +/// +/// Returns an empty string if args is empty. +/// +/// \param args The Args object to flatten. +/// \returns A wide string containing the flattened command line. +static llvm::ErrorOr<std::wstring> +GetFlattenedWindowsCommandStringW(Args args) { if (args.empty()) - return false; + return L""; std::vector<llvm::StringRef> args_ref; for (auto &entry : args.entries()) args_ref.push_back(entry.ref()); - llvm::ErrorOr<std::wstring> result = - llvm::sys::flattenWindowsCommandLine(args_ref); - if (result.getError()) - return false; - - command = *result; - return true; + return llvm::sys::flattenWindowsCommandLine(args_ref); } HostProcess @@ -64,11 +86,9 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, Status &error) { error.Clear(); - std::string executable; - std::vector<char> environment; - STARTUPINFOEX startupinfoex = {}; - STARTUPINFO &startupinfo = startupinfoex.StartupInfo; - PROCESS_INFORMATION pi = {}; + STARTUPINFOEXW startupinfoex = {}; + startupinfoex.StartupInfo.cb = sizeof(startupinfoex); + startupinfoex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; HANDLE stdin_handle = GetStdioHandle(launch_info, STDIN_FILENO); HANDLE stdout_handle = GetStdioHandle(launch_info, STDOUT_FILENO); @@ -82,23 +102,6 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, ::CloseHandle(stderr_handle); }); - startupinfo.cb = sizeof(startupinfoex); - startupinfo.dwFlags |= STARTF_USESTDHANDLES; - startupinfo.hStdError = - stderr_handle ? stderr_handle : ::GetStdHandle(STD_ERROR_HANDLE); - startupinfo.hStdInput = - stdin_handle ? stdin_handle : ::GetStdHandle(STD_INPUT_HANDLE); - startupinfo.hStdOutput = - stdout_handle ? stdout_handle : ::GetStdHandle(STD_OUTPUT_HANDLE); - - std::vector<HANDLE> inherited_handles; - if (startupinfo.hStdError) - inherited_handles.push_back(startupinfo.hStdError); - if (startupinfo.hStdInput) - inherited_handles.push_back(startupinfo.hStdInput); - if (startupinfo.hStdOutput) - inherited_handles.push_back(startupinfo.hStdOutput); - SIZE_T attributelist_size = 0; InitializeProcThreadAttributeList(/*lpAttributeList=*/nullptr, /*dwAttributeCount=*/1, /*dwFlags=*/0, @@ -116,29 +119,21 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, } auto delete_attributelist = llvm::make_scope_exit( [&] { DeleteProcThreadAttributeList(startupinfoex.lpAttributeList); }); - for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) { - const FileAction *act = launch_info.GetFileActionAtIndex(i); - if (act->GetAction() == FileAction::eFileActionDuplicate && - act->GetFD() == act->GetActionArgument()) - inherited_handles.push_back(reinterpret_cast<HANDLE>(act->GetFD())); - } - if (!inherited_handles.empty()) { - if (!UpdateProcThreadAttribute( - startupinfoex.lpAttributeList, /*dwFlags=*/0, - PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(), - inherited_handles.size() * sizeof(HANDLE), - /*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr)) { - error = Status(::GetLastError(), eErrorTypeWin32); - return HostProcess(); - } + + auto inherited_handles_or_err = GetInheritedHandles( + launch_info, startupinfoex, stdout_handle, stderr_handle, stdin_handle); + if (!inherited_handles_or_err) { + error = Status(inherited_handles_or_err.getError()); + return HostProcess(); } + std::vector<HANDLE> inherited_handles = *inherited_handles_or_err; const char *hide_console_var = getenv("LLDB_LAUNCH_INFERIORS_WITHOUT_CONSOLE"); if (hide_console_var && llvm::StringRef(hide_console_var).equals_insensitive("true")) { - startupinfo.dwFlags |= STARTF_USESHOWWINDOW; - startupinfo.wShowWindow = SW_HIDE; + startupinfoex.StartupInfo.dwFlags |= STARTF_USESHOWWINDOW; + startupinfoex.StartupInfo.wShowWindow = SW_HIDE; } DWORD flags = CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT | @@ -149,28 +144,34 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, if (launch_info.GetFlags().Test(eLaunchFlagDisableSTDIO)) flags &= ~CREATE_NEW_CONSOLE; - LPVOID env_block = nullptr; - ::CreateEnvironmentBuffer(launch_info.GetEnvironment(), environment); - env_block = environment.data(); + std::vector<wchar_t> environment = + CreateEnvironmentBufferW(launch_info.GetEnvironment()); - executable = launch_info.GetExecutableFile().GetPath(); - std::wstring wcommandLine; - GetFlattenedWindowsCommandString(launch_info.GetArguments(), wcommandLine); - - std::wstring wexecutable, wworkingDirectory; - llvm::ConvertUTF8toWide(executable, wexecutable); - llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(), - wworkingDirectory); + auto wcommandLineOrErr = + GetFlattenedWindowsCommandStringW(launch_info.GetArguments()); + if (!wcommandLineOrErr) { + error = Status(wcommandLineOrErr.getError()); + return HostProcess(); + } + std::wstring wcommandLine = *wcommandLineOrErr; // If the command line is empty, it's best to pass a null pointer to tell // CreateProcessW to use the executable name as the command line. If the // command line is not empty, its contents may be modified by CreateProcessW. WCHAR *pwcommandLine = wcommandLine.empty() ? nullptr : &wcommandLine[0]; + std::wstring wexecutable, wworkingDirectory; + llvm::ConvertUTF8toWide(launch_info.GetExecutableFile().GetPath(), + wexecutable); + llvm::ConvertUTF8toWide(launch_info.GetWorkingDirectory().GetPath(), + wworkingDirectory); + + PROCESS_INFORMATION pi = {}; + BOOL result = ::CreateProcessW( wexecutable.c_str(), pwcommandLine, NULL, NULL, - /*bInheritHandles=*/!inherited_handles.empty(), flags, env_block, + /*bInheritHandles=*/!inherited_handles.empty(), flags, environment.data(), wworkingDirectory.size() == 0 ? NULL : wworkingDirectory.c_str(), - reinterpret_cast<STARTUPINFO *>(&startupinfoex), &pi); + reinterpret_cast<STARTUPINFOW *>(&startupinfoex), &pi); if (!result) { // Call GetLastError before we make any other system calls. @@ -191,6 +192,45 @@ ProcessLauncherWindows::LaunchProcess(const ProcessLaunchInfo &launch_info, return HostProcess(pi.hProcess); } +llvm::ErrorOr<std::vector<HANDLE>> ProcessLauncherWindows::GetInheritedHandles( + const ProcessLaunchInfo &launch_info, STARTUPINFOEXW &startupinfoex, + HANDLE stdout_handle, HANDLE stderr_handle, HANDLE stdin_handle) { + std::vector<HANDLE> inherited_handles; + + startupinfoex.StartupInfo.hStdError = + stderr_handle ? stderr_handle : GetStdHandle(STD_ERROR_HANDLE); + startupinfoex.StartupInfo.hStdInput = + stdin_handle ? stdin_handle : GetStdHandle(STD_INPUT_HANDLE); + startupinfoex.StartupInfo.hStdOutput = + stdout_handle ? stdout_handle : GetStdHandle(STD_OUTPUT_HANDLE); + + if (startupinfoex.StartupInfo.hStdError) + inherited_handles.push_back(startupinfoex.StartupInfo.hStdError); + if (startupinfoex.StartupInfo.hStdInput) + inherited_handles.push_back(startupinfoex.StartupInfo.hStdInput); + if (startupinfoex.StartupInfo.hStdOutput) + inherited_handles.push_back(startupinfoex.StartupInfo.hStdOutput); + + for (size_t i = 0; i < launch_info.GetNumFileActions(); ++i) { + const FileAction *act = launch_info.GetFileActionAtIndex(i); + if (act->GetAction() == FileAction::eFileActionDuplicate && + act->GetFD() == act->GetActionArgument()) + inherited_handles.push_back(reinterpret_cast<HANDLE>(act->GetFD())); + } + + if (inherited_handles.empty()) + return inherited_handles; + + if (!UpdateProcThreadAttribute( + startupinfoex.lpAttributeList, /*dwFlags=*/0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, inherited_handles.data(), + inherited_handles.size() * sizeof(HANDLE), + /*lpPreviousValue=*/nullptr, /*lpReturnSize=*/nullptr)) + return llvm::mapWindowsError(::GetLastError()); + + return inherited_handles; +} + HANDLE ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, int fd) { @@ -201,7 +241,6 @@ ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, secattr.nLength = sizeof(SECURITY_ATTRIBUTES); secattr.bInheritHandle = TRUE; - llvm::StringRef path = action->GetPath(); DWORD access = 0; DWORD share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; DWORD create = 0; @@ -218,6 +257,7 @@ ProcessLauncherWindows::GetStdioHandle(const ProcessLaunchInfo &launch_info, flags = FILE_FLAG_WRITE_THROUGH; } + const std::string path = action->GetFileSpec().GetPath(); std::wstring wpath; llvm::ConvertUTF8toWide(path, wpath); HANDLE result = ::CreateFileW(wpath.c_str(), access, share, &secattr, create, diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index ffcc9ce..cb6acfc 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -3708,7 +3708,7 @@ CommandInterpreter::ResolveCommandImpl(std::string &command_line, for (uint32_t i = 0; i < num_matches; ++i) { error_msg.Printf("\t%s\n", matches.GetStringAtIndex(i)); } - result.AppendRawError(error_msg.GetString()); + result.AppendError(error_msg.GetString()); } } else { // We didn't have only one match, otherwise we wouldn't get here. diff --git a/lldb/source/Interpreter/CommandReturnObject.cpp b/lldb/source/Interpreter/CommandReturnObject.cpp index 0a2948e..85b058e 100644 --- a/lldb/source/Interpreter/CommandReturnObject.cpp +++ b/lldb/source/Interpreter/CommandReturnObject.cpp @@ -8,7 +8,7 @@ #include "lldb/Interpreter/CommandReturnObject.h" -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/StreamString.h" @@ -173,15 +173,6 @@ StructuredData::ObjectSP CommandReturnObject::GetErrorData() { return Serialize(m_diagnostics); } -// Similar to AppendError, but do not prepend 'Status: ' to message, and don't -// append "\n" to the end of it. - -void CommandReturnObject::AppendRawError(llvm::StringRef in_string) { - SetStatus(eReturnStatusFailed); - assert(!in_string.empty() && "Expected a non-empty error message"); - GetErrorStream() << in_string; -} - void CommandReturnObject::SetStatus(ReturnStatus status) { m_status = status; } ReturnStatus CommandReturnObject::GetStatus() const { return m_status; } diff --git a/lldb/source/Interpreter/Options.cpp b/lldb/source/Interpreter/Options.cpp index cae61781..eab452c 100644 --- a/lldb/source/Interpreter/Options.cpp +++ b/lldb/source/Interpreter/Options.cpp @@ -14,13 +14,13 @@ #include <set> #include "lldb/Host/OptionParser.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Interpreter/CommandCompletions.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandObject.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Target/Target.h" #include "lldb/Utility/AnsiTerminal.h" -#include "lldb/Utility/DiagnosticsRendering.h" #include "lldb/Utility/StreamString.h" #include "llvm/ADT/STLExtras.h" @@ -1410,7 +1410,9 @@ llvm::Error lldb_private::CreateOptionParsingError( llvm::StringRef long_option, llvm::StringRef additional_context) { std::string buffer; llvm::raw_string_ostream stream(buffer); - stream << "Invalid value ('" << option_arg << "') for -" << short_option; + stream << "invalid value ('" << option_arg << "')"; + if (short_option) + stream << " for -" << short_option; if (!long_option.empty()) stream << " (" << long_option << ")"; if (!additional_context.empty()) diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp index ca768db..7bad10f 100644 --- a/lldb/source/Interpreter/ScriptInterpreter.cpp +++ b/lldb/source/Interpreter/ScriptInterpreter.cpp @@ -106,6 +106,13 @@ ScriptInterpreter::GetStatusFromSBError(const lldb::SBError &error) const { return Status(); } +lldb::ThreadSP ScriptInterpreter::GetOpaqueTypeFromSBThread( + const lldb::SBThread &thread) const { + if (thread.m_opaque_sp) + return thread.m_opaque_sp->GetThreadSP(); + return nullptr; +} + lldb::StackFrameSP ScriptInterpreter::GetOpaqueTypeFromSBFrame(const lldb::SBFrame &frame) const { if (frame.m_opaque_sp) @@ -136,7 +143,7 @@ SymbolContext ScriptInterpreter::GetOpaqueTypeFromSBSymbolContext( return {}; } -std::optional<MemoryRegionInfo> +std::optional<lldb_private::MemoryRegionInfo> ScriptInterpreter::GetOpaqueTypeFromSBMemoryRegionInfo( const lldb::SBMemoryRegionInfo &mem_region) const { if (!mem_region.m_opaque_up) @@ -150,6 +157,11 @@ ScriptInterpreter::GetOpaqueTypeFromSBExecutionContext( return exe_ctx.m_exe_ctx_sp; } +lldb::StackFrameListSP ScriptInterpreter::GetOpaqueTypeFromSBFrameList( + const lldb::SBFrameList &frame_list) const { + return frame_list.m_opaque_sp; +} + lldb::ScriptLanguage ScriptInterpreter::StringToLanguage(const llvm::StringRef &language) { if (language.equals_insensitive(LanguageToString(eScriptLanguageNone))) diff --git a/lldb/source/Plugins/CMakeLists.txt b/lldb/source/Plugins/CMakeLists.txt index 08f444e..b6878b2 100644 --- a/lldb/source/Plugins/CMakeLists.txt +++ b/lldb/source/Plugins/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(SymbolFile) add_subdirectory(SystemRuntime) add_subdirectory(SymbolLocator) add_subdirectory(SymbolVendor) +add_subdirectory(SyntheticFrameProvider) add_subdirectory(Trace) add_subdirectory(TraceExporter) add_subdirectory(TypeSystem) diff --git a/lldb/source/Plugins/Disassembler/LLVMC/DisassemblerLLVMC.cpp b/lldb/source/Plugins/Disassembler/LLVMC/DisassemblerLLVMC.cpp index 66d0a50..e8bb706 100644 --- a/lldb/source/Plugins/Disassembler/LLVMC/DisassemblerLLVMC.cpp +++ b/lldb/source/Plugins/Disassembler/LLVMC/DisassemblerLLVMC.cpp @@ -70,6 +70,7 @@ public: bool HasDelaySlot(llvm::MCInst &mc_inst) const; bool IsCall(llvm::MCInst &mc_inst) const; bool IsLoad(llvm::MCInst &mc_inst) const; + bool IsBarrier(llvm::MCInst &mc_inst) const; bool IsAuthenticated(llvm::MCInst &mc_inst) const; private: @@ -436,6 +437,11 @@ public: return m_is_load; } + bool IsBarrier() override { + VisitInstruction(); + return m_is_barrier; + } + bool IsAuthenticated() override { VisitInstruction(); return m_is_authenticated; @@ -1195,6 +1201,7 @@ protected: bool m_is_call = false; bool m_is_load = false; bool m_is_authenticated = false; + bool m_is_barrier = false; void VisitInstruction() { if (m_has_visited_instruction) @@ -1227,6 +1234,7 @@ protected: m_is_call = mc_disasm_ptr->IsCall(inst); m_is_load = mc_disasm_ptr->IsLoad(inst); m_is_authenticated = mc_disasm_ptr->IsAuthenticated(inst); + m_is_barrier = mc_disasm_ptr->IsBarrier(inst); } private: @@ -1432,6 +1440,11 @@ bool DisassemblerLLVMC::MCDisasmInstance::IsLoad(llvm::MCInst &mc_inst) const { return m_instr_info_up->get(mc_inst.getOpcode()).mayLoad(); } +bool DisassemblerLLVMC::MCDisasmInstance::IsBarrier( + llvm::MCInst &mc_inst) const { + return m_instr_info_up->get(mc_inst.getOpcode()).isBarrier(); +} + bool DisassemblerLLVMC::MCDisasmInstance::IsAuthenticated( llvm::MCInst &mc_inst) const { const auto &InstrDesc = m_instr_info_up->get(mc_inst.getOpcode()); diff --git a/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp b/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp index 1d210ea..2d0a4f67 100644 --- a/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp +++ b/lldb/source/Plugins/DynamicLoader/Darwin-Kernel/DynamicLoaderDarwinKernel.cpp @@ -789,6 +789,7 @@ bool DynamicLoaderDarwinKernel::KextImageInfo::LoadImageUsingMemoryModule( // Search for the kext on the local filesystem via the UUID if (!m_module_sp && m_uuid.IsValid()) { ModuleSpec module_spec; + module_spec.SetTarget(target.shared_from_this()); module_spec.GetUUID() = m_uuid; if (!m_uuid.IsValid()) module_spec.GetArchitecture() = target.GetArchitecture(); @@ -801,9 +802,8 @@ bool DynamicLoaderDarwinKernel::KextImageInfo::LoadImageUsingMemoryModule( // system. PlatformSP platform_sp(target.GetPlatform()); if (platform_sp) { - FileSpecList search_paths = target.GetExecutableSearchPaths(); - platform_sp->GetSharedModule(module_spec, process, m_module_sp, - &search_paths, nullptr, nullptr); + platform_sp->GetSharedModule(module_spec, process, m_module_sp, nullptr, + nullptr); } // Ask the Target to find this file on the local system, if possible. diff --git a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp index 326b691..3605e7b2 100644 --- a/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp +++ b/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp @@ -469,7 +469,8 @@ void DynamicLoaderPOSIXDYLD::RefreshModules() { } ModuleSP module_sp = LoadModuleAtAddress( - so_entry.file_spec, so_entry.link_addr, so_entry.base_addr, true); + so_entry.file_spec, so_entry.link_addr, so_entry.base_addr, + /*base_addr_is_offset=*/true); if (!module_sp.get()) return; @@ -726,9 +727,8 @@ void DynamicLoaderPOSIXDYLD::LoadAllCurrentModules() { task_group.async(load_module_fn, *I); task_group.wait(); } else { - for (I = m_rendezvous.begin(), E = m_rendezvous.end(); I != E; ++I) { + for (I = m_rendezvous.begin(), E = m_rendezvous.end(); I != E; ++I) load_module_fn(*I); - } } m_process->GetTarget().ModulesDidLoad(module_list); @@ -901,10 +901,9 @@ void DynamicLoaderPOSIXDYLD::ResolveExecutableModule( if (module_sp && module_sp->MatchesModuleSpec(module_spec)) return; + module_spec.SetTarget(target.shared_from_this()); const auto executable_search_paths(Target::GetDefaultExecutableSearchPaths()); - auto error = platform_sp->ResolveExecutable( - module_spec, module_sp, - !executable_search_paths.IsEmpty() ? &executable_search_paths : nullptr); + auto error = platform_sp->ResolveExecutable(module_spec, module_sp); if (error.Fail()) { StreamString stream; module_spec.Dump(stream); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt index 01d588f..759a7c4 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt +++ b/lldb/source/Plugins/ExpressionParser/Clang/CMakeLists.txt @@ -51,10 +51,10 @@ add_lldb_library(lldbPluginExpressionParserClang CLANG_LIBS clangAST clangCodeGen - clangDriver clangEdit clangFrontend clangLex + clangOptions clangParse clangRewrite clangRewriteFrontend diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp index 9900745..bae3c44 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp @@ -12,6 +12,7 @@ #include "clang/AST/PrettyPrinter.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/DarwinSDKInfo.h" +#include "clang/Basic/DiagnosticFrontend.h" #include "clang/Basic/DiagnosticIDs.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/SourceLocation.h" @@ -25,7 +26,6 @@ #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" -#include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/Frontend/TextDiagnostic.h" #include "clang/Frontend/TextDiagnosticBuffer.h" @@ -115,6 +115,7 @@ class ClangExpressionParser::LLDBPreprocessorCallbacks : public PPCallbacks { ClangModulesDeclVendor &m_decl_vendor; ClangPersistentVariables &m_persistent_vars; clang::SourceManager &m_source_mgr; + /// Accumulates error messages across all moduleImport calls. StreamString m_error_stream; bool m_has_errors = false; @@ -140,11 +141,12 @@ public: module.path.push_back( ConstString(component.getIdentifierInfo()->getName())); - StreamString error_stream; - ClangModulesDeclVendor::ModuleVector exported_modules; - if (!m_decl_vendor.AddModule(module, &exported_modules, m_error_stream)) + if (auto err = m_decl_vendor.AddModule(module, &exported_modules)) { m_has_errors = true; + m_error_stream.PutCString(llvm::toString(std::move(err))); + m_error_stream.PutChar('\n'); + } for (ClangModulesDeclVendor::ModuleID module : exported_modules) m_persistent_vars.AddHandLoadedClangModule(module); @@ -169,9 +171,9 @@ public: : m_options(opts), m_filename(filename) { m_options.ShowPresumedLoc = true; m_options.ShowLevel = false; - m_os = std::make_shared<llvm::raw_string_ostream>(m_output); + m_os = std::make_unique<llvm::raw_string_ostream>(m_output); m_passthrough = - std::make_shared<clang::TextDiagnosticPrinter>(*m_os, m_options); + std::make_unique<clang::TextDiagnosticPrinter>(*m_os, m_options); } void ResetManager(DiagnosticManager *manager = nullptr) { @@ -313,11 +315,11 @@ public: private: DiagnosticManager *m_manager = nullptr; DiagnosticOptions m_options; - std::shared_ptr<clang::TextDiagnosticPrinter> m_passthrough; - /// Output stream of m_passthrough. - std::shared_ptr<llvm::raw_string_ostream> m_os; /// Output string filled by m_os. std::string m_output; + /// Output stream of m_passthrough. + std::unique_ptr<llvm::raw_string_ostream> m_os; + std::unique_ptr<clang::TextDiagnosticPrinter> m_passthrough; StringRef m_filename; }; @@ -1502,7 +1504,7 @@ lldb_private::Status ClangExpressionParser::DoPrepareForExecution( LLDB_LOGF(log, "%s - Current expression language is %s\n", __FUNCTION__, lang.GetDescription().data()); lldb::ProcessSP process_sp = exe_ctx.GetProcessSP(); - if (process_sp && lang != lldb::eLanguageTypeUnknown) { + if (process_sp && lang) { auto runtime = process_sp->GetLanguageRuntime(lang.AsLanguageType()); if (runtime) runtime->GetIRPasses(custom_passes); diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp index ff9ed9c..ad48d29 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionSourceCode.cpp @@ -383,10 +383,11 @@ bool ClangExpressionSourceCode::GetText( block->CalculateSymbolContext(&sc); if (sc.comp_unit) { - StreamString error_stream; - - decl_vendor->AddModulesForCompileUnit( - *sc.comp_unit, modules_for_macros, error_stream); + if (auto err = decl_vendor->AddModulesForCompileUnit( + *sc.comp_unit, modules_for_macros)) + LLDB_LOG_ERROR( + GetLog(LLDBLog::Expressions), std::move(err), + "Error while loading hand-imported modules:\n{0}"); } } } diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangHost.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangHost.cpp index 6de8510..660a21e 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangHost.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangHost.cpp @@ -10,7 +10,7 @@ #include "clang/Basic/Version.h" #include "clang/Config/config.h" -#include "clang/Driver/Driver.h" +#include "clang/Options/OptionUtils.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" @@ -53,7 +53,7 @@ static bool DefaultComputeClangResourceDirectory(FileSpec &lldb_shlib_spec, std::string raw_path = lldb_shlib_spec.GetPath(); llvm::StringRef parent_dir = llvm::sys::path::parent_path(raw_path); static const std::string clang_resource_path = - clang::driver::Driver::GetResourcesPath("bin/lldb"); + clang::GetResourcesPath("bin/lldb"); static const llvm::StringRef kResourceDirSuffixes[] = { // LLVM.org's build of LLDB uses the clang resource directory placed diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp index b77e269..ce8dc50b 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.cpp @@ -10,6 +10,7 @@ #include "clang/Basic/DiagnosticFrontend.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Driver/CreateInvocationFromArgs.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendActions.h" #include "clang/Frontend/TextDiagnosticPrinter.h" @@ -67,13 +68,13 @@ private: IDAndDiagnostic; std::vector<IDAndDiagnostic> m_diagnostics; std::unique_ptr<clang::DiagnosticOptions> m_diag_opts; + /// Output string filled by m_os. Will be reused for different diagnostics. + std::string m_output; + /// Output stream of m_diag_printer. + std::unique_ptr<llvm::raw_string_ostream> m_os; /// The DiagnosticPrinter used for creating the full diagnostic messages /// that are stored in m_diagnostics. std::unique_ptr<clang::TextDiagnosticPrinter> m_diag_printer; - /// Output stream of m_diag_printer. - std::unique_ptr<llvm::raw_string_ostream> m_os; - /// Output string filled by m_os. Will be reused for different diagnostics. - std::string m_output; /// A Progress with explicitly managed lifetime. std::unique_ptr<Progress> m_current_progress_up; std::vector<std::string> m_module_build_stack; @@ -92,11 +93,11 @@ public: ~ClangModulesDeclVendorImpl() override = default; - bool AddModule(const SourceModule &module, ModuleVector *exported_modules, - Stream &error_stream) override; + llvm::Error AddModule(const SourceModule &module, + ModuleVector *exported_modules) override; - bool AddModulesForCompileUnit(CompileUnit &cu, ModuleVector &exported_modules, - Stream &error_stream) override; + llvm::Error AddModulesForCompileUnit(CompileUnit &cu, + ModuleVector &exported_modules) override; uint32_t FindDecls(ConstString name, bool append, uint32_t max_matches, std::vector<CompilerDecl> &decls) override; @@ -273,16 +274,14 @@ void ClangModulesDeclVendorImpl::ReportModuleExports( exports.push_back(module); } -bool ClangModulesDeclVendorImpl::AddModule(const SourceModule &module, - ModuleVector *exported_modules, - Stream &error_stream) { +llvm::Error +ClangModulesDeclVendorImpl::AddModule(const SourceModule &module, + ModuleVector *exported_modules) { // Fail early. - if (m_compiler_instance->hadModuleLoaderFatalFailure()) { - error_stream.PutCString("error: Couldn't load a module because the module " - "loader is in a fatal state.\n"); - return false; - } + if (m_compiler_instance->hadModuleLoaderFatalFailure()) + return llvm::createStringError( + "couldn't load a module because the module loader is in a fatal state"); // Check if we've already imported this module. @@ -297,7 +296,7 @@ bool ClangModulesDeclVendorImpl::AddModule(const SourceModule &module, if (mi != m_imported_modules.end()) { if (exported_modules) ReportModuleExports(*exported_modules, mi->second); - return true; + return llvm::Error::success(); } } @@ -315,30 +314,30 @@ bool ClangModulesDeclVendorImpl::AddModule(const SourceModule &module, std::equal(sysroot_begin, sysroot_end, path_begin); // No need to inject search paths to modules in the sysroot. if (!is_system_module) { - auto error = [&]() { - error_stream.Printf("error: No module map file in %s\n", - module.search_path.AsCString()); - return false; - }; - bool is_system = true; bool is_framework = false; auto dir = HS.getFileMgr().getOptionalDirectoryRef( module.search_path.GetStringRef()); if (!dir) - return error(); + return llvm::createStringError( + "couldn't find module search path directory %s", + module.search_path.GetCString()); + auto file = HS.lookupModuleMapFile(*dir, is_framework); if (!file) - return error(); + return llvm::createStringError("couldn't find modulemap file in %s", + module.search_path.GetCString()); + if (HS.parseAndLoadModuleMapFile(*file, is_system)) - return error(); + return llvm::createStringError( + "failed to parse and load modulemap file in %s", + module.search_path.GetCString()); } } - if (!HS.lookupModule(module.path.front().GetStringRef())) { - error_stream.Printf("error: Header search couldn't locate module '%s'\n", - module.path.front().AsCString()); - return false; - } + + if (!HS.lookupModule(module.path.front().GetStringRef())) + return llvm::createStringError("header search couldn't locate module '%s'", + module.path.front().AsCString()); llvm::SmallVector<clang::IdentifierLoc, 4> clang_path; @@ -364,22 +363,29 @@ bool ClangModulesDeclVendorImpl::AddModule(const SourceModule &module, clang::Module *top_level_module = DoGetModule(clang_path.front(), false); if (!top_level_module) { + lldb_private::StreamString error_stream; diagnostic_consumer->DumpDiagnostics(error_stream); - error_stream.Printf("error: Couldn't load top-level module %s\n", - module.path.front().AsCString()); - return false; + + return llvm::createStringError(llvm::formatv( + "couldn't load top-level module {0}:\n{1}", + module.path.front().GetStringRef(), error_stream.GetString())); } clang::Module *submodule = top_level_module; for (auto &component : llvm::ArrayRef<ConstString>(module.path).drop_front()) { - submodule = submodule->findSubmodule(component.GetStringRef()); - if (!submodule) { + clang::Module *found = submodule->findSubmodule(component.GetStringRef()); + if (!found) { + lldb_private::StreamString error_stream; diagnostic_consumer->DumpDiagnostics(error_stream); - error_stream.Printf("error: Couldn't load submodule %s\n", - component.GetCString()); - return false; + + return llvm::createStringError(llvm::formatv( + "couldn't load submodule '{0}' of module '{1}':\n{2}", + component.GetStringRef(), submodule->getFullModuleName(), + error_stream.GetString())); } + + submodule = found; } // If we didn't make the submodule visible here, Clang wouldn't allow LLDB to @@ -399,10 +405,12 @@ bool ClangModulesDeclVendorImpl::AddModule(const SourceModule &module, m_enabled = true; - return true; + return llvm::Error::success(); } - return false; + return llvm::createStringError( + llvm::formatv("unknown error while loading module {0}\n", + module.path.front().GetStringRef())); } bool ClangModulesDeclVendor::LanguageSupportsClangModules( @@ -424,15 +432,18 @@ bool ClangModulesDeclVendor::LanguageSupportsClangModules( } } -bool ClangModulesDeclVendorImpl::AddModulesForCompileUnit( - CompileUnit &cu, ClangModulesDeclVendor::ModuleVector &exported_modules, - Stream &error_stream) { - if (LanguageSupportsClangModules(cu.GetLanguage())) { - for (auto &imported_module : cu.GetImportedModules()) - if (!AddModule(imported_module, &exported_modules, error_stream)) - return false; - } - return true; +llvm::Error ClangModulesDeclVendorImpl::AddModulesForCompileUnit( + CompileUnit &cu, ClangModulesDeclVendor::ModuleVector &exported_modules) { + if (!LanguageSupportsClangModules(cu.GetLanguage())) + return llvm::Error::success(); + + llvm::Error errors = llvm::Error::success(); + + for (auto &imported_module : cu.GetImportedModules()) + if (auto err = AddModule(imported_module, &exported_modules)) + errors = llvm::joinErrors(std::move(errors), std::move(err)); + + return errors; } // ClangImporter::lookupValue diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h index ad4d060..0436320 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h @@ -41,21 +41,16 @@ public: /// The path to the exact module to be loaded. E.g., if the desired /// module is std.io, then this should be { "std", "io" }. /// - /// \param[in] exported_modules + /// \param[out] exported_modules /// If non-NULL, a pointer to a vector to populate with the ID of every /// module that is re-exported by the specified module. /// - /// \param[in] error_stream - /// A stream to populate with the output of the Clang parser when - /// it tries to load the module. - /// /// \return /// True if the module could be loaded; false if not. If the /// compiler encountered a fatal error during a previous module /// load, then this will always return false for this ModuleImporter. - virtual bool AddModule(const SourceModule &module, - ModuleVector *exported_modules, - Stream &error_stream) = 0; + virtual llvm::Error AddModule(const SourceModule &module, + ModuleVector *exported_modules) = 0; /// Add all modules referred to in a given compilation unit to the list /// of modules to search. @@ -63,22 +58,17 @@ public: /// \param[in] cu /// The compilation unit to scan for imported modules. /// - /// \param[in] exported_modules + /// \param[out] exported_modules /// A vector to populate with the ID of each module loaded (directly /// and via re-exports) in this way. /// - /// \param[in] error_stream - /// A stream to populate with the output of the Clang parser when - /// it tries to load the modules. - /// /// \return /// True if all modules referred to by the compilation unit could be /// loaded; false if one could not be loaded. If the compiler /// encountered a fatal error during a previous module /// load, then this will always return false for this ModuleImporter. - virtual bool AddModulesForCompileUnit(CompileUnit &cu, - ModuleVector &exported_modules, - Stream &error_stream) = 0; + virtual llvm::Error + AddModulesForCompileUnit(CompileUnit &cu, ModuleVector &exported_modules) = 0; /// Enumerate all the macros that are defined by a given set of modules /// that are already imported. diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp index e8d5ec3..d1feda1 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp @@ -371,26 +371,20 @@ static void SetupDeclVendor(ExecutionContext &exe_ctx, Target *target, if (!sc.comp_unit) return; - StreamString error_stream; - ClangModulesDeclVendor::ModuleVector modules_for_macros = persistent_state->GetHandLoadedClangModules(); - if (decl_vendor->AddModulesForCompileUnit(*sc.comp_unit, modules_for_macros, - error_stream)) - return; - // Failed to load some modules, so emit the error stream as a diagnostic. - if (!error_stream.Empty()) { - // The error stream already contains several Clang diagnostics that might - // be either errors or warnings, so just print them all as one remark - // diagnostic to prevent that the message starts with "error: error:". - diagnostic_manager.PutString(lldb::eSeverityInfo, error_stream.GetString()); + auto err = + decl_vendor->AddModulesForCompileUnit(*sc.comp_unit, modules_for_macros); + if (!err) return; - } - diagnostic_manager.PutString(lldb::eSeverityError, - "Unknown error while loading modules needed for " - "current compilation unit."); + // Module load errors aren't fatal to the expression evaluator. Printing + // them as diagnostics to the console would be too noisy and misleading + // Hence just print them to the expression log. + llvm::handleAllErrors(std::move(err), [](const llvm::StringError &e) { + LLDB_LOG(GetLog(LLDBLog::Expressions), "{0}", e.getMessage()); + }); } ClangExpressionSourceCode::WrapKind ClangUserExpression::GetWrapKind() const { diff --git a/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp b/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp index a8901be..f124424 100644 --- a/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp +++ b/lldb/source/Plugins/Instruction/ARM64/EmulateInstructionARM64.cpp @@ -346,6 +346,16 @@ EmulateInstructionARM64::GetOpcodeForInstruction(const uint32_t opcode) { &EmulateInstructionARM64::EmulateLDRSTRImm<AddrMode_OFF>, "LDR <Xt>, [<Xn|SP>{, #<pimm>}]"}, + {0x3f200c00, 0x3c000400, No_VFP, + &EmulateInstructionARM64::EmulateLDRSTRImm<AddrMode_POST>, + "LDR|STR <Bt|Ht|St|Dt|Qt>, [<Xn|SP>], #<simm>"}, + {0x3f200c00, 0x3c000c00, No_VFP, + &EmulateInstructionARM64::EmulateLDRSTRImm<AddrMode_PRE>, + "LDR|STR <Bt|Ht|St|Dt|Qt>, [<Xn|SP>, #<simm>]!"}, + {0x3f000000, 0x3d000000, No_VFP, + &EmulateInstructionARM64::EmulateLDRSTRImm<AddrMode_OFF>, + "LDR|STR <Bt|Ht|St|Dt|Qt>, [<Xn|SP>{, #<pimm>}]"}, + {0xfc000000, 0x14000000, No_VFP, &EmulateInstructionARM64::EmulateB, "B <label>"}, {0xff000010, 0x54000000, No_VFP, &EmulateInstructionARM64::EmulateBcond, @@ -930,9 +940,29 @@ template <EmulateInstructionARM64::AddrMode a_mode> bool EmulateInstructionARM64::EmulateLDRSTRImm(const uint32_t opcode) { uint32_t size = Bits32(opcode, 31, 30); uint32_t opc = Bits32(opcode, 23, 22); + uint32_t vr = Bit32(opcode, 26); uint32_t n = Bits32(opcode, 9, 5); uint32_t t = Bits32(opcode, 4, 0); + MemOp memop; + if (vr) { + // opc<1> == 1 && size != 0 is an undefined encoding. + if (Bit32(opc, 1) == 1 && size != 0) + return false; + // opc<1> == 1 && size == 0 encode the 128-bit variant. + if (Bit32(opc, 1) == 1) + size = 4; + memop = Bit32(opc, 0) == 1 ? MemOp_LOAD : MemOp_STORE; + } else { + if (Bit32(opc, 1) == 0) { + memop = Bit32(opc, 0) == 1 ? MemOp_LOAD : MemOp_STORE; + } else { + memop = MemOp_LOAD; + if (size == 2 && Bit32(opc, 0) == 1) + return false; + } + } + bool wback; bool postindex; uint64_t offset; @@ -955,16 +985,6 @@ bool EmulateInstructionARM64::EmulateLDRSTRImm(const uint32_t opcode) { break; } - MemOp memop; - - if (Bit32(opc, 1) == 0) { - memop = Bit32(opc, 0) == 1 ? MemOp_LOAD : MemOp_STORE; - } else { - memop = MemOp_LOAD; - if (size == 2 && Bit32(opc, 0) == 1) - return false; - } - Status error; bool success = false; uint64_t address; @@ -989,7 +1009,8 @@ bool EmulateInstructionARM64::EmulateLDRSTRImm(const uint32_t opcode) { return false; std::optional<RegisterInfo> reg_info_Rt = - GetRegisterInfo(eRegisterKindLLDB, gpr_x0_arm64 + t); + vr ? GetRegisterInfo(eRegisterKindLLDB, fpu_d0_arm64 + t) + : GetRegisterInfo(eRegisterKindLLDB, gpr_x0_arm64 + t); if (!reg_info_Rt) return false; diff --git a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp index 5c1b7d4..2957cb71 100644 --- a/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp +++ b/lldb/source/Plugins/Instruction/RISCV/EmulateInstructionRISCV.cpp @@ -1328,32 +1328,36 @@ public: m_emu, inst, 8, ZextD, [](uint64_t a, uint64_t b) { return std::max(a, b); }); } - template <typename T> - bool F_Load(T inst, const fltSemantics &(*semantics)(), - unsigned int numBits) { + template <typename I, typename T> + bool F_Load(I inst, const fltSemantics &(*semantics)()) { return transformOptional(inst.rs1.Read(m_emu), [&](auto &&rs1) { - uint64_t addr = rs1 + uint64_t(inst.imm); - uint64_t bits = *m_emu.ReadMem<uint64_t>(addr); + uint64_t addr = + rs1 + uint64_t(SignExt(inst.imm)); + uint64_t bits = *m_emu.ReadMem<T>(addr); + unsigned numBits = sizeof(T) * 8; APFloat f(semantics(), APInt(numBits, bits)); return inst.rd.WriteAPFloat(m_emu, f); }) .value_or(false); } - bool operator()(FLW inst) { return F_Load(inst, &APFloat::IEEEsingle, 32); } - template <typename T> bool F_Store(T inst, bool isDouble) { + bool operator()(FLW inst) { + return F_Load<FLW, uint32_t>(inst, &APFloat::IEEEsingle); + } + template <typename I, typename T> bool F_Store(I inst, bool isDouble) { return transformOptional(zipOpt(inst.rs1.Read(m_emu), inst.rs2.ReadAPFloat(m_emu, isDouble)), [&](auto &&tup) { auto [rs1, rs2] = tup; - uint64_t addr = rs1 + uint64_t(inst.imm); + uint64_t addr = + rs1 + uint64_t(SignExt(inst.imm)); uint64_t bits = rs2.bitcastToAPInt().getZExtValue(); - return m_emu.WriteMem<uint64_t>(addr, bits); + return m_emu.WriteMem<T>(addr, bits); }) .value_or(false); } - bool operator()(FSW inst) { return F_Store(inst, false); } + bool operator()(FSW inst) { return F_Store<FSW, uint32_t>(inst, false); } std::tuple<bool, APFloat> FusedMultiplyAdd(APFloat rs1, APFloat rs2, APFloat rs3) { auto opStatus = rs1.fusedMultiplyAdd(rs2, rs3, m_emu.GetRoundingMode()); @@ -1616,8 +1620,10 @@ public: bool operator()(FCVT_S_LU inst) { return FCVT_f2i(inst, &Rs::Read, APFloat::IEEEsingle()); } - bool operator()(FLD inst) { return F_Load(inst, &APFloat::IEEEdouble, 64); } - bool operator()(FSD inst) { return F_Store(inst, true); } + bool operator()(FLD inst) { + return F_Load<FLD, uint64_t>(inst, &APFloat::IEEEdouble); + } + bool operator()(FSD inst) { return F_Store<FSD, uint64_t>(inst, true); } bool operator()(FMADD_D inst) { return FMA(inst, true, 1.0f, 1.0f); } bool operator()(FMSUB_D inst) { return FMA(inst, true, 1.0f, -1.0f); } bool operator()(FNMSUB_D inst) { return FMA(inst, true, -1.0f, 1.0f); } diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt new file mode 100644 index 0000000..adbd6c4 --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt @@ -0,0 +1,13 @@ +add_lldb_library(lldbPluginInstrumentationRuntimeBoundsSafety PLUGIN + InstrumentationRuntimeBoundsSafety.cpp + + LINK_LIBS + lldbBreakpoint + lldbCore + lldbSymbol + lldbTarget + lldbPluginInstrumentationRuntimeUtility + + CLANG_LIBS + clangCodeGen + ) diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp new file mode 100644 index 0000000..db9b213 --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp @@ -0,0 +1,481 @@ +//===----------------------------------------------------------------------===// +// +// 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 "InstrumentationRuntimeBoundsSafety.h" + +#include "Plugins/Process/Utility/HistoryThread.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/InstrumentationRuntimeStopInfo.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/RegularExpression.h" +#include "clang/CodeGen/ModuleBuilder.h" + +#include <memory> +#include <type_traits> + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(InstrumentationRuntimeBoundsSafety) + +constexpr llvm::StringLiteral + BoundsSafetySoftTrapMinimal("__bounds_safety_soft_trap"); +constexpr llvm::StringLiteral + BoundsSafetySoftTrapStr("__bounds_safety_soft_trap_s"); + +constexpr std::array<llvm::StringLiteral, 2> +getBoundsSafetySoftTrapRuntimeFuncs() { + return {BoundsSafetySoftTrapMinimal, BoundsSafetySoftTrapStr}; +} + +#define SOFT_TRAP_CATEGORY_PREFIX "Soft " +#define SOFT_TRAP_FALLBACK_CATEGORY \ + SOFT_TRAP_CATEGORY_PREFIX "Bounds check failed" + +using ComputedStopInfo = + std::pair<std::optional<std::string>, std::optional<uint32_t>>; + +class InstrumentationBoundsSafetyStopInfo : public StopInfo { +public: + ~InstrumentationBoundsSafetyStopInfo() override = default; + + lldb::StopReason GetStopReason() const override { + return lldb::eStopReasonInstrumentation; + } + + std::optional<uint32_t> + GetSuggestedStackFrameIndex(bool inlined_stack) override { + return m_value; + } + + const char *GetDescription() override { return m_description.c_str(); } + + bool DoShouldNotify(Event *event_ptr) override { return true; } + + static lldb::StopInfoSP + CreateInstrumentationBoundsSafetyStopInfo(Thread &thread) { + return StopInfoSP(new InstrumentationBoundsSafetyStopInfo(thread)); + } + +private: + InstrumentationBoundsSafetyStopInfo(Thread &thread); + + ComputedStopInfo + ComputeStopReasonAndSuggestedStackFrame(bool &warning_emitted_for_failure); + + ComputedStopInfo ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure); + + ComputedStopInfo ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo( + ThreadSP thread_sp, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure); +}; + +InstrumentationBoundsSafetyStopInfo::InstrumentationBoundsSafetyStopInfo( + Thread &thread) + : StopInfo(thread, 0) { + // No additional data describing the reason for stopping. + m_extended_info = nullptr; + m_description = SOFT_TRAP_FALLBACK_CATEGORY; + + bool warning_emitted_for_failure = false; + auto [MaybeDescription, MaybeSuggestedStackIndex] = + ComputeStopReasonAndSuggestedStackFrame(warning_emitted_for_failure); + if (MaybeDescription) + m_description = MaybeDescription.value(); + if (MaybeSuggestedStackIndex) + m_value = MaybeSuggestedStackIndex.value(); + + // Emit warning about the failure to compute the stop info if one wasn't + // already emitted. + if ((!MaybeDescription.has_value()) && !warning_emitted_for_failure) { + if (ThreadSP thread_sp = GetThread()) { + lldb::user_id_t debugger_id = + thread_sp->GetProcess()->GetTarget().GetDebugger().GetID(); + Debugger::ReportWarning( + "specific BoundsSafety trap reason could not be computed", + debugger_id); + } + } +} + +// Helper functions to make it convenient to log a failure and then return. +template <typename T, typename... ArgTys> +[[nodiscard]] T LogBeforeReturn(ArgTys &&...Args) { + LLDB_LOG(GetLog(LLDBLog::InstrumentationRuntime), Args...); + return T(); +} + +template <typename... ArgTys> +[[nodiscard]] ComputedStopInfo LogFailedCSI(ArgTys &&...Args) { + return LogBeforeReturn<ComputedStopInfo>(Args...); +} + +ComputedStopInfo +InstrumentationBoundsSafetyStopInfo::ComputeStopReasonAndSuggestedStackFrame( + bool &warning_emitted_for_failure) { + ThreadSP thread_sp = GetThread(); + if (!thread_sp) + return LogFailedCSI("failed to get thread while stopped"); + + lldb::user_id_t debugger_id = + thread_sp->GetProcess()->GetTarget().GetDebugger().GetID(); + + StackFrameSP parent_sf = thread_sp->GetStackFrameAtIndex(1); + if (!parent_sf) + return LogFailedCSI("got nullptr when fetching stackframe at index 1"); + + if (parent_sf->HasDebugInformation()) + return ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + parent_sf, debugger_id, warning_emitted_for_failure); + + // If the debug info is missing we can still get some information + // from the parameter in the soft trap runtime call. + return ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo( + thread_sp, debugger_id, warning_emitted_for_failure); +} + +ComputedStopInfo InstrumentationBoundsSafetyStopInfo:: + ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure) { + // First try to use debug info to understand the reason for trapping. The + // call stack will look something like this: + // + // ``` + // frame #0: `__bounds_safety_soft_trap_s(reason="") + // frame #1: `__clang_trap_msg$Bounds check failed$<reason>' + // frame #2: `bad_read(index=10) + // ``` + // .... + const char *TrapReasonFuncName = parent_sf->GetFunctionName(); + + auto MaybeTrapReason = + clang::CodeGen::DemangleTrapReasonInDebugInfo(TrapReasonFuncName); + if (!MaybeTrapReason.has_value()) + return LogFailedCSI( + "clang::CodeGen::DemangleTrapReasonInDebugInfo(\"{0}\") call failed", + TrapReasonFuncName); + + llvm::StringRef category = MaybeTrapReason.value().first; + llvm::StringRef message = MaybeTrapReason.value().second; + + // TODO: Clang should probably be changed to emit the "Soft " prefix itself + std::string stop_reason; + llvm::raw_string_ostream ss(stop_reason); + ss << SOFT_TRAP_CATEGORY_PREFIX; + if (category.empty()) + ss << "<empty category>"; + else + ss << category; + if (message.empty()) { + // This is not a failure so leave `warning_emitted_for_failure` untouched. + Debugger::ReportWarning( + "specific BoundsSafety trap reason is not " + "available because the compiler omitted it from the debug info", + debugger_id); + } else { + ss << ": " << message; + } + // Use computed stop-reason and assume the parent of `parent_sf` is the + // the place in the user's code where the call to the soft trap runtime + // originated. + return std::make_pair(stop_reason, parent_sf->GetFrameIndex() + 1); +} + +ComputedStopInfo InstrumentationBoundsSafetyStopInfo:: + ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo( + ThreadSP thread_sp, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure) { + + StackFrameSP softtrap_sf = thread_sp->GetStackFrameAtIndex(0); + if (!softtrap_sf) + return LogFailedCSI("got nullptr when fetching stackframe at index 0"); + llvm::StringRef trap_reason_func_name = softtrap_sf->GetFunctionName(); + + if (trap_reason_func_name == BoundsSafetySoftTrapMinimal) { + // This function has no arguments so there's no additional information + // that would allow us to identify the trap reason. + // + // Use the fallback stop reason and the current frame. + // While we "could" set the suggested frame to our parent (where the + // bounds check failed), doing this leads to very misleading output in + // LLDB. E.g.: + // + // ``` + // 0x100003b40 <+104>: bl 0x100003d64 ; __bounds_safety_soft_trap + // -> 0x100003b44 <+108>: b 0x100003b48 ; <+112> + // ``` + // + // This makes it look we stopped after finishing the call to + // `__bounds_safety_soft_trap` but actually we are in the middle of the + // call. To avoid this confusion just use the current frame. + std::string warning; + llvm::raw_string_ostream ss(warning); + ss << "specific BoundsSafety trap reason is not available because debug " + "info is missing on the caller of '" + << BoundsSafetySoftTrapMinimal << "'"; + Debugger::ReportWarning(warning.c_str(), debugger_id); + warning_emitted_for_failure = true; + return {}; + } + + // __bounds_safety_soft_trap_s has one argument which is a pointer to a string + // describing the trap or a nullptr. + if (trap_reason_func_name != BoundsSafetySoftTrapStr) { + assert(0 && "hit breakpoint for unexpected function name"); + return LogFailedCSI( + "unexpected function name. Expected \"{0}\" but got \"{1}\"", + BoundsSafetySoftTrapStr.data(), trap_reason_func_name.data()); + } + + RegisterContextSP rc = thread_sp->GetRegisterContext(); + if (!rc) + return LogFailedCSI("failed to get register context"); + + // FIXME: LLDB should have an API that tells us for the current target if + // `LLDB_REGNUM_GENERIC_ARG1` can be used. + // https://github.com/llvm/llvm-project/issues/168602 + // Don't try for architectures where examining the first register won't + // work. + ProcessSP process = thread_sp->GetProcess(); + if (!process) + return LogFailedCSI("failed to get process"); + + switch (process->GetTarget().GetArchitecture().GetCore()) { + case ArchSpec::eCore_x86_32_i386: + case ArchSpec::eCore_x86_32_i486: + case ArchSpec::eCore_x86_32_i486sx: + case ArchSpec::eCore_x86_32_i686: { + // Technically some x86 calling conventions do use a register for + // passing the first argument but let's ignore that for now. + std::string warning; + llvm::raw_string_ostream ss(warning); + ss << "specific BoundsSafety trap reason cannot be inferred on x86 when " + "the caller of '" + << BoundsSafetySoftTrapStr << "' is missing debug info"; + Debugger::ReportWarning(warning.c_str(), debugger_id); + warning_emitted_for_failure = true; + return {}; + } + default: { + } + }; + + // Examine the register for the first argument. + const RegisterInfo *arg0_info = rc->GetRegisterInfo( + lldb::RegisterKind::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_ARG1); + if (!arg0_info) + return LogFailedCSI( + "failed to get register info for LLDB_REGNUM_GENERIC_ARG1"); + RegisterValue reg_value; + if (!rc->ReadRegister(arg0_info, reg_value)) + return LogFailedCSI("failed to read register {0}", arg0_info->name); + uint64_t reg_value_as_int = reg_value.GetAsUInt64(UINT64_MAX); + if (reg_value_as_int == UINT64_MAX) + return LogFailedCSI("failed to read register {0} as a UInt64", + arg0_info->name); + + if (reg_value_as_int == 0) { + // nullptr arg. The compiler will pass that if no trap reason string was + // available. + Debugger::ReportWarning( + "specific BoundsSafety trap reason cannot be inferred because the " + "compiler omitted the reason", + debugger_id); + warning_emitted_for_failure = true; + return {}; + } + + // The first argument to the call is a pointer to a global C string + // containing the trap reason. + std::string out_string; + Status error_status; + thread_sp->GetProcess()->ReadCStringFromMemory(reg_value_as_int, out_string, + error_status); + if (error_status.Fail()) + return LogFailedCSI("failed to read C string from address {0}", + (void *)reg_value_as_int); + + LLDB_LOG(GetLog(LLDBLog::InstrumentationRuntime), + "read C string from {0} found in register {1}: \"{2}\"", + (void *)reg_value_as_int, arg0_info->name, out_string.c_str()); + std::string stop_reason; + llvm::raw_string_ostream SS(stop_reason); + SS << SOFT_TRAP_FALLBACK_CATEGORY; + if (!stop_reason.empty()) { + SS << ": " << out_string; + } + // Use the current frame as the suggested frame for the same reason as for + // `__bounds_safety_soft_trap`. + return {stop_reason, 0}; +} + +InstrumentationRuntimeBoundsSafety::~InstrumentationRuntimeBoundsSafety() { + Deactivate(); +} + +lldb::InstrumentationRuntimeSP +InstrumentationRuntimeBoundsSafety::CreateInstance( + const lldb::ProcessSP &process_sp) { + return InstrumentationRuntimeSP( + new InstrumentationRuntimeBoundsSafety(process_sp)); +} + +void InstrumentationRuntimeBoundsSafety::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + "BoundsSafety instrumentation runtime plugin.", + CreateInstance, GetTypeStatic); +} + +void InstrumentationRuntimeBoundsSafety::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +lldb::InstrumentationRuntimeType +InstrumentationRuntimeBoundsSafety::GetTypeStatic() { + return lldb::eInstrumentationRuntimeTypeBoundsSafety; +} + +const RegularExpression & +InstrumentationRuntimeBoundsSafety::GetPatternForRuntimeLibrary() { + static RegularExpression regex; + return regex; +} + +bool InstrumentationRuntimeBoundsSafety::CheckIfRuntimeIsValid( + const lldb::ModuleSP module_sp) { + Log *log_category = GetLog(LLDBLog::InstrumentationRuntime); + for (const auto &SoftTrapFunc : getBoundsSafetySoftTrapRuntimeFuncs()) { + ConstString test_sym(SoftTrapFunc); + + if (module_sp->FindFirstSymbolWithNameAndType(test_sym, + lldb::eSymbolTypeAny)) { + LLDB_LOG(log_category, "found \"{0}\" in {1}", + test_sym.AsCString("<unknown symbol>"), + module_sp->GetObjectName().AsCString("<unknown module>")); + return true; + } + } + LLDB_LOG(log_category, + "did not find BoundsSafety soft trap functions in module {0}", + module_sp->GetObjectName().AsCString("<unknown module>")); + return false; +} + +bool InstrumentationRuntimeBoundsSafety::NotifyBreakpointHit( + void *baton, StoppointCallbackContext *context, user_id_t break_id, + user_id_t break_loc_id) { + assert(baton && "null baton"); + if (!baton) + return false; ///< false => resume execution. + + InstrumentationRuntimeBoundsSafety *const instance = + static_cast<InstrumentationRuntimeBoundsSafety *>(baton); + + ProcessSP process_sp = instance->GetProcessSP(); + if (!process_sp) + return LogBeforeReturn<bool>("failed to get process from baton"); + ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP(); + if (!thread_sp) + return LogBeforeReturn<bool>( + "failed to get thread from StoppointCallbackContext"); + + if (process_sp != context->exe_ctx_ref.GetProcessSP()) + return LogBeforeReturn<bool>( + "process from baton ({0}) and StoppointCallbackContext ({1}) do " + "not match", + (void *)process_sp.get(), + (void *)context->exe_ctx_ref.GetProcessSP().get()); + + if (process_sp->GetModIDRef().IsLastResumeForUserExpression()) + return LogBeforeReturn<bool>("IsLastResumeForUserExpression is true"); + + // Maybe the stop reason and stackframe selection should be done by + // a stackframe recognizer instead? + thread_sp->SetStopInfo( + InstrumentationBoundsSafetyStopInfo:: + CreateInstrumentationBoundsSafetyStopInfo(*thread_sp)); + return true; +} + +void InstrumentationRuntimeBoundsSafety::Activate() { + if (IsActive()) + return; + + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) + return LogBeforeReturn<void>("could not get process during Activate()"); + + std::vector<std::string> breakpoints; + for (auto &breakpoint_func : getBoundsSafetySoftTrapRuntimeFuncs()) + breakpoints.emplace_back(breakpoint_func); + + BreakpointSP breakpoint = process_sp->GetTarget().CreateBreakpoint( + /*containingModules=*/nullptr, + /*containingSourceFiles=*/nullptr, breakpoints, eFunctionNameTypeFull, + eLanguageTypeUnknown, + /*m_offset=*/0, + /*skip_prologue*/ eLazyBoolNo, + /*internal=*/true, + /*request_hardware*/ false); + + if (!breakpoint) + return LogBeforeReturn<void>("failed to create breakpoint"); + + if (!breakpoint->HasResolvedLocations()) { + assert(0 && "breakpoint has no resolved locations"); + process_sp->GetTarget().RemoveBreakpointByID(breakpoint->GetID()); + return LogBeforeReturn<void>( + "breakpoint {0} for BoundsSafety soft traps did not resolve to " + "any locations", + breakpoint->GetID()); + } + + // Note: When `sync=true` the suggested stackframe is completely ignored. So + // we use `sync=false`. Is that a bug? + breakpoint->SetCallback( + InstrumentationRuntimeBoundsSafety::NotifyBreakpointHit, this, + /*sync=*/false); + breakpoint->SetBreakpointKind("bounds-safety-soft-trap"); + SetBreakpointID(breakpoint->GetID()); + LLDB_LOG(GetLog(LLDBLog::InstrumentationRuntime), + "created breakpoint {0} for BoundsSafety soft traps", + breakpoint->GetID()); + SetActive(true); +} + +void InstrumentationRuntimeBoundsSafety::Deactivate() { + SetActive(false); + Log *log_category = GetLog(LLDBLog::InstrumentationRuntime); + if (ProcessSP process_sp = GetProcessSP()) { + bool success = + process_sp->GetTarget().RemoveBreakpointByID(GetBreakpointID()); + LLDB_LOG(log_category, + "{0}removed breakpoint {1} for BoundsSafety soft traps", + success ? "" : "failed to ", GetBreakpointID()); + } else { + LLDB_LOG(log_category, "no process available during Deactivate()"); + } + + SetBreakpointID(LLDB_INVALID_BREAK_ID); +} diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h new file mode 100644 index 0000000..06c30f8 --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// 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_SOURCE_PLUGINS_INSTRUMENTATIONRUNTIME_BOUNDS_SAFETY_SOFT_TRAP_H +#define LLDB_SOURCE_PLUGINS_INSTRUMENTATIONRUNTIME_BOUNDS_SAFETY_SOFT_TRAP_H + +#include "lldb/Target/ABI.h" +#include "lldb/Target/InstrumentationRuntime.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +class InstrumentationRuntimeBoundsSafety + : public lldb_private::InstrumentationRuntime { +public: + ~InstrumentationRuntimeBoundsSafety() override; + + static lldb::InstrumentationRuntimeSP + CreateInstance(const lldb::ProcessSP &process_sp); + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "BoundsSafety"; } + + static lldb::InstrumentationRuntimeType GetTypeStatic(); + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + virtual lldb::InstrumentationRuntimeType GetType() { return GetTypeStatic(); } + +private: + InstrumentationRuntimeBoundsSafety(const lldb::ProcessSP &process_sp) + : lldb_private::InstrumentationRuntime(process_sp) {} + + const RegularExpression &GetPatternForRuntimeLibrary() override; + + bool CheckIfRuntimeIsValid(const lldb::ModuleSP module_sp) override; + + void Activate() override; + + void Deactivate(); + + static bool NotifyBreakpointHit(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id); + + bool MatchAllModules() override { return true; } +}; + +} // namespace lldb_private + +#endif diff --git a/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt b/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt index 2a6cf93..b7e1a60 100644 --- a/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt +++ b/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt @@ -2,6 +2,7 @@ set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND InstrumentationRuntime) add_subdirectory(ASan) add_subdirectory(ASanLibsanitizers) +add_subdirectory(BoundsSafety) add_subdirectory(MainThreadChecker) add_subdirectory(TSan) add_subdirectory(UBSan) diff --git a/lldb/source/Plugins/InstrumentationRuntime/Utility/ReportRetriever.cpp b/lldb/source/Plugins/InstrumentationRuntime/Utility/ReportRetriever.cpp index 38c334b..3642cb1 100644 --- a/lldb/source/Plugins/InstrumentationRuntime/Utility/ReportRetriever.cpp +++ b/lldb/source/Plugins/InstrumentationRuntime/Utility/ReportRetriever.cpp @@ -207,8 +207,11 @@ bool ReportRetriever::NotifyBreakpointHit(ProcessSP process_sp, return false; StructuredData::ObjectSP report = RetrieveReportData(process_sp); - if (!report || report->GetType() != lldb::eStructuredDataTypeDictionary) + if (!report || report->GetType() != lldb::eStructuredDataTypeDictionary) { + LLDB_LOGF(GetLog(LLDBLog::InstrumentationRuntime), + "ReportRetriever::RetrieveReportData() failed"); return false; + } std::string description = FormatDescription(report); diff --git a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt index cbc6f14..c52d3bd 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt @@ -14,11 +14,11 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN CxxStringTypes.cpp Generic.cpp GenericBitset.cpp + GenericInitializerList.cpp GenericList.cpp GenericOptional.cpp LibCxx.cpp LibCxxAtomic.cpp - LibCxxInitializerList.cpp LibCxxMap.cpp LibCxxQueue.cpp LibCxxRangesRefView.cpp @@ -31,6 +31,7 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN LibCxxValarray.cpp LibCxxVector.cpp LibStdcpp.cpp + LibStdcppSpan.cpp LibStdcppTuple.cpp LibStdcppUniquePointer.cpp MsvcStl.cpp diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index a2199cb..ae6086f 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -84,7 +84,7 @@ CPlusPlusLanguage::GetFunctionNameInfo(ConstString name) const { if (basename.empty()) { llvm::StringRef context; func_name_type |= - (ExtractContextAndIdentifier(name.GetCString(), context, basename) + (ExtractContextAndIdentifier(name.GetStringRef(), context, basename) ? (eFunctionNameTypeMethod | eFunctionNameTypeBase) : eFunctionNameTypeFull); } else { @@ -103,7 +103,7 @@ CPlusPlusLanguage::GetFunctionNameInfo(ConstString name) const { return {func_name_type, ConstString(basename)}; } -bool CPlusPlusLanguage::SymbolNameFitsToLanguage(Mangled mangled) const { +bool CPlusPlusLanguage::SymbolNameFitsToLanguage(const Mangled &mangled) const { auto mangling_scheme = Mangled::GetManglingScheme(mangled.GetMangledName().GetStringRef()); return mangling_scheme == Mangled::eManglingSchemeItanium || @@ -208,6 +208,20 @@ static bool IsTrivialBasename(const llvm::StringRef &basename) { return idx == basename.size(); } +/// A context is trivial if an only if it matches this pattern. +/// "^\s*([A-Za-z_:]*)\s*$". for example function `foo::bar::func()` +/// has a trivial context but. but `foo<int>::bar::func()` doesn't. +static bool IsTrivialContext(llvm::StringRef context) { + // remove trailing or leading whitespace. + context = context.trim(); + + const auto iter = context.find_if_not([](char current) { + return std::isalnum(static_cast<unsigned char>(current)) || + current == '_' || current == ':'; + }); + return iter == llvm::StringRef::npos; +} + /// Writes out the function name in 'full_name' to 'out_stream' /// but replaces each argument type with the variable name /// and the corresponding pretty-printed value @@ -481,18 +495,17 @@ bool CPlusPlusLanguage::CxxMethodName::TrySimplifiedParse() { m_basename = full.substr(basename_begin, basename_end - basename_begin); } - if (IsTrivialBasename(m_basename)) { + if (IsTrivialBasename(m_basename) && IsTrivialContext(m_context)) { return true; - } else { - // The C++ basename doesn't match our regular expressions so this can't - // be a valid C++ method, clear everything out and indicate an error - m_context = llvm::StringRef(); - m_basename = llvm::StringRef(); - m_arguments = llvm::StringRef(); - m_qualifiers = llvm::StringRef(); - m_return_type = llvm::StringRef(); - return false; } + // The C++ basename doesn't match our regular expressions so this can't + // be a valid C++ method, clear everything out and indicate an error + m_context = llvm::StringRef(); + m_basename = llvm::StringRef(); + m_arguments = llvm::StringRef(); + m_qualifiers = llvm::StringRef(); + m_return_type = llvm::StringRef(); + return false; } return false; } @@ -546,9 +559,8 @@ bool CPlusPlusLanguage::CxxMethodName::ContainsPath(llvm::StringRef path) { llvm::StringRef identifier; llvm::StringRef context; - std::string path_str = path.str(); - bool success = CPlusPlusLanguage::ExtractContextAndIdentifier( - path_str.c_str(), context, identifier); + const bool success = + CPlusPlusLanguage::ExtractContextAndIdentifier(path, context, identifier); if (!success) return m_full.GetStringRef().contains(path); @@ -592,7 +604,8 @@ bool CPlusPlusLanguage::DemangledNameContainsPath(llvm::StringRef path, } bool CPlusPlusLanguage::ExtractContextAndIdentifier( - const char *name, llvm::StringRef &context, llvm::StringRef &identifier) { + llvm::StringRef name, llvm::StringRef &context, + llvm::StringRef &identifier) { if (MSVCUndecoratedNameParser::IsMSVCUndecoratedName(name)) return MSVCUndecoratedNameParser::ExtractContextAndIdentifier(name, context, identifier); @@ -899,11 +912,6 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "libc++ std::unordered containers synthetic children", "^std::__[[:alnum:]]+::unordered_(multi)?(map|set)<.+> >$", stl_synth_flags, true); - AddCXXSynthetic( - cpp_category_sp, - lldb_private::formatters::LibcxxInitializerListSyntheticFrontEndCreator, - "libc++ std::initializer_list synthetic children", - "^std::initializer_list<.+>$", stl_synth_flags, true); AddCXXSynthetic(cpp_category_sp, LibcxxQueueFrontEndCreator, "libc++ std::queue synthetic children", "^std::__[[:alnum:]]+::queue<.+>$", stl_synth_flags, true); @@ -1416,6 +1424,10 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { stl_synth_flags, "lldb.formatters.cpp.gnu_libstdcpp.StdForwardListSynthProvider"))); + AddCXXSynthetic(cpp_category_sp, LibStdcppSpanSyntheticFrontEndCreator, + "libstdc++ std::span synthetic children", "^std::span<.+>$", + stl_deref_flags, true); + stl_summary_flags.SetDontShowChildren(false); stl_summary_flags.SetSkipPointers(false); @@ -1506,6 +1518,11 @@ static void LoadLibStdcppFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { lldb_private::formatters::StdlibCoroutineHandleSummaryProvider, "libstdc++ std::coroutine_handle summary provider", libstdcpp_std_coroutine_handle_regex, stl_summary_flags, true); + + AddCXXSummary(cpp_category_sp, + lldb_private::formatters::ContainerSizeSummaryProvider, + "libstdc++ std::span summary provider", "^std::span<.+>$", + stl_summary_flags, true); } static lldb_private::SyntheticChildrenFrontEnd * @@ -1705,6 +1722,14 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { }, "MSVC STL/libstdc++ std::wstring summary provider")); + // NOTE: it is loaded as a common formatter because the libc++ version is not + // in the `__1` namespace, hence we need to dispatch based on the class + // layout. + AddCXXSynthetic(cpp_category_sp, + GenericInitializerListSyntheticFrontEndCreator, + "std::initializer_list synthetic children", + "^std::initializer_list<.+>$", stl_synth_flags, true); + stl_summary_flags.SetDontShowChildren(false); stl_summary_flags.SetSkipPointers(false); @@ -1748,6 +1773,9 @@ static void LoadCommonStlFormatters(lldb::TypeCategoryImplSP cpp_category_sp) { "^std::(multi)?(map|set)<.+>(( )?&)?$", stl_synth_flags, true); + AddCXXSummary(cpp_category_sp, ContainerSizeSummaryProvider, + "std::initializer_list summary provider", + "^std::initializer_list<.+>$", stl_summary_flags, true); AddCXXSummary(cpp_category_sp, GenericSmartPointerSummaryProvider, "MSVC STL/libstdc++ std::shared_ptr summary provider", "^std::shared_ptr<.+>(( )?&)?$", stl_summary_flags, true); diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h index 9a528ca..b547234 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h @@ -92,7 +92,7 @@ public: static llvm::StringRef GetPluginNameStatic() { return "cplusplus"; } - bool SymbolNameFitsToLanguage(Mangled mangled) const override; + bool SymbolNameFitsToLanguage(const Mangled &mangled) const override; bool DemangledNameContainsPath(llvm::StringRef path, ConstString demangled) const override; @@ -154,7 +154,7 @@ public: // C/C++ identifier, then it will return false // and identifier and context will be unchanged. - static bool ExtractContextAndIdentifier(const char *name, + static bool ExtractContextAndIdentifier(llvm::StringRef name, llvm::StringRef &context, llvm::StringRef &identifier); diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp index d8c095d..4d283bb 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusNameParser.cpp @@ -315,7 +315,7 @@ bool CPlusPlusNameParser::ConsumeAbiTag() { // Consume the actual tag string (and allow some special characters) while (ConsumeToken(tok::raw_identifier, tok::comma, tok::period, - tok::numeric_constant)) + tok::numeric_constant, tok::kw_operator)) ; if (!ConsumeToken(tok::r_square)) @@ -420,10 +420,11 @@ bool CPlusPlusNameParser::ConsumeOperator() { // Make sure we have more tokens before attempting to look ahead one more. if (m_next_token_index + 1 < m_tokens.size()) { // Look ahead two tokens. - clang::Token n_token = m_tokens[m_next_token_index + 1]; - // If we find ( or < then this is indeed operator<< no need for fix. - if (n_token.getKind() != tok::l_paren && n_token.getKind() != tok::less) { - clang::Token tmp_tok; + const clang::Token n_token = m_tokens[m_next_token_index + 1]; + // If we find `(`, `<` or `[` then this is indeed operator<< no need for + // fix. + if (!n_token.isOneOf(tok::l_paren, tok::less, tok::l_square)) { + clang::Token tmp_tok{}; tmp_tok.startToken(); tmp_tok.setLength(1); tmp_tok.setLocation(token.getLocation().getLocWithOffset(1)); diff --git a/lldb/source/Plugins/Language/CPlusPlus/Generic.h b/lldb/source/Plugins/Language/CPlusPlus/Generic.h index f394622..539eddd 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/Generic.h +++ b/lldb/source/Plugins/Language/CPlusPlus/Generic.h @@ -24,6 +24,9 @@ bool GenericOptionalSummaryProvider(ValueObject &valobj, Stream &stream, lldb::ValueObjectSP GetDesugaredSmartPointerValue(ValueObject &ptr, ValueObject &container); +SyntheticChildrenFrontEnd * +GenericInitializerListSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP valobj_sp); } // namespace formatters } // namespace lldb_private diff --git a/lldb/source/Plugins/Language/CPlusPlus/GenericInitializerList.cpp b/lldb/source/Plugins/Language/CPlusPlus/GenericInitializerList.cpp new file mode 100644 index 0000000..7f012b7 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/GenericInitializerList.cpp @@ -0,0 +1,145 @@ +//===-- GenericInitializerList.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 "lldb/DataFormatters/FormattersHelpers.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/ValueObject/ValueObject.h" +#include <cstddef> +#include <optional> +#include <type_traits> + +using namespace lldb; +using namespace lldb_private; + +namespace generic_check { +template <class T> +using size_func = decltype(T::GetSizeMember(std::declval<ValueObject &>())); +template <class T> +using start_func = decltype(T::GetStartMember(std::declval<ValueObject &>())); +namespace { +template <typename...> struct check_func : std::true_type {}; +} // namespace + +template <typename T> +using has_functions = check_func<size_func<T>, start_func<T>>; +} // namespace generic_check + +struct LibCxx { + static ValueObjectSP GetStartMember(ValueObject &backend) { + return backend.GetChildMemberWithName("__begin_"); + } + + static ValueObjectSP GetSizeMember(ValueObject &backend) { + return backend.GetChildMemberWithName("__size_"); + } +}; + +struct LibStdcpp { + static ValueObjectSP GetStartMember(ValueObject &backend) { + return backend.GetChildMemberWithName("_M_array"); + } + + static ValueObjectSP GetSizeMember(ValueObject &backend) { + return backend.GetChildMemberWithName("_M_len"); + } +}; + +namespace lldb_private::formatters { + +template <class StandardImpl> +class GenericInitializerListSyntheticFrontEnd + : public SyntheticChildrenFrontEnd { +public: + static_assert(generic_check::has_functions<StandardImpl>::value, + "Missing Required Functions."); + + GenericInitializerListSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) + : SyntheticChildrenFrontEnd(*valobj_sp), m_element_type() { + if (valobj_sp) + Update(); + } + + ~GenericInitializerListSyntheticFrontEnd() override { + // this needs to stay around because it's a child object who will follow its + // parent's life cycle + // delete m_start; + } + + llvm::Expected<uint32_t> CalculateNumChildren() override { + m_num_elements = 0; + + const ValueObjectSP size_sp(StandardImpl::GetSizeMember(m_backend)); + if (size_sp) + m_num_elements = size_sp->GetValueAsUnsigned(0); + return m_num_elements; + } + + lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { + if (!m_start) + return {}; + + uint64_t offset = static_cast<uint64_t>(idx) * m_element_size; + offset = offset + m_start->GetValueAsUnsigned(0); + StreamString name; + name.Printf("[%" PRIu64 "]", (uint64_t)idx); + return CreateValueObjectFromAddress(name.GetString(), offset, + m_backend.GetExecutionContextRef(), + m_element_type); + } + + lldb::ChildCacheState Update() override { + m_start = nullptr; + m_num_elements = 0; + m_element_type = m_backend.GetCompilerType().GetTypeTemplateArgument(0); + if (!m_element_type.IsValid()) + return lldb::ChildCacheState::eRefetch; + + llvm::Expected<uint64_t> size_or_err = m_element_type.GetByteSize(nullptr); + if (!size_or_err) + LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), size_or_err.takeError(), + "{0}"); + else { + m_element_size = *size_or_err; + // Store raw pointers or end up with a circular dependency. + m_start = StandardImpl::GetStartMember(m_backend).get(); + } + + return lldb::ChildCacheState::eRefetch; + } + + llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { + if (!m_start) { + return llvm::createStringError("Type has no child named '%s'", + name.AsCString()); + } + auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); + if (!optional_idx) { + return llvm::createStringError("Type has no child named '%s'", + name.AsCString()); + } + return *optional_idx; + } + +private: + ValueObject *m_start = nullptr; + CompilerType m_element_type; + uint32_t m_element_size = 0; + size_t m_num_elements = 0; +}; + +SyntheticChildrenFrontEnd *GenericInitializerListSyntheticFrontEndCreator( + CXXSyntheticChildren * /*unused*/, lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + + if (LibCxx::GetStartMember(*valobj_sp) != nullptr) + return new GenericInitializerListSyntheticFrontEnd<LibCxx>(valobj_sp); + + return new GenericInitializerListSyntheticFrontEnd<LibStdcpp>(valobj_sp); +} +} // namespace lldb_private::formatters diff --git a/lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp b/lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp index 5289027..8c5ac31 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/GenericList.cpp @@ -203,6 +203,16 @@ private: ValueObject *m_tail = nullptr; }; +/// Gets the (forward-)list element type from the head node instead of the +/// template arguments. This is needed with PDB as it doesn't have info about +/// the template arguments. +CompilerType GetMsvcStlElementTypeFromHead(ValueObject &head) { + auto val_sp = head.GetChildMemberWithName("_Myval"); + if (val_sp) + return val_sp->GetCompilerType(); + return CompilerType(); +} + } // end anonymous namespace template <StlType Stl> @@ -530,6 +540,10 @@ lldb::ChildCacheState MsvcStlForwardListFrontEnd::Update() { m_backend.GetChildAtNamePath({"_Mypair", "_Myval2", "_Myhead"})) m_head = head_sp.get(); + // With PDB, we can't get the element type from the template arguments + if (!m_element_type && m_head) + m_element_type = GetMsvcStlElementTypeFromHead(*m_head); + return ChildCacheState::eRefetch; } @@ -606,6 +620,10 @@ lldb::ChildCacheState MsvcStlListFrontEnd::Update() { m_head = first.get(); m_tail = last.get(); + // With PDB, we can't get the element type from the template arguments + if (!m_element_type && m_head) + m_element_type = GetMsvcStlElementTypeFromHead(*m_head); + return lldb::ChildCacheState::eRefetch; } diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h index 819f8a9..8fd2928 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.h @@ -194,10 +194,6 @@ SyntheticChildrenFrontEnd * LibCxxUnorderedMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); -SyntheticChildrenFrontEnd * -LibcxxInitializerListSyntheticFrontEndCreator(CXXSyntheticChildren *, - lldb::ValueObjectSP); - SyntheticChildrenFrontEnd *LibcxxQueueFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxxInitializerList.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxxInitializerList.cpp deleted file mode 100644 index d952688..0000000 --- a/lldb/source/Plugins/Language/CPlusPlus/LibCxxInitializerList.cpp +++ /dev/null @@ -1,124 +0,0 @@ -//===-- LibCxxInitializerList.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 "LibCxx.h" - -#include "lldb/DataFormatters/FormattersHelpers.h" -#include "lldb/Utility/ConstString.h" -#include "lldb/ValueObject/ValueObject.h" -#include <optional> - -using namespace lldb; -using namespace lldb_private; -using namespace lldb_private::formatters; - -namespace lldb_private { -namespace formatters { -class LibcxxInitializerListSyntheticFrontEnd - : public SyntheticChildrenFrontEnd { -public: - LibcxxInitializerListSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp); - - ~LibcxxInitializerListSyntheticFrontEnd() override; - - llvm::Expected<uint32_t> CalculateNumChildren() override; - - lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override; - - lldb::ChildCacheState Update() override; - - llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override; - -private: - ValueObject *m_start = nullptr; - CompilerType m_element_type; - uint32_t m_element_size = 0; - size_t m_num_elements = 0; -}; -} // namespace formatters -} // namespace lldb_private - -lldb_private::formatters::LibcxxInitializerListSyntheticFrontEnd:: - LibcxxInitializerListSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) - : SyntheticChildrenFrontEnd(*valobj_sp), m_element_type() { - if (valobj_sp) - Update(); -} - -lldb_private::formatters::LibcxxInitializerListSyntheticFrontEnd:: - ~LibcxxInitializerListSyntheticFrontEnd() { - // this needs to stay around because it's a child object who will follow its - // parent's life cycle - // delete m_start; -} - -llvm::Expected<uint32_t> lldb_private::formatters:: - LibcxxInitializerListSyntheticFrontEnd::CalculateNumChildren() { - m_num_elements = 0; - ValueObjectSP size_sp(m_backend.GetChildMemberWithName("__size_")); - if (size_sp) - m_num_elements = size_sp->GetValueAsUnsigned(0); - return m_num_elements; -} - -lldb::ValueObjectSP lldb_private::formatters:: - LibcxxInitializerListSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) { - if (!m_start) - return lldb::ValueObjectSP(); - - uint64_t offset = idx * m_element_size; - offset = offset + m_start->GetValueAsUnsigned(0); - StreamString name; - name.Printf("[%" PRIu64 "]", (uint64_t)idx); - return CreateValueObjectFromAddress(name.GetString(), offset, - m_backend.GetExecutionContextRef(), - m_element_type); -} - -lldb::ChildCacheState -lldb_private::formatters::LibcxxInitializerListSyntheticFrontEnd::Update() { - m_start = nullptr; - m_num_elements = 0; - m_element_type = m_backend.GetCompilerType().GetTypeTemplateArgument(0); - if (!m_element_type.IsValid()) - return lldb::ChildCacheState::eRefetch; - - llvm::Expected<uint64_t> size_or_err = m_element_type.GetByteSize(nullptr); - if (!size_or_err) - LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), size_or_err.takeError(), - "{0}"); - else { - m_element_size = *size_or_err; - // Store raw pointers or end up with a circular dependency. - m_start = m_backend.GetChildMemberWithName("__begin_").get(); - } - - return lldb::ChildCacheState::eRefetch; -} - -llvm::Expected<size_t> -lldb_private::formatters::LibcxxInitializerListSyntheticFrontEnd:: - GetIndexOfChildWithName(ConstString name) { - if (!m_start) { - return llvm::createStringError("Type has no child named '%s'", - name.AsCString()); - } - auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); - if (!optional_idx) { - return llvm::createStringError("Type has no child named '%s'", - name.AsCString()); - } - return *optional_idx; -} - -lldb_private::SyntheticChildrenFrontEnd * -lldb_private::formatters::LibcxxInitializerListSyntheticFrontEndCreator( - CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { - return (valobj_sp ? new LibcxxInitializerListSyntheticFrontEnd(valobj_sp) - : nullptr); -} diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp index f4a695e..86f0a5a 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.cpp @@ -199,9 +199,6 @@ lldb::ChildCacheState VectorIteratorSyntheticFrontEnd::Update() { if (!valobj_sp) return lldb::ChildCacheState::eRefetch; - if (!valobj_sp) - return lldb::ChildCacheState::eRefetch; - ValueObjectSP item_ptr = formatters::GetChildMemberWithName(*valobj_sp, m_item_names); if (!item_ptr) diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h index 429142f6..8d2c81f 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcpp.h @@ -38,6 +38,10 @@ LibstdcppMapIteratorSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); SyntheticChildrenFrontEnd * +LibStdcppSpanSyntheticFrontEndCreator(CXXSyntheticChildren *, + lldb::ValueObjectSP); + +SyntheticChildrenFrontEnd * LibStdcppTupleSyntheticFrontEndCreator(CXXSyntheticChildren *, lldb::ValueObjectSP); diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp new file mode 100644 index 0000000..5e69792 --- /dev/null +++ b/lldb/source/Plugins/Language/CPlusPlus/LibStdcppSpan.cpp @@ -0,0 +1,112 @@ +//===---------------------------------------------------------------------===// +// +// 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 "LibStdcpp.h" + +#include "lldb/DataFormatters/FormattersHelpers.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/ValueObject/ValueObject.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/Support/Error.h" +#include <cstddef> +#include <optional> + +using namespace lldb; + +namespace lldb_private::formatters { + +class LibStdcppSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd { +public: + LibStdcppSpanSyntheticFrontEnd(const lldb::ValueObjectSP &valobj_sp) + : SyntheticChildrenFrontEnd(*valobj_sp) { + if (valobj_sp) + Update(); + } + + ~LibStdcppSpanSyntheticFrontEnd() override = default; + + llvm::Expected<uint32_t> CalculateNumChildren() override { + return m_num_elements; + } + + lldb::ValueObjectSP GetChildAtIndex(uint32_t idx) override { + if (!m_start) + return {}; + + uint64_t offset = (static_cast<uint64_t>(idx) * m_element_size); + offset += m_start->GetValueAsUnsigned(0); + const std::string name = llvm::formatv("[{0}]", idx); + return CreateValueObjectFromAddress( + name, offset, m_backend.GetExecutionContextRef(), m_element_type); + } + + lldb::ChildCacheState Update() override { + const ValueObjectSP data_ptr = m_backend.GetChildMemberWithName("_M_ptr"); + if (!data_ptr) + return lldb::ChildCacheState::eRefetch; + + m_element_type = data_ptr->GetCompilerType().GetPointeeType(); + + // Get element size. + llvm::Expected<uint64_t> size_or_err = m_element_type.GetByteSize(nullptr); + if (!size_or_err) { + LLDB_LOG_ERRORV(GetLog(LLDBLog::DataFormatters), size_or_err.takeError(), + "{0}"); + return lldb::ChildCacheState::eReuse; + } + + m_element_size = *size_or_err; + if (m_element_size > 0) { + m_start = data_ptr.get(); + } + + // Get number of elements. + if (const ValueObjectSP size_sp = + m_backend.GetChildAtNamePath({"_M_extent", "_M_extent_value"})) { + m_num_elements = size_sp->GetValueAsUnsigned(0); + } else if (const auto arg = + m_backend.GetCompilerType().GetIntegralTemplateArgument(1)) { + + m_num_elements = arg->value.GetAPSInt().getLimitedValue(); + } + + return lldb::ChildCacheState::eReuse; + } + + llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override { + if (!m_start) + return llvm::createStringError( + llvm::formatv("Type has no child named {0}", name.GetStringRef())); + + auto optional_idx = formatters::ExtractIndexFromString(name.GetCString()); + if (!optional_idx) { + return llvm::createStringError( + llvm::formatv("Type has no child named {0}", name.GetStringRef())); + } + return *optional_idx; + } + +private: + ValueObject *m_start = nullptr; ///< First element of span. Held, not owned. + CompilerType m_element_type; ///< Type of span elements. + size_t m_num_elements = 0; ///< Number of elements in span. + uint32_t m_element_size = 0; ///< Size in bytes of each span element. +}; + +SyntheticChildrenFrontEnd * +LibStdcppSpanSyntheticFrontEndCreator(CXXSyntheticChildren * /*unused*/, + lldb::ValueObjectSP valobj_sp) { + if (!valobj_sp) + return nullptr; + const CompilerType type = valobj_sp->GetCompilerType(); + if (!type || type.GetNumTemplateArguments() != 2) + return nullptr; + return new LibStdcppSpanSyntheticFrontEnd(valobj_sp); +} + +} // namespace lldb_private::formatters diff --git a/lldb/source/Plugins/Language/ObjC/NSSet.cpp b/lldb/source/Plugins/Language/ObjC/NSSet.cpp index 7d814e65..150b233 100644 --- a/lldb/source/Plugins/Language/ObjC/NSSet.cpp +++ b/lldb/source/Plugins/Language/ObjC/NSSet.cpp @@ -419,8 +419,6 @@ lldb_private::formatters::NSSetISyntheticFrontEnd::Update() { ValueObjectSP valobj_sp = m_backend.GetSP(); if (!valobj_sp) return lldb::ChildCacheState::eRefetch; - if (!valobj_sp) - return lldb::ChildCacheState::eRefetch; m_exe_ctx_ref = valobj_sp->GetExecutionContextRef(); lldb::ProcessSP process_sp(valobj_sp->GetProcessSP()); if (!process_sp) diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp index 3b8e21c..c0dcb95 100644 --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp @@ -235,7 +235,7 @@ ObjCLanguage::GetFunctionNameInfo(ConstString name) const { return {func_name_type, std::nullopt}; } -bool ObjCLanguage::SymbolNameFitsToLanguage(Mangled mangled) const { +bool ObjCLanguage::SymbolNameFitsToLanguage(const Mangled &mangled) const { ConstString demangled_name = mangled.GetDemangledName(); if (!demangled_name) return false; @@ -1065,3 +1065,10 @@ ObjCLanguage::GetBooleanFromString(llvm::StringRef str) const { .Case("NO", {false}) .Default({}); } + +bool ObjCLanguage::IsPossibleObjCMethodName(llvm::StringRef name) { + if (!name.starts_with("-[") && !name.starts_with("+[")) + return false; + + return name.ends_with("]"); +} diff --git a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h index a68ea41..ced6bd3 100644 --- a/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h +++ b/lldb/source/Plugins/Language/ObjC/ObjCLanguage.h @@ -145,7 +145,7 @@ public: std::pair<lldb::FunctionNameType, std::optional<ConstString>> GetFunctionNameInfo(ConstString name) const override; - bool SymbolNameFitsToLanguage(Mangled mangled) const override; + bool SymbolNameFitsToLanguage(const Mangled &mangled) const override; lldb::TypeCategoryImplSP GetFormatters() override; @@ -175,13 +175,7 @@ public: static llvm::StringRef GetPluginNameStatic() { return "objc"; } - static bool IsPossibleObjCMethodName(const char *name) { - if (!name) - return false; - bool starts_right = (name[0] == '+' || name[0] == '-') && name[1] == '['; - bool ends_right = (name[strlen(name) - 1] == ']'); - return (starts_right && ends_right); - } + static bool IsPossibleObjCMethodName(llvm::StringRef name); static bool IsPossibleObjCSelector(const char *name) { if (!name) diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt index a27bcef..727c829 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CMakeLists.txt @@ -6,6 +6,8 @@ add_lldb_library(lldbPluginCPPRuntime lldbCore lldbSymbol lldbTarget + CLANG_LIBS + clangCodeGen ) add_subdirectory(ItaniumABI) diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp index 730aba5..2b6bf2c 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/VerboseTrapFrameRecognizer.cpp @@ -95,33 +95,14 @@ VerboseTrapFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) { if (func_name.empty()) return {}; - static auto trap_regex = - llvm::Regex(llvm::formatv("^{0}\\$(.*)\\$(.*)$", ClangTrapPrefix).str()); - SmallVector<llvm::StringRef, 3> matches; - std::string regex_err_msg; - if (!trap_regex.match(func_name, &matches, ®ex_err_msg)) { - LLDB_LOGF(GetLog(LLDBLog::Unwind), - "Failed to parse match trap regex for '%s': %s", func_name.data(), - regex_err_msg.c_str()); - - return {}; - } - - // For `__clang_trap_msg$category$message$` we expect 3 matches: - // 1. entire string - // 2. category - // 3. message - if (matches.size() != 3) { - LLDB_LOGF(GetLog(LLDBLog::Unwind), - "Unexpected function name format. Expected '<trap prefix>$<trap " - "category>$<trap message>'$ but got: '%s'.", - func_name.data()); - + auto maybe_trap_reason = + clang::CodeGen::DemangleTrapReasonInDebugInfo(func_name); + if (!maybe_trap_reason.has_value()) { + LLDB_LOGF(GetLog(LLDBLog::Unwind), "Failed to demangle '%s' as trap reason", + func_name.str().c_str()); return {}; } - - auto category = matches[1]; - auto message = matches[2]; + auto [category, message] = maybe_trap_reason.value(); std::string stop_reason = category.empty() ? "<empty category>" : category.str(); diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp index 954f269..ebde889 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.cpp @@ -111,7 +111,6 @@ bool ClassDescriptorV2::class_rw_t::Read(Process *process, lldb::addr_t addr) { process->GetAddressByteSize()); lldb::offset_t cursor = 0; - m_flags = extractor.GetU32_unchecked(&cursor); m_version = extractor.GetU32_unchecked(&cursor); m_ro_ptr = extractor.GetAddress_unchecked(&cursor); @@ -119,18 +118,16 @@ bool ClassDescriptorV2::class_rw_t::Read(Process *process, lldb::addr_t addr) { m_ro_ptr = abi_sp->FixCodeAddress(m_ro_ptr); m_method_list_ptr = extractor.GetAddress_unchecked(&cursor); m_properties_ptr = extractor.GetAddress_unchecked(&cursor); - m_firstSubclass = extractor.GetAddress_unchecked(&cursor); - m_nextSiblingClass = extractor.GetAddress_unchecked(&cursor); if (m_ro_ptr & 1) { DataBufferHeap buffer(ptr_size, '\0'); process->ReadMemory(m_ro_ptr ^ 1, buffer.GetBytes(), ptr_size, error); if (error.Fail()) return false; - cursor = 0; DataExtractor extractor(buffer.GetBytes(), ptr_size, process->GetByteOrder(), process->GetAddressByteSize()); + lldb::offset_t cursor = 0; m_ro_ptr = extractor.GetAddress_unchecked(&cursor); if (ABISP abi_sp = process->GetABI()) m_ro_ptr = abi_sp->FixCodeAddress(m_ro_ptr); diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h index 0fff9af..8d19b00 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCClassDescriptorV2.h @@ -133,9 +133,6 @@ private: lldb::addr_t m_properties_ptr; lldb::addr_t m_protocols_ptr; - ObjCLanguageRuntime::ObjCISA m_firstSubclass; - ObjCLanguageRuntime::ObjCISA m_nextSiblingClass; - bool Read(Process *process, lldb::addr_t addr); }; diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp index 9beb133..83e39f3 100644 --- a/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntimeV2.cpp @@ -320,7 +320,7 @@ extern "C" static const char *g_get_shared_cache_class_info_name = "__lldb_apple_objc_v2_get_shared_cache_class_info"; -static const char *g_get_shared_cache_class_info_body = R"( +static const char *g_get_shared_cache_class_info_definitions = R"( extern "C" { @@ -411,6 +411,9 @@ struct ClassInfo Class isa; uint32_t hash; } __attribute__((__packed__)); +)"; + +static const char *g_get_shared_cache_class_info_body = R"( uint32_t __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, @@ -418,6 +421,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, void *class_infos_ptr, uint64_t *relative_selector_offset, uint32_t class_infos_byte_size, + uint32_t *start_idx, uint32_t should_log) { *relative_selector_offset = 0; @@ -426,6 +430,7 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, DEBUG_PRINTF ("shared_cache_base_ptr = %p\n", shared_cache_base_ptr); DEBUG_PRINTF ("class_infos_ptr = %p\n", class_infos_ptr); DEBUG_PRINTF ("class_infos_byte_size = %u (%llu class infos)\n", class_infos_byte_size, (uint64_t)(class_infos_byte_size/sizeof(ClassInfo))); + DEBUG_PRINTF ("start_idx = %u\n", *start_idx); if (objc_opt_ro_ptr) { const objc_opt_t *objc_opt = (objc_opt_t *)objc_opt_ro_ptr; @@ -480,7 +485,11 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, DEBUG_PRINTF ("clsopt->mask = 0x%8.8x\n", clsopt->mask); DEBUG_PRINTF ("classOffsets = %p\n", classOffsets); - for (uint32_t i=0; i<clsopt->capacity; ++i) + const uint32_t original_start_idx = *start_idx; + + // Always start at the start_idx here. If it's greater than the capacity, + // it will skip the loop entirely and go to the duplicate handling below. + for (uint32_t i=*start_idx; i<clsopt->capacity; ++i) { const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset; DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset); @@ -524,59 +533,78 @@ __lldb_apple_objc_v2_get_shared_cache_class_info (void *objc_opt_ro_ptr, else { DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); + *start_idx = i; + break; } ++idx; } - const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; - const uint32_t duplicate_count = *duplicate_count_ptr; - const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]); - - DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); - DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); - - for (uint32_t i=0; i<duplicate_count; ++i) - { - const uint64_t objectCacheOffset = classOffsets[i].objectCacheOffset; - DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset); + if (idx < max_class_infos) { + const uint32_t *duplicate_count_ptr = (uint32_t *)&classOffsets[clsopt->capacity]; + const uint32_t duplicate_count = *duplicate_count_ptr; + const objc_classheader_v16_t *duplicateClassOffsets = (const objc_classheader_v16_t *)(&duplicate_count_ptr[1]); - if (classOffsets[i].isDuplicate) { - DEBUG_PRINTF("isDuplicate = true\n"); - continue; // duplicate - } + DEBUG_PRINTF ("duplicate_count = %u\n", duplicate_count); + DEBUG_PRINTF ("duplicateClassOffsets = %p\n", duplicateClassOffsets); - if (objectCacheOffset == 0) { - DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n"); - continue; // invalid offset - } + const uint32_t duplicate_start_idx = + *start_idx < clsopt->capacity ? + 0 : + *start_idx - clsopt->capacity; - if (class_infos && idx < max_class_infos) + for (uint32_t i=duplicate_start_idx; i<duplicate_count; ++i) { - class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset); + const uint64_t objectCacheOffset = duplicateClassOffsets[i].objectCacheOffset; + DEBUG_PRINTF("objectCacheOffset[%u] = %u\n", i, objectCacheOffset); - // Lookup the class name. - const char *name = class_name_lookup_func(class_infos[idx].isa); - DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); + if (duplicateClassOffsets[i].isDuplicate) { + DEBUG_PRINTF("isDuplicate = true\n"); + continue; // duplicate + } - // Hash the class name so we don't have to read it. - const char *s = name; - uint32_t h = 5381; - for (unsigned char c = *s; c; c = *++s) + if (objectCacheOffset == 0) { + DEBUG_PRINTF("objectCacheOffset == invalidEntryOffset\n"); + continue; // invalid offset + } + + if (class_infos && idx < max_class_infos) { - // class_getName demangles swift names and the hash must - // be calculated on the mangled name. hash==0 means lldb - // will fetch the mangled name and compute the hash in - // ParseClassInfoArray. - if (c == '.') + class_infos[idx].isa = (Class)((uint8_t *)shared_cache_base_ptr + objectCacheOffset); + + // Lookup the class name. + const char *name = class_name_lookup_func(class_infos[idx].isa); + DEBUG_PRINTF("[%u] isa = %8p %s\n", idx, class_infos[idx].isa, name); + + // Hash the class name so we don't have to read it. + const char *s = name; + uint32_t h = 5381; + for (unsigned char c = *s; c; c = *++s) { - h = 0; - break; + // class_getName demangles swift names and the hash must + // be calculated on the mangled name. hash==0 means lldb + // will fetch the mangled name and compute the hash in + // ParseClassInfoArray. + if (c == '.') + { + h = 0; + break; + } + h = ((h << 5) + h) + c; } - h = ((h << 5) + h) + c; + class_infos[idx].hash = h; + } else { + DEBUG_PRINTF("not(class_infos && idx < max_class_infos)\n"); + *start_idx = i; + break; } - class_infos[idx].hash = h; + ++idx; } } + // Always make sure start_idx gets updated. Otherwise we have an infinite + // loop if there are exactly max_class_infos number of classes. + if (*start_idx == original_start_idx) { + *start_idx = idx; + } } else if (objc_opt->version >= 12 && objc_opt->version <= 15) { @@ -1937,6 +1965,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: class_name_getter_function_name.AsCString(), class_name_getter_function_name.AsCString()); + shared_class_expression += g_get_shared_cache_class_info_definitions; shared_class_expression += g_get_shared_cache_class_info_body; auto utility_fn_or_error = exe_ctx.GetTargetRef().CreateUtilityFunction( @@ -1958,6 +1987,9 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: CompilerType clang_uint64_t_pointer_type = scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 64) .GetPointerType(); + CompilerType clang_uint32_t_pointer_type = + scratch_ts_sp->GetBuiltinTypeForEncodingAndBitSize(eEncodingUint, 32) + .GetPointerType(); // Next make the function caller for our implementation utility function. ValueList arguments; @@ -1975,6 +2007,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor:: value.SetValueType(Value::ValueType::Scalar); value.SetCompilerType(clang_uint32_t_type); arguments.PushValue(value); + + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_pointer_type); + arguments.PushValue(value); + + value.SetValueType(Value::ValueType::Scalar); + value.SetCompilerType(clang_uint32_t_type); arguments.PushValue(value); std::unique_ptr<UtilityFunction> utility_fn = std::move(*utility_fn_or_error); @@ -2312,10 +2351,7 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { // The number of entries to pre-allocate room for. // Each entry is (addrsize + 4) bytes - // FIXME: It is not sustainable to continue incrementing this value every time - // the shared cache grows. This is because it requires allocating memory in - // the inferior process and some inferior processes have small memory limits. - const uint32_t max_num_classes = 212992; + const uint32_t max_num_classes_in_buffer = 212992; UtilityFunction *get_class_info_code = GetClassInfoUtilityFunction(exe_ctx); if (!get_class_info_code) { @@ -2337,15 +2373,22 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { DiagnosticManager diagnostics; const uint32_t class_info_byte_size = addr_size + 4; - const uint32_t class_infos_byte_size = max_num_classes * class_info_byte_size; + const uint32_t class_infos_byte_size = + max_num_classes_in_buffer * class_info_byte_size; lldb::addr_t class_infos_addr = process->AllocateMemory( class_infos_byte_size, ePermissionsReadable | ePermissionsWritable, err); const uint32_t relative_selector_offset_addr_size = 64; lldb::addr_t relative_selector_offset_addr = process->AllocateMemory(relative_selector_offset_addr_size, ePermissionsReadable | ePermissionsWritable, err); + constexpr uint32_t class_info_start_idx_byte_size = sizeof(uint32_t); + lldb::addr_t class_info_start_idx_addr = + process->AllocateMemory(class_info_start_idx_byte_size, + ePermissionsReadable | ePermissionsWritable, err); - if (class_infos_addr == LLDB_INVALID_ADDRESS) { + if (class_infos_addr == LLDB_INVALID_ADDRESS || + relative_selector_offset_addr == LLDB_INVALID_ADDRESS || + class_info_start_idx_addr == LLDB_INVALID_ADDRESS) { LLDB_LOGF(log, "unable to allocate %" PRIu32 " bytes in process for shared cache read", @@ -2353,6 +2396,17 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { return DescriptorMapUpdateResult::Fail(); } + const uint32_t start_idx_init_value = 0; + size_t bytes_written = process->WriteMemory( + class_info_start_idx_addr, &start_idx_init_value, sizeof(uint32_t), err); + if (bytes_written != sizeof(uint32_t)) { + LLDB_LOGF(log, + "unable to write %" PRIu32 + " bytes in process for shared cache read", + class_infos_byte_size); + return DescriptorMapUpdateResult::Fail(); + } + std::lock_guard<std::mutex> guard(m_mutex); // Fill in our function argument values @@ -2361,12 +2415,13 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { arguments.GetValueAtIndex(2)->GetScalar() = class_infos_addr; arguments.GetValueAtIndex(3)->GetScalar() = relative_selector_offset_addr; arguments.GetValueAtIndex(4)->GetScalar() = class_infos_byte_size; + arguments.GetValueAtIndex(5)->GetScalar() = class_info_start_idx_addr; // Only dump the runtime classes from the expression evaluation if the log is // verbose: Log *type_log = GetLog(LLDBLog::Types); bool dump_log = type_log && type_log->GetVerbose(); - arguments.GetValueAtIndex(5)->GetScalar() = dump_log ? 1 : 0; + arguments.GetValueAtIndex(6)->GetScalar() = dump_log ? 1 : 0; bool success = false; @@ -2393,78 +2448,80 @@ AppleObjCRuntimeV2::SharedCacheClassInfoExtractor::UpdateISAToDescriptorMap() { diagnostics.Clear(); - // Run the function - ExpressionResults results = - get_shared_cache_class_info_function->ExecuteFunction( - exe_ctx, &m_args, options, diagnostics, return_value); - - if (results == eExpressionCompleted) { - // The result is the number of ClassInfo structures that were filled in - num_class_infos = return_value.GetScalar().ULong(); - LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache", - num_class_infos); - // Assert if there were more classes than we pre-allocated - // room for. - assert(num_class_infos <= max_num_classes); - if (num_class_infos > 0) { - if (num_class_infos > max_num_classes) { - num_class_infos = max_num_classes; - - success = false; - } else { + uint32_t num_class_infos_read = 0; + bool already_read_relative_selector_offset = false; + + do { + // Run the function. + ExpressionResults results = + get_shared_cache_class_info_function->ExecuteFunction( + exe_ctx, &m_args, options, diagnostics, return_value); + + if (results == eExpressionCompleted) { + // The result is the number of ClassInfo structures that were filled in. + num_class_infos_read = return_value.GetScalar().ULong(); + num_class_infos += num_class_infos_read; + LLDB_LOG(log, "Discovered {0} Objective-C classes in the shared cache", + num_class_infos_read); + if (num_class_infos_read > 0) { success = true; - } - // Read the relative selector offset. - DataBufferHeap relative_selector_offset_buffer(64, 0); - if (process->ReadMemory(relative_selector_offset_addr, - relative_selector_offset_buffer.GetBytes(), - relative_selector_offset_buffer.GetByteSize(), - err) == - relative_selector_offset_buffer.GetByteSize()) { - DataExtractor relative_selector_offset_data( - relative_selector_offset_buffer.GetBytes(), - relative_selector_offset_buffer.GetByteSize(), - process->GetByteOrder(), addr_size); - lldb::offset_t offset = 0; - uint64_t relative_selector_offset = - relative_selector_offset_data.GetU64(&offset); - if (relative_selector_offset > 0) { - // The offset is relative to the objc_opt struct. - m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr + - relative_selector_offset); + // Read the relative selector offset. This only needs to occur once no + // matter how many times the function is called. + if (!already_read_relative_selector_offset) { + DataBufferHeap relative_selector_offset_buffer(64, 0); + if (process->ReadMemory( + relative_selector_offset_addr, + relative_selector_offset_buffer.GetBytes(), + relative_selector_offset_buffer.GetByteSize(), + err) == relative_selector_offset_buffer.GetByteSize()) { + DataExtractor relative_selector_offset_data( + relative_selector_offset_buffer.GetBytes(), + relative_selector_offset_buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + lldb::offset_t offset = 0; + uint64_t relative_selector_offset = + relative_selector_offset_data.GetU64(&offset); + if (relative_selector_offset > 0) { + // The offset is relative to the objc_opt struct. + m_runtime.SetRelativeSelectorBaseAddr(objc_opt_ptr + + relative_selector_offset); + } + } + already_read_relative_selector_offset = true; } - } - - // Read the ClassInfo structures - DataBufferHeap class_infos_buffer( - num_class_infos * class_info_byte_size, 0); - if (process->ReadMemory(class_infos_addr, class_infos_buffer.GetBytes(), - class_infos_buffer.GetByteSize(), - err) == class_infos_buffer.GetByteSize()) { - DataExtractor class_infos_data(class_infos_buffer.GetBytes(), - class_infos_buffer.GetByteSize(), - process->GetByteOrder(), addr_size); - m_runtime.ParseClassInfoArray(class_infos_data, num_class_infos); + // Read the ClassInfo structures + DataBufferHeap class_infos_buffer( + num_class_infos_read * class_info_byte_size, 0); + if (process->ReadMemory(class_infos_addr, + class_infos_buffer.GetBytes(), + class_infos_buffer.GetByteSize(), + err) == class_infos_buffer.GetByteSize()) { + DataExtractor class_infos_data(class_infos_buffer.GetBytes(), + class_infos_buffer.GetByteSize(), + process->GetByteOrder(), addr_size); + + m_runtime.ParseClassInfoArray(class_infos_data, + num_class_infos_read); + } } - } else { - success = true; - } - } else { - if (log) { + } else if (log) { LLDB_LOGF(log, "Error evaluating our find class name function."); diagnostics.Dump(log); + break; } - } - } else { - if (log) { - LLDB_LOGF(log, "Error writing function arguments."); - diagnostics.Dump(log); - } + } while (num_class_infos_read == max_num_classes_in_buffer); + } else if (log) { + LLDB_LOGF(log, "Error writing function arguments."); + diagnostics.Dump(log); } - // Deallocate the memory we allocated for the ClassInfo array + LLDB_LOG(log, "Processed {0} Objective-C classes total from the shared cache", + num_class_infos); + // Cleanup memory we allocated in the process. + process->DeallocateMemory(relative_selector_offset_addr); + process->DeallocateMemory(class_info_start_idx_addr); process->DeallocateMemory(class_infos_addr); return DescriptorMapUpdateResult(success, false, num_class_infos); diff --git a/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp b/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp index 33673f1..53ff6ef6 100644 --- a/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp +++ b/lldb/source/Plugins/ObjectFile/Breakpad/ObjectFileBreakpad.cpp @@ -130,13 +130,13 @@ void ObjectFileBreakpad::CreateSections(SectionList &unified_section_list) { std::optional<Record::Kind> current_section; offset_t section_start; - llvm::StringRef text = toStringRef(m_data.GetData()); + llvm::StringRef text = toStringRef(m_data_nsp->GetData()); uint32_t next_section_id = 1; auto maybe_add_section = [&](const uint8_t *end_ptr) { if (!current_section) return; // We have been called before parsing the first line. - offset_t end_offset = end_ptr - m_data.GetDataStart(); + offset_t end_offset = end_ptr - m_data_nsp->GetDataStart(); auto section_sp = std::make_shared<Section>( GetModule(), this, next_section_id++, ConstString(toString(*current_section)), eSectionTypeOther, @@ -162,8 +162,8 @@ void ObjectFileBreakpad::CreateSections(SectionList &unified_section_list) { maybe_add_section(line.bytes_begin()); // And start a new one. current_section = next_section; - section_start = line.bytes_begin() - m_data.GetDataStart(); + section_start = line.bytes_begin() - m_data_nsp->GetDataStart(); } // Finally, add the last section. - maybe_add_section(m_data.GetDataEnd()); + maybe_add_section(m_data_nsp->GetDataEnd()); } diff --git a/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp b/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp index 1121f69..e78f4f0 100644 --- a/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp @@ -300,8 +300,8 @@ bool ObjectFileCOFF::ParseHeader() { std::lock_guard<std::recursive_mutex> guard(module->GetMutex()); - m_data.SetByteOrder(eByteOrderLittle); - m_data.SetAddressByteSize(GetAddressByteSize()); + m_data_nsp->SetByteOrder(eByteOrderLittle); + m_data_nsp->SetAddressByteSize(GetAddressByteSize()); return true; } diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp index e06e69f..5d81b11 100644 --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -130,6 +130,29 @@ private: RelocUnion reloc; }; + +lldb::SectionSP MergeSections(lldb::SectionSP lhs, lldb::SectionSP rhs) { + assert(lhs && rhs); + + lldb::ModuleSP lhs_module_parent = lhs->GetModule(); + lldb::ModuleSP rhs_module_parent = rhs->GetModule(); + assert(lhs_module_parent && rhs_module_parent); + + // Do a sanity check, these should be the same. + if (lhs->GetFileAddress() != rhs->GetFileAddress()) + lhs_module_parent->ReportWarning( + "Mismatch addresses for section {0} when " + "merging with {1}, expected: {2:x}, " + "actual: {3:x}", + lhs->GetTypeAsCString(), + rhs_module_parent->GetFileSpec().GetPathAsConstString().GetCString(), + lhs->GetByteSize(), rhs->GetByteSize()); + + // We want to take the greater of two sections. If LHS and RHS are both + // SHT_NOBITS, we should default to LHS. If RHS has a bigger section, + // indicating it has data that wasn't stripped, we should take that instead. + return rhs->GetFileSize() > lhs->GetFileSize() ? rhs : lhs; +} } // end anonymous namespace ELFRelocation::ELFRelocation(unsigned type) { @@ -781,7 +804,7 @@ ByteOrder ObjectFileELF::GetByteOrder() const { } uint32_t ObjectFileELF::GetAddressByteSize() const { - return m_data.GetAddressByteSize(); + return m_data_nsp->GetAddressByteSize(); } AddressClass ObjectFileELF::GetAddressClass(addr_t file_addr) { @@ -822,7 +845,7 @@ size_t ObjectFileELF::SectionIndex(const SectionHeaderCollConstIter &I) const { bool ObjectFileELF::ParseHeader() { lldb::offset_t offset = 0; - return m_header.Parse(m_data, &offset); + return m_header.Parse(*m_data_nsp.get(), &offset); } UUID ObjectFileELF::GetUUID() { @@ -858,7 +881,7 @@ UUID ObjectFileELF::GetUUID() { return UUID(); core_notes_crc = - CalculateELFNotesSegmentsCRC32(m_program_headers, m_data); + CalculateELFNotesSegmentsCRC32(m_program_headers, *m_data_nsp.get()); if (core_notes_crc) { // Use 8 bytes - first 4 bytes for *magic* prefix, mainly to make it @@ -869,7 +892,7 @@ UUID ObjectFileELF::GetUUID() { } } else { if (!m_gnu_debuglink_crc) - m_gnu_debuglink_crc = calc_crc32(0, m_data); + m_gnu_debuglink_crc = calc_crc32(0, *m_data_nsp.get()); if (m_gnu_debuglink_crc) { // Use 4 bytes of crc from the .gnu_debuglink section. u32le data(m_gnu_debuglink_crc); @@ -1055,7 +1078,8 @@ size_t ObjectFileELF::GetProgramHeaderInfo(ProgramHeaderColl &program_headers, // ParseProgramHeaders bool ObjectFileELF::ParseProgramHeaders() { - return GetProgramHeaderInfo(m_program_headers, m_data, m_header) != 0; + return GetProgramHeaderInfo(m_program_headers, *m_data_nsp.get(), m_header) != + 0; } lldb_private::Status @@ -1645,8 +1669,8 @@ ObjectFileELF::StripLinkerSymbolAnnotations(llvm::StringRef symbol_name) const { // ParseSectionHeaders size_t ObjectFileELF::ParseSectionHeaders() { - return GetSectionHeaderInfo(m_section_headers, m_data, m_header, m_uuid, - m_gnu_debuglink_file, m_gnu_debuglink_crc, + return GetSectionHeaderInfo(m_section_headers, *m_data_nsp.get(), m_header, + m_uuid, m_gnu_debuglink_file, m_gnu_debuglink_crc, m_arch_spec); } @@ -1967,10 +1991,10 @@ void ObjectFileELF::CreateSections(SectionList &unified_section_list) { provider.AddSection(std::move(*InfoOr), std::move(section_sp)); } - // For eTypeDebugInfo files, the Symbol Vendor will take care of updating the - // unified section list. - if (GetType() != eTypeDebugInfo) - unified_section_list = *m_sections_up; + // Merge the two adding any new sections, and overwriting any existing + // sections that are SHT_NOBITS + unified_section_list = + SectionList::Merge(unified_section_list, *m_sections_up, MergeSections); // If there's a .gnu_debugdata section, we'll try to read the .symtab that's // embedded in there and replace the one in the original object file (if any). @@ -3655,7 +3679,8 @@ ArchSpec ObjectFileELF::GetArchitecture() { if (H.p_type != PT_NOTE || H.p_offset == 0 || H.p_filesz == 0) continue; DataExtractor data; - if (data.SetData(m_data, H.p_offset, H.p_filesz) == H.p_filesz) { + if (data.SetData(*m_data_nsp.get(), H.p_offset, H.p_filesz) == + H.p_filesz) { UUID uuid; RefineModuleDetailsFromNote(data, m_arch_spec, uuid); } @@ -3810,10 +3835,10 @@ llvm::ArrayRef<ELFProgramHeader> ObjectFileELF::ProgramHeaders() { } DataExtractor ObjectFileELF::GetSegmentData(const ELFProgramHeader &H) { - // Try and read the program header from our cached m_data which can come from - // the file on disk being mmap'ed or from the initial part of the ELF file we - // read from memory and cached. - DataExtractor data = DataExtractor(m_data, H.p_offset, H.p_filesz); + // Try and read the program header from our cached m_data_nsp which can come + // from the file on disk being mmap'ed or from the initial part of the ELF + // file we read from memory and cached. + DataExtractor data = DataExtractor(*m_data_nsp.get(), H.p_offset, H.p_filesz); if (data.GetByteSize() == H.p_filesz) return data; if (IsInMemory()) { diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp index c8e520d..dff9eab 100644 --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.cpp @@ -1012,35 +1012,35 @@ bool ObjectFileMachO::ParseHeader() { std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); bool can_parse = false; lldb::offset_t offset = 0; - m_data.SetByteOrder(endian::InlHostByteOrder()); + m_data_nsp->SetByteOrder(endian::InlHostByteOrder()); // Leave magic in the original byte order - m_header.magic = m_data.GetU32(&offset); + m_header.magic = m_data_nsp->GetU32(&offset); switch (m_header.magic) { case MH_MAGIC: - m_data.SetByteOrder(endian::InlHostByteOrder()); - m_data.SetAddressByteSize(4); + m_data_nsp->SetByteOrder(endian::InlHostByteOrder()); + m_data_nsp->SetAddressByteSize(4); can_parse = true; break; case MH_MAGIC_64: - m_data.SetByteOrder(endian::InlHostByteOrder()); - m_data.SetAddressByteSize(8); + m_data_nsp->SetByteOrder(endian::InlHostByteOrder()); + m_data_nsp->SetAddressByteSize(8); can_parse = true; break; case MH_CIGAM: - m_data.SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig - ? eByteOrderLittle - : eByteOrderBig); - m_data.SetAddressByteSize(4); + m_data_nsp->SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig + ? eByteOrderLittle + : eByteOrderBig); + m_data_nsp->SetAddressByteSize(4); can_parse = true; break; case MH_CIGAM_64: - m_data.SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig - ? eByteOrderLittle - : eByteOrderBig); - m_data.SetAddressByteSize(8); + m_data_nsp->SetByteOrder(endian::InlHostByteOrder() == eByteOrderBig + ? eByteOrderLittle + : eByteOrderBig); + m_data_nsp->SetAddressByteSize(8); can_parse = true; break; @@ -1049,12 +1049,13 @@ bool ObjectFileMachO::ParseHeader() { } if (can_parse) { - m_data.GetU32(&offset, &m_header.cputype, 6); + m_data_nsp->GetU32(&offset, &m_header.cputype, 6); ModuleSpecList all_specs; ModuleSpec base_spec; - GetAllArchSpecs(m_header, m_data, MachHeaderSizeFromMagic(m_header.magic), - base_spec, all_specs); + GetAllArchSpecs(m_header, *m_data_nsp.get(), + MachHeaderSizeFromMagic(m_header.magic), base_spec, + all_specs); for (unsigned i = 0, e = all_specs.GetSize(); i != e; ++i) { ArchSpec mach_arch = @@ -1068,7 +1069,7 @@ bool ObjectFileMachO::ParseHeader() { if (SetModulesArchitecture(mach_arch)) { const size_t header_and_lc_size = m_header.sizeofcmds + MachHeaderSizeFromMagic(m_header.magic); - if (m_data.GetByteSize() < header_and_lc_size) { + if (m_data_nsp->GetByteSize() < header_and_lc_size) { DataBufferSP data_sp; ProcessSP process_sp(m_process_wp.lock()); if (process_sp) { @@ -1080,7 +1081,7 @@ bool ObjectFileMachO::ParseHeader() { continue; } if (data_sp) - m_data.SetData(data_sp); + m_data_nsp->SetData(data_sp); } } return true; @@ -1094,7 +1095,7 @@ bool ObjectFileMachO::ParseHeader() { } ByteOrder ObjectFileMachO::GetByteOrder() const { - return m_data.GetByteOrder(); + return m_data_nsp->GetByteOrder(); } bool ObjectFileMachO::IsExecutable() const { @@ -1114,7 +1115,7 @@ bool ObjectFileMachO::IsKext() const { } uint32_t ObjectFileMachO::GetAddressByteSize() const { - return m_data.GetAddressByteSize(); + return m_data_nsp->GetAddressByteSize(); } AddressClass ObjectFileMachO::GetAddressClass(lldb::addr_t file_addr) { @@ -1297,13 +1298,13 @@ bool ObjectFileMachO::IsStripped() { const lldb::offset_t load_cmd_offset = offset; llvm::MachO::load_command lc = {}; - if (m_data.GetU32(&offset, &lc.cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &lc.cmd, 2) == nullptr) break; if (lc.cmd == LC_DYSYMTAB) { m_dysymtab.cmd = lc.cmd; m_dysymtab.cmdsize = lc.cmdsize; - if (m_data.GetU32(&offset, &m_dysymtab.ilocalsym, - (sizeof(m_dysymtab) / sizeof(uint32_t)) - 2) == + if (m_data_nsp->GetU32(&offset, &m_dysymtab.ilocalsym, + (sizeof(m_dysymtab) / sizeof(uint32_t)) - 2) == nullptr) { // Clear m_dysymtab if we were unable to read all items from the // load command @@ -1326,14 +1327,14 @@ ObjectFileMachO::EncryptedFileRanges ObjectFileMachO::GetEncryptedFileRanges() { llvm::MachO::encryption_info_command encryption_cmd; for (uint32_t i = 0; i < m_header.ncmds; ++i) { const lldb::offset_t load_cmd_offset = offset; - if (m_data.GetU32(&offset, &encryption_cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &encryption_cmd, 2) == nullptr) break; // LC_ENCRYPTION_INFO and LC_ENCRYPTION_INFO_64 have the same sizes for the // 3 fields we care about, so treat them the same. if (encryption_cmd.cmd == LC_ENCRYPTION_INFO || encryption_cmd.cmd == LC_ENCRYPTION_INFO_64) { - if (m_data.GetU32(&offset, &encryption_cmd.cryptoff, 3)) { + if (m_data_nsp->GetU32(&offset, &encryption_cmd.cryptoff, 3)) { if (encryption_cmd.cryptid != 0) { EncryptedFileRanges::Entry entry; entry.SetRangeBase(encryption_cmd.cryptoff); @@ -1562,7 +1563,7 @@ void ObjectFileMachO::ProcessSegmentCommand( llvm::MachO::segment_command_64 load_cmd; memcpy(&load_cmd, &load_cmd_, sizeof(load_cmd_)); - if (!m_data.GetU8(&offset, (uint8_t *)load_cmd.segname, 16)) + if (!m_data_nsp->GetU8(&offset, (uint8_t *)load_cmd.segname, 16)) return; ModuleSP module_sp = GetModule(); @@ -1586,11 +1587,11 @@ void ObjectFileMachO::ProcessSegmentCommand( add_section = false; } } - load_cmd.vmaddr = m_data.GetAddress(&offset); - load_cmd.vmsize = m_data.GetAddress(&offset); - load_cmd.fileoff = m_data.GetAddress(&offset); - load_cmd.filesize = m_data.GetAddress(&offset); - if (!m_data.GetU32(&offset, &load_cmd.maxprot, 4)) + load_cmd.vmaddr = m_data_nsp->GetAddress(&offset); + load_cmd.vmsize = m_data_nsp->GetAddress(&offset); + load_cmd.fileoff = m_data_nsp->GetAddress(&offset); + load_cmd.filesize = m_data_nsp->GetAddress(&offset); + if (!m_data_nsp->GetU32(&offset, &load_cmd.maxprot, 4)) return; SanitizeSegmentCommand(load_cmd, cmd_idx); @@ -1681,16 +1682,16 @@ void ObjectFileMachO::ProcessSegmentCommand( const uint32_t num_u32s = load_cmd.cmd == LC_SEGMENT ? 7 : 8; for (segment_sect_idx = 0; segment_sect_idx < load_cmd.nsects; ++segment_sect_idx) { - if (m_data.GetU8(&offset, (uint8_t *)sect64.sectname, - sizeof(sect64.sectname)) == nullptr) + if (m_data_nsp->GetU8(&offset, (uint8_t *)sect64.sectname, + sizeof(sect64.sectname)) == nullptr) break; - if (m_data.GetU8(&offset, (uint8_t *)sect64.segname, - sizeof(sect64.segname)) == nullptr) + if (m_data_nsp->GetU8(&offset, (uint8_t *)sect64.segname, + sizeof(sect64.segname)) == nullptr) break; - sect64.addr = m_data.GetAddress(&offset); - sect64.size = m_data.GetAddress(&offset); + sect64.addr = m_data_nsp->GetAddress(&offset); + sect64.size = m_data_nsp->GetAddress(&offset); - if (m_data.GetU32(&offset, §64.offset, num_u32s) == nullptr) + if (m_data_nsp->GetU32(&offset, §64.offset, num_u32s) == nullptr) break; if (IsSharedCacheBinary() && !IsInMemory()) { @@ -1855,8 +1856,8 @@ void ObjectFileMachO::ProcessDysymtabCommand( const llvm::MachO::load_command &load_cmd, lldb::offset_t offset) { m_dysymtab.cmd = load_cmd.cmd; m_dysymtab.cmdsize = load_cmd.cmdsize; - m_data.GetU32(&offset, &m_dysymtab.ilocalsym, - (sizeof(m_dysymtab) / sizeof(uint32_t)) - 2); + m_data_nsp->GetU32(&offset, &m_dysymtab.ilocalsym, + (sizeof(m_dysymtab) / sizeof(uint32_t)) - 2); } void ObjectFileMachO::CreateSections(SectionList &unified_section_list) { @@ -1875,7 +1876,7 @@ void ObjectFileMachO::CreateSections(SectionList &unified_section_list) { llvm::MachO::load_command load_cmd; for (uint32_t i = 0; i < m_header.ncmds; ++i) { const lldb::offset_t load_cmd_offset = offset; - if (m_data.GetU32(&offset, &load_cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &load_cmd, 2) == nullptr) break; if (load_cmd.cmd == LC_SEGMENT || load_cmd.cmd == LC_SEGMENT_64) @@ -2240,13 +2241,13 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { const lldb::offset_t cmd_offset = offset; // Read in the load command and load command size llvm::MachO::load_command lc; - if (m_data.GetU32(&offset, &lc, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &lc, 2) == nullptr) break; // Watch for the symbol table load command switch (lc.cmd) { case LC_SYMTAB: { llvm::MachO::symtab_command lc_obj; - if (m_data.GetU32(&offset, &lc_obj.symoff, 4)) { + if (m_data_nsp->GetU32(&offset, &lc_obj.symoff, 4)) { lc_obj.cmd = lc.cmd; lc_obj.cmdsize = lc.cmdsize; symtab_load_command = lc_obj; @@ -2256,7 +2257,7 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: { llvm::MachO::dyld_info_command lc_obj; - if (m_data.GetU32(&offset, &lc_obj.rebase_off, 10)) { + if (m_data_nsp->GetU32(&offset, &lc_obj.rebase_off, 10)) { lc_obj.cmd = lc.cmd; lc_obj.cmdsize = lc.cmdsize; dyld_info = lc_obj; @@ -2268,8 +2269,8 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { case LC_REEXPORT_DYLIB: case LC_LOADFVMLIB: case LC_LOAD_UPWARD_DYLIB: { - uint32_t name_offset = cmd_offset + m_data.GetU32(&offset); - const char *path = m_data.PeekCStr(name_offset); + uint32_t name_offset = cmd_offset + m_data_nsp->GetU32(&offset); + const char *path = m_data_nsp->PeekCStr(name_offset); if (path) { FileSpec file_spec(path); // Strip the path if there is @rpath, @executable, etc so we just use @@ -2289,19 +2290,19 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { llvm::MachO::linkedit_data_command lc_obj; lc_obj.cmd = lc.cmd; lc_obj.cmdsize = lc.cmdsize; - if (m_data.GetU32(&offset, &lc_obj.dataoff, 2)) + if (m_data_nsp->GetU32(&offset, &lc_obj.dataoff, 2)) exports_trie_load_command = lc_obj; } break; case LC_FUNCTION_STARTS: { llvm::MachO::linkedit_data_command lc_obj; lc_obj.cmd = lc.cmd; lc_obj.cmdsize = lc.cmdsize; - if (m_data.GetU32(&offset, &lc_obj.dataoff, 2)) + if (m_data_nsp->GetU32(&offset, &lc_obj.dataoff, 2)) function_starts_load_command = lc_obj; } break; case LC_UUID: { - const uint8_t *uuid_bytes = m_data.PeekData(offset, 16); + const uint8_t *uuid_bytes = m_data_nsp->PeekData(offset, 16); if (uuid_bytes) image_uuid = UUID(uuid_bytes, 16); @@ -2321,8 +2322,8 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { if (section_list == nullptr) return; - const uint32_t addr_byte_size = m_data.GetAddressByteSize(); - const ByteOrder byte_order = m_data.GetByteOrder(); + const uint32_t addr_byte_size = m_data_nsp->GetAddressByteSize(); + const ByteOrder byte_order = m_data_nsp->GetByteOrder(); bool bit_width_32 = addr_byte_size == 4; const size_t nlist_byte_size = bit_width_32 ? sizeof(struct nlist) : sizeof(struct nlist_64); @@ -2487,9 +2488,9 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { exports_trie_load_command.dataoff += linkedit_slide; } - nlist_data.SetData(m_data, symtab_load_command.symoff, + nlist_data.SetData(*m_data_nsp.get(), symtab_load_command.symoff, nlist_data_byte_size); - strtab_data.SetData(m_data, symtab_load_command.stroff, + strtab_data.SetData(*m_data_nsp.get(), symtab_load_command.stroff, strtab_data_byte_size); // We shouldn't have exports data from both the LC_DYLD_INFO command @@ -2497,19 +2498,22 @@ void ObjectFileMachO::ParseSymtab(Symtab &symtab) { lldbassert(!((dyld_info.export_size > 0) && (exports_trie_load_command.datasize > 0))); if (dyld_info.export_size > 0) { - dyld_trie_data.SetData(m_data, dyld_info.export_off, + dyld_trie_data.SetData(*m_data_nsp.get(), dyld_info.export_off, dyld_info.export_size); } else if (exports_trie_load_command.datasize > 0) { - dyld_trie_data.SetData(m_data, exports_trie_load_command.dataoff, + dyld_trie_data.SetData(*m_data_nsp.get(), + exports_trie_load_command.dataoff, exports_trie_load_command.datasize); } if (dysymtab.nindirectsyms != 0) { - indirect_symbol_index_data.SetData(m_data, dysymtab.indirectsymoff, + indirect_symbol_index_data.SetData(*m_data_nsp.get(), + dysymtab.indirectsymoff, dysymtab.nindirectsyms * 4); } if (function_starts_load_command.cmd) { - function_starts_data.SetData(m_data, function_starts_load_command.dataoff, + function_starts_data.SetData(*m_data_nsp.get(), + function_starts_load_command.dataoff, function_starts_load_command.datasize); } } @@ -4561,8 +4565,9 @@ void ObjectFileMachO::Dump(Stream *s) { *s << ", file = '" << m_file; ModuleSpecList all_specs; ModuleSpec base_spec; - GetAllArchSpecs(m_header, m_data, MachHeaderSizeFromMagic(m_header.magic), - base_spec, all_specs); + GetAllArchSpecs(m_header, *m_data_nsp.get(), + MachHeaderSizeFromMagic(m_header.magic), base_spec, + all_specs); for (unsigned i = 0, e = all_specs.GetSize(); i != e; ++i) { *s << "', triple"; if (e) @@ -4868,7 +4873,7 @@ UUID ObjectFileMachO::GetUUID() { if (module_sp) { std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); lldb::offset_t offset = MachHeaderSizeFromMagic(m_header.magic); - return GetUUID(m_header, m_data, offset); + return GetUUID(m_header, *m_data_nsp.get(), offset); } return UUID(); } @@ -4888,7 +4893,7 @@ uint32_t ObjectFileMachO::GetDependentModules(FileSpecList &files) { uint32_t i; for (i = 0; i < m_header.ncmds; ++i) { const uint32_t cmd_offset = offset; - if (m_data.GetU32(&offset, &load_cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &load_cmd, 2) == nullptr) break; switch (load_cmd.cmd) { @@ -4899,17 +4904,17 @@ uint32_t ObjectFileMachO::GetDependentModules(FileSpecList &files) { case LC_LOAD_DYLINKER: case LC_LOADFVMLIB: case LC_LOAD_UPWARD_DYLIB: { - uint32_t name_offset = cmd_offset + m_data.GetU32(&offset); + uint32_t name_offset = cmd_offset + m_data_nsp->GetU32(&offset); // For LC_LOAD_DYLIB there is an alternate encoding // which adds a uint32_t `flags` field for `DYLD_USE_*` // flags. This can be detected by a timestamp field with // the `DYLIB_USE_MARKER` constant value. bool is_delayed_init = false; - uint32_t use_command_marker = m_data.GetU32(&offset); + uint32_t use_command_marker = m_data_nsp->GetU32(&offset); if (use_command_marker == 0x1a741800 /* DYLIB_USE_MARKER */) { offset += 4; /* uint32_t current_version */ offset += 4; /* uint32_t compat_version */ - uint32_t flags = m_data.GetU32(&offset); + uint32_t flags = m_data_nsp->GetU32(&offset); // If this LC_LOAD_DYLIB is marked delay-init, // don't report it as a dependent library -- it // may be loaded in the process at some point, @@ -4917,7 +4922,7 @@ uint32_t ObjectFileMachO::GetDependentModules(FileSpecList &files) { if (flags & 0x08 /* DYLIB_USE_DELAYED_INIT */) is_delayed_init = true; } - const char *path = m_data.PeekCStr(name_offset); + const char *path = m_data_nsp->PeekCStr(name_offset); if (path && !is_delayed_init) { if (load_cmd.cmd == LC_RPATH) rpath_paths.push_back(path); @@ -5037,15 +5042,15 @@ lldb_private::Address ObjectFileMachO::GetEntryPointAddress() { for (i = 0; i < m_header.ncmds; ++i) { const lldb::offset_t cmd_offset = offset; - if (m_data.GetU32(&offset, &load_cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &load_cmd, 2) == nullptr) break; switch (load_cmd.cmd) { case LC_UNIXTHREAD: case LC_THREAD: { while (offset < cmd_offset + load_cmd.cmdsize) { - uint32_t flavor = m_data.GetU32(&offset); - uint32_t count = m_data.GetU32(&offset); + uint32_t flavor = m_data_nsp->GetU32(&offset); + uint32_t count = m_data_nsp->GetU32(&offset); if (count == 0) { // We've gotten off somehow, log and exit; return m_entry_point_address; @@ -5059,7 +5064,7 @@ lldb_private::Address ObjectFileMachO::GetEntryPointAddress() { { offset += 60; // This is the offset of pc in the GPR thread state // data structure. - start_address = m_data.GetU32(&offset); + start_address = m_data_nsp->GetU32(&offset); done = true; } break; @@ -5069,7 +5074,7 @@ lldb_private::Address ObjectFileMachO::GetEntryPointAddress() { { offset += 256; // This is the offset of pc in the GPR thread state // data structure. - start_address = m_data.GetU64(&offset); + start_address = m_data_nsp->GetU64(&offset); done = true; } break; @@ -5079,7 +5084,7 @@ lldb_private::Address ObjectFileMachO::GetEntryPointAddress() { { offset += 16 * 8; // This is the offset of rip in the GPR thread // state data structure. - start_address = m_data.GetU64(&offset); + start_address = m_data_nsp->GetU64(&offset); done = true; } break; @@ -5094,7 +5099,7 @@ lldb_private::Address ObjectFileMachO::GetEntryPointAddress() { } } break; case LC_MAIN: { - uint64_t entryoffset = m_data.GetU64(&offset); + uint64_t entryoffset = m_data_nsp->GetU64(&offset); SectionSP text_segment_sp = GetSectionList()->FindSectionByName(GetSegmentNameTEXT()); if (text_segment_sp) { @@ -5178,7 +5183,7 @@ uint32_t ObjectFileMachO::GetNumThreadContexts() { llvm::MachO::thread_command thread_cmd; for (uint32_t i = 0; i < m_header.ncmds; ++i) { const uint32_t cmd_offset = offset; - if (m_data.GetU32(&offset, &thread_cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &thread_cmd, 2) == nullptr) break; if (thread_cmd.cmd == LC_THREAD) { @@ -5204,17 +5209,17 @@ ObjectFileMachO::FindLC_NOTEByName(std::string name) { for (uint32_t i = 0; i < m_header.ncmds; ++i) { const uint32_t cmd_offset = offset; llvm::MachO::load_command lc = {}; - if (m_data.GetU32(&offset, &lc.cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &lc.cmd, 2) == nullptr) break; if (lc.cmd == LC_NOTE) { char data_owner[17]; - m_data.CopyData(offset, 16, data_owner); + m_data_nsp->CopyData(offset, 16, data_owner); data_owner[16] = '\0'; offset += 16; if (name == data_owner) { - offset_t payload_offset = m_data.GetU64_unchecked(&offset); - offset_t payload_size = m_data.GetU64_unchecked(&offset); + offset_t payload_offset = m_data_nsp->GetU64_unchecked(&offset); + offset_t payload_size = m_data_nsp->GetU64_unchecked(&offset); results.push_back({payload_offset, payload_size}); } } @@ -5236,11 +5241,11 @@ std::string ObjectFileMachO::GetIdentifierString() { offset_t payload_offset = std::get<0>(lc_note); offset_t payload_size = std::get<1>(lc_note); uint32_t version; - if (m_data.GetU32(&payload_offset, &version, 1) != nullptr) { + if (m_data_nsp->GetU32(&payload_offset, &version, 1) != nullptr) { if (version == 1) { uint32_t strsize = payload_size - sizeof(uint32_t); std::string result(strsize, '\0'); - m_data.CopyData(payload_offset, strsize, result.data()); + m_data_nsp->CopyData(payload_offset, strsize, result.data()); LLDB_LOGF(log, "LC_NOTE 'kern ver str' found with text '%s'", result.c_str()); return result; @@ -5254,12 +5259,12 @@ std::string ObjectFileMachO::GetIdentifierString() { for (uint32_t i = 0; i < m_header.ncmds; ++i) { const uint32_t cmd_offset = offset; llvm::MachO::ident_command ident_command; - if (m_data.GetU32(&offset, &ident_command, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &ident_command, 2) == nullptr) break; if (ident_command.cmd == LC_IDENT && ident_command.cmdsize != 0) { std::string result(ident_command.cmdsize, '\0'); - if (m_data.CopyData(offset, ident_command.cmdsize, result.data()) == - ident_command.cmdsize) { + if (m_data_nsp->CopyData(offset, ident_command.cmdsize, + result.data()) == ident_command.cmdsize) { LLDB_LOGF(log, "LC_IDENT found with text '%s'", result.c_str()); return result; } @@ -5281,9 +5286,10 @@ AddressableBits ObjectFileMachO::GetAddressableBits() { for (auto lc_note : lc_notes) { offset_t payload_offset = std::get<0>(lc_note); uint32_t version; - if (m_data.GetU32(&payload_offset, &version, 1) != nullptr) { + if (m_data_nsp->GetU32(&payload_offset, &version, 1) != nullptr) { if (version == 3) { - uint32_t num_addr_bits = m_data.GetU32_unchecked(&payload_offset); + uint32_t num_addr_bits = + m_data_nsp->GetU32_unchecked(&payload_offset); addressable_bits.SetAddressableBits(num_addr_bits); LLDB_LOGF(log, "LC_NOTE 'addrable bits' v3 found, value %d " @@ -5291,8 +5297,8 @@ AddressableBits ObjectFileMachO::GetAddressableBits() { num_addr_bits); } if (version == 4) { - uint32_t lo_addr_bits = m_data.GetU32_unchecked(&payload_offset); - uint32_t hi_addr_bits = m_data.GetU32_unchecked(&payload_offset); + uint32_t lo_addr_bits = m_data_nsp->GetU32_unchecked(&payload_offset); + uint32_t hi_addr_bits = m_data_nsp->GetU32_unchecked(&payload_offset); if (lo_addr_bits == hi_addr_bits) addressable_bits.SetAddressableBits(lo_addr_bits); @@ -5363,25 +5369,26 @@ bool ObjectFileMachO::GetCorefileMainBinaryInfo(addr_t &value, // uint32_t unused [ for alignment ] uint32_t version; - if (m_data.GetU32(&payload_offset, &version, 1) != nullptr && + if (m_data_nsp->GetU32(&payload_offset, &version, 1) != nullptr && version <= 2) { uint32_t binspec_type = 0; uuid_t raw_uuid; memset(raw_uuid, 0, sizeof(uuid_t)); - if (!m_data.GetU32(&payload_offset, &binspec_type, 1)) + if (!m_data_nsp->GetU32(&payload_offset, &binspec_type, 1)) return false; - if (!m_data.GetU64(&payload_offset, &value, 1)) + if (!m_data_nsp->GetU64(&payload_offset, &value, 1)) return false; uint64_t slide = LLDB_INVALID_ADDRESS; - if (version > 1 && !m_data.GetU64(&payload_offset, &slide, 1)) + if (version > 1 && !m_data_nsp->GetU64(&payload_offset, &slide, 1)) return false; if (value == LLDB_INVALID_ADDRESS && slide != LLDB_INVALID_ADDRESS) { value = slide; value_is_offset = true; } - if (m_data.CopyData(payload_offset, sizeof(uuid_t), raw_uuid) != 0) { + if (m_data_nsp->CopyData(payload_offset, sizeof(uuid_t), raw_uuid) != + 0) { uuid = UUID(raw_uuid, sizeof(uuid_t)); // convert the "main bin spec" type into our // ObjectFile::BinaryType enum @@ -5415,9 +5422,9 @@ bool ObjectFileMachO::GetCorefileMainBinaryInfo(addr_t &value, version, type, typestr, value, value_is_offset ? "true" : "false", uuid.GetAsString().c_str()); - if (!m_data.GetU32(&payload_offset, &log2_pagesize, 1)) + if (!m_data_nsp->GetU32(&payload_offset, &log2_pagesize, 1)) return false; - if (version > 1 && !m_data.GetU32(&payload_offset, &platform, 1)) + if (version > 1 && !m_data_nsp->GetU32(&payload_offset, &platform, 1)) return false; return true; } @@ -5497,7 +5504,7 @@ StructuredData::ObjectSP ObjectFileMachO::GetCorefileProcessMetadata() { auto [payload_offset, strsize] = lc_notes[0]; std::string buf(strsize, '\0'); - if (m_data.CopyData(payload_offset, strsize, buf.data()) != strsize) { + if (m_data_nsp->CopyData(payload_offset, strsize, buf.data()) != strsize) { LLDB_LOGF(log, "Unable to read %" PRIu64 " bytes of 'process metadata' LC_NOTE JSON contents", @@ -5537,7 +5544,8 @@ ObjectFileMachO::GetThreadContextAtIndex(uint32_t idx, m_thread_context_offsets.GetEntryAtIndex(idx); if (thread_context_file_range) { - DataExtractor data(m_data, thread_context_file_range->GetRangeBase(), + DataExtractor data(*m_data_nsp.get(), + thread_context_file_range->GetRangeBase(), thread_context_file_range->GetByteSize()); switch (m_header.cputype) { @@ -5677,13 +5685,13 @@ llvm::VersionTuple ObjectFileMachO::GetVersion() { uint32_t i; for (i = 0; i < m_header.ncmds; ++i) { const lldb::offset_t cmd_offset = offset; - if (m_data.GetU32(&offset, &load_cmd, 2) == nullptr) + if (m_data_nsp->GetU32(&offset, &load_cmd, 2) == nullptr) break; if (load_cmd.cmd == LC_ID_DYLIB) { if (version_cmd == 0) { version_cmd = load_cmd.cmd; - if (m_data.GetU32(&offset, &load_cmd.dylib, 4) == nullptr) + if (m_data_nsp->GetU32(&offset, &load_cmd.dylib, 4) == nullptr) break; version = load_cmd.dylib.current_version; } @@ -5709,7 +5717,7 @@ ArchSpec ObjectFileMachO::GetArchitecture() { if (module_sp) { std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); - return GetArchitecture(module_sp, m_header, m_data, + return GetArchitecture(module_sp, m_header, *m_data_nsp.get(), MachHeaderSizeFromMagic(m_header.magic)); } return arch; @@ -5880,14 +5888,16 @@ static llvm::VersionTuple FindMinimumVersionInfo(DataExtractor &data, llvm::VersionTuple ObjectFileMachO::GetMinimumOSVersion() { if (!m_min_os_version) m_min_os_version = FindMinimumVersionInfo( - m_data, MachHeaderSizeFromMagic(m_header.magic), m_header.ncmds); + *m_data_nsp.get(), MachHeaderSizeFromMagic(m_header.magic), + m_header.ncmds); return *m_min_os_version; } llvm::VersionTuple ObjectFileMachO::GetSDKVersion() { if (!m_sdk_versions) m_sdk_versions = FindMinimumVersionInfo( - m_data, MachHeaderSizeFromMagic(m_header.magic), m_header.ncmds); + *m_data_nsp.get(), MachHeaderSizeFromMagic(m_header.magic), + m_header.ncmds); return *m_sdk_versions; } @@ -5936,6 +5946,20 @@ Section *ObjectFileMachO::GetMachHeaderSection() { return nullptr; } +bool ObjectFileMachO::IsGOTSection(const lldb_private::Section §ion) const { + assert(section.GetObjectFile() == this && "Wrong object file!"); + SectionSP segment = section.GetParent(); + if (!segment) + return false; + + const bool is_data_const_got = + segment->GetName() == "__DATA_CONST" && section.GetName() == "__got"; + const bool is_auth_const_ptr = + segment->GetName() == "__AUTH_CONST" && + (section.GetName() == "__auth_got" || section.GetName() == "__auth_ptr"); + return is_data_const_got || is_auth_const_ptr; +} + bool ObjectFileMachO::SectionIsLoadable(const Section *section) { if (!section) return false; @@ -6688,12 +6712,12 @@ ObjectFileMachO::GetCorefileAllImageInfos() { for (auto lc_note : lc_notes) { offset_t payload_offset = std::get<0>(lc_note); // Read the struct all_image_infos_header. - uint32_t version = m_data.GetU32(&payload_offset); + uint32_t version = m_data_nsp->GetU32(&payload_offset); if (version != 1) { return image_infos; } - uint32_t imgcount = m_data.GetU32(&payload_offset); - uint64_t entries_fileoff = m_data.GetU64(&payload_offset); + uint32_t imgcount = m_data_nsp->GetU32(&payload_offset); + uint64_t entries_fileoff = m_data_nsp->GetU64(&payload_offset); // 'entries_size' is not used, nor is the 'unused' entry. // offset += 4; // uint32_t entries_size; // offset += 4; // uint32_t unused; @@ -6703,17 +6727,18 @@ ObjectFileMachO::GetCorefileAllImageInfos() { payload_offset = entries_fileoff; for (uint32_t i = 0; i < imgcount; i++) { // Read the struct image_entry. - offset_t filepath_offset = m_data.GetU64(&payload_offset); + offset_t filepath_offset = m_data_nsp->GetU64(&payload_offset); uuid_t uuid; - memcpy(&uuid, m_data.GetData(&payload_offset, sizeof(uuid_t)), + memcpy(&uuid, m_data_nsp->GetData(&payload_offset, sizeof(uuid_t)), sizeof(uuid_t)); - uint64_t load_address = m_data.GetU64(&payload_offset); - offset_t seg_addrs_offset = m_data.GetU64(&payload_offset); - uint32_t segment_count = m_data.GetU32(&payload_offset); - uint32_t currently_executing = m_data.GetU32(&payload_offset); + uint64_t load_address = m_data_nsp->GetU64(&payload_offset); + offset_t seg_addrs_offset = m_data_nsp->GetU64(&payload_offset); + uint32_t segment_count = m_data_nsp->GetU32(&payload_offset); + uint32_t currently_executing = m_data_nsp->GetU32(&payload_offset); MachOCorefileImageEntry image_entry; - image_entry.filename = (const char *)m_data.GetCStr(&filepath_offset); + image_entry.filename = + (const char *)m_data_nsp->GetCStr(&filepath_offset); image_entry.uuid = UUID(uuid, sizeof(uuid_t)); image_entry.load_address = load_address; image_entry.currently_executing = currently_executing; @@ -6721,10 +6746,10 @@ ObjectFileMachO::GetCorefileAllImageInfos() { offset_t seg_vmaddrs_offset = seg_addrs_offset; for (uint32_t j = 0; j < segment_count; j++) { char segname[17]; - m_data.CopyData(seg_vmaddrs_offset, 16, segname); + m_data_nsp->CopyData(seg_vmaddrs_offset, 16, segname); segname[16] = '\0'; seg_vmaddrs_offset += 16; - uint64_t vmaddr = m_data.GetU64(&seg_vmaddrs_offset); + uint64_t vmaddr = m_data_nsp->GetU64(&seg_vmaddrs_offset); seg_vmaddrs_offset += 8; /* unused */ std::tuple<ConstString, addr_t> new_seg{ConstString(segname), vmaddr}; @@ -6743,14 +6768,14 @@ ObjectFileMachO::GetCorefileAllImageInfos() { lc_notes = FindLC_NOTEByName("load binary"); for (auto lc_note : lc_notes) { offset_t payload_offset = std::get<0>(lc_note); - uint32_t version = m_data.GetU32(&payload_offset); + uint32_t version = m_data_nsp->GetU32(&payload_offset); if (version == 1) { uuid_t uuid; - memcpy(&uuid, m_data.GetData(&payload_offset, sizeof(uuid_t)), + memcpy(&uuid, m_data_nsp->GetData(&payload_offset, sizeof(uuid_t)), sizeof(uuid_t)); - uint64_t load_address = m_data.GetU64(&payload_offset); - uint64_t slide = m_data.GetU64(&payload_offset); - std::string filename = m_data.GetCStr(&payload_offset); + uint64_t load_address = m_data_nsp->GetU64(&payload_offset); + uint64_t slide = m_data_nsp->GetU64(&payload_offset); + std::string filename = m_data_nsp->GetCStr(&payload_offset); MachOCorefileImageEntry image_entry; image_entry.filename = filename; diff --git a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h index 25643aa..5456f03 100644 --- a/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h +++ b/lldb/source/Plugins/ObjectFile/Mach-O/ObjectFileMachO.h @@ -162,6 +162,8 @@ public: lldb_private::Section *GetMachHeaderSection(); + bool IsGOTSection(const lldb_private::Section §ion) const override; + // PluginInterface protocol llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp index 244489a..f25ed51 100644 --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -396,7 +396,7 @@ bool ObjectFilePECOFF::CreateBinary() { Log *log = GetLog(LLDBLog::Object); auto binary = llvm::object::createBinary(llvm::MemoryBufferRef( - toStringRef(m_data.GetData()), m_file.GetFilename().GetStringRef())); + toStringRef(m_data_nsp->GetData()), m_file.GetFilename().GetStringRef())); if (!binary) { LLDB_LOG_ERROR(log, binary.takeError(), "Failed to create binary for file ({1}): {0}", m_file); @@ -442,20 +442,20 @@ bool ObjectFilePECOFF::ParseHeader() { if (module_sp) { std::lock_guard<std::recursive_mutex> guard(module_sp->GetMutex()); m_sect_headers.clear(); - m_data.SetByteOrder(eByteOrderLittle); + m_data_nsp->SetByteOrder(eByteOrderLittle); lldb::offset_t offset = 0; - if (ParseDOSHeader(m_data, m_dos_header)) { + if (ParseDOSHeader(*m_data_nsp.get(), m_dos_header)) { offset = m_dos_header.e_lfanew; - uint32_t pe_signature = m_data.GetU32(&offset); + uint32_t pe_signature = m_data_nsp->GetU32(&offset); if (pe_signature != IMAGE_NT_SIGNATURE) return false; - if (ParseCOFFHeader(m_data, &offset, m_coff_header)) { + if (ParseCOFFHeader(*m_data_nsp.get(), &offset, m_coff_header)) { if (m_coff_header.hdrsize > 0) ParseCOFFOptionalHeader(&offset); ParseSectionHeaders(offset); } - m_data.SetAddressByteSize(GetAddressByteSize()); + m_data_nsp->SetAddressByteSize(GetAddressByteSize()); return true; } } @@ -602,57 +602,63 @@ bool ObjectFilePECOFF::ParseCOFFOptionalHeader(lldb::offset_t *offset_ptr) { const lldb::offset_t end_offset = *offset_ptr + m_coff_header.hdrsize; if (*offset_ptr < end_offset) { success = true; - m_coff_header_opt.magic = m_data.GetU16(offset_ptr); - m_coff_header_opt.major_linker_version = m_data.GetU8(offset_ptr); - m_coff_header_opt.minor_linker_version = m_data.GetU8(offset_ptr); - m_coff_header_opt.code_size = m_data.GetU32(offset_ptr); - m_coff_header_opt.data_size = m_data.GetU32(offset_ptr); - m_coff_header_opt.bss_size = m_data.GetU32(offset_ptr); - m_coff_header_opt.entry = m_data.GetU32(offset_ptr); - m_coff_header_opt.code_offset = m_data.GetU32(offset_ptr); + m_coff_header_opt.magic = m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.major_linker_version = m_data_nsp->GetU8(offset_ptr); + m_coff_header_opt.minor_linker_version = m_data_nsp->GetU8(offset_ptr); + m_coff_header_opt.code_size = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.data_size = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.bss_size = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.entry = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.code_offset = m_data_nsp->GetU32(offset_ptr); const uint32_t addr_byte_size = GetAddressByteSize(); if (*offset_ptr < end_offset) { if (m_coff_header_opt.magic == OPT_HEADER_MAGIC_PE32) { // PE32 only - m_coff_header_opt.data_offset = m_data.GetU32(offset_ptr); + m_coff_header_opt.data_offset = m_data_nsp->GetU32(offset_ptr); } else m_coff_header_opt.data_offset = 0; if (*offset_ptr < end_offset) { m_coff_header_opt.image_base = - m_data.GetMaxU64(offset_ptr, addr_byte_size); - m_coff_header_opt.sect_alignment = m_data.GetU32(offset_ptr); - m_coff_header_opt.file_alignment = m_data.GetU32(offset_ptr); - m_coff_header_opt.major_os_system_version = m_data.GetU16(offset_ptr); - m_coff_header_opt.minor_os_system_version = m_data.GetU16(offset_ptr); - m_coff_header_opt.major_image_version = m_data.GetU16(offset_ptr); - m_coff_header_opt.minor_image_version = m_data.GetU16(offset_ptr); - m_coff_header_opt.major_subsystem_version = m_data.GetU16(offset_ptr); - m_coff_header_opt.minor_subsystem_version = m_data.GetU16(offset_ptr); - m_coff_header_opt.reserved1 = m_data.GetU32(offset_ptr); - m_coff_header_opt.image_size = m_data.GetU32(offset_ptr); - m_coff_header_opt.header_size = m_data.GetU32(offset_ptr); - m_coff_header_opt.checksum = m_data.GetU32(offset_ptr); - m_coff_header_opt.subsystem = m_data.GetU16(offset_ptr); - m_coff_header_opt.dll_flags = m_data.GetU16(offset_ptr); + m_data_nsp->GetMaxU64(offset_ptr, addr_byte_size); + m_coff_header_opt.sect_alignment = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.file_alignment = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.major_os_system_version = + m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.minor_os_system_version = + m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.major_image_version = m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.minor_image_version = m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.major_subsystem_version = + m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.minor_subsystem_version = + m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.reserved1 = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.image_size = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.header_size = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.checksum = m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.subsystem = m_data_nsp->GetU16(offset_ptr); + m_coff_header_opt.dll_flags = m_data_nsp->GetU16(offset_ptr); m_coff_header_opt.stack_reserve_size = - m_data.GetMaxU64(offset_ptr, addr_byte_size); + m_data_nsp->GetMaxU64(offset_ptr, addr_byte_size); m_coff_header_opt.stack_commit_size = - m_data.GetMaxU64(offset_ptr, addr_byte_size); + m_data_nsp->GetMaxU64(offset_ptr, addr_byte_size); m_coff_header_opt.heap_reserve_size = - m_data.GetMaxU64(offset_ptr, addr_byte_size); + m_data_nsp->GetMaxU64(offset_ptr, addr_byte_size); m_coff_header_opt.heap_commit_size = - m_data.GetMaxU64(offset_ptr, addr_byte_size); - m_coff_header_opt.loader_flags = m_data.GetU32(offset_ptr); - uint32_t num_data_dir_entries = m_data.GetU32(offset_ptr); + m_data_nsp->GetMaxU64(offset_ptr, addr_byte_size); + m_coff_header_opt.loader_flags = m_data_nsp->GetU32(offset_ptr); + uint32_t num_data_dir_entries = m_data_nsp->GetU32(offset_ptr); m_coff_header_opt.data_dirs.clear(); m_coff_header_opt.data_dirs.resize(num_data_dir_entries); uint32_t i; for (i = 0; i < num_data_dir_entries; i++) { - m_coff_header_opt.data_dirs[i].vmaddr = m_data.GetU32(offset_ptr); - m_coff_header_opt.data_dirs[i].vmsize = m_data.GetU32(offset_ptr); + m_coff_header_opt.data_dirs[i].vmaddr = + m_data_nsp->GetU32(offset_ptr); + m_coff_header_opt.data_dirs[i].vmsize = + m_data_nsp->GetU32(offset_ptr); } m_image_base = m_coff_header_opt.image_base; @@ -684,8 +690,8 @@ DataExtractor ObjectFilePECOFF::ReadImageData(uint32_t offset, size_t size) { if (!size) return {}; - if (m_data.ValidOffsetForDataOfSize(offset, size)) - return DataExtractor(m_data, offset, size); + if (m_data_nsp->ValidOffsetForDataOfSize(offset, size)) + return DataExtractor(*m_data_nsp.get(), offset, size); ProcessSP process_sp(m_process_wp.lock()); DataExtractor data; @@ -759,7 +765,7 @@ llvm::StringRef ObjectFilePECOFF::GetSectionName(const section_header_t §) { return ""; lldb::offset_t string_file_offset = m_coff_header.symoff + (m_coff_header.nsyms * 18) + stroff; - if (const char *name = m_data.GetCStr(&string_file_offset)) + if (const char *name = m_data_nsp->GetCStr(&string_file_offset)) return name; return ""; } diff --git a/lldb/source/Plugins/ObjectFile/XCOFF/ObjectFileXCOFF.cpp b/lldb/source/Plugins/ObjectFile/XCOFF/ObjectFileXCOFF.cpp index d2c46ed..bfe1556 100644 --- a/lldb/source/Plugins/ObjectFile/XCOFF/ObjectFileXCOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/XCOFF/ObjectFileXCOFF.cpp @@ -94,7 +94,7 @@ bool ObjectFileXCOFF::CreateBinary() { Log *log = GetLog(LLDBLog::Object); - auto memory_ref = llvm::MemoryBufferRef(toStringRef(m_data.GetData()), + auto memory_ref = llvm::MemoryBufferRef(toStringRef(m_data_nsp->GetData()), m_file.GetFilename().GetStringRef()); llvm::file_magic magic = llvm::identify_magic(memory_ref.getBuffer()); diff --git a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp index 492b441..0bedb5e 100644 --- a/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp +++ b/lldb/source/Plugins/ObjectFile/wasm/ObjectFileWasm.cpp @@ -287,7 +287,7 @@ ObjectFileWasm::ObjectFileWasm(const ModuleSP &module_sp, DataBufferSP data_sp, offset_t offset, offset_t length) : ObjectFile(module_sp, file, offset, length, data_sp, data_offset), m_arch("wasm32-unknown-unknown-wasm") { - m_data.SetAddressByteSize(4); + m_data_nsp->SetAddressByteSize(4); } ObjectFileWasm::ObjectFileWasm(const lldb::ModuleSP &module_sp, @@ -719,11 +719,11 @@ DataExtractor ObjectFileWasm::ReadImageData(offset_t offset, uint32_t size) { DataBufferSP buffer_sp(data_up.release()); data.SetData(buffer_sp, 0, buffer_sp->GetByteSize()); } - } else if (offset < m_data.GetByteSize()) { - size = - std::min(static_cast<uint64_t>(size), m_data.GetByteSize() - offset); - return DataExtractor(m_data.GetDataStart() + offset, size, GetByteOrder(), - GetAddressByteSize()); + } else if (offset < m_data_nsp->GetByteSize()) { + size = std::min(static_cast<uint64_t>(size), + m_data_nsp->GetByteSize() - offset); + return DataExtractor(m_data_nsp->GetDataStart() + offset, size, + GetByteOrder(), GetAddressByteSize()); } } data.SetByteOrder(GetByteOrder()); diff --git a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp index 57d88f6..22b9711 100644 --- a/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp +++ b/lldb/source/Plugins/Platform/Android/PlatformAndroid.cpp @@ -15,6 +15,8 @@ #include "lldb/Utility/UriParser.h" #include "lldb/ValueObject/ValueObject.h" +#include "llvm/ADT/DenseMap.h" + #include "AdbClient.h" #include "PlatformAndroid.h" #include "PlatformAndroidRemoteGDBServer.h" @@ -479,136 +481,90 @@ std::string PlatformAndroid::GetRunAs() { return run_as.str(); } -// Helper function to populate process status information from -// /proc/[pid]/status -void PlatformAndroid::PopulateProcessStatusInfo( - lldb::pid_t pid, ProcessInstanceInfo &process_info) { - // Read /proc/[pid]/status to get parent PID, UIDs, and GIDs - Status error; - AdbClientUP status_adb = GetAdbClient(error); - if (error.Fail()) - return; - - std::string status_output; - StreamString status_cmd; - status_cmd.Printf( - "cat /proc/%llu/status 2>/dev/null | grep -E '^(PPid|Uid|Gid):'", - static_cast<unsigned long long>(pid)); - Status status_error = - status_adb->Shell(status_cmd.GetData(), seconds(5), &status_output); +static bool NeedsCmdlineSupplement(const ProcessInstanceInfo &proc_info) { + llvm::StringRef name = + proc_info.GetExecutableFile().GetFilename().GetStringRef(); + return name.contains("app_process") || name.contains("zygote"); +} - if (status_error.Fail() || status_output.empty()) +// Fetch /proc/PID/cmdline for processes to get actual package names. +// Android apps often show as "zygote" or "app_process" without this. +static void SupplementWithCmdlineInfo(ProcessInstanceInfoList &proc_infos, + AdbClient *adb) { + if (proc_infos.empty()) return; - llvm::SmallVector<llvm::StringRef, 16> lines; - llvm::StringRef(status_output).split(lines, '\n'); - - for (llvm::StringRef line : lines) { - line = line.trim(); - if (line.starts_with("PPid:")) { - llvm::StringRef ppid_str = line.substr(5).trim(); - lldb::pid_t ppid; - if (llvm::to_integer(ppid_str, ppid)) - process_info.SetParentProcessID(ppid); - } else if (line.starts_with("Uid:")) { - llvm::SmallVector<llvm::StringRef, 4> uid_parts; - line.substr(4).trim().split(uid_parts, '\t', -1, false); - if (uid_parts.size() >= 2) { - uint32_t uid, euid; - if (llvm::to_integer(uid_parts[0].trim(), uid)) - process_info.SetUserID(uid); - if (llvm::to_integer(uid_parts[1].trim(), euid)) - process_info.SetEffectiveUserID(euid); - } - } else if (line.starts_with("Gid:")) { - llvm::SmallVector<llvm::StringRef, 4> gid_parts; - line.substr(4).trim().split(gid_parts, '\t', -1, false); - if (gid_parts.size() >= 2) { - uint32_t gid, egid; - if (llvm::to_integer(gid_parts[0].trim(), gid)) - process_info.SetGroupID(gid); - if (llvm::to_integer(gid_parts[1].trim(), egid)) - process_info.SetEffectiveGroupID(egid); - } + llvm::DenseMap<lldb::pid_t, ProcessInstanceInfo *> pid_map; + std::string pid_list; + for (auto &proc_info : proc_infos) { + if (NeedsCmdlineSupplement(proc_info)) { + lldb::pid_t pid = proc_info.GetProcessID(); + pid_map[pid] = &proc_info; + if (!pid_list.empty()) + pid_list += " "; + pid_list += std::to_string(pid); } } -} -// Helper function to populate command line arguments from /proc/[pid]/cmdline -void PlatformAndroid::PopulateProcessCommandLine( - lldb::pid_t pid, ProcessInstanceInfo &process_info) { - // Read /proc/[pid]/cmdline to get command line arguments - Status error; - AdbClientUP cmdline_adb = GetAdbClient(error); - if (error.Fail()) + if (pid_list.empty()) return; + Log *log = GetLog(LLDBLog::Platform); + + // Use xargs -P to parallelize cmdline fetching (up to 8 concurrent reads) + StreamString cmd; + cmd.Printf( + "echo '%s' | xargs -n 1 -P 8 sh -c " + "'echo \"$1:$(cat /proc/$1/cmdline 2>/dev/null | tr \"\\0\" \" \")\"' sh", + pid_list.c_str()); + std::string cmdline_output; - StreamString cmdline_cmd; - cmdline_cmd.Printf("cat /proc/%llu/cmdline 2>/dev/null | tr '\\000' ' '", - static_cast<unsigned long long>(pid)); - Status cmdline_error = - cmdline_adb->Shell(cmdline_cmd.GetData(), seconds(5), &cmdline_output); + Status error = adb->Shell(cmd.GetData(), seconds(5), &cmdline_output); - if (cmdline_error.Fail() || cmdline_output.empty()) + if (error.Fail() || cmdline_output.empty()) return; - cmdline_output = llvm::StringRef(cmdline_output).trim().str(); - if (cmdline_output.empty()) - return; + llvm::SmallVector<llvm::StringRef, 256> lines; + llvm::StringRef(cmdline_output).split(lines, '\n', -1, false); - llvm::SmallVector<llvm::StringRef, 16> args; - llvm::StringRef(cmdline_output).split(args, ' ', -1, false); - if (args.empty()) - return; + for (llvm::StringRef line : lines) { + line = line.trim(); + auto [pid_str, cmdline] = line.split(':'); + if (pid_str.empty() || cmdline.empty()) + continue; - process_info.SetArg0(args[0]); - Args process_args; - for (size_t i = 1; i < args.size(); i++) { - if (!args[i].empty()) - process_args.AppendArgument(args[i]); - } - process_info.SetArguments(process_args, false); -} + cmdline = cmdline.trim(); -// Helper function to populate architecture from /proc/[pid]/exe -void PlatformAndroid::PopulateProcessArchitecture( - lldb::pid_t pid, ProcessInstanceInfo &process_info) { - // Read /proc/[pid]/exe to get executable path for architecture detection - Status error; - AdbClientUP exe_adb = GetAdbClient(error); - if (error.Fail()) - return; + lldb::pid_t pid; + if (!llvm::to_integer(pid_str, pid) || cmdline.empty()) + continue; - std::string exe_output; - StreamString exe_cmd; - exe_cmd.Printf("readlink /proc/%llu/exe 2>/dev/null", - static_cast<unsigned long long>(pid)); - Status exe_error = exe_adb->Shell(exe_cmd.GetData(), seconds(5), &exe_output); + auto it = pid_map.find(pid); + if (it == pid_map.end()) + continue; - if (exe_error.Fail() || exe_output.empty()) - return; + ProcessInstanceInfo *proc_info = it->second; + llvm::SmallVector<llvm::StringRef, 16> args; + cmdline.split(args, ' ', -1, false); - exe_output = llvm::StringRef(exe_output).trim().str(); - - // Determine architecture from exe path - ArchSpec arch; - if (exe_output.find("64") != std::string::npos || - exe_output.find("arm64") != std::string::npos || - exe_output.find("aarch64") != std::string::npos) { - arch.SetTriple("aarch64-unknown-linux-android"); - } else if (exe_output.find("x86_64") != std::string::npos) { - arch.SetTriple("x86_64-unknown-linux-android"); - } else if (exe_output.find("x86") != std::string::npos || - exe_output.find("i686") != std::string::npos) { - arch.SetTriple("i686-unknown-linux-android"); - } else { - // Default to armv7 for 32-bit ARM (most common on Android) - arch.SetTriple("armv7-unknown-linux-android"); - } + if (!args.empty()) { + proc_info->GetExecutableFile().SetFile(args[0], FileSpec::Style::posix); + + if (args.size() > 1) { + Args process_args; + for (size_t i = 1; i < args.size(); ++i) { + if (!args[i].empty()) + process_args.AppendArgument(args[i]); + } + proc_info->SetArguments(process_args, false); + } - if (arch.IsValid()) - process_info.SetArchitecture(arch); + LLDB_LOGF(log, + "PlatformAndroid::%s supplemented PID %llu with cmdline: %s", + __FUNCTION__, static_cast<unsigned long long>(pid), + cmdline.str().c_str()); + } + } } uint32_t @@ -616,109 +572,39 @@ PlatformAndroid::FindProcesses(const ProcessInstanceInfoMatch &match_info, ProcessInstanceInfoList &proc_infos) { proc_infos.clear(); - // When LLDB is running natively on an Android device (IsHost() == true), - // use the parent class's standard Linux /proc enumeration. IsHost() is only - // true when compiled for Android (#if defined(__ANDROID__)), so calling - // PlatformLinux methods is safe (Android is Linux-based). if (IsHost()) return PlatformLinux::FindProcesses(match_info, proc_infos); - // Remote Android platform: implement process name lookup using 'pidof' over - // adb. - - // LLDB stores the search name in GetExecutableFile() (even though it's - // actually a process name like "com.android.chrome" rather than an - // executable path). If no search name is provided, we can't use - // 'pidof', so return early with no results. - const ProcessInstanceInfo &match_process_info = match_info.GetProcessInfo(); - if (!match_process_info.GetExecutableFile() || - match_info.GetNameMatchType() == NameMatch::Ignore) { - return 0; - } - - // Extract the process name to search for (typically an Android package name - // like "com.example.app" or binary name like "app_process64") - std::string process_name = match_process_info.GetExecutableFile().GetPath(); - if (process_name.empty()) - return 0; - - // Use adb to find the process by name - Status error; - AdbClientUP adb(GetAdbClient(error)); - if (error.Fail()) { - Log *log = GetLog(LLDBLog::Platform); - LLDB_LOGF(log, "PlatformAndroid::%s failed to get ADB client: %s", - __FUNCTION__, error.AsCString()); - return 0; - } - - // Use 'pidof' command to get PIDs for the process name. - // Quote the process name to handle special characters (spaces, etc.) - std::string pidof_output; - StreamString command; - command.Printf("pidof '%s'", process_name.c_str()); - error = adb->Shell(command.GetData(), seconds(5), &pidof_output); - - if (error.Fail()) { - Log *log = GetLog(LLDBLog::Platform); - LLDB_LOG(log, "PlatformAndroid::{} 'pidof {}' failed: {}", __FUNCTION__, - process_name.c_str(), error.AsCString()); - return 0; - } - - // Parse PIDs from pidof output. - // Note: pidof can return multiple PIDs (space-separated) if multiple - // instances of the same executable are running. - pidof_output = llvm::StringRef(pidof_output).trim().str(); - if (pidof_output.empty()) { - Log *log = GetLog(LLDBLog::Platform); - LLDB_LOGF(log, "PlatformAndroid::%s no process found with name '%s'", - __FUNCTION__, process_name.c_str()); + if (!m_remote_platform_sp) return 0; - } - - // Split the output by whitespace to handle multiple PIDs - llvm::SmallVector<llvm::StringRef, 8> pid_strings; - llvm::StringRef(pidof_output).split(pid_strings, ' ', -1, false); - - Log *log = GetLog(LLDBLog::Platform); - - // Process each PID and gather information - uint32_t num_matches = 0; - for (llvm::StringRef pid_str : pid_strings) { - pid_str = pid_str.trim(); - if (pid_str.empty()) - continue; - - lldb::pid_t pid; - if (!llvm::to_integer(pid_str, pid)) { - LLDB_LOGF(log, "PlatformAndroid::%s failed to parse PID from: '%s'", - __FUNCTION__, pid_str.str().c_str()); - continue; - } - - ProcessInstanceInfo process_info; - process_info.SetProcessID(pid); - process_info.GetExecutableFile().SetFile(process_name, - FileSpec::Style::posix); - - // Populate additional process information - PopulateProcessStatusInfo(pid, process_info); - PopulateProcessCommandLine(pid, process_info); - PopulateProcessArchitecture(pid, process_info); - - // Check if this process matches the criteria - if (match_info.Matches(process_info)) { - proc_infos.push_back(process_info); - num_matches++; - LLDB_LOGF(log, "PlatformAndroid::%s found process '%s' with PID %llu", - __FUNCTION__, process_name.c_str(), - static_cast<unsigned long long>(pid)); + // Android-specific process name handling: + // Apps spawned from zygote initially appear as "app_process" or "zygote" + // in the process list, but their actual package names (e.g., + // "com.example.app") are only available in /proc/PID/cmdline. To support + // name-based matching, we must first fetch cmdline info for all processes, + // then apply the original name filter. + ProcessInstanceInfoMatch broad_match_info = match_info; + broad_match_info.SetNameMatchType(NameMatch::Ignore); + + ProcessInstanceInfoList all_procs; + uint32_t count = + m_remote_platform_sp->FindProcesses(broad_match_info, all_procs); + + if (count > 0) { + Status error; + AdbClientUP adb(GetAdbClient(error)); + if (error.Success()) + SupplementWithCmdlineInfo(all_procs, adb.get()); + + // Apply the original name matching against supplemented process info. + for (auto &proc_info : all_procs) { + if (match_info.Matches(proc_info)) + proc_infos.push_back(proc_info); } } - return num_matches; + return proc_infos.size(); } std::unique_ptr<AdbSyncService> PlatformAndroid::GetSyncService(Status &error) { diff --git a/lldb/source/Plugins/Platform/Android/PlatformAndroid.h b/lldb/source/Plugins/Platform/Android/PlatformAndroid.h index e771c6a..c6a412b 100644 --- a/lldb/source/Plugins/Platform/Android/PlatformAndroid.h +++ b/lldb/source/Plugins/Platform/Android/PlatformAndroid.h @@ -60,7 +60,7 @@ public: uint32_t GetDefaultMemoryCacheLineSize() override; uint32_t FindProcesses(const ProcessInstanceInfoMatch &match_info, - ProcessInstanceInfoList &proc_infos) override; + ProcessInstanceInfoList &process_infos) override; protected: const char *GetCacheHostname() override; @@ -86,17 +86,8 @@ public: protected: virtual std::unique_ptr<AdbSyncService> GetSyncService(Status &error); -private: std::string m_device_id; uint32_t m_sdk_version; - - // Helper functions for process information gathering - void PopulateProcessStatusInfo(lldb::pid_t pid, - ProcessInstanceInfo &process_info); - void PopulateProcessCommandLine(lldb::pid_t pid, - ProcessInstanceInfo &process_info); - void PopulateProcessArchitecture(lldb::pid_t pid, - ProcessInstanceInfo &process_info); }; } // namespace platform_android diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.cpp index 4cfb0a8..47111c9 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.cpp @@ -90,7 +90,7 @@ void PlatformAppleSimulator::GetStatus(Stream &strm) { if (!sdk.empty()) strm << " SDK Path: \"" << sdk << "\"\n"; else - strm << " SDK Path: error: unable to locate SDK\n"; + strm << " SDK Path: <unable to locate SDK>\n"; #if defined(__APPLE__) // This will get called by subclasses, so just output status on the current @@ -420,7 +420,6 @@ Status PlatformAppleSimulator::GetSymbolFile(const FileSpec &platform_file, Status PlatformAppleSimulator::GetSharedModule( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) { // For iOS/tvOS/watchOS, the SDK files are all cached locally on the // host system. So first we ask for the file in the cached SDK, then @@ -432,12 +431,10 @@ Status PlatformAppleSimulator::GetSharedModule( error = GetSymbolFile(platform_file, module_spec.GetUUIDPtr(), platform_module_spec.GetFileSpec()); if (error.Success()) { - error = ResolveExecutable(platform_module_spec, module_sp, - module_search_paths_ptr); + error = ResolveExecutable(platform_module_spec, module_sp); } else { const bool always_create = false; - error = ModuleList::GetSharedModule(module_spec, module_sp, - module_search_paths_ptr, old_modules, + error = ModuleList::GetSharedModule(module_spec, module_sp, old_modules, did_create_ptr, always_create); } if (module_sp) @@ -660,4 +657,3 @@ void PlatformAppleSimulator::Terminate() { PlatformDarwin::Terminate(); } } - diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.h b/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.h index 7fcf2c5..77d2a3b 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformAppleSimulator.h @@ -89,7 +89,6 @@ public: Status GetSharedModule(const ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) override; diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp index 5aad447..bfbd85e 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.cpp @@ -56,7 +56,7 @@ using namespace lldb; using namespace lldb_private; #define OPTTABLE_STR_TABLE_CODE -#include "clang/Driver/Options.inc" +#include "clang/Options/Options.inc" #undef OPTTABLE_STR_TABLE_CODE static Status ExceptionMaskValidator(const char *string, void *unused) { @@ -331,7 +331,6 @@ Status PlatformDarwin::ResolveSymbolFile(Target &target, Status PlatformDarwin::GetSharedModule( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) { Status error; module_sp.reset(); @@ -341,19 +340,22 @@ Status PlatformDarwin::GetSharedModule( // module first. if (m_remote_platform_sp) { error = m_remote_platform_sp->GetSharedModule( - module_spec, process, module_sp, module_search_paths_ptr, old_modules, - did_create_ptr); + module_spec, process, module_sp, old_modules, did_create_ptr); } } if (!module_sp) { // Fall back to the local platform and find the file locally error = Platform::GetSharedModule(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, - did_create_ptr); + old_modules, did_create_ptr); const FileSpec &platform_file = module_spec.GetFileSpec(); - if (!module_sp && module_search_paths_ptr && platform_file) { + // Get module search paths from the target if available. + TargetSP target_sp = module_spec.GetTargetSP(); + FileSpecList module_search_paths; + if (target_sp) + module_search_paths = target_sp->GetExecutableSearchPaths(); + if (!module_sp && !module_search_paths.IsEmpty() && platform_file) { // We can try to pull off part of the file path up to the bundle // directory level and try any module search paths... FileSpec bundle_directory; @@ -362,9 +364,9 @@ Status PlatformDarwin::GetSharedModule( ModuleSpec new_module_spec(module_spec); new_module_spec.GetFileSpec() = bundle_directory; if (Host::ResolveExecutableInBundle(new_module_spec.GetFileSpec())) { - Status new_error(Platform::GetSharedModule( - new_module_spec, process, module_sp, nullptr, old_modules, - did_create_ptr)); + Status new_error(Platform::GetSharedModule(new_module_spec, process, + module_sp, old_modules, + did_create_ptr)); if (module_sp) return new_error; @@ -376,10 +378,10 @@ Status PlatformDarwin::GetSharedModule( const size_t bundle_directory_len = bundle_directory.GetPath(bundle_dir, sizeof(bundle_dir)); char new_path[PATH_MAX]; - size_t num_module_search_paths = module_search_paths_ptr->GetSize(); + size_t num_module_search_paths = module_search_paths.GetSize(); for (size_t i = 0; i < num_module_search_paths; ++i) { const size_t search_path_len = - module_search_paths_ptr->GetFileSpecAtIndex(i).GetPath( + module_search_paths.GetFileSpecAtIndex(i).GetPath( new_path, sizeof(new_path)); if (search_path_len < sizeof(new_path)) { snprintf(new_path + search_path_len, @@ -390,7 +392,7 @@ Status PlatformDarwin::GetSharedModule( ModuleSpec new_module_spec(module_spec); new_module_spec.GetFileSpec() = new_file_spec; Status new_error(Platform::GetSharedModule( - new_module_spec, process, module_sp, nullptr, old_modules, + new_module_spec, process, module_sp, old_modules, did_create_ptr)); if (module_sp) { @@ -1122,7 +1124,7 @@ void PlatformDarwin::AddClangModuleCompilationOptionsForSDKType( #define OPTION(PREFIX_OFFSET, NAME_OFFSET, VAR, ...) \ llvm::StringRef opt_##VAR = OptionStrTable[NAME_OFFSET]; \ (void)opt_##VAR; -#include "clang/Driver/Options.inc" +#include "clang/Options/Options.inc" #undef OPTION minimum_version_option << '-'; switch (sdk_type) { @@ -1303,12 +1305,15 @@ PlatformDarwin::LaunchProcess(lldb_private::ProcessLaunchInfo &launch_info) { lldb_private::Status PlatformDarwin::FindBundleBinaryInExecSearchPaths( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) { const FileSpec &platform_file = module_spec.GetFileSpec(); - // See if the file is present in any of the module_search_paths_ptr + TargetSP target_sp = module_spec.GetTargetSP(); + FileSpecList module_search_paths; + if (target_sp) + module_search_paths = target_sp->GetExecutableSearchPaths(); + // See if the file is present in any of the module_search_paths // directories. - if (!module_sp && module_search_paths_ptr && platform_file) { + if (!module_sp && !module_search_paths.IsEmpty() && platform_file) { // create a vector of all the file / directory names in platform_file e.g. // this might be // /System/Library/PrivateFrameworks/UIFoundation.framework/UIFoundation @@ -1322,21 +1327,21 @@ lldb_private::Status PlatformDarwin::FindBundleBinaryInExecSearchPaths( std::reverse(path_parts.begin(), path_parts.end()); const size_t path_parts_size = path_parts.size(); - size_t num_module_search_paths = module_search_paths_ptr->GetSize(); + size_t num_module_search_paths = module_search_paths.GetSize(); for (size_t i = 0; i < num_module_search_paths; ++i) { Log *log_verbose = GetLog(LLDBLog::Host); LLDB_LOGF( log_verbose, "PlatformRemoteDarwinDevice::GetSharedModule searching for binary in " "search-path %s", - module_search_paths_ptr->GetFileSpecAtIndex(i).GetPath().c_str()); + module_search_paths.GetFileSpecAtIndex(i).GetPath().c_str()); // Create a new FileSpec with this module_search_paths_ptr plus just the // filename ("UIFoundation"), then the parent dir plus filename // ("UIFoundation.framework/UIFoundation") etc - up to four names (to // handle "Foo.framework/Contents/MacOS/Foo") for (size_t j = 0; j < 4 && j < path_parts_size - 1; ++j) { - FileSpec path_to_try(module_search_paths_ptr->GetFileSpecAtIndex(i)); + FileSpec path_to_try(module_search_paths.GetFileSpecAtIndex(i)); // Add the components backwards. For // .../PrivateFrameworks/UIFoundation.framework/UIFoundation path_parts @@ -1356,9 +1361,9 @@ lldb_private::Status PlatformDarwin::FindBundleBinaryInExecSearchPaths( if (FileSystem::Instance().Exists(path_to_try)) { ModuleSpec new_module_spec(module_spec); new_module_spec.GetFileSpec() = path_to_try; - Status new_error( - Platform::GetSharedModule(new_module_spec, process, module_sp, - nullptr, old_modules, did_create_ptr)); + Status new_error(Platform::GetSharedModule(new_module_spec, process, + module_sp, old_modules, + did_create_ptr)); if (module_sp) { module_sp->SetPlatformFileSpec(path_to_try); diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h index f8a62ce..82e69e3 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwin.h @@ -73,7 +73,6 @@ public: Status GetSharedModule(const ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) override; @@ -189,7 +188,7 @@ protected: Status FindBundleBinaryInExecSearchPaths( const ModuleSpec &module_spec, Process *process, - lldb::ModuleSP &module_sp, const FileSpecList *module_search_paths_ptr, + lldb::ModuleSP &module_sp, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr); // The OSType where lldb is running. diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp index 68ef817..a72d94e 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.cpp @@ -295,7 +295,6 @@ BringInRemoteFile(Platform *platform, lldb_private::Status PlatformDarwinDevice::GetSharedModuleWithLocalCache( const lldb_private::ModuleSpec &module_spec, lldb::ModuleSP &module_sp, - const lldb_private::FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) { Log *log = GetLog(LLDBLog::Platform); @@ -329,8 +328,7 @@ lldb_private::Status PlatformDarwinDevice::GetSharedModuleWithLocalCache( ModuleSpec shared_cache_spec(module_spec.GetFileSpec(), image_info.uuid, image_info.data_sp); err = ModuleList::GetSharedModule(shared_cache_spec, module_sp, - module_search_paths_ptr, old_modules, - did_create_ptr); + old_modules, did_create_ptr); if (module_sp) { LLDB_LOGF(log, "[%s] module %s was found in the in-memory shared cache", (IsHost() ? "host" : "remote"), @@ -348,8 +346,7 @@ lldb_private::Status PlatformDarwinDevice::GetSharedModuleWithLocalCache( FileSystem::Instance().Resolve(device_support_spec); if (FileSystem::Instance().Exists(device_support_spec)) { ModuleSpec local_spec(device_support_spec, module_spec.GetUUID()); - err = ModuleList::GetSharedModule(local_spec, module_sp, - module_search_paths_ptr, old_modules, + err = ModuleList::GetSharedModule(local_spec, module_sp, old_modules, did_create_ptr); if (module_sp) { LLDB_LOGF(log, @@ -363,8 +360,7 @@ lldb_private::Status PlatformDarwinDevice::GetSharedModuleWithLocalCache( } } - err = ModuleList::GetSharedModule(module_spec, module_sp, - module_search_paths_ptr, old_modules, + err = ModuleList::GetSharedModule(module_spec, module_sp, old_modules, did_create_ptr); if (module_sp) return err; diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.h index e1eba08f..e0142ab 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinDevice.h @@ -26,7 +26,6 @@ public: protected: virtual Status GetSharedModuleWithLocalCache( const ModuleSpec &module_spec, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr); struct SDKDirectoryInfo { diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.cpp index 07c5a52..04e87b9d 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.cpp @@ -719,7 +719,6 @@ void PlatformDarwinKernel::UpdateKextandKernelsLocalScan() { Status PlatformDarwinKernel::GetSharedModule( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) { Status error; module_sp.reset(); @@ -734,14 +733,12 @@ Status PlatformDarwinKernel::GetSharedModule( // UUID search can get here with no name - and it may be a kernel. if (kext_bundle_id == "mach_kernel" || kext_bundle_id.empty()) { error = GetSharedModuleKernel(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, - did_create_ptr); + old_modules, did_create_ptr); if (error.Success() && module_sp) { return error; } } else { - return GetSharedModuleKext(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, + return GetSharedModuleKext(module_spec, process, module_sp, old_modules, did_create_ptr); } } @@ -749,13 +746,11 @@ Status PlatformDarwinKernel::GetSharedModule( // Give the generic methods, including possibly calling into DebugSymbols // framework on macOS systems, a chance. return PlatformDarwin::GetSharedModule(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, - did_create_ptr); + old_modules, did_create_ptr); } Status PlatformDarwinKernel::GetSharedModuleKext( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) { Status error; module_sp.reset(); @@ -782,8 +777,7 @@ Status PlatformDarwinKernel::GetSharedModuleKext( // Give the generic methods, including possibly calling into DebugSymbols // framework on macOS systems, a chance. error = PlatformDarwin::GetSharedModule(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, - did_create_ptr); + old_modules, did_create_ptr); if (error.Success() && module_sp.get()) { return error; } @@ -793,7 +787,6 @@ Status PlatformDarwinKernel::GetSharedModuleKext( Status PlatformDarwinKernel::GetSharedModuleKernel( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) { assert(module_sp.get() == nullptr); UpdateKextandKernelsLocalScan(); @@ -848,8 +841,7 @@ Status PlatformDarwinKernel::GetSharedModuleKernel( // Give the generic methods, including possibly calling into DebugSymbols // framework on macOS systems, a chance. return PlatformDarwin::GetSharedModule(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, - did_create_ptr); + old_modules, did_create_ptr); } std::vector<lldb_private::FileSpec> @@ -888,8 +880,8 @@ Status PlatformDarwinKernel::ExamineKextForMatchingUUID( ModuleSP module_sp(new Module(exe_spec)); if (module_sp && module_sp->GetObjectFile() && module_sp->MatchesModuleSpec(exe_spec)) { - Status error = ModuleList::GetSharedModule(exe_spec, exe_module_sp, - NULL, NULL, NULL); + Status error = + ModuleList::GetSharedModule(exe_spec, exe_module_sp, NULL, NULL); if (exe_module_sp && exe_module_sp->GetObjectFile()) { return error; } diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.h b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.h index 9db9c00..b5cf701 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformDarwinKernel.h @@ -60,7 +60,6 @@ public: Status GetSharedModule(const ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) override; @@ -142,14 +141,14 @@ protected: Status GetSharedModuleKext(const ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr); - Status GetSharedModuleKernel( - const ModuleSpec &module_spec, Process *process, - lldb::ModuleSP &module_sp, const FileSpecList *module_search_paths_ptr, - llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr); + Status + GetSharedModuleKernel(const ModuleSpec &module_spec, Process *process, + lldb::ModuleSP &module_sp, + llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, + bool *did_create_ptr); Status ExamineKextForMatchingUUID(const FileSpec &kext_bundle_path, const UUID &uuid, const ArchSpec &arch, diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.cpp index dad6dcd..e6ea75a 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.cpp @@ -182,10 +182,8 @@ PlatformMacOSX::GetSupportedArchitectures(const ArchSpec &process_host_arch) { lldb_private::Status PlatformMacOSX::GetSharedModule( const lldb_private::ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const lldb_private::FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) { Status error = GetSharedModuleWithLocalCache(module_spec, module_sp, - module_search_paths_ptr, old_modules, did_create_ptr); if (module_sp) { @@ -199,9 +197,9 @@ lldb_private::Status PlatformMacOSX::GetSharedModule( lldb::ModuleSP x86_64_module_sp; llvm::SmallVector<lldb::ModuleSP, 1> old_x86_64_modules; bool did_create = false; - Status x86_64_error = GetSharedModuleWithLocalCache( - module_spec_x86_64, x86_64_module_sp, module_search_paths_ptr, - &old_x86_64_modules, &did_create); + Status x86_64_error = + GetSharedModuleWithLocalCache(module_spec_x86_64, x86_64_module_sp, + &old_x86_64_modules, &did_create); if (x86_64_module_sp && x86_64_module_sp->GetObjectFile()) { module_sp = x86_64_module_sp; if (old_modules) @@ -217,7 +215,6 @@ lldb_private::Status PlatformMacOSX::GetSharedModule( if (!module_sp) { error = FindBundleBinaryInExecSearchPaths(module_spec, process, module_sp, - module_search_paths_ptr, old_modules, did_create_ptr); } return error; diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.h b/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.h index be84485..9555b16 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformMacOSX.h @@ -48,7 +48,6 @@ public: Status GetSharedModule(const ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) override; diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.cpp b/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.cpp index b83d07b..53fab93 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.cpp +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.cpp @@ -53,7 +53,7 @@ void PlatformRemoteDarwinDevice::GetStatus(Stream &strm) { if (sdk_directory) strm.Printf(" SDK Path: \"%s\"\n", sdk_directory); else - strm.PutCString(" SDK Path: error: unable to locate SDK\n"); + strm.PutCString(" SDK Path: <unable to locate SDK>\n"); const uint32_t num_sdk_infos = m_sdk_directory_infos.size(); for (uint32_t i = 0; i < num_sdk_infos; ++i) { @@ -158,7 +158,6 @@ Status PlatformRemoteDarwinDevice::GetSymbolFile(const FileSpec &platform_file, Status PlatformRemoteDarwinDevice::GetSharedModule( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) { // For iOS, the SDK files are all cached locally on the host system. So first // we ask for the file in the cached SDK, then we attempt to get a shared @@ -185,7 +184,7 @@ Status PlatformRemoteDarwinDevice::GetSharedModule( if (GetFileInSDK(platform_file_path, connected_sdk_idx, platform_module_spec.GetFileSpec())) { module_sp.reset(); - error = ResolveExecutable(platform_module_spec, module_sp, nullptr); + error = ResolveExecutable(platform_module_spec, module_sp); if (module_sp) { m_last_module_sdk_idx = connected_sdk_idx; error.Clear(); @@ -202,7 +201,7 @@ Status PlatformRemoteDarwinDevice::GetSharedModule( if (GetFileInSDK(platform_file_path, m_last_module_sdk_idx, platform_module_spec.GetFileSpec())) { module_sp.reset(); - error = ResolveExecutable(platform_module_spec, module_sp, nullptr); + error = ResolveExecutable(platform_module_spec, module_sp); if (module_sp) { error.Clear(); return error; @@ -224,7 +223,7 @@ Status PlatformRemoteDarwinDevice::GetSharedModule( if (GetFileInSDK(platform_file_path, current_sdk_idx, platform_module_spec.GetFileSpec())) { module_sp.reset(); - error = ResolveExecutable(platform_module_spec, module_sp, nullptr); + error = ResolveExecutable(platform_module_spec, module_sp); if (module_sp) { m_last_module_sdk_idx = current_sdk_idx; error.Clear(); @@ -245,7 +244,7 @@ Status PlatformRemoteDarwinDevice::GetSharedModule( platform_module_spec.GetFileSpec())) { // printf ("sdk[%u]: '%s'\n", sdk_idx, local_file.GetPath().c_str()); - error = ResolveExecutable(platform_module_spec, module_sp, nullptr); + error = ResolveExecutable(platform_module_spec, module_sp); if (module_sp) { // Remember the index of the last SDK that we found a file in in case // the wrong SDK was selected. @@ -261,8 +260,7 @@ Status PlatformRemoteDarwinDevice::GetSharedModule( // This may not be an SDK-related module. Try whether we can bring in the // thing to our local cache. - error = GetSharedModuleWithLocalCache(module_spec, module_sp, - module_search_paths_ptr, old_modules, + error = GetSharedModuleWithLocalCache(module_spec, module_sp, old_modules, did_create_ptr); if (error.Success()) return error; @@ -271,15 +269,13 @@ Status PlatformRemoteDarwinDevice::GetSharedModule( // directories. if (!module_sp) error = PlatformDarwin::FindBundleBinaryInExecSearchPaths( - module_spec, process, module_sp, module_search_paths_ptr, old_modules, - did_create_ptr); + module_spec, process, module_sp, old_modules, did_create_ptr); if (error.Success()) return error; const bool always_create = false; - error = ModuleList::GetSharedModule(module_spec, module_sp, - module_search_paths_ptr, old_modules, + error = ModuleList::GetSharedModule(module_spec, module_sp, old_modules, did_create_ptr, always_create); if (module_sp) diff --git a/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.h b/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.h index 557f487..4abd74e 100644 --- a/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.h +++ b/lldb/source/Plugins/Platform/MacOSX/PlatformRemoteDarwinDevice.h @@ -47,7 +47,6 @@ public: Status GetSharedModule(const ModuleSpec &module_spec, Process *process, lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) override; diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp index 0fecefe..4cc39f9 100644 --- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp +++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp @@ -79,8 +79,10 @@ namespace lldb_private { ProcessSP ProcessWindows::CreateInstance(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, - const FileSpec *, + const FileSpec *crash_file_path, bool can_connect) { + if (crash_file_path) + return nullptr; // Cannot create a Windows process from a crash_file. return ProcessSP(new ProcessWindows(target_sp, listener_sp)); } diff --git a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp index b7029fb..f8e33ea 100644 --- a/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp +++ b/lldb/source/Plugins/Process/elf-core/ProcessElfCore.cpp @@ -84,8 +84,9 @@ bool ProcessElfCore::CanDebug(lldb::TargetSP target_sp, // For now we are just making sure the file exists for a given module if (!m_core_module_sp && FileSystem::Instance().Exists(m_core_file)) { ModuleSpec core_module_spec(m_core_file, target_sp->GetArchitecture()); + core_module_spec.SetTarget(target_sp); Status error(ModuleList::GetSharedModule(core_module_spec, m_core_module_sp, - nullptr, nullptr, nullptr)); + nullptr, nullptr)); if (m_core_module_sp) { ObjectFile *core_objfile = m_core_module_sp->GetObjectFile(); if (core_objfile && core_objfile->GetType() == ObjectFile::eTypeCoreFile) diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 3c4d9a1..cde68b5 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -210,11 +210,9 @@ void ProcessGDBRemote::Terminate() { lldb::ProcessSP ProcessGDBRemote::CreateInstance( lldb::TargetSP target_sp, ListenerSP listener_sp, const FileSpec *crash_file_path, bool can_connect) { - lldb::ProcessSP process_sp; - if (crash_file_path == nullptr) - process_sp = std::shared_ptr<ProcessGDBRemote>( - new ProcessGDBRemote(target_sp, listener_sp)); - return process_sp; + if (crash_file_path) + return nullptr; // Cannot create a GDBRemote process from a crash_file. + return lldb::ProcessSP(new ProcessGDBRemote(target_sp, listener_sp)); } void ProcessGDBRemote::DumpPluginHistory(Stream &s) { @@ -441,8 +439,16 @@ void ProcessGDBRemote::BuildDynamicRegisterInfo(bool force) { if (!arch_to_use.IsValid()) arch_to_use = target_arch; - if (GetGDBServerRegisterInfo(arch_to_use)) + llvm::Error register_info_err = GetGDBServerRegisterInfo(arch_to_use); + if (!register_info_err) { + // We got the registers from target XML. return; + } + + Log *log = GetLog(GDBRLog::Process); + LLDB_LOG_ERROR(log, std::move(register_info_err), + "Failed to read register information from target XML: {0}"); + LLDB_LOG(log, "Now trying to use qRegisterInfo instead."); char packet[128]; std::vector<DynamicRegisterInfo::Register> registers; @@ -541,7 +547,23 @@ void ProcessGDBRemote::BuildDynamicRegisterInfo(bool force) { assert(reg_info.byte_size != 0); registers.push_back(reg_info); } else { - break; // ensure exit before reg_num is incremented + // Only warn if we were offered Target XML and could not use it, and + // the qRegisterInfo fallback failed. This is something a user could + // take action on by getting an lldb with libxml2. + // + // It's possible we weren't offered Target XML and qRegisterInfo failed, + // but there's no much a user can do about that. It may be the intended + // way the debug stub works, so we do not warn for that case. + if (response_type == StringExtractorGDBRemote::eUnsupported && + m_gdb_comm.GetQXferFeaturesReadSupported() && + !XMLDocument::XMLEnabled()) { + Debugger::ReportWarning( + "the debug server supports Target Description XML but LLDB does " + "not have XML parsing enabled. Using \"qRegisterInfo\" was also " + "not possible. Register information may be incorrect or missing.", + GetTarget().GetDebugger().GetID()); + } + break; } } else { break; @@ -5137,14 +5159,19 @@ void ProcessGDBRemote::AddRemoteRegisters( // query the target of gdb-remote for extended target information returns // true on success (got register definitions), false on failure (did not). -bool ProcessGDBRemote::GetGDBServerRegisterInfo(ArchSpec &arch_to_use) { - // Make sure LLDB has an XML parser it can use first - if (!XMLDocument::XMLEnabled()) - return false; - - // check that we have extended feature read support +llvm::Error ProcessGDBRemote::GetGDBServerRegisterInfo(ArchSpec &arch_to_use) { + // If the remote does not offer XML, does not matter if we would have been + // able to parse it. if (!m_gdb_comm.GetQXferFeaturesReadSupported()) - return false; + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "the debug server does not support \"qXfer:features:read\""); + + if (!XMLDocument::XMLEnabled()) + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "the debug server supports \"qXfer:features:read\", but LLDB does not " + "have XML parsing enabled (check LLLDB_ENABLE_LIBXML2)"); // These hold register type information for the whole of target.xml. // target.xml may include further documents that @@ -5161,7 +5188,11 @@ bool ProcessGDBRemote::GetGDBServerRegisterInfo(ArchSpec &arch_to_use) { !registers.empty()) AddRemoteRegisters(registers, arch_to_use); - return m_register_info_sp->GetNumRegisters() > 0; + return m_register_info_sp->GetNumRegisters() > 0 + ? llvm::ErrorSuccess() + : llvm::createStringError( + llvm::inconvertibleErrorCode(), + "the debug server did not describe any registers"); } llvm::Expected<LoadedModuleInfoList> ProcessGDBRemote::GetLoadedModuleList() { diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index eb33b52..b7e8777 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -416,7 +416,7 @@ protected: void AddRemoteRegisters(std::vector<DynamicRegisterInfo::Register> ®isters, const ArchSpec &arch_to_use); // Query remote GDBServer for register information - bool GetGDBServerRegisterInfo(ArchSpec &arch); + llvm::Error GetGDBServerRegisterInfo(ArchSpec &arch); lldb::ModuleSP LoadModuleAtAddress(const FileSpec &file, lldb::addr_t link_map, diff --git a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp index a780b3f..83d684e 100644 --- a/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp +++ b/lldb/source/Plugins/Process/mach-core/ProcessMachCore.cpp @@ -95,8 +95,9 @@ bool ProcessMachCore::CanDebug(lldb::TargetSP target_sp, // header but we should still try to use it - // ModuleSpecList::FindMatchingModuleSpec enforces a strict arch mach. ModuleSpec core_module_spec(m_core_file); + core_module_spec.SetTarget(target_sp); Status error(ModuleList::GetSharedModule(core_module_spec, m_core_module_sp, - nullptr, nullptr, nullptr)); + nullptr, nullptr)); if (m_core_module_sp) { ObjectFile *core_objfile = m_core_module_sp->GetObjectFile(); diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp index 6519df9..70ce101 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp @@ -7,42 +7,77 @@ //===----------------------------------------------------------------------===// #include "ScriptedFrame.h" - +#include "Plugins/Process/Utility/RegisterContextMemory.h" + +#include "lldb/Core/Address.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleList.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/SymbolFile.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/Thread.h" #include "lldb/Utility/DataBufferHeap.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/StructuredData.h" using namespace lldb; using namespace lldb_private; +char ScriptedFrame::ID; + void ScriptedFrame::CheckInterpreterAndScriptObject() const { lldbassert(m_script_object_sp && "Invalid Script Object."); lldbassert(GetInterface() && "Invalid Scripted Frame Interface."); } llvm::Expected<std::shared_ptr<ScriptedFrame>> -ScriptedFrame::Create(ScriptedThread &thread, +ScriptedFrame::Create(ThreadSP thread_sp, + ScriptedThreadInterfaceSP scripted_thread_interface_sp, StructuredData::DictionarySP args_sp, StructuredData::Generic *script_object) { - if (!thread.IsValid()) - return llvm::createStringError("Invalid scripted thread."); + if (!thread_sp || !thread_sp->IsValid()) + return llvm::createStringError("invalid thread"); + + ProcessSP process_sp = thread_sp->GetProcess(); + if (!process_sp || !process_sp->IsValid()) + return llvm::createStringError("invalid process"); - thread.CheckInterpreterAndScriptObject(); + ScriptInterpreter *script_interp = + process_sp->GetTarget().GetDebugger().GetScriptInterpreter(); + if (!script_interp) + return llvm::createStringError("no script interpreter"); - auto scripted_frame_interface = - thread.GetInterface()->CreateScriptedFrameInterface(); + auto scripted_frame_interface = script_interp->CreateScriptedFrameInterface(); if (!scripted_frame_interface) return llvm::createStringError("failed to create scripted frame interface"); llvm::StringRef frame_class_name; if (!script_object) { - std::optional<std::string> class_name = - thread.GetInterface()->GetScriptedFramePluginName(); - if (!class_name || class_name->empty()) + // If no script object is provided and we have a scripted thread interface, + // try to get the frame class name from it. + if (scripted_thread_interface_sp) { + std::optional<std::string> class_name = + scripted_thread_interface_sp->GetScriptedFramePluginName(); + if (!class_name || class_name->empty()) + return llvm::createStringError( + "failed to get scripted frame class name"); + frame_class_name = *class_name; + } else { return llvm::createStringError( - "failed to get scripted thread class name"); - frame_class_name = *class_name; + "no script object provided and no scripted thread interface"); + } } - ExecutionContext exe_ctx(thread); + ExecutionContext exe_ctx(thread_sp); auto obj_or_err = scripted_frame_interface->CreatePluginObject( frame_class_name, exe_ctx, args_sp, script_object); @@ -62,66 +97,43 @@ ScriptedFrame::Create(ScriptedThread &thread, SymbolContext sc; Address symbol_addr; if (pc != LLDB_INVALID_ADDRESS) { - symbol_addr.SetLoadAddress(pc, &thread.GetProcess()->GetTarget()); + symbol_addr.SetLoadAddress(pc, &process_sp->GetTarget()); symbol_addr.CalculateSymbolContext(&sc); } std::optional<SymbolContext> maybe_sym_ctx = scripted_frame_interface->GetSymbolContext(); - if (maybe_sym_ctx) { + if (maybe_sym_ctx) sc = *maybe_sym_ctx; - } - - StructuredData::DictionarySP reg_info = - scripted_frame_interface->GetRegisterInfo(); - - if (!reg_info) - return llvm::createStringError( - "failed to get scripted thread registers info"); - - std::shared_ptr<DynamicRegisterInfo> register_info_sp = - DynamicRegisterInfo::Create( - *reg_info, thread.GetProcess()->GetTarget().GetArchitecture()); lldb::RegisterContextSP reg_ctx_sp; - - std::optional<std::string> reg_data = - scripted_frame_interface->GetRegisterContext(); - if (reg_data) { - DataBufferSP data_sp( - std::make_shared<DataBufferHeap>(reg_data->c_str(), reg_data->size())); - - if (!data_sp->GetByteSize()) - return llvm::createStringError("failed to copy raw registers data"); - - std::shared_ptr<RegisterContextMemory> reg_ctx_memory = - std::make_shared<RegisterContextMemory>( - thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS); - if (!reg_ctx_memory) - return llvm::createStringError("failed to create a register context."); - - reg_ctx_memory->SetAllRegisterData(data_sp); - reg_ctx_sp = reg_ctx_memory; - } - - return std::make_shared<ScriptedFrame>( - thread, scripted_frame_interface, frame_id, pc, sc, reg_ctx_sp, - register_info_sp, owned_script_object_sp); + auto regs_or_err = + CreateRegisterContext(*scripted_frame_interface, *thread_sp, frame_id); + if (!regs_or_err) + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), regs_or_err.takeError(), "{0}"); + else + reg_ctx_sp = *regs_or_err; + + return std::make_shared<ScriptedFrame>(thread_sp, scripted_frame_interface, + frame_id, pc, sc, reg_ctx_sp, + owned_script_object_sp); } -ScriptedFrame::ScriptedFrame(ScriptedThread &thread, +ScriptedFrame::ScriptedFrame(ThreadSP thread_sp, ScriptedFrameInterfaceSP interface_sp, lldb::user_id_t id, lldb::addr_t pc, SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp, - std::shared_ptr<DynamicRegisterInfo> reg_info_sp, StructuredData::GenericSP script_object_sp) - : StackFrame(thread.shared_from_this(), /*frame_idx=*/id, + : StackFrame(thread_sp, /*frame_idx=*/id, /*concrete_frame_idx=*/id, /*reg_context_sp=*/reg_ctx_sp, /*cfa=*/0, /*pc=*/pc, /*behaves_like_zeroth_frame=*/!id, /*symbol_ctx=*/&sym_ctx), m_scripted_frame_interface_sp(interface_sp), - m_script_object_sp(script_object_sp), m_register_info_sp(reg_info_sp) {} + m_script_object_sp(script_object_sp) { + // FIXME: This should be part of the base class constructor. + m_stack_frame_kind = StackFrame::Kind::Synthetic; +} ScriptedFrame::~ScriptedFrame() {} @@ -129,7 +141,7 @@ const char *ScriptedFrame::GetFunctionName() { CheckInterpreterAndScriptObject(); std::optional<std::string> function_name = GetInterface()->GetFunctionName(); if (!function_name) - return nullptr; + return StackFrame::GetFunctionName(); return ConstString(function_name->c_str()).AsCString(); } @@ -138,7 +150,7 @@ const char *ScriptedFrame::GetDisplayFunctionName() { std::optional<std::string> function_name = GetInterface()->GetDisplayFunctionName(); if (!function_name) - return nullptr; + return StackFrame::GetDisplayFunctionName(); return ConstString(function_name->c_str()).AsCString(); } @@ -157,35 +169,99 @@ lldb::ScriptedFrameInterfaceSP ScriptedFrame::GetInterface() const { std::shared_ptr<DynamicRegisterInfo> ScriptedFrame::GetDynamicRegisterInfo() { CheckInterpreterAndScriptObject(); - if (!m_register_info_sp) { - StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo(); + StructuredData::DictionarySP reg_info = GetInterface()->GetRegisterInfo(); + + Status error; + if (!reg_info) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr<DynamicRegisterInfo>>( + LLVM_PRETTY_FUNCTION, "failed to get scripted frame registers info", + error, LLDBLog::Thread); + + ThreadSP thread_sp = m_thread_wp.lock(); + if (!thread_sp || !thread_sp->IsValid()) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr<DynamicRegisterInfo>>( + LLVM_PRETTY_FUNCTION, + "failed to get scripted frame registers info: invalid thread", error, + LLDBLog::Thread); + + ProcessSP process_sp = thread_sp->GetProcess(); + if (!process_sp || !process_sp->IsValid()) + return ScriptedInterface::ErrorWithMessage< + std::shared_ptr<DynamicRegisterInfo>>( + LLVM_PRETTY_FUNCTION, + "failed to get scripted frame registers info: invalid process", error, + LLDBLog::Thread); + + return DynamicRegisterInfo::Create(*reg_info, + process_sp->GetTarget().GetArchitecture()); +} + +llvm::Expected<lldb::RegisterContextSP> +ScriptedFrame::CreateRegisterContext(ScriptedFrameInterface &interface, + Thread &thread, lldb::user_id_t frame_id) { + StructuredData::DictionarySP reg_info = interface.GetRegisterInfo(); + if (!reg_info) + return llvm::createStringError( + "failed to get scripted frame registers info"); + + std::shared_ptr<DynamicRegisterInfo> register_info_sp = + DynamicRegisterInfo::Create( + *reg_info, thread.GetProcess()->GetTarget().GetArchitecture()); + + lldb::RegisterContextSP reg_ctx_sp; + + std::optional<std::string> reg_data = interface.GetRegisterContext(); + if (!reg_data) + return llvm::createStringError( + "failed to get scripted frame registers data"); + + DataBufferSP data_sp( + std::make_shared<DataBufferHeap>(reg_data->c_str(), reg_data->size())); + + if (!data_sp->GetByteSize()) + return llvm::createStringError("failed to copy raw registers data"); + + std::shared_ptr<RegisterContextMemory> reg_ctx_memory = + std::make_shared<RegisterContextMemory>( + thread, frame_id, *register_info_sp, LLDB_INVALID_ADDRESS); + + reg_ctx_memory->SetAllRegisterData(data_sp); + reg_ctx_sp = reg_ctx_memory; + + return reg_ctx_sp; +} + +lldb::RegisterContextSP ScriptedFrame::GetRegisterContext() { + if (!m_reg_context_sp) { Status error; - if (!reg_info) - return ScriptedInterface::ErrorWithMessage< - std::shared_ptr<DynamicRegisterInfo>>( - LLVM_PRETTY_FUNCTION, "Failed to get scripted frame registers info.", + if (!m_scripted_frame_interface_sp) + return ScriptedInterface::ErrorWithMessage<RegisterContextSP>( + LLVM_PRETTY_FUNCTION, + "failed to get scripted frame registers context: invalid interface", error, LLDBLog::Thread); - ThreadSP thread_sp = m_thread_wp.lock(); - if (!thread_sp || !thread_sp->IsValid()) - return ScriptedInterface::ErrorWithMessage< - std::shared_ptr<DynamicRegisterInfo>>( + ThreadSP thread_sp = GetThread(); + if (!thread_sp) + return ScriptedInterface::ErrorWithMessage<RegisterContextSP>( LLVM_PRETTY_FUNCTION, - "Failed to get scripted frame registers info: invalid thread.", error, - LLDBLog::Thread); + "failed to get scripted frame registers context: invalid thread", + error, LLDBLog::Thread); - ProcessSP process_sp = thread_sp->GetProcess(); - if (!process_sp || !process_sp->IsValid()) - return ScriptedInterface::ErrorWithMessage< - std::shared_ptr<DynamicRegisterInfo>>( + auto regs_or_err = CreateRegisterContext(*m_scripted_frame_interface_sp, + *thread_sp, GetFrameIndex()); + if (!regs_or_err) { + error = Status::FromError(regs_or_err.takeError()); + return ScriptedInterface::ErrorWithMessage<RegisterContextSP>( LLVM_PRETTY_FUNCTION, - "Failed to get scripted frame registers info: invalid process.", - error, LLDBLog::Thread); + "failed to get scripted frame registers context", error, + LLDBLog::Thread); + } - m_register_info_sp = DynamicRegisterInfo::Create( - *reg_info, process_sp->GetTarget().GetArchitecture()); + m_reg_context_sp = *regs_or_err; } - return m_register_info_sp; + return m_reg_context_sp; } diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h index 6e01e2f..0545548 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h +++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h @@ -9,33 +9,50 @@ #ifndef LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H #define LLDB_SOURCE_PLUGINS_SCRIPTED_FRAME_H -#include "Plugins/Process/Utility/RegisterContextMemory.h" #include "ScriptedThread.h" -#include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Target/DynamicRegisterInfo.h" #include "lldb/Target/StackFrame.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" +#include <memory> #include <string> namespace lldb_private { -class ScriptedThread; -} - -namespace lldb_private { class ScriptedFrame : public lldb_private::StackFrame { public: - ScriptedFrame(ScriptedThread &thread, + ScriptedFrame(lldb::ThreadSP thread_sp, lldb::ScriptedFrameInterfaceSP interface_sp, lldb::user_id_t frame_idx, lldb::addr_t pc, SymbolContext &sym_ctx, lldb::RegisterContextSP reg_ctx_sp, - std::shared_ptr<DynamicRegisterInfo> reg_info_sp, StructuredData::GenericSP script_object_sp = nullptr); ~ScriptedFrame() override; + /// Create a ScriptedFrame from a object instanciated in the script + /// interpreter. + /// + /// \param[in] thread_sp + /// The thread this frame belongs to. + /// + /// \param[in] scripted_thread_interface_sp + /// The scripted thread interface (needed for ScriptedThread + /// compatibility). Can be nullptr for frames on real threads. + /// + /// \param[in] args_sp + /// Arguments to pass to the frame creation. + /// + /// \param[in] script_object + /// The optional script object representing this frame. + /// + /// \return + /// An Expected containing the ScriptedFrame shared pointer if successful, + /// otherwise an error. static llvm::Expected<std::shared_ptr<ScriptedFrame>> - Create(ScriptedThread &thread, StructuredData::DictionarySP args_sp, + Create(lldb::ThreadSP thread_sp, + lldb::ScriptedThreadInterfaceSP scripted_thread_interface_sp, + StructuredData::DictionarySP args_sp, StructuredData::Generic *script_object = nullptr); bool IsInlined() override; @@ -44,9 +61,19 @@ public: const char *GetFunctionName() override; const char *GetDisplayFunctionName() override; + lldb::RegisterContextSP GetRegisterContext() override; + + bool isA(const void *ClassID) const override { + return ClassID == &ID || StackFrame::isA(ClassID); + } + static bool classof(const StackFrame *obj) { return obj->isA(&ID); } + private: void CheckInterpreterAndScriptObject() const; lldb::ScriptedFrameInterfaceSP GetInterface() const; + static llvm::Expected<lldb::RegisterContextSP> + CreateRegisterContext(ScriptedFrameInterface &interface, Thread &thread, + lldb::user_id_t frame_id); ScriptedFrame(const ScriptedFrame &) = delete; const ScriptedFrame &operator=(const ScriptedFrame &) = delete; @@ -55,7 +82,8 @@ private: lldb::ScriptedFrameInterfaceSP m_scripted_frame_interface_sp; lldb_private::StructuredData::GenericSP m_script_object_sp; - std::shared_ptr<DynamicRegisterInfo> m_register_info_sp; + + static char ID; }; } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp index 491efac..1dd9c48 100644 --- a/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp +++ b/lldb/source/Plugins/Process/scripted/ScriptedThread.cpp @@ -210,7 +210,7 @@ bool ScriptedThread::LoadArtificialStackFrames() { SymbolContext sc; symbol_addr.CalculateSymbolContext(&sc); - return std::make_shared<StackFrame>(this->shared_from_this(), idx, idx, cfa, + return std::make_shared<StackFrame>(shared_from_this(), idx, idx, cfa, cfa_is_valid, pc, StackFrame::Kind::Synthetic, artificial, behaves_like_zeroth_frame, &sc); @@ -231,8 +231,8 @@ bool ScriptedThread::LoadArtificialStackFrames() { return error.ToError(); } - auto frame_or_error = - ScriptedFrame::Create(*this, nullptr, object_sp->GetAsGeneric()); + auto frame_or_error = ScriptedFrame::Create( + shared_from_this(), GetInterface(), nullptr, object_sp->GetAsGeneric()); if (!frame_or_error) { ScriptedInterface::ErrorWithMessage<bool>( diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt index 0910357..50569cd 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/CMakeLists.txt @@ -23,6 +23,7 @@ add_lldb_library(lldbPluginScriptInterpreterPythonInterfaces PLUGIN OperatingSystemPythonInterface.cpp ScriptInterpreterPythonInterfaces.cpp ScriptedFramePythonInterface.cpp + ScriptedFrameProviderPythonInterface.cpp ScriptedPlatformPythonInterface.cpp ScriptedProcessPythonInterface.cpp ScriptedPythonInterface.cpp diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp index d43036d..f6c707b 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.cpp @@ -31,6 +31,7 @@ void ScriptInterpreterPythonInterfaces::Initialize() { ScriptedStopHookPythonInterface::Initialize(); ScriptedBreakpointPythonInterface::Initialize(); ScriptedThreadPlanPythonInterface::Initialize(); + ScriptedFrameProviderPythonInterface::Initialize(); } void ScriptInterpreterPythonInterfaces::Terminate() { @@ -40,6 +41,7 @@ void ScriptInterpreterPythonInterfaces::Terminate() { ScriptedStopHookPythonInterface::Terminate(); ScriptedBreakpointPythonInterface::Terminate(); ScriptedThreadPlanPythonInterface::Terminate(); + ScriptedFrameProviderPythonInterface::Terminate(); } #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h index 3814f46..b2a3479 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptInterpreterPythonInterfaces.h @@ -17,6 +17,7 @@ #include "OperatingSystemPythonInterface.h" #include "ScriptedBreakpointPythonInterface.h" +#include "ScriptedFrameProviderPythonInterface.h" #include "ScriptedFramePythonInterface.h" #include "ScriptedPlatformPythonInterface.h" #include "ScriptedProcessPythonInterface.h" diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp new file mode 100644 index 0000000..3dde503 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.cpp @@ -0,0 +1,113 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Core/PluginManager.h" +#include "lldb/Host/Config.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/Log.h" +#include "lldb/lldb-enumerations.h" + +#if LLDB_ENABLE_PYTHON + +// LLDB Python header must be included first +#include "../lldb-python.h" + +#include "../SWIGPythonBridge.h" +#include "../ScriptInterpreterPythonImpl.h" +#include "ScriptedFrameProviderPythonInterface.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; +using namespace lldb_private::python; +using Locker = ScriptInterpreterPythonImpl::Locker; + +ScriptedFrameProviderPythonInterface::ScriptedFrameProviderPythonInterface( + ScriptInterpreterPythonImpl &interpreter) + : ScriptedFrameProviderInterface(), ScriptedPythonInterface(interpreter) {} + +bool ScriptedFrameProviderPythonInterface::AppliesToThread( + llvm::StringRef class_name, lldb::ThreadSP thread_sp) { + // If there is any issue with this method, we will just assume it also applies + // to this thread which is the default behavior. + constexpr bool fail_value = true; + Status error; + StructuredData::ObjectSP obj = + CallStaticMethod(class_name, "applies_to_thread", error, thread_sp); + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return fail_value; + + return obj->GetBooleanValue(fail_value); +} + +llvm::Expected<StructuredData::GenericSP> +ScriptedFrameProviderPythonInterface::CreatePluginObject( + const llvm::StringRef class_name, lldb::StackFrameListSP input_frames, + StructuredData::DictionarySP args_sp) { + if (!input_frames) + return llvm::createStringError("invalid frame list"); + + StructuredDataImpl sd_impl(args_sp); + return ScriptedPythonInterface::CreatePluginObject(class_name, nullptr, + input_frames, sd_impl); +} + +std::string ScriptedFrameProviderPythonInterface::GetDescription( + llvm::StringRef class_name) { + Status error; + StructuredData::ObjectSP obj = + CallStaticMethod(class_name, "get_description", error); + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return {}; + + return obj->GetStringValue().str(); +} + +StructuredData::ObjectSP +ScriptedFrameProviderPythonInterface::GetFrameAtIndex(uint32_t index) { + Status error; + StructuredData::ObjectSP obj = Dispatch("get_frame_at_index", error, index); + + if (!ScriptedInterface::CheckStructuredDataObject(LLVM_PRETTY_FUNCTION, obj, + error)) + return {}; + + return obj; +} + +bool ScriptedFrameProviderPythonInterface::CreateInstance( + lldb::ScriptLanguage language, ScriptedInterfaceUsages usages) { + if (language != eScriptLanguagePython) + return false; + + return true; +} + +void ScriptedFrameProviderPythonInterface::Initialize() { + const std::vector<llvm::StringRef> ci_usages = { + "target frame-provider register -C <script-name> [-k key -v value ...]", + "target frame-provider list", + "target frame-provider remove <provider-name>", + "target frame-provider clear"}; + const std::vector<llvm::StringRef> api_usages = { + "SBTarget.RegisterScriptedFrameProvider", + "SBTarget.RemoveScriptedFrameProvider", + "SBTarget.ClearScriptedFrameProvider"}; + PluginManager::RegisterPlugin( + GetPluginNameStatic(), + llvm::StringRef("Provide scripted stack frames for threads"), + CreateInstance, eScriptLanguagePython, {ci_usages, api_usages}); +} + +void ScriptedFrameProviderPythonInterface::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h new file mode 100644 index 0000000..97a5cc7 --- /dev/null +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedFrameProviderPythonInterface.h @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// 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_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H + +#include "lldb/Host/Config.h" + +#if LLDB_ENABLE_PYTHON + +#include "ScriptedPythonInterface.h" +#include "lldb/Core/PluginInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" +#include <optional> + +namespace lldb_private { +class ScriptedFrameProviderPythonInterface + : public ScriptedFrameProviderInterface, + public ScriptedPythonInterface, + public PluginInterface { +public: + ScriptedFrameProviderPythonInterface( + ScriptInterpreterPythonImpl &interpreter); + + bool AppliesToThread(llvm::StringRef class_name, + lldb::ThreadSP thread_sp) override; + + llvm::Expected<StructuredData::GenericSP> + CreatePluginObject(llvm::StringRef class_name, + lldb::StackFrameListSP input_frames, + StructuredData::DictionarySP args_sp) override; + + llvm::SmallVector<AbstractMethodRequirement> + GetAbstractMethodRequirements() const override { + return llvm::SmallVector<AbstractMethodRequirement>( + {{"get_description"}, {"get_frame_at_index"}}); + } + + std::string GetDescription(llvm::StringRef class_name) override; + + StructuredData::ObjectSP GetFrameAtIndex(uint32_t index) override; + + static void Initialize(); + static void Terminate(); + + static bool CreateInstance(lldb::ScriptLanguage language, + ScriptedInterfaceUsages usages); + + static llvm::StringRef GetPluginNameStatic() { + return "ScriptedFrameProviderPythonInterface"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } +}; +} // namespace lldb_private + +#endif // LLDB_ENABLE_PYTHON +#endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDFRAMEPROVIDERPYTHONINTERFACE_H diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp index 4fdf2b1..ba4473c 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.cpp @@ -94,6 +94,19 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>( } template <> +lldb::ThreadSP +ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>( + python::PythonObject &p, Status &error) { + if (lldb::SBThread *sb_thread = reinterpret_cast<lldb::SBThread *>( + python::LLDBSWIGPython_CastPyObjectToSBThread(p.get()))) + return m_interpreter.GetOpaqueTypeFromSBThread(*sb_thread); + error = Status::FromErrorString( + "Couldn't cast lldb::SBThread to lldb_private::Thread."); + + return nullptr; +} + +template <> SymbolContext ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>( python::PythonObject &p, Status &error) { @@ -243,4 +256,21 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>( return static_cast<lldb::DescriptionLevel>(unsigned_val); } +template <> +lldb::StackFrameListSP +ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>( + python::PythonObject &p, Status &error) { + + lldb::SBFrameList *sb_frame_list = reinterpret_cast<lldb::SBFrameList *>( + python::LLDBSWIGPython_CastPyObjectToSBFrameList(p.get())); + + if (!sb_frame_list) { + error = Status::FromErrorStringWithFormat( + "couldn't cast lldb::SBFrameList to lldb::StackFrameListSP."); + return {}; + } + + return m_interpreter.GetOpaqueTypeFromSBFrameList(*sb_frame_list); +} + #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h index 2335b2e..b737f94 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h @@ -41,7 +41,7 @@ public: eValid }; - struct AbstrackMethodCheckerPayload { + struct AbstractMethodCheckerPayload { struct InvalidArgumentCountPayload { InvalidArgumentCountPayload(size_t required, size_t actual) @@ -55,13 +55,69 @@ public: std::variant<std::monostate, InvalidArgumentCountPayload> payload; }; - llvm::Expected<std::map<llvm::StringLiteral, AbstrackMethodCheckerPayload>> + llvm::Expected<FileSpec> GetScriptedModulePath() override { + using namespace python; + using Locker = ScriptInterpreterPythonImpl::Locker; + + Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, + Locker::FreeLock); + + if (!m_object_instance_sp) + return llvm::createStringError("scripted Interface has invalid object"); + + PythonObject py_obj = + PythonObject(PyRefType::Borrowed, + static_cast<PyObject *>(m_object_instance_sp->GetValue())); + + if (!py_obj.IsAllocated()) + return llvm::createStringError( + "scripted Interface has invalid python object"); + + PythonObject py_obj_class = py_obj.GetAttributeValue("__class__"); + if (!py_obj_class.IsValid()) + return llvm::createStringError( + "scripted Interface python object is missing '__class__' attribute"); + + PythonObject py_obj_module = py_obj_class.GetAttributeValue("__module__"); + if (!py_obj_module.IsValid()) + return llvm::createStringError( + "scripted Interface python object '__class__' is missing " + "'__module__' attribute"); + + PythonString py_obj_module_str = py_obj_module.Str(); + if (!py_obj_module_str.IsValid()) + return llvm::createStringError( + "scripted Interface python object '__class__.__module__' attribute " + "is not a string"); + + llvm::StringRef py_obj_module_str_ref = py_obj_module_str.GetString(); + PythonModule py_module = PythonModule::AddModule(py_obj_module_str_ref); + if (!py_module.IsValid()) + return llvm::createStringError("failed to import '%s' module", + py_obj_module_str_ref.data()); + + PythonObject py_module_file = py_module.GetAttributeValue("__file__"); + if (!py_module_file.IsValid()) + return llvm::createStringError( + "module '%s' is missing '__file__' attribute", + py_obj_module_str_ref.data()); + + PythonString py_module_file_str = py_module_file.Str(); + if (!py_module_file_str.IsValid()) + return llvm::createStringError( + "module '%s.__file__' attribute is not a string", + py_obj_module_str_ref.data()); + + return FileSpec(py_module_file_str.GetString()); + } + + llvm::Expected<std::map<llvm::StringLiteral, AbstractMethodCheckerPayload>> CheckAbstractMethodImplementation( const python::PythonDictionary &class_dict) const { using namespace python; - std::map<llvm::StringLiteral, AbstrackMethodCheckerPayload> checker; + std::map<llvm::StringLiteral, AbstractMethodCheckerPayload> checker; #define SET_CASE_AND_CONTINUE(method_name, case) \ { \ checker[method_name] = {case, {}}; \ @@ -74,7 +130,8 @@ public: if (!class_dict.HasKey(method_name)) SET_CASE_AND_CONTINUE(method_name, AbstractMethodCheckerCases::eNotImplemented) - auto callable_or_err = class_dict.GetItem(method_name); + llvm::Expected<PythonObject> callable_or_err = + class_dict.GetItem(method_name); if (!callable_or_err) { llvm::consumeError(callable_or_err.takeError()); SET_CASE_AND_CONTINUE(method_name, @@ -102,7 +159,7 @@ public: } else { checker[method_name] = { AbstractMethodCheckerCases::eInvalidArgumentCount, - AbstrackMethodCheckerPayload::InvalidArgumentCountPayload( + AbstractMethodCheckerPayload::InvalidArgumentCountPayload( requirement.min_arg_count, arg_info.max_positional_args)}; } } @@ -188,8 +245,13 @@ public: // This addresses the cases where the embedded interpreter session // dictionary is passed to the extension initializer which is not used // most of the time. + // Note, though none of our API's suggest defining the interfaces with + // varargs, we have some extant clients that were doing that. To keep + // from breaking them, we just say putting a varargs in these signatures + // turns off argument checking. size_t num_args = sizeof...(Args); - if (num_args != arg_info->max_positional_args) { + if (arg_info->max_positional_args != PythonCallable::ArgInfo::UNBOUNDED && + num_args != arg_info->max_positional_args) { if (num_args != arg_info->max_positional_args - 1) return create_error("Passed arguments ({0}) doesn't match the number " "of expected arguments ({1}).", @@ -286,7 +348,7 @@ public: case AbstractMethodCheckerCases::eInvalidArgumentCount: { auto &payload_variant = method_checker.second.payload; if (!std::holds_alternative< - AbstrackMethodCheckerPayload::InvalidArgumentCountPayload>( + AbstractMethodCheckerPayload::InvalidArgumentCountPayload>( payload_variant)) { abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), @@ -295,7 +357,7 @@ public: obj_class_name.GetString(), method_checker.first))); } else { auto payload = std::get< - AbstrackMethodCheckerPayload::InvalidArgumentCountPayload>( + AbstractMethodCheckerPayload::InvalidArgumentCountPayload>( payload_variant); abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), @@ -325,6 +387,112 @@ public: return m_object_instance_sp; } + /// Call a static method on a Python class without creating an instance. + /// + /// This method resolves a Python class by name and calls a static method + /// on it, returning the result. This is useful for calling class-level + /// methods that don't require an instance. + /// + /// \param class_name The fully-qualified name of the Python class. + /// \param method_name The name of the static method to call. + /// \param error Output parameter to receive error information if the call + /// fails. + /// \param args Arguments to pass to the static method. + /// + /// \return The return value of the static method call, or an error value. + template <typename T = StructuredData::ObjectSP, typename... Args> + T CallStaticMethod(llvm::StringRef class_name, llvm::StringRef method_name, + Status &error, Args &&...args) { + using namespace python; + using Locker = ScriptInterpreterPythonImpl::Locker; + + std::string caller_signature = + llvm::Twine(LLVM_PRETTY_FUNCTION + llvm::Twine(" (") + + llvm::Twine(class_name) + llvm::Twine(".") + + llvm::Twine(method_name) + llvm::Twine(")")) + .str(); + + if (class_name.empty()) + return ErrorWithMessage<T>(caller_signature, "missing script class name", + error); + + Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, + Locker::FreeLock); + + // Get the interpreter dictionary. + auto dict = + PythonModule::MainModule().ResolveName<python::PythonDictionary>( + m_interpreter.GetDictionaryName()); + if (!dict.IsAllocated()) + return ErrorWithMessage<T>( + caller_signature, + llvm::formatv("could not find interpreter dictionary: {0}", + m_interpreter.GetDictionaryName()) + .str(), + error); + + // Resolve the class. + auto class_obj = + PythonObject::ResolveNameWithDictionary<python::PythonCallable>( + class_name, dict); + if (!class_obj.IsAllocated()) + return ErrorWithMessage<T>( + caller_signature, + llvm::formatv("could not find script class: {0}", class_name).str(), + error); + + // Get the static method from the class. + if (!class_obj.HasAttribute(method_name)) + return ErrorWithMessage<T>( + caller_signature, + llvm::formatv("class {0} does not have method {1}", class_name, + method_name) + .str(), + error); + + PythonCallable method = + class_obj.GetAttributeValue(method_name).AsType<PythonCallable>(); + if (!method.IsAllocated()) + return ErrorWithMessage<T>(caller_signature, + llvm::formatv("method {0}.{1} is not callable", + class_name, method_name) + .str(), + error); + + // Transform the arguments. + std::tuple<Args...> original_args = std::forward_as_tuple(args...); + auto transformed_args = TransformArgs(original_args); + + // Call the static method. + llvm::Expected<PythonObject> expected_return_object = + llvm::make_error<llvm::StringError>("Not initialized.", + llvm::inconvertibleErrorCode()); + std::apply( + [&method, &expected_return_object](auto &&...args) { + llvm::consumeError(expected_return_object.takeError()); + expected_return_object = method(args...); + }, + transformed_args); + + if (llvm::Error e = expected_return_object.takeError()) { + error = Status::FromError(std::move(e)); + return ErrorWithMessage<T>( + caller_signature, "python static method could not be called", error); + } + + PythonObject py_return = std::move(expected_return_object.get()); + + // Re-assign reference and pointer arguments if needed. + if (sizeof...(Args) > 0) + if (!ReassignPtrsOrRefsArgs(original_args, transformed_args)) + return ErrorWithMessage<T>( + caller_signature, + "couldn't re-assign reference and pointer arguments", error); + + // Extract value from Python object (handles unallocated case). + return ExtractValueFromPythonObject<T>(py_return, error); + } + protected: template <typename T = StructuredData::ObjectSP> T ExtractValueFromPythonObject(python::PythonObject &p, Status &error) { @@ -341,7 +509,7 @@ protected: llvm::Twine(method_name) + llvm::Twine(")")) .str(); if (!m_object_instance_sp) - return ErrorWithMessage<T>(caller_signature, "Python object ill-formed", + return ErrorWithMessage<T>(caller_signature, "python object ill-formed", error); Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, @@ -353,7 +521,7 @@ protected: if (!implementor.IsAllocated()) return llvm::is_contained(GetAbstractMethods(), method_name) ? ErrorWithMessage<T>(caller_signature, - "Python implementor not allocated.", + "python implementor not allocated", error) : T{}; @@ -374,20 +542,20 @@ protected: if (llvm::Error e = expected_return_object.takeError()) { error = Status::FromError(std::move(e)); return ErrorWithMessage<T>(caller_signature, - "Python method could not be called.", error); + "python method could not be called", error); } PythonObject py_return = std::move(expected_return_object.get()); // Now that we called the python method with the transformed arguments, - // we need to interate again over both the original and transformed + // we need to iterate again over both the original and transformed // parameter pack, and transform back the parameter that were passed in // the original parameter pack as references or pointers. if (sizeof...(Args) > 0) if (!ReassignPtrsOrRefsArgs(original_args, transformed_args)) return ErrorWithMessage<T>( caller_signature, - "Couldn't re-assign reference and pointer arguments.", error); + "couldn't re-assign reference and pointer arguments", error); if (!py_return.IsAllocated()) return {}; @@ -444,6 +612,14 @@ protected: return python::SWIGBridge::ToSWIGWrapper(arg); } + python::PythonObject Transform(lldb::ThreadSP arg) { + return python::SWIGBridge::ToSWIGWrapper(arg); + } + + python::PythonObject Transform(lldb::StackFrameListSP arg) { + return python::SWIGBridge::ToSWIGWrapper(arg); + } + python::PythonObject Transform(lldb::ThreadPlanSP arg) { return python::SWIGBridge::ToSWIGWrapper(arg); } @@ -586,6 +762,11 @@ ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>( python::PythonObject &p, Status &error); template <> +lldb::ThreadSP +ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>( + python::PythonObject &p, Status &error); + +template <> lldb::StackFrameSP ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>( python::PythonObject &p, Status &error); @@ -628,6 +809,11 @@ lldb::DescriptionLevel ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>( python::PythonObject &p, Status &error); +template <> +lldb::StackFrameListSP +ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>( + python::PythonObject &p, Status &error); + } // namespace lldb_private #endif // LLDB_ENABLE_PYTHON diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp index a2a287a..d2f795c 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -810,6 +810,17 @@ bool PythonCallable::Check(PyObject *py_obj) { if (!py_obj) return false; + PythonObject python_obj(PyRefType::Borrowed, py_obj); + + // Handle staticmethod/classmethod descriptors by extracting the + // `__func__` attribute. + if (python_obj.HasAttribute("__func__")) { + PythonObject function_obj = python_obj.GetAttributeValue("__func__"); + if (!function_obj.IsAllocated()) + return false; + return PyCallable_Check(function_obj.release()); + } + return PyCallable_Check(py_obj); } diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 27f5d2e..32948ff 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -93,6 +93,7 @@ public: static PythonObject ToSWIGWrapper(const StructuredDataImpl &data_impl); static PythonObject ToSWIGWrapper(lldb::ThreadSP thread_sp); static PythonObject ToSWIGWrapper(lldb::StackFrameSP frame_sp); + static PythonObject ToSWIGWrapper(lldb::StackFrameListSP frames_sp); static PythonObject ToSWIGWrapper(lldb::DebuggerSP debugger_sp); static PythonObject ToSWIGWrapper(lldb::WatchpointSP watchpoint_sp); static PythonObject ToSWIGWrapper(lldb::BreakpointLocationSP bp_loc_sp); @@ -264,11 +265,13 @@ void *LLDBSWIGPython_CastPyObjectToSBLaunchInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBError(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBEvent(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBSymbolContext(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBValue(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBMemoryRegionInfo(PyObject *data); void *LLDBSWIGPython_CastPyObjectToSBExecutionContext(PyObject *data); +void *LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data); } // namespace python } // namespace lldb_private diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index d257a08..35a772c 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -272,6 +272,7 @@ void ScriptInterpreterPython::SharedLibraryDirectoryHelper( // does. if (this_file.GetFileNameExtension() == ".pyd") { this_file.RemoveLastPathComponent(); // _lldb.pyd or _lldb_d.pyd + this_file.RemoveLastPathComponent(); // native this_file.RemoveLastPathComponent(); // lldb llvm::StringRef libdir = LLDB_PYTHON_RELATIVE_LIBDIR; for (auto it = llvm::sys::path::begin(libdir), @@ -1526,6 +1527,11 @@ ScriptInterpreterPythonImpl::CreateScriptedFrameInterface() { return std::make_shared<ScriptedFramePythonInterface>(*this); } +ScriptedFrameProviderInterfaceSP +ScriptInterpreterPythonImpl::CreateScriptedFrameProviderInterface() { + return std::make_shared<ScriptedFrameProviderPythonInterface>(*this); +} + ScriptedThreadPlanInterfaceSP ScriptInterpreterPythonImpl::CreateScriptedThreadPlanInterface() { return std::make_shared<ScriptedThreadPlanPythonInterface>(*this); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index 00ae59c..ad2ddd2 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -101,6 +101,9 @@ public: lldb::ScriptedFrameInterfaceSP CreateScriptedFrameInterface() override; + lldb::ScriptedFrameProviderInterfaceSP + CreateScriptedFrameProviderInterface() override; + lldb::ScriptedThreadPlanInterfaceSP CreateScriptedThreadPlanInterface() override; diff --git a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp index ce2ba69..14932e9 100644 --- a/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp +++ b/lldb/source/Plugins/SymbolFile/Breakpad/SymbolFileBreakpad.cpp @@ -437,7 +437,7 @@ void SymbolFileBreakpad::FindFunctions( sc.comp_unit = cu_sp.get(); sc.function = func_sp.get(); sc.module_sp = func_sp->CalculateSymbolContextModule(); - sc_list.Append(sc); + sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/true); } } } diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp index 63b2dc4..d65aa40 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp @@ -623,6 +623,7 @@ TypeSP DWARFASTParserClang::ParseTypeFromDWARF(const SymbolContext &sc, switch (tag) { case DW_TAG_typedef: + case DW_TAG_template_alias: case DW_TAG_base_type: case DW_TAG_pointer_type: case DW_TAG_reference_type: @@ -748,7 +749,7 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc, TypeSP type_sp; CompilerType clang_type; - if (tag == DW_TAG_typedef) { + if (tag == DW_TAG_typedef || tag == DW_TAG_template_alias) { // DeclContext will be populated when the clang type is materialized in // Type::ResolveCompilerType. PrepareContextToReceiveMembers( @@ -836,6 +837,7 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc, encoding_data_type = Type::eEncodingIsRValueReferenceUID; break; case DW_TAG_typedef: + case DW_TAG_template_alias: encoding_data_type = Type::eEncodingIsTypedefUID; break; case DW_TAG_const_type: @@ -1705,8 +1707,11 @@ void DWARFASTParserClang::GetUniqueTypeNameAndDeclaration( // For C++, we rely solely upon the one definition rule that says // only one thing can exist at a given decl context. We ignore the // file and line that things are declared on. - if (!die.IsValid() || !Language::LanguageIsCPlusPlus(language) || - unique_typename.IsEmpty()) + // FIXME: Rust pretends to be C++ for now, so use C++ name qualification rules + if (!Language::LanguageIsCPlusPlus(language) && + language != lldb::eLanguageTypeRust) + return; + if (!die.IsValid() || unique_typename.IsEmpty()) return; decl_declaration.Clear(); std::string qualified_name; @@ -3704,12 +3709,10 @@ bool DWARFASTParserClang::CopyUniqueClassMethodTypes( } } - DWARFASTParserClang *src_dwarf_ast_parser = - static_cast<DWARFASTParserClang *>( - SymbolFileDWARF::GetDWARFParser(*src_class_die.GetCU())); - DWARFASTParserClang *dst_dwarf_ast_parser = - static_cast<DWARFASTParserClang *>( - SymbolFileDWARF::GetDWARFParser(*dst_class_die.GetCU())); + auto *src_dwarf_ast_parser = llvm::cast<DWARFASTParserClang>( + SymbolFileDWARF::GetDWARFParser(*src_class_die.GetCU())); + auto *dst_dwarf_ast_parser = llvm::cast<DWARFASTParserClang>( + SymbolFileDWARF::GetDWARFParser(*dst_class_die.GetCU())); auto link = [&](DWARFDIE src, DWARFDIE dst) { auto &die_to_type = dst_class_die.GetDWARF()->GetDIEToType(); clang::DeclContext *dst_decl_ctx = diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h index f5f7071..6eb2b6b 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h @@ -47,6 +47,11 @@ public: ~DWARFASTParserClang() override; + // LLVM RTTI support + static bool classof(const DWARFASTParser *Parser) { + return Parser->GetKind() == Kind::DWARFASTParserClang; + } + // DWARFASTParser interface. lldb::TypeSP ParseTypeFromDWARF(const lldb_private::SymbolContext &sc, @@ -264,10 +269,6 @@ protected: lldb::ModuleSP GetModuleForType(const lldb_private::plugin::dwarf::DWARFDIE &die); - static bool classof(const DWARFASTParser *Parser) { - return Parser->GetKind() == Kind::DWARFASTParserClang; - } - private: struct FieldInfo { /// Size in bits that this field occupies. Can but diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp index 64a8005..c4efc06 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.cpp @@ -173,6 +173,14 @@ void DWARFIndex::GetNamespacesWithParents( }); } +void DWARFIndex::GetFunctions( + const std::vector<Module::LookupInfo> &lookup_infos, SymbolFileDWARF &dwarf, + const CompilerDeclContext &parent_decl_ctx, + llvm::function_ref<IterationAction(DWARFDIE die)> callback) { + for (auto &lookup_info : lookup_infos) + GetFunctions(lookup_info, dwarf, parent_decl_ctx, callback); +} + IterationAction DWARFIndex::ProcessNamespaceDieMatchParents( const CompilerDeclContext &parent_decl_ctx, DWARFDIE die, llvm::function_ref<IterationAction(DWARFDIE die)> callback) { diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h index be73255..eaf1da1 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFIndex.h @@ -86,6 +86,11 @@ public: const CompilerDeclContext &parent_decl_ctx, llvm::function_ref<IterationAction(DWARFDIE die)> callback) = 0; virtual void + GetFunctions(const std::vector<Module::LookupInfo> &lookup_infos, + SymbolFileDWARF &dwarf, + const CompilerDeclContext &parent_decl_ctx, + llvm::function_ref<IterationAction(DWARFDIE die)> callback); + virtual void GetFunctions(const RegularExpression ®ex, llvm::function_ref<IterationAction(DWARFDIE die)> callback) = 0; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp index 94fc2e83..b78e6ce8 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp @@ -348,6 +348,10 @@ void DWARFUnit::ExtractDIEsRWLocked() { void DWARFUnit::SetDwoStrOffsetsBase() { lldb::offset_t baseOffset = 0; + // Size of offset for .debug_str_offsets is same as DWARF offset byte size + // of the DWARFUnit as a default. We might override this if below if needed. + m_str_offset_size = m_header.getDwarfOffsetByteSize(); + if (const llvm::DWARFUnitIndex::Entry *entry = m_header.getIndexEntry()) { if (const auto *contribution = entry->getContribution(llvm::DW_SECT_STR_OFFSETS)) @@ -357,14 +361,17 @@ void DWARFUnit::SetDwoStrOffsetsBase() { } if (GetVersion() >= 5) { - const DWARFDataExtractor &strOffsets = - GetSymbolFileDWARF().GetDWARFContext().getOrLoadStrOffsetsData(); - uint64_t length = strOffsets.GetU32(&baseOffset); - if (length == 0xffffffff) - length = strOffsets.GetU64(&baseOffset); - + const llvm::DWARFDataExtractor &strOffsets = GetSymbolFileDWARF() + .GetDWARFContext() + .getOrLoadStrOffsetsData() + .GetAsLLVMDWARF(); + + uint64_t length; + llvm::dwarf::DwarfFormat format; + std::tie(length, format) = strOffsets.getInitialLength(&baseOffset); + m_str_offset_size = format == llvm::dwarf::DwarfFormat::DWARF64 ? 8 : 4; // Check version. - if (strOffsets.GetU16(&baseOffset) < 5) + if (strOffsets.getU16(&baseOffset) < 5) return; // Skip padding. @@ -409,7 +416,16 @@ void DWARFUnit::AddUnitDIE(const DWARFDebugInfoEntry &cu_die) { SetRangesBase(form_value.Unsigned()); break; case DW_AT_str_offsets_base: + // When we have a DW_AT_str_offsets_base attribute, it points us to the + // first string offset for this DWARFUnit which is after the string + // offsets table header. In this case we use the DWARF32/DWARF64 of the + // DWARFUnit to determine the string offset byte size. DWO files do not + // use this attribute and they point to the start of the string offsets + // table header which can be used to determine the DWARF32/DWARF64 status + // of the string table. See SetDwoStrOffsetsBase() for now it figures out + // the m_str_offset_size value that should be used. SetStrOffsetsBase(form_value.Unsigned()); + m_str_offset_size = m_header.getDwarfOffsetByteSize(); break; case DW_AT_low_pc: SetBaseAddress(form_value.Address()); @@ -1079,10 +1095,9 @@ uint32_t DWARFUnit::GetHeaderByteSize() const { return m_header.getSize(); } std::optional<uint64_t> DWARFUnit::GetStringOffsetSectionItem(uint32_t index) const { - lldb::offset_t offset = - GetStrOffsetsBase() + index * m_header.getDwarfOffsetByteSize(); + lldb::offset_t offset = GetStrOffsetsBase() + index * m_str_offset_size; return m_dwarf.GetDWARFContext().getOrLoadStrOffsetsData().GetMaxU64( - &offset, m_header.getDwarfOffsetByteSize()); + &offset, m_str_offset_size); } llvm::Expected<llvm::DWARFAddressRangesVector> diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h index 91a6938..b5199a8 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h @@ -364,6 +364,7 @@ protected: dw_offset_t m_line_table_offset = DW_INVALID_OFFSET; dw_offset_t m_str_offsets_base = 0; // Value of DW_AT_str_offsets_base. + dw_offset_t m_str_offset_size = 4; // Size in bytes of a string offset. std::optional<llvm::DWARFDebugRnglistTable> m_rnglist_table; bool m_rnglist_table_done = false; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp index 881268b..b755f3a 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -794,12 +794,12 @@ lldb::CompUnitSP SymbolFileDWARF::ParseCompileUnit(DWARFCompileUnit &dwarf_cu) { } else { ModuleSP module_sp(m_objfile_sp->GetModule()); if (module_sp) { - auto initialize_cu = [&](lldb::SupportFileSP support_file_sp, + auto initialize_cu = [&](SupportFileNSP support_file_nsp, LanguageType cu_language, SupportFileList &&support_files = {}) { BuildCuTranslationTable(); cu_sp = std::make_shared<CompileUnit>( - module_sp, &dwarf_cu, support_file_sp, + module_sp, &dwarf_cu, support_file_nsp, *GetDWARFUnitIndex(dwarf_cu.GetID()), cu_language, eLazyBoolCalculate, std::move(support_files)); @@ -1560,8 +1560,8 @@ bool SymbolFileDWARF::HasForwardDeclForCompilerType( auto clang_type_system = compiler_type.GetTypeSystem<TypeSystemClang>(); if (!clang_type_system) return false; - DWARFASTParserClang *ast_parser = - static_cast<DWARFASTParserClang *>(clang_type_system->GetDWARFParser()); + auto *ast_parser = + llvm::cast<DWARFASTParserClang>(clang_type_system->GetDWARFParser()); return ast_parser->GetClangASTImporter().CanImport(compiler_type); } @@ -1569,8 +1569,8 @@ bool SymbolFileDWARF::CompleteType(CompilerType &compiler_type) { std::lock_guard<std::recursive_mutex> guard(GetModuleMutex()); auto clang_type_system = compiler_type.GetTypeSystem<TypeSystemClang>(); if (clang_type_system) { - DWARFASTParserClang *ast_parser = - static_cast<DWARFASTParserClang *>(clang_type_system->GetDWARFParser()); + auto *ast_parser = + llvm::cast<DWARFASTParserClang>(clang_type_system->GetDWARFParser()); if (ast_parser && ast_parser->GetClangASTImporter().CanImport(compiler_type)) return ast_parser->GetClangASTImporter().CompleteType(compiler_type); @@ -1614,8 +1614,7 @@ bool SymbolFileDWARF::CompleteType(CompilerType &compiler_type) { if (decl_die != def_die) { GetDIEToType()[def_die.GetDIE()] = type; - DWARFASTParserClang *ast_parser = - static_cast<DWARFASTParserClang *>(dwarf_ast); + auto *ast_parser = llvm::cast<DWARFASTParserClang>(dwarf_ast); ast_parser->MapDeclDIEToDefDIE(decl_die, def_die); } @@ -2018,7 +2017,7 @@ void SymbolFileDWARF::UpdateExternalModuleListIfNeeded() { } Status error = ModuleList::GetSharedModule(dwo_module_spec, module_sp, - nullptr, nullptr, nullptr); + nullptr, nullptr); if (!module_sp) { // ReportWarning also rate-limits based on the warning string, // but in a -gmodules build, each object file has a similar DAG @@ -2341,7 +2340,7 @@ void SymbolFileDWARF::FindGlobalVariables( bool name_is_mangled = Mangled::GetManglingScheme(name.GetStringRef()) != Mangled::eManglingSchemeNone; - if (!CPlusPlusLanguage::ExtractContextAndIdentifier(name.GetCString(), + if (!CPlusPlusLanguage::ExtractContextAndIdentifier(name.GetStringRef(), context, basename)) basename = name.GetStringRef(); @@ -2482,7 +2481,7 @@ bool SymbolFileDWARF::ResolveFunction(const DWARFDIE &orig_die, sc.block = function_block.FindBlockByID(inlined_die.GetOffset()); } - sc_list.Append(sc); + sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/true); return true; } @@ -2550,11 +2549,11 @@ SymbolFileDWARF::FindFunctionDefinition(const FunctionCallLabel &label, const DWARFDIE &declaration) { auto do_lookup = [this](llvm::StringRef lookup_name) -> DWARFDIE { DWARFDIE found; - Module::LookupInfo info(ConstString(lookup_name), - lldb::eFunctionNameTypeFull, - lldb::eLanguageTypeUnknown); + auto lookup_infos = Module::LookupInfo::MakeLookupInfos( + ConstString(lookup_name), lldb::eFunctionNameTypeFull, + lldb::eLanguageTypeUnknown); - m_index->GetFunctions(info, *this, {}, [&](DWARFDIE entry) { + m_index->GetFunctions(lookup_infos, *this, {}, [&](DWARFDIE entry) { if (entry.GetAttributeValueAsUnsigned(llvm::dwarf::DW_AT_declaration, 0)) return IterationAction::Continue; diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index aaec160..3bf113a 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -86,6 +86,40 @@ static lldb::LanguageType TranslateLanguage(PDB_Lang lang) { } } +static std::optional<std::string> +findMatchingPDBFilePath(llvm::StringRef original_pdb_path, + llvm::StringRef exe_path) { + const FileSystem &fs = FileSystem::Instance(); + + if (fs.Exists(original_pdb_path)) + return std::string(original_pdb_path); + + const auto exe_dir = FileSpec(exe_path).CopyByRemovingLastPathComponent(); + // While the exe_path uses the native style, the exe might be compiled on a + // different OS, so try to guess the style used. + const FileSpec original_pdb_spec(original_pdb_path, + FileSpec::GuessPathStyle(original_pdb_path) + .value_or(FileSpec::Style::native)); + const llvm::StringRef pdb_filename = original_pdb_spec.GetFilename(); + + // If the file doesn't exist, perhaps the path specified at build time + // doesn't match the PDB's current location, so check the location of the + // executable. + const FileSpec local_pdb = exe_dir.CopyByAppendingPathComponent(pdb_filename); + if (fs.Exists(local_pdb)) + return local_pdb.GetPath(); + + // Otherwise, search for one in target.debug-file-search-paths + FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths(); + for (const FileSpec &search_dir : search_paths) { + FileSpec pdb_path = search_dir.CopyByAppendingPathComponent(pdb_filename); + if (fs.Exists(pdb_path)) + return pdb_path.GetPath(); + } + + return std::nullopt; +} + static std::unique_ptr<PDBFile> loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) { // Try to find a matching PDB for an EXE. @@ -113,17 +147,14 @@ loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) { return nullptr; } - // If the file doesn't exist, perhaps the path specified at build time - // doesn't match the PDB's current location, so check the location of the - // executable. - if (!FileSystem::Instance().Exists(pdb_file)) { - const auto exe_dir = FileSpec(exe_path).CopyByRemovingLastPathComponent(); - const auto pdb_name = FileSpec(pdb_file).GetFilename().GetCString(); - pdb_file = exe_dir.CopyByAppendingPathComponent(pdb_name).GetPathAsConstString().GetStringRef(); - } + std::optional<std::string> resolved_pdb_path = + findMatchingPDBFilePath(pdb_file, exe_path); + if (!resolved_pdb_path) + return nullptr; // If the file is not a PDB or if it doesn't have a matching GUID, fail. - auto pdb = ObjectFilePDB::loadPDBFile(std::string(pdb_file), allocator); + auto pdb = + ObjectFilePDB::loadPDBFile(*std::move(resolved_pdb_path), allocator); if (!pdb) return nullptr; @@ -137,6 +168,9 @@ loadMatchingPDBFile(std::string exe_path, llvm::BumpPtrAllocator &allocator) { if (expected_info->getGuid() != guid) return nullptr; + + LLDB_LOG(GetLog(LLDBLog::Symbols), "Loading {0} for {1}", pdb->getFilePath(), + exe_path); return pdb; } @@ -1126,7 +1160,8 @@ lldb::LanguageType SymbolFileNativePDB::ParseLanguage(CompileUnit &comp_unit) { } void SymbolFileNativePDB::AddSymbols(Symtab &symtab) { - auto *section_list = m_objfile_sp->GetSectionList(); + auto *section_list = + m_objfile_sp->GetModule()->GetObjectFile()->GetSectionList(); if (!section_list) return; diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp index 1c575e9..46cf9b8 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/UdtRecordCompleter.cpp @@ -442,6 +442,10 @@ void UdtRecordCompleter::Record::ConstructRecord() { // The end offset to a vector of field/struct that ends at the offset. std::map<uint64_t, std::vector<Member *>> end_offset_map; + auto is_last_end_offset = [&](auto it) { + return it != end_offset_map.end() && ++it == end_offset_map.end(); + }; + for (auto &pair : fields_map) { uint64_t offset = pair.first; auto &fields = pair.second; @@ -462,8 +466,23 @@ void UdtRecordCompleter::Record::ConstructRecord() { } if (iter->second.empty()) continue; - parent = iter->second.back(); - iter->second.pop_back(); + + // If the new fields come after the already added ones + // without overlap, go back to the root. + if (iter->first <= offset && is_last_end_offset(iter)) { + if (record.kind == Member::Struct) { + parent = &record; + } else { + assert(record.kind == Member::Union && + "Current record must be a union"); + assert(!record.fields.empty()); + // For unions, append the field to the last struct + parent = record.fields.back().get(); + } + } else { + parent = iter->second.back(); + iter->second.pop_back(); + } } // If it's a field, then the field is inside a union, so we can safely // increase its size by converting it to a struct to hold multiple fields. diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp index 0ccb1804..97c995fc 100644 --- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp @@ -287,8 +287,10 @@ uint32_t SymbolFilePDB::CalculateAbilities() { } void SymbolFilePDB::InitializeObject() { - lldb::addr_t obj_load_address = - m_objfile_sp->GetBaseAddress().GetFileAddress(); + lldb::addr_t obj_load_address = m_objfile_sp->GetModule() + ->GetObjectFile() + ->GetBaseAddress() + .GetFileAddress(); lldbassert(obj_load_address && obj_load_address != LLDB_INVALID_ADDRESS); m_session_up->setLoadAddress(obj_load_address); if (!m_global_scope_up) @@ -1479,7 +1481,8 @@ void SymbolFilePDB::AddSymbols(lldb_private::Symtab &symtab) { if (!results) return; - auto section_list = m_objfile_sp->GetSectionList(); + auto section_list = + m_objfile_sp->GetModule()->GetObjectFile()->GetSectionList(); if (!section_list) return; diff --git a/lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt b/lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt new file mode 100644 index 0000000..85b405e --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ScriptedFrameProvider) diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt new file mode 100644 index 0000000..fe67d39 --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/CMakeLists.txt @@ -0,0 +1,12 @@ +add_lldb_library(lldbPluginScriptedFrameProvider PLUGIN + ScriptedFrameProvider.cpp + + LINK_COMPONENTS + Support + + LINK_LIBS + lldbCore + lldbInterpreter + lldbTarget + lldbUtility + ) diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp new file mode 100644 index 0000000..739963e --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.cpp @@ -0,0 +1,221 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ScriptedFrameProvider.h" +#include "Plugins/Process/scripted/ScriptedFrame.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" +#include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Target/BorrowedStackFrame.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrame.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/Status.h" +#include "llvm/Support/Error.h" +#include <cstdint> + +using namespace lldb; +using namespace lldb_private; + +void ScriptedFrameProvider::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + "Provides synthetic frames via scripting", + nullptr, ScriptedFrameProvider::CreateInstance); +} + +void ScriptedFrameProvider::Terminate() { + PluginManager::UnregisterPlugin(ScriptedFrameProvider::CreateInstance); +} + +llvm::Expected<lldb::SyntheticFrameProviderSP> +ScriptedFrameProvider::CreateInstance( + lldb::StackFrameListSP input_frames, + const ScriptedFrameProviderDescriptor &descriptor) { + if (!input_frames) + return llvm::createStringError( + "failed to create scripted frame provider: invalid input frames"); + + Thread &thread = input_frames->GetThread(); + ProcessSP process_sp = thread.GetProcess(); + if (!process_sp) + return nullptr; + + if (!descriptor.IsValid()) + return llvm::createStringError( + "failed to create scripted frame provider: invalid scripted metadata"); + + if (!descriptor.AppliesToThread(thread)) + return nullptr; + + ScriptInterpreter *script_interp = + process_sp->GetTarget().GetDebugger().GetScriptInterpreter(); + if (!script_interp) + return llvm::createStringError("cannot create scripted frame provider: No " + "script interpreter installed"); + + ScriptedFrameProviderInterfaceSP interface_sp = + script_interp->CreateScriptedFrameProviderInterface(); + if (!interface_sp) + return llvm::createStringError( + "cannot create scripted frame provider: script interpreter couldn't " + "create Scripted Frame Provider Interface"); + + const ScriptedMetadataSP scripted_metadata = descriptor.scripted_metadata_sp; + + // If we shouldn't attach a frame provider to this thread, just exit early. + if (!interface_sp->AppliesToThread(scripted_metadata->GetClassName(), + thread.shared_from_this())) + return nullptr; + + auto obj_or_err = interface_sp->CreatePluginObject( + scripted_metadata->GetClassName(), input_frames, + scripted_metadata->GetArgsSP()); + if (!obj_or_err) + return obj_or_err.takeError(); + + StructuredData::ObjectSP object_sp = *obj_or_err; + if (!object_sp || !object_sp->IsValid()) + return llvm::createStringError( + "cannot create scripted frame provider: failed to create valid scripted" + "frame provider object"); + + return std::make_shared<ScriptedFrameProvider>(input_frames, interface_sp, + descriptor); +} + +ScriptedFrameProvider::ScriptedFrameProvider( + StackFrameListSP input_frames, + lldb::ScriptedFrameProviderInterfaceSP interface_sp, + const ScriptedFrameProviderDescriptor &descriptor) + : SyntheticFrameProvider(input_frames), m_interface_sp(interface_sp), + m_descriptor(descriptor) {} + +ScriptedFrameProvider::~ScriptedFrameProvider() = default; + +std::string ScriptedFrameProvider::GetDescription() const { + if (!m_interface_sp) + return {}; + + return m_interface_sp->GetDescription(m_descriptor.GetName()); +} + +llvm::Expected<StackFrameSP> +ScriptedFrameProvider::GetFrameAtIndex(uint32_t idx) { + if (!m_interface_sp) + return llvm::createStringError( + "cannot get stack frame: scripted frame provider not initialized"); + + auto create_frame_from_dict = + [this](StructuredData::Dictionary *dict, + uint32_t index) -> llvm::Expected<StackFrameSP> { + lldb::addr_t pc; + if (!dict->GetValueForKeyAsInteger("pc", pc)) + return llvm::createStringError( + "missing 'pc' key from scripted frame dictionary"); + + Address symbol_addr; + symbol_addr.SetLoadAddress(pc, &GetThread().GetProcess()->GetTarget()); + + const lldb::addr_t cfa = LLDB_INVALID_ADDRESS; + const bool cfa_is_valid = false; + const bool artificial = false; + const bool behaves_like_zeroth_frame = false; + SymbolContext sc; + symbol_addr.CalculateSymbolContext(&sc); + + ThreadSP thread_sp = GetThread().shared_from_this(); + return std::make_shared<StackFrame>(thread_sp, index, index, cfa, + cfa_is_valid, pc, + StackFrame::Kind::Synthetic, artificial, + behaves_like_zeroth_frame, &sc); + }; + + auto create_frame_from_script_object = + [this]( + StructuredData::ObjectSP object_sp) -> llvm::Expected<StackFrameSP> { + Status error; + if (!object_sp || !object_sp->GetAsGeneric()) + return llvm::createStringError("invalid script object"); + + ThreadSP thread_sp = GetThread().shared_from_this(); + auto frame_or_error = ScriptedFrame::Create(thread_sp, nullptr, nullptr, + object_sp->GetAsGeneric()); + + if (!frame_or_error) { + ScriptedInterface::ErrorWithMessage<bool>( + LLVM_PRETTY_FUNCTION, toString(frame_or_error.takeError()), error); + return error.ToError(); + } + + return *frame_or_error; + }; + + StructuredData::ObjectSP obj_sp = m_interface_sp->GetFrameAtIndex(idx); + + // None/null means no more frames or error. + if (!obj_sp || !obj_sp->IsValid()) + return llvm::createStringError("invalid script object returned for frame " + + llvm::Twine(idx)); + + StackFrameSP synth_frame_sp = nullptr; + if (StructuredData::UnsignedInteger *int_obj = + obj_sp->GetAsUnsignedInteger()) { + uint32_t real_frame_index = int_obj->GetValue(); + if (real_frame_index < m_input_frames->GetNumFrames()) { + StackFrameSP real_frame_sp = + m_input_frames->GetFrameAtIndex(real_frame_index); + synth_frame_sp = + (real_frame_index == idx) + ? real_frame_sp + : std::make_shared<BorrowedStackFrame>(real_frame_sp, idx); + } + } else if (StructuredData::Dictionary *dict = obj_sp->GetAsDictionary()) { + // Check if it's a dictionary describing a frame. + auto frame_from_dict_or_err = create_frame_from_dict(dict, idx); + if (!frame_from_dict_or_err) { + return llvm::createStringError(llvm::Twine( + "couldn't create frame from dictionary at index " + llvm::Twine(idx) + + ": " + toString(frame_from_dict_or_err.takeError()))); + } + synth_frame_sp = *frame_from_dict_or_err; + } else if (obj_sp->GetAsGeneric()) { + // It's a ScriptedFrame object. + auto frame_from_script_obj_or_err = create_frame_from_script_object(obj_sp); + if (!frame_from_script_obj_or_err) { + return llvm::createStringError( + llvm::Twine("couldn't create frame from script object at index " + + llvm::Twine(idx) + ": " + + toString(frame_from_script_obj_or_err.takeError()))); + } + synth_frame_sp = *frame_from_script_obj_or_err; + } else { + return llvm::createStringError( + llvm::Twine("invalid return type from get_frame_at_index at index " + + llvm::Twine(idx))); + } + + if (!synth_frame_sp) + return llvm::createStringError( + llvm::Twine("failed to create frame at index " + llvm::Twine(idx))); + + synth_frame_sp->SetFrameIndex(idx); + + return synth_frame_sp; +} + +namespace lldb_private { +void lldb_initialize_ScriptedFrameProvider() { + ScriptedFrameProvider::Initialize(); +} + +void lldb_terminate_ScriptedFrameProvider() { + ScriptedFrameProvider::Terminate(); +} +} // namespace lldb_private diff --git a/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h new file mode 100644 index 0000000..3434bf2 --- /dev/null +++ b/lldb/source/Plugins/SyntheticFrameProvider/ScriptedFrameProvider/ScriptedFrameProvider.h @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// 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_SYNTHETICFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_H +#define LLDB_PLUGINS_SYNTHETICFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_H + +#include "lldb/Target/SyntheticFrameProvider.h" +#include "lldb/Utility/ScriptedMetadata.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-forward.h" +#include "llvm/Support/Error.h" + +namespace lldb_private { + +class ScriptedFrameProvider : public SyntheticFrameProvider { +public: + static llvm::StringRef GetPluginNameStatic() { + return "ScriptedFrameProvider"; + } + + static llvm::Expected<lldb::SyntheticFrameProviderSP> + CreateInstance(lldb::StackFrameListSP input_frames, + const ScriptedFrameProviderDescriptor &descriptor); + + static void Initialize(); + + static void Terminate(); + + ScriptedFrameProvider(lldb::StackFrameListSP input_frames, + lldb::ScriptedFrameProviderInterfaceSP interface_sp, + const ScriptedFrameProviderDescriptor &descriptor); + ~ScriptedFrameProvider() override; + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + std::string GetDescription() const override; + + /// Get a single stack frame at the specified index. + llvm::Expected<lldb::StackFrameSP> GetFrameAtIndex(uint32_t idx) override; + +private: + lldb::ScriptedFrameProviderInterfaceSP m_interface_sp; + const ScriptedFrameProviderDescriptor &m_descriptor; +}; + +} // namespace lldb_private + +#endif // LLDB_PLUGINS_SYNTHETICFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_SCRIPTEDFRAMEPROVIDER_H diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index 51cb883..625d0e5 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -2149,7 +2149,8 @@ PrintingPolicy TypeSystemClang::GetTypePrintingPolicy() { printing_policy.SuppressTagKeyword = true; // Inline namespaces are important for some type formatters (e.g., libc++ // and libstdc++ are differentiated by their inline namespaces). - printing_policy.SuppressInlineNamespace = false; + printing_policy.SuppressInlineNamespace = + llvm::to_underlying(PrintingPolicy::SuppressInlineNamespaceMode::None); printing_policy.SuppressUnwrittenScope = false; // Default arguments are also always important for type formatters. Otherwise // we would need to always specify two type names for the setups where we do @@ -3870,7 +3871,8 @@ TypeSystemClang::GetDisplayTypeName(lldb::opaque_compiler_type_t type) { printing_policy.SuppressTagKeyword = true; printing_policy.SuppressScope = false; printing_policy.SuppressUnwrittenScope = true; - printing_policy.SuppressInlineNamespace = true; + printing_policy.SuppressInlineNamespace = + llvm::to_underlying(PrintingPolicy::SuppressInlineNamespaceMode::All); return ConstString(qual_type.getAsString(printing_policy)); } @@ -7346,6 +7348,102 @@ CompilerType TypeSystemClang::GetTypeForFormatters(void *type) { return CompilerType(); } +bool TypeSystemClang::IsPromotableIntegerType( + lldb::opaque_compiler_type_t type) { + // Unscoped enums are always considered as promotable, even if their + // underlying type does not need to be promoted (e.g. "int"). + bool is_signed = false; + bool isUnscopedEnumerationType = + IsEnumerationType(type, is_signed) && !IsScopedEnumerationType(type); + if (isUnscopedEnumerationType) + return true; + + switch (GetBasicTypeEnumeration(type)) { + case lldb::eBasicTypeBool: + case lldb::eBasicTypeChar: + case lldb::eBasicTypeSignedChar: + case lldb::eBasicTypeUnsignedChar: + case lldb::eBasicTypeShort: + case lldb::eBasicTypeUnsignedShort: + case lldb::eBasicTypeWChar: + case lldb::eBasicTypeSignedWChar: + case lldb::eBasicTypeUnsignedWChar: + case lldb::eBasicTypeChar16: + case lldb::eBasicTypeChar32: + return true; + + default: + return false; + } + + llvm_unreachable("All cases handled above."); +} + +llvm::Expected<CompilerType> +TypeSystemClang::DoIntegralPromotion(CompilerType from, + ExecutionContextScope *exe_scope) { + if (!from.IsInteger() && !from.IsUnscopedEnumerationType()) + return from; + + if (!from.IsPromotableIntegerType()) + return from; + + if (from.IsUnscopedEnumerationType()) { + EnumDecl *enum_decl = GetAsEnumDecl(from); + CompilerType promotion_type = GetType(enum_decl->getPromotionType()); + return DoIntegralPromotion(promotion_type, exe_scope); + } + + lldb::BasicType builtin_type = + from.GetCanonicalType().GetBasicTypeEnumeration(); + uint64_t from_size = 0; + if (builtin_type == lldb::eBasicTypeWChar || + builtin_type == lldb::eBasicTypeSignedWChar || + builtin_type == lldb::eBasicTypeUnsignedWChar || + builtin_type == lldb::eBasicTypeChar16 || + builtin_type == lldb::eBasicTypeChar32) { + // Find the type that can hold the entire range of values for our type. + bool is_signed = from.IsSigned(); + llvm::Expected<uint64_t> from_size = from.GetByteSize(exe_scope); + if (!from_size) + return from_size.takeError(); + CompilerType promote_types[] = { + GetBasicTypeFromAST(lldb::eBasicTypeInt), + GetBasicTypeFromAST(lldb::eBasicTypeUnsignedInt), + GetBasicTypeFromAST(lldb::eBasicTypeLong), + GetBasicTypeFromAST(lldb::eBasicTypeUnsignedLong), + GetBasicTypeFromAST(lldb::eBasicTypeLongLong), + GetBasicTypeFromAST(lldb::eBasicTypeUnsignedLongLong), + }; + for (CompilerType &type : promote_types) { + llvm::Expected<uint64_t> byte_size = type.GetByteSize(exe_scope); + if (!byte_size) + return byte_size.takeError(); + if (*from_size < *byte_size || + (*from_size == *byte_size && is_signed == type.IsSigned())) { + return type; + } + } + llvm_unreachable("char type should fit into long long"); + } + + // Here we can promote only to "int" or "unsigned int". + CompilerType int_type = GetBasicTypeFromAST(lldb::eBasicTypeInt); + llvm::Expected<uint64_t> int_byte_size = int_type.GetByteSize(exe_scope); + if (!int_byte_size) + return int_byte_size.takeError(); + + // Signed integer types can be safely promoted to "int". + if (from.IsSigned()) { + return int_type; + } + // Unsigned integer types are promoted to "unsigned int" if "int" cannot hold + // their entire value range. + return (from_size == *int_byte_size) + ? GetBasicTypeFromAST(lldb::eBasicTypeUnsignedInt) + : int_type; +} + clang::EnumDecl *TypeSystemClang::GetAsEnumDecl(const CompilerType &type) { const clang::EnumType *enutype = llvm::dyn_cast<clang::EnumType>(ClangUtil::GetCanonicalQualType(type)); diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h index 375891b..67d206e 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h @@ -938,6 +938,14 @@ public: CompilerType GetTypeForFormatters(void *type) override; + // DIL + + bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type) override; + + llvm::Expected<CompilerType> + DoIntegralPromotion(CompilerType from, + ExecutionContextScope *exe_scope) override; + #define LLDB_INVALID_DECL_LEVEL UINT32_MAX // LLDB_INVALID_DECL_LEVEL is returned by CountDeclLevels if child_decl_ctx // could not be found in decl_ctx. diff --git a/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp b/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp index 397c693..19ae1cf 100644 --- a/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp +++ b/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.cpp @@ -25,6 +25,8 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/Status.h" #include "lldb/Utility/StreamString.h" +#include "llvm/ADT/SmallSet.h" +#include <deque> using namespace lldb; using namespace lldb_private; @@ -50,6 +52,33 @@ bool UnwindAssemblyInstEmulation::GetNonCallSiteUnwindPlanFromAssembly( range, function_text.data(), function_text.size(), unwind_plan); } +static void DumpUnwindRowsToLog(Log *log, AddressRange range, + const UnwindPlan &unwind_plan) { + if (!log || !log->GetVerbose()) + return; + StreamString strm; + lldb::addr_t base_addr = range.GetBaseAddress().GetFileAddress(); + strm.Printf("Resulting unwind rows for [0x%" PRIx64 " - 0x%" PRIx64 "):", + base_addr, base_addr + range.GetByteSize()); + unwind_plan.Dump(strm, nullptr, base_addr); + log->PutString(strm.GetString()); +} + +static void DumpInstToLog(Log *log, Instruction &inst, + const InstructionList &inst_list) { + if (!log || !log->GetVerbose()) + return; + const bool show_address = true; + const bool show_bytes = true; + const bool show_control_flow_kind = false; + StreamString strm; + lldb_private::FormatEntity::Entry format; + FormatEntity::Parse("${frame.pc}: ", format); + inst.Dump(&strm, inst_list.GetMaxOpcocdeByteSize(), show_address, show_bytes, + show_control_flow_kind, nullptr, nullptr, nullptr, &format, 0); + log->PutString(strm.GetString()); +} + bool UnwindAssemblyInstEmulation::GetNonCallSiteUnwindPlanFromAssembly( AddressRange &range, uint8_t *opcode_data, size_t opcode_size, UnwindPlan &unwind_plan) { @@ -82,11 +111,6 @@ bool UnwindAssemblyInstEmulation::GetNonCallSiteUnwindPlanFromAssembly( m_range_ptr = ⦥ m_unwind_plan_ptr = &unwind_plan; - const uint32_t addr_byte_size = m_arch.GetAddressByteSize(); - const bool show_address = true; - const bool show_bytes = true; - const bool show_control_flow_kind = false; - m_state.cfa_reg_info = *m_inst_emulator_up->GetRegisterInfo( unwind_plan.GetRegisterKind(), unwind_plan.GetInitialCFARegister()); m_state.fp_is_cfa = false; @@ -94,134 +118,142 @@ bool UnwindAssemblyInstEmulation::GetNonCallSiteUnwindPlanFromAssembly( m_pushed_regs.clear(); - // Initialize the CFA with a known value. In the 32 bit case it will be - // 0x80000000, and in the 64 bit case 0x8000000000000000. We use the address - // byte size to be safe for any future address sizes - m_initial_sp = (1ull << ((addr_byte_size * 8) - 1)); RegisterValue cfa_reg_value; - cfa_reg_value.SetUInt(m_initial_sp, m_state.cfa_reg_info.byte_size); + cfa_reg_value.SetUInt(m_initial_cfa, m_state.cfa_reg_info.byte_size); SetRegisterValue(m_state.cfa_reg_info, cfa_reg_value); - const InstructionList &inst_list = disasm_sp->GetInstructionList(); - const size_t num_instructions = inst_list.GetSize(); - - if (num_instructions > 0) { - Instruction *inst = inst_list.GetInstructionAtIndex(0).get(); - const lldb::addr_t base_addr = inst->GetAddress().GetFileAddress(); - - // Map for storing the unwind state at a given offset. When we see a forward - // branch we add a new entry to this map with the actual unwind plan row and - // register context for the target address of the branch as the current data - // have to be valid for the target address of the branch too if we are in - // the same function. - std::map<lldb::addr_t, UnwindState> saved_unwind_states; - - // Make a copy of the current instruction Row and save it in m_state so - // we can add updates as we process the instructions. - m_state.row = *unwind_plan.GetLastRow(); - - // Add the initial state to the save list with offset 0. - auto condition_block_start_state = - saved_unwind_states.emplace(0, m_state).first; - - // The architecture dependent condition code of the last processed - // instruction. - EmulateInstruction::InstructionCondition last_condition = - EmulateInstruction::UnconditionalCondition; - - for (size_t idx = 0; idx < num_instructions; ++idx) { - m_curr_row_modified = false; - m_forward_branch_offset = 0; - - inst = inst_list.GetInstructionAtIndex(idx).get(); - if (!inst) - continue; - - lldb::addr_t current_offset = - inst->GetAddress().GetFileAddress() - base_addr; - auto it = saved_unwind_states.upper_bound(current_offset); - assert(it != saved_unwind_states.begin() && - "Unwind row for the function entry missing"); - --it; // Move it to the row corresponding to the current offset - - // If the offset of m_curr_row don't match with the offset we see in - // saved_unwind_states then we have to update current unwind state to - // the saved values. It is happening after we processed an epilogue and a - // return to caller instruction. - if (it->second.row.GetOffset() != m_state.row.GetOffset()) - m_state = it->second; - - m_inst_emulator_up->SetInstruction(inst->GetOpcode(), inst->GetAddress(), - nullptr); - - if (last_condition != m_inst_emulator_up->GetInstructionCondition()) { - // If the last instruction was conditional with a different condition - // than the current condition then restore the state. - if (last_condition != EmulateInstruction::UnconditionalCondition) { - m_state = condition_block_start_state->second; - m_state.row.SetOffset(current_offset); - // The last instruction might already created a row for this offset - // and we want to overwrite it. - saved_unwind_states.insert_or_assign(current_offset, m_state); - } + InstructionList inst_list = disasm_sp->GetInstructionList(); - // We are starting a new conditional block at the actual offset - condition_block_start_state = it; - } + if (inst_list.GetSize() == 0) { + DumpUnwindRowsToLog(log, range, unwind_plan); + return unwind_plan.GetRowCount() > 0; + } - if (log && log->GetVerbose()) { - StreamString strm; - lldb_private::FormatEntity::Entry format; - FormatEntity::Parse("${frame.pc}: ", format); - inst->Dump(&strm, inst_list.GetMaxOpcocdeByteSize(), show_address, - show_bytes, show_control_flow_kind, nullptr, nullptr, - nullptr, &format, 0); - log->PutString(strm.GetString()); - } + Instruction &first_inst = *inst_list.GetInstructionAtIndex(0); + const lldb::addr_t base_addr = first_inst.GetAddress().GetFileAddress(); + + // Map for storing the unwind state at a given offset. When we see a forward + // branch we add a new entry to this map with the actual unwind plan row and + // register context for the target address of the branch as the current data + // have to be valid for the target address of the branch too if we are in + // the same function. + std::map<lldb::addr_t, UnwindState> saved_unwind_states; - last_condition = m_inst_emulator_up->GetInstructionCondition(); + // Make a copy of the current instruction Row and save it in m_state so + // we can add updates as we process the instructions. + m_state.row = *unwind_plan.GetLastRow(); - m_inst_emulator_up->EvaluateInstruction( - eEmulateInstructionOptionIgnoreConditions); + // Add the initial state to the save list with offset 0. + auto condition_block_start_state = + saved_unwind_states.emplace(0, m_state).first; - // If the current instruction is a branch forward then save the current - // CFI information for the offset where we are branching. - if (m_forward_branch_offset != 0 && - range.ContainsFileAddress(inst->GetAddress().GetFileAddress() + - m_forward_branch_offset)) { - if (auto [it, inserted] = saved_unwind_states.emplace( - current_offset + m_forward_branch_offset, m_state); - inserted) - it->second.row.SetOffset(current_offset + m_forward_branch_offset); + // The architecture dependent condition code of the last processed + // instruction. + EmulateInstruction::InstructionCondition last_condition = + EmulateInstruction::UnconditionalCondition; + + std::deque<std::size_t> to_visit = {0}; + llvm::SmallSet<std::size_t, 0> enqueued = {0}; + + // Instructions reachable through jumps are inserted on the front. + // The next instruction is inserted on the back. + // Pop from the back to ensure non-branching instructions are visited + // sequentially. + while (!to_visit.empty()) { + const std::size_t current_index = to_visit.back(); + Instruction &inst = *inst_list.GetInstructionAtIndex(current_index); + to_visit.pop_back(); + DumpInstToLog(log, inst, inst_list); + + m_curr_row_modified = false; + m_branch_offset = 0; + + lldb::addr_t current_offset = + inst.GetAddress().GetFileAddress() - base_addr; + auto it = saved_unwind_states.upper_bound(current_offset); + assert(it != saved_unwind_states.begin() && + "Unwind row for the function entry missing"); + --it; // Move it to the row corresponding to the current offset + + // When state is forwarded through a branch, the offset of m_state.row is + // different from the offset available in saved_unwind_states. Use the + // forwarded state in this case, as the previous instruction may have been + // an unconditional jump. + // FIXME: this assignment can always be done unconditionally. + if (it->second.row.GetOffset() != m_state.row.GetOffset()) + m_state = it->second; + + m_inst_emulator_up->SetInstruction(inst.GetOpcode(), inst.GetAddress(), + nullptr); + const EmulateInstruction::InstructionCondition new_condition = + m_inst_emulator_up->GetInstructionCondition(); + + if (last_condition != new_condition) { + // If the last instruction was conditional with a different condition + // than the current condition then restore the state. + if (last_condition != EmulateInstruction::UnconditionalCondition) { + m_state = condition_block_start_state->second; + m_state.row.SetOffset(current_offset); + // The last instruction might already created a row for this offset + // and we want to overwrite it. + saved_unwind_states.insert_or_assign(current_offset, m_state); } - // Were there any changes to the CFI while evaluating this instruction? - if (m_curr_row_modified) { - // Save the modified row if we don't already have a CFI row in the - // current address - if (saved_unwind_states.count(current_offset + - inst->GetOpcode().GetByteSize()) == 0) { - m_state.row.SetOffset(current_offset + - inst->GetOpcode().GetByteSize()); - saved_unwind_states.emplace( - current_offset + inst->GetOpcode().GetByteSize(), m_state); + // We are starting a new conditional block at the actual offset + condition_block_start_state = it; + } + + last_condition = new_condition; + + m_inst_emulator_up->EvaluateInstruction( + eEmulateInstructionOptionIgnoreConditions); + + // If the current instruction is a branch forward then save the current + // CFI information for the offset where we are branching. + Address branch_address = inst.GetAddress(); + branch_address.Slide(m_branch_offset); + if (m_branch_offset != 0 && + range.ContainsFileAddress(branch_address.GetFileAddress())) { + if (auto [it, inserted] = saved_unwind_states.emplace( + current_offset + m_branch_offset, m_state); + inserted) { + it->second.row.SetOffset(current_offset + m_branch_offset); + if (std::size_t dest_instr_index = + inst_list.GetIndexOfInstructionAtAddress(branch_address); + dest_instr_index < inst_list.GetSize()) { + to_visit.push_front(dest_instr_index); + enqueued.insert(dest_instr_index); } } } - for (auto &[_, state] : saved_unwind_states) { - unwind_plan.InsertRow(std::move(state.row), - /*replace_existing=*/true); + + // If inst is a barrier, do not propagate state to the next instruction. + if (inst.IsBarrier()) + continue; + + // Were there any changes to the CFI while evaluating this instruction? + if (m_curr_row_modified) { + // Save the modified row if we don't already have a CFI row in the + // current address + const lldb::addr_t next_inst_offset = + current_offset + inst.GetOpcode().GetByteSize(); + if (saved_unwind_states.count(next_inst_offset) == 0) { + m_state.row.SetOffset(next_inst_offset); + saved_unwind_states.emplace(next_inst_offset, m_state); + } } - } - if (log && log->GetVerbose()) { - StreamString strm; - lldb::addr_t base_addr = range.GetBaseAddress().GetFileAddress(); - strm.Printf("Resulting unwind rows for [0x%" PRIx64 " - 0x%" PRIx64 "):", - base_addr, base_addr + range.GetByteSize()); - unwind_plan.Dump(strm, nullptr, base_addr); - log->PutString(strm.GetString()); + const size_t next_idx = current_index + 1; + const bool never_enqueued = enqueued.insert(next_idx).second; + if (never_enqueued && next_idx < inst_list.GetSize()) + to_visit.push_back(next_idx); } + + for (auto &[_, state] : saved_unwind_states) + unwind_plan.InsertRow(std::move(state.row), + /*replace_existing=*/true); + + DumpUnwindRowsToLog(log, range, unwind_plan); return unwind_plan.GetRowCount() > 0; } @@ -382,7 +414,7 @@ size_t UnwindAssemblyInstEmulation::WriteMemory( if (reg_num != LLDB_INVALID_REGNUM && generic_regnum != LLDB_REGNUM_GENERIC_SP) { if (m_pushed_regs.try_emplace(reg_num, addr).second) { - const int32_t offset = addr - m_initial_sp; + const int32_t offset = addr - m_initial_cfa; m_state.row.SetRegisterLocationToAtCFAPlusOffset(reg_num, offset, /*can_replace=*/true); m_curr_row_modified = true; @@ -502,21 +534,20 @@ bool UnwindAssemblyInstEmulation::WriteRegister( case EmulateInstruction::eContextAbsoluteBranchRegister: case EmulateInstruction::eContextRelativeBranchImmediate: { if (context.GetInfoType() == EmulateInstruction::eInfoTypeISAAndImmediate && - context.info.ISAAndImmediate.unsigned_data32 > 0) { - m_forward_branch_offset = - context.info.ISAAndImmediateSigned.signed_data32; + context.info.ISAAndImmediate.unsigned_data32 != 0) { + m_branch_offset = context.info.ISAAndImmediate.unsigned_data32; } else if (context.GetInfoType() == EmulateInstruction::eInfoTypeISAAndImmediateSigned && - context.info.ISAAndImmediateSigned.signed_data32 > 0) { - m_forward_branch_offset = context.info.ISAAndImmediate.unsigned_data32; + context.info.ISAAndImmediateSigned.signed_data32 != 0) { + m_branch_offset = context.info.ISAAndImmediateSigned.signed_data32; } else if (context.GetInfoType() == EmulateInstruction::eInfoTypeImmediate && - context.info.unsigned_immediate > 0) { - m_forward_branch_offset = context.info.unsigned_immediate; + context.info.unsigned_immediate != 0) { + m_branch_offset = context.info.unsigned_immediate; } else if (context.GetInfoType() == EmulateInstruction::eInfoTypeImmediateSigned && - context.info.signed_immediate > 0) { - m_forward_branch_offset = context.info.signed_immediate; + context.info.signed_immediate != 0) { + m_branch_offset = context.info.signed_immediate; } } break; @@ -549,7 +580,7 @@ bool UnwindAssemblyInstEmulation::WriteRegister( sp_reg_info.kinds[m_unwind_plan_ptr->GetRegisterKind()]; assert(cfa_reg_num != LLDB_INVALID_REGNUM); m_state.row.GetCFAValue().SetIsRegisterPlusOffset( - cfa_reg_num, m_initial_sp - sp_reg_val.GetAsUInt64()); + cfa_reg_num, m_initial_cfa - sp_reg_val.GetAsUInt64()); } } } @@ -580,7 +611,7 @@ bool UnwindAssemblyInstEmulation::WriteRegister( reg_info->kinds[m_unwind_plan_ptr->GetRegisterKind()]; assert(cfa_reg_num != LLDB_INVALID_REGNUM); m_state.row.GetCFAValue().SetIsRegisterPlusOffset( - cfa_reg_num, m_initial_sp - reg_value.GetAsUInt64()); + cfa_reg_num, m_initial_cfa - reg_value.GetAsUInt64()); m_curr_row_modified = true; } break; @@ -593,7 +624,7 @@ bool UnwindAssemblyInstEmulation::WriteRegister( reg_info->kinds[m_unwind_plan_ptr->GetRegisterKind()]; assert(cfa_reg_num != LLDB_INVALID_REGNUM); m_state.row.GetCFAValue().SetIsRegisterPlusOffset( - cfa_reg_num, m_initial_sp - reg_value.GetAsUInt64()); + cfa_reg_num, m_initial_cfa - reg_value.GetAsUInt64()); m_curr_row_modified = true; } break; @@ -604,7 +635,7 @@ bool UnwindAssemblyInstEmulation::WriteRegister( if (!m_state.fp_is_cfa) { m_state.row.GetCFAValue().SetIsRegisterPlusOffset( m_state.row.GetCFAValue().GetRegisterNumber(), - m_initial_sp - reg_value.GetAsUInt64()); + m_initial_cfa - reg_value.GetAsUInt64()); m_curr_row_modified = true; } break; diff --git a/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.h b/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.h index 96a0881..43daf1c 100644 --- a/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.h +++ b/lldb/source/Plugins/UnwindAssembly/InstEmulation/UnwindAssemblyInstEmulation.h @@ -63,13 +63,17 @@ private: UnwindAssemblyInstEmulation(const lldb_private::ArchSpec &arch, lldb_private::EmulateInstruction *inst_emulator) : UnwindAssembly(arch), m_inst_emulator_up(inst_emulator), - m_range_ptr(nullptr), m_unwind_plan_ptr(nullptr), m_initial_sp(0), - m_curr_row_modified(false), m_forward_branch_offset(0) { + m_range_ptr(nullptr), m_unwind_plan_ptr(nullptr), + m_curr_row_modified(false) { if (m_inst_emulator_up) { m_inst_emulator_up->SetBaton(this); m_inst_emulator_up->SetCallbacks(ReadMemory, WriteMemory, ReadRegister, WriteRegister); } + // Initialize the CFA with a known value. In the 32 bit case it will be + // 0x80000000, and in the 64 bit case 0x8000000000000000. We use the address + // byte size to be safe for any future address sizes + m_initial_cfa = (1ull << ((m_arch.GetAddressByteSize() * 8) - 1)); } static size_t @@ -134,8 +138,8 @@ private: lldb_private::AddressRange *m_range_ptr; lldb_private::UnwindPlan *m_unwind_plan_ptr; UnwindState m_state; + uint64_t m_initial_cfa; typedef std::map<uint64_t, uint64_t> PushedRegisterToAddrMap; - uint64_t m_initial_sp; PushedRegisterToAddrMap m_pushed_regs; // While processing the instruction stream, we need to communicate some state @@ -148,7 +152,7 @@ private: bool m_curr_row_modified; // The instruction is branching forward with the given offset. 0 value means // no branching. - uint32_t m_forward_branch_offset; + int64_t m_branch_offset = 0; }; #endif // LLDB_SOURCE_PLUGINS_UNWINDASSEMBLY_INSTEMULATION_UNWINDASSEMBLYINSTEMULATION_H diff --git a/lldb/source/Symbol/CompileUnit.cpp b/lldb/source/Symbol/CompileUnit.cpp index 166f111..703ef13 100644 --- a/lldb/source/Symbol/CompileUnit.cpp +++ b/lldb/source/Symbol/CompileUnit.cpp @@ -27,14 +27,14 @@ CompileUnit::CompileUnit(const lldb::ModuleSP &module_sp, void *user_data, language, is_optimized) {} CompileUnit::CompileUnit(const lldb::ModuleSP &module_sp, void *user_data, - lldb::SupportFileSP support_file_sp, + SupportFileNSP support_file_nsp, const lldb::user_id_t cu_sym_id, lldb::LanguageType language, lldb_private::LazyBool is_optimized, SupportFileList &&support_files) : ModuleChild(module_sp), UserID(cu_sym_id), m_user_data(user_data), m_language(language), m_flags(0), - m_primary_support_file_sp(support_file_sp), + m_primary_support_file_nsp(support_file_nsp), m_support_files(std::move(support_files)), m_is_optimized(is_optimized) { if (language != eLanguageTypeUnknown) m_flags.Set(flagsParsedLanguage); diff --git a/lldb/source/Symbol/CompilerType.cpp b/lldb/source/Symbol/CompilerType.cpp index c999ab2..1a39ea9 100644 --- a/lldb/source/Symbol/CompilerType.cpp +++ b/lldb/source/Symbol/CompilerType.cpp @@ -370,30 +370,10 @@ bool CompilerType::IsScalarOrUnscopedEnumerationType() const { } bool CompilerType::IsPromotableIntegerType() const { - // Unscoped enums are always considered as promotable, even if their - // underlying type does not need to be promoted (e.g. "int"). - if (IsUnscopedEnumerationType()) - return true; - - switch (GetBasicTypeEnumeration()) { - case lldb::eBasicTypeBool: - case lldb::eBasicTypeChar: - case lldb::eBasicTypeSignedChar: - case lldb::eBasicTypeUnsignedChar: - case lldb::eBasicTypeShort: - case lldb::eBasicTypeUnsignedShort: - case lldb::eBasicTypeWChar: - case lldb::eBasicTypeSignedWChar: - case lldb::eBasicTypeUnsignedWChar: - case lldb::eBasicTypeChar16: - case lldb::eBasicTypeChar32: - return true; - - default: - return false; - } - - llvm_unreachable("All cases handled above."); + if (IsValid()) + if (auto type_system_sp = GetTypeSystem()) + return type_system_sp->IsPromotableIntegerType(m_type); + return false; } bool CompilerType::IsPointerToVoid() const { diff --git a/lldb/source/Symbol/Function.cpp b/lldb/source/Symbol/Function.cpp index 2be1e389..11b823c 100644 --- a/lldb/source/Symbol/Function.cpp +++ b/lldb/source/Symbol/Function.cpp @@ -272,7 +272,7 @@ Function::Function(CompileUnit *comp_unit, lldb::user_id_t func_uid, Function::~Function() = default; -void Function::GetStartLineSourceInfo(SupportFileSP &source_file_sp, +void Function::GetStartLineSourceInfo(SupportFileNSP &source_file_sp, uint32_t &line_no) { line_no = 0; source_file_sp = std::make_shared<SupportFile>(); @@ -300,9 +300,9 @@ void Function::GetStartLineSourceInfo(SupportFileSP &source_file_sp, } } -llvm::Expected<std::pair<SupportFileSP, Function::SourceRange>> +llvm::Expected<std::pair<SupportFileNSP, Function::SourceRange>> Function::GetSourceInfo() { - SupportFileSP source_file_sp; + SupportFileNSP source_file_sp = std::make_shared<SupportFile>(); uint32_t start_line; GetStartLineSourceInfo(source_file_sp, start_line); LineTable *line_table = m_comp_unit->GetLineTable(); diff --git a/lldb/source/Symbol/LineEntry.cpp b/lldb/source/Symbol/LineEntry.cpp index c941a69..dcfbac8 100644 --- a/lldb/source/Symbol/LineEntry.cpp +++ b/lldb/source/Symbol/LineEntry.cpp @@ -14,7 +14,7 @@ using namespace lldb_private; LineEntry::LineEntry() - : range(), file_sp(std::make_shared<SupportFile>()), + : range(), synthetic(false), file_sp(std::make_shared<SupportFile>()), original_file_sp(std::make_shared<SupportFile>()), is_start_of_statement(0), is_start_of_basic_block(0), is_prologue_end(0), is_epilogue_begin(0), is_terminal_entry(0) {} @@ -33,7 +33,8 @@ void LineEntry::Clear() { } bool LineEntry::IsValid() const { - return range.GetBaseAddress().IsValid() && line != LLDB_INVALID_LINE_NUMBER; + return (range.GetBaseAddress().IsValid() || synthetic) && + line != LLDB_INVALID_LINE_NUMBER; } bool LineEntry::DumpStopContext(Stream *s, bool show_fullpaths) const { diff --git a/lldb/source/Symbol/LineTable.cpp b/lldb/source/Symbol/LineTable.cpp index ca3accd..ae2abf5 100644 --- a/lldb/source/Symbol/LineTable.cpp +++ b/lldb/source/Symbol/LineTable.cpp @@ -327,7 +327,7 @@ void LineTable::Dump(Stream *s, Target *target, Address::DumpStyle style, Address::DumpStyle fallback_style, bool show_line_ranges) { const size_t count = m_entries.size(); LineEntry line_entry; - SupportFileSP prev_file; + SupportFileNSP prev_file = std::make_shared<SupportFile>(); for (size_t idx = 0; idx < count; ++idx) { ConvertEntryAtIndexToLineEntry(idx, line_entry); line_entry.Dump(s, target, !prev_file->Equal(*line_entry.original_file_sp), diff --git a/lldb/source/Symbol/ObjectFile.cpp b/lldb/source/Symbol/ObjectFile.cpp index 6f5348c..ab28c17 100644 --- a/lldb/source/Symbol/ObjectFile.cpp +++ b/lldb/source/Symbol/ObjectFile.cpp @@ -254,13 +254,14 @@ ObjectFile::ObjectFile(const lldb::ModuleSP &module_sp, : ModuleChild(module_sp), m_file(), // This file could be different from the original module's file m_type(eTypeInvalid), m_strata(eStrataInvalid), - m_file_offset(file_offset), m_length(length), m_data(), m_process_wp(), + m_file_offset(file_offset), m_length(length), + m_data_nsp(std::make_shared<DataExtractor>()), m_process_wp(), m_memory_addr(LLDB_INVALID_ADDRESS), m_sections_up(), m_symtab_up(), m_symtab_once_up(new llvm::once_flag()) { if (file_spec_ptr) m_file = *file_spec_ptr; if (data_sp) - m_data.SetData(data_sp, data_offset, length); + m_data_nsp->SetData(data_sp, data_offset, length); Log *log = GetLog(LLDBLog::Object); LLDB_LOGF(log, "%p ObjectFile::ObjectFile() module = %p (%s), file = %s, " @@ -275,11 +276,12 @@ ObjectFile::ObjectFile(const lldb::ModuleSP &module_sp, const ProcessSP &process_sp, lldb::addr_t header_addr, DataBufferSP header_data_sp) : ModuleChild(module_sp), m_file(), m_type(eTypeInvalid), - m_strata(eStrataInvalid), m_file_offset(0), m_length(0), m_data(), - m_process_wp(process_sp), m_memory_addr(header_addr), m_sections_up(), - m_symtab_up(), m_symtab_once_up(new llvm::once_flag()) { + m_strata(eStrataInvalid), m_file_offset(0), m_length(0), + m_data_nsp(std::make_shared<DataExtractor>()), m_process_wp(process_sp), + m_memory_addr(header_addr), m_sections_up(), m_symtab_up(), + m_symtab_once_up(new llvm::once_flag()) { if (header_data_sp) - m_data.SetData(header_data_sp, 0, header_data_sp->GetByteSize()); + m_data_nsp->SetData(header_data_sp, 0, header_data_sp->GetByteSize()); Log *log = GetLog(LLDBLog::Object); LLDB_LOGF(log, "%p ObjectFile::ObjectFile() module = %p (%s), process = %p, " @@ -474,16 +476,16 @@ WritableDataBufferSP ObjectFile::ReadMemory(const ProcessSP &process_sp, size_t ObjectFile::GetData(lldb::offset_t offset, size_t length, DataExtractor &data) const { - // The entire file has already been mmap'ed into m_data, so just copy from + // The entire file has already been mmap'ed into m_data_nsp, so just copy from // there as the back mmap buffer will be shared with shared pointers. - return data.SetData(m_data, offset, length); + return data.SetData(*m_data_nsp.get(), offset, length); } size_t ObjectFile::CopyData(lldb::offset_t offset, size_t length, void *dst) const { - // The entire file has already been mmap'ed into m_data, so just copy from + // The entire file has already been mmap'ed into m_data_nsp, so just copy from // there Note that the data remains in target byte order. - return m_data.CopyData(offset, length, dst); + return m_data_nsp->CopyData(offset, length, dst); } size_t ObjectFile::ReadSectionData(Section *section, diff --git a/lldb/source/Symbol/SymbolContext.cpp b/lldb/source/Symbol/SymbolContext.cpp index 3bbd1ef..ead924a 100644 --- a/lldb/source/Symbol/SymbolContext.cpp +++ b/lldb/source/Symbol/SymbolContext.cpp @@ -324,12 +324,32 @@ uint32_t SymbolContext::GetResolvedMask() const { bool lldb_private::operator==(const SymbolContext &lhs, const SymbolContext &rhs) { - return lhs.function == rhs.function && lhs.symbol == rhs.symbol && + return SymbolContext::CompareWithoutSymbol(lhs, rhs) && + lhs.symbol == rhs.symbol; +} + +bool SymbolContext::CompareConsideringPossiblyNullSymbol( + const SymbolContext &lhs, const SymbolContext &rhs) { + if (!CompareWithoutSymbol(lhs, rhs)) + return false; + + // If one (or both) of the symbol context's symbol is empty, consider them + // equal. + if (!lhs.symbol || !rhs.symbol) + return true; + + // If both symbols are present, make sure they're the same. + return lhs.symbol == rhs.symbol; +} + +bool SymbolContext::CompareWithoutSymbol(const SymbolContext &lhs, + const SymbolContext &rhs) { + return lhs.function == rhs.function && lhs.module_sp.get() == rhs.module_sp.get() && lhs.comp_unit == rhs.comp_unit && lhs.target_sp.get() == rhs.target_sp.get() && LineEntry::Compare(lhs.line_entry, rhs.line_entry) == 0 && - lhs.variable == rhs.variable; + lhs.variable == rhs.variable && lhs.block == rhs.block; } bool lldb_private::operator!=(const SymbolContext &lhs, @@ -1200,7 +1220,10 @@ bool SymbolContextList::AppendIfUnique(const SymbolContext &sc, bool merge_symbol_into_function) { collection::iterator pos, end = m_symbol_contexts.end(); for (pos = m_symbol_contexts.begin(); pos != end; ++pos) { - if (*pos == sc) + // Because symbol contexts might first be built without the symbol, + // which is then appended later on, compare the symbol contexts taking into + // accout that one (or either) of them might not have a symbol yet. + if (SymbolContext::CompareConsideringPossiblyNullSymbol(*pos, sc)) return false; } if (merge_symbol_into_function && sc.symbol != nullptr && diff --git a/lldb/source/Symbol/SymbolFile.cpp b/lldb/source/Symbol/SymbolFile.cpp index 870d778d..bfc6300 100644 --- a/lldb/source/Symbol/SymbolFile.cpp +++ b/lldb/source/Symbol/SymbolFile.cpp @@ -126,6 +126,14 @@ void SymbolFile::FindFunctions(const Module::LookupInfo &lookup_info, bool include_inlines, SymbolContextList &sc_list) {} +void SymbolFile::FindFunctions(llvm::ArrayRef<Module::LookupInfo> lookup_infos, + const CompilerDeclContext &parent_decl_ctx, + bool include_inlines, + SymbolContextList &sc_list) { + for (const auto &lookup_info : lookup_infos) + FindFunctions(lookup_info, parent_decl_ctx, include_inlines, sc_list); +} + void SymbolFile::FindFunctions(const RegularExpression ®ex, bool include_inlines, SymbolContextList &sc_list) {} diff --git a/lldb/source/Symbol/Symtab.cpp b/lldb/source/Symbol/Symtab.cpp index 6080703..9964ae4 100644 --- a/lldb/source/Symbol/Symtab.cpp +++ b/lldb/source/Symbol/Symtab.cpp @@ -722,15 +722,11 @@ Symtab::AppendSymbolIndexesWithNameAndType(ConstString symbol_name, std::vector<uint32_t> &indexes) { std::lock_guard<std::recursive_mutex> guard(m_mutex); - if (AppendSymbolIndexesWithName(symbol_name, indexes) > 0) { - std::vector<uint32_t>::iterator pos = indexes.begin(); - while (pos != indexes.end()) { - if (symbol_type == eSymbolTypeAny || - m_symbols[*pos].GetType() == symbol_type) - ++pos; - else - pos = indexes.erase(pos); - } + if (AppendSymbolIndexesWithName(symbol_name, indexes) > 0 && + symbol_type != eSymbolTypeAny) { + llvm::erase_if(indexes, [this, symbol_type](uint32_t index) { + return m_symbols[index].GetType() != symbol_type; + }); } return indexes.size(); } @@ -742,15 +738,11 @@ uint32_t Symtab::AppendSymbolIndexesWithNameAndType( std::lock_guard<std::recursive_mutex> guard(m_mutex); if (AppendSymbolIndexesWithName(symbol_name, symbol_debug_type, - symbol_visibility, indexes) > 0) { - std::vector<uint32_t>::iterator pos = indexes.begin(); - while (pos != indexes.end()) { - if (symbol_type == eSymbolTypeAny || - m_symbols[*pos].GetType() == symbol_type) - ++pos; - else - pos = indexes.erase(pos); - } + symbol_visibility, indexes) > 0 && + symbol_type != eSymbolTypeAny) { + llvm::erase_if(indexes, [this, symbol_type](uint32_t index) { + return m_symbols[index].GetType() != symbol_type; + }); } return indexes.size(); } diff --git a/lldb/source/Symbol/TypeSystem.cpp b/lldb/source/Symbol/TypeSystem.cpp index f7d634f..8712142 100644 --- a/lldb/source/Symbol/TypeSystem.cpp +++ b/lldb/source/Symbol/TypeSystem.cpp @@ -123,6 +123,17 @@ CompilerType TypeSystem::GetTypeForFormatters(void *type) { return CompilerType(weak_from_this(), type); } +bool TypeSystem::IsPromotableIntegerType(lldb::opaque_compiler_type_t type) { + return false; +} + +llvm::Expected<CompilerType> +TypeSystem::DoIntegralPromotion(CompilerType from, + ExecutionContextScope *exe_scope) { + return llvm::createStringError( + "Integral promotion is not implemented for this TypeSystem"); +} + bool TypeSystem::IsTemplateType(lldb::opaque_compiler_type_t type) { return false; } diff --git a/lldb/source/Target/BorrowedStackFrame.cpp b/lldb/source/Target/BorrowedStackFrame.cpp new file mode 100644 index 0000000..5afadf2 --- /dev/null +++ b/lldb/source/Target/BorrowedStackFrame.cpp @@ -0,0 +1,187 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Target/BorrowedStackFrame.h" + +using namespace lldb; +using namespace lldb_private; + +char BorrowedStackFrame::ID; + +BorrowedStackFrame::BorrowedStackFrame( + StackFrameSP borrowed_frame_sp, uint32_t new_frame_index, + std::optional<uint32_t> new_concrete_frame_index) + : StackFrame( + borrowed_frame_sp->GetThread(), new_frame_index, + borrowed_frame_sp->GetConcreteFrameIndex(), + borrowed_frame_sp->GetRegisterContextSP(), + borrowed_frame_sp->GetStackID().GetPC(), + borrowed_frame_sp->GetStackID().GetCallFrameAddressWithoutMetadata(), + borrowed_frame_sp->m_behaves_like_zeroth_frame, + &borrowed_frame_sp->GetSymbolContext(eSymbolContextEverything)), + m_borrowed_frame_sp(borrowed_frame_sp), + m_new_frame_index(new_frame_index) { + if (new_concrete_frame_index) + m_new_concrete_frame_index = *new_concrete_frame_index; + else + m_new_concrete_frame_index = + IsInlined() ? LLDB_INVALID_FRAME_ID : new_frame_index; +} + +uint32_t BorrowedStackFrame::GetFrameIndex() const { return m_new_frame_index; } + +void BorrowedStackFrame::SetFrameIndex(uint32_t index) { + m_new_frame_index = index; +} + +uint32_t BorrowedStackFrame::GetConcreteFrameIndex() { + // FIXME: We need to find where the concrete frame into which this frame was + // inlined landed in the new stack frame list as that is the correct concrete + // frame index in this + // stack frame. + return m_new_concrete_frame_index; +} + +StackID &BorrowedStackFrame::GetStackID() { + return m_borrowed_frame_sp->GetStackID(); +} + +const Address &BorrowedStackFrame::GetFrameCodeAddress() { + return m_borrowed_frame_sp->GetFrameCodeAddress(); +} + +Address BorrowedStackFrame::GetFrameCodeAddressForSymbolication() { + return m_borrowed_frame_sp->GetFrameCodeAddressForSymbolication(); +} + +bool BorrowedStackFrame::ChangePC(addr_t pc) { + return m_borrowed_frame_sp->ChangePC(pc); +} + +const SymbolContext & +BorrowedStackFrame::GetSymbolContext(SymbolContextItem resolve_scope) { + return m_borrowed_frame_sp->GetSymbolContext(resolve_scope); +} + +llvm::Error BorrowedStackFrame::GetFrameBaseValue(Scalar &value) { + return m_borrowed_frame_sp->GetFrameBaseValue(value); +} + +DWARFExpressionList * +BorrowedStackFrame::GetFrameBaseExpression(Status *error_ptr) { + return m_borrowed_frame_sp->GetFrameBaseExpression(error_ptr); +} + +Block *BorrowedStackFrame::GetFrameBlock() { + return m_borrowed_frame_sp->GetFrameBlock(); +} + +RegisterContextSP BorrowedStackFrame::GetRegisterContext() { + return m_borrowed_frame_sp->GetRegisterContext(); +} + +VariableList *BorrowedStackFrame::GetVariableList(bool get_file_globals, + Status *error_ptr) { + return m_borrowed_frame_sp->GetVariableList(get_file_globals, error_ptr); +} + +VariableListSP +BorrowedStackFrame::GetInScopeVariableList(bool get_file_globals, + bool must_have_valid_location) { + return m_borrowed_frame_sp->GetInScopeVariableList(get_file_globals, + must_have_valid_location); +} + +ValueObjectSP BorrowedStackFrame::GetValueForVariableExpressionPath( + llvm::StringRef var_expr, DynamicValueType use_dynamic, uint32_t options, + VariableSP &var_sp, Status &error) { + return m_borrowed_frame_sp->GetValueForVariableExpressionPath( + var_expr, use_dynamic, options, var_sp, error); +} + +bool BorrowedStackFrame::HasDebugInformation() { + return m_borrowed_frame_sp->HasDebugInformation(); +} + +const char *BorrowedStackFrame::Disassemble() { + return m_borrowed_frame_sp->Disassemble(); +} + +ValueObjectSP BorrowedStackFrame::GetValueObjectForFrameVariable( + const VariableSP &variable_sp, DynamicValueType use_dynamic) { + return m_borrowed_frame_sp->GetValueObjectForFrameVariable(variable_sp, + use_dynamic); +} + +bool BorrowedStackFrame::IsInlined() { + return m_borrowed_frame_sp->IsInlined(); +} + +bool BorrowedStackFrame::IsSynthetic() const { + return m_borrowed_frame_sp->IsSynthetic(); +} + +bool BorrowedStackFrame::IsHistorical() const { + return m_borrowed_frame_sp->IsHistorical(); +} + +bool BorrowedStackFrame::IsArtificial() const { + return m_borrowed_frame_sp->IsArtificial(); +} + +bool BorrowedStackFrame::IsHidden() { return m_borrowed_frame_sp->IsHidden(); } + +const char *BorrowedStackFrame::GetFunctionName() { + return m_borrowed_frame_sp->GetFunctionName(); +} + +const char *BorrowedStackFrame::GetDisplayFunctionName() { + return m_borrowed_frame_sp->GetDisplayFunctionName(); +} + +ValueObjectSP BorrowedStackFrame::FindVariable(ConstString name) { + return m_borrowed_frame_sp->FindVariable(name); +} + +SourceLanguage BorrowedStackFrame::GetLanguage() { + return m_borrowed_frame_sp->GetLanguage(); +} + +SourceLanguage BorrowedStackFrame::GuessLanguage() { + return m_borrowed_frame_sp->GuessLanguage(); +} + +ValueObjectSP BorrowedStackFrame::GuessValueForAddress(addr_t addr) { + return m_borrowed_frame_sp->GuessValueForAddress(addr); +} + +ValueObjectSP +BorrowedStackFrame::GuessValueForRegisterAndOffset(ConstString reg, + int64_t offset) { + return m_borrowed_frame_sp->GuessValueForRegisterAndOffset(reg, offset); +} + +StructuredData::ObjectSP BorrowedStackFrame::GetLanguageSpecificData() { + return m_borrowed_frame_sp->GetLanguageSpecificData(); +} + +RecognizedStackFrameSP BorrowedStackFrame::GetRecognizedFrame() { + return m_borrowed_frame_sp->GetRecognizedFrame(); +} + +StackFrameSP BorrowedStackFrame::GetBorrowedFrame() const { + return m_borrowed_frame_sp; +} + +bool BorrowedStackFrame::isA(const void *ClassID) const { + return ClassID == &ID || StackFrame::isA(ClassID); +} + +bool BorrowedStackFrame::classof(const StackFrame *obj) { + return obj->isA(&ID); +} diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt index 8e6d51e..df2ee03 100644 --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -38,8 +38,10 @@ add_lldb_library(lldbTarget RegisterNumber.cpp RemoteAwarePlatform.cpp ScriptedThreadPlan.cpp + SyntheticFrameProvider.cpp SectionLoadHistory.cpp SectionLoadList.cpp + BorrowedStackFrame.cpp StackFrame.cpp StackFrameList.cpp StackFrameRecognizer.cpp diff --git a/lldb/source/Target/ExecutionContext.cpp b/lldb/source/Target/ExecutionContext.cpp index a795913..b16ff26 100644 --- a/lldb/source/Target/ExecutionContext.cpp +++ b/lldb/source/Target/ExecutionContext.cpp @@ -466,10 +466,13 @@ operator=(const ExecutionContext &exe_ctx) { else m_tid = LLDB_INVALID_THREAD_ID; lldb::StackFrameSP frame_sp(exe_ctx.GetFrameSP()); - if (frame_sp) + if (frame_sp) { m_stack_id = frame_sp->GetStackID(); - else + m_frame_list_wp = frame_sp->GetContainingStackFrameList(); + } else { m_stack_id.Clear(); + m_frame_list_wp.reset(); + } return *this; } @@ -511,6 +514,7 @@ void ExecutionContextRef::SetThreadSP(const lldb::ThreadSP &thread_sp) { void ExecutionContextRef::SetFrameSP(const lldb::StackFrameSP &frame_sp) { if (frame_sp) { m_stack_id = frame_sp->GetStackID(); + m_frame_list_wp = frame_sp->GetContainingStackFrameList(); SetThreadSP(frame_sp->GetThread()); } else { ClearFrame(); @@ -638,6 +642,15 @@ lldb::ThreadSP ExecutionContextRef::GetThreadSP() const { lldb::StackFrameSP ExecutionContextRef::GetFrameSP() const { if (m_stack_id.IsValid()) { + // Try the remembered frame list first to avoid circular dependencies + // during frame provider initialization. + if (auto frame_list_sp = m_frame_list_wp.lock()) { + if (auto frame_sp = frame_list_sp->GetFrameWithStackID(m_stack_id)) + return frame_sp; + } + + // Fallback: ask the thread, which might re-trigger the frame provider + // initialization. lldb::ThreadSP thread_sp(GetThreadSP()); if (thread_sp) return thread_sp->GetFrameWithStackID(m_stack_id); diff --git a/lldb/source/Target/Language.cpp b/lldb/source/Target/Language.cpp index 8268d4a..c8b09c3 100644 --- a/lldb/source/Target/Language.cpp +++ b/lldb/source/Target/Language.cpp @@ -159,6 +159,48 @@ void Language::ForEach( } } +llvm::Expected<LanguageType> +Language::GetExceptionLanguageForLanguage(llvm::StringRef lang_name) { + LanguageType language = Language::GetLanguageTypeFromString(lang_name); + LanguageType exception_language = eLanguageTypeUnknown; + + llvm::StringRef error_context; + switch (language) { + case eLanguageTypeC89: + case eLanguageTypeC: + case eLanguageTypeC99: + case eLanguageTypeC11: + exception_language = eLanguageTypeC; + break; + case eLanguageTypeC_plus_plus: + case eLanguageTypeC_plus_plus_03: + case eLanguageTypeC_plus_plus_11: + case eLanguageTypeC_plus_plus_14: + exception_language = eLanguageTypeC_plus_plus; + break; + case eLanguageTypeObjC_plus_plus: + error_context = + "Set exception breakpoints separately for c++ and objective-c"; + break; + case eLanguageTypeUnknown: + error_context = "Unknown language type for exception breakpoint"; + break; + default: + if (Language *languagePlugin = Language::FindPlugin(language)) { + if (languagePlugin->SupportsExceptionBreakpointsOnThrow() || + languagePlugin->SupportsExceptionBreakpointsOnCatch()) { + exception_language = language; + break; + } + } + error_context = "Unsupported language type for exception breakpoint"; + } + if (!error_context.empty()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + error_context); + return exception_language; +} + bool Language::IsTopLevelFunction(Function &function) { return false; } lldb::TypeCategoryImplSP Language::GetFormatters() { return nullptr; } diff --git a/lldb/source/Target/ModuleCache.cpp b/lldb/source/Target/ModuleCache.cpp index f737836..9978946 100644 --- a/lldb/source/Target/ModuleCache.cpp +++ b/lldb/source/Target/ModuleCache.cpp @@ -255,7 +255,7 @@ Status ModuleCache::Get(const FileSpec &root_dir_spec, const char *hostname, cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); error = ModuleList::GetSharedModule(cached_module_spec, cached_module_sp, - nullptr, nullptr, did_create_ptr, false); + nullptr, did_create_ptr, false); if (error.Fail()) return error; diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp index 8681ada..5b0930c 100644 --- a/lldb/source/Target/Platform.cpp +++ b/lldb/source/Target/Platform.cpp @@ -163,11 +163,12 @@ Platform::LocateExecutableScriptingResources(Target *target, Module &module, Status Platform::GetSharedModule( const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr, llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) { if (IsHost()) - return ModuleList::GetSharedModule(module_spec, module_sp, - module_search_paths_ptr, old_modules, + // Note: module_search_paths_ptr functionality is now handled internally + // by getting target from module_spec and calling + // target->GetExecutableSearchPaths() + return ModuleList::GetSharedModule(module_spec, module_sp, old_modules, did_create_ptr, false); // Module resolver lambda. @@ -180,16 +181,14 @@ Status Platform::GetSharedModule( resolved_spec = spec; resolved_spec.GetFileSpec().PrependPathComponent(m_sdk_sysroot); // Try to get shared module with resolved spec. - error = ModuleList::GetSharedModule(resolved_spec, module_sp, - module_search_paths_ptr, old_modules, + error = ModuleList::GetSharedModule(resolved_spec, module_sp, old_modules, did_create_ptr, false); } // If we don't have sysroot or it didn't work then // try original module spec. if (!error.Success()) { resolved_spec = spec; - error = ModuleList::GetSharedModule(resolved_spec, module_sp, - module_search_paths_ptr, old_modules, + error = ModuleList::GetSharedModule(resolved_spec, module_sp, old_modules, did_create_ptr, false); } if (error.Success() && module_sp) @@ -731,10 +730,8 @@ bool Platform::SetOSVersion(llvm::VersionTuple version) { return false; } -Status -Platform::ResolveExecutable(const ModuleSpec &module_spec, - lldb::ModuleSP &exe_module_sp, - const FileSpecList *module_search_paths_ptr) { +Status Platform::ResolveExecutable(const ModuleSpec &module_spec, + lldb::ModuleSP &exe_module_sp) { // We may connect to a process and use the provided executable (Don't use // local $PATH). @@ -750,9 +747,8 @@ Platform::ResolveExecutable(const ModuleSpec &module_spec, if (resolved_module_spec.GetArchitecture().IsValid() || resolved_module_spec.GetUUID().IsValid()) { - Status error = - ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp, - module_search_paths_ptr, nullptr, nullptr); + Status error = ModuleList::GetSharedModule(resolved_module_spec, + exe_module_sp, nullptr, nullptr); if (exe_module_sp && exe_module_sp->GetObjectFile()) return error; @@ -767,9 +763,9 @@ Platform::ResolveExecutable(const ModuleSpec &module_spec, Status error; for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) { resolved_module_spec.GetArchitecture() = arch; - error = - ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp, - module_search_paths_ptr, nullptr, nullptr); + + error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp, + nullptr, nullptr); if (error.Success()) { if (exe_module_sp && exe_module_sp->GetObjectFile()) break; @@ -1446,16 +1442,13 @@ const std::vector<ConstString> &Platform::GetTrapHandlerSymbolNames() { return m_trap_handlers; } -Status -Platform::GetCachedExecutable(ModuleSpec &module_spec, - lldb::ModuleSP &module_sp, - const FileSpecList *module_search_paths_ptr) { +Status Platform::GetCachedExecutable(ModuleSpec &module_spec, + lldb::ModuleSP &module_sp) { FileSpec platform_spec = module_spec.GetFileSpec(); Status error = GetRemoteSharedModule( module_spec, nullptr, module_sp, [&](const ModuleSpec &spec) { - return Platform::ResolveExecutable(spec, module_sp, - module_search_paths_ptr); + return Platform::ResolveExecutable(spec, module_sp); }, nullptr); if (error.Success()) { @@ -1497,7 +1490,7 @@ Status Platform::GetRemoteSharedModule(const ModuleSpec &module_spec, for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) { arch_module_spec.GetArchitecture() = arch; error = ModuleList::GetSharedModule(arch_module_spec, module_sp, nullptr, - nullptr, nullptr); + nullptr); // Did we find an executable using one of the if (error.Success() && module_sp) break; @@ -1673,11 +1666,12 @@ void Platform::CallLocateModuleCallbackIfSet(const ModuleSpec &module_spec, cached_module_spec.GetUUID().Clear(); // Clear UUID since it may contain md5 // content hash instead of real UUID. cached_module_spec.GetFileSpec() = module_file_spec; + cached_module_spec.GetSymbolFileSpec() = symbol_file_spec; cached_module_spec.GetPlatformFileSpec() = module_spec.GetFileSpec(); cached_module_spec.SetObjectOffset(0); error = ModuleList::GetSharedModule(cached_module_spec, module_sp, nullptr, - nullptr, did_create_ptr, false); + did_create_ptr, false, false); if (error.Success() && module_sp) { // Succeeded to load the module file. LLDB_LOGF(log, "%s: locate module callback succeeded: module=%s symbol=%s", diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 42ce198..9c8e8fa7 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -2452,8 +2452,10 @@ size_t Process::ReadScalarIntegerFromMemory(addr_t addr, uint32_t byte_size, scalar = data.GetMaxU32(&offset, byte_size); else scalar = data.GetMaxU64(&offset, byte_size); - if (is_signed) + if (is_signed) { + scalar.MakeSigned(); scalar.SignExtend(byte_size * 8); + } return bytes_read; } } else { @@ -3256,6 +3258,7 @@ Status Process::ConnectRemote(llvm::StringRef remote_url) { if (state == eStateStopped || state == eStateCrashed) { // If we attached and actually have a process on the other end, then // this ended up being the equivalent of an attach. + SetShouldDetach(true); CompleteAttach(); // This delays passing the stopped event to listeners till @@ -6544,7 +6547,7 @@ Status Process::WriteMemoryTags(lldb::addr_t addr, size_t len, // Create a CoreFileMemoryRange from a MemoryRegionInfo static CoreFileMemoryRange -CreateCoreFileMemoryRange(const MemoryRegionInfo ®ion) { +CreateCoreFileMemoryRange(const lldb_private::MemoryRegionInfo ®ion) { const addr_t addr = region.GetRange().GetRangeBase(); llvm::AddressRange range(addr, addr + region.GetRange().GetByteSize()); return {range, region.GetLLDBPermissions()}; @@ -6553,7 +6556,7 @@ CreateCoreFileMemoryRange(const MemoryRegionInfo ®ion) { // Add dirty pages to the core file ranges and return true if dirty pages // were added. Return false if the dirty page information is not valid or in // the region. -static bool AddDirtyPages(const MemoryRegionInfo ®ion, +static bool AddDirtyPages(const lldb_private::MemoryRegionInfo ®ion, CoreFileMemoryRanges &ranges) { const auto &dirty_page_list = region.GetDirtyPageList(); if (!dirty_page_list) @@ -6592,8 +6595,8 @@ static bool AddDirtyPages(const MemoryRegionInfo ®ion, // given region. If the region has dirty page information, only dirty pages // will be added to \a ranges, else the entire range will be added to \a // ranges. -static void AddRegion(const MemoryRegionInfo ®ion, bool try_dirty_pages, - CoreFileMemoryRanges &ranges) { +static void AddRegion(const lldb_private::MemoryRegionInfo ®ion, + bool try_dirty_pages, CoreFileMemoryRanges &ranges) { // Don't add empty ranges. if (region.GetRange().GetByteSize() == 0) return; @@ -6616,7 +6619,7 @@ static void SaveDynamicLoaderSections(Process &process, if (!dyld) return; - std::vector<MemoryRegionInfo> dynamic_loader_mem_regions; + std::vector<lldb_private::MemoryRegionInfo> dynamic_loader_mem_regions; std::function<bool(const lldb_private::Thread &)> save_thread_predicate = [&](const lldb_private::Thread &t) -> bool { return options.ShouldThreadBeSaved(t.GetID()); @@ -6741,10 +6744,11 @@ static void GetCoreFileSaveRangesStackOnly(Process &process, // TODO: We should refactor CoreFileMemoryRanges to use the lldb range type, and // then add an intersect method on it, or MemoryRegionInfo. -static MemoryRegionInfo Intersect(const MemoryRegionInfo &lhs, - const MemoryRegionInfo::RangeType &rhs) { +static lldb_private::MemoryRegionInfo +Intersect(const lldb_private::MemoryRegionInfo &lhs, + const lldb_private::MemoryRegionInfo::RangeType &rhs) { - MemoryRegionInfo region_info; + lldb_private::MemoryRegionInfo region_info; region_info.SetLLDBPermissions(lhs.GetLLDBPermissions()); region_info.GetRange() = lhs.GetRange().Intersect(rhs); diff --git a/lldb/source/Target/RemoteAwarePlatform.cpp b/lldb/source/Target/RemoteAwarePlatform.cpp index cac738e..89b946b 100644 --- a/lldb/source/Target/RemoteAwarePlatform.cpp +++ b/lldb/source/Target/RemoteAwarePlatform.cpp @@ -29,9 +29,8 @@ bool RemoteAwarePlatform::GetModuleSpec(const FileSpec &module_file_spec, return false; } -Status RemoteAwarePlatform::ResolveExecutable( - const ModuleSpec &module_spec, lldb::ModuleSP &exe_module_sp, - const FileSpecList *module_search_paths_ptr) { +Status RemoteAwarePlatform::ResolveExecutable(const ModuleSpec &module_spec, + lldb::ModuleSP &exe_module_sp) { ModuleSpec resolved_module_spec(module_spec); // The host platform can resolve the path more aggressively. @@ -47,12 +46,10 @@ Status RemoteAwarePlatform::ResolveExecutable( if (!FileSystem::Instance().Exists(resolved_file_spec)) FileSystem::Instance().ResolveExecutableLocation(resolved_file_spec); } else if (m_remote_platform_sp) { - return GetCachedExecutable(resolved_module_spec, exe_module_sp, - module_search_paths_ptr); + return GetCachedExecutable(resolved_module_spec, exe_module_sp); } - return Platform::ResolveExecutable(resolved_module_spec, exe_module_sp, - module_search_paths_ptr); + return Platform::ResolveExecutable(resolved_module_spec, exe_module_sp); } Status RemoteAwarePlatform::RunShellCommand( diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp index 2ed58c53..3bbb851 100644 --- a/lldb/source/Target/StackFrame.cpp +++ b/lldb/source/Target/StackFrame.cpp @@ -45,6 +45,9 @@ using namespace lldb; using namespace lldb_private; +// LLVM RTTI support. +char StackFrame::ID; + // The first bits in the flags are reserved for the SymbolContext::Scope bits // so we know if we have tried to look up information in our internal symbol // context (m_sc) already. @@ -328,6 +331,13 @@ StackFrame::GetSymbolContext(SymbolContextItem resolve_scope) { // following the function call instruction... Address lookup_addr(GetFrameCodeAddressForSymbolication()); + // For PC-less frames (e.g., scripted frames), skip PC-based symbol + // resolution and preserve any already-populated SymbolContext fields. + if (!lookup_addr.IsValid()) { + m_flags.Set(resolve_scope | resolved); + return m_sc; + } + if (m_sc.module_sp) { // We have something in our stack frame symbol context, lets check if we // haven't already tried to lookup one of those things. If we haven't @@ -1344,18 +1354,18 @@ const char *StackFrame::GetDisplayFunctionName() { SourceLanguage StackFrame::GetLanguage() { CompileUnit *cu = GetSymbolContext(eSymbolContextCompUnit).comp_unit; if (cu) - return cu->GetLanguage(); + return SourceLanguage{cu->GetLanguage()}; return {}; } SourceLanguage StackFrame::GuessLanguage() { SourceLanguage lang_type = GetLanguage(); - if (lang_type == eLanguageTypeUnknown) { + if (!lang_type) { SymbolContext sc = GetSymbolContext(eSymbolContextFunction | eSymbolContextSymbol); if (sc.function) - lang_type = LanguageType(sc.function->GetMangled().GuessLanguage()); + lang_type = SourceLanguage(sc.function->GetMangled().GuessLanguage()); else if (sc.symbol) lang_type = SourceLanguage(sc.symbol->GetMangled().GuessLanguage()); } @@ -2054,10 +2064,10 @@ bool StackFrame::GetStatus(Stream &strm, bool show_frame_info, bool show_source, disasm_display = debugger.GetStopDisassemblyDisplay(); GetSymbolContext(eSymbolContextCompUnit | eSymbolContextLineEntry); - if (m_sc.comp_unit && m_sc.line_entry.IsValid()) { + if (m_sc.comp_unit || m_sc.line_entry.IsValid()) { have_debuginfo = true; if (source_lines_before > 0 || source_lines_after > 0) { - SupportFileSP source_file_sp = m_sc.line_entry.file_sp; + SupportFileNSP source_file_sp = m_sc.line_entry.file_sp; uint32_t start_line = m_sc.line_entry.line; if (!start_line && m_sc.function) { m_sc.function->GetStartLineSourceInfo(source_file_sp, start_line); diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index ccf874f..896a760 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -20,6 +20,7 @@ #include "lldb/Target/StackFrame.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/StopInfo.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Target/Unwind.h" @@ -55,6 +56,49 @@ StackFrameList::~StackFrameList() { Clear(); } +SyntheticStackFrameList::SyntheticStackFrameList( + Thread &thread, lldb::StackFrameListSP input_frames, + const lldb::StackFrameListSP &prev_frames_sp, bool show_inline_frames) + : StackFrameList(thread, prev_frames_sp, show_inline_frames), + m_input_frames(std::move(input_frames)) {} + +bool SyntheticStackFrameList::FetchFramesUpTo( + uint32_t end_idx, InterruptionControl allow_interrupt) { + + size_t num_synthetic_frames = 0; + // Check if the thread has a synthetic frame provider. + if (auto provider_sp = m_thread.GetFrameProvider()) { + // Use the synthetic frame provider to generate frames lazily. + // Keep fetching until we reach end_idx or the provider returns an error. + for (uint32_t idx = m_frames.size(); idx <= end_idx; idx++) { + if (allow_interrupt && + m_thread.GetProcess()->GetTarget().GetDebugger().InterruptRequested()) + return true; + auto frame_or_err = provider_sp->GetFrameAtIndex(idx); + if (!frame_or_err) { + // Provider returned error - we've reached the end. + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), frame_or_err.takeError(), + "Frame provider reached end at index {0}: {1}", idx); + SetAllFramesFetched(); + break; + } + StackFrameSP frame_sp = *frame_or_err; + if (frame_sp->IsSynthetic()) + frame_sp->GetStackID().SetCFA(num_synthetic_frames++, + GetThread().GetProcess().get()); + // Set the frame list weak pointer so ExecutionContextRef can resolve + // the frame without calling Thread::GetStackFrameList(). + frame_sp->m_frame_list_wp = shared_from_this(); + m_frames.push_back(frame_sp); + } + + return false; // Not interrupted. + } + + // If no provider, fall back to the base implementation. + return StackFrameList::FetchFramesUpTo(end_idx, allow_interrupt); +} + void StackFrameList::CalculateCurrentInlinedDepth() { uint32_t cur_inlined_depth = GetCurrentInlinedDepth(); if (cur_inlined_depth == UINT32_MAX) { @@ -330,6 +374,7 @@ void StackFrameList::SynthesizeTailCallFrames(StackFrame &next_frame) { m_thread.shared_from_this(), frame_idx, concrete_frame_idx, cfa, cfa_is_valid, pc, StackFrame::Kind::Regular, artificial, behaves_like_zeroth_frame, &sc); + synth_frame->m_frame_list_wp = shared_from_this(); m_frames.push_back(synth_frame); LLDB_LOG(log, "Pushed frame {0} at {1:x}", callee->GetDisplayName(), pc); } @@ -445,6 +490,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, unwind_frame_sp = std::make_shared<StackFrame>( m_thread.shared_from_this(), m_frames.size(), idx, reg_ctx_sp, cfa, pc, behaves_like_zeroth_frame, nullptr); + unwind_frame_sp->m_frame_list_wp = shared_from_this(); m_frames.push_back(unwind_frame_sp); } } else { @@ -479,6 +525,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, // although its concrete index will stay the same. SynthesizeTailCallFrames(*unwind_frame_sp.get()); + unwind_frame_sp->m_frame_list_wp = shared_from_this(); m_frames.push_back(unwind_frame_sp); } @@ -503,6 +550,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, unwind_frame_sp->GetRegisterContextSP(), cfa, next_frame_address, behaves_like_zeroth_frame, &next_frame_sc)); + frame_sp->m_frame_list_wp = shared_from_this(); m_frames.push_back(frame_sp); unwind_sc = next_frame_sc; curr_frame_address = next_frame_address; @@ -559,6 +607,7 @@ bool StackFrameList::FetchFramesUpTo(uint32_t end_idx, prev_frame->UpdatePreviousFrameFromCurrentFrame(*curr_frame); // Now copy the fixed up previous frame into the current frames so the // pointer doesn't change. + prev_frame_sp->m_frame_list_wp = shared_from_this(); m_frames[curr_frame_idx] = prev_frame_sp; #if defined(DEBUG_STACK_FRAMES) diff --git a/lldb/source/Target/SyntheticFrameProvider.cpp b/lldb/source/Target/SyntheticFrameProvider.cpp new file mode 100644 index 0000000..97ff42d --- /dev/null +++ b/lldb/source/Target/SyntheticFrameProvider.cpp @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Target/SyntheticFrameProvider.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Status.h" +#include "lldb/Utility/Stream.h" + +using namespace lldb; +using namespace lldb_private; + +SyntheticFrameProvider::SyntheticFrameProvider(StackFrameListSP input_frames) + : m_input_frames(std::move(input_frames)) {} + +SyntheticFrameProvider::~SyntheticFrameProvider() = default; + +void ScriptedFrameProviderDescriptor::Dump(Stream *s) const { + if (!s) + return; + + s->Format(" ID: {0:x}\n", GetID()); + s->Printf(" Name: %s\n", GetName().str().c_str()); + + std::string description = GetDescription(); + if (!description.empty()) + s->Printf(" Description: %s\n", description.c_str()); + + // Show thread filter information. + if (thread_specs.empty()) { + s->PutCString(" Thread Filter: (applies to all threads)\n"); + } else { + s->Printf(" Thread Filter: %zu specification(s)\n", thread_specs.size()); + for (size_t i = 0; i < thread_specs.size(); ++i) { + const ThreadSpec &spec = thread_specs[i]; + s->Printf(" [%zu] ", i); + spec.GetDescription(s, lldb::eDescriptionLevelVerbose); + s->PutChar('\n'); + } + } +} + +uint32_t ScriptedFrameProviderDescriptor::GetID() const { + if (!scripted_metadata_sp) + return 0; + + return scripted_metadata_sp->GetID(); +} + +std::string ScriptedFrameProviderDescriptor::GetDescription() const { + // If we have an interface, call get_description() to fetch it. + if (interface_sp && scripted_metadata_sp) + return interface_sp->GetDescription(scripted_metadata_sp->GetClassName()); + return {}; +} + +llvm::Expected<SyntheticFrameProviderSP> SyntheticFrameProvider::CreateInstance( + StackFrameListSP input_frames, + const ScriptedFrameProviderDescriptor &descriptor) { + if (!input_frames) + return llvm::createStringError( + "cannot create synthetic frame provider: invalid input frames"); + + // Iterate through all registered ScriptedFrameProvider plugins. + ScriptedFrameProviderCreateInstance create_callback = nullptr; + for (uint32_t idx = 0; + (create_callback = + PluginManager::GetScriptedFrameProviderCreateCallbackAtIndex( + idx)) != nullptr; + ++idx) { + auto provider_or_err = create_callback(input_frames, descriptor); + if (!provider_or_err) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Target), provider_or_err.takeError(), + "Failed to create synthetic frame provider: {0}"); + continue; + } + + if (auto frame_provider_up = std::move(*provider_or_err)) + return std::move(frame_provider_up); + } + + return llvm::createStringError( + "cannot create synthetic frame provider: no suitable plugin found"); +} + +llvm::Expected<SyntheticFrameProviderSP> SyntheticFrameProvider::CreateInstance( + StackFrameListSP input_frames, llvm::StringRef plugin_name, + const std::vector<ThreadSpec> &thread_specs) { + if (!input_frames) + return llvm::createStringError( + "cannot create synthetic frame provider: invalid input frames"); + + // Look up the specific C++ plugin by name. + SyntheticFrameProviderCreateInstance create_callback = + PluginManager::GetSyntheticFrameProviderCreateCallbackForPluginName( + plugin_name); + + if (!create_callback) + return llvm::createStringError( + "cannot create synthetic frame provider: C++ plugin '%s' not found", + plugin_name.str().c_str()); + + auto provider_or_err = create_callback(input_frames, thread_specs); + if (!provider_or_err) + return provider_or_err.takeError(); + + if (auto frame_provider_sp = std::move(*provider_or_err)) + return std::move(frame_provider_sp); + + return llvm::createStringError( + "cannot create synthetic frame provider: C++ plugin '%s' returned null", + plugin_name.str().c_str()); +} diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index a23091a..2305f10 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -156,8 +156,6 @@ static Status installExecutable(const Installer &installer) { return Status(); } -constexpr std::chrono::milliseconds EvaluateExpressionOptions::default_timeout; - Target::Arch::Arch(const ArchSpec &spec) : m_spec(spec), m_plugin_up(PluginManager::CreateArchitectureInstance(spec)) {} @@ -187,6 +185,8 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch, m_internal_stop_hooks(), m_latest_stop_hook_id(0), m_valid(true), m_suppress_stop_hooks(false), m_is_dummy_target(is_dummy_target), m_target_unique_id(g_target_unique_id++), + m_target_session_name( + llvm::formatv("Session {0}", m_target_unique_id).str()), m_frame_recognizer_manager_up( std::make_unique<StackFrameRecognizerManager>()) { SetEventName(eBroadcastBitBreakpointChanged, "breakpoint-changed"); @@ -194,6 +194,7 @@ Target::Target(Debugger &debugger, const ArchSpec &target_arch, SetEventName(eBroadcastBitModulesUnloaded, "modules-unloaded"); SetEventName(eBroadcastBitWatchpointChanged, "watchpoint-changed"); SetEventName(eBroadcastBitSymbolsLoaded, "symbols-loaded"); + SetEventName(eBroadcastBitNewTargetCreated, "new-target-created"); CheckInWithManager(); @@ -1779,9 +1780,9 @@ bool Target::SetArchitecture(const ArchSpec &arch_spec, bool set_platform, arch_spec.GetArchitectureName(), arch_spec.GetTriple().getTriple().c_str()); ModuleSpec module_spec(executable_sp->GetFileSpec(), other); - FileSpecList search_paths = GetExecutableSearchPaths(); + module_spec.SetTarget(shared_from_this()); Status error = ModuleList::GetSharedModule(module_spec, executable_sp, - &search_paths, nullptr, nullptr); + nullptr, nullptr); if (!error.Fail() && executable_sp) { SetExecutableModule(executable_sp, eLoadDependentsYes); @@ -1855,6 +1856,9 @@ void Target::NotifyModulesRemoved(lldb_private::ModuleList &module_list) { } void Target::ModulesDidLoad(ModuleList &module_list) { + if (GetPreloadSymbols()) + module_list.PreloadSymbols(GetParallelModuleLoad()); + const size_t num_images = module_list.GetSize(); if (m_valid && num_images) { for (size_t idx = 0; idx < num_images; ++idx) { @@ -2279,8 +2283,10 @@ size_t Target::ReadScalarIntegerFromMemory(const Address &addr, uint32_t byte_si else scalar = data.GetMaxU64(&offset, byte_size); - if (is_signed) + if (is_signed) { + scalar.MakeSigned(); scalar.SignExtend(byte_size * 8); + } return bytes_read; } } else { @@ -2295,7 +2301,7 @@ int64_t Target::ReadSignedIntegerFromMemory(const Address &addr, int64_t fail_value, Status &error, bool force_live_memory) { Scalar scalar; - if (ReadScalarIntegerFromMemory(addr, integer_byte_size, false, scalar, error, + if (ReadScalarIntegerFromMemory(addr, integer_byte_size, true, scalar, error, force_live_memory)) return scalar.SLongLong(fail_value); return fail_value; @@ -2350,6 +2356,7 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &orig_module_spec, // Apply any remappings specified in target.object-map: ModuleSpec module_spec(orig_module_spec); + module_spec.SetTarget(shared_from_this()); PathMappingList &obj_mapping = GetObjectPathMap(); if (std::optional<FileSpec> remapped_obj_file = obj_mapping.RemapPath(orig_module_spec.GetFileSpec().GetPath(), @@ -2408,9 +2415,9 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &orig_module_spec, transformed_spec.GetFileSpec().SetDirectory(transformed_dir); transformed_spec.GetFileSpec().SetFilename( module_spec.GetFileSpec().GetFilename()); + transformed_spec.SetTarget(shared_from_this()); error = ModuleList::GetSharedModule(transformed_spec, module_sp, - &search_paths, &old_modules, - &did_create_module); + &old_modules, &did_create_module); } } } @@ -2426,9 +2433,8 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &orig_module_spec, // cache. if (module_spec.GetUUID().IsValid()) { // We have a UUID, it is OK to check the global module list... - error = - ModuleList::GetSharedModule(module_spec, module_sp, &search_paths, - &old_modules, &did_create_module); + error = ModuleList::GetSharedModule(module_spec, module_sp, + &old_modules, &did_create_module); } if (!module_sp) { @@ -2436,8 +2442,8 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &orig_module_spec, // module in the shared module cache. if (m_platform_sp) { error = m_platform_sp->GetSharedModule( - module_spec, m_process_sp.get(), module_sp, &search_paths, - &old_modules, &did_create_module); + module_spec, m_process_sp.get(), module_sp, &old_modules, + &did_create_module); } else { error = Status::FromErrorString("no platform is currently set"); } @@ -2509,10 +2515,6 @@ ModuleSP Target::GetOrCreateModule(const ModuleSpec &orig_module_spec, if (symbol_file_spec) module_sp->SetSymbolFileFileSpec(symbol_file_spec); - // Preload symbols outside of any lock, so hopefully we can do this for - // each library in parallel. - if (GetPreloadSymbols()) - module_sp->PreloadSymbols(); llvm::SmallVector<ModuleSP, 1> replaced_modules; for (ModuleSP &old_module_sp : old_modules) { if (m_images.GetIndexForModule(old_module_sp.get()) != @@ -3207,6 +3209,11 @@ bool Target::RunStopHooks(bool at_initial_stop) { bool should_stop = false; bool requested_continue = false; + // A stop hook might get deleted while running stop hooks. + // We have to decide what that means. We will follow the rule that deleting + // a stop hook while processing these stop hooks will delete it for FUTURE + // stops but not this stop. Fortunately, copying the m_stop_hooks to the + // active_hooks list before iterating over the hooks has this effect. for (auto cur_hook_sp : active_hooks) { bool any_thread_matched = false; for (auto exc_ctx : exc_ctx_with_reasons) { @@ -3713,6 +3720,61 @@ Status Target::Attach(ProcessAttachInfo &attach_info, Stream *stream) { return error; } +llvm::Expected<uint32_t> Target::AddScriptedFrameProviderDescriptor( + const ScriptedFrameProviderDescriptor &descriptor) { + if (!descriptor.IsValid()) + return llvm::createStringError("invalid frame provider descriptor"); + + llvm::StringRef name = descriptor.GetName(); + if (name.empty()) + return llvm::createStringError( + "frame provider descriptor has no class name"); + + std::lock_guard<std::recursive_mutex> guard( + m_frame_provider_descriptors_mutex); + + uint32_t descriptor_id = descriptor.GetID(); + m_frame_provider_descriptors[descriptor_id] = descriptor; + + // Clear frame providers on existing threads so they reload with new config. + if (ProcessSP process_sp = GetProcessSP()) + for (ThreadSP thread_sp : process_sp->Threads()) + thread_sp->ClearScriptedFrameProvider(); + + return descriptor_id; +} + +bool Target::RemoveScriptedFrameProviderDescriptor(uint32_t id) { + std::lock_guard<std::recursive_mutex> guard( + m_frame_provider_descriptors_mutex); + bool removed = m_frame_provider_descriptors.erase(id); + + if (removed) + if (ProcessSP process_sp = GetProcessSP()) + for (ThreadSP thread_sp : process_sp->Threads()) + thread_sp->ClearScriptedFrameProvider(); + + return removed; +} + +void Target::ClearScriptedFrameProviderDescriptors() { + std::lock_guard<std::recursive_mutex> guard( + m_frame_provider_descriptors_mutex); + + m_frame_provider_descriptors.clear(); + + if (ProcessSP process_sp = GetProcessSP()) + for (ThreadSP thread_sp : process_sp->Threads()) + thread_sp->ClearScriptedFrameProvider(); +} + +const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> & +Target::GetScriptedFrameProviderDescriptors() const { + std::lock_guard<std::recursive_mutex> guard( + m_frame_provider_descriptors_mutex); + return m_frame_provider_descriptors; +} + void Target::FinalizeFileActions(ProcessLaunchInfo &info) { Log *log = GetLog(LLDBLog::Process); @@ -4945,7 +5007,7 @@ void TargetProperties::SetStandardErrorPath(llvm::StringRef path) { SourceLanguage TargetProperties::GetLanguage() const { const uint32_t idx = ePropertyLanguage; - return {GetPropertyAtIndexAs<LanguageType>(idx, {})}; + return SourceLanguage{GetPropertyAtIndexAs<LanguageType>(idx, {})}; } llvm::StringRef TargetProperties::GetExpressionPrefixContents() { @@ -5083,17 +5145,17 @@ void TargetProperties::SetProcessLaunchInfo( const FileAction *input_file_action = launch_info.GetFileActionForFD(STDIN_FILENO); if (input_file_action) { - SetStandardInputPath(input_file_action->GetPath()); + SetStandardInputPath(input_file_action->GetFileSpec().GetPath()); } const FileAction *output_file_action = launch_info.GetFileActionForFD(STDOUT_FILENO); if (output_file_action) { - SetStandardOutputPath(output_file_action->GetPath()); + SetStandardOutputPath(output_file_action->GetFileSpec().GetPath()); } const FileAction *error_file_action = launch_info.GetFileActionForFD(STDERR_FILENO); if (error_file_action) { - SetStandardErrorPath(error_file_action->GetPath()); + SetStandardErrorPath(error_file_action->GetFileSpec().GetPath()); } SetDetachOnError(launch_info.GetFlags().Test(lldb::eLaunchFlagDetachOnError)); SetDisableASLR(launch_info.GetFlags().Test(lldb::eLaunchFlagDisableASLR)); @@ -5196,6 +5258,11 @@ Target::TargetEventData::TargetEventData(const lldb::TargetSP &target_sp, const ModuleList &module_list) : EventData(), m_target_sp(target_sp), m_module_list(module_list) {} +Target::TargetEventData::TargetEventData( + const lldb::TargetSP &target_sp, const lldb::TargetSP &created_target_sp) + : EventData(), m_target_sp(target_sp), + m_created_target_sp(created_target_sp), m_module_list() {} + Target::TargetEventData::~TargetEventData() = default; llvm::StringRef Target::TargetEventData::GetFlavorString() { @@ -5230,6 +5297,15 @@ TargetSP Target::TargetEventData::GetTargetFromEvent(const Event *event_ptr) { return target_sp; } +TargetSP +Target::TargetEventData::GetCreatedTargetFromEvent(const Event *event_ptr) { + TargetSP created_target_sp; + const TargetEventData *event_data = GetEventDataFromEvent(event_ptr); + if (event_data) + created_target_sp = event_data->m_created_target_sp; + return created_target_sp; +} + ModuleList Target::TargetEventData::GetModuleListFromEvent(const Event *event_ptr) { ModuleList module_list; diff --git a/lldb/source/Target/TargetList.cpp b/lldb/source/Target/TargetList.cpp index 188c250..ce04e9c 100644 --- a/lldb/source/Target/TargetList.cpp +++ b/lldb/source/Target/TargetList.cpp @@ -48,7 +48,7 @@ Status TargetList::CreateTarget(Debugger &debugger, LoadDependentFiles load_dependent_files, const OptionGroupPlatform *platform_options, TargetSP &target_sp) { - std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto result = TargetList::CreateTargetInternal( debugger, user_exe_path, triple_str, load_dependent_files, platform_options, target_sp); @@ -63,7 +63,7 @@ Status TargetList::CreateTarget(Debugger &debugger, const ArchSpec &specified_arch, LoadDependentFiles load_dependent_files, PlatformSP &platform_sp, TargetSP &target_sp) { - std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); + auto result = TargetList::CreateTargetInternal( debugger, user_exe_path, specified_arch, load_dependent_files, platform_sp, target_sp); @@ -304,13 +304,9 @@ Status TargetList::CreateTargetInternal(Debugger &debugger, ModuleSP exe_module_sp; if (platform_sp) { - FileSpecList executable_search_paths( - Target::GetDefaultExecutableSearchPaths()); ModuleSpec module_spec(file, arch); - error = platform_sp->ResolveExecutable(module_spec, exe_module_sp, - executable_search_paths.GetSize() - ? &executable_search_paths - : nullptr); + module_spec.SetTarget(target_sp); + error = platform_sp->ResolveExecutable(module_spec, exe_module_sp); } if (error.Success() && exe_module_sp) { @@ -525,6 +521,7 @@ uint32_t TargetList::GetIndexOfTarget(lldb::TargetSP target_sp) const { } void TargetList::AddTargetInternal(TargetSP target_sp, bool do_select) { + std::lock_guard<std::recursive_mutex> guard(m_target_list_mutex); lldbassert(!llvm::is_contained(m_target_list, target_sp) && "target already exists it the list"); UnregisterInProcessTarget(target_sp); diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 8c3e197..b40e753 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -13,9 +13,12 @@ #include "lldb/Core/Module.h" #include "lldb/Core/StructuredDataImpl.h" #include "lldb/Host/Host.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h" +#include "lldb/Interpreter/Interfaces/ScriptedFrameProviderInterface.h" #include "lldb/Interpreter/OptionValueFileSpecList.h" #include "lldb/Interpreter/OptionValueProperties.h" #include "lldb/Interpreter/Property.h" +#include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Symbol/Function.h" #include "lldb/Target/ABI.h" #include "lldb/Target/DynamicLoader.h" @@ -26,6 +29,7 @@ #include "lldb/Target/ScriptedThreadPlan.h" #include "lldb/Target/StackFrameRecognizer.h" #include "lldb/Target/StopInfo.h" +#include "lldb/Target/SyntheticFrameProvider.h" #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Target.h" #include "lldb/Target/ThreadPlan.h" @@ -45,6 +49,7 @@ #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/ScriptedMetadata.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" @@ -257,6 +262,7 @@ void Thread::DestroyThread() { std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); m_curr_frames_sp.reset(); m_prev_frames_sp.reset(); + m_frame_provider_sp.reset(); m_prev_framezero_pc.reset(); } @@ -1439,13 +1445,76 @@ void Thread::CalculateExecutionContext(ExecutionContext &exe_ctx) { StackFrameListSP Thread::GetStackFrameList() { std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); - if (!m_curr_frames_sp) + if (m_curr_frames_sp) + return m_curr_frames_sp; + + // First, try to load a frame provider if we don't have one yet. + if (!m_frame_provider_sp) { + ProcessSP process_sp = GetProcess(); + if (process_sp) { + Target &target = process_sp->GetTarget(); + const auto &descriptors = target.GetScriptedFrameProviderDescriptors(); + + // Find first descriptor that applies to this thread. + for (const auto &entry : descriptors) { + const ScriptedFrameProviderDescriptor &descriptor = entry.second; + if (descriptor.IsValid() && descriptor.AppliesToThread(*this)) { + if (llvm::Error error = LoadScriptedFrameProvider(descriptor)) { + LLDB_LOG_ERROR(GetLog(LLDBLog::Thread), std::move(error), + "Failed to load scripted frame provider: {0}"); + } + break; // Use first matching descriptor (success or failure). + } + } + } + } + + // Create the frame list based on whether we have a provider. + if (m_frame_provider_sp) { + // We have a provider - create synthetic frame list. + StackFrameListSP input_frames = m_frame_provider_sp->GetInputFrames(); + m_curr_frames_sp = std::make_shared<SyntheticStackFrameList>( + *this, input_frames, m_prev_frames_sp, true); + } else { + // No provider - use normal unwinder frames. m_curr_frames_sp = std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true); + } return m_curr_frames_sp; } +llvm::Error Thread::LoadScriptedFrameProvider( + const ScriptedFrameProviderDescriptor &descriptor) { + std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); + + // Note: We don't create input_frames here - it will be created lazily + // by SyntheticStackFrameList when frames are first fetched. + // Creating them too early can cause crashes during thread initialization. + + // Create a temporary StackFrameList just to get the thread reference for the + // provider. The provider won't actually use this - it will get real input + // frames from SyntheticStackFrameList later. + StackFrameListSP temp_frames = + std::make_shared<StackFrameList>(*this, m_prev_frames_sp, true); + + auto provider_or_err = + SyntheticFrameProvider::CreateInstance(temp_frames, descriptor); + if (!provider_or_err) + return provider_or_err.takeError(); + + ClearScriptedFrameProvider(); + m_frame_provider_sp = *provider_or_err; + return llvm::Error::success(); +} + +void Thread::ClearScriptedFrameProvider() { + std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); + m_frame_provider_sp.reset(); + m_curr_frames_sp.reset(); + m_prev_frames_sp.reset(); +} + std::optional<addr_t> Thread::GetPreviousFrameZeroPC() { return m_prev_framezero_pc; } @@ -1466,6 +1535,7 @@ void Thread::ClearStackFrames() { m_prev_frames_sp.swap(m_curr_frames_sp); m_curr_frames_sp.reset(); + m_frame_provider_sp.reset(); m_extended_info.reset(); m_extended_info_fetched = false; } diff --git a/lldb/source/Target/ThreadPlanStepOut.cpp b/lldb/source/Target/ThreadPlanStepOut.cpp index d49a01b..0307b38 100644 --- a/lldb/source/Target/ThreadPlanStepOut.cpp +++ b/lldb/source/Target/ThreadPlanStepOut.cpp @@ -356,13 +356,10 @@ bool ThreadPlanStepOut::DoPlanExplainsStop(Event *event_ptr) { } } - // If there was only one owner, then we're done. But if we also hit - // some user breakpoint on our way out, we should mark ourselves as - // done, but also not claim to explain the stop, since it is more - // important to report the user breakpoint than the step out - // completion. - - if (site_sp->GetNumberOfConstituents() == 1) + // If the thread also hit a user breakpoint on its way out, the plan is + // done but should not claim to explain the stop. It is more important + // to report the user breakpoint than the step out completion. + if (!site_sp->ContainsUserBreakpointForThread(GetThread())) return true; } return false; diff --git a/lldb/source/Target/ThreadPlanStepRange.cpp b/lldb/source/Target/ThreadPlanStepRange.cpp index dca96cc..3a9deb6 100644 --- a/lldb/source/Target/ThreadPlanStepRange.cpp +++ b/lldb/source/Target/ThreadPlanStepRange.cpp @@ -431,10 +431,10 @@ bool ThreadPlanStepRange::SetNextBranchBreakpoint() { top_most_line_entry.original_file_sp = std::make_shared<SupportFile>(call_site_file_spec); top_most_line_entry.range = range; - top_most_line_entry.file_sp.reset(); + top_most_line_entry.file_sp = std::make_shared<SupportFile>(); top_most_line_entry.ApplyFileMappings( GetThread().CalculateTarget()); - if (!top_most_line_entry.file_sp) + if (!top_most_line_entry.file_sp->GetSpecOnly()) top_most_line_entry.file_sp = top_most_line_entry.original_file_sp; } diff --git a/lldb/source/Target/ThreadSpec.cpp b/lldb/source/Target/ThreadSpec.cpp index ba4c3aa..624f64e 100644 --- a/lldb/source/Target/ThreadSpec.cpp +++ b/lldb/source/Target/ThreadSpec.cpp @@ -19,6 +19,10 @@ const char *ThreadSpec::g_option_names[static_cast<uint32_t>( ThreadSpec::ThreadSpec() : m_name(), m_queue_name() {} +ThreadSpec::ThreadSpec(Thread &thread) + : m_index(thread.GetIndexID()), m_tid(thread.GetID()), + m_name(thread.GetName()), m_queue_name(thread.GetQueueName()) {} + std::unique_ptr<ThreadSpec> ThreadSpec::CreateFromStructuredData( const StructuredData::Dictionary &spec_dict, Status &error) { uint32_t index = UINT32_MAX; diff --git a/lldb/source/Target/UnixSignals.cpp b/lldb/source/Target/UnixSignals.cpp index 6113c66..881431f 100644 --- a/lldb/source/Target/UnixSignals.cpp +++ b/lldb/source/Target/UnixSignals.cpp @@ -137,6 +137,13 @@ llvm::StringRef UnixSignals::GetSignalAsStringRef(int32_t signo) const { return pos->second.m_name; } +llvm::StringRef UnixSignals::GetSignalNumberDescription(int32_t signo) const { + const auto pos = m_signals.find(signo); + if (pos == m_signals.end()) + return {}; + return pos->second.m_description; +} + std::string UnixSignals::GetSignalDescription( int32_t signo, std::optional<int32_t> code, std::optional<lldb::addr_t> addr, std::optional<lldb::addr_t> lower, diff --git a/lldb/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt index 1dd4d63..80b53f8 100644 --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -38,7 +38,6 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES DataEncoder.cpp DataExtractor.cpp Diagnostics.cpp - DiagnosticsRendering.cpp Environment.cpp ErrorMessages.cpp Event.cpp @@ -78,6 +77,7 @@ add_lldb_library(lldbUtility NO_INTERNAL_DEPENDENCIES UserIDResolver.cpp VASprintf.cpp VMRange.cpp + VirtualDataExtractor.cpp XcodeSDK.cpp ZipFile.cpp diff --git a/lldb/source/Utility/DataExtractor.cpp b/lldb/source/Utility/DataExtractor.cpp index e9be0cb..a9aea16 100644 --- a/lldb/source/Utility/DataExtractor.cpp +++ b/lldb/source/Utility/DataExtractor.cpp @@ -662,10 +662,6 @@ size_t DataExtractor::ExtractBytes(offset_t offset, offset_t length, const uint8_t *src = PeekData(offset, length); if (src) { if (dst_byte_order != GetByteOrder()) { - // Validate that only a word- or register-sized dst is byte swapped - assert(length == 1 || length == 2 || length == 4 || length == 8 || - length == 10 || length == 16 || length == 32); - for (uint32_t i = 0; i < length; ++i) (static_cast<uint8_t *>(dst))[i] = src[length - i - 1]; } else diff --git a/lldb/source/Utility/FileSpecList.cpp b/lldb/source/Utility/FileSpecList.cpp index 5852367..8aa0820 100644 --- a/lldb/source/Utility/FileSpecList.cpp +++ b/lldb/source/Utility/FileSpecList.cpp @@ -45,10 +45,9 @@ bool FileSpecList::AppendIfUnique(const FileSpec &file_spec) { // FIXME: Replace this with a DenseSet at the call site. It is inefficient. bool SupportFileList::AppendIfUnique(const FileSpec &file_spec) { collection::iterator end = m_files.end(); - if (find_if(m_files.begin(), end, - [&](const std::shared_ptr<SupportFile> &support_file) { - return support_file->GetSpecOnly() == file_spec; - }) == end) { + if (find_if(m_files.begin(), end, [&](const SupportFileNSP &support_file) { + return support_file->GetSpecOnly() == file_spec; + }) == end) { Append(file_spec); return true; } @@ -214,11 +213,10 @@ const FileSpec &SupportFileList::GetFileSpecAtIndex(size_t idx) const { return g_empty_file_spec; } -std::shared_ptr<SupportFile> -SupportFileList::GetSupportFileAtIndex(size_t idx) const { +SupportFileNSP SupportFileList::GetSupportFileAtIndex(size_t idx) const { if (idx < m_files.size()) return m_files[idx]; - return {}; + return std::make_shared<SupportFile>(); } // Return the size in bytes that this object takes in memory. This returns the diff --git a/lldb/source/Utility/LLDBLog.cpp b/lldb/source/Utility/LLDBLog.cpp index 613dae4..a08764d 100644 --- a/lldb/source/Utility/LLDBLog.cpp +++ b/lldb/source/Utility/LLDBLog.cpp @@ -67,6 +67,9 @@ static constexpr Log::Category g_categories[] = { {{"disassembler"}, {"log disassembler related activities"}, LLDBLog::Disassembler}, + {{"instrumentation-runtime"}, + {"log instrumentation runtime plugin related activities"}, + LLDBLog::InstrumentationRuntime}, }; static Log::Channel g_log_channel(g_categories, diff --git a/lldb/source/Utility/RegisterValue.cpp b/lldb/source/Utility/RegisterValue.cpp index 12c349a..4d762dc 100644 --- a/lldb/source/Utility/RegisterValue.cpp +++ b/lldb/source/Utility/RegisterValue.cpp @@ -127,7 +127,7 @@ bool RegisterValue::GetScalarValue(Scalar &scalar) const { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -180,8 +180,6 @@ Status RegisterValue::SetValueFromData(const RegisterInfo ®_info, if (src_len > reg_info.byte_size) src_len = reg_info.byte_size; - type128 int128; - m_type = eTypeInvalid; switch (reg_info.encoding) { case eEncodingInvalid: @@ -196,17 +194,13 @@ Status RegisterValue::SetValueFromData(const RegisterInfo ®_info, SetUInt32(src.GetMaxU32(&src_offset, src_len)); else if (reg_info.byte_size <= 8) SetUInt64(src.GetMaxU64(&src_offset, src_len)); - else if (reg_info.byte_size <= 16) { - uint64_t data1 = src.GetU64(&src_offset); - uint64_t data2 = src.GetU64(&src_offset); - if (src.GetByteOrder() == eByteOrderLittle) { - int128.x[0] = data1; - int128.x[1] = data2; - } else { - int128.x[0] = data2; - int128.x[1] = data1; - } - SetUInt128(llvm::APInt(128, 2, int128.x)); + else { + std::vector<uint8_t> native_endian_src(src_len, 0); + src.ExtractBytes(src_offset, src_len, endian::InlHostByteOrder(), + native_endian_src.data()); + llvm::APInt uint = llvm::APInt::getZero(src_len * 8); + llvm::LoadIntFromMemory(uint, native_endian_src.data(), src_len); + SetUIntN(uint); } break; case eEncodingIEEE754: @@ -442,7 +436,7 @@ bool RegisterValue::SignExtend(uint32_t sign_bitpos) { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: return m_scalar.SignExtend(sign_bitpos); case eTypeFloat: case eTypeDouble: @@ -465,7 +459,7 @@ bool RegisterValue::CopyValue(const RegisterValue &rhs) { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -581,7 +575,7 @@ llvm::APInt RegisterValue::GetAsUInt128(const llvm::APInt &fail_value, case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -596,8 +590,10 @@ llvm::APInt RegisterValue::GetAsUInt128(const llvm::APInt &fail_value, case 8: case 16: return llvm::APInt( - BITWIDTH_INT128, NUM_OF_WORDS_INT128, - (reinterpret_cast<const type128 *>(buffer.bytes.data()))->x); + BITWIDTH_INT128, + llvm::ArrayRef( + (reinterpret_cast<const type128 *>(buffer.bytes.data()))->x, + NUM_OF_WORDS_INT128)); } } break; } @@ -614,7 +610,7 @@ float RegisterValue::GetAsFloat(float fail_value, bool *success_ptr) const { break; case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -634,7 +630,7 @@ double RegisterValue::GetAsDouble(double fail_value, bool *success_ptr) const { case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -655,7 +651,7 @@ long double RegisterValue::GetAsLongDouble(long double fail_value, case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -674,7 +670,7 @@ const void *RegisterValue::GetBytes() const { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -696,7 +692,7 @@ uint32_t RegisterValue::GetByteSize() const { return 2; case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -719,7 +715,7 @@ bool RegisterValue::SetUInt(uint64_t uint, uint32_t byte_size) { } else if (byte_size <= 8) { SetUInt64(uint); } else if (byte_size <= 16) { - SetUInt128(llvm::APInt(128, uint)); + SetUIntN(llvm::APInt(128, uint)); } else return false; return true; @@ -747,7 +743,7 @@ bool RegisterValue::operator==(const RegisterValue &rhs) const { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: case eTypeFloat: case eTypeDouble: case eTypeLongDouble: @@ -772,7 +768,7 @@ bool RegisterValue::ClearBit(uint32_t bit) { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: if (bit < (GetByteSize() * 8)) { return m_scalar.ClearBit(bit); } @@ -812,7 +808,7 @@ bool RegisterValue::SetBit(uint32_t bit) { case eTypeUInt16: case eTypeUInt32: case eTypeUInt64: - case eTypeUInt128: + case eTypeUIntN: if (bit < (GetByteSize() * 8)) { return m_scalar.SetBit(bit); } diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp index 010149a..40b5d03 100644 --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -12,9 +12,6 @@ #include <cstring> #include <optional> -constexpr lldb::pid_t StringExtractorGDBRemote::AllProcesses; -constexpr lldb::tid_t StringExtractorGDBRemote::AllThreads; - StringExtractorGDBRemote::ResponseType StringExtractorGDBRemote::GetResponseType() const { if (m_packet.empty()) diff --git a/lldb/source/Utility/VirtualDataExtractor.cpp b/lldb/source/Utility/VirtualDataExtractor.cpp new file mode 100644 index 0000000..a23e43b --- /dev/null +++ b/lldb/source/Utility/VirtualDataExtractor.cpp @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Utility/VirtualDataExtractor.h" +#include <cassert> + +using namespace lldb; +using namespace lldb_private; + +VirtualDataExtractor::VirtualDataExtractor(const void *data, + offset_t data_length, + ByteOrder byte_order, + uint32_t addr_size, + LookupTable lookup_table) + : DataExtractor(data, data_length, byte_order, addr_size), + m_lookup_table(std::move(lookup_table)) { + m_lookup_table.Sort(); +} + +VirtualDataExtractor::VirtualDataExtractor(const DataBufferSP &data_sp, + ByteOrder byte_order, + uint32_t addr_size, + LookupTable lookup_table) + : DataExtractor(data_sp, byte_order, addr_size), + m_lookup_table(std::move(lookup_table)) { + m_lookup_table.Sort(); +} + +const VirtualDataExtractor::LookupTable::Entry * +VirtualDataExtractor::FindEntry(offset_t virtual_addr) const { + // Use RangeDataVector's binary search instead of linear search. + return m_lookup_table.FindEntryThatContains(virtual_addr); +} + +bool VirtualDataExtractor::ValidateVirtualRead(offset_t virtual_addr, + offset_t length) const { + const LookupTable::Entry *entry = FindEntry(virtual_addr); + if (!entry) + return false; + + // Assert that the read does not cross entry boundaries. + // RangeData.Contains() checks if a range is fully contained. + assert(entry->Contains(LookupTable::Range(virtual_addr, length)) && + "Read crosses lookup table entry boundary"); + + // Also validate that the physical offset is within the data buffer. + // RangeData.data contains the physical offset. + offset_t physical_offset = entry->data + (virtual_addr - entry->base); + return ValidOffsetForDataOfSize(physical_offset, length); +} + +const void *VirtualDataExtractor::GetData(offset_t *offset_ptr, + offset_t length) const { + // Override to treat offset as virtual address. + if (!offset_ptr) + return nullptr; + + offset_t virtual_addr = *offset_ptr; + + if (!ValidateVirtualRead(virtual_addr, length)) + return nullptr; + + const LookupTable::Entry *entry = FindEntry(virtual_addr); + assert(entry && "ValidateVirtualRead should have found an entry"); + + offset_t physical_offset = entry->data + (virtual_addr - entry->base); + // Use base class PeekData directly to avoid recursion. + const void *result = DataExtractor::PeekData(physical_offset, length); + + if (result) { + // Advance the virtual offset pointer. + *offset_ptr += length; + } + + return result; +} + +const uint8_t *VirtualDataExtractor::PeekData(offset_t offset, + offset_t length) const { + // Override to treat offset as virtual address. + if (!ValidateVirtualRead(offset, length)) + return nullptr; + + const LookupTable::Entry *entry = FindEntry(offset); + assert(entry && "ValidateVirtualRead should have found an entry"); + + offset_t physical_offset = entry->data + (offset - entry->base); + // Use the base class PeekData with the physical offset. + return DataExtractor::PeekData(physical_offset, length); +} + +uint8_t VirtualDataExtractor::GetU8_unchecked(offset_t *offset_ptr) const { + offset_t virtual_addr = *offset_ptr; + const LookupTable::Entry *entry = FindEntry(virtual_addr); + assert(entry && "Unchecked methods require valid virtual address"); + + offset_t physical_offset = entry->data + (virtual_addr - entry->base); + uint8_t result = DataExtractor::GetU8_unchecked(&physical_offset); + *offset_ptr += 1; + return result; +} + +uint16_t VirtualDataExtractor::GetU16_unchecked(offset_t *offset_ptr) const { + offset_t virtual_addr = *offset_ptr; + const LookupTable::Entry *entry = FindEntry(virtual_addr); + assert(entry && "Unchecked methods require valid virtual address"); + + offset_t physical_offset = entry->data + (virtual_addr - entry->base); + uint16_t result = DataExtractor::GetU16_unchecked(&physical_offset); + *offset_ptr += 2; + return result; +} + +uint32_t VirtualDataExtractor::GetU32_unchecked(offset_t *offset_ptr) const { + offset_t virtual_addr = *offset_ptr; + const LookupTable::Entry *entry = FindEntry(virtual_addr); + assert(entry && "Unchecked methods require valid virtual address"); + + offset_t physical_offset = entry->data + (virtual_addr - entry->base); + uint32_t result = DataExtractor::GetU32_unchecked(&physical_offset); + *offset_ptr += 4; + return result; +} + +uint64_t VirtualDataExtractor::GetU64_unchecked(offset_t *offset_ptr) const { + offset_t virtual_addr = *offset_ptr; + const LookupTable::Entry *entry = FindEntry(virtual_addr); + assert(entry && "Unchecked methods require valid virtual address"); + + offset_t physical_offset = entry->data + (virtual_addr - entry->base); + uint64_t result = DataExtractor::GetU64_unchecked(&physical_offset); + *offset_ptr += 8; + return result; +} diff --git a/lldb/source/ValueObject/CMakeLists.txt b/lldb/source/ValueObject/CMakeLists.txt index 2a61407..f0fe7f3 100644 --- a/lldb/source/ValueObject/CMakeLists.txt +++ b/lldb/source/ValueObject/CMakeLists.txt @@ -1,4 +1,4 @@ -add_lldb_library(lldbValueObject +add_lldb_library(lldbValueObject NO_PLUGIN_DEPENDENCIES DILAST.cpp DILEval.cpp DILLexer.cpp @@ -34,6 +34,4 @@ add_lldb_library(lldbValueObject lldbSymbol lldbTarget lldbUtility - lldbPluginCPlusPlusLanguage - lldbPluginObjCLanguage ) diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp index 7ed34db..0b9e1f4 100644 --- a/lldb/source/ValueObject/DILAST.cpp +++ b/lldb/source/ValueObject/DILAST.cpp @@ -51,4 +51,8 @@ BooleanLiteralNode::Accept(Visitor *v) const { return v->Visit(this); } +llvm::Expected<lldb::ValueObjectSP> CastNode::Accept(Visitor *v) const { + return v->Visit(this); +} + } // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp index a9dbfad..dc0d93d 100644 --- a/lldb/source/ValueObject/DILEval.cpp +++ b/lldb/source/ValueObject/DILEval.cpp @@ -21,6 +21,101 @@ namespace lldb_private::dil { +static llvm::Expected<lldb::TypeSystemSP> +GetTypeSystemFromCU(std::shared_ptr<ExecutionContextScope> ctx) { + auto stack_frame = ctx->CalculateStackFrame(); + if (!stack_frame) + return llvm::createStringError("no stack frame in this context"); + SymbolContext symbol_context = + stack_frame->GetSymbolContext(lldb::eSymbolContextCompUnit); + lldb::LanguageType language = symbol_context.comp_unit->GetLanguage(); + + symbol_context = stack_frame->GetSymbolContext(lldb::eSymbolContextModule); + return symbol_context.module_sp->GetTypeSystemForLanguage(language); +} + +static CompilerType GetBasicType(lldb::TypeSystemSP type_system, + lldb::BasicType basic_type) { + if (type_system) + return type_system.get()->GetBasicTypeFromAST(basic_type); + + return CompilerType(); +} + +static lldb::ValueObjectSP +ArrayToPointerConversion(ValueObject &valobj, ExecutionContextScope &ctx) { + uint64_t addr = valobj.GetLoadAddress(); + ExecutionContext exe_ctx; + ctx.CalculateExecutionContext(exe_ctx); + return ValueObject::CreateValueObjectFromAddress( + "result", addr, exe_ctx, + valobj.GetCompilerType().GetArrayElementType(&ctx).GetPointerType(), + /* do_deref */ false); +} + +llvm::Expected<lldb::ValueObjectSP> +Interpreter::UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location) { + if (!valobj) + return llvm::make_error<DILDiagnosticError>(m_expr, "invalid value object", + location); + llvm::Expected<lldb::TypeSystemSP> type_system = + GetTypeSystemFromCU(m_exe_ctx_scope); + if (!type_system) + return type_system.takeError(); + + CompilerType in_type = valobj->GetCompilerType(); + if (valobj->IsBitfield()) { + // Promote bitfields. If `int` can represent the bitfield value, it is + // converted to `int`. Otherwise, if `unsigned int` can represent it, it + // is converted to `unsigned int`. Otherwise, it is treated as its + // underlying type. + uint32_t bitfield_size = valobj->GetBitfieldBitSize(); + // Some bitfields have undefined size (e.g. result of ternary operation). + // The AST's `bitfield_size` of those is 0, and no promotion takes place. + if (bitfield_size > 0 && in_type.IsInteger()) { + CompilerType int_type = GetBasicType(*type_system, lldb::eBasicTypeInt); + CompilerType uint_type = + GetBasicType(*type_system, lldb::eBasicTypeUnsignedInt); + llvm::Expected<uint64_t> int_bit_size = + int_type.GetBitSize(m_exe_ctx_scope.get()); + if (!int_bit_size) + return int_bit_size.takeError(); + llvm::Expected<uint64_t> uint_bit_size = + uint_type.GetBitSize(m_exe_ctx_scope.get()); + if (!uint_bit_size) + return int_bit_size.takeError(); + if (bitfield_size < *int_bit_size || + (in_type.IsSigned() && bitfield_size == *int_bit_size)) + return valobj->CastToBasicType(int_type); + if (bitfield_size <= *uint_bit_size) + return valobj->CastToBasicType(uint_type); + // Re-create as a const value with the same underlying type + Scalar scalar; + bool resolved = valobj->ResolveValue(scalar); + if (!resolved) + return llvm::createStringError("invalid scalar value"); + return ValueObject::CreateValueObjectFromScalar(m_target, scalar, in_type, + "result"); + } + } + + if (in_type.IsArrayType()) + valobj = ArrayToPointerConversion(*valobj, *m_exe_ctx_scope); + + if (valobj->GetCompilerType().IsInteger() || + valobj->GetCompilerType().IsUnscopedEnumerationType()) { + llvm::Expected<CompilerType> promoted_type = + type_system.get()->DoIntegralPromotion(valobj->GetCompilerType(), + m_exe_ctx_scope.get()); + if (!promoted_type) + return promoted_type.takeError(); + if (!promoted_type->CompareTypes(valobj->GetCompilerType())) + return valobj->CastToBasicType(*promoted_type); + } + + return valobj; +} + static lldb::VariableSP DILFindVariable(ConstString name, VariableList &variable_list) { lldb::VariableSP exact_match; @@ -147,6 +242,10 @@ Interpreter::Interpreter(lldb::TargetSP target, llvm::StringRef expr, llvm::Expected<lldb::ValueObjectSP> Interpreter::Evaluate(const ASTNode *node) { // Evaluate an AST. auto value_or_error = node->Accept(this); + // Convert SP with a nullptr to an error. + if (value_or_error && !*value_or_error) + return llvm::make_error<DILDiagnosticError>(m_expr, "invalid value object", + node->GetLocation()); // Return the computed value-or-error. The caller is responsible for // checking if an error occured during the evaluation. return value_or_error; @@ -175,21 +274,21 @@ Interpreter::Visit(const IdentifierNode *node) { llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const UnaryOpNode *node) { Status error; - auto rhs_or_err = Evaluate(node->GetOperand()); - if (!rhs_or_err) - return rhs_or_err; + auto op_or_err = Evaluate(node->GetOperand()); + if (!op_or_err) + return op_or_err; - lldb::ValueObjectSP rhs = *rhs_or_err; + lldb::ValueObjectSP operand = *op_or_err; switch (node->GetKind()) { case UnaryOpKind::Deref: { - lldb::ValueObjectSP dynamic_rhs = rhs->GetDynamicValue(m_use_dynamic); - if (dynamic_rhs) - rhs = dynamic_rhs; + lldb::ValueObjectSP dynamic_op = operand->GetDynamicValue(m_use_dynamic); + if (dynamic_op) + operand = dynamic_op; - lldb::ValueObjectSP child_sp = rhs->Dereference(error); + lldb::ValueObjectSP child_sp = operand->Dereference(error); if (!child_sp && m_use_synthetic) { - if (lldb::ValueObjectSP synth_obj_sp = rhs->GetSyntheticValue()) { + if (lldb::ValueObjectSP synth_obj_sp = operand->GetSyntheticValue()) { error.Clear(); child_sp = synth_obj_sp->Dereference(error); } @@ -202,18 +301,69 @@ Interpreter::Visit(const UnaryOpNode *node) { } case UnaryOpKind::AddrOf: { Status error; - lldb::ValueObjectSP value = rhs->AddressOf(error); + lldb::ValueObjectSP value = operand->AddressOf(error); if (error.Fail()) return llvm::make_error<DILDiagnosticError>(m_expr, error.AsCString(), node->GetLocation()); return value; } + case UnaryOpKind::Minus: { + if (operand->GetCompilerType().IsReferenceType()) { + operand = operand->Dereference(error); + if (error.Fail()) + return error.ToError(); + } + llvm::Expected<lldb::ValueObjectSP> conv_op = + UnaryConversion(operand, node->GetOperand()->GetLocation()); + if (!conv_op) + return conv_op; + operand = *conv_op; + CompilerType operand_type = operand->GetCompilerType(); + if (!operand_type.IsScalarType()) { + std::string errMsg = + llvm::formatv("invalid argument type '{0}' to unary expression", + operand_type.GetTypeName()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, + node->GetLocation()); + } + Scalar scalar; + bool resolved = operand->ResolveValue(scalar); + if (!resolved) + break; + + bool negated = scalar.UnaryNegate(); + if (negated) + return ValueObject::CreateValueObjectFromScalar( + m_target, scalar, operand->GetCompilerType(), "result"); + break; } - - // Unsupported/invalid operation. - return llvm::make_error<DILDiagnosticError>( - m_expr, "invalid ast: unexpected binary operator", node->GetLocation()); + case UnaryOpKind::Plus: { + if (operand->GetCompilerType().IsReferenceType()) { + operand = operand->Dereference(error); + if (error.Fail()) + return error.ToError(); + } + llvm::Expected<lldb::ValueObjectSP> conv_op = + UnaryConversion(operand, node->GetOperand()->GetLocation()); + if (!conv_op) + return conv_op; + operand = *conv_op; + CompilerType operand_type = operand->GetCompilerType(); + if (!operand_type.IsScalarType() && + // Unary plus is allowed for pointers. + !operand_type.IsPointerType()) { + std::string errMsg = + llvm::formatv("invalid argument type '{0}' to unary expression", + operand_type.GetTypeName()); + return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, + node->GetLocation()); + } + return operand; + } + } + return llvm::make_error<DILDiagnosticError>(m_expr, "invalid unary operation", + node->GetLocation()); } llvm::Expected<lldb::ValueObjectSP> @@ -499,24 +649,6 @@ Interpreter::Visit(const BitFieldExtractionNode *node) { return child_valobj_sp; } -static llvm::Expected<lldb::TypeSystemSP> -GetTypeSystemFromCU(std::shared_ptr<StackFrame> ctx) { - SymbolContext symbol_context = - ctx->GetSymbolContext(lldb::eSymbolContextCompUnit); - lldb::LanguageType language = symbol_context.comp_unit->GetLanguage(); - - symbol_context = ctx->GetSymbolContext(lldb::eSymbolContextModule); - return symbol_context.module_sp->GetTypeSystemForLanguage(language); -} - -static CompilerType GetBasicType(lldb::TypeSystemSP type_system, - lldb::BasicType basic_type) { - if (type_system) - return type_system.get()->GetBasicTypeFromAST(basic_type); - - return CompilerType(); -} - llvm::Expected<CompilerType> Interpreter::PickIntegerType(lldb::TypeSystemSP type_system, std::shared_ptr<ExecutionContextScope> ctx, @@ -608,4 +740,16 @@ Interpreter::Visit(const BooleanLiteralNode *node) { return ValueObject::CreateValueObjectFromBool(m_target, value, "result"); } +llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const CastNode *node) { + auto operand_or_err = Evaluate(node->GetOperand()); + if (!operand_or_err) + return operand_or_err; + + lldb::ValueObjectSP operand = *operand_or_err; + // Don't actually do the cast for now -- that code will be added later. + // For now just return an error message. + return llvm::make_error<DILDiagnosticError>( + m_expr, "Type casting is not supported here.", node->GetLocation()); +} + } // namespace lldb_private::dil diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp index 566bcaf..e94ce31 100644 --- a/lldb/source/ValueObject/DILParser.cpp +++ b/lldb/source/ValueObject/DILParser.cpp @@ -12,8 +12,10 @@ //===----------------------------------------------------------------------===// #include "lldb/ValueObject/DILParser.h" +#include "lldb/Host/common/DiagnosticsRendering.h" +#include "lldb/Symbol/CompileUnit.h" #include "lldb/Target/ExecutionContextScope.h" -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Target/LanguageRuntime.h" #include "lldb/ValueObject/DILAST.h" #include "lldb/ValueObject/DILEval.h" #include "llvm/ADT/StringRef.h" @@ -80,26 +82,77 @@ ASTNodeUP DILParser::Run() { // Parse an expression. // // expression: -// unary_expression +// cast_expression // -ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); } +ASTNodeUP DILParser::ParseExpression() { return ParseCastExpression(); } + +// Parse a cast_expression. +// +// cast_expression: +// unary_expression +// "(" type_id ")" cast_expression + +ASTNodeUP DILParser::ParseCastExpression() { + if (!CurToken().Is(Token::l_paren)) + return ParseUnaryExpression(); + + // This could be a type cast, try parsing the contents as a type declaration. + Token token = CurToken(); + uint32_t loc = token.GetLocation(); + + // Enable lexer backtracking, so that we can rollback in case it's not + // actually a type declaration. + + // Start tentative parsing (save token location/idx, for possible rollback). + uint32_t save_token_idx = m_dil_lexer.GetCurrentTokenIdx(); + + // Consume the token only after enabling the backtracking. + m_dil_lexer.Advance(); + + // Try parsing the type declaration. If the returned value is not valid, + // then we should rollback and try parsing the expression. + auto type_id = ParseTypeId(); + if (type_id) { + // Successfully parsed the type declaration. Commit the backtracked + // tokens and parse the cast_expression. + + if (!type_id.value().IsValid()) + return std::make_unique<ErrorNode>(); + + Expect(Token::r_paren); + m_dil_lexer.Advance(); + auto rhs = ParseCastExpression(); + + return std::make_unique<CastNode>(loc, type_id.value(), std::move(rhs), + CastKind::eNone); + } + + // Failed to parse the contents of the parentheses as a type declaration. + // Rollback the lexer and try parsing it as unary_expression. + TentativeParsingRollback(save_token_idx); + + return ParseUnaryExpression(); +} // Parse an unary_expression. // // unary_expression: // postfix_expression -// unary_operator expression +// unary_operator cast_expression // // unary_operator: // "&" // "*" +// "+" +// "-" // ASTNodeUP DILParser::ParseUnaryExpression() { - if (CurToken().IsOneOf({Token::amp, Token::star})) { + if (CurToken().IsOneOf( + {Token::amp, Token::star, Token::minus, Token::plus})) { Token token = CurToken(); uint32_t loc = token.GetLocation(); m_dil_lexer.Advance(); - auto rhs = ParseExpression(); + auto rhs = ParseCastExpression(); switch (token.GetKind()) { case Token::star: return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Deref, @@ -107,7 +160,12 @@ ASTNodeUP DILParser::ParseUnaryExpression() { case Token::amp: return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::AddrOf, std::move(rhs)); - + case Token::minus: + return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Minus, + std::move(rhs)); + case Token::plus: + return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Plus, + std::move(rhs)); default: llvm_unreachable("invalid token kind"); } @@ -274,6 +332,81 @@ std::string DILParser::ParseNestedNameSpecifier() { } } +// Parse a type_id. +// +// type_id: +// type_specifier_seq [abstract_declarator] +// +// type_specifier_seq: +// type_specifier [type_specifier] +// +// type_specifier: +// ["::"] [nested_name_specifier] type_name // not handled for now! +// builtin_typename +// +std::optional<CompilerType> DILParser::ParseTypeId() { + CompilerType type; + // For now only allow builtin types -- will expand add to this later. + auto maybe_builtin_type = ParseBuiltinType(); + if (maybe_builtin_type) { + type = *maybe_builtin_type; + } else + return {}; + + // + // abstract_declarator: + // ptr_operator [abstract_declarator] + // + std::vector<Token> ptr_operators; + while (CurToken().IsOneOf({Token::star, Token::amp})) { + Token tok = CurToken(); + ptr_operators.push_back(std::move(tok)); + m_dil_lexer.Advance(); + } + type = ResolveTypeDeclarators(type, ptr_operators); + + return type; +} + +// Parse a built-in type +// +// builtin_typename: +// identifer_seq +// +// identifier_seq +// identifer [identifier_seq] +// +// A built-in type can be a single identifier or a space-separated +// list of identifiers (e.g. "short" or "long long"). +std::optional<CompilerType> DILParser::ParseBuiltinType() { + std::string type_name = ""; + uint32_t save_token_idx = m_dil_lexer.GetCurrentTokenIdx(); + bool first_word = true; + while (CurToken().GetKind() == Token::identifier) { + if (CurToken().GetSpelling() == "const" || + CurToken().GetSpelling() == "volatile") + continue; + if (!first_word) + type_name.push_back(' '); + else + first_word = false; + type_name.append(CurToken().GetSpelling()); + m_dil_lexer.Advance(); + } + + if (type_name.size() > 0) { + lldb::TargetSP target_sp = m_ctx_scope->CalculateTarget(); + ConstString const_type_name(type_name.c_str()); + for (auto type_system_sp : target_sp->GetScratchTypeSystems()) + if (auto compiler_type = + type_system_sp->GetBuiltinTypeByName(const_type_name)) + return compiler_type; + } + + TentativeParsingRollback(save_token_idx); + return {}; +} + // Parse an id_expression. // // id_expression: @@ -339,6 +472,40 @@ std::string DILParser::ParseUnqualifiedId() { return identifier; } +CompilerType +DILParser::ResolveTypeDeclarators(CompilerType type, + const std::vector<Token> &ptr_operators) { + // Resolve pointers/references. + for (Token tk : ptr_operators) { + uint32_t loc = tk.GetLocation(); + if (tk.GetKind() == Token::star) { + // Pointers to reference types are forbidden. + if (type.IsReferenceType()) { + BailOut(llvm::formatv("'type name' declared as a pointer to a " + "reference of type {0}", + type.TypeDescription()), + loc, CurToken().GetSpelling().length()); + return {}; + } + // Get pointer type for the base type: e.g. int* -> int**. + type = type.GetPointerType(); + + } else if (tk.GetKind() == Token::amp) { + // References to references are forbidden. + // FIXME: In future we may want to allow rvalue references (i.e. &&). + if (type.IsReferenceType()) { + BailOut("type name declared as a reference to a reference", loc, + CurToken().GetSpelling().length()); + return {}; + } + // Get reference type for the base type: e.g. int -> int&. + type = type.GetLValueReferenceType(); + } + } + + return type; +} + // Parse an boolean_literal. // // boolean_literal: diff --git a/lldb/source/ValueObject/ValueObjectSynthetic.cpp b/lldb/source/ValueObject/ValueObjectSynthetic.cpp index f673c51..44e53bd 100644 --- a/lldb/source/ValueObject/ValueObjectSynthetic.cpp +++ b/lldb/source/ValueObject/ValueObjectSynthetic.cpp @@ -443,3 +443,18 @@ void ValueObjectSynthetic::SetLanguageFlags(uint64_t flags) { else this->ValueObject::SetLanguageFlags(flags); } + +void ValueObjectSynthetic::GetExpressionPath(Stream &stream, + GetExpressionPathFormat epformat) { + // A synthetic ValueObject may wrap an underlying Register or RegisterSet + // ValueObject, which requires a different approach to generating the + // expression path. In such cases, delegate to the non-synthetic value object. + if (const lldb::ValueType obj_value_type = GetValueType(); + IsSynthetic() && (obj_value_type == lldb::eValueTypeRegister || + obj_value_type == lldb::eValueTypeRegisterSet)) { + + if (const lldb::ValueObjectSP raw_value = GetNonSyntheticValue()) + return raw_value->GetExpressionPath(stream, epformat); + } + return ValueObject::GetExpressionPath(stream, epformat); +} diff --git a/lldb/source/Version/CMakeLists.txt b/lldb/source/Version/CMakeLists.txt index 8b0acb9..d179805 100644 --- a/lldb/source/Version/CMakeLists.txt +++ b/lldb/source/Version/CMakeLists.txt @@ -40,4 +40,7 @@ add_lldb_library(lldbVersion NO_PLUGIN_DEPENDENCIES ADDITIONAL_HEADERS ${version_inc} ${vcs_version_inc} + + CLANG_LIBS + clangBasic ) diff --git a/lldb/test/API/commands/frame/select-hidden/Makefile b/lldb/test/API/commands/frame/select-hidden/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/commands/frame/select-hidden/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/select-hidden/TestNavigateHiddenFrame.py b/lldb/test/API/commands/frame/select-hidden/TestNavigateHiddenFrame.py new file mode 100644 index 0000000..698447b5 --- /dev/null +++ b/lldb/test/API/commands/frame/select-hidden/TestNavigateHiddenFrame.py @@ -0,0 +1,32 @@ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class NavigateHiddenFrameTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + @add_test_categories(["libc++"]) + def test(self): + """Test going up/down a backtrace but we started in a hidden frame.""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec("main.cpp") + ) + # up + self.assertIn("__impl2", thread.selected_frame.GetFunctionName()) + self.expect("up") + self.assertIn("__impl1", thread.selected_frame.GetFunctionName()) + self.expect("up") + self.assertIn("__impl", thread.selected_frame.GetFunctionName()) + self.expect("up") + self.assertIn("non_impl", thread.selected_frame.GetFunctionName()) + + # Back down again. + self.expect("down") + self.assertIn("__impl", thread.selected_frame.GetFunctionName()) + self.expect("down") + self.assertIn("__impl1", thread.selected_frame.GetFunctionName()) + self.expect("down") + self.assertIn("__impl2", thread.selected_frame.GetFunctionName()) diff --git a/lldb/test/API/commands/frame/select-hidden/main.cpp b/lldb/test/API/commands/frame/select-hidden/main.cpp new file mode 100644 index 0000000..dc97abb --- /dev/null +++ b/lldb/test/API/commands/frame/select-hidden/main.cpp @@ -0,0 +1,13 @@ +namespace std { +namespace __1 { +static const char *__impl2() { return "Break here"; } +static const char *__impl1() { return __impl2(); } +static const char *__impl() { return __impl1(); } +static const char *non_impl() { return __impl(); } +} // namespace __1 +} // namespace std + +int main() { + std::__1::non_impl(); + __builtin_debugtrap(); +} diff --git a/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py index 4d55767..5c91755 100644 --- a/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py +++ b/lldb/test/API/commands/frame/var-dil/basics/GlobalVariableLookup/TestFrameVarDILGlobalVariableLookup.py @@ -19,7 +19,7 @@ class TestFrameVarDILGlobalVariableLookup(TestBase): NO_DEBUG_INFO_TESTCASE = True @skipIf(macos_version=["<", "15.0"], archs=["arm64", "arm64e"]) - @expectedFailureAll( + @skipIf( dwarf_version=["<", "5"], oslist=[lldbplatformutil.getDarwinOSTriples()], ) diff --git a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py new file mode 100644 index 0000000..53a85fe --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py @@ -0,0 +1,46 @@ +""" +Test DIL arithmetic. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestFrameVarDILArithmetic(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_arithmetic(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + + # Check unary results and integral promotion + self.expect_var_path("+0", value="0") + self.expect_var_path("-0", value="0") + self.expect_var_path("+1", value="1") + self.expect_var_path("-1", value="-1") + self.expect_var_path("-9223372036854775808", value="9223372036854775808") + self.expect_var_path("s", value="10", type="short") + self.expect_var_path("+s", value="10", type="int") + self.expect_var_path("-s", value="-10", type="int") + self.expect_var_path("+us", value="1", type="int") + self.expect_var_path("-us", value="-1", type="int") + self.expect_var_path("+ref", value="2", type="int") + self.expect_var_path("-ref", value="-2", type="int") + self.expect_var_path("+0.0", value="0") + self.expect_var_path("-0.0", value="-0") + self.expect_var_path("+enum_one", value="1") + self.expect_var_path("-enum_one", value="-1") + self.expect_var_path("+wchar", value="1") + self.expect_var_path("+char16", value="2") + self.expect_var_path("+char32", value="3") + self.expect_var_path("-bitfield.a", value="-1", type="int") + self.expect_var_path("+bitfield.a", value="1", type="int") + self.expect_var_path("+bitfield.b", value="2", type="int") + self.expect_var_path("+bitfield.c", value="3", type="unsigned int") + self.expect_var_path("+bitfield.d", value="4", type="uint64_t") diff --git a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp new file mode 100644 index 0000000..2c70e93 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp @@ -0,0 +1,23 @@ +#include <cstdint> + +int main(int argc, char **argv) { + short s = 10; + unsigned short us = 1; + + int x = 2; + int &ref = x; + enum Enum { kZero, kOne } enum_one = kOne; + wchar_t wchar = 1; + char16_t char16 = 2; + char32_t char32 = 3; + + struct BitFieldStruct { + char a : 4; + int b : 32; + unsigned int c : 32; + uint64_t d : 48; + }; + BitFieldStruct bitfield = {1, 2, 3, 4}; + + return 0; // Set a breakpoint here +} diff --git a/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/Makefile b/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/TestFrameVarDILExprPointerArithmetic.py b/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/TestFrameVarDILExprPointerArithmetic.py new file mode 100644 index 0000000..448cd5b --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/TestFrameVarDILExprPointerArithmetic.py @@ -0,0 +1,29 @@ +""" +Test DIL pointer arithmetic. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test import lldbutil + + +class TestFrameVarDILExprPointerArithmetic(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_pointer_arithmetic(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp") + ) + + self.runCmd("settings set target.experimental.use-DIL true") + + self.expect_var_path("+array", type="int *") + self.expect_var_path("+array_ref", type="int *") + self.expect_var_path("+p_int0", type="int *") + self.expect( + "frame var -- '-p_int0'", + error=True, + substrs=["invalid argument type 'int *' to unary expression"], + ) diff --git a/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/main.cpp new file mode 100644 index 0000000..b4e0e88 --- /dev/null +++ b/lldb/test/API/commands/frame/var-dil/expr/PointerArithmetic/main.cpp @@ -0,0 +1,11 @@ +void stop() {} + +int main(int argc, char **argv) { + int array[10]; + array[0] = 0; + int (&array_ref)[10] = array; + int *p_int0 = &array[0]; + + stop(); // Set a breakpoint here + return 0; +} diff --git a/lldb/test/API/commands/target/auto-install-main-executable/Makefile b/lldb/test/API/commands/target/auto-install-main-executable/Makefile index 07e6c9a..d0578fb 100644 --- a/lldb/test/API/commands/target/auto-install-main-executable/Makefile +++ b/lldb/test/API/commands/target/auto-install-main-executable/Makefile @@ -6,4 +6,4 @@ a.out: a.device.out include Makefile.rules a.device.out: - $(CXX) $(CXXFLAGS) -DBUILD=74 -o $@ $(SRCDIR)/main.cpp + $(CXX) $(ASAN_LDFLAGS) $(CXXFLAGS) -DBUILD=74 -o $@ $(SRCDIR)/main.cpp diff --git a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py index 954cac1..8e91781 100644 --- a/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py +++ b/lldb/test/API/commands/target/stop-hooks/TestStopHookScripted.py @@ -48,6 +48,39 @@ class TestStopHooks(TestBase): "Got the right error", ) + def test_self_deleting(self): + """Test that we can handle a stop hook that deletes itself""" + self.script_setup() + # Run to the first breakpoint before setting the stop hook + # so we don't have to figure out where it showed up in the new + # target. + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "Stop here first", self.main_source_file + ) + + # Now add our stop hook and register it: + result = lldb.SBCommandReturnObject() + command = "target stop-hook add -P stop_hook.self_deleting_stop" + self.interp.HandleCommand(command, result) + self.assertCommandReturn(result, f"Added my stop hook: {result.GetError()}") + + result_str = result.GetOutput() + p = re.compile("Stop hook #([0-9]+) added.") + m = p.match(result_str) + current_stop_hook_id = m.group(1) + command = "command script add -o -f stop_hook.handle_stop_hook_id handle_id" + self.interp.HandleCommand(command, result) + self.assertCommandReturn(result, "Added my command") + + command = f"handle_id {current_stop_hook_id}" + self.interp.HandleCommand(command, result) + self.assertCommandReturn(result, "Registered my stop ID") + + # Now step the process and make sure the stop hook was deleted. + thread.StepOver() + self.interp.HandleCommand("target stop-hook list", result) + self.assertEqual(result.GetOutput().rstrip(), "No stop hooks.", "Deleted hook") + def test_stop_hooks_scripted(self): """Test that a scripted stop hook works with no specifiers""" self.stop_hooks_scripted(5, "-I false") diff --git a/lldb/test/API/commands/target/stop-hooks/stop_hook.py b/lldb/test/API/commands/target/stop-hooks/stop_hook.py index cb7a433..a41190ba 100644 --- a/lldb/test/API/commands/target/stop-hooks/stop_hook.py +++ b/lldb/test/API/commands/target/stop-hooks/stop_hook.py @@ -48,3 +48,28 @@ class bad_handle_stop: class no_handle_stop: def __init__(self, target, extra_args, dict): print("I am okay") + + +class self_deleting_stop: + def __init__(self, target, extra_args, dict): + self.target = target + + def handle_stop(self, exe_ctx, stream): + interp = exe_ctx.target.debugger.GetCommandInterpreter() + result = lldb.SBCommandReturnObject() + interp.HandleCommand("handle_id", result) + id_str = result.GetOutput().rstrip() + + command = f"target stop-hook delete {id_str}" + interp.HandleCommand(command, result) + + +stop_hook_id = 0 + + +def handle_stop_hook_id(debugger, command, exe_ctx, result, extra_args): + global stop_hook_id + if command == "": + result.AppendMessage(str(stop_hook_id)) + else: + stop_hook_id = int(command) diff --git a/lldb/test/API/functionalities/abbreviation/TestAbbreviations.py b/lldb/test/API/functionalities/abbreviation/TestAbbreviations.py index cc767ed..5dd4f6b 100644 --- a/lldb/test/API/functionalities/abbreviation/TestAbbreviations.py +++ b/lldb/test/API/functionalities/abbreviation/TestAbbreviations.py @@ -41,7 +41,7 @@ class AbbreviationsTestCase(TestBase): # "pl" could be "platform" or "plugin". command_interpreter.ResolveCommand("pl", result) self.assertFalse(result.Succeeded()) - self.assertTrue(result.GetError().startswith("Ambiguous command")) + self.assertTrue(result.GetError().startswith("error: Ambiguous command")) # Make sure an unabbreviated command is not mangled. command_interpreter.ResolveCommand( diff --git a/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py b/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py index 14c66fe..31b67d7 100644 --- a/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py +++ b/lldb/test/API/functionalities/ambigous_commands/TestAmbiguousCommands.py @@ -24,7 +24,7 @@ class AmbiguousCommandTestCase(TestBase): self.assertFalse(result.Succeeded()) self.assertEqual( result.GetError(), - "Ambiguous command 'co'. Possible matches:\n\tcommand\n\tcontinue\n\tcorefile\n", + "error: Ambiguous command 'co'. Possible matches:\n\tcommand\n\tcontinue\n\tcorefile\n", ) command_interpreter.HandleCommand("command unalias continue", result) diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_by_line_and_column/TestBreakpointByLineAndColumn.py b/lldb/test/API/functionalities/breakpoint/breakpoint_by_line_and_column/TestBreakpointByLineAndColumn.py index 5798c8f..1f57181 100644 --- a/lldb/test/API/functionalities/breakpoint/breakpoint_by_line_and_column/TestBreakpointByLineAndColumn.py +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_by_line_and_column/TestBreakpointByLineAndColumn.py @@ -27,6 +27,25 @@ class BreakpointByLineAndColumnTestCase(TestBase): in_then |= b_loc.GetColumn() == 50 self.assertTrue(in_then) + def testBreakpointByLineAndColumnUsingCLI(self): + self.build() + src_file = lldb.SBFileSpec("main.cpp") + line = ( + line_number("main.cpp", "At the beginning of a function name (col:50)") + 1 + ) # Next line after comment + target, process, _, _ = lldbutil.run_to_source_breakpoint(self, "This is a random comment", src_file) + bkpt_no = lldbutil.run_break_set_by_file_and_line(self, "main.cpp", line, "--column 50") + breakpoint = target.FindBreakpointByID(bkpt_no) + threads = lldbutil.continue_to_breakpoint(process, breakpoint) + self.assertEqual(len(threads), 1, "Stopped at our breakpoint") + self.expect("fr v did_call", substrs=["1"]) + in_then = False + for i in range(breakpoint.GetNumLocations()): + b_loc = breakpoint.GetLocationAtIndex(i).GetAddress().GetLineEntry() + self.assertEqual(b_loc.GetLine(), line) + in_then |= b_loc.GetColumn() == 50 + self.assertTrue(in_then) + ## Skip gcc version less 7.1 since it doesn't support -gcolumn-info @skipIf(compiler="gcc", compiler_version=["<", "7.1"]) def testBreakpointByLine(self): diff --git a/lldb/test/API/functionalities/breakpoint/breakpoint_locations/after_rebuild/TestLocationsAfterRebuild.py b/lldb/test/API/functionalities/breakpoint/breakpoint_locations/after_rebuild/TestLocationsAfterRebuild.py index 1c7bb53..bc53fea 100644 --- a/lldb/test/API/functionalities/breakpoint/breakpoint_locations/after_rebuild/TestLocationsAfterRebuild.py +++ b/lldb/test/API/functionalities/breakpoint/breakpoint_locations/after_rebuild/TestLocationsAfterRebuild.py @@ -54,6 +54,24 @@ class TestLocationsAfterRebuild(TestBase): self, target, bkpt ) + # After enabling locate_module callback for main executables, + # the number of locations may vary depending on the platform. + num_locs = bkpt.GetNumLocations() bkpt_id = bkpt.GetID() - loc_string = f"{bkpt_id}.3" - self.runCmd(f"break disable {loc_string}") + + self.assertGreater( + num_locs, + 0, + f"Expected at least one breakpoint location, but found {num_locs}", + ) + + # Iterate through all valid locations and verify we can disable each one. + # This tests that breakpoint location IDs remain valid after rebuilds. + for loc_idx in range(num_locs): + loc = bkpt.GetLocationAtIndex(loc_idx) + self.assertTrue(loc.IsValid(), f"Location at index {loc_idx} is not valid") + + # Get the actual location ID from the location object + loc_id = loc.GetID() + loc_string = f"{bkpt_id}.{loc_id}" + self.runCmd(f"break disable {loc_string}") diff --git a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/simple_hw_breakpoints/TestSimpleHWBreakpoints.py b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/simple_hw_breakpoints/TestSimpleHWBreakpoints.py index ccbb235..acf75df 100644 --- a/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/simple_hw_breakpoints/TestSimpleHWBreakpoints.py +++ b/lldb/test/API/functionalities/breakpoint/hardware_breakpoints/simple_hw_breakpoints/TestSimpleHWBreakpoints.py @@ -7,13 +7,7 @@ from functionalities.breakpoint.hardware_breakpoints.base import * class SimpleHWBreakpointTest(HardwareBreakpointTestBase): - def does_not_support_hw_breakpoints(self): - # FIXME: Use HardwareBreakpointTestBase.supports_hw_breakpoints - if super().supports_hw_breakpoints() is None: - return "Hardware breakpoints are unsupported" - return None - - @skipTestIfFn(does_not_support_hw_breakpoints) + @skipTestIfFn(HardwareBreakpointTestBase.hw_breakpoints_unsupported) def test(self): """Test SBBreakpoint::SetIsHardware""" self.build() @@ -35,16 +29,13 @@ class SimpleHWBreakpointTest(HardwareBreakpointTestBase): # breakpoint will be marked as a hardware breakpoint. self.assertTrue(break_on_me_bp.IsHardware()) - if super().supports_hw_breakpoints(): - self.assertSuccess(error) - - # Continue to our Hardware breakpoint and verify that's the reason - # we're stopped. - process.Continue() - self.expect( - "thread list", - STOPPED_DUE_TO_BREAKPOINT, - substrs=["stopped", "stop reason = breakpoint"], - ) - else: - self.assertFailure(error) + self.assertSuccess(error) + + # Continue to our Hardware breakpoint and verify that's the reason + # we're stopped. + process.Continue() + self.expect( + "thread list", + STOPPED_DUE_TO_BREAKPOINT, + substrs=["stopped", "stop reason = breakpoint"], + ) diff --git a/lldb/test/API/functionalities/breakpoint/serialize/TestBreakpointSerialization.py b/lldb/test/API/functionalities/breakpoint/serialize/TestBreakpointSerialization.py index 411ce9c..55cc12e 100644 --- a/lldb/test/API/functionalities/breakpoint/serialize/TestBreakpointSerialization.py +++ b/lldb/test/API/functionalities/breakpoint/serialize/TestBreakpointSerialization.py @@ -210,6 +210,11 @@ class BreakpointSerialization(TestBase): "Source and dest breakpoints are not identical: \nsource: %s\ndest: %s" % (source_text, copy_text), ) + self.assertEqual( + source_bp.GetNumLocations(), + copy_bp.GetNumLocations(), + "Source and dest num locations are not the same", + ) def do_check_resolvers(self): """Use Python APIs to check serialization of breakpoint resolvers""" @@ -386,7 +391,7 @@ class BreakpointSerialization(TestBase): source_bps.Clear() bkpt = self.orig_target.BreakpointCreateByName( - "blubby", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list + "main", lldb.eFunctionNameTypeAuto, empty_module_list, empty_cu_list ) bkpt.SetIgnoreCount(10) bkpt.SetThreadName("grubby") @@ -394,7 +399,7 @@ class BreakpointSerialization(TestBase): all_bps.Append(bkpt) bkpt = self.orig_target.BreakpointCreateByName( - "blubby", lldb.eFunctionNameTypeFull, empty_module_list, empty_cu_list + "main", lldb.eFunctionNameTypeFull, empty_module_list, empty_cu_list ) bkpt.SetCondition("something != something_else") bkpt.SetQueueName("grubby") diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py index 45695c4..1db0c48 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py @@ -9,6 +9,8 @@ from lldbsuite.test import lldbutil class TestDataFormatterGenericForwardList(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): TestBase.setUp(self) self.line = line_number("main.cpp", "// break here") diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/TestDataFormatterStdInitializerList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/TestDataFormatterStdInitializerList.py index 133f8f7d..38d8cdb 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/TestDataFormatterStdInitializerList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/TestDataFormatterStdInitializerList.py @@ -28,13 +28,25 @@ class InitializerListTestCase(TestBase): substrs=["stopped", "stop reason = breakpoint"], ) - self.expect("frame variable ili", substrs=["[1] = 2", "[4] = 5"]) + self.expect( + "frame variable ili", + substrs=["ili = size=5", "[0] = 1", "[1] = 2", "[4] = 5"], + ) self.expect( "frame variable ils", - substrs=['[4] = "surprise it is a long string!! yay!!"'], + substrs=[ + "ils = size=5", + '[0] = "1"', + '[4] = "surprise it is a long string!! yay!!"', + ], ) @add_test_categories(["libc++"]) def test_libcxx(self): self.build(dictionary={"USE_LIBCPP": 1}) self.do_test() + + @add_test_categories(["libstdcxx"]) + def test_libstdcpp(self): + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test() diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/main.cpp index a9d159e..7c62cac 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/initializer_list/main.cpp @@ -1,6 +1,5 @@ #include <initializer_list> #include <string> -#include <vector> int main() { std::initializer_list<int> ili{1, 2, 3, 4, 5}; diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py index c0207e6..fbd0211 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py @@ -10,6 +10,8 @@ from lldbsuite.test import lldbutil class GenericListDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): # Call super's setUp(). TestBase.setUp(self) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py index f6174dd..9c5daf7 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py @@ -11,6 +11,7 @@ from lldbsuite.test import lldbutil class GenericListDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True NO_DEBUG_INFO_TESTCASE = True def do_test_with_run_command(self): diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/map/TestDataFormatterStdMap.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/map/TestDataFormatterStdMap.py index 07d6c96..ca2d2d6 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/map/TestDataFormatterStdMap.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/map/TestDataFormatterStdMap.py @@ -9,6 +9,8 @@ from lldbsuite.test import lldbutil class StdMapDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): TestBase.setUp(self) ns = "ndk" if lldbplatformutil.target_is_android() else "" diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py index 7ac7971..4b0854b 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multimap/TestDataFormatterGenericMultiMap.py @@ -11,6 +11,8 @@ from lldbsuite.test import lldbutil class GenericMultiMapDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): TestBase.setUp(self) self.namespace = "std" diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multiset/TestDataFormatterGenericMultiSet.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multiset/TestDataFormatterGenericMultiSet.py index 7e922fc..e846e07 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multiset/TestDataFormatterGenericMultiSet.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/multiset/TestDataFormatterGenericMultiSet.py @@ -10,6 +10,8 @@ from lldbsuite.test import lldbutil class GenericMultiSetDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): TestBase.setUp(self) self.namespace = "std" diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/optional/TestDataFormatterGenericOptional.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/optional/TestDataFormatterGenericOptional.py index 7bb4f75..c88e83b 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/optional/TestDataFormatterGenericOptional.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/optional/TestDataFormatterGenericOptional.py @@ -5,6 +5,8 @@ from lldbsuite.test import lldbutil class GenericOptionalDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def do_test_with_run_command(self): """Test that that file and class static variables display correctly.""" @@ -55,7 +57,11 @@ class GenericOptionalDataFormatterTestCase(TestBase): self.expect( "frame var numbers", substrs=[ - "(optional_int_vect) numbers = Has Value=true {", + ( + "(std::optional<std::vector<int, std::allocator<int>>>) numbers = Has Value=true {" + if self.getDebugInfo() == "pdb" + else "(optional_int_vect) numbers = Has Value=true {" + ), "Value = size=4 {", "[0] = 1", "[1] = 2", @@ -69,7 +75,11 @@ class GenericOptionalDataFormatterTestCase(TestBase): self.expect( "frame var ostring", substrs=[ - "(optional_string) ostring = Has Value=true {", + ( + "(std::optional<std::basic_string<char, std::char_traits<char>, std::allocator<char>>>) ostring = Has Value=true {" + if self.getDebugInfo() == "pdb" + else "(optional_string) ostring = Has Value=true {" + ), 'Value = "hello"', "}", ], diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/set/TestDataFormatterGenericSet.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/set/TestDataFormatterGenericSet.py index 1ac5e32..355f0c6 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/set/TestDataFormatterGenericSet.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/set/TestDataFormatterGenericSet.py @@ -10,6 +10,8 @@ from lldbsuite.test import lldbutil class GenericSetDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): TestBase.setUp(self) self.namespace = "std" diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py index d71fbf8..fa03fc1 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/shared_ptr/TestDataFormatterStdSharedPtr.py @@ -9,6 +9,8 @@ from lldbsuite.test import lldbutil class TestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def do_test(self): """Test `frame variable` output for `std::shared_ptr` types.""" (_, process, _, bkpt) = lldbutil.run_to_source_breakpoint( @@ -62,7 +64,7 @@ class TestCase(TestBase): valobj = self.expect_var_path("sp_user", type="std::shared_ptr<User>") self.assertRegex( valobj.summary, - "element_type @ 0x0*[1-9a-f][0-9a-f]+( strong=1)? weak=0", + f"{'User' if self.getDebugInfo() == 'pdb' else 'element_type'} @ 0x0*[1-9a-f][0-9a-f]+( strong=1)? weak=0", ) self.assertNotEqual(valobj.child[0].unsigned, 0) @@ -77,7 +79,15 @@ class TestCase(TestBase): self.assertEqual(str(valobj), '(User) *pointer = (id = 30, name = "steph")') self.expect_var_path("sp_user->id", type="int", value="30") - self.expect_var_path("sp_user->name", type="std::string", summary='"steph"') + self.expect_var_path( + "sp_user->name", + type=( + "std::basic_string<char, std::char_traits<char>, std::allocator<char>>" + if self.getDebugInfo() == "pdb" + else "std::string" + ), + summary='"steph"', + ) valobj = self.expect_var_path( "si", type="std::shared_ptr<int>", summary="47 strong=2 weak=0" diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py index a45c0ff..f586fb3 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/span/TestDataFormatterStdSpan.py @@ -74,7 +74,7 @@ class StdSpanDataFormatterTestCase(TestBase): result_summary="item 0 is 1", ) - self.runCmd("type summary delete span") + self.runCmd("type summary clear") # New span with strings lldbutil.continue_to_breakpoint(process, bkpt) @@ -155,12 +155,6 @@ class StdSpanDataFormatterTestCase(TestBase): ) self.check_size("nested", 2) - @skipIf(compiler="clang", compiler_version=["<", "11.0"]) - @add_test_categories(["libc++"]) - def test_libcxx(self): - self.build(dictionary={"USE_LIBCPP": 1}) - self.do_test() - def do_test_ref_and_ptr(self): """Test that std::span is correctly formatted when passed by ref and ptr""" (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( @@ -176,6 +170,24 @@ class StdSpanDataFormatterTestCase(TestBase): @skipIf(compiler="clang", compiler_version=["<", "11.0"]) @add_test_categories(["libc++"]) + def test_libcxx(self): + self.build(dictionary={"USE_LIBCPP": 1}) + self.do_test() + + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) + @add_test_categories(["libc++"]) def test_ref_and_ptr_libcxx(self): self.build(dictionary={"USE_LIBCPP": 1}) self.do_test_ref_and_ptr() + + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) + @add_test_categories(["libstdcxx"]) + def test_libstdcxx(self): + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test() + + @skipIf(compiler="clang", compiler_version=["<", "11.0"]) + @add_test_categories(["libstdcxx"]) + def test_ref_and_ptr_libstdcxx(self): + self.build(dictionary={"USE_LIBSTDCPP": 1}) + self.do_test_ref_and_ptr() diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string/TestDataFormatterStdString.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string/TestDataFormatterStdString.py index 6a27b5d..00047e41 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string/TestDataFormatterStdString.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string/TestDataFormatterStdString.py @@ -11,6 +11,8 @@ from lldbsuite.test import lldbutil class StdStringDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): # Call super's setUp(). TestBase.setUp(self) @@ -18,6 +20,17 @@ class StdStringDataFormatterTestCase(TestBase): self.main_spec = lldb.SBFileSpec("main.cpp") self.namespace = "std" + def _makeStringName(self, typedef: str, char_type: str, allocator=None): + if allocator is None: + allocator = self.namespace + "::allocator" + + if self.getDebugInfo() == "pdb": + return f"{self.namespace}::basic_string<{char_type}, std::char_traits<{char_type}>, {allocator}<{char_type}>>" + + if typedef.startswith("::"): + return self.namespace + typedef + return typedef + def do_test(self): """Test that that file and class static variables display correctly.""" (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( @@ -36,10 +49,17 @@ class StdStringDataFormatterTestCase(TestBase): # Execute the cleanup function during test case tear down. self.addTearDownHook(cleanup) - ns = self.namespace + string_name = self._makeStringName("::string", "char") + wstring_name = self._makeStringName("::wstring", "wchar_t") + custom_string_name = self._makeStringName( + "CustomString", "char", allocator="CustomAlloc" + ) + custom_wstring_name = self._makeStringName( + "CustomWString", "wchar_t", allocator="CustomAlloc" + ) # Check 'S' pre-assignment. - self.expect("frame variable S", substrs=['(%s::wstring) S = L"!!!!"' % ns]) + self.expect("frame variable S", substrs=[f'({wstring_name}) S = L"!!!!"']) thread.StepOver() @@ -54,34 +74,31 @@ class StdStringDataFormatterTestCase(TestBase): ) self.expect_expr( - "s", result_type=ns + "::wstring", result_summary='L"hello world! מזל טוב!"' + "s", result_type=wstring_name, result_summary='L"hello world! מזל טוב!"' ) - self.expect_expr( - "q", result_type=ns + "::string", result_summary='"hello world"' - ) + self.expect_expr("q", result_type=string_name, result_summary='"hello world"') self.expect_expr( "Q", - result_type=ns + "::string", + result_type=string_name, result_summary='"quite a long std::strin with lots of info inside it"', ) self.expect( "frame variable", substrs=[ - '(%s::wstring) wempty = L""' % ns, - '(%s::wstring) s = L"hello world! מזל טוב!"' % ns, - '(%s::wstring) S = L"!!!!!"' % ns, + f'({wstring_name}) wempty = L""', + f'({wstring_name}) s = L"hello world! מזל טוב!"', + f'({wstring_name}) S = L"!!!!!"', "(const wchar_t *) mazeltov = 0x", 'L"מזל טוב"', - '(%s::string) empty = ""' % ns, - '(%s::string) q = "hello world"' % ns, - '(%s::string) Q = "quite a long std::strin with lots of info inside it"' - % ns, - "(%s::string *) null_str = nullptr" % ns, - '(CustomString) custom_str = "hello!"', - '(CustomWString) custom_wstr = L"hello!"', + f'({string_name}) empty = ""', + f'({string_name}) q = "hello world"', + f'({string_name}) Q = "quite a long std::strin with lots of info inside it"', + f"({string_name} *) null_str = nullptr", + f'({custom_string_name}) custom_str = "hello!"', + f'({custom_wstring_name}) custom_wstr = L"hello!"', ], ) @@ -136,19 +153,26 @@ class StdStringDataFormatterTestCase(TestBase): self, "Set break point at this line.", self.main_spec ) - ns = self.namespace + u16string_name = self._makeStringName("::u16string", "char16_t") + u32string_name = self._makeStringName("::u32string", "char32_t") + custom_u16string_name = self._makeStringName( + "CustomStringU16", "char16_t", allocator="CustomAlloc" + ) + custom_u32string_name = self._makeStringName( + "CustomStringU32", "char32_t", allocator="CustomAlloc" + ) self.expect( "frame variable", substrs=[ - '(%s::u16string) u16_string = u"ß水氶"' % ns, - '(%s::u16string) u16_empty = u""' % ns, - '(%s::u32string) u32_string = U"🍄🍅🍆🍌"' % ns, - '(%s::u32string) u32_empty = U""' % ns, - '(CustomStringU16) custom_u16 = u"ß水氶"', - '(CustomStringU16) custom_u16_empty = u""', - '(CustomStringU32) custom_u32 = U"🍄🍅🍆🍌"', - '(CustomStringU32) custom_u32_empty = U""', + f'({u16string_name}) u16_string = u"ß水氶"', + f'({u16string_name}) u16_empty = u""', + f'({u32string_name}) u32_string = U"🍄🍅🍆🍌"', + f'({u32string_name}) u32_empty = U""', + f'({custom_u16string_name}) custom_u16 = u"ß水氶"', + f'({custom_u16string_name}) custom_u16_empty = u""', + f'({custom_u32string_name}) custom_u32 = U"🍄🍅🍆🍌"', + f'({custom_u32string_name}) custom_u32_empty = U""', ], ) @@ -271,9 +295,8 @@ class StdStringDataFormatterTestCase(TestBase): self.expect( "frame variable", substrs=[ - '(%s::string) IHaveEmbeddedZeros = "a\\0b\\0c\\0d"' % ns, - '(%s::wstring) IHaveEmbeddedZerosToo = L"hello world!\\0てざ ル゜䋨ミ㠧槊 きゅへ狦穤襩 じゃ馩リョ 䤦監"' - % ns, + f'({self._makeStringName("::string", "char")}) IHaveEmbeddedZeros = "a\\0b\\0c\\0d"', + f'({self._makeStringName("::wstring", "wchar_t")}) IHaveEmbeddedZerosToo = L"hello world!\\0てざ ル゜䋨ミ㠧槊 きゅへ狦穤襩 じゃ馩リョ 䤦監"', ], ) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string_view/TestDataFormatterStdStringView.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string_view/TestDataFormatterStdStringView.py index 1811418..5c915b6 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string_view/TestDataFormatterStdStringView.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/string_view/TestDataFormatterStdStringView.py @@ -11,6 +11,8 @@ from lldbsuite.test import lldbutil class StdStringViewDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): # Call super's setUp(). TestBase.setUp(self) @@ -20,6 +22,12 @@ class StdStringViewDataFormatterTestCase(TestBase): "main.cpp", "// Break here to look at bad string view." ) + def _makeStringName(self, typedef: str, char_type: str): + if self.getDebugInfo() == "pdb": + return f"std::basic_string_view<{char_type}, std::char_traits<{char_type}>>" + + return typedef + def do_test(self): """Test that that file and class static variables display correctly.""" self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) @@ -51,39 +59,47 @@ class StdStringViewDataFormatterTestCase(TestBase): # Execute the cleanup function during test case tear down. self.addTearDownHook(cleanup) - self.expect_var_path("wempty", type="std::wstring_view", summary='L""') + string_view_name = self._makeStringName("std::string_view", "char") + wstring_view_name = self._makeStringName("std::wstring_view", "wchar_t") + u16string_view_name = self._makeStringName("std::u16string_view", "char16_t") + u32string_view_name = self._makeStringName("std::u32string_view", "char32_t") + string_name = ( + "std::basic_string<char, std::char_traits<char>, std::allocator<char>>" + if self.getDebugInfo() == "pdb" + else "std::string" + ) + + self.expect_var_path("wempty", type=wstring_view_name, summary='L""') self.expect_var_path( - "s", type="std::wstring_view", summary='L"hello world! מזל טוב!"' + "s", type=wstring_view_name, summary='L"hello world! מזל טוב!"' ) - self.expect_var_path("S", type="std::wstring_view", summary='L"!!!!"') - self.expect_var_path("empty", type="std::string_view", summary='""') - self.expect_var_path("q_source", type="std::string", summary='"hello world"') - self.expect_var_path("q", type="std::string_view", summary='"hello world"') + self.expect_var_path("S", type=wstring_view_name, summary='L"!!!!"') + self.expect_var_path("empty", type=string_view_name, summary='""') + self.expect_var_path("q_source", type=string_name, summary='"hello world"') + self.expect_var_path("q", type=string_view_name, summary='"hello world"') self.expect_var_path( "Q", - type="std::string_view", + type=string_view_name, summary='"quite a long std::strin with lots of info inside it"', ) self.expect_var_path( - "IHaveEmbeddedZeros", type="std::string_view", summary='"a\\0b\\0c\\0d"' + "IHaveEmbeddedZeros", type=string_view_name, summary='"a\\0b\\0c\\0d"' ) self.expect_var_path( "IHaveEmbeddedZerosToo", - type="std::wstring_view", + type=wstring_view_name, summary='L"hello world!\\0てざ ル゜䋨ミ㠧槊 きゅへ狦穤襩 じゃ馩リョ 䤦監"', ) - self.expect_var_path("u16_string", type="std::u16string_view", summary='u"ß水氶"') - self.expect_var_path("u16_empty", type="std::u16string_view", summary='u""') - self.expect_var_path( - "u32_string", type="std::u32string_view", summary='U"🍄🍅🍆🍌"' - ) - self.expect_var_path("u32_empty", type="std::u32string_view", summary='U""') + self.expect_var_path("u16_string", type=u16string_view_name, summary='u"ß水氶"') + self.expect_var_path("u16_empty", type=u16string_view_name, summary='u""') + self.expect_var_path("u32_string", type=u32string_view_name, summary='U"🍄🍅🍆🍌"') + self.expect_var_path("u32_empty", type=u32string_view_name, summary='U""') # GetSummary returns None so can't be checked by expect_var_path, so we # use the str representation instead null_obj = self.frame().GetValueForVariablePath("null_str") self.assertEqual(null_obj.GetSummary(), "Summary Unavailable") - self.assertEqual(str(null_obj), "(std::string_view *) null_str = nullptr") + self.assertEqual(str(null_obj), f"({string_view_name} *) null_str = nullptr") self.runCmd("n") @@ -108,37 +124,35 @@ class StdStringViewDataFormatterTestCase(TestBase): self.expect_expr( "s", - result_type="std::wstring_view", + result_type=wstring_view_name, result_summary='L"hello world! מזל טוב!"', ) - self.expect_var_path("wempty", type="std::wstring_view", summary='L""') + self.expect_var_path("wempty", type=wstring_view_name, summary='L""') self.expect_var_path( - "s", type="std::wstring_view", summary='L"hello world! מזל טוב!"' + "s", type=wstring_view_name, summary='L"hello world! מזל טוב!"' ) - self.expect_var_path("S", type="std::wstring_view", summary='L"!!!!"') - self.expect_var_path("empty", type="std::string_view", summary='""') - self.expect_var_path("q_source", type="std::string", summary='"Hello world"') - self.expect_var_path("q", type="std::string_view", summary='"Hello world"') + self.expect_var_path("S", type=wstring_view_name, summary='L"!!!!"') + self.expect_var_path("empty", type=string_view_name, summary='""') + self.expect_var_path("q_source", type=string_name, summary='"Hello world"') + self.expect_var_path("q", type=string_view_name, summary='"Hello world"') self.expect_var_path( "Q", - type="std::string_view", + type=string_view_name, summary='"quite a long std::strin with lots of info inside it"', ) self.expect_var_path( - "IHaveEmbeddedZeros", type="std::string_view", summary='"a\\0b\\0c\\0d"' + "IHaveEmbeddedZeros", type=string_view_name, summary='"a\\0b\\0c\\0d"' ) self.expect_var_path( "IHaveEmbeddedZerosToo", - type="std::wstring_view", + type=wstring_view_name, summary='L"hello world!\\0てざ ル゜䋨ミ㠧槊 きゅへ狦穤襩 じゃ馩リョ 䤦監"', ) - self.expect_var_path("u16_string", type="std::u16string_view", summary='u"ß水氶"') - self.expect_var_path("u16_empty", type="std::u16string_view", summary='u""') - self.expect_var_path( - "u32_string", type="std::u32string_view", summary='U"🍄🍅🍆🍌"' - ) - self.expect_var_path("u32_empty", type="std::u32string_view", summary='U""') + self.expect_var_path("u16_string", type=u16string_view_name, summary='u"ß水氶"') + self.expect_var_path("u16_empty", type=u16string_view_name, summary='u""') + self.expect_var_path("u32_string", type=u32string_view_name, summary='U"🍄🍅🍆🍌"') + self.expect_var_path("u32_empty", type=u32string_view_name, summary='U""') self.runCmd("cont") self.expect( diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py index b23d549..8984387 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/tuple/TestDataFormatterStdTuple.py @@ -9,6 +9,8 @@ from lldbsuite.test import lldbutil class TestDataFormatterStdTuple(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): TestBase.setUp(self) self.line = line_number("main.cpp", "// break here") diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string/TestDataFormatterStdU8String.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string/TestDataFormatterStdU8String.py index b983ee1..dda97945f 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string/TestDataFormatterStdU8String.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string/TestDataFormatterStdU8String.py @@ -11,18 +11,26 @@ from lldbsuite.test import lldbutil class StdU8StringDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def do_test(self): lldbutil.run_to_source_breakpoint( self, "Set break point at this line.", lldb.SBFileSpec("main.cpp") ) + string_name = ( + "std::basic_string<char8_t, std::char_traits<char8_t>, std::allocator<char8_t>>" + if self.getDebugInfo() == "pdb" + else "std::u8string" + ) + self.expect( "frame variable", substrs=[ - '(std::u8string) u8_string_small = u8"🍄"', - '(std::u8string) u8_string = u8"❤️👍📄📁😃🧑🌾"', - '(std::u8string) u8_empty = u8""', - '(std::u8string) u8_text = u8"ABCd"', + f'({string_name}) u8_string_small = u8"🍄"', + f'({string_name}) u8_string = u8"❤️👍📄📁😃🧑🌾"', + f'({string_name}) u8_empty = u8""', + f'({string_name}) u8_text = u8"ABCd"', ], ) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string_view/TestDataFormatterStdU8StringView.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string_view/TestDataFormatterStdU8StringView.py index 1e35a0f..6cf72d1 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string_view/TestDataFormatterStdU8StringView.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/u8string_view/TestDataFormatterStdU8StringView.py @@ -11,18 +11,26 @@ from lldbsuite.test import lldbutil class StdU8StringViewDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def do_test(self): lldbutil.run_to_source_breakpoint( self, "Set break point at this line.", lldb.SBFileSpec("main.cpp") ) + string_view_name = ( + "std::basic_string_view<char8_t, std::char_traits<char8_t>>" + if self.getDebugInfo() == "pdb" + else "std::u8string_view" + ) + self.expect( "frame variable", substrs=[ - '(std::u8string_view) u8_string_small = u8"🍄"', - '(std::u8string_view) u8_string = u8"❤️👍📄📁😃🧑🌾"', - '(std::u8string_view) u8_empty = u8""', - '(std::u8string_view) u8_text = u8"ABCd"', + f'({string_view_name}) u8_string_small = u8"🍄"', + f'({string_view_name}) u8_string = u8"❤️👍📄📁😃🧑🌾"', + f'({string_view_name}) u8_empty = u8""', + f'({string_view_name}) u8_text = u8"ABCd"', ], ) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py index 0b68b1b..1516db6 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/unique_ptr/TestDataFormatterStdUniquePtr.py @@ -9,6 +9,8 @@ from lldbsuite.test import lldbutil class TestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def do_test(self): """Test `frame variable` output for `std::unique_ptr` types.""" @@ -84,7 +86,15 @@ class TestCase(TestBase): self.assertNotEqual(valobj.child[0].unsigned, 0) self.expect_var_path("up_user->id", type="int", value="30") - self.expect_var_path("up_user->name", type="std::string", summary='"steph"') + self.expect_var_path( + "up_user->name", + type=( + "std::basic_string<char, std::char_traits<char>, std::allocator<char>>" + if self.getDebugInfo() == "pdb" + else "std::string" + ), + summary='"steph"', + ) self.runCmd("settings set target.experimental.use-DIL true") self.expect_var_path("ptr_node->value", value="1") diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/vbool/TestDataFormatterStdVBool.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/vbool/TestDataFormatterStdVBool.py index dd142d2..f74092c 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/vbool/TestDataFormatterStdVBool.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/vbool/TestDataFormatterStdVBool.py @@ -9,6 +9,8 @@ from lldbsuite.test import lldbutil class StdVBoolDataFormatterTestCase(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + def setUp(self): # Call super's setUp(). TestBase.setUp(self) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/invalid-string/TestDataFormatterLibcxxInvalidString.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/invalid-string/TestDataFormatterLibcxxInvalidString.py index ae8e0ac..b504c01 100644 --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/invalid-string/TestDataFormatterLibcxxInvalidString.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/invalid-string/TestDataFormatterLibcxxInvalidString.py @@ -20,7 +20,7 @@ class LibcxxInvalidStringDataFormatterTestCase(TestBase): frame = thread.frames[0] if not self.process().GetAddressByteSize() == 8: - self.skip() + self.skipTest("The test requires a 64-bit process") # The test assumes that std::string is in its cap-size-data layout. self.expect( diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestConnectRemoteDetach.py b/lldb/test/API/functionalities/gdb_remote_client/TestConnectRemoteDetach.py new file mode 100644 index 0000000..4380455 --- /dev/null +++ b/lldb/test/API/functionalities/gdb_remote_client/TestConnectRemoteDetach.py @@ -0,0 +1,67 @@ +""" +Test that ConnectRemote sets ShouldDetach flag correctly. + +When connecting to a remote process that stops after connection, +the process should be marked for detach (not kill) on destruction. +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +from lldbsuite.test.gdbclientutils import * +from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase +from lldbsuite.test import lldbutil + + +class TestConnectRemoteDetach(GDBRemoteTestBase): + """Test that ConnectRemote properly sets ShouldDetach flag.""" + + class StoppedResponder(MockGDBServerResponder): + """A responder that returns a stopped process.""" + + def qfThreadInfo(self): + return "m1" + + def qsThreadInfo(self): + return "l" + + def qC(self): + return "QC1" + + def haltReason(self): + # Return that we're stopped + return "T05thread:1;" + + def cont(self): + # Stay stopped + return "T05thread:1;" + + def D(self): + # Detach packet: this is what we want to verify gets called. + return "OK" + + def k(self): + # Kill packet: this is what we want to verify doesn't get called. + raise RuntimeError("should not receive k(ill) packet") + + def test_connect_remote_sets_detach(self): + """Test that ConnectRemote to a stopped process sets ShouldDetach.""" + self.server.responder = self.StoppedResponder() + + target = self.createTarget("a.yaml") + process = self.connect(target) + + # Wait for the process to be in stopped state after connecting. + # When ConnectRemote connects to a remote process that is stopped, + # it should call SetShouldDetach(true) before CompleteAttach(). + lldbutil.expect_state_changes( + self, self.dbg.GetListener(), process, [lldb.eStateStopped] + ) + + # Now destroy the process. Because ShouldDetach was set to true + # during ConnectRemote, this should send a 'D' (detach) packet + # rather than a 'k' (kill) packet when the process is destroyed. + process.Destroy() + + # Verify that the (D)etach packet was sent. + self.assertPacketLogReceived(["D"]) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py new file mode 100644 index 0000000..922cb7f --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py @@ -0,0 +1,555 @@ +""" +Test scripted frame provider functionality. +""" + +import os + +import lldb +import lldbsuite.test.lldbplatformutil as lldbplatformutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase +from lldbsuite.test import lldbutil + +@skipIf(oslist=["linux"], archs=["arm$"]) +class ScriptedFrameProviderTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.source = "main.cpp" + + def test_replace_all_frames(self): + """Test that we can replace the entire stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Import the test frame provider. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Attach the Replace provider. + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ReplaceFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have exactly 3 synthetic frames. + self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames") + + # Verify frame indices and PCs (dictionary-based frames don't have custom function names). + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual(frame0.GetPC(), 0x1000) + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertIn("thread_func", frame1.GetFunctionName()) + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual(frame2.GetPC(), 0x3000) + + def test_prepend_frames(self): + """Test that we can add frames before real stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count and PC. + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + + # Import and attach Prepend provider. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.PrependFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 2 more frames. + new_frame_count = thread.GetNumFrames() + self.assertEqual(new_frame_count, original_frame_count + 2) + + # Verify first 2 frames are synthetic (check PCs, not function names). + frame0 = thread.GetFrameAtIndex(0) + self.assertEqual(frame0.GetPC(), 0x9000) + + frame1 = thread.GetFrameAtIndex(1) + self.assertEqual(frame1.GetPC(), 0xA000) + + # Verify frame 2 is the original real frame 0. + frame2 = thread.GetFrameAtIndex(2) + self.assertIn("thread_func", frame2.GetFunctionName()) + + def test_append_frames(self): + """Test that we can add frames after real stack.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count. + original_frame_count = thread.GetNumFrames() + + # Import and attach Append provider. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.AppendFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 1 more frame. + new_frame_count = thread.GetNumFrames() + self.assertEqual(new_frame_count, original_frame_count + 1) + + # Verify first frames are still real. + frame0 = thread.GetFrameAtIndex(0) + self.assertIn("thread_func", frame0.GetFunctionName()) + + frame_n_plus_1 = thread.GetFrameAtIndex(new_frame_count - 1) + self.assertEqual(frame_n_plus_1.GetPC(), 0x10) + + def test_scripted_frame_objects(self): + """Test that provider can return ScriptedFrame objects.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Import the provider that returns ScriptedFrame objects. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ScriptedFrameObjectProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 5 frames. + self.assertEqual( + thread.GetNumFrames(), 5, "Should have 5 custom scripted frames" + ) + + # Verify frame properties from CustomScriptedFrame. + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual(frame0.GetFunctionName(), "custom_scripted_frame_0") + self.assertEqual(frame0.GetPC(), 0x5000) + self.assertTrue(frame0.IsSynthetic(), "Frame should be marked as synthetic") + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual(frame1.GetPC(), 0x6000) + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual(frame2.GetFunctionName(), "custom_scripted_frame_2") + self.assertEqual(frame2.GetPC(), 0x7000) + self.assertTrue(frame2.IsSynthetic(), "Frame should be marked as synthetic") + + def test_applies_to_thread(self): + """Test that applies_to_thread filters which threads get the provider.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # We should have at least 2 threads (worker threads) at the breakpoint. + num_threads = process.GetNumThreads() + self.assertGreaterEqual( + num_threads, 2, "Should have at least 2 threads at breakpoint" + ) + + # Import the test frame provider. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Collect original thread info before applying provider. + thread_info = {} + for i in range(num_threads): + t = process.GetThreadAtIndex(i) + thread_info[t.GetIndexID()] = { + "frame_count": t.GetNumFrames(), + "pc": t.GetFrameAtIndex(0).GetPC(), + } + + # Register the ThreadFilterFrameProvider which only applies to thread ID 1. + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.ThreadFilterFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Check each thread. + thread_id_1_found = False + # On ARM32, FixCodeAddress clears bit 0, so synthetic PCs get modified. + is_arm_32bit = lldbplatformutil.getArchitecture() == "arm" + expected_synthetic_pc = 0xFFFE if is_arm_32bit else 0xFFFF + + for i in range(num_threads): + t = process.GetThreadAtIndex(i) + thread_id = t.GetIndexID() + + if thread_id == 1: + # Thread with ID 1 should have synthetic frame. + thread_id_1_found = True + self.assertEqual( + t.GetNumFrames(), + 1, + f"Thread with ID 1 should have 1 synthetic frame", + ) + self.assertEqual( + t.GetFrameAtIndex(0).GetPC(), + expected_synthetic_pc, + f"Thread with ID 1 should have synthetic PC {expected_synthetic_pc:#x}", + ) + else: + # Other threads should keep their original frames. + self.assertEqual( + t.GetNumFrames(), + thread_info[thread_id]["frame_count"], + f"Thread with ID {thread_id} should not be affected by provider", + ) + self.assertEqual( + t.GetFrameAtIndex(0).GetPC(), + thread_info[thread_id]["pc"], + f"Thread with ID {thread_id} should have its original PC", + ) + + # We should have found at least one thread with ID 1. + self.assertTrue( + thread_id_1_found, + "Should have found a thread with ID 1 to test filtering", + ) + + def test_remove_frame_provider_by_id(self): + """Test that RemoveScriptedFrameProvider removes a specific provider by ID.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Import the test frame providers. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Get original frame count. + original_frame_count = thread.GetNumFrames() + original_pc = thread.GetFrameAtIndex(0).GetPC() + + # Register the first provider and get its ID. + error = lldb.SBError() + provider_id_1 = target.RegisterScriptedFrameProvider( + "test_frame_providers.ReplaceFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider 1: {error}") + + # Verify first provider is active (3 synthetic frames). + self.assertEqual(thread.GetNumFrames(), 3, "Should have 3 synthetic frames") + self.assertEqual( + thread.GetFrameAtIndex(0).GetPC(), 0x1000, "Should have first provider's PC" + ) + + # Register a second provider and get its ID. + provider_id_2 = target.RegisterScriptedFrameProvider( + "test_frame_providers.PrependFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider 2: {error}") + + # Verify IDs are different + self.assertNotEqual( + provider_id_1, provider_id_2, "Provider IDs should be unique" + ) + + # Now remove the first provider by ID + result = target.RemoveScriptedFrameProvider(provider_id_1) + self.assertSuccess( + result, f"Should successfully remove provider with ID {provider_id_1}" + ) + + # After removing the first provider, the second provider should still be + # active. The PrependFrameProvider adds 2 frames before the real stack. + # Since ReplaceFrameProvider had 3 frames, and we removed it, we should now + # have the original frames (from real stack) with PrependFrameProvider applied. + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 2, + "Should have original frames + 2 prepended frames", + ) + + # First two frames should be from PrependFrameProvider. + self.assertEqual( + thread.GetFrameAtIndex(0).GetPC(), + 0x9000, + "First frame should be from PrependFrameProvider", + ) + self.assertEqual( + thread.GetFrameAtIndex(1).GetPC(), + 0xA000, + "Second frame should be from PrependFrameProvider", + ) + + # Remove the second provider. + result = target.RemoveScriptedFrameProvider(provider_id_2) + self.assertSuccess( + result, f"Should successfully remove provider with ID {provider_id_2}" + ) + + # After removing both providers, frames should be back to original. + self.assertEqual( + thread.GetNumFrames(), + original_frame_count, + "Should restore original frame count", + ) + self.assertEqual( + thread.GetFrameAtIndex(0).GetPC(), + original_pc, + "Should restore original PC", + ) + + # Try to remove a provider that doesn't exist. + result = target.RemoveScriptedFrameProvider(999999) + self.assertTrue(result.Fail(), "Should fail to remove non-existent provider") + + def test_circular_dependency_fix(self): + """Test that accessing input_frames in __init__ doesn't cause circular dependency. + + This test verifies the fix for the circular dependency issue where: + 1. Thread::GetStackFrameList() creates the frame provider + 2. Provider's __init__ accesses input_frames and calls methods on frames + 3. SBFrame methods trigger ExecutionContextRef::GetFrameSP() + 4. Before the fix: GetFrameSP() would call Thread::GetStackFrameList() again -> circular dependency! + 5. After the fix: GetFrameSP() uses the remembered frame list -> no circular dependency + + The fix works by: + - StackFrame stores m_frame_list_wp (weak pointer to originating list) + - ExecutionContextRef stores m_frame_list_wp when created from a frame + - ExecutionContextRef::GetFrameSP() tries the remembered list first before asking the thread + """ + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count and PC. + original_frame_count = thread.GetNumFrames() + original_pc = thread.GetFrameAtIndex(0).GetPC() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + + # Import the provider that accesses input frames in __init__. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Register the CircularDependencyTestProvider. + # Before the fix, this would crash or hang due to circular dependency. + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.CircularDependencyTestProvider", + lldb.SBStructuredData(), + error, + ) + + # If we get here without crashing, the fix is working! + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify the provider worked correctly, + # Should have 1 synthetic frame + all original frames. + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 1, + "Should have original frames + 1 synthetic frame", + ) + + # On ARM32, FixCodeAddress clears bit 0, so synthetic PCs get modified. + is_arm_32bit = lldbplatformutil.getArchitecture() == "arm" + expected_synthetic_pc = 0xDEADBEEE if is_arm_32bit else 0xDEADBEEF + + # First frame should be synthetic. + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetPC(), + expected_synthetic_pc, + f"First frame should be synthetic frame with PC {expected_synthetic_pc:#x}", + ) + + # Second frame should be the original first frame. + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetPC(), + original_pc, + "Second frame should be original first frame", + ) + + # Verify we can still call methods on frames (no circular dependency!). + for i in range(min(3, new_frame_count)): + frame = thread.GetFrameAtIndex(i) + self.assertIsNotNone(frame) + # These calls should not trigger circular dependency. + pc = frame.GetPC() + self.assertNotEqual(pc, 0, f"Frame {i} should have valid PC") + + def test_python_source_frames(self): + """Test that frames can point to Python source files and display properly.""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Break here", lldb.SBFileSpec(self.source), only_one_thread=False + ) + + # Get original frame count. + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 2, "Should have at least 2 real frames" + ) + + # Import the provider. + script_path = os.path.join(self.getSourceDir(), "test_frame_providers.py") + self.runCmd("command script import " + script_path) + + # Register the PythonSourceFrameProvider. + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "test_frame_providers.PythonSourceFrameProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Failed to register provider: {error}") + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify we have 3 more frames (Python frames). + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count + 3, + "Should have original frames + 3 Python frames", + ) + + # Verify first three frames are Python source frames. + frame0 = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0) + self.assertEqual( + frame0.GetFunctionName(), + "compute_fibonacci", + "First frame should be compute_fibonacci", + ) + self.assertTrue(frame0.IsSynthetic(), "Frame should be marked as synthetic") + # PC-less frames should show invalid address and not crash. + self.assertEqual( + frame0.GetPC(), + lldb.LLDB_INVALID_ADDRESS, + "PC-less frame should have LLDB_INVALID_ADDRESS", + ) + + self.assertEqual( + frame0.GetFP(), + lldb.LLDB_INVALID_ADDRESS, + "PC-less frame FP should return LLDB_INVALID_ADDRESS", + ) + self.assertEqual( + frame0.GetSP(), + lldb.LLDB_INVALID_ADDRESS, + "PC-less frame SP should return LLDB_INVALID_ADDRESS", + ) + self.assertEqual( + frame0.GetCFA(), + 0, + "PC-less frame CFA should return 0", + ) + + frame1 = thread.GetFrameAtIndex(1) + self.assertIsNotNone(frame1) + self.assertEqual( + frame1.GetFunctionName(), + "process_data", + "Second frame should be process_data", + ) + self.assertTrue(frame1.IsSynthetic(), "Frame should be marked as synthetic") + + frame2 = thread.GetFrameAtIndex(2) + self.assertIsNotNone(frame2) + self.assertEqual(frame2.GetFunctionName(), "main", "Third frame should be main") + self.assertTrue(frame2.IsSynthetic(), "Frame should be marked as synthetic") + + # Verify line entry information is present. + line_entry0 = frame0.GetLineEntry() + self.assertTrue(line_entry0.IsValid(), "Frame 0 should have a valid line entry") + self.assertEqual(line_entry0.GetLine(), 7, "Frame 0 should point to line 7") + file_spec0 = line_entry0.GetFileSpec() + self.assertTrue(file_spec0.IsValid(), "Frame 0 should have valid file spec") + self.assertEqual( + file_spec0.GetFilename(), + "python_helper.py", + "Frame 0 should point to python_helper.py", + ) + + line_entry1 = frame1.GetLineEntry() + self.assertTrue(line_entry1.IsValid(), "Frame 1 should have a valid line entry") + self.assertEqual(line_entry1.GetLine(), 16, "Frame 1 should point to line 16") + + line_entry2 = frame2.GetLineEntry() + self.assertTrue(line_entry2.IsValid(), "Frame 2 should have a valid line entry") + self.assertEqual(line_entry2.GetLine(), 27, "Frame 2 should point to line 27") + + # Verify the frames display properly in backtrace. + # This tests that PC-less frames don't show 0xffffffffffffffff. + self.runCmd("bt") + output = self.res.GetOutput() + + # Should show function names. + self.assertIn("compute_fibonacci", output) + self.assertIn("process_data", output) + self.assertIn("main", output) + + # Should show Python file. + self.assertIn("python_helper.py", output) + + # Should show line numbers. + self.assertIn(":7", output) # compute_fibonacci line. + self.assertIn(":16", output) # process_data line. + self.assertIn(":27", output) # main line. + + # Should NOT show invalid address (0xffffffffffffffff). + self.assertNotIn("0xffffffffffffffff", output.lower()) + + # Verify frame 3 is the original real frame 0. + frame3 = thread.GetFrameAtIndex(3) + self.assertIsNotNone(frame3) + self.assertIn("thread_func", frame3.GetFunctionName()) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile new file mode 100644 index 0000000..1049594 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py new file mode 100644 index 0000000..b15bfb2 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/TestFrameProviderCircularDependency.py @@ -0,0 +1,119 @@ +""" +Test that frame providers wouldn't cause a hang due to a circular dependency +during its initialization. +""" + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase +from lldbsuite.test import lldbutil + +@skipIf(oslist=["linux"], archs=["arm$"]) +class FrameProviderCircularDependencyTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.source = "main.c" + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_circular_dependency_with_function_replacement(self): + """ + Test the circular dependency fix with a provider that replaces function names. + """ + self.build() + + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + self.assertTrue(target, "Target should be valid") + + bkpt = target.BreakpointCreateBySourceRegex( + "break here", lldb.SBFileSpec(self.source) + ) + self.assertTrue(bkpt.IsValid(), "Breakpoint should be valid") + self.assertEqual(bkpt.GetNumLocations(), 1, "Should have 1 breakpoint location") + + process = target.LaunchSimple(None, None, self.get_process_working_directory()) + self.assertTrue(process, "Process should be valid") + self.assertEqual( + process.GetState(), lldb.eStateStopped, "Process should be stopped" + ) + + thread = process.GetSelectedThread() + self.assertTrue(thread.IsValid(), "Thread should be valid") + + frame0 = thread.GetFrameAtIndex(0) + self.assertIn("bar", frame0.GetFunctionName(), "Should be stopped in bar()") + + original_frame_count = thread.GetNumFrames() + self.assertGreaterEqual( + original_frame_count, 3, "Should have at least 3 frames: bar, foo, main" + ) + + frame_names = [thread.GetFrameAtIndex(i).GetFunctionName() for i in range(3)] + self.assertEqual(frame_names[0], "bar", "Frame 0 should be bar") + self.assertEqual(frame_names[1], "foo", "Frame 1 should be foo") + self.assertEqual(frame_names[2], "main", "Frame 2 should be main") + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + # Register the frame provider that accesses input_frames. + # Before the fix, this registration would trigger the circular dependency: + # - Thread::GetStackFrameList() creates provider + # - Provider's get_frame_at_index() accesses input_frames[0] + # - Calls frame.GetFunctionName() -> ExecutionContextRef::GetFrameSP() + # - Before fix: Calls Thread::GetStackFrameList() again -> CIRCULAR! + # - After fix: Uses remembered m_frame_list_wp -> Works! + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.ScriptedFrameObjectProvider", + lldb.SBStructuredData(), + error, + ) + + # If we reach here without crashing/hanging, the fix is working! + self.assertTrue( + error.Success(), + f"Should successfully register provider (if this fails, circular dependency!): {error}", + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Verify the provider is working correctly. + # Frame count should be unchanged (we're replacing frames, not adding). + new_frame_count = thread.GetNumFrames() + self.assertEqual( + new_frame_count, + original_frame_count, + "Frame count should be unchanged (replacement, not addition)", + ) + + # Verify that "bar" was replaced with "baz". + frame0_new = thread.GetFrameAtIndex(0) + self.assertIsNotNone(frame0_new, "Frame 0 should exist") + self.assertEqual( + frame0_new.GetFunctionName(), + "baz", + "Frame 0 function should be replaced: bar -> baz", + ) + + # Verify other frames are unchanged. + frame1_new = thread.GetFrameAtIndex(1) + self.assertEqual( + frame1_new.GetFunctionName(), "foo", "Frame 1 should still be foo" + ) + + frame2_new = thread.GetFrameAtIndex(2) + self.assertEqual( + frame2_new.GetFunctionName(), "main", "Frame 2 should still be main" + ) + + # Verify we can call methods on all frames (no circular dependency!). + for i in range(new_frame_count): + frame = thread.GetFrameAtIndex(i) + self.assertIsNotNone(frame, f"Frame {i} should exist") + # These calls should not trigger circular dependency. + pc = frame.GetPC() + self.assertNotEqual(pc, 0, f"Frame {i} should have valid PC") + func_name = frame.GetFunctionName() + self.assertIsNotNone(func_name, f"Frame {i} should have function name") diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py new file mode 100644 index 0000000..f27f18c --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/frame_provider.py @@ -0,0 +1,102 @@ +""" +Frame provider that reproduces the circular dependency issue. + +This provider accesses input_frames and calls methods on them, +which before the fix would cause a circular dependency. +""" + +import lldb +from lldb.plugins.scripted_process import ScriptedFrame +from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider + + +class CustomScriptedFrame(ScriptedFrame): + """Custom scripted frame with full control over frame behavior.""" + + def __init__(self, thread, idx, pc, function_name): + args = lldb.SBStructuredData() + super().__init__(thread, args) + + self.idx = idx + self.pc = pc + self.function_name = function_name + + def get_id(self): + """Return the frame index.""" + return self.idx + + def get_pc(self): + """Return the program counter.""" + return self.pc + + def get_function_name(self): + """Return the function name.""" + return self.function_name + + def is_artificial(self): + """Mark as artificial frame.""" + return False + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + return None + + +class ScriptedFrameObjectProvider(ScriptedFrameProvider): + """ + Provider that returns ScriptedFrame objects and accesses input_frames. + + This provider demonstrates the circular dependency bug fix: + 1. During get_frame_at_index(), we access input_frames[idx] + 2. We call frame.GetFunctionName() and frame.GetPC() on input frames + 3. Before the fix: These calls would trigger ExecutionContextRef::GetFrameSP() + which would call Thread::GetStackFrameList() -> circular dependency! + 4. After the fix: ExecutionContextRef uses the remembered frame list -> no circular dependency + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + self.replacement_count = 0 + if self.target.process: + baz_symbol_ctx = self.target.FindFunctions("baz") + self.baz_symbol_ctx = None + if len(baz_symbol_ctx) == 1: + self.baz_symbol_ctx = baz_symbol_ctx[0] + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that replaces 'bar' function with 'baz'" + + def get_frame_at_index(self, idx): + """ + Replace frames named 'bar' with custom frames named 'baz'. + + This accesses input_frames and calls methods on them, which would + trigger the circular dependency bug before the fix. + """ + if idx < len(self.input_frames): + # This access and method calls would cause circular dependency before fix! + frame = self.input_frames[idx] + + # Calling GetFunctionName() triggers ExecutionContextRef resolution. + function_name = frame.GetFunctionName() + + if function_name == "bar" and self.baz_symbol_ctx: + # Replace "bar" with "baz". + baz_func = self.baz_symbol_ctx.GetFunction() + new_function_name = baz_func.GetName() + pc = baz_func.GetStartAddress().GetLoadAddress(self.target) + custom_frame = CustomScriptedFrame( + self.thread, idx, pc, new_function_name + ) + self.replacement_count += 1 + return custom_frame + + # Pass through other frames by returning their index. + return idx + + return None diff --git a/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c new file mode 100644 index 0000000..bbd1028 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/circular_dependency/main.c @@ -0,0 +1,21 @@ +#include <stdio.h> + +int baz() { + printf("baz\n"); + return 666; +} + +int bar() { + printf("bar\n"); + return 42; // break here. +} + +int foo() { + printf("foo\n"); + return bar(); +} + +int main() { + printf("main\n"); + return foo(); +} diff --git a/lldb/test/API/functionalities/scripted_frame_provider/main.cpp b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp new file mode 100644 index 0000000..0298e88 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/main.cpp @@ -0,0 +1,53 @@ +// Multi-threaded test program for testing frame providers. + +#include <condition_variable> +#include <iostream> +#include <mutex> +#include <thread> + +std::mutex mtx; +std::condition_variable cv; +int ready_count = 0; +constexpr int NUM_THREADS = 2; + +void thread_func(int thread_num) { + std::cout << "Thread " << thread_num << " started\n"; + + { + std::unique_lock<std::mutex> lock(mtx); + ready_count++; + if (ready_count == NUM_THREADS + 1) { + cv.notify_all(); + } else { + cv.wait(lock, [] { return ready_count == NUM_THREADS + 1; }); + } + } + + std::cout << "Thread " << thread_num << " at breakpoint\n"; // Break here. +} + +int main(int argc, char **argv) { + std::thread threads[NUM_THREADS]; + + for (int i = 0; i < NUM_THREADS; i++) { + threads[i] = std::thread(thread_func, i); + } + + { + std::unique_lock<std::mutex> lock(mtx); + ready_count++; + if (ready_count == NUM_THREADS + 1) { + cv.notify_all(); + } else { + cv.wait(lock, [] { return ready_count == NUM_THREADS + 1; }); + } + } + + std::cout << "Main thread at barrier\n"; + + for (int i = 0; i < NUM_THREADS; i++) + threads[i].join(); + + std::cout << "All threads completed\n"; + return 0; +} diff --git a/lldb/test/API/functionalities/scripted_frame_provider/python_helper.py b/lldb/test/API/functionalities/scripted_frame_provider/python_helper.py new file mode 100644 index 0000000..27f3816 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/python_helper.py @@ -0,0 +1,36 @@ +""" +Sample Python module to demonstrate Python source display in scripted frames. +""" + + +def compute_fibonacci(n): + """Compute the nth Fibonacci number.""" + if n <= 1: + return n + a, b = 0, 1 + for _ in range(n - 1): + a, b = b, a + b + return b + + +def process_data(data): + """Process some data and return result.""" + result = [] + for item in data: + if isinstance(item, int): + result.append(item * 2) + elif isinstance(item, str): + result.append(item.upper()) + return result + + +def main(): + """Main entry point for testing.""" + fib_10 = compute_fibonacci(10) + data = [1, 2, "hello", 3, "world"] + processed = process_data(data) + return fib_10, processed + + +if __name__ == "__main__": + main() diff --git a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py new file mode 100644 index 0000000..76f8597 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py @@ -0,0 +1,316 @@ +""" +Test frame providers for scripted frame provider functionality. + +These providers demonstrate various merge strategies: +- Replace: Replace entire stack +- Prepend: Add frames before real stack +- Append: Add frames after real stack + +It also shows the ability to mix a dictionary, a ScriptedFrame or an SBFrame +index to create stackframes +""" + +import os +import lldb +from lldb.plugins.scripted_process import ScriptedFrame +from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider + + +class ReplaceFrameProvider(ScriptedFrameProvider): + """Replace entire stack with custom frames.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + self.frames = [ + { + "idx": 0, + "pc": 0x1000, + }, + 0, + { + "idx": 2, + "pc": 0x3000, + }, + ] + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Replace entire stack with 3 custom frames" + + def get_frame_at_index(self, index): + if index >= len(self.frames): + return None + return self.frames[index] + + +class PrependFrameProvider(ScriptedFrameProvider): + """Prepend synthetic frames before real stack.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Prepend 2 synthetic frames before real stack" + + def get_frame_at_index(self, index): + if index == 0: + return {"pc": 0x9000} + elif index == 1: + return {"pc": 0xA000} + elif index - 2 < len(self.input_frames): + return index - 2 # Return real frame index. + return None + + +class AppendFrameProvider(ScriptedFrameProvider): + """Append synthetic frames after real stack.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Append 1 synthetic frame after real stack" + + def get_frame_at_index(self, index): + if index < len(self.input_frames): + return index # Return real frame index. + elif index == len(self.input_frames): + return { + "idx": 1, + "pc": 0x10, + } + return None + + +class CustomScriptedFrame(ScriptedFrame): + """Custom scripted frame with full control over frame behavior.""" + + def __init__(self, thread, idx, pc, function_name): + args = lldb.SBStructuredData() + super().__init__(thread, args) + + self.idx = idx + self.pc = pc + self.function_name = function_name + + def get_id(self): + """Return the frame index.""" + return self.idx + + def get_pc(self): + """Return the program counter.""" + return self.pc + + def get_function_name(self): + """Return the function name.""" + return self.function_name + + def is_artificial(self): + """Mark as artificial frame.""" + return False + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + """No register context for this test.""" + return None + + +class ScriptedFrameObjectProvider(ScriptedFrameProvider): + """Provider that returns ScriptedFrame objects instead of dictionaries.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider returning custom ScriptedFrame objects" + + def get_frame_at_index(self, index): + """Return ScriptedFrame objects or dictionaries based on index.""" + if index == 0: + return CustomScriptedFrame( + self.thread, 0, 0x5000, "custom_scripted_frame_0" + ) + elif index == 1: + return {"pc": 0x6000} + elif index == 2: + return CustomScriptedFrame( + self.thread, 2, 0x7000, "custom_scripted_frame_2" + ) + elif index == 3: + return len(self.input_frames) - 2 # Real frame index. + elif index == 4: + return len(self.input_frames) - 1 # Real frame index. + return None + + +class ThreadFilterFrameProvider(ScriptedFrameProvider): + """Provider that only applies to thread with ID 1.""" + + @staticmethod + def applies_to_thread(thread): + """Only apply to thread with index ID 1.""" + return thread.GetIndexID() == 1 + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that only applies to thread ID 1" + + def get_frame_at_index(self, index): + """Return a single synthetic frame.""" + if index == 0: + return {"pc": 0xFFFF} + return None + + +class CircularDependencyTestProvider(ScriptedFrameProvider): + """ + Provider that tests the circular dependency fix. + + This provider accesses input_frames during __init__ and calls methods + on those frames. Before the fix, this would cause a circular dependency: + - Thread::GetStackFrameList() creates provider + - Provider's __init__ accesses input_frames[0] + - SBFrame::GetPC() tries to resolve ExecutionContextRef + - ExecutionContextRef::GetFrameSP() calls Thread::GetStackFrameList() + - Re-enters initialization -> circular dependency! + + With the fix, ExecutionContextRef remembers the frame list, so it doesn't + re-enter Thread::GetStackFrameList(). + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + # This would cause circular dependency before the fix! + # Accessing frames and calling methods on them during __init__ + self.original_frame_count = len(input_frames) + self.original_pcs = [] + + # Call GetPC() on each input frame - this triggers ExecutionContextRef resolution. + for i in range(min(3, len(input_frames))): + frame = input_frames[i] + if frame.IsValid(): + pc = frame.GetPC() + self.original_pcs.append(pc) + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that tests circular dependency fix by accessing frames in __init__" + + def get_frame_at_index(self, index): + """Prepend a synthetic frame, then pass through original frames.""" + if index == 0: + # Synthetic frame at index 0. + return {"pc": 0xDEADBEEF} + elif index - 1 < self.original_frame_count: + # Pass through original frames at indices 1, 2, 3, ... + return index - 1 + return None + + +class PythonSourceFrame(ScriptedFrame): + """Scripted frame that points to Python source code.""" + + def __init__(self, thread, idx, function_name, python_file, line_number): + args = lldb.SBStructuredData() + super().__init__(thread, args) + + self.idx = idx + self.function_name = function_name + self.python_file = python_file + self.line_number = line_number + + def get_id(self): + """Return the frame index.""" + return self.idx + + def get_pc(self): + """PC-less frame - return invalid address.""" + return lldb.LLDB_INVALID_ADDRESS + + def get_function_name(self): + """Return the function name.""" + return self.function_name + + def get_symbol_context(self): + """Return a symbol context with LineEntry pointing to Python source.""" + # Create a LineEntry pointing to the Python source file + line_entry = lldb.SBLineEntry() + line_entry.SetFileSpec(lldb.SBFileSpec(self.python_file, True)) + line_entry.SetLine(self.line_number) + line_entry.SetColumn(0) + + # Create a symbol context with the line entry + sym_ctx = lldb.SBSymbolContext() + sym_ctx.SetLineEntry(line_entry) + + return sym_ctx + + def is_artificial(self): + """Not artificial.""" + return False + + def is_hidden(self): + """Not hidden.""" + return False + + def get_register_context(self): + """No register context for PC-less frames.""" + return None + + +class PythonSourceFrameProvider(ScriptedFrameProvider): + """ + Provider that demonstrates Python source display in scripted frames. + + This provider prepends frames pointing to Python source code, showing + that PC-less frames can display Python source files with proper line + numbers and module/compile unit information. + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + # Find the python_helper.py file + current_dir = os.path.dirname(os.path.abspath(__file__)) + self.python_file = os.path.join(current_dir, "python_helper.py") + + @staticmethod + def get_description(): + """Return a description of this provider.""" + return "Provider that prepends frames pointing to Python source" + + def get_frame_at_index(self, index): + """Return Python source frames followed by original frames.""" + if index == 0: + # Frame pointing to compute_fibonacci function (line 7) + return PythonSourceFrame( + self.thread, 0, "compute_fibonacci", self.python_file, 7 + ) + elif index == 1: + # Frame pointing to process_data function (line 16) + return PythonSourceFrame( + self.thread, 1, "process_data", self.python_file, 16 + ) + elif index == 2: + # Frame pointing to main function (line 27) + return PythonSourceFrame(self.thread, 2, "main", self.python_file, 27) + elif index - 3 < len(self.input_frames): + # Pass through original frames + return index - 3 + return None diff --git a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py index 8352672..f676cfb 100644 --- a/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py +++ b/lldb/test/API/functionalities/scripted_process/dummy_scripted_process.py @@ -8,6 +8,15 @@ from lldb.plugins.scripted_process import ScriptedThread from lldb.plugins.scripted_process import ScriptedFrame +def my_python_function(x, y): + """A sample Python function to demonstrate Python source display in scripted frames.""" + result = x + y + if result > 100: + return result * 2 + else: + return result + + class DummyStopHook: def __init__(self, target, args): self.target = target @@ -74,6 +83,40 @@ class DummyScriptedThread(ScriptedThread): self.frames.append(DummyScriptedFrame(self, args, len(self.frames), "bar")) self.frames.append(DummyScriptedFrame(self, args, len(self.frames), "foo")) + cwd = os.path.dirname(os.path.abspath(__file__)) + + le = lldb.SBLineEntry() + le.SetFileSpec(lldb.SBFileSpec(os.path.join(cwd, "baz.cpp"), True)) + le.SetLine(9) + le.SetColumn(10) + + sym_ctx = lldb.SBSymbolContext() + sym_ctx.SetLineEntry(le) + + self.frames.append( + DummyScriptedFrame(self, args, len(self.frames), "baz", sym_ctx) + ) + + # Add a frame with Python source + code = my_python_function.__code__ + lineno = code.co_firstlineno + col_offset = getattr( + code, "co_firstcol_offset", 0 + ) # Python ≥3.11 has column info + py_le = lldb.SBLineEntry() + py_le.SetFileSpec(lldb.SBFileSpec(__file__, True)) + py_le.SetLine(lineno) # Line where my_python_function is defined + py_le.SetColumn(col_offset) + + py_sym_ctx = lldb.SBSymbolContext() + py_sym_ctx.SetLineEntry(py_le) + + self.frames.append( + DummyScriptedFrame( + self, args, len(self.frames), "my_python_function", py_sym_ctx + ) + ) + def get_thread_id(self) -> int: return 0x19 diff --git a/lldb/test/API/functionalities/statusline/TestStatusline.py b/lldb/test/API/functionalities/statusline/TestStatusline.py index ca376cc..4ffa864 100644 --- a/lldb/test/API/functionalities/statusline/TestStatusline.py +++ b/lldb/test/API/functionalities/statusline/TestStatusline.py @@ -71,8 +71,10 @@ class TestStatusline(PExpectTest): ) self.expect('set set separator "| "') - # Hide the statusline and check or the control character. - self.expect("set set show-statusline false", ["\x1b[1;0r"]) + # Hide the statusline and check for the control character. + self.expect( + "set set show-statusline false", ["\x1b[1;{}r".format(self.TERMINAL_HEIGHT)] + ) def test_no_color(self): """Basic test for the statusline with colors disabled.""" diff --git a/lldb/test/API/functionalities/thread/step_until/function.list b/lldb/test/API/functionalities/thread/step_until/function.list index 5900fe8..d8caa20 100644 --- a/lldb/test/API/functionalities/thread/step_until/function.list +++ b/lldb/test/API/functionalities/thread/step_until/function.list @@ -1 +1,4 @@ -!call_me +v1 +f call_me +c 0 +c 1 diff --git a/lldb/test/API/functionalities/unwind/libunwind_ret_injection/Makefile b/lldb/test/API/functionalities/unwind/libunwind_ret_injection/Makefile new file mode 100644 index 0000000..4698eaa --- /dev/null +++ b/lldb/test/API/functionalities/unwind/libunwind_ret_injection/Makefile @@ -0,0 +1,6 @@ +CXX_SOURCES := main.cpp + +# Build with C++ exceptions enabled +CXXFLAGS := -g -O0 -fexceptions + +include Makefile.rules diff --git a/lldb/test/API/functionalities/unwind/libunwind_ret_injection/TestLibUnwindRetInjection.py b/lldb/test/API/functionalities/unwind/libunwind_ret_injection/TestLibUnwindRetInjection.py new file mode 100644 index 0000000..e03234d --- /dev/null +++ b/lldb/test/API/functionalities/unwind/libunwind_ret_injection/TestLibUnwindRetInjection.py @@ -0,0 +1,177 @@ +""" +Test that libunwind correctly injects 'ret' instructions to rebalance execution flow +when unwinding C++ exceptions. This is important for Apple Processor Trace analysis. +""" + +import lldb +import os +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test import configuration + + +class LibunwindRetInjectionTestCase(TestBase): + @skipIf(archs=no_match(["arm64", "arm64e", "aarch64"])) + @skipUnlessDarwin + @skipIfOutOfTreeLibunwind + def test_ret_injection_on_exception_unwind(self): + """Test that __libunwind_Registers_arm64_jumpto receives correct walkedFrames count and injects the right number of ret instructions.""" + self.build() + + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + + # Find the just-built libunwind, not the system one. + # llvm_tools_dir is typically <build>/bin, so lib is a sibling. + self.assertIsNotNone( + configuration.llvm_tools_dir, + "llvm_tools_dir must be set to find in-tree libunwind", + ) + + llvm_lib_dir = os.path.join( + os.path.dirname(configuration.llvm_tools_dir), "lib" + ) + + # Find the libunwind library (platform-agnostic). + libunwind_path = None + for filename in os.listdir(llvm_lib_dir): + if filename.startswith("libunwind.") or filename.startswith("unwind."): + libunwind_path = os.path.join(llvm_lib_dir, filename) + break + + self.assertIsNotNone( + libunwind_path, f"Could not find libunwind in {llvm_lib_dir}" + ) + + # Set breakpoint in __libunwind_Registers_arm64_jumpto. + # This is the function that performs the actual jump and ret injection. + bp = target.BreakpointCreateByName("__libunwind_Registers_arm64_jumpto") + self.assertTrue(bp.IsValid()) + self.assertGreater(bp.GetNumLocations(), 0) + + # Set up DYLD_INSERT_LIBRARIES to use the just-built libunwind. + launch_info = lldb.SBLaunchInfo(None) + env = target.GetEnvironment() + env.Set("DYLD_INSERT_LIBRARIES", libunwind_path, True) + launch_info.SetEnvironment(env, False) + + # Launch the process with our custom libunwind. + error = lldb.SBError() + process = target.Launch(launch_info, error) + self.assertSuccess( + error, f"Failed to launch process with libunwind at {libunwind_path}" + ) + self.assertTrue(process, PROCESS_IS_VALID) + + # We should hit the breakpoint in __libunwind_Registers_arm64_jumpto + # during the exception unwinding phase 2. + threads = lldbutil.get_threads_stopped_at_breakpoint(process, bp) + self.assertEqual(len(threads), 1, "Should have stopped at breakpoint") + + thread = threads[0] + frame = thread.GetFrameAtIndex(0) + + # Verify we're in __libunwind_Registers_arm64_jumpto. + function_name = frame.GetFunctionName() + self.assertTrue( + "__libunwind_Registers_arm64_jumpto" in function_name, + f"Expected to be in __libunwind_Registers_arm64_jumpto, got {function_name}", + ) + + # On ARM64, the walkedFrames parameter should be in register x1 (second parameter). + # According to the ARM64 calling convention, integer arguments are passed in x0-x7. + # x0 = Registers_arm64* pointer. + # x1 = unsigned walkedFrames. + error = lldb.SBError() + x1_value = frame.register["x1"].GetValueAsUnsigned(error) + self.assertSuccess(error, "Failed to read x1 register") + + # According to the code in UnwindCursor.hpp, the walkedFrames value represents: + # 1. The number of frames walked in unwind_phase2 to reach the landing pad. + # 2. Plus _EXTRA_LIBUNWIND_FRAMES_WALKED = 5 - 1 = 4 additional libunwind frames. + # + # From the comment in the code: + # frame #0: __libunwind_Registers_arm64_jumpto + # frame #1: Registers_arm64::returnto + # frame #2: UnwindCursor::jumpto + # frame #3: __unw_resume + # frame #4: __unw_resume_with_frames_walked + # frame #5: unwind_phase2 + # + # Since __libunwind_Registers_arm64_jumpto returns to the landing pad, + # we subtract 1, so _EXTRA_LIBUNWIND_FRAMES_WALKED = 4. + # + # For our test program: + # - unwind_phase2 starts walking (frame 0 counted here). + # - Walks through: func_d (throw site), func_c, func_b, func_a. + # - Finds landing pad in main. + # That's approximately 4-5 frames from the user code. + # Plus the 4 extra libunwind frames. + # + # So we expect x1 to be roughly 8-10. + expected_min_frames = 8 + expected_max_frames = 13 # Allow some variation for libc++abi frames. + + self.assertGreaterEqual( + x1_value, + expected_min_frames, + f"walkedFrames (x1) should be >= {expected_min_frames}, got {x1_value}. " + "This is the number of 'ret' instructions that will be executed.", + ) + + self.assertLessEqual( + x1_value, + expected_max_frames, + f"walkedFrames (x1) should be <= {expected_max_frames}, got {x1_value}. " + "Value seems too high.", + ) + + # Now step through the ret injection loop and count the actual number of 'ret' executions. + # The loop injects exactly x1_value ret instructions before continuing with register restoration. + # We step until we hit the first 'ldp' instruction (register restoration starts with 'ldp x2, x3, [x0, #0x010]'). + ret_executed_count = 0 + max_steps = 100 # Safety limit to prevent infinite loops. + + for step_count in range(max_steps): + # Get current instruction. + pc = frame.GetPC() + inst = process.ReadMemory(pc, 4, lldb.SBError()) + + # Disassemble current instruction. + current_inst = target.GetInstructions(lldb.SBAddress(pc, target), inst)[0] + mnemonic = current_inst.GetMnemonic(target) + operands = current_inst.GetOperands(target) + + # Check if we've reached the register restoration part (first ldp after the loop). + if mnemonic == "ldp": + # We've exited the ret injection loop. + break + + # Count 'ret' instructions that get executed. + if mnemonic == "ret": + self.assertEqual(operands, "x16") + ret_executed_count += 1 + + # Step one instruction. + thread.StepInstruction(False) # False = step over. + + # Update frame reference. + frame = thread.GetFrameAtIndex(0) + + # Verify we didn't hit the safety limit. + self.assertLess( + step_count, + max_steps - 1, + f"Stepped {max_steps} times without reaching 'ldp' instruction. Something is wrong.", + ) + + # The number of executed 'ret' instructions should match x1_value. + # According to the implementation, the loop executes exactly x1_value times. + self.assertEqual( + ret_executed_count, + x1_value, + f"Expected {x1_value} 'ret' instructions to be executed (matching x1 register), " + f"but counted {ret_executed_count} executed 'ret' instructions.", + ) diff --git a/lldb/test/API/functionalities/unwind/libunwind_ret_injection/main.cpp b/lldb/test/API/functionalities/unwind/libunwind_ret_injection/main.cpp new file mode 100644 index 0000000..00685e4 --- /dev/null +++ b/lldb/test/API/functionalities/unwind/libunwind_ret_injection/main.cpp @@ -0,0 +1,45 @@ +// Test program to verify libunwind ret injection feature for execution flow +// rebalancing. +// +// This test creates a multi-frame call stack and throws a C++ exception to +// trigger libunwind's two-phase exception handling. The test verifies that +// libunwind correctly injects the right amount of 'ret' instructions to +// rebalance the execution flow when returning to the landing pad, which is +// important for Apple Processor Trace analysis. + +#include <cstdio> +#include <exception> +#include <stdexcept> + +// Marker functions with noinline to ensure they appear in the stack. +static void __attribute__((noinline)) func_d() { + printf("In func_d, about to throw exception\n"); + throw std::runtime_error("test exception"); +} + +static void __attribute__((noinline)) func_c() { + printf("In func_c\n"); + func_d(); +} + +static void __attribute__((noinline)) func_b() { + printf("In func_b\n"); + func_c(); +} + +static void __attribute__((noinline)) func_a() { + printf("In func_a\n"); + func_b(); +} + +int main(int argc, char *argv[]) { + try { + printf("In main, about to call func_a\n"); + func_a(); + printf("ERROR: Should not reach here\n"); + return 1; + } catch (const std::exception &e) { + printf("Caught exception in main: %s\n", e.what()); + return 0; + } +} diff --git a/lldb/test/API/functionalities/wrong_commands/TestWrongCommands.py b/lldb/test/API/functionalities/wrong_commands/TestWrongCommands.py index 6d2ce2b..25f95f3 100644 --- a/lldb/test/API/functionalities/wrong_commands/TestWrongCommands.py +++ b/lldb/test/API/functionalities/wrong_commands/TestWrongCommands.py @@ -17,7 +17,9 @@ class UnknownCommandTestCase(TestBase): command_interpreter.HandleCommand("g", result) self.assertFalse(result.Succeeded()) - self.assertRegex(result.GetError(), "Ambiguous command 'g'. Possible matches:") + self.assertRegex( + result.GetError(), "error: Ambiguous command 'g'. Possible matches:" + ) self.assertRegex(result.GetError(), "gui") self.assertRegex(result.GetError(), "gdb-remote") self.assertEqual(1, result.GetError().count("gdb-remote")) diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/Makefile b/lldb/test/API/lang/BoundsSafety/soft_trap/Makefile new file mode 100644 index 0000000..5e83e7a --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/Makefile @@ -0,0 +1,10 @@ +# FIXME: mockSoftTrapRuntime.c shouldn't really be built with -fbounds-safety +C_SOURCES := main.c mockSoftTrapRuntime.c + +soft-trap-test-minimal: CFLAGS_EXTRAS := -fbounds-safety -fbounds-safety-soft-traps=call-minimal +soft-trap-test-minimal: all + +soft-trap-test-with-str: CFLAGS_EXTRAS := -fbounds-safety -fbounds-safety-soft-traps=call-with-str +soft-trap-test-with-str: all + +include Makefile.rules diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py new file mode 100644 index 0000000..535a0bcf --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py @@ -0,0 +1,148 @@ +""" +Test the BoundsSafety instrumentation plugin +""" + +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * + + +STOP_REASON_MAX_LEN = 100 +SOFT_TRAP_FUNC_MINIMAL = "__bounds_safety_soft_trap" +SOFT_TRAP_FUNC_WITH_STR = "__bounds_safety_soft_trap_s" + + +class BoundsSafetyTestSoftTrapPlugin(TestBase): + def _check_stop_reason_impl( + self, + expected_soft_trap_func: str, + expected_stop_reason: str, + expected_func_name: str, + expected_file_name: str, + expected_line_num: int, + ): + process = self.test_target.process + thread = process.GetSelectedThread() + self.assertEqual( + thread.GetStopReason(), + lldb.eStopReasonInstrumentation, + ) + + stop_reason = thread.GetStopDescription(STOP_REASON_MAX_LEN) + self.assertEqual(stop_reason, expected_stop_reason) + + soft_trap_func_frame = thread.GetFrameAtIndex(0) + self.assertEqual(soft_trap_func_frame.name, expected_soft_trap_func) + + stop_frame = thread.GetSelectedFrame() + self.assertEqual(stop_frame.name, expected_func_name) + # The stop frame isn't frame 1 because that frame is the artificial + # frame containing the trap reason. + self.assertEqual(stop_frame.idx, 2) + file_name = stop_frame.GetLineEntry().GetFileSpec().basename + self.assertEqual(file_name, expected_file_name) + line = stop_frame.GetLineEntry().line + self.assertEqual(line, expected_line_num) + + def check_state_soft_trap_minimal( + self, stop_reason: str, func_name: str, file_name: str, line_num: int + ): + """ + Check the program state is as expected when hitting + a soft trap from -fbounds-safety-soft-traps=call-minimal + """ + self._check_stop_reason_impl( + SOFT_TRAP_FUNC_MINIMAL, + expected_stop_reason=stop_reason, + expected_func_name=func_name, + expected_file_name=file_name, + expected_line_num=line_num, + ) + + def check_state_soft_trap_with_str( + self, stop_reason: str, func_name: str, file_name: str, line_num: int + ): + """ + Check the program state is as expected when hitting + a soft trap from -fbounds-safety-soft-traps=call-with_str + """ + self._check_stop_reason_impl( + SOFT_TRAP_FUNC_WITH_STR, + expected_stop_reason=stop_reason, + expected_func_name=func_name, + expected_file_name=file_name, + expected_line_num=line_num, + ) + + # Skip the tests on Windows because they fail due to the stop reason + # being `eStopReasonNon` instead of the expected + # `eStopReasonInstrumentation`. + @skipIfWindows + @skipUnlessBoundsSafety + def test_call_minimal(self): + """ + Test the plugin on code built with + -fbounds-safety-soft-traps=call-minimal + """ + self.build(make_targets=["soft-trap-test-minimal"]) + self.test_target = self.createTestTarget() + self.runCmd("run") + + process = self.test_target.process + + # First soft trap hit + self.check_state_soft_trap_minimal( + "Soft Bounds check failed: indexing above upper bound in 'buffer[2]'", + "main", + "main.c", + 7, + ) + + process.Continue() + + # Second soft trap hit + self.check_state_soft_trap_minimal( + "Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'", + "main", + "main.c", + 8, + ) + + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) + + @skipIfWindows + @skipUnlessBoundsSafety + def test_call_with_str(self): + """ + Test the plugin on code built with + -fbounds-safety-soft-traps=call-with-str + """ + self.build(make_targets=["soft-trap-test-with-str"]) + self.test_target = self.createTestTarget() + self.runCmd("run") + + process = self.test_target.process + + # First soft trap hit + self.check_state_soft_trap_with_str( + "Soft Bounds check failed: indexing above upper bound in 'buffer[2]'", + "main", + "main.c", + 7, + ) + + process.Continue() + + # Second soft trap hit + self.check_state_soft_trap_with_str( + "Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'", + "main", + "main.c", + 8, + ) + + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/main.c b/lldb/test/API/lang/BoundsSafety/soft_trap/main.c new file mode 100644 index 0000000..518afaa --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/main.c @@ -0,0 +1,10 @@ +#include <ptrcheck.h> + +int main(void) { + int pad; + int buffer[] = {0, 1}; + int pad2; + int tmp = buffer[2]; // access past upper bound + tmp = buffer[-1]; // access below lower bound + return 0; +} diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c b/lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c new file mode 100644 index 0000000..2cfbd24 --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c @@ -0,0 +1,17 @@ +#include <bounds_safety_soft_traps.h> +#include <ptrcheck.h> +#include <stdio.h> + +#if __CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION > 0 +#error API version changed +#endif + +// FIXME: The runtimes really shouldn't be built with `-fbounds-safety` in +// soft trap mode because of the risk of infinite recursion. However, +// there's currently no way to have source files built with different flags + +void __bounds_safety_soft_trap_s(const char *reason) { + printf("BoundsSafety check FAILED: message:\"%s\"\n", reason ? reason : ""); +} + +void __bounds_safety_soft_trap(void) { printf("BoundsSafety check FAILED\n"); } diff --git a/lldb/test/API/lang/cpp/inline-namespace-in-typename/Makefile b/lldb/test/API/lang/cpp/inline-namespace-in-typename/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/lang/cpp/inline-namespace-in-typename/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/lang/cpp/inline-namespace-in-typename/TestInlineNamespaceInTypename.py b/lldb/test/API/lang/cpp/inline-namespace-in-typename/TestInlineNamespaceInTypename.py new file mode 100644 index 0000000..1968136 --- /dev/null +++ b/lldb/test/API/lang/cpp/inline-namespace-in-typename/TestInlineNamespaceInTypename.py @@ -0,0 +1,30 @@ +import lldb +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestInlineNamespaceInTypename(TestBase): + def test(self): + """ + Tests that we correctly omit the inline namespace when printing + the type name for "display", even if omitting the inline namespace + would be ambiguous in the current context. + """ + self.build() + target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) + + t1 = target.FindGlobalVariables("t1", 1) + self.assertTrue(len(t1), 1) + self.assertEqual(t1[0].GetDisplayTypeName(), "foo::Duplicate") + + # 'foo::Duplicate' would be an ambiguous reference, but we still + # omit the inline namespace when displaying the type. + t2 = target.FindGlobalVariables("t2", 1) + self.assertTrue(len(t2), 1) + self.assertEqual(t2[0].GetDisplayTypeName(), "foo::Duplicate") + self.assertEqual(t2[0].GetTypeName(), "foo::bar::Duplicate") + + t3 = target.FindGlobalVariables("t3", 1) + self.assertTrue(len(t3), 1) + self.assertEqual(t3[0].GetDisplayTypeName(), "foo::Unique") + self.assertEqual(t3[0].GetTypeName(), "foo::bar::Unique") diff --git a/lldb/test/API/lang/cpp/inline-namespace-in-typename/main.cpp b/lldb/test/API/lang/cpp/inline-namespace-in-typename/main.cpp new file mode 100644 index 0000000..eabd93c --- /dev/null +++ b/lldb/test/API/lang/cpp/inline-namespace-in-typename/main.cpp @@ -0,0 +1,13 @@ +namespace foo { +struct Duplicate { +} t1; + +inline namespace bar { +struct Duplicate { +} t2; +struct Unique { +} t3; +} // namespace bar +} // namespace foo + +int main() { return 0; } diff --git a/lldb/test/API/lang/cpp/libcxx-internals-recognizer/TestLibcxxInternalsRecognizer.py b/lldb/test/API/lang/cpp/libcxx-internals-recognizer/TestLibcxxInternalsRecognizer.py index d8a729b..280ac71 100644 --- a/lldb/test/API/lang/cpp/libcxx-internals-recognizer/TestLibcxxInternalsRecognizer.py +++ b/lldb/test/API/lang/cpp/libcxx-internals-recognizer/TestLibcxxInternalsRecognizer.py @@ -5,11 +5,12 @@ from lldbsuite.test import lldbutil import re +@skipIf(macos_version=[">=", "15.4"], asan=True) class LibCxxInternalsRecognizerTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True @add_test_categories(["libc++"]) - @skipIf(compiler="clang", compiler_version=["<", "19.0"]) + @skipIf(compiler="clang", compiler_version=["<", "21.0"]) def test_frame_recognizer(self): """Test that implementation details of libc++ are hidden""" self.build() diff --git a/lldb/test/API/lang/cpp/template-alias/Makefile b/lldb/test/API/lang/cpp/template-alias/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/lang/cpp/template-alias/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/lang/cpp/template-alias/TestTemplateAlias.py b/lldb/test/API/lang/cpp/template-alias/TestTemplateAlias.py new file mode 100644 index 0000000..b8314eb --- /dev/null +++ b/lldb/test/API/lang/cpp/template-alias/TestTemplateAlias.py @@ -0,0 +1,50 @@ +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestTemplateAlias(TestBase): + def do_test(self, extra_flags): + self.build(dictionary=extra_flags) + self.main_source_file = lldb.SBFileSpec("main.cpp") + lldbutil.run_to_source_breakpoint(self, "return", lldb.SBFileSpec("main.cpp")) + + self.expect_expr("f1", result_type="Foo<int>") + self.expect_expr("f2", result_type="Foo<double>") + self.expect_expr("b1", result_type="Bar<int>") + self.expect_expr("b2", result_type="Bar<double>") + self.expect_expr("bf1", result_type="Bar<int>") + self.expect_expr("bf2", result_type="Bar<double>") + self.expect_expr("bf1", result_type="Bar<int>") + self.expect_expr("bf2", result_type="Bar<double>") + self.expect_expr("cbf1", result_type="Container<int>") + + @expectedFailureAll( + bugnumber="LLDB doesn't reconstruct template alias names from template parameters" + ) + def test_tag_alias_simple(self): + self.do_test( + dict(CXXFLAGS_EXTRAS="-gdwarf-5 -gtemplate-alias -gsimple-template-names") + ) + + def test_tag_alias_no_simple(self): + self.do_test( + dict( + CXXFLAGS_EXTRAS="-gdwarf-5 -gtemplate-alias -gno-simple-template-names" + ) + ) + + def test_no_tag_alias_simple(self): + self.do_test( + dict( + CXXFLAGS_EXTRAS="-gdwarf-5 -gno-template-alias -gsimple-template-names" + ) + ) + + def test_no_tag_alias_no_simple(self): + self.do_test( + dict( + CXXFLAGS_EXTRAS="-gdwarf-5 -gno-template-alias -gno-simple-template-names" + ) + ) diff --git a/lldb/test/API/lang/cpp/template-alias/main.cpp b/lldb/test/API/lang/cpp/template-alias/main.cpp new file mode 100644 index 0000000..af6c979 --- /dev/null +++ b/lldb/test/API/lang/cpp/template-alias/main.cpp @@ -0,0 +1,16 @@ +template <typename T> using Foo = T; + +template <typename T> using Bar = Foo<T>; + +template <typename T> struct Container {}; + +int main() { + Foo<int> f1; + Foo<double> f2; + Bar<int> b1; + Bar<double> b2; + Bar<Foo<int>> bf1; + Bar<Foo<double>> bf2; + Container<Bar<Foo<int>>> cbf1; + return 0; +} diff --git a/lldb/test/API/lang/objc/foundation/TestFoundationDisassembly.py b/lldb/test/API/lang/objc/foundation/TestFoundationDisassembly.py index 245313d..75f6651a 100644 --- a/lldb/test/API/lang/objc/foundation/TestFoundationDisassembly.py +++ b/lldb/test/API/lang/objc/foundation/TestFoundationDisassembly.py @@ -13,52 +13,6 @@ class FoundationDisassembleTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True @skipIfAsan - def test_foundation_disasm(self): - """Do 'disassemble -n func' on each and every 'Code' symbol entry from the Foundation.framework.""" - self.build() - - # Enable synchronous mode - self.dbg.SetAsync(False) - - # Create a target by the debugger. - target = self.dbg.CreateTarget(self.getBuildArtifact("a.out")) - self.assertTrue(target, VALID_TARGET) - - # Now launch the process, and do not stop at entry point. - process = target.LaunchSimple(None, None, self.get_process_working_directory()) - self.assertTrue(process, PROCESS_IS_VALID) - - foundation_framework = None - for module in target.modules: - if module.file.basename == "Foundation": - foundation_framework = module.file.fullpath - break - - self.assertIsNotNone(foundation_framework, "Foundation.framework path located") - self.runCmd("image dump symtab '%s'" % foundation_framework) - raw_output = self.res.GetOutput() - # Now, grab every 'Code' symbol and feed it into the command: - # 'disassemble -n func'. - # - # The symbol name is on the last column and trails the flag column which - # looks like '0xhhhhhhhh', i.e., 8 hexadecimal digits. - codeRE = re.compile( - r""" - \ Code\ {9} # ' Code' followed by 9 SPCs, - .* # the wildcard chars, - 0x[0-9a-f]{8} # the flag column, and - \ (.+)$ # finally the function symbol. - """, - re.VERBOSE, - ) - for line in raw_output.split(os.linesep): - match = codeRE.search(line) - if match: - func = match.group(1) - self.runCmd('image lookup -s "%s"' % func) - self.runCmd('disassemble --force -n "%s"' % func) - - @skipIfAsan def test_simple_disasm(self): """Test the lldb 'disassemble' command""" self.build() diff --git a/lldb/test/API/lang/objc/modules-compile-error/Makefile b/lldb/test/API/lang/objc/modules-compile-error/Makefile deleted file mode 100644 index e031aa0..0000000 --- a/lldb/test/API/lang/objc/modules-compile-error/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -OBJC_SOURCES := main.m - -CFLAGS_EXTRAS = $(MANDATORY_MODULE_BUILD_CFLAGS) -I$(BUILDDIR) -DONLY_CLANG=1 - -include Makefile.rules diff --git a/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py b/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py deleted file mode 100644 index 36e302be..0000000 --- a/lldb/test/API/lang/objc/modules-compile-error/TestModulesCompileError.py +++ /dev/null @@ -1,28 +0,0 @@ -import lldb -from lldbsuite.test.decorators import * -from lldbsuite.test.lldbtest import * -from lldbsuite.test import lldbutil - - -class TestCase(TestBase): - @skipIf(compiler="clang", compiler_version=["<", "11.0"]) - def test(self): - self.build() - lldbutil.run_to_source_breakpoint( - self, "// break here", lldb.SBFileSpec("main.m") - ) - - # Try importing our custom module. This will fail as LLDB won't define - # the CLANG_ONLY define when it compiles the module for the expression - # evaluator. - # Check that the error message shows file/line/column, prints the relevant - # line from the source code and mentions the module that failed to build. - self.expect( - "expr @import LLDBTestModule", - error=True, - substrs=[ - "module.h:4:1: error: use of undeclared identifier 'syntax_error_for_lldb_to_find'", - "syntax_error_for_lldb_to_find // comment that tests source printing", - "could not build module 'LLDBTestModule'", - ], - ) diff --git a/lldb/test/API/lang/objc/modules-compile-error/main.m b/lldb/test/API/lang/objc/modules-compile-error/main.m deleted file mode 100644 index 35259dd..0000000 --- a/lldb/test/API/lang/objc/modules-compile-error/main.m +++ /dev/null @@ -1,5 +0,0 @@ -@import LLDBTestModule; - -int main() { - return foo(); // break here -} diff --git a/lldb/test/API/lang/objc/modules-compile-error/module.h b/lldb/test/API/lang/objc/modules-compile-error/module.h deleted file mode 100644 index 2edd13b..0000000 --- a/lldb/test/API/lang/objc/modules-compile-error/module.h +++ /dev/null @@ -1,5 +0,0 @@ -int foo() { return 123; } - -#ifndef ONLY_CLANG -syntax_error_for_lldb_to_find // comment that tests source printing -#endif diff --git a/lldb/test/API/lang/objc/modules-compile-error/module.modulemap b/lldb/test/API/lang/objc/modules-compile-error/module.modulemap deleted file mode 100644 index 3d44faf..0000000 --- a/lldb/test/API/lang/objc/modules-compile-error/module.modulemap +++ /dev/null @@ -1 +0,0 @@ -module LLDBTestModule { header "module.h" export * } diff --git a/lldb/test/API/lang/rust/enum-variant-same-name/RustEnumValue.py b/lldb/test/API/lang/rust/enum-variant-same-name/RustEnumValue.py new file mode 100644 index 0000000..bc4fd7d --- /dev/null +++ b/lldb/test/API/lang/rust/enum-variant-same-name/RustEnumValue.py @@ -0,0 +1,69 @@ +"""Helper library to traverse data emitted for Rust enums """ +from lldbsuite.test.lldbtest import * + +DISCRIMINANT_MEMBER_NAME = "$discr$" +VALUE_MEMBER_NAME = "value" + + +class RustEnumValue: + def __init__(self, value: lldb.SBValue): + self.value = value + + def getAllVariantTypes(self): + result = [] + for i in range(self._inner().GetNumChildren()): + result.append(self.getVariantByIndex(i).GetDisplayTypeName()) + return result + + def _inner(self) -> lldb.SBValue: + return self.value.GetChildAtIndex(0) + + def getVariantByIndex(self, index): + return ( + self._inner() + .GetChildAtIndex(index) + .GetChildMemberWithName(VALUE_MEMBER_NAME) + ) + + @staticmethod + def _getDiscriminantValueAsUnsigned(discr_sbvalue: lldb.SBValue): + byte_size = discr_sbvalue.GetType().GetByteSize() + error = lldb.SBError() + + # when discriminant is u16 Clang emits 'unsigned char' + # and LLDB seems to treat it as character type disalowing to call GetValueAsUnsigned + if byte_size == 1: + return discr_sbvalue.GetData().GetUnsignedInt8(error, 0) + elif byte_size == 2: + return discr_sbvalue.GetData().GetUnsignedInt16(error, 0) + elif byte_size == 4: + return discr_sbvalue.GetData().GetUnsignedInt32(error, 0) + elif byte_size == 8: + return discr_sbvalue.GetData().GetUnsignedInt64(error, 0) + else: + return discr_sbvalue.GetValueAsUnsigned() + + def getCurrentVariantIndex(self): + default_index = 0 + for i in range(self._inner().GetNumChildren()): + variant: lldb.SBValue = self._inner().GetChildAtIndex(i) + discr = variant.GetChildMemberWithName(DISCRIMINANT_MEMBER_NAME) + if discr.IsValid(): + discr_unsigned_value = RustEnumValue._getDiscriminantValueAsUnsigned( + discr + ) + if variant.GetName() == f"$variant${discr_unsigned_value}": + return discr_unsigned_value + else: + default_index = i + return default_index + + def getFields(self): + result = [] + for i in range(self._inner().GetNumChildren()): + type: lldb.SBType = self._inner().GetType() + result.append(type.GetFieldAtIndex(i).GetName()) + return result + + def getCurrentValue(self) -> lldb.SBValue: + return self.getVariantByIndex(self.getCurrentVariantIndex()) diff --git a/lldb/test/API/lang/rust/enum-variant-same-name/TestRustEnumVariantSameName.py b/lldb/test/API/lang/rust/enum-variant-same-name/TestRustEnumVariantSameName.py new file mode 100644 index 0000000..0a192dc --- /dev/null +++ b/lldb/test/API/lang/rust/enum-variant-same-name/TestRustEnumVariantSameName.py @@ -0,0 +1,36 @@ +"""Test that lldb recognizes enum variant emitted by Rust compiler """ +import logging + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from RustEnumValue import RustEnumValue + + +class TestRustEnumStructs(TestBase): + def setUp(self): + TestBase.setUp(self) + src_dir = self.getSourceDir() + yaml_path = os.path.join(src_dir, "main.yaml") + obj_path = self.getBuildArtifact("main.o") + self.yaml2obj(yaml_path, obj_path) + self.dbg.CreateTarget(obj_path) + + def getFromGlobal(self, name): + values = self.target().FindGlobalVariables(name, 1) + self.assertEqual(values.GetSize(), 1) + return RustEnumValue(values[0]) + + def test_enum_instance(self): + # static ENUM_INSTANCE: A = A::A(B::B(10)); + value = self.getFromGlobal("ENUM_INSTANCE").getCurrentValue() + self.assertEqual(value.GetType().GetDisplayTypeName(), "main::A::A") + + value_b = RustEnumValue(value.GetChildAtIndex(0)) + self.assertEqual( + value_b.getCurrentValue() + .GetChildAtIndex(0) + .GetData() + .GetUnsignedInt8(lldb.SBError(), 0), + 10, + ) diff --git a/lldb/test/API/lang/rust/enum-variant-same-name/main.rs b/lldb/test/API/lang/rust/enum-variant-same-name/main.rs new file mode 100644 index 0000000..e76be3d --- /dev/null +++ b/lldb/test/API/lang/rust/enum-variant-same-name/main.rs @@ -0,0 +1,15 @@ +/// Command: +/// rustc -g --emit=obj --crate-type=bin -C panic=abort -C link-arg=-nostdlib main.rs && obj2yaml main.o -o main.yaml + +pub enum A { + A(B), +} + +pub enum B { + B(u8), +} + +static ENUM_INSTANCE: A = A::A(B::B(10)); + +pub fn main() { +} diff --git a/lldb/test/API/lang/rust/enum-variant-same-name/main.yaml b/lldb/test/API/lang/rust/enum-variant-same-name/main.yaml new file mode 100644 index 0000000..76e7cb6 --- /dev/null +++ b/lldb/test/API/lang/rust/enum-variant-same-name/main.yaml @@ -0,0 +1,1137 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 + SectionHeaderStringTable: .strtab +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x4 + - Name: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 4883EC2889C84889D14889F248897C2408488954241048894C24188844242748893C244889E7488D3500000000440FB6C0FF15000000004883C428C3 + - Name: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 4883EC1848897C2408488B3FE800000000E800000000884424170FB6C04883C418C3 + - Name: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 4883EC1848897C2408E8000000004883C418C3 + - Name: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 4883EC1848897C2410488B3FE8000000004883C418C3 + - Name: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 4883EC1848897C2408488D7C2408E8000000004883C418C3 + - Name: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 4883EC1848897C2410FFD74883C418C3 + - Name: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 48897C24F8C3 + - Name: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 31C0C3 + - Name: .text._ZN4main4main17h5659c6c02e5cd445E + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: C3 + - Name: .text.main + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x10 + Content: 504889F2488B05000000008A004863F7488D3D0000000031C9E80000000059C3 + - Name: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Type: SHT_PROGBITS + Flags: [ SHF_WRITE, SHF_ALLOC ] + AddressAlign: 0x8 + Content: '000000000000000008000000000000000800000000000000000000000000000000000000000000000000000000000000' + - Name: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + AddressAlign: 0x1 + Content: 0A + - Name: .debug_gdb_scripts + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x1 + EntSize: 0x1 + Content: 016764625F6C6F61645F727573745F7072657474795F7072696E746572732E707900 + - Name: .debug_abbrev + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 011101250E1305030E10171B0E110155170000023400030E4913021800000313011D13030E0B0B88010F0000040D00030E491388010F380B0000050F004913030E33060000062400030E3E0B0B0B0000073901030E0000081301030E0B0B88010F0000092E011101120640186E0E030E3A0B3B0B491300000A34000218030E88010F3A0B3B0B491300000B1D01311311011206580B590B570B00000C05000218311300000D1D00311311011206580B5905570B00000E2F004913030E00000F05000218030E3A0B3B0B49130000101301030E0B0B320B88010F0000110D00030E491388010F380B320B0000122E016E0E030E3A0B3B0549133C19000013050049130000142E011101120640186E0E030E3A0B3B0B0000150B01110112060000163400021831130000172E011101120640186E0E030E3A0B3B054913000018050002183A0B3B054913000019150000001A3400030E49133A0B3B0B88010F02186E0E00001B330100001C190100001D2E001101120640186E0E030E3A0B3B0B6A1900001E2E004713200B00001F2E014713200B0000200500030E3A0B3B0549130000212E016E0E030E3A0B3B05200B0000223400030E88010F3A0B3B054913000023050002183A0B3B0B49130000242E011101120640186E0E030E3A0B3B05000000 + - Name: .debug_info + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: F00400000400000000000801000000001C0000000000000000000000000000000000000000000000000002000000003D0000000903000000000000000003B500000000000000300804000000008B000000080004000000009F000000080804000000009F000000081004000000008B000000081804000000008B000000082004000000008B000000082800059800000000000000000000000600000000070006000000000708070000000007000000000700000000080000000008080400000000B1020000080000090000000000000000220000000157000000000000000002C7590300000A03910806000000000802C1B10200000B7303000000000000000000000300000002C7550C029117790300000D6D030000000000000000000003000000041D0810000E980000000000000000000900000000000000003C0000000157000000000000000002C0C50400000F0291080000000002C1B10200000F0291100000000002C2C50400000F0291180000000002C3CC0400000F0291270000000002C4520300000E9800000000000000000007000000000700000000070000000007000000001000000000010101110000000052030000010003120000000000000000037C0259030000136003000000000000000700000000140000000000000000130000000157000000000000000005940F029108000000000594B1020000150000000000000000000000000A02910700000000010598980000000B90030000000000000000000000000000059B0516029117A603000000000EB1020000000000000E9800000000000000000000070000000010000000000101011100000000A6010000010003120000000000000000041C0859030000135402000000000700000000170000000000000000030000000157000000000000000004A909540200001802917F04A909980000000000000005BE02000000000000000000001907000000001A00000000DE020000010C01090300000000000000000000000010000000000101011B1C0400000000F502000001000000100000000001010111000000000B030000010001000010000000000101011B1C04000000002203000001000000100000000001010111000000005203000001000100001D00000000000000000100000001570000000000000000010E00060000000007010600000000050405A601000000000000000000001EBA010000011F68020000012000000000041C0854020000000700000000070000000021000000000000000006DC01010E980000000000000022000000000106DC01980000000000070000000007000000000700000000090000000000000000160000000157000000000000000007FA590300002302911007FAE60400002302910F07FA980000000EB5000000000000000E980000000000000000090000000000000000180000000157000000000000000007FA590300002302910807FAB50000002302911707FA980000000EB5000000000000000E980000000000000000140000000000000000100000000157000000000000000007FA2302911007FAB10200002302910F07FA980000000EB1020000000000000E98000000000000000000000007000000002400000000000000000600000001570000000000000000080B0218029178080B02E60400000EB5000000000000000000000600000000050805D904000000000000000000000552030000000000000000000005B5000000000000000000000000 + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x1 + EntSize: 0x1 + Content: 0072757374632076657273696F6E20312E38372E30202831373036376539616320323032352D30352D30392900 + - Name: .note.GNU-stack + Type: SHT_PROGBITS + AddressAlign: 0x1 + - Name: .eh_frame + Type: SHT_X86_64_UNWIND + Flags: [ SHF_ALLOC ] + AddressAlign: 0x8 + Content: 1400000000000000017A5200017810011B0C070890010000140000001C000000000000003C00000000440E30770E08001400000034000000000000002200000000440E205D0E0800140000004C000000000000001300000000440E204E0E08001400000064000000000000001600000000440E20510E0800140000007C000000000000001800000000440E20530E08001400000094000000000000001000000000440E204B0E080010000000AC00000000000000060000000000000010000000C000000000000000030000000000000010000000D400000000000000010000000000000018000000E8000000000000002000000000410E105E0E080000000000 + - Name: .debug_line + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 0D0400000400E9020000010101FB0E0D0001010101000000010000012F686F6D652F6B6976612F2E7275737475702F746F6F6C636861696E732F737461626C652D7838365F36342D756E6B6E6F776E2D6C696E75782D676E752F6C69622F727573746C69622F7372632F727573742F6C6962726172792F7374642F737263002F686F6D652F6B6976612F2E7275737475702F746F6F6C636861696E732F737461626C652D7838365F36342D756E6B6E6F776E2D6C696E75782D676E752F6C69622F727573746C69622F7372632F727573742F6C6962726172792F7374642F7372632F7379732F70726F636573732F756E6978002F686F6D652F6B6976612F2E7275737475702F746F6F6C636861696E732F737461626C652D7838365F36342D756E6B6E6F776E2D6C696E75782D676E752F6C69622F727573746C69622F7372632F727573742F6C6962726172792F7374642F7372632F737973002F686F6D652F6B6976612F2E7275737475702F746F6F6C636861696E732F737461626C652D7838365F36342D756E6B6E6F776E2D6C696E75782D676E752F6C69622F727573746C69622F7372632F727573742F6C6962726172792F636F72652F737263002F686F6D652F6B6976612F2E7275737475702F746F6F6C636861696E732F737461626C652D7838365F36342D756E6B6E6F776E2D6C696E75782D676E752F6C69622F727573746C69622F7372632F727573742F6C6962726172792F636F72652F7372632F6F7073002F686F6D652F6B6976612F2E7275737475702F746F6F6C636861696E732F737461626C652D7838365F36342D756E6B6E6F776E2D6C696E75782D676E752F6C69622F727573746C69622F7372632F727573742F6C6962726172792F636F72652F7372632F70747200006D61696E2E72730000000072742E727300010000636F6D6D6F6E2E72730002000070726F636573732E7273000100006261636B74726163652E72730003000068696E742E72730004000066756E6374696F6E2E7273000500006D6F642E727300060000000402000902000000000000000003BF0101050A0A08DD05054905020B084202050001010402000902000000000000000003C6010105460A900512063C040305090603B603D60402055D0B03CA7C3C0205000101040500090200000000000000000393010105120A940406050503C50258040505020B03C17D0102050001010407000902000000000000000003F9010105050A90060B8202050001010407000902000000000000000003F9010105050A90060B9E02050001010407000902000000000000000003F9010105050A90060B2E020500010104080009020000000000000000038A040105010A580201000101040405060A000902000000000000000003AA1301020300010105020A0009020000000000000000030E010201000101 + - Name: .rela.text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Relocations: + - Offset: 0x29 + Symbol: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Type: R_X86_64_PC32 + Addend: -4 + - Offset: 0x33 + Symbol: _ZN3std2rt19lang_start_internal17h418648f91f5be3a1E + Type: R_X86_64_GOTPCREL + Addend: -4 + - Name: '.rela.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Relocations: + - Offset: 0xD + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_PLT32 + Addend: -4 + - Offset: 0x12 + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_PLT32 + Addend: -4 + - Name: .rela.text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Relocations: + - Offset: 0xA + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_PLT32 + Addend: -4 + - Name: '.rela.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Relocations: + - Offset: 0xD + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_PLT32 + Addend: -4 + - Name: .rela.text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Relocations: + - Offset: 0xF + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_PLT32 + Addend: -4 + - Name: .rela.text.main + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .text.main + Relocations: + - Offset: 0x7 + Symbol: __rustc_debug_gdb_scripts_section__ + Type: R_X86_64_GOTPCREL + Addend: -4 + - Offset: 0x13 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_PC32 + Addend: -4 + - Offset: 0x1A + Symbol: _ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_PLT32 + Addend: -4 + - Name: .rela.data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Relocations: + - Offset: 0x18 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_64 + - Offset: 0x20 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + - Offset: 0x28 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + - Name: .rela.debug_info + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .debug_info + Relocations: + - Offset: 0x6 + Symbol: .debug_abbrev + Type: R_X86_64_32 + - Offset: 0xC + Symbol: .debug_str + Type: R_X86_64_32 + - Offset: 0x12 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 57 + - Offset: 0x16 + Symbol: .debug_line + Type: R_X86_64_32 + - Offset: 0x1A + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 95 + - Offset: 0x26 + Symbol: .debug_ranges + Type: R_X86_64_32 + - Offset: 0x2B + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 176 + - Offset: 0x35 + Symbol: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Type: R_X86_64_64 + - Offset: 0x42 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 382 + - Offset: 0x49 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 260 + - Offset: 0x54 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 287 + - Offset: 0x5F + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 298 + - Offset: 0x6A + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 304 + - Offset: 0x75 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 314 + - Offset: 0x80 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 324 + - Offset: 0x90 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 277 + - Offset: 0x99 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 274 + - Offset: 0xA0 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 292 + - Offset: 0xA7 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 334 + - Offset: 0xAC + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 338 + - Offset: 0xB1 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 341 + - Offset: 0xB6 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 362 + - Offset: 0xBD + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 352 + - Offset: 0xC9 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + - Offset: 0xD7 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 943 + - Offset: 0xDB + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1016 + - Offset: 0xEA + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 352 + - Offset: 0xFA + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + Addend: 26 + - Offset: 0x116 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + Addend: 26 + - Offset: 0x12C + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 769 + - Offset: 0x133 + Symbol: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_64 + - Offset: 0x141 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 879 + - Offset: 0x145 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 922 + - Offset: 0x153 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 352 + - Offset: 0x161 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1744 + - Offset: 0x16F + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1749 + - Offset: 0x17D + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1781 + - Offset: 0x18C + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 769 + - Offset: 0x193 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 540 + - Offset: 0x198 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 544 + - Offset: 0x19D + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 552 + - Offset: 0x1A2 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 557 + - Offset: 0x1A7 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 564 + - Offset: 0x1AF + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 487 + - Offset: 0x1BB + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 573 + - Offset: 0x1BF + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 641 + - Offset: 0x1D5 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 835 + - Offset: 0x1DA + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + - Offset: 0x1E8 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1034 + - Offset: 0x1EC + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1106 + - Offset: 0x1F6 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1796 + - Offset: 0x201 + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + Addend: 14 + - Offset: 0x211 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1789 + - Offset: 0x221 + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + Addend: 14 + - Offset: 0x23F + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1032 + - Offset: 0x248 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 769 + - Offset: 0x250 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 544 + - Offset: 0x255 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 564 + - Offset: 0x25D + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 487 + - Offset: 0x269 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 695 + - Offset: 0x26D + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 747 + - Offset: 0x280 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 869 + - Offset: 0x285 + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_64 + - Offset: 0x293 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1616 + - Offset: 0x297 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1703 + - Offset: 0x2B6 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 357 + - Offset: 0x2C0 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 352 + - Offset: 0x2C5 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 471 + - Offset: 0x2D2 + Symbol: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + Type: R_X86_64_64 + - Offset: 0x2DA + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 496 + - Offset: 0x2DF + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 485 + - Offset: 0x2E9 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 485 + - Offset: 0x2F6 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 485 + - Offset: 0x2FE + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 487 + - Offset: 0x30C + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 491 + - Offset: 0x316 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 491 + - Offset: 0x323 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 491 + - Offset: 0x32B + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 487 + - Offset: 0x339 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_64 + - Offset: 0x347 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1710 + - Offset: 0x34B + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 352 + - Offset: 0x353 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 493 + - Offset: 0x35A + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 648 + - Offset: 0x365 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 652 + - Offset: 0x37A + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 754 + - Offset: 0x387 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 759 + - Offset: 0x38C + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 764 + - Offset: 0x391 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 771 + - Offset: 0x395 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 815 + - Offset: 0x3A2 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 769 + - Offset: 0x3A7 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 829 + - Offset: 0x3B6 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 845 + - Offset: 0x3BB + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 849 + - Offset: 0x3C0 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 858 + - Offset: 0x3C5 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_64 + - Offset: 0x3D3 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1155 + - Offset: 0x3D7 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1246 + - Offset: 0x3FA + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1145 + - Offset: 0x403 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1150 + - Offset: 0x409 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_64 + - Offset: 0x417 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1302 + - Offset: 0x41B + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1246 + - Offset: 0x43E + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1145 + - Offset: 0x447 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1150 + - Offset: 0x44D + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_64 + - Offset: 0x45B + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1361 + - Offset: 0x45F + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1420 + - Offset: 0x47E + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1145 + - Offset: 0x487 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1150 + - Offset: 0x490 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 865 + - Offset: 0x495 + Symbol: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: R_X86_64_64 + - Offset: 0x4A3 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1440 + - Offset: 0x4A7 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1560 + - Offset: 0x4BE + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 769 + - Offset: 0x4C6 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 937 + - Offset: 0x4D1 + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1764 + - Offset: 0x4DE + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1754 + - Offset: 0x4EB + Symbol: .debug_str + Type: R_X86_64_32 + Addend: 1798 + - Name: .rela.debug_aranges + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .debug_aranges + Relocations: + - Offset: 0x6 + Symbol: .debug_info + Type: R_X86_64_32 + - Offset: 0x10 + Symbol: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Type: R_X86_64_64 + - Offset: 0x20 + Symbol: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + Type: R_X86_64_64 + - Offset: 0x30 + Symbol: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_64 + - Offset: 0x40 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + - Offset: 0x50 + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + - Offset: 0x60 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_64 + - Offset: 0x70 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_64 + - Offset: 0x80 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_64 + - Offset: 0x90 + Symbol: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: R_X86_64_64 + - Offset: 0xA0 + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_64 + - Offset: 0xB0 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_64 + - Name: .rela.debug_ranges + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .debug_ranges + Relocations: + - Symbol: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_64 + - Offset: 0x8 + Symbol: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_64 + Addend: 60 + - Offset: 0x10 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + - Offset: 0x18 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + Addend: 34 + - Offset: 0x20 + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + - Offset: 0x28 + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + Addend: 19 + - Offset: 0x30 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_64 + - Offset: 0x38 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_64 + Addend: 22 + - Offset: 0x40 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_64 + - Offset: 0x48 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_64 + Addend: 24 + - Offset: 0x50 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_64 + - Offset: 0x58 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_64 + Addend: 16 + - Offset: 0x60 + Symbol: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: R_X86_64_64 + - Offset: 0x68 + Symbol: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: R_X86_64_64 + Addend: 6 + - Offset: 0x70 + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_64 + - Offset: 0x78 + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_64 + Addend: 3 + - Offset: 0x80 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_64 + - Offset: 0x88 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_64 + Addend: 1 + - Name: .rela.eh_frame + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .eh_frame + Relocations: + - Offset: 0x20 + Symbol: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_PC32 + - Offset: 0x38 + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_PC32 + - Offset: 0x50 + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_PC32 + - Offset: 0x68 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_PC32 + - Offset: 0x80 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_PC32 + - Offset: 0x98 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_PC32 + - Offset: 0xB0 + Symbol: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: R_X86_64_PC32 + - Offset: 0xC4 + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_PC32 + - Offset: 0xD8 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_PC32 + - Offset: 0xEC + Symbol: .text.main + Type: R_X86_64_PC32 + - Name: .rela.debug_line + Type: SHT_RELA + Flags: [ SHF_INFO_LINK ] + Link: .symtab + AddressAlign: 0x8 + Info: .debug_line + Relocations: + - Offset: 0x2F8 + Symbol: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: R_X86_64_64 + - Offset: 0x31B + Symbol: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: R_X86_64_64 + - Offset: 0x34B + Symbol: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: R_X86_64_64 + - Offset: 0x376 + Symbol: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: R_X86_64_64 + - Offset: 0x393 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: R_X86_64_64 + - Offset: 0x3B0 + Symbol: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: R_X86_64_64 + - Offset: 0x3CD + Symbol: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: R_X86_64_64 + - Offset: 0x3EA + Symbol: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: R_X86_64_64 + - Offset: 0x401 + Symbol: .text._ZN4main4main17h5659c6c02e5cd445E + Type: R_X86_64_64 + - Type: SectionHeaderTable + Sections: + - Name: .strtab + - Name: .text + - Name: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + - Name: .rela.text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + - Name: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + - Name: '.rela.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + - Name: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + - Name: .rela.text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + - Name: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + - Name: '.rela.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + - Name: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + - Name: .rela.text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + - Name: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + - Name: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + - Name: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + - Name: .text._ZN4main4main17h5659c6c02e5cd445E + - Name: .text.main + - Name: .rela.text.main + - Name: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + - Name: .rela.data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + - Name: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + - Name: .debug_gdb_scripts + - Name: .debug_abbrev + - Name: .debug_info + - Name: .rela.debug_info + - Name: .debug_aranges + - Name: .rela.debug_aranges + - Name: .debug_ranges + - Name: .rela.debug_ranges + - Name: .debug_str + - Name: .comment + - Name: .note.GNU-stack + - Name: .eh_frame + - Name: .rela.eh_frame + - Name: .debug_line + - Name: .rela.debug_line + - Name: .symtab +Symbols: + - Name: main.5f6bf0c8e9d0afce-cgu.0 + Type: STT_FILE + Index: SHN_ABS + - Name: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: STT_SECTION + Section: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + - Name: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: STT_SECTION + Section: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + - Name: '_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Type: STT_FUNC + Section: '.text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + Size: 0x22 + - Name: _ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: STT_FUNC + Section: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Size: 0x13 + - Name: '_ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: STT_FUNC + Section: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Size: 0x3 + - Name: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + Type: STT_SECTION + Section: .text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + - Name: _ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: STT_FUNC + Section: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Size: 0x10 + - Name: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: STT_SECTION + Section: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + - Name: '_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Type: STT_FUNC + Section: '.text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + Size: 0x16 + - Name: _ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: STT_FUNC + Section: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Size: 0x18 + - Name: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + Type: STT_SECTION + Section: .text._ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + - Name: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + Type: STT_SECTION + Section: .text._ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + - Name: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: STT_SECTION + Section: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + - Name: '_ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Type: STT_FUNC + Section: '.text._ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + Size: 0x6 + - Name: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + Type: STT_SECTION + Section: '.text._ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + - Name: .text._ZN4main4main17h5659c6c02e5cd445E + Type: STT_SECTION + Section: .text._ZN4main4main17h5659c6c02e5cd445E + - Name: _ZN4main4main17h5659c6c02e5cd445E + Type: STT_FUNC + Section: .text._ZN4main4main17h5659c6c02e5cd445E + Size: 0x1 + - Name: .text.main + Type: STT_SECTION + Section: .text.main + - Name: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + Type: STT_SECTION + Section: .data.rel.ro..Lanon.4a406b9d9c1243847b49ddffb2385826.0 + - Name: _ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + Type: STT_OBJECT + Section: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + Size: 0x1 + - Name: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + Type: STT_SECTION + Section: .rodata._ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + - Name: .debug_abbrev + Type: STT_SECTION + Section: .debug_abbrev + - Name: .debug_info + Type: STT_SECTION + Section: .debug_info + - Name: .debug_ranges + Type: STT_SECTION + Section: .debug_ranges + - Name: .debug_str + Type: STT_SECTION + Section: .debug_str + - Name: .debug_line + Type: STT_SECTION + Section: .debug_line + - Name: _ZN3std2rt10lang_start17hcb2a6a78164896cfE + Type: STT_FUNC + Section: .text._ZN3std2rt10lang_start17hcb2a6a78164896cfE + Binding: STB_GLOBAL + Size: 0x3C + Other: [ STV_HIDDEN ] + - Name: _ZN3std2rt19lang_start_internal17h418648f91f5be3a1E + Binding: STB_GLOBAL + - Name: main + Type: STT_FUNC + Section: .text.main + Binding: STB_GLOBAL + Size: 0x20 + - Name: __rustc_debug_gdb_scripts_section__ + Type: STT_OBJECT + Section: .debug_gdb_scripts + Binding: STB_WEAK + Size: 0x22 +DWARF: + debug_str: + - 'clang LLVM (rustc version 1.87.0 (17067e9ac 2025-05-09))' + - 'main.rs/@/main.5f6bf0c8e9d0afce-cgu.0' + - '/home/kiva/upstream/llvm-upstream/lldb/test/API/lang/rust/enum-variant-same-name' + - '<std::rt::lang_start::{closure_env#0}<()> as core::ops::function::Fn<()>>::{vtable}' + - drop_in_place + - '()' + - '*const ()' + - size + - usize + - align + - __method3 + - __method4 + - __method5 + - std + - rt + - lang_start + - main + - 'fn()' + - '{closure_env#0}<()>' + - '<std::rt::lang_start::{closure_env#0}<()> as core::ops::function::Fn<()>>::{vtable_type}' + - ENUM_INSTANCE + - A + - __0 + - B + - u8 + - _ZN4main13ENUM_INSTANCE17hc6db515181ea378fE + - sys + - process + - unix + - common + - ExitCode + - _ZN3std3sys7process4unix6common8ExitCode6as_i3217h7893c1c2ab39ed8fE + - as_i32 + - i32 + - '&std::sys::process::unix::common::ExitCode' + - _ZN3std7process8ExitCode6to_i3217habce61080992bc43E + - to_i32 + - self + - core + - hint + - T + - _ZN4core4hint9black_box17h29a7c52ab9efa45eE + - 'black_box<()>' + - dummy + - backtrace + - ops + - function + - FnOnce + - ptr + - '{impl#59}' + - _ZN3std2rt10lang_start17hcb2a6a78164896cfE + - 'lang_start<()>' + - isize + - '_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hda4568952b4f838eE' + - '{closure#0}<()>' + - F + - _ZN3std3sys9backtrace28__rust_begin_short_backtrace17h4c89f1d03cb386ebE + - '__rust_begin_short_backtrace<fn(), ()>' + - Self + - Args + - '_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h356d5dadf319082fE' + - 'call_once<std::rt::lang_start::{closure_env#0}<()>, ()>' + - _ZN4core3ops8function6FnOnce9call_once17h21112f971fb5dae8E + - _ZN4core3ops8function6FnOnce9call_once17hf55c273de81f71dfE + - 'call_once<fn(), ()>' + - '_ZN4core3ptr85drop_in_place$LT$std..rt..lang_start$LT$$LP$$RP$$GT$..$u7b$$u7b$closure$u7d$$u7d$$GT$17h623f70e9f0cf3066E' + - 'drop_in_place<std::rt::lang_start::{closure_env#0}<()>>' + - '_ZN54_$LT$$LP$$RP$$u20$as$u20$std..process..Termination$GT$6report17h10aa5c9c6135be01E' + - report + - _ZN4main4main17h5659c6c02e5cd445E + - argc + - argv + - '*const u8' + - '*const *const u8' + - sigpipe + - result + - f + - '*mut std::rt::lang_start::{closure_env#0}<()>' + debug_aranges: + - Length: 0xCC + Version: 2 + CuOffset: 0x0 + AddressSize: 0x8 + Descriptors: + - Address: 0x0 + Length: 0x30 + - Address: 0x0 + Length: 0x1 + - Address: 0x0 + Length: 0x3C + - Address: 0x0 + Length: 0x22 + - Address: 0x0 + Length: 0x13 + - Address: 0x0 + Length: 0x16 + - Address: 0x0 + Length: 0x18 + - Address: 0x0 + Length: 0x10 + - Address: 0x0 + Length: 0x6 + - Address: 0x0 + Length: 0x3 + - Address: 0x0 + Length: 0x1 + debug_ranges: + - Offset: 0x0 + AddrSize: 0x8 + Entries: [] + - Offset: 0x10 + AddrSize: 0x8 + Entries: [] + - Offset: 0x20 + AddrSize: 0x8 + Entries: [] + - Offset: 0x30 + AddrSize: 0x8 + Entries: [] + - Offset: 0x40 + AddrSize: 0x8 + Entries: [] + - Offset: 0x50 + AddrSize: 0x8 + Entries: [] + - Offset: 0x60 + AddrSize: 0x8 + Entries: [] + - Offset: 0x70 + AddrSize: 0x8 + Entries: [] + - Offset: 0x80 + AddrSize: 0x8 + Entries: [] + - Offset: 0x90 + AddrSize: 0x8 + Entries: [] +... diff --git a/lldb/test/API/macosx/find-dsym/bundle-with-dot-in-filename/Makefile b/lldb/test/API/macosx/find-dsym/bundle-with-dot-in-filename/Makefile index 12781fd..f135840 100644 --- a/lldb/test/API/macosx/find-dsym/bundle-with-dot-in-filename/Makefile +++ b/lldb/test/API/macosx/find-dsym/bundle-with-dot-in-filename/Makefile @@ -5,7 +5,7 @@ all: clean $(EXE) include Makefile.rules $(EXE): - $(CC) $(CFLAGS) -dynamiclib -o com.apple.sbd $(SRCDIR)/bundle.c + $(CC) $(ASAN_LDFLAGS) $(CFLAGS) -dynamiclib -o com.apple.sbd $(SRCDIR)/bundle.c mkdir com.apple.sbd.xpc mv com.apple.sbd com.apple.sbd.xpc/ mkdir -p com.apple.sbd.xpc.dSYM/Contents/Resources/DWARF @@ -13,7 +13,7 @@ $(EXE): rm -rf com.apple.sbd.dSYM mkdir hide.app tar cf - com.apple.sbd.xpc com.apple.sbd.xpc.dSYM | ( cd hide.app;tar xBpf -) - $(CC) $(CFLAGS) -o find-bundle-with-dots-in-fn $(SRCDIR)/main.c + $(CC) $(ASAN_LDFLAGS) $(CFLAGS) -o find-bundle-with-dots-in-fn $(SRCDIR)/main.c clean:: rm -rf a.out a.out.dSYM hide.app com.apple.sbd com.apple.sbd.dSYM com.apple.sbd.xpc com.apple.sbd.xpc.dSYM find-bundle-with-dots-in-fn find-bundle-with-dots-in-fn.dSYM diff --git a/lldb/test/API/macosx/find-dsym/deep-bundle/Makefile b/lldb/test/API/macosx/find-dsym/deep-bundle/Makefile index 806c840..c041d9e 100644 --- a/lldb/test/API/macosx/find-dsym/deep-bundle/Makefile +++ b/lldb/test/API/macosx/find-dsym/deep-bundle/Makefile @@ -4,7 +4,7 @@ all: clean $(EXE) include Makefile.rules $(EXE): - $(CC) $(CFLAGS) -install_name $(shell pwd)/MyFramework.framework/Versions/A/MyFramework -dynamiclib -o MyFramework $(SRCDIR)/myframework.c + $(CC) $(ASAN_LDFLAGS) $(CFLAGS) -install_name $(shell pwd)/MyFramework.framework/Versions/A/MyFramework -dynamiclib -o MyFramework $(SRCDIR)/myframework.c mkdir -p MyFramework.framework/Versions/A/Headers mkdir -p MyFramework.framework/Versions/A/Resources cp MyFramework MyFramework.framework/Versions/A @@ -18,7 +18,7 @@ $(EXE): mkdir hide.app rm -f MyFramework tar cf - MyFramework.framework MyFramework.framework.dSYM | ( cd hide.app;tar xBpf -) - $(CC) $(CFLAGS) -o deep-bundle $(SRCDIR)/main.c -F. -framework MyFramework + $(CC) $(ASAN_LDFLAGS) $(CFLAGS) -o deep-bundle $(SRCDIR)/main.c -F. -framework MyFramework clean:: rm -rf a.out a.out.dSYM deep-bundle deep-bundle.dSYM MyFramework.framework MyFramework.framework.dSYM MyFramework MyFramework.dSYM hide.app diff --git a/lldb/test/API/macosx/posix_spawn/Makefile b/lldb/test/API/macosx/posix_spawn/Makefile index 7ae46ca..cbdee91 100644 --- a/lldb/test/API/macosx/posix_spawn/Makefile +++ b/lldb/test/API/macosx/posix_spawn/Makefile @@ -6,13 +6,13 @@ include Makefile.rules all: fat.out x86_64.out: x86_64.c - $(CC) -isysroot $(SDKROOT) -target x86_64-apple-macosx10.9 -o x86_64.out $< + $(CC) $(ASAN_LDFLAGS) -isysroot $(SDKROOT) -target x86_64-apple-macosx10.9 -o x86_64.out $< x86_64h.out: x86_64h.c - $(CC) -isysroot $(SDKROOT) -target x86_64h-apple-macosx10.9 -o x86_64h.out $< + $(CC) $(ASAN_LDFLAGS) -isysroot $(SDKROOT) -target x86_64h-apple-macosx10.9 -o x86_64h.out $< arm64.out: arm64.c - $(CC) -isysroot $(SDKROOT) -target arm64-apple-macosx10.9 -o arm64.out $< + $(CC) $(ASAN_LDFLAGS) -isysroot $(SDKROOT) -target arm64-apple-macosx10.9 -o arm64.out $< fat.out: x86_64.out x86_64h.out arm64.out $(LIPO) -o fat.out -create $^ diff --git a/lldb/test/API/python_api/command_script_output/TestCommandScriptOutput.py b/lldb/test/API/python_api/command_script_output/TestCommandScriptOutput.py new file mode 100644 index 0000000..abe0eec --- /dev/null +++ b/lldb/test/API/python_api/command_script_output/TestCommandScriptOutput.py @@ -0,0 +1,47 @@ +""" +Test that HandleCommand captures stdout and stderr from script commands. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class CommandScriptOutputTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_script_command_stdout_stderr(self): + """Test that HandleCommand captures stdout and stderr from script commands.""" + ci = self.dbg.GetCommandInterpreter() + self.assertTrue(ci, VALID_COMMAND_INTERPRETER) + + res = lldb.SBCommandReturnObject() + + # Execute a script command that writes to stdout. + ci.HandleCommand("script print('Hello stdout')", res) + self.assertTrue(res.Succeeded()) + self.assertIn("Hello stdout", res.GetOutput()) + + # Execute a script command that writes to stderr. + ci.HandleCommand("script import sys; sys.stderr.write('Hello stderr\\n')", res) + self.assertTrue(res.Succeeded()) + self.assertIn("Hello stderr", res.GetOutput()) + + # Execute a script command that writes to both stdout and stderr. + ci.HandleCommand( + "script import sys; print('Output line'); sys.stderr.write('Error line\\n')", + res, + ) + self.assertTrue(res.Succeeded()) + self.assertIn("Output line", res.GetOutput()) + self.assertIn("Error line", res.GetOutput()) + + # Test that multiple print statements are captured. + ci.HandleCommand( + "script print('Line 1'); print('Line 2'); print('Line 3')", res + ) + self.assertTrue(res.Succeeded()) + output = res.GetOutput() + self.assertIn("Line 1", output) + self.assertIn("Line 2", output) + self.assertIn("Line 3", output) diff --git a/lldb/test/API/python_api/exprpath_register/Makefile b/lldb/test/API/python_api/exprpath_register/Makefile new file mode 100644 index 0000000..1049594 --- /dev/null +++ b/lldb/test/API/python_api/exprpath_register/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/python_api/exprpath_register/TestExprPathRegisters.py b/lldb/test/API/python_api/exprpath_register/TestExprPathRegisters.py new file mode 100644 index 0000000..4ffbc5e --- /dev/null +++ b/lldb/test/API/python_api/exprpath_register/TestExprPathRegisters.py @@ -0,0 +1,64 @@ +""" +Test Getting the expression path for registers works correctly +""" + +import lldb +from lldbsuite.test import lldbutil +from lldbsuite.test.lldbtest import TestBase, VALID_BREAKPOINT, VALID_TARGET + + +class TestExprPathRegisters(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def verify_register_path(self, reg_value: lldb.SBValue): + stream = lldb.SBStream() + reg_name = reg_value.name + self.assertTrue( + reg_value.GetExpressionPath(stream), + f"Expected an expression path for register {reg_name}.", + ) + reg_expr_path = stream.GetData() + self.assertEqual(reg_expr_path, f"${reg_name}") + + def test_float_registers(self): + """Verify the expression path of the registers is valid.""" + self.build() + _, _, thread, _ = lldbutil.run_to_name_breakpoint(self, "my_foo") + frame = thread.GetSelectedFrame() + self.assertTrue(frame, "Expected a valid Frame.") + + # possible floating point register on some cpus. + register_names = [ + "xmm0", + "ymm0", + "v0", + "v1", + "f0", + "f1", + "d0", + "d1", + "vr0", + "vr1", + "st0", + "st1", + ] + for name in register_names: + reg_value = frame.FindRegister(name) + # some the register will not be available for the cpu + # only verify if it is valid. + if reg_value: + self.verify_register_path(reg_value) + + def test_all_registers(self): + """Test all the registers that is avaiable on the machine""" + self.build() + _, _, thread, _ = lldbutil.run_to_name_breakpoint(self, "my_foo") + frame = thread.GetSelectedFrame() + self.assertTrue(frame, "Expected a valid Frame.") + + register_sets = frame.GetRegisters() + self.assertTrue(register_sets.IsValid(), "Expected Frame Registers") + + for register_set in register_sets: + for register in register_set.children: + self.verify_register_path(register) diff --git a/lldb/test/API/python_api/exprpath_register/main.c b/lldb/test/API/python_api/exprpath_register/main.c new file mode 100644 index 0000000..4809a87cd --- /dev/null +++ b/lldb/test/API/python_api/exprpath_register/main.c @@ -0,0 +1,10 @@ + +float my_foo() { + float result = 10.0 + 20.0; + return result; +} + +int main(void) { + float result = my_foo(); + return (int)result; +} diff --git a/lldb/test/API/python_api/frame_list/Makefile b/lldb/test/API/python_api/frame_list/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/python_api/frame_list/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/python_api/frame_list/TestSBFrameList.py b/lldb/test/API/python_api/frame_list/TestSBFrameList.py new file mode 100644 index 0000000..f348ce4 --- /dev/null +++ b/lldb/test/API/python_api/frame_list/TestSBFrameList.py @@ -0,0 +1,194 @@ +""" +Test SBFrameList API. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class FrameListAPITestCase(TestBase): + def test_frame_list_api(self): + """Test SBThread.GetFrames() returns a valid SBFrameList.""" + self.build() + self.frame_list_api() + + def test_frame_list_iterator(self): + """Test SBFrameList iterator functionality.""" + self.build() + self.frame_list_iterator() + + def test_frame_list_indexing(self): + """Test SBFrameList indexing and length.""" + self.build() + self.frame_list_indexing() + + def test_frame_list_get_thread(self): + """Test SBFrameList.GetThread() returns correct thread.""" + self.build() + self.frame_list_get_thread() + + def setUp(self): + TestBase.setUp(self) + self.main_source = "main.cpp" + + def frame_list_api(self): + """Test SBThread.GetFrames() returns a valid SBFrameList.""" + exe = self.getBuildArtifact("a.out") + + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Set break point at this line", lldb.SBFileSpec(self.main_source) + ) + + self.assertTrue( + thread.IsValid(), "There should be a thread stopped due to breakpoint" + ) + + # Test GetFrames() returns a valid SBFrameList + frame_list = thread.GetFrames() + self.assertTrue(frame_list.IsValid(), "Frame list should be valid") + self.assertGreater( + frame_list.GetSize(), 0, "Frame list should have at least one frame" + ) + + # Verify frame list size matches thread frame count + self.assertEqual( + frame_list.GetSize(), + thread.GetNumFrames(), + "Frame list size should match thread frame count", + ) + + # Verify frames are the same + for i in range(frame_list.GetSize()): + frame_from_list = frame_list.GetFrameAtIndex(i) + frame_from_thread = thread.GetFrameAtIndex(i) + self.assertTrue( + frame_from_list.IsValid(), f"Frame {i} from list should be valid" + ) + self.assertEqual( + frame_from_list.GetPC(), + frame_from_thread.GetPC(), + f"Frame {i} PC should match", + ) + + def frame_list_iterator(self): + """Test SBFrameList iterator functionality.""" + exe = self.getBuildArtifact("a.out") + + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Set break point at this line", lldb.SBFileSpec(self.main_source) + ) + + self.assertTrue( + thread.IsValid(), "There should be a thread stopped due to breakpoint" + ) + + frame_list = thread.GetFrames() + + # Test iteration + frame_count = 0 + for frame in frame_list: + self.assertTrue(frame.IsValid(), "Each frame should be valid") + frame_count += 1 + + self.assertEqual( + frame_count, + frame_list.GetSize(), + "Iterator should visit all frames", + ) + + # Test that we can iterate multiple times + second_count = 0 + for frame in frame_list: + second_count += 1 + + self.assertEqual( + frame_count, second_count, "Should be able to iterate multiple times" + ) + + def frame_list_indexing(self): + """Test SBFrameList indexing and length.""" + exe = self.getBuildArtifact("a.out") + + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Set break point at this line", lldb.SBFileSpec(self.main_source) + ) + + self.assertTrue( + thread.IsValid(), "There should be a thread stopped due to breakpoint" + ) + + frame_list = thread.GetFrames() + + # Test len() + self.assertEqual( + len(frame_list), frame_list.GetSize(), "len() should return frame count" + ) + + # Test positive indexing + first_frame = frame_list[0] + self.assertTrue(first_frame.IsValid(), "First frame should be valid") + self.assertEqual( + first_frame.GetPC(), + thread.GetFrameAtIndex(0).GetPC(), + "Indexed frame should match", + ) + + # Test negative indexing + if len(frame_list) > 0: + last_frame = frame_list[-1] + self.assertTrue(last_frame.IsValid(), "Last frame should be valid") + self.assertEqual( + last_frame.GetPC(), + thread.GetFrameAtIndex(len(frame_list) - 1).GetPC(), + "Negative indexing should work", + ) + + # Test out of bounds returns None + out_of_bounds = frame_list[10000] + self.assertIsNone(out_of_bounds, "Out of bounds index should return None") + + # Test bool conversion + self.assertTrue(bool(frame_list), "Non-empty frame list should be truthy") + + # Test Clear() + frame_list.Clear() + # Note: Clear() clears the underlying StackFrameList cache, + # but the frame list object itself should still be valid + self.assertTrue( + frame_list.IsValid(), "Frame list should still be valid after Clear()" + ) + + def frame_list_get_thread(self): + """Test SBFrameList.GetThread() returns correct thread.""" + exe = self.getBuildArtifact("a.out") + + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, "Set break point at this line", lldb.SBFileSpec(self.main_source) + ) + + self.assertTrue( + thread.IsValid(), "There should be a thread stopped due to breakpoint" + ) + + frame_list = thread.GetFrames() + self.assertTrue(frame_list.IsValid(), "Frame list should be valid") + + # Test GetThread() returns the correct thread + thread_from_list = frame_list.GetThread() + self.assertTrue( + thread_from_list.IsValid(), "Thread from frame list should be valid" + ) + self.assertEqual( + thread_from_list.GetThreadID(), + thread.GetThreadID(), + "Frame list should return the correct thread", + ) + + # Verify it's the same thread object + self.assertEqual( + thread_from_list.GetProcess().GetProcessID(), + thread.GetProcess().GetProcessID(), + "Thread should belong to same process", + ) diff --git a/lldb/test/API/python_api/frame_list/main.cpp b/lldb/test/API/python_api/frame_list/main.cpp new file mode 100644 index 0000000..e399446 --- /dev/null +++ b/lldb/test/API/python_api/frame_list/main.cpp @@ -0,0 +1,22 @@ +#include <stdio.h> + +int c(int val) { + // Set break point at this line + return val + 3; +} + +int b(int val) { + int result = c(val); + return result; +} + +int a(int val) { + int result = b(val); + return result; +} + +int main() { + int result = a(1); + printf("Result: %d\n", result); + return 0; +} diff --git a/lldb/test/API/python_api/sblineentry/TestSBLineEntry.py b/lldb/test/API/python_api/sblineentry/TestSBLineEntry.py new file mode 100644 index 0000000..ee8404c --- /dev/null +++ b/lldb/test/API/python_api/sblineentry/TestSBLineEntry.py @@ -0,0 +1,144 @@ +""" +Test SBLineEntry APIs, particularly synthetic line entries. +""" + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class SBLineEntryTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_synthetic_line_entry(self): + """Test that synthetic LineEntry objects (created via SBAPI) can be + valid without a valid address range and can be set in SBSymbolContext.""" + + # Test creating a synthetic line entry via SBAPI. + line_entry = lldb.SBLineEntry() + self.assertFalse( + line_entry.IsValid(), "Default constructed line entry should be invalid" + ) + + # Set line number - this should mark the line entry as synthetic. + line_entry.SetLine(42) + self.assertTrue( + line_entry.IsValid(), + "Line entry should be valid after setting line, even without address", + ) + self.assertEqual(line_entry.GetLine(), 42) + + # Set file and column. + file_spec = lldb.SBFileSpec(os.path.join(self.getSourceDir(), "test.cpp"), True) + line_entry.SetFileSpec(file_spec) + line_entry.SetColumn(10) + + self.assertEqual(line_entry.GetColumn(), 10) + self.assertEqual(line_entry.GetFileSpec().GetFilename(), "test.cpp") + + # Verify address range is still invalid (synthetic). + start_addr = line_entry.GetStartAddress() + self.assertFalse( + start_addr.IsValid(), "Synthetic line entry should not have valid address" + ) + + # Test setting synthetic line entry in symbol context. + sym_ctx = lldb.SBSymbolContext() + sym_ctx.SetLineEntry(line_entry) + + retrieved_line_entry = sym_ctx.GetLineEntry() + self.assertTrue( + retrieved_line_entry.IsValid(), "Retrieved line entry should be valid" + ) + self.assertEqual(retrieved_line_entry.GetLine(), 42) + self.assertEqual(retrieved_line_entry.GetColumn(), 10) + self.assertEqual(retrieved_line_entry.GetFileSpec().GetFilename(), "test.cpp") + + def test_line_entry_validity_without_address(self): + """Test that line entries created via SBAPI are valid without addresses.""" + + line_entry = lldb.SBLineEntry() + + # Initially invalid. + self.assertFalse(line_entry.IsValid()) + + # Still invalid with just a file spec. + file_spec = lldb.SBFileSpec("foo.cpp", True) + line_entry.SetFileSpec(file_spec) + self.assertFalse( + line_entry.IsValid(), "Line entry should be invalid without line number" + ) + + # Valid once line number is set (marks as synthetic). + line_entry.SetLine(100) + self.assertTrue( + line_entry.IsValid(), "Line entry should be valid with line number set" + ) + + # Verify no valid address range. + self.assertFalse(line_entry.GetStartAddress().IsValid()) + self.assertFalse(line_entry.GetEndAddress().IsValid()) + + def test_line_entry_column(self): + """Test setting and getting column information on synthetic line entries.""" + + line_entry = lldb.SBLineEntry() + line_entry.SetLine(50) + + # Default column should be 0. + self.assertEqual(line_entry.GetColumn(), 0) + + # Set column. + line_entry.SetColumn(25) + self.assertEqual(line_entry.GetColumn(), 25) + + # Verify line entry is still valid. + self.assertTrue(line_entry.IsValid()) + + def test_non_synthetic_line_entry_requires_line_number(self): + """Test that non-synthetic line entries with addresses still require a line number to be valid.""" + + # A line entry is always invalid without a line number, regardless of whether it has an address. + line_entry = lldb.SBLineEntry() + self.assertFalse( + line_entry.IsValid(), "Line entry should be invalid without line number" + ) + + # Even with a file spec, it's still invalid. + file_spec = lldb.SBFileSpec("test.cpp", True) + line_entry.SetFileSpec(file_spec) + self.assertFalse( + line_entry.IsValid(), "Line entry should be invalid without line number" + ) + + # Only after setting a line number does it become valid. + line_entry.SetLine(42) + self.assertTrue( + line_entry.IsValid(), "Line entry should be valid with line number" + ) + + def test_symbol_context_with_synthetic_line_entry(self): + """Test that SBSymbolContext correctly stores and retrieves synthetic line entries.""" + + # Create a synthetic line entry. + line_entry = lldb.SBLineEntry() + line_entry.SetLine(123) + line_entry.SetColumn(45) + file_spec = lldb.SBFileSpec("source.cpp", True) + line_entry.SetFileSpec(file_spec) + + # Create symbol context and set line entry. + sym_ctx = lldb.SBSymbolContext() + sym_ctx.SetLineEntry(line_entry) + + # Retrieve and verify. + retrieved = sym_ctx.GetLineEntry() + self.assertTrue(retrieved.IsValid()) + self.assertEqual(retrieved.GetLine(), 123) + self.assertEqual(retrieved.GetColumn(), 45) + self.assertEqual(retrieved.GetFileSpec().GetFilename(), "source.cpp") + + # Verify it's still synthetic (no valid address). + self.assertFalse(retrieved.GetStartAddress().IsValid()) diff --git a/lldb/test/API/python_api/target/TestTargetAPI.py b/lldb/test/API/python_api/target/TestTargetAPI.py index d346563..d3c64d8 100644 --- a/lldb/test/API/python_api/target/TestTargetAPI.py +++ b/lldb/test/API/python_api/target/TestTargetAPI.py @@ -105,6 +105,24 @@ class TargetAPITestCase(TestBase): self.assertIsNotNone(data_section2) self.assertEqual(data_section.name, data_section2.name) + def test_get_arch_name(self): + d = {"EXE": "b.out"} + self.build(dictionary=d) + self.setTearDownCleanup(dictionary=d) + target = self.create_simple_target("b.out") + + arch_name = target.arch_name + self.assertTrue(len(arch_name) > 0, "Got an arch name") + + # Test consistency with triple. + triple = target.triple + self.assertTrue(len(triple) > 0, "Got a triple") + self.assertEqual( + triple.split("-")[0], + arch_name, + "Arch name is equal to the first item of the triple", + ) + def test_get_ABIName(self): d = {"EXE": "b.out"} self.build(dictionary=d) diff --git a/lldb/test/API/python_api/unified_section_list/Makefile b/lldb/test/API/python_api/unified_section_list/Makefile new file mode 100644 index 0000000..431e716 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/Makefile @@ -0,0 +1,5 @@ +CXX_SOURCES := main.cpp + +SPLIT_DEBUG_SYMBOLS := YES + +include Makefile.rules diff --git a/lldb/test/API/python_api/unified_section_list/TestModuleUnifiedSectionList.py b/lldb/test/API/python_api/unified_section_list/TestModuleUnifiedSectionList.py new file mode 100644 index 0000000..93b23d0 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/TestModuleUnifiedSectionList.py @@ -0,0 +1,285 @@ +""" +Test Unified Section List merging. +""" + +import os +import shutil + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.lldbutil import symbol_type_to_str + + +class ModuleUnifiedSectionList(TestBase): + @skipUnlessPlatform(["linux", "freebsd", "netbsd"]) + def test_unified_section_list(self): + self.build() + exe = self.getBuildArtifact("a.out") + debug_info = self.getBuildArtifact("a.out.debug") + new_dir = os.path.join(os.path.dirname(debug_info), "new_dir") + os.mkdir(new_dir) + renamed_debug_info = os.path.join(new_dir, "renamed.debug") + os.rename(debug_info, renamed_debug_info) + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + self.assertGreater(target.GetNumModules(), 0) + + main_exe_module = target.GetModuleAtIndex(0) + eh_frame = main_exe_module.FindSection(".eh_frame") + self.assertTrue(eh_frame.IsValid()) + self.assertGreater(eh_frame.size, 0) + + # Should be stripped in main executable. + debug_info_section = main_exe_module.FindSection(".debug_info") + self.assertFalse(debug_info_section.IsValid()) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {renamed_debug_info}", res) + self.assertTrue(res.Succeeded()) + + # Should be stripped in .debuginfo but be present in main executable. + main_exe_module = target.GetModuleAtIndex(0) + eh_frame = main_exe_module.FindSection(".eh_frame") + self.assertTrue(eh_frame.IsValid()) + self.assertGreater(eh_frame.size, 0) + + # Should be unified and both sections should have contents. + debug_info_section = main_exe_module.FindSection(".debug_info") + self.assertTrue(debug_info_section.IsValid()) + self.assertGreater(debug_info_section.file_size, 0) + + def test_unified_section_list_overwrite_larger_section(self): + """ + Test the merging of an ELF file with another ELF File where all the new sections are bigger, validating we + overwrite .comment from SHT_NOBITS to the new SHT_PROGBITS section and the smaller .text with the larger + .text + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # First we verify out .text section is the expected BEC0FFEE + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEE")) + + # .comment in main.yaml should be SHT_NOBITS, and size 0 + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + self.assertEqual(comment_before_merge.data.size, 0) + + # yamlize the main.largertext.yaml and force symbol loading + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.largertext.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # verify we took the larger .text section + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + self.assertGreater(text_after_merge.data.size, text_before_merge.data.size) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + # in main.largertext.yaml comment is not SHT_NOBITS, and so we should see + # the size > 0 and equal to BAADF00D + comment_after_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_after_merge.IsValid()) + comment_content_after_merge = comment_after_merge.data.ReadRawData( + error, 0, comment_after_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00D")) + + def test_unified_section_list_overwrite_smaller_section(self): + """ + Test the merging of an ELF file with another ELF File where all the existing sections are bigger, validating we don't + overwrite with the SHT_NOBITS for .comment or the smaller .text section. + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.largertext.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # Same as above test but inverse, verify our larger .text section + # is the expected BEC0FFEE palindrome + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + # Comment is SHT_PROGBITS on the larger yaml and should remain + # the same after merge. + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + comment_content = comment_before_merge.data.ReadRawData( + error, 0, comment_before_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content, bytes.fromhex("BAADF00D")) + + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # Verify we didn't replace the sections after merge.s + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + self.assertEqual(text_after_merge.data.size, text_before_merge.data.size) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + comment_after_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_after_merge.IsValid()) + comment_content_after_merge = comment_after_merge.data.ReadRawData( + error, 0, comment_after_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00D")) + + def test_unified_section_list_overwrite_mixed_merge(self): + """ + Test the merging of an ELF file with another ELF File where the lhs has a larger .comment section + and the RHS has a larger .text section. + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.largercomment.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # Verify we have the expected smaller BEC0FFEE + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEE")) + + # Verify we have the larger palindromic comment + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + comment_content = comment_before_merge.data.ReadRawData( + error, 0, comment_before_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content, bytes.fromhex("BAADF00DF00DBAAD")) + + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.largertext.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # Verify we replaced .text + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + # Verify .comment is still the same. + comment_after_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_after_merge.IsValid()) + comment_content_after_merge = comment_after_merge.data.ReadRawData( + error, 0, comment_after_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00DF00DBAAD")) + + def test_unified_section_list_overwrite_equal_size(self): + """ + Test the merging of an ELF file with an ELF file with sections of the same size with different values + .text + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # First we verify out .text section is the expected BEC0FFEE + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEE")) + + # .comment in main.yaml should be SHT_NOBITS, and size 0 + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + self.assertEqual(comment_before_merge.data.size, 0) + + # yamlize the main with the .text reversed from BEC0FFEE + # to EEFF0CEB. We should still keep our .text with BEC0FFEE + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.reversedtext.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # verify .text did not change + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEE")) + + # verify comment did not change + comment_afer_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_afer_merge.IsValid()) + self.assertEqual(comment_afer_merge.data.size, 0) diff --git a/lldb/test/API/python_api/unified_section_list/main.cpp b/lldb/test/API/python_api/unified_section_list/main.cpp new file mode 100644 index 0000000..45fd52e --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.cpp @@ -0,0 +1,3 @@ +#include <stdio.h> + +int main() { printf("Hello World\n"); } diff --git a/lldb/test/API/python_api/unified_section_list/main.largercomment.yaml b/lldb/test/API/python_api/unified_section_list/main.largercomment.yaml new file mode 100644 index 0000000..f786006 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.largercomment.yaml @@ -0,0 +1,46 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEE + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 + Content: BAADF00DF00DBAAD +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/python_api/unified_section_list/main.largertext.yaml b/lldb/test/API/python_api/unified_section_list/main.largertext.yaml new file mode 100644 index 0000000..6450e67 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.largertext.yaml @@ -0,0 +1,46 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEEEEFF0CEB + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 + Content: BAADF00D +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/python_api/unified_section_list/main.reversedtext.yaml b/lldb/test/API/python_api/unified_section_list/main.reversedtext.yaml new file mode 100644 index 0000000..5720666 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.reversedtext.yaml @@ -0,0 +1,45 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEE + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_NOBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/python_api/unified_section_list/main.yaml b/lldb/test/API/python_api/unified_section_list/main.yaml new file mode 100644 index 0000000..5720666 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.yaml @@ -0,0 +1,45 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEE + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_NOBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py index 8bf294b..6904d8e 100644 --- a/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py +++ b/lldb/test/API/symbol_ondemand/breakpoint_source_regex/TestSourceTextRegexBreakpoint.py @@ -20,7 +20,9 @@ class TestSourceTextRegexBreakpoint(TestBase): self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) - lldbutil.run_break_set_by_source_regexp(self, "Set break point at this line.") + lldbutil.run_break_set_by_source_regexp( + self, "Set break point at this line.", extra_options="-f main.cpp" + ) self.runCmd("run", RUN_SUCCEEDED) # The stop reason of the thread should be breakpoint. diff --git a/lldb/test/API/terminal/TestEditline.py b/lldb/test/API/terminal/TestEditline.py index 38f4f34..4696b1e 100644 --- a/lldb/test/API/terminal/TestEditline.py +++ b/lldb/test/API/terminal/TestEditline.py @@ -94,7 +94,7 @@ class EditlineTest(PExpectTest): # after the prompt. self.child.send("foo") # Check that there are no escape codes. - self.child.expect(re.escape("\n(lldb) foo")) + self.child.expect(re.escape("\n\r\x1b[K(lldb) foo")) @skipIfAsan @skipIfEditlineSupportMissing diff --git a/lldb/test/API/test_utils/pdb/Makefile b/lldb/test/API/test_utils/pdb/Makefile new file mode 100644 index 0000000..99998b2 --- /dev/null +++ b/lldb/test/API/test_utils/pdb/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/test_utils/pdb/TestPdb.py b/lldb/test/API/test_utils/pdb/TestPdb.py new file mode 100644 index 0000000..bd3a9d0 --- /dev/null +++ b/lldb/test/API/test_utils/pdb/TestPdb.py @@ -0,0 +1,18 @@ +""" +Test PDB enabled tests +""" + +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestBuildMethod(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + + def test(self): + self.build() + self.assertTrue(self.dbg.CreateTarget(self.getBuildArtifact())) + if self.getDebugInfo() == "pdb": + self.expect( + "target modules dump symfile", patterns=["SymbolFile (native-)?pdb"] + ) diff --git a/lldb/test/API/test_utils/pdb/main.cpp b/lldb/test/API/test_utils/pdb/main.cpp new file mode 100644 index 0000000..76e8197 --- /dev/null +++ b/lldb/test/API/test_utils/pdb/main.cpp @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py index 2db00a5..d628739 100644 --- a/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py +++ b/lldb/test/API/tools/lldb-dap/attach/TestDAP_attach.py @@ -75,3 +75,38 @@ class TestDAP_attach(lldbdap_testcase.DAPTestCaseBase): self.spawn_thread.start() self.attach(program=program, waitFor=True) self.continue_and_verify_pid() + + def test_attach_with_missing_debuggerId_or_targetId(self): + """ + Test that attaching with only one of debuggerId/targetId specified + fails with the expected error message. + """ + self.build_and_create_debug_adapter() + + # Test with only targetId specified (no debuggerId) + resp = self.attach(targetId=99999, expectFailure=True) + self.assertFalse(resp["success"]) + self.assertIn( + "Both debuggerId and targetId must be specified together", + resp["body"]["error"]["format"], + ) + + def test_attach_with_invalid_debuggerId_and_targetId(self): + """ + Test that attaching with both debuggerId and targetId specified but + invalid fails with an appropriate error message. + """ + self.build_and_create_debug_adapter() + + # Attach with both debuggerId=9999 and targetId=99999 (both invalid). + # Since debugger ID 9999 likely doesn't exist in the global registry, + # we expect a validation error. + resp = self.attach(debuggerId=9999, targetId=99999, expectFailure=True) + self.assertFalse(resp["success"]) + error_msg = resp["body"]["error"]["format"] + # Either error is acceptable - both indicate the debugger reuse + # validation is working correctly + self.assertTrue( + "Unable to find existing debugger" in error_msg + or f"Expected debugger/target not found error, got: {error_msg}" + ) diff --git a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py index a542a31..df029ca 100644 --- a/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/databreakpoint/TestDAP_setDataBreakpoints.py @@ -39,18 +39,21 @@ class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase): {"dataId": response_x["body"]["dataId"], "accessType": "write"}, ] set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints) - self.assertEqual( - set_response["body"]["breakpoints"], - [{"verified": False}, {"verified": True}, {"verified": True}], - ) + breakpoints = set_response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 3) + self.assertFalse(breakpoints[0]["verified"]) + self.assertTrue(breakpoints[1]["verified"]) + self.assertTrue(breakpoints[2]["verified"]) - self.continue_to_next_stop() + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[2]["id"]]) x_val = self.dap_server.get_local_variable_value("x") i_val = self.dap_server.get_local_variable_value("i") self.assertEqual(x_val, "2") self.assertEqual(i_val, "1") - self.continue_to_next_stop() + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[1]["id"]]) arr_2 = self.dap_server.get_local_variable_child("arr", "[2]") i_val = self.dap_server.get_local_variable_value("i") self.assertEqual(arr_2["value"], "42") @@ -79,18 +82,20 @@ class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase): {"dataId": response_arr_2["body"]["dataId"], "accessType": "write"}, ] set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints) - self.assertEqual( - set_response["body"]["breakpoints"], - [{"verified": True}, {"verified": True}], - ) + breakpoints = set_response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 2) + self.assertTrue(breakpoints[0]["verified"]) + self.assertTrue(breakpoints[1]["verified"]) - self.continue_to_next_stop() + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[0]["id"]]) x_val = self.dap_server.get_local_variable_value("x") i_val = self.dap_server.get_local_variable_value("i") self.assertEqual(x_val, "2") self.assertEqual(i_val, "1") - self.continue_to_next_stop() + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[1]["id"]]) arr_2 = self.dap_server.get_local_variable_child("arr", "[2]") i_val = self.dap_server.get_local_variable_value("i") self.assertEqual(arr_2["value"], "42") @@ -123,18 +128,20 @@ class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase): {"dataId": response_arr_2["body"]["dataId"], "accessType": "write"}, ] set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints) - self.assertEqual( - set_response["body"]["breakpoints"], - [{"verified": True}, {"verified": True}], - ) + breakpoints = set_response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 2) + self.assertTrue(breakpoints[0]["verified"]) + self.assertTrue(breakpoints[1]["verified"]) - self.continue_to_next_stop() + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[0]["id"]]) x_val = self.dap_server.get_local_variable_value("x") i_val = self.dap_server.get_local_variable_value("i") self.assertEqual(x_val, "2") self.assertEqual(i_val, "1") - self.continue_to_next_stop() + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[1]["id"]]) arr_2 = self.dap_server.get_local_variable_child("arr", "[2]") i_val = self.dap_server.get_local_variable_value("i") self.assertEqual(arr_2["value"], "42") @@ -153,8 +160,11 @@ class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase): } ] set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints) - self.assertEqual(set_response["body"]["breakpoints"], [{"verified": True}]) - self.continue_to_next_stop() + breakpoints = set_response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 1) + self.assertTrue(breakpoints[0]["verified"]) + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[0]["id"]]) x_val = self.dap_server.get_local_variable_value("x") self.assertEqual(x_val, "3") @@ -167,7 +177,64 @@ class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase): } ] set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints) - self.assertEqual(set_response["body"]["breakpoints"], [{"verified": True}]) - self.continue_to_next_stop() + breakpoints = set_response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 1) + self.assertTrue(breakpoints[0]["verified"]) + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[0]["id"]]) x_val = self.dap_server.get_local_variable_value("x") self.assertEqual(x_val, "10") + + @skipIfWindows + def test_bytes(self): + """Tests setting data breakpoints on memory range.""" + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.cpp" + first_loop_break_line = line_number(source, "// first loop breakpoint") + self.set_source_breakpoints(source, [first_loop_break_line]) + self.continue_to_next_stop() + # Test write watchpoints on x, arr[2] + x = self.dap_server.get_local_variable("x") + response_x = self.dap_server.request_dataBreakpointInfo( + 0, x["memoryReference"], 4 + ) + arr_2 = self.dap_server.get_local_variable_child("arr", "[2]") + response_arr_2 = self.dap_server.request_dataBreakpointInfo( + 0, arr_2["memoryReference"], 4 + ) + + # Test response from dataBreakpointInfo request. + self.assertEqual( + response_x["body"]["dataId"].split("/"), [x["memoryReference"][2:], "4"] + ) + self.assertEqual(response_x["body"]["accessTypes"], self.accessTypes) + self.assertEqual( + response_arr_2["body"]["dataId"].split("/"), + [arr_2["memoryReference"][2:], "4"], + ) + self.assertEqual(response_arr_2["body"]["accessTypes"], self.accessTypes) + dataBreakpoints = [ + {"dataId": response_x["body"]["dataId"], "accessType": "write"}, + {"dataId": response_arr_2["body"]["dataId"], "accessType": "write"}, + ] + set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints) + breakpoints = set_response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 2) + self.assertTrue(breakpoints[0]["verified"]) + self.assertTrue(breakpoints[1]["verified"]) + + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[0]["id"]]) + x_val = self.dap_server.get_local_variable_value("x") + i_val = self.dap_server.get_local_variable_value("i") + self.assertEqual(x_val, "2") + self.assertEqual(i_val, "1") + + self.dap_server.request_continue() + self.verify_breakpoint_hit([breakpoints[1]["id"]]) + arr_2 = self.dap_server.get_local_variable_child("arr", "[2]") + i_val = self.dap_server.get_local_variable_value("i") + self.assertEqual(arr_2["value"], "42") + self.assertEqual(i_val, "2") + self.dap_server.request_setDataBreakpoint([]) diff --git a/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py b/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py index 09e3f62..19f88d8 100644 --- a/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py +++ b/lldb/test/API/tools/lldb-dap/disconnect/TestDAP_disconnect.py @@ -3,17 +3,15 @@ Test lldb-dap disconnect request """ -import dap_server from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil import lldbdap_testcase -import subprocess import time import os -class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase): +class TestDAP_disconnect(lldbdap_testcase.DAPTestCaseBase): source = "main.cpp" def disconnect_and_assert_no_output_printed(self): @@ -67,10 +65,11 @@ class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase): lambda: self.run_platform_command("rm %s" % (sync_file_path)) ) - self.process = subprocess.Popen([program, sync_file_path]) + proc = self.spawnSubprocess(program, [sync_file_path]) lldbutil.wait_for_file_on_target(self, sync_file_path) - self.attach(pid=self.process.pid, disconnectAutomatically=False) + self.attach(pid=proc.pid, disconnectAutomatically=False, stopOnEntry=True) + self.continue_to_next_stop() response = self.dap_server.request_evaluate("wait_for_attach = false;") self.assertTrue(response["success"]) diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py index 20a75f4..9557378 100644 --- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py +++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py @@ -1,5 +1,5 @@ """ -Test lldb-dap completions request +Test lldb-dap evaluate request """ import re @@ -7,16 +7,70 @@ import re import lldbdap_testcase from lldbsuite.test.decorators import skipIfWindows from lldbsuite.test.lldbtest import line_number +from typing import TypedDict, Optional + + +class EvaluateResponseBody(TypedDict, total=False): + result: str + variablesReference: int + type: Optional[str] + memoryReference: Optional[str] + valueLocationReference: Optional[int] class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): - def assertEvaluate(self, expression, regex): + def assertEvaluate( + self, + expression, + result: str, + want_type="", + want_varref=False, + want_memref=True, + want_locref=False, + is_hex=None, + ): + resp = self.dap_server.request_evaluate( + expression, context=self.context, is_hex=is_hex + ) + self.assertTrue( + resp["success"], f"Failed to evaluate expression {expression!r}" + ) + body: EvaluateResponseBody = resp["body"] self.assertRegex( - self.dap_server.request_evaluate(expression, context=self.context)["body"][ - "result" - ], - regex, + body["result"], + result, + f"Unexpected 'result' for expression {expression!r} in response body {body}", ) + if want_varref: + self.assertNotEqual( + body["variablesReference"], + 0, + f"Unexpected 'variablesReference' for expression {expression!r} in response body {body}", + ) + else: + self.assertEqual( + body["variablesReference"], + 0, + f"Unexpected 'variablesReference' for expression {expression!r} in response body {body}", + ) + if want_type: + self.assertEqual( + body["type"], + want_type, + f"Unexpected 'type' for expression {expression!r} in response body {body}", + ) + if want_memref: + self.assertIn( + "memoryReference", + body, + f"Unexpected 'memoryReference' for expression {expression!r} in response body {body}", + ) + if want_locref: + self.assertIn( + "valueLocationReference", + body, + f"Unexpected 'valueLocationReference' for expression {expression!r} in response body {body}", + ) def assertEvaluateFailure(self, expression): self.assertNotIn( @@ -71,29 +125,45 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): self.continue_to_breakpoint(breakpoint_1) # Expressions at breakpoint 1, which is in main - self.assertEvaluate("var1", "20") + self.assertEvaluate("var1", "20", want_type="int") # Empty expression should equate to the previous expression. if context == "repl": self.assertEvaluate("", "20") else: self.assertEvaluateFailure("") - self.assertEvaluate("var2", "21") + self.assertEvaluate("var2", "21", want_type="int") if context == "repl": - self.assertEvaluate("", "21") - self.assertEvaluate("", "21") - self.assertEvaluate("static_int", "42") - self.assertEvaluate("non_static_int", "43") - self.assertEvaluate("struct1.foo", "15") - self.assertEvaluate("struct2->foo", "16") + self.assertEvaluate("", "21", want_type="int") + self.assertEvaluate("", "21", want_type="int") + self.assertEvaluate("static_int", "0x0000002a", want_type="int", is_hex=True) + self.assertEvaluate( + "non_static_int", "0x0000002b", want_type="int", is_hex=True + ) + self.assertEvaluate("struct1.foo", "0x0000000f", want_type="int", is_hex=True) + self.assertEvaluate("struct2->foo", "0x00000010", want_type="int", is_hex=True) + self.assertEvaluate("static_int", "42", want_type="int") + self.assertEvaluate("non_static_int", "43", want_type="int") + self.assertEvaluate("struct1.foo", "15", want_type="int") + self.assertEvaluate("struct2->foo", "16", want_type="int") if self.isResultExpandedDescription(): self.assertEvaluate( "struct1", r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)", + want_type="my_struct", + want_varref=True, + ) + self.assertEvaluate( + "struct2", + r"\(my_struct \*\) (struct2|\$\d+) = 0x.*", + want_type="my_struct *", + want_varref=True, ) - self.assertEvaluate("struct2", r"\(my_struct \*\) (struct2|\$\d+) = 0x.*") self.assertEvaluate( - "struct3", r"\(my_struct \*\) (struct3|\$\d+) = nullptr" + "struct3", + r"\(my_struct \*\) (struct3|\$\d+) = nullptr", + want_type="my_struct *", + want_varref=True, ) else: self.assertEvaluate( @@ -103,16 +173,22 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): if enableAutoVariableSummaries else "my_struct @ 0x" ), + want_varref=True, ) self.assertEvaluate( - "struct2", "0x.* {foo:16}" if enableAutoVariableSummaries else "0x.*" + "struct2", + "0x.* {foo:16}" if enableAutoVariableSummaries else "0x.*", + want_varref=True, + want_type="my_struct *", + ) + self.assertEvaluate( + "struct3", "0x.*0", want_varref=True, want_type="my_struct *" ) - self.assertEvaluate("struct3", "0x.*0") if context == "repl": # In the repl context expressions may be interpreted as lldb # commands since no variables have the same name as the command. - self.assertEvaluate("list", r".*") + self.assertEvaluate("list", r".*", want_memref=False) else: self.assertEvaluateFailure("list") # local variable of a_function @@ -121,10 +197,26 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): self.assertEvaluateFailure("foo") # member of my_struct if self.isExpressionParsedExpected(): - self.assertEvaluate("a_function", "0x.*a.out`a_function.*") - self.assertEvaluate("a_function(1)", "1") - self.assertEvaluate("var2 + struct1.foo", "36") - self.assertEvaluate("foo_func", "0x.*a.out`foo_func.*") + self.assertEvaluate( + "a_function", + "0x.*a.out`a_function.*", + want_type="int (*)(int)", + want_varref=True, + want_memref=False, + want_locref=True, + ) + self.assertEvaluate( + "a_function(1)", "1", want_memref=False, want_type="int" + ) + self.assertEvaluate("var2 + struct1.foo", "36", want_memref=False) + self.assertEvaluate( + "foo_func", + "0x.*a.out`foo_func.*", + want_type="int (*)()", + want_varref=True, + want_memref=False, + want_locref=True, + ) self.assertEvaluate("foo_var", "44") else: self.assertEvaluateFailure("a_function") @@ -145,6 +237,8 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): self.assertEvaluate( "struct1", r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)", + want_type="my_struct", + want_varref=True, ) else: self.assertEvaluate( @@ -154,15 +248,26 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): if enableAutoVariableSummaries else "my_struct @ 0x" ), + want_type="my_struct", + want_varref=True, ) self.assertEvaluate("struct1.foo", "15") self.assertEvaluate("struct2->foo", "16") if self.isExpressionParsedExpected(): - self.assertEvaluate("a_function", "0x.*a.out`a_function.*") - self.assertEvaluate("a_function(1)", "1") - self.assertEvaluate("var2 + struct1.foo", "17") - self.assertEvaluate("foo_func", "0x.*a.out`foo_func.*") + self.assertEvaluate( + "a_function", + "0x.*a.out`a_function.*", + want_type="int (*)(int)", + want_varref=True, + want_memref=False, + want_locref=True, + ) + self.assertEvaluate("a_function(1)", "1", want_memref=False) + self.assertEvaluate("var2 + struct1.foo", "17", want_memref=False) + self.assertEvaluate( + "foo_func", "0x.*a.out`foo_func.*", want_varref=True, want_memref=False + ) self.assertEvaluate("foo_var", "44") else: self.assertEvaluateFailure("a_function") @@ -185,10 +290,18 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): self.assertEvaluateFailure("var2 + struct1.foo") if self.isExpressionParsedExpected(): - self.assertEvaluate("a_function", "0x.*a.out`a_function.*") - self.assertEvaluate("a_function(1)", "1") - self.assertEvaluate("list + 1", "43") - self.assertEvaluate("foo_func", "0x.*a.out`foo_func.*") + self.assertEvaluate( + "a_function", + "0x.*a.out`a_function.*", + want_varref=True, + want_memref=False, + want_locref=True, + ) + self.assertEvaluate("a_function(1)", "1", want_memref=False) + self.assertEvaluate("list + 1", "43", want_memref=False) + self.assertEvaluate( + "foo_func", "0x.*a.out`foo_func.*", want_varref=True, want_memref=False + ) self.assertEvaluate("foo_var", "44") else: self.assertEvaluateFailure("a_function") @@ -199,26 +312,28 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): # Now we check that values are updated after stepping self.continue_to_breakpoint(breakpoint_4) - self.assertEvaluate("my_vec", "size=2") + self.assertEvaluate("my_vec", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_5) - self.assertEvaluate("my_vec", "size=3") + self.assertEvaluate("my_vec", "size=3", want_varref=True) - self.assertEvaluate("my_map", "size=2") + self.assertEvaluate("my_map", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_6) - self.assertEvaluate("my_map", "size=3") + self.assertEvaluate("my_map", "size=3", want_varref=True) - self.assertEvaluate("my_bool_vec", "size=1") + self.assertEvaluate("my_bool_vec", "size=1", want_varref=True) self.continue_to_breakpoint(breakpoint_7) - self.assertEvaluate("my_bool_vec", "size=2") + self.assertEvaluate("my_bool_vec", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_8) # Test memory read, especially with 'empty' repeat commands. if context == "repl": - self.assertEvaluate("memory read -c 1 &my_ints", ".* 05 .*\n") - self.assertEvaluate("", ".* 0a .*\n") - self.assertEvaluate("", ".* 0f .*\n") - self.assertEvaluate("", ".* 14 .*\n") - self.assertEvaluate("", ".* 19 .*\n") + self.assertEvaluate( + "memory read -c 1 &my_ints", ".* 05 .*\n", want_memref=False + ) + self.assertEvaluate("", ".* 0a .*\n", want_memref=False) + self.assertEvaluate("", ".* 0f .*\n", want_memref=False) + self.assertEvaluate("", ".* 14 .*\n", want_memref=False) + self.assertEvaluate("", ".* 19 .*\n", want_memref=False) self.continue_to_exit() @@ -245,4 +360,6 @@ class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): @skipIfWindows def test_variable_evaluate_expressions(self): # Tests expression evaluations that are triggered in the variable explorer - self.run_test_evaluate_expressions("variable", enableAutoVariableSummaries=True) + self.run_test_evaluate_expressions( + "variables", enableAutoVariableSummaries=True + ) diff --git a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py index 12b321c..3c53cf2 100644 --- a/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py +++ b/lldb/test/API/tools/lldb-dap/server/TestDAP_server.py @@ -37,7 +37,7 @@ class TestDAP_server(lldbdap_testcase.DAPTestCaseBase): def run_debug_session(self, connection, name, sleep_seconds_in_middle=None): self.dap_server = dap_server.DebugAdapterServer( - connection=connection, + connection=connection, spawn_helper=self.spawnSubprocess ) program = self.getBuildArtifact("a.out") source = "main.c" @@ -94,6 +94,7 @@ class TestDAP_server(lldbdap_testcase.DAPTestCaseBase): (process, connection) = self.start_server(connection="listen://localhost:0") self.dap_server = dap_server.DebugAdapterServer( connection=connection, + spawn_helper=self.spawnSubprocess, ) program = self.getBuildArtifact("a.out") source = "main.c" diff --git a/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile new file mode 100644 index 0000000..1049594 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py new file mode 100644 index 0000000..4ddf924 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/TestDAP_stackTraceCompilerGeneratedCode.py @@ -0,0 +1,66 @@ +""" +Test lldb-dap stackTrace request for compiler generated code +""" + +import os + +import lldbdap_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestDAP_stackTraceCompilerGeneratedCode(lldbdap_testcase.DAPTestCaseBase): + def test_non_leaf_frame_compiler_generate_code(self): + """ + Test that non-leaf frames with compiler-generated code are properly resolved. + + This test verifies that LLDB correctly handles stack frames containing + compiler-generated code (code without valid source location information). + When a non-leaf frame contains compiler-generated code immediately after a + call instruction, LLDB should resolve the frame's source location to the + call instruction's line, rather than to the compiler-generated code that + follows, which lacks proper symbolication information. + """ + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + source = "main.c" + + # Set breakpoint inside bar() function + lines = [line_number(source, "// breakpoint here")] + breakpoint_ids = self.set_source_breakpoints(source, lines) + self.assertEqual( + len(breakpoint_ids), len(lines), "expect correct number of breakpoints" + ) + + self.continue_to_breakpoints(breakpoint_ids) + + # Get the stack frames: [0] = bar(), [1] = foo(), [2] = main() + stack_frames = self.get_stackFrames() + self.assertGreater(len(stack_frames), 2, "Expected more than 2 stack frames") + + # Examine the foo() frame (stack_frames[1]) + # This is the critical frame containing compiler-generated code + foo_frame = stack_frames[1] + + # Verify that the frame's line number points to the bar() call, + # not to the compiler-generated code after it + foo_call_bar_source_line = foo_frame.get("line") + self.assertEqual( + foo_call_bar_source_line, + line_number(source, "foo call bar"), + "Expected foo call bar to be the source line of the frame", + ) + + # Verify the source file name is correctly resolved + foo_source_name = foo_frame.get("source", {}).get("name") + self.assertEqual( + foo_source_name, "main.c", "Expected foo source name to be main.c" + ) + + # When lldb fails to symbolicate a frame it will emit a fake assembly + # source with path of format <module>`<symbol> or <module>`<address> with + # sourceReference to retrieve disassembly source file. + # Verify that this didn't happen - the path should be a real file path. + foo_path = foo_frame.get("source", {}).get("path") + self.assertNotIn("`", foo_path, "Expected foo source path to not contain `") + self.continue_to_exit() diff --git a/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c new file mode 100644 index 0000000..dd3fcc2 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/stackTraceCompilerGeneratedCode/main.c @@ -0,0 +1,19 @@ +void bar() { + int val = 32; // breakpoint here +} + +void at_line_zero() {} + +int foo(); + +int main(int argc, char const *argv[]) { + foo(); + return 0; +} + +int foo() { + bar(); // foo call bar +#line 0 "test.cpp" + at_line_zero(); + return 0; +} diff --git a/lldb/test/API/tools/lldb-dap/startDebugging/TestDAP_startDebugging.py b/lldb/test/API/tools/lldb-dap/startDebugging/TestDAP_startDebugging.py index b487257..7e60dd2 100644 --- a/lldb/test/API/tools/lldb-dap/startDebugging/TestDAP_startDebugging.py +++ b/lldb/test/API/tools/lldb-dap/startDebugging/TestDAP_startDebugging.py @@ -36,3 +36,54 @@ class TestDAP_startDebugging(lldbdap_testcase.DAPTestCaseBase): request = self.dap_server.reverse_requests[0] self.assertEqual(request["arguments"]["configuration"]["pid"], 321) self.assertEqual(request["arguments"]["request"], "attach") + + def test_startDebugging_debugger_reuse(self): + """ + Tests that debugger and target IDs can be passed through startDebugging + for debugger reuse. This verifies the infrastructure for child DAP + sessions to reuse the parent's debugger and attach to an existing target. + """ + program = self.getBuildArtifact("a.out") + source = "main.c" + self.build_and_launch(program) + + breakpoint_line = line_number(source, "// breakpoint") + self.set_source_breakpoints(source, [breakpoint_line]) + self.continue_to_next_stop() + + # Use mock IDs to test the infrastructure + # In a real scenario, these would come from the parent session + test_debugger_id = 1 + test_target_id = 100 + + # Send a startDebugging request with debuggerId and targetId + # This simulates creating a child DAP session that reuses the debugger + self.dap_server.request_evaluate( + f'`lldb-dap start-debugging attach \'{{"debuggerId":{test_debugger_id},"targetId":{test_target_id}}}\'', + context="repl", + ) + + self.continue_to_exit() + + # Verify the reverse request was sent with the correct IDs + self.assertEqual( + len(self.dap_server.reverse_requests), + 1, + "Should have received one startDebugging reverse request", + ) + + request = self.dap_server.reverse_requests[0] + self.assertEqual(request["command"], "startDebugging") + self.assertEqual(request["arguments"]["request"], "attach") + + config = request["arguments"]["configuration"] + self.assertEqual( + config["debuggerId"], + test_debugger_id, + "Reverse request should include debugger ID", + ) + self.assertEqual( + config["targetId"], + test_target_id, + "Reverse request should include target ID", + ) diff --git a/lldb/test/CMakeLists.txt b/lldb/test/CMakeLists.txt index 513d1ec..818dff5 100644 --- a/lldb/test/CMakeLists.txt +++ b/lldb/test/CMakeLists.txt @@ -202,7 +202,7 @@ if(TARGET clang) else() # We require libcxx for the test suite, so if we aren't building it, # provide a helpful error about how to resolve the situation. - if(NOT LLDB_HAS_LIBCXX) + if(LLDB_ENFORCE_STRICT_TEST_REQUIREMENTS AND NOT LLDB_HAS_LIBCXX) message(SEND_ERROR "LLDB test suite requires libc++, but it is currently disabled. " "Please add `libcxx` to `LLVM_ENABLE_RUNTIMES` or disable tests via " diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c new file mode 100644 index 0000000..698cf27 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c @@ -0,0 +1,8 @@ +#include <bounds_safety_soft_traps.h> +#include <stdio.h> + +int main(void) { + __bounds_safety_soft_trap_s(0); + printf("Execution continued\n"); + return 0; +} diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c new file mode 100644 index 0000000..3e68045 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c @@ -0,0 +1,15 @@ +#include <bounds_safety_soft_traps.h> +#include <ptrcheck.h> +#include <stdio.h> + +#if __CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION > 0 +#error API version changed +#endif + +#if __has_ptrcheck +#error Do not compile the runtime with -fbounds-safety enabled due to potential for infinite recursion +#endif + +void __bounds_safety_soft_trap_s(const char *reason) { printf("BoundsSafety check FAILED: message:\"%s\"\n", reason ? reason : ""); } + +void __bounds_safety_soft_trap(void) { printf("BoundsSafety check FAILED\n"); } diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c new file mode 100644 index 0000000..265e57e --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c @@ -0,0 +1,12 @@ +#include <stdio.h> + +int bad_read(int index) { + int array[] = {0, 1, 2}; + return array[index]; +} + +int main(int argc, char **argv) { + bad_read(10); + printf("Execution continued\n"); + return 0; +} diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTrapsMissingReason.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTrapsMissingReason.c new file mode 100644 index 0000000..db65c9d --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTrapsMissingReason.c @@ -0,0 +1,20 @@ +#include <ptrcheck.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int bad_call(int *__counted_by(count) ptr, int count) {} + +int main(int argc, char **argv) { + const int num_bytes = sizeof(int) * 2; + int *array = (int *)malloc(num_bytes); + memset(array, 0, num_bytes); + + // The count argument is too large and will cause a trap. + // This code pattern is currently missing a trap reason (rdar://100346924) and + // so we can use it to test how `InstrumentationRuntimeBoundsSafety` handles + // this. + bad_call(array, 3); + printf("Execution continued\n"); + return 0; +} diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test new file mode 100644 index 0000000..7e93f14 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test @@ -0,0 +1,31 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed: indexing above upper bound in 'array[index]'{{$}} + +# Check that the `bad_read` frame is selected +bt +# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' +# CHECK-NEXT: * frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_missing_reason.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_missing_reason.test new file mode 100644 index 0000000..a56144a --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_missing_reason.test @@ -0,0 +1,34 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTrapsMissingReason.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out 2> %t.warnings | FileCheck %s +# Warnings are checked separately because their order in the output is not guaranteed. +# RUN: FileCheck --input-file=%t.warnings --check-prefix=WARN %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed{{$}} +# WARN: warning: specific BoundsSafety trap reason is not available because the compiler omitted it from the debug info + +# Check that the `bad_read` frame is selected +bt +# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$ +# CHECK-NEXT: * frame #{{.*}}`main({{.+}}) at boundsSafetySoftTrapsMissingReason.c:17 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test new file mode 100644 index 0000000..dfff65d --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test @@ -0,0 +1,33 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out 2> %t.warnings | FileCheck %s +# Warnings are checked separately because their order in the output is not guaranteed. +# RUN: FileCheck --input-file=%t.warnings --check-prefix=WARN %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed{{$}} +# WARN: warning: specific BoundsSafety trap reason is not available because debug info is missing on the caller of '__bounds_safety_soft_trap' + +# Check that the `__bounds_safety_soft_trap` frame is selected +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK-NEXT: frame #{{.*}}`bad_read + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test new file mode 100644 index 0000000..4acc927 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test @@ -0,0 +1,30 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# Run without the plugin. A user might want to do this so they can set their +# own custom breakpoint with custom stopping behavior (e.g. stop after n hits). +plugin disable instrumentation-runtime.BoundsSafety +# CHECK: [-] BoundsSafety + +b __bounds_safety_soft_trap +run + +# CHECK: * thread #{{.*}} stop reason = breakpoint 1.1 + +# Check that reason for bounds check failing can be seen in the stacktrace +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' +# CHECK-NEXT: frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test new file mode 100644 index 0000000..2fdae6d --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test @@ -0,0 +1,31 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed: indexing above upper bound in 'array[index]'{{$}} + +# Check that the `bad_read` frame is selected +bt +# CHECK: frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' +# CHECK-NEXT: * frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_missing_reason.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_missing_reason.test new file mode 100644 index 0000000..68d10c0 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_missing_reason.test @@ -0,0 +1,34 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -g -O0 %S/Inputs/boundsSafetySoftTrapsMissingReason.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out 2> %t.warnings | FileCheck %s +# Warnings are checked separately because their order in the output is not guaranteed. +# RUN: FileCheck --input-file=%t.warnings --check-prefix=WARN %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed{{$}} +# WARN: warning: specific BoundsSafety trap reason is not available because the compiler omitted it from the debug info + +# Check that the `bad_read` frame is selected +bt +# CHECK: frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$ +# CHECK-NEXT: * frame #{{.*}}`main({{.+}}) at boundsSafetySoftTrapsMissingReason.c:17 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test new file mode 100644 index 0000000..afe6098 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test @@ -0,0 +1,30 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed: indexing above upper bound in 'array[index]'{{$}} + +# Check that the `__bounds_safety_soft_trap_s` frame is selected +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`bad_read + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test new file mode 100644 index 0000000..6e0bf32 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test @@ -0,0 +1,36 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockCallSoftTrapRuntime.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out 2> %t.warnings | FileCheck %s +# Warnings are checked separately because their order in the output is not guaranteed. +# RUN: FileCheck --input-file=%t.warnings --check-prefix=WARN %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +# Emit logging so that the code has test coverage +log enable lldb instrumentation-runtime + +run + +# This exists to check that the instrumentation correctly handles +# `__bounds_safety_soft_trap_s()` being called with a nullptr argument. + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed{{$}} +# WARN: warning: specific BoundsSafety trap reason cannot be inferred because the compiler omitted the reason + +# Check that the `__bounds_safety_soft_trap_s` frame is selected +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`main + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test new file mode 100644 index 0000000..ca2ca98 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test @@ -0,0 +1,30 @@ +# UNSUPPORTED: system-windows +# REQUIRES: clang-bounds-safety +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# Run without the plugin. A user might want to do this so they can set their +# own custom breakpoint with custom stopping behavior (e.g. stop after n hits). +plugin disable instrumentation-runtime.BoundsSafety +# CHECK: [-] BoundsSafety + +b __bounds_safety_soft_trap_s +run + +# CHECK: * thread #{{.*}} stop reason = breakpoint 1.1 + +# Check that reason for bounds check failing can be seen in the stacktrace +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' +# CHECK-NEXT: frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED: message:"indexing above upper bound in 'array[index]'" +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/Breakpoint/condition-lang.test b/lldb/test/Shell/Breakpoint/condition-lang.test index 9a64bf4f..469c903 100644 --- a/lldb/test/Shell/Breakpoint/condition-lang.test +++ b/lldb/test/Shell/Breakpoint/condition-lang.test @@ -1,5 +1,5 @@ RUN: not %lldb -b -o 'break set -n foo -c bar -Y bogus' 2>&1 | FileCheck %s --check-prefix INVALID -INVALID: error: Invalid value ('bogus') for -Y (condition-language): invalid language +INVALID: error: invalid value ('bogus') for -Y (condition-language): invalid language RUN: not %lldb -b -o 'break set -n foo -c bar -Y python' 2>&1 | FileCheck %s --check-prefix NOEXPRSUPPORT -NOEXPRSUPPORT: error: Invalid value ('python') for -Y (condition-language): no expression support for language +NOEXPRSUPPORT: error: invalid value ('python') for -Y (condition-language): no expression support for language diff --git a/lldb/test/Shell/Commands/Inputs/sigchld.c b/lldb/test/Shell/Commands/Inputs/sigchld.c index ba8c5ef..0121e70 100644 --- a/lldb/test/Shell/Commands/Inputs/sigchld.c +++ b/lldb/test/Shell/Commands/Inputs/sigchld.c @@ -1,3 +1,7 @@ +#if defined(__linux__) +#define _XOPEN_SOURCE 500 /* for CLD_EXITED */ +#endif + #include <assert.h> #include <signal.h> #include <stdio.h> diff --git a/lldb/test/Shell/Commands/command-disassemble-aarch64-extensions.s b/lldb/test/Shell/Commands/command-disassemble-aarch64-extensions.s index 685d0a8..4e28133 100644 --- a/lldb/test/Shell/Commands/command-disassemble-aarch64-extensions.s +++ b/lldb/test/Shell/Commands/command-disassemble-aarch64-extensions.s @@ -61,7 +61,6 @@ fn: sm4e z0.s, z0.s, z0.s // AEK_SVE2SM4 addqv v0.8h, p0, z0.h // AEK_SVE2P1 / AEK_SME2P1 rcwswp x0, x1, [x2] // AEK_THE - tcommit // AEK_TME lbl: .fn_end: .size fn, .fn_end-fn @@ -81,7 +80,7 @@ lbl: # CHECK-NEXT: fcvt d0, s0 # CHECK-NEXT: fabs h1, h2 # CHECK-NEXT: fmlal v0.2s, v1.2h, v2.2h -# CHECK-NEXT: bc.eq 0xc8 +# CHECK-NEXT: bc.eq 0xc4 # CHECK-NEXT: smmla v1.4s, v16.16b, v31.16b # CHECK-NEXT: ld64b x0, [x13] # CHECK-NEXT: ldaddab w0, w0, [sp] @@ -116,4 +115,3 @@ lbl: # CHECK-NEXT: sm4e z0.s, z0.s, z0.s # CHECK-NEXT: addqv v0.8h, p0, z0.h # CHECK-NEXT: rcwswp x0, x1, [x2] -# CHECK-NEXT: tcommit diff --git a/lldb/test/Shell/Commands/command-list-reach-beginning-of-file.test b/lldb/test/Shell/Commands/command-list-reach-beginning-of-file.test index fa4a93e..9987efe 100644 --- a/lldb/test/Shell/Commands/command-list-reach-beginning-of-file.test +++ b/lldb/test/Shell/Commands/command-list-reach-beginning-of-file.test @@ -4,7 +4,7 @@ # RUN: %lldb %t.out -b -s %s 2>&1 | FileCheck %s list -# CHECK: note: No source available +# CHECK: note: No source available b main # CHECK: Breakpoint 1: @@ -18,7 +18,7 @@ list list - # CHECK: int main() -list -10 +list -13 # CHECK: #include <assert.h> list - diff --git a/lldb/test/Shell/Commands/command-version.test b/lldb/test/Shell/Commands/command-version.test new file mode 100644 index 0000000..b13ab96 --- /dev/null +++ b/lldb/test/Shell/Commands/command-version.test @@ -0,0 +1,6 @@ +RUN: %lldb -b -o 'version -v' | FileCheck %s + +CHECK: lldb version +CHECK: xml: {{yes|no}} +CHECK: python: {{yes|no}} +CHECK: targets: [{{.*}}] diff --git a/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test b/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test index 445f8d1..968a4d6 100644 --- a/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test +++ b/lldb/test/Shell/Commands/command-wrong-subcommand-error-msg.test @@ -4,5 +4,5 @@ # RUN: not %lldb -b -o 'breakpoint foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix BP-MSG # RUN: not %lldb -b -o 'watchpoint set foo' %t.out -o exit 2>&1 | FileCheck %s --check-prefix WP-MSG # CHECK: at main.c:2:21 -# BP-MSG: "foo" is not a valid subcommand of "breakpoint". Valid subcommands are: clear, command, delete, disable, enable, and others. Use "help breakpoint" to find out more. -# WP-MSG: "foo" is not a valid subcommand of "watchpoint set". Valid subcommands are: expression, variable. Use "help watchpoint set" to find out more.
\ No newline at end of file +# BP-MSG: error: "foo" is not a valid subcommand of "breakpoint". Valid subcommands are: add, clear, command, delete, disable, and others. Use "help breakpoint" to find out more. +# WP-MSG: error: "foo" is not a valid subcommand of "watchpoint set". Valid subcommands are: expression, variable. Use "help watchpoint set" to find out more. diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_CompileFailure.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_CompileFailure.test new file mode 100644 index 0000000..49ee277 --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_CompileFailure.test @@ -0,0 +1,46 @@ +## Tests the case where module compilation fails. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -DSHOULD_COMPILE=1 \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s + +#--- main.m +@import foo; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#ifndef SHOULD_COMPILE +#error "Compilation failure." +#endif + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +#--- commands.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# CHECK: Finished building Clang module foo +# CHECK: couldn't load top-level module foo: +# CHECK: While building module 'foo' imported from LLDBModulesMemoryBuffer +# CHEKC: {{.*}}sources/foo.h{{.*}}: error: "Compilation failure." +# CHECK: LLDBModulesMemoryBuffer:1:1: fatal error: could not build module 'foo' + +# CHECK: Error while loading hand-imported modules: +# CHECK: couldn't load top-level module foo: +# CHECK-NOT: Compilation failure diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_FromExpression.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_FromExpression.test new file mode 100644 index 0000000..b964e9b --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_FromExpression.test @@ -0,0 +1,54 @@ +## Tests the case where we fail to import modules from @import +## statements that are part of the expression being run. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# +# RUN: sed -i '' -e 's/foo\.h/baz\.h/' %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s + +#--- main.m +@import foo; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr @import Foo; @import Bar +expr @import foo + +# CHECK: error: while importing modules: +# CHECK-NEXT: header search couldn't locate module 'Foo' +# CHECK-NEXT: header search couldn't locate module 'Bar' +# +# CHECK: expr @import foo +# CHECK: error: while importing modules: +# CHECK-NEXT: couldn't load top-level module foo +## No mention of the previous import errors. +# CHECK-NOT: Foo +# CHECK-NOT: Bar diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidNestedSubmodule.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidNestedSubmodule.test new file mode 100644 index 0000000..1e8075d --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidNestedSubmodule.test @@ -0,0 +1,70 @@ +## Tests the case where we fail to load a submodule of a submodule. We force this +## by removing the submodule 'module qux' of 'module baz' from the modulemap. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# RUN: sed -i '' -e 's/module qux/module quz/' %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo.baz.qux; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- baz.h +struct baz {}; + +#--- qux.h +struct qux {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * + + module baz { + header "baz.h" + export * + + module qux { + header "qux.h" + export * + } + } +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: couldn't load submodule 'qux' of module 'foo.baz' + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: couldn't load submodule 'qux' of module 'foo.baz' diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidSearchPath.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidSearchPath.test new file mode 100644 index 0000000..35ba580 --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidSearchPath.test @@ -0,0 +1,59 @@ +## Tests the case where the DW_AT_LLVM_include_path of the module is invalid. +## We forces this by just removing that directory (which in our case is 'sources'). +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# +# RUN: cp %t/sources/commands.input %t/commands.input +# RUN: cp %t/sources/commands-with-log.input %t/commands-with-log.input +# RUN: rm -r %t/sources +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: couldn't find module search path directory {{.*}}sources +# NO_LOG-NOT: couldn't find module search path directory {{.*}}sources + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: couldn't find module search path directory {{.*}}sources +# LOG: couldn't find module search path directory {{.*}}sources diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidSubmodule.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidSubmodule.test new file mode 100644 index 0000000..1bfbbcf --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidSubmodule.test @@ -0,0 +1,62 @@ +## Tests the case where we fail to load a submodule. We force this by removing +## the submodule 'module baz' from the modulemap. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# RUN: sed -i '' -e 's/module baz/module qux/' %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo.baz; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- baz.h +struct baz {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * + + module baz { + header "baz.h" + export * + } +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: couldn't load submodule 'baz' of module 'foo' + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: couldn't load submodule 'baz' of module 'foo' diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidTopLevelModule.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidTopLevelModule.test new file mode 100644 index 0000000..ad181ee --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_InvalidTopLevelModule.test @@ -0,0 +1,59 @@ +## Tests the case where a module fails to load. We force this by +## replacing the contents of the 'module foo' declaration with garbage. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# RUN: sed -i '' -e 's/foo\.h/baz\.h/' %t/sources/module.modulemap +# RUN: sed -i '' -e 's/bar\.h/qux\.h/' %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: couldn't load top-level module foo +# NO_LOG-NOT: error: header + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: couldn't load top-level module foo +# LOG: error: header 'baz.h' +# LOG: couldn't load top-level module bar +# LOG: error: header 'qux.h' diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_ModulemapParsing.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_ModulemapParsing.test new file mode 100644 index 0000000..6d8e665 --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_ModulemapParsing.test @@ -0,0 +1,57 @@ +## Tests the case where the modulemap is semantically invalid and thus +## Clang fails to load it on behalf of LLDB. We force this error by +## creating a redefinition of 'module bar'. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# RUN: sed -i '' -e 's/module foo/module bar/' %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: failed to parse and load +# NO_LOG-NOT: failed to parse and load + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: failed to parse and load modulemap file in {{.*}}sources +# LOG: failed to parse and load modulemap file in {{.*}}sources diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_NoModule.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_NoModule.test new file mode 100644 index 0000000..bcb8a7d --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_NoModule.test @@ -0,0 +1,58 @@ +## Tests the case where the module LLDB is trying to load isn't +## present in the modulemap. We force this by replacing 'module foo' +## in the modulemap. +# +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# RUN: sed -i '' -e 's/module foo/module baz/' %t/sources/module.modulemap +# RUN: sed -i '' -e 's/module bar/module qux/' %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: header search couldn't locate module 'foo' +# NO_LOG-NOT: header search couldn't locate module 'bar' + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: header search couldn't locate module 'foo' +# LOG: header search couldn't locate module 'bar' diff --git a/lldb/test/Shell/Expr/TestClangModuleLoadError_NoModuleMap.test b/lldb/test/Shell/Expr/TestClangModuleLoadError_NoModuleMap.test new file mode 100644 index 0000000..57f7f16 --- /dev/null +++ b/lldb/test/Shell/Expr/TestClangModuleLoadError_NoModuleMap.test @@ -0,0 +1,53 @@ +# REQUIRES: system-darwin +# +# RUN: split-file %s %t/sources +# RUN: %clang_host -g %t/sources/main.m -fmodules -fcxx-modules \ +# RUN: -fmodule-map-file=%t/sources/module.modulemap \ +# RUN: -fmodules-cache-path=%t/ModuleCache -o %t.out +# RUN: rm %t/sources/module.modulemap +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=NO_LOG +# +# RUN: %lldb -x -o "settings set interpreter.stop-command-source-on-error false" \ +# RUN: -s %t/sources/commands-with-log.input %t.out -o exit 2>&1 | FileCheck %s --check-prefix=LOG + +#--- main.m +@import foo; +@import bar; + +int main() { __builtin_debugtrap(); } + +#--- foo.h +struct foo {}; + +#--- bar.h +struct bar {}; + +#--- module.modulemap +module foo { + header "foo.h" + export * +} + +module bar { + header "bar.h" + export * +} + +#--- commands.input +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# NO_LOG-NOT: couldn't find modulemap +# NO_LOG-NOT: couldn't find modulemap + +#--- commands-with-log.input +log enable lldb expr +run +## Make sure expression fails so the 'note' diagnostics get printed. +expr blah + +# LOG: couldn't find modulemap file in {{.*}}sources +# LOG: couldn't find modulemap file in {{.*}}sources diff --git a/lldb/test/Shell/Recognizer/registration-unique.test b/lldb/test/Shell/Recognizer/registration-unique.test index 34400d9..e964192 100644 --- a/lldb/test/Shell/Recognizer/registration-unique.test +++ b/lldb/test/Shell/Recognizer/registration-unique.test @@ -5,16 +5,16 @@ # RUN: split-file %s %t -# RUN: %clang_host %t/main.cpp -g -o %t/cpp.out +# RUN: %clangxx_host %t/main.cpp -g -o %t/cpp.out # RUN: %lldb -b -s %t/commands.input %t/cpp.out | FileCheck %s -# RUN: %clang_host -x objective-c++ %t/main.mm -g -o %t/objcxx.out +# RUN: %clangxx_host %t/main.mm -g -o %t/objcxx.out # RUN: %lldb -b -s %t/commands.input %t/objcxx.out | FileCheck %s # RUN: %clang_host %t/main.c -g -o %t/c.out # RUN: %lldb -b -s %t/commands.input %t/c.out | FileCheck %s -# RUN: %clang_host -x objective-c %t/main.m -g -o %t/objc.out +# RUN: %clang_host %t/main.m -g -o %t/objc.out # RUN: %lldb -b -s %t/commands.input %t/objc.out | FileCheck %s #--- main.m diff --git a/lldb/test/Shell/Recognizer/verbose_trap-objc.test b/lldb/test/Shell/Recognizer/verbose_trap-objc.test index 789e79c..0dbb04e 100644 --- a/lldb/test/Shell/Recognizer/verbose_trap-objc.test +++ b/lldb/test/Shell/Recognizer/verbose_trap-objc.test @@ -1,6 +1,6 @@ # REQUIRES: system-darwin # -# RUN: %clangxx_host -x objective-c -g %S/Inputs/verbose_trap.m -o %t.out +# RUN: %clang_host -g %S/Inputs/verbose_trap.m -o %t.out # RUN: %lldb -b -s %s %t.out | FileCheck %s run diff --git a/lldb/test/Shell/SymbolFile/DWARF/Inputs/dwp-str-offsets-dwarf64-dwp.yaml b/lldb/test/Shell/SymbolFile/DWARF/Inputs/dwp-str-offsets-dwarf64-dwp.yaml new file mode 100644 index 0000000..d4e2cee --- /dev/null +++ b/lldb/test/Shell/SymbolFile/DWARF/Inputs/dwp-str-offsets-dwarf64-dwp.yaml @@ -0,0 +1,44 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 + SectionHeaderStringTable: .strtab +Sections: + - Name: .debug_abbrev.dwo + Type: SHT_PROGBITS + Flags: [ SHF_EXCLUDE ] + AddressAlign: 0x1 + Content: 01110125251305032576250000022E01111B1206401803253A0B3B0B49133F190000030500021803253A0B3B0B4913000004240003253E0B0B0B0000050F00491300000626004913000000 + - Name: .debug_str.dwo + Type: SHT_PROGBITS + Flags: [ SHF_EXCLUDE, SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x1 + EntSize: 0x1 + Content: 6D61696E00696E74006172676300617267760063686172004170706C6520636C616E672076657273696F6E2031372E302E302028636C616E672D313730302E342E342E3129006D61696E2E6D696E696D616C2E637070006D61696E2E6D696E696D616C2E64776F00 + - Name: .debug_str_offsets.dwo + Type: SHT_PROGBITS + Flags: [ SHF_EXCLUDE ] + AddressAlign: 0x1 + Content: 'FFFFFFFF4400000000000000050000000000000000000000050000000000000009000000000000000E000000000000001300000000000000180000000000000046000000000000005700000000000000' + - Name: .debug_info.dwo + Type: SHT_PROGBITS + Flags: [ SHF_EXCLUDE ] + AddressAlign: 0x1 + Content: 54000000050005080000000099E97383BBC6980B0105210006070200160000000156000001400000000302917802000140000000030291700300014400000000040105040549000000054E00000006530000000404060100 + - Name: .debug_cu_index + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 05000000030000000100000002000000000000000000000099E97383BBC6980B0000000001000000010000000300000006000000000000000000000000000000580000004B00000028000000 + - Type: SectionHeaderTable + Sections: + - Name: .strtab + - Name: .debug_abbrev.dwo + - Name: .debug_str.dwo + - Name: .debug_str_offsets.dwo + - Name: .debug_info.dwo + - Name: .debug_cu_index + - Name: .symtab +Symbols: [] +... diff --git a/lldb/test/Shell/SymbolFile/DWARF/Inputs/dwp-str-offsets-dwarf64-exe.yaml b/lldb/test/Shell/SymbolFile/DWARF/Inputs/dwp-str-offsets-dwarf64-exe.yaml new file mode 100644 index 0000000..3785bbe --- /dev/null +++ b/lldb/test/Shell/SymbolFile/DWARF/Inputs/dwp-str-offsets-dwarf64-exe.yaml @@ -0,0 +1,100 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_X86_64 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x200040 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .eh_frame + LastSec: .eh_frame + VAddr: 0x200000 + Align: 0x1000 + Offset: 0x0 + - Type: PT_LOAD + Flags: [ PF_X, PF_R ] + FirstSec: .text + LastSec: .text + VAddr: 0x201160 + Align: 0x1000 + Offset: 0x160 + - Type: PT_GNU_STACK + Flags: [ PF_W, PF_R ] + Align: 0x0 + Offset: 0x0 +Sections: + - Name: .eh_frame + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x200120 + AddressAlign: 0x8 + Content: 1400000000000000017A5200017810011B0C0708900100001C0000001C000000201000001600000000410E108602430D06510C070800000000000000 + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x201160 + AddressAlign: 0x10 + Content: 554889E5C745FC00000000897DF8488975F031C05DC3 + - Name: .debug_abbrev + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 014A00101772171B25B442197625111B12067317000000 + - Name: .debug_info + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 24000000050004080000000099E97383BBC6980B0100000000080000000001001600000008000000 + - Name: .debug_str_offsets + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 0C000000050000000000000002000000 + - Name: .debug_gnu_pubnames + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 18000000020000000000280000001A000000306D61696E0000000000 + - Name: .debug_gnu_pubtypes + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: '21000000020000000000280000004000000090696E74005300000090636861720000000000' + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x1 + EntSize: 0x1 + Content: 004170706C6520636C616E672076657273696F6E2031372E302E302028636C616E672D313730302E342E342E3129004C696E6B65723A204C4C442032322E302E30202868747470733A2F2F6769746875622E636F6D2F636C6179626F72672F6C6C766D2D70726F6A6563742E67697420613234333130363863303837656463303938393330303934343864343162356138336361303363392900 + - Name: .debug_line + Type: SHT_PROGBITS + AddressAlign: 0x1 + Content: 5A0000000500080037000000010101FB0E0D00010101010000000100000101011F010000000003011F020F051E0102000000004E0649A10A56ED4D381C49A3DB4F6825040000090260112000000000000105030A0821060B2E0202000101 + - Name: .debug_line_str + Type: SHT_PROGBITS + Flags: [ SHF_MERGE, SHF_STRINGS ] + AddressAlign: 0x1 + EntSize: 0x1 + Content: 2E006D61696E2E6D696E696D616C2E63707000 +Symbols: + - Name: main.minimal.cpp + Type: STT_FILE + Index: SHN_ABS + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x201160 + Size: 0x16 +DWARF: + debug_str: + - . + - main.minimal.dwo + debug_addr: + - Length: 0xC + Version: 0x5 + AddressSize: 0x8 + Entries: + - Address: 0x201160 +... diff --git a/lldb/test/Shell/SymbolFile/DWARF/dwp-str-offsets-dwarf64.test b/lldb/test/Shell/SymbolFile/DWARF/dwp-str-offsets-dwarf64.test new file mode 100644 index 0000000..2ef4db0 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/DWARF/dwp-str-offsets-dwarf64.test @@ -0,0 +1,25 @@ +# This test verifies that LLDB can read a .dwp file that has a DWARF32 compile +# unit that has a DWARF64 .debug_str_offsets table. This will be needed for +# llvm-dwp changes that will start emitting 64 bit line tables for compile units +# that have strings in the .debug_str section whose string offsets exceed 4GB. +# By having a 64 bit .debug_str_offsets table, we can emit .debug_str sections +# with no size limits. + +# RUN: yaml2obj %S/Inputs/dwp-str-offsets-dwarf64-exe.yaml > %t +# RUN: yaml2obj %S/Inputs/dwp-str-offsets-dwarf64-dwp.yaml > %t.dwp +# RUN: %lldb %t -b \ +# RUN: -o 'image lookup --verbose --address 0x0000000000201172' | \ +# RUN: FileCheck %s + +# CHECK: (lldb) image lookup --verbose --address 0x0000000000201172 +# CHECK-NEXT: Address: dwp-str-offsets-dwarf64.test.tmp[0x0000000000201172] (dwp-str-offsets-dwarf64.test.tmp.PT_LOAD[1]..text + 18) +# CHECK-NEXT: Summary: dwp-str-offsets-dwarf64.test.tmp`main + 18 at main.minimal.cpp:2:3 +# CHECK-NEXT: Module: file = "{{.*[\\/]}}dwp-str-offsets-dwarf64.test.tmp", arch = "x86_64" +# CHECK-NEXT: CompileUnit: id = {0x00000000}, file = "main.minimal.cpp", language = "<not loaded>" +# CHECK-NEXT: Function: id = {0x7fffff000000001a}, name = "main", range = [0x0000000000201160-0x0000000000201176) +# CHECK-NEXT: FuncType: id = {0x7fffff000000001a}, byte-size = 0, decl = main.minimal.cpp:1, compiler_type = "int (int, const char **)" +# CHECK-NEXT: Blocks: id = {0x7fffff000000001a}, range = [0x00201160-0x00201176) +# CHECK-NEXT: LineEntry: [0x0000000000201172-0x0000000000201174): main.minimal.cpp:2:3 +# CHECK-NEXT: Symbol: id = {0x00000002}, range = [0x0000000000201160-0x0000000000201176), name="main" +# CHECK-NEXT: Variable: id = {0x7fffff0000000029}, name = "argc", type = "int", valid ranges = <block>, location = DW_OP_fbreg -8, decl = main.minimal.cpp:1 +# CHECK-NEXT: Variable: id = {0x7fffff0000000034}, name = "argv", type = "const char **", valid ranges = <block>, location = DW_OP_fbreg -16, decl = main.minimal.cpp:1 diff --git a/lldb/test/Shell/SymbolFile/DWARF/x86/dwarf5-macho.c b/lldb/test/Shell/SymbolFile/DWARF/x86/dwarf5-macho.c index cc73056..1bc0722 100644 --- a/lldb/test/Shell/SymbolFile/DWARF/x86/dwarf5-macho.c +++ b/lldb/test/Shell/SymbolFile/DWARF/x86/dwarf5-macho.c @@ -2,9 +2,9 @@ // REQUIRES: system-darwin -// RUN: %clang -target x86_64-apple-darwin %s -c -o %t.o -gdwarf-5 +// RUN: %clang_host -target x86_64-apple-darwin %s -c -o %t.o -gdwarf-5 // RUN: llvm-readobj --sections %t.o | FileCheck %s --check-prefix NAMES -// RUN: xcrun %clang -target x86_64-apple-darwin -o %t.exe %t.o +// RUN: xcrun %clang_host -target x86_64-apple-darwin -o %t.exe %t.o // RUN: %lldb %t.exe -b -o "target modules dump line-table %s" | FileCheck %s // NAMES: Name: __debug_line_str diff --git a/lldb/test/Shell/SymbolFile/NativePDB/class_layout.cpp b/lldb/test/Shell/SymbolFile/NativePDB/class_layout.cpp index 36bfdb9..83ed533 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/class_layout.cpp +++ b/lldb/test/Shell/SymbolFile/NativePDB/class_layout.cpp @@ -34,9 +34,6 @@ // CHECK-NEXT: s4 = { // CHECK-NEXT: x = ([0] = 67, [1] = 68, [2] = 99) // CHECK-NEXT: } -// CHECK-NEXT: s1 = { -// CHECK-NEXT: x = ([0] = 69, [1] = 70, [2] = 71) -// CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: } @@ -47,6 +44,9 @@ // CHECK-NEXT: c2 = 'D' // CHECK-NEXT: } // CHECK-NEXT: } +// CHECK-NEXT: s1 = { +// CHECK-NEXT: x = ([0] = 69, [1] = 70, [2] = 71) +// CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: (lldb) type lookup C // CHECK-NEXT: struct C { @@ -63,7 +63,6 @@ // CHECK-NEXT: struct { // CHECK-NEXT: char c4; // CHECK-NEXT: S3 s4; -// CHECK-NEXT: S3 s1; // CHECK-NEXT: }; // CHECK-NEXT: }; // CHECK-NEXT: }; @@ -72,6 +71,7 @@ // CHECK-NEXT: char c2; // CHECK-NEXT: }; // CHECK-NEXT: }; +// CHECK-NEXT: S3 s1; // CHECK-NEXT: } diff --git a/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test new file mode 100644 index 0000000..c35c82a --- /dev/null +++ b/lldb/test/Shell/SymbolFile/NativePDB/find-pdb-next-to-exe.test @@ -0,0 +1,76 @@ +# REQUIRES: lld, target-windows + +# Test where LLDB looks for PDBs. +# RUN: split-file %s %t + +# RUN: mkdir -p %t/build +# RUN: mkdir -p %t/dir1 +# RUN: mkdir -p %t/dir2 +# RUN: mkdir -p %t/dir3 + +# RUN: echo "settings append target.debug-file-search-paths %t/dir2" >> %t/init.input +# RUN: echo "settings append target.debug-file-search-paths %t/dir3" >> %t/init.input + +# RUN: %build --compiler=clang-cl --nodefaultlib --output=%t/build/a.exe %t/main.cpp + +# Regular setup - PDB is at the original path +# RUN: %lldb -S %t/init.input -s %t/check.input %t/build/a.exe | FileCheck --check-prefix=BOTH-ORIG %s +# BOTH-ORIG: (lldb) target create +# BOTH-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}build{{[/\\]}}a.exe +# BOTH-ORIG: (A) a = (x = 47) + +# Move the executable to a different directory but keep the PDB. +# RUN: mv %t/build/a.exe %t/dir1 +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=PDB-ORIG %s +# PDB-ORIG: (lldb) target create +# PDB-ORIG-NEXT: Loading {{.*[/\\]}}build{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# PDB-ORIG: (A) a = (x = 47) + +# Copy the PDB to the same directory and all search dirs. LLDB should prefer the original PDB. +# RUN: cp %t/build/a.pdb %t/dir1 +# RUN: cp %t/build/a.pdb %t/dir2 +# RUN: cp %t/build/a.pdb %t/dir3 +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=PDB-ORIG %s + +# Remove the original PDB. LLDB should now use the one next to the exe. +# RUN: rm %t/build/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=NEXT-TO-EXE %s +# NEXT-TO-EXE: (lldb) target create +# NEXT-TO-EXE-NEXT: Loading {{.*[/\\]}}dir1{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# NEXT-TO-EXE: (A) a = (x = 47) + +# Remove the PDB next to the exe. LLDB should now use the one in dir2 (first in list). +# RUN: rm %t/dir1/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR2 %s +# DIR2: (lldb) target create +# DIR2-NEXT: Loading {{.*[/\\]}}dir2{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# DIR2: (A) a = (x = 47) + +# Remove the PDB in dir2. LLDB should now use the one in dir3 (second in list). +# RUN: rm %t/dir2/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input %t/dir1/a.exe | FileCheck --check-prefix=DIR3 %s +# DIR3: (lldb) target create +# DIR3-NEXT: Loading {{.*[/\\]}}dir3{{[/\\]}}a.pdb for {{.*[/\\]}}dir1{{[/\\]}}a.exe +# DIR3: (A) a = (x = 47) + +# Remove the last PDB in dir3. Now, there's no matching PDB anymore. +# RUN: rm %t/dir3/a.pdb +# RUN: %lldb -S %t/init.input -s %t/check.input -f %t/dir1/a.exe 2>&1 | FileCheck --check-prefix=NOPDB %s +# NOPDB: error: can't find global variable 'a' + +#--- main.cpp + +struct A { + int x = 47; +}; +A a; +int main() {} + +#--- init.input + +log enable lldb symbol + +#--- check.input + +target variable a +q diff --git a/lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp b/lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp new file mode 100644 index 0000000..64fbc84 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp @@ -0,0 +1,39 @@ +// REQUIRES: lld, target-windows + +// Test that `target symbols add <pdb>` works. +// RUN: %build --compiler=clang-cl --nodefaultlib --output=%t.exe %s +// RUN: mv %t.pdb %t-renamed.pdb + +// RUN: env LLDB_USE_NATIVE_PDB_READER=0 %lldb \ +// RUN: -o "b main" \ +// RUN: -o "target symbols add %t-renamed.pdb" \ +// RUN: -o r \ +// RUN: -o "target variable a" \ +// RUN: -o "target modules dump symtab" \ +// RUN: -b %t.exe | FileCheck %s + +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 %lldb \ +// RUN: -o "b main" \ +// RUN: -o "target symbols add %t-renamed.pdb" \ +// RUN: -o r \ +// RUN: -o "target variable a" \ +// RUN: -o "target modules dump symtab" \ +// RUN: -b %t.exe | FileCheck %s + +// CHECK: target create +// CHECK: (lldb) b main +// CHECK-NEXT: Breakpoint 1: no locations (pending). +// CHECK: (lldb) target symbols add +// CHECK: 1 location added to breakpoint 1 + +// CHECK: * thread #1, stop reason = breakpoint 1.1 +// CHECK: (lldb) target variable a +// CHECK-NEXT: (A) a = (x = 47) +// CHECK: (lldb) target modules dump symtab +// CHECK: [{{.*}} main + +struct A { + int x = 47; +}; +A a; +int main() {} diff --git a/lldb/test/Shell/Unwind/Inputs/call-asm.c b/lldb/test/Shell/Unwind/Inputs/call-asm.c index b154c1a..778c16b 100644 --- a/lldb/test/Shell/Unwind/Inputs/call-asm.c +++ b/lldb/test/Shell/Unwind/Inputs/call-asm.c @@ -1,3 +1,3 @@ -int asm_main() asm("asm_main"); - +// Explicit mangling is necessary as on Darwin an underscore is prepended to the symbol. +int asm_main() __asm("asm_main"); int main() { return asm_main(); } diff --git a/lldb/test/Shell/helper/build.py b/lldb/test/Shell/helper/build.py index a5a7e99..1fa8aab 100755 --- a/lldb/test/Shell/helper/build.py +++ b/lldb/test/Shell/helper/build.py @@ -804,7 +804,19 @@ class GccBuilder(Builder): args.extend(self._obj_file_names()) if sys.platform == "darwin": + # By default, macOS doesn't allow injecting the ASAN + # runtime into system processes. + system_clang = ( + subprocess.check_output(["xcrun", "-find", "clang"]) + .strip() + .decode("utf-8") + ) + system_liblto = os.path.join( + os.path.dirname(os.path.dirname(system_clang)), "lib", "libLTO.dylib" + ) args.extend(["-isysroot", self.apple_sdk]) + args.extend(["-Wl,-lto_library", "-Wl," + system_liblto]) + elif self.objc_gnustep_lib: args.extend(["-L", self.objc_gnustep_lib, "-lobjc"]) if sys.platform == "linux": diff --git a/lldb/test/Shell/helper/toolchain.py b/lldb/test/Shell/helper/toolchain.py index 728f634..0c8c39d 100644 --- a/lldb/test/Shell/helper/toolchain.py +++ b/lldb/test/Shell/helper/toolchain.py @@ -226,7 +226,7 @@ def use_support_substitutions(config): except OSError: res = -1 if res == 0 and out: - sdk_path = lit.util.to_string(out) + sdk_path = out.decode("utf-8") llvm_config.lit_config.note("using SDKROOT: %r" % sdk_path) host_flags += ["-isysroot", sdk_path] elif sys.platform != "win32": @@ -250,6 +250,15 @@ def use_support_substitutions(config): "-L{}".format(config.libcxx_libs_dir), "-lc++", ] + # By default, macOS doesn't allow injecting the ASAN runtime into system processes. + if platform.system() in ["Darwin"] and config.llvm_use_sanitizer: + system_clang = ( + subprocess.check_output(["xcrun", "-find", "clang"]).strip().decode("utf-8") + ) + system_liblto = os.path.join( + os.path.dirname(os.path.dirname(system_clang)), "lib", "libLTO.dylib" + ) + host_flags += ["-Wl,-lto_library", "-Wl," + system_liblto] host_flags = " ".join(host_flags) config.substitutions.append(("%clang_host", "%clang " + host_flags)) @@ -268,6 +277,9 @@ def use_support_substitutions(config): required=True, use_installed=True, ) + if llvm_config.clang_has_bounds_safety(): + llvm_config.lit_config.note("clang has -fbounds-safety support") + config.available_features.add("clang-bounds-safety") if sys.platform == "win32": _use_msvc_substitutions(config) diff --git a/lldb/test/Shell/lldb-server/TestErrorMessages.test b/lldb/test/Shell/lldb-server/TestGdbserverErrorMessages.test index b9689fb..b9689fb 100644 --- a/lldb/test/Shell/lldb-server/TestErrorMessages.test +++ b/lldb/test/Shell/lldb-server/TestGdbserverErrorMessages.test diff --git a/lldb/test/Shell/lldb-server/TestPlatformErrorMessages.test b/lldb/test/Shell/lldb-server/TestPlatformErrorMessages.test new file mode 100644 index 0000000..7d3b37a --- /dev/null +++ b/lldb/test/Shell/lldb-server/TestPlatformErrorMessages.test @@ -0,0 +1,25 @@ +RUN: %platformserver 2>&1 | FileCheck --check-prefixes=NO_LISTEN,ALL %s +NO_LISTEN: error: either --listen or --child-platform-fd is required + +RUN: %lldb-server platform --listen 2>&1 | FileCheck --check-prefixes=LISTEN_MISSING,ALL %s +LISTEN_MISSING: error: --listen: missing argument + +RUN: %lldb-server p --bogus 2>&1 | FileCheck --check-prefixes=BOGUS,ALL %s +BOGUS: error: unknown argument '--bogus' + +RUN: %platformserver --gdbserver-port 2>&1 | FileCheck --check-prefixes=GDBPORT_MISSING,ALL %s +GDBPORT_MISSING: error: --gdbserver-port: missing argument + +RUN: %platformserver --gdbserver-port notanumber --listen :1234 2>&1 | FileCheck --check-prefixes=GDBPORT_INVALID %s +GDBPORT_INVALID: error: invalid --gdbserver-port value + +RUN: %platformserver --socket-file 2>&1 | FileCheck --check-prefixes=SOCKETFILE_MISSING,ALL %s +SOCKETFILE_MISSING: error: --socket-file: missing argument + +RUN: %platformserver --log-file 2>&1 | FileCheck --check-prefixes=LOGFILE_MISSING,ALL %s +LOGFILE_MISSING: error: --log-file: missing argument + +RUN: %platformserver --log-channels 2>&1 | FileCheck --check-prefixes=LOGCHANNELS_MISSING,ALL %s +LOGCHANNELS_MISSING: error: --log-channels: missing argument + +ALL: Use 'lldb-server{{(\.exe)?}} {{p|platform}} --help' for a complete list of options. diff --git a/lldb/test/Shell/lldb-server/TestPlatformHelp.test b/lldb/test/Shell/lldb-server/TestPlatformHelp.test new file mode 100644 index 0000000..c5ced8a --- /dev/null +++ b/lldb/test/Shell/lldb-server/TestPlatformHelp.test @@ -0,0 +1,40 @@ +RUN: %platformserver --help 2>&1 | FileCheck %s +RUN: %platformserver -h 2>&1 | FileCheck %s +RUN: %lldb-server p --help 2>&1 | FileCheck %s +RUN: %lldb-server p -h 2>&1 | FileCheck %s +RUN: %lldb-server platform --help 2>&1 | FileCheck %s +RUN: %lldb-server platform -h 2>&1 | FileCheck %s + +CHECK: OVERVIEW: lldb-server{{(\.exe)?}} platform + +CHECK: USAGE: lldb-server{{(\.exe)?}} {{p|platform}} [options] --listen <[host]:port> {{\[}}[--] program args...] + +CHECK: CONNECTION OPTIONS: +CHECK: --gdbserver-port <port> +CHECK-SAME: Short form: -P +CHECK: --listen <[host]:port> +CHECK-SAME: Short form: -L +CHECK: --socket-file <path> +CHECK-SAME: Short form: -f + +CHECK: GENERAL OPTIONS: +CHECK: --help +CHECK: --log-channels <channel1 categories...:channel2 categories...> +CHECK: Short form: -c +CHECK: --log-file <file> +CHECK-SAME: Short form: -l +CHECK: --server + +CHECK: OPTIONS: +CHECK: -- program args + +CHECK: DESCRIPTION +CHECK: Acts as a platform server for remote debugging + +CHECK: EXAMPLES +CHECK: # Listen on port 1234, exit after first connection +CHECK: lldb-server{{(\.exe)?}} platform --listen tcp://0.0.0.0:1234 +CHECK: # Listen on port 5555, accept multiple connections +CHECK: lldb-server{{(\.exe)?}} platform --server --listen tcp://localhost:5555 +CHECK: # Listen on Unix domain socket +CHECK: lldb-server{{(\.exe)?}} platform --listen unix:///tmp/lldb-server.sock diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index 0cd48d9..4d5afcf 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/tools/debugserver/source/DNB.cpp @@ -1101,7 +1101,7 @@ DNBGetLibrariesInfoForAddresses(nub_process_t pid, JSONGenerator::ObjectSP DNBGetSharedCacheInfo(nub_process_t pid) { MachProcessSP procSP; if (GetProcessSP(pid, procSP)) { - return procSP->GetSharedCacheInfo(pid); + return procSP->GetInferiorSharedCacheInfo(pid); } return JSONGenerator::ObjectSP(); } diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.h b/lldb/tools/debugserver/source/MacOSX/MachProcess.h index 56bc9d6..67b27b9 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.h +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.h @@ -283,7 +283,10 @@ public: JSONGenerator::ObjectSP GetAllLoadedLibrariesInfos(nub_process_t pid, bool fetch_report_load_commands); - JSONGenerator::ObjectSP GetSharedCacheInfo(nub_process_t pid); + bool GetDebugserverSharedCacheInfo(uuid_t &uuid, + std::string &shared_cache_path); + bool GetInferiorSharedCacheFilepath(std::string &inferior_sc_path); + JSONGenerator::ObjectSP GetInferiorSharedCacheInfo(nub_process_t pid); nub_size_t GetNumThreads() const; nub_thread_t GetThreadAtIndex(nub_size_t thread_idx) const; @@ -474,6 +477,14 @@ private: void *(*m_dyld_process_info_create)(task_t task, uint64_t timestamp, kern_return_t *kernelError); + void *(*m_dyld_process_create_for_task)(task_read_t task, kern_return_t *kr); + void *(*m_dyld_process_snapshot_create_for_process)(void *process, + kern_return_t *kr); + void *(*m_dyld_process_snapshot_get_shared_cache)(void *snapshot); + void (*m_dyld_shared_cache_for_each_file)( + void *cache, void (^block)(const char *file_path)); + void (*m_dyld_process_snapshot_dispose)(void *snapshot); + void (*m_dyld_process_dispose)(void *process); void (*m_dyld_process_info_for_each_image)( void *info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char *path)); @@ -481,6 +492,7 @@ private: void (*m_dyld_process_info_get_cache)(void *info, void *cacheInfo); uint32_t (*m_dyld_process_info_get_platform)(void *info); void (*m_dyld_process_info_get_state)(void *info, void *stateInfo); + const char *(*m_dyld_shared_cache_file_path)(); }; #endif // LLDB_TOOLS_DEBUGSERVER_SOURCE_MACOSX_MACHPROCESS_H diff --git a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm index 3afaaa2..10ed804 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachProcess.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachProcess.mm @@ -534,13 +534,35 @@ MachProcess::MachProcess() m_image_infos_baton(NULL), m_sent_interrupt_signo(0), m_auto_resume_signo(0), m_did_exec(false), m_dyld_process_info_create(nullptr), + m_dyld_process_create_for_task(nullptr), + m_dyld_process_snapshot_create_for_process(nullptr), + m_dyld_process_snapshot_get_shared_cache(nullptr), + m_dyld_shared_cache_for_each_file(nullptr), + m_dyld_process_snapshot_dispose(nullptr), m_dyld_process_dispose(nullptr), m_dyld_process_info_for_each_image(nullptr), m_dyld_process_info_release(nullptr), m_dyld_process_info_get_cache(nullptr), - m_dyld_process_info_get_state(nullptr) { + m_dyld_process_info_get_state(nullptr), + m_dyld_shared_cache_file_path(nullptr) { m_dyld_process_info_create = (void *(*)(task_t task, uint64_t timestamp, kern_return_t * kernelError)) dlsym(RTLD_DEFAULT, "_dyld_process_info_create"); + + m_dyld_process_create_for_task = + (void *(*)(task_read_t, kern_return_t *))dlsym( + RTLD_DEFAULT, "dyld_process_create_for_task"); + m_dyld_process_snapshot_create_for_process = + (void *(*)(void *, kern_return_t *))dlsym( + RTLD_DEFAULT, "dyld_process_snapshot_create_for_process"); + m_dyld_process_snapshot_get_shared_cache = (void *(*)(void *))dlsym( + RTLD_DEFAULT, "dyld_process_snapshot_get_shared_cache"); + m_dyld_shared_cache_for_each_file = + (void (*)(void *, void (^)(const char *)))dlsym( + RTLD_DEFAULT, "dyld_shared_cache_for_each_file"); + m_dyld_process_snapshot_dispose = + (void (*)(void *))dlsym(RTLD_DEFAULT, "dyld_process_snapshot_dispose"); + m_dyld_process_dispose = + (void (*)(void *))dlsym(RTLD_DEFAULT, "dyld_process_dispose"); m_dyld_process_info_for_each_image = (void (*)(void *info, void (^)(uint64_t machHeaderAddress, const uuid_t uuid, const char *path))) @@ -553,6 +575,8 @@ MachProcess::MachProcess() RTLD_DEFAULT, "_dyld_process_info_get_platform"); m_dyld_process_info_get_state = (void (*)(void *info, void *stateInfo))dlsym( RTLD_DEFAULT, "_dyld_process_info_get_state"); + m_dyld_shared_cache_file_path = + (const char *(*)())dlsym(RTLD_DEFAULT, "dyld_shared_cache_file_path"); DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__); } @@ -1179,13 +1203,82 @@ JSONGenerator::ObjectSP MachProcess::GetLibrariesInfoForAddresses( /* report_load_commands = */ true); } -// From dyld's internal podyld_process_info.h: +bool MachProcess::GetDebugserverSharedCacheInfo( + uuid_t &uuid, std::string &shared_cache_path) { + uuid_clear(uuid); + shared_cache_path.clear(); + + if (m_dyld_process_info_create && m_dyld_process_info_get_cache) { + kern_return_t kern_ret; + dyld_process_info info = + m_dyld_process_info_create(mach_task_self(), 0, &kern_ret); + if (info) { + struct dyld_process_cache_info shared_cache_info; + m_dyld_process_info_get_cache(info, &shared_cache_info); + uuid_copy(uuid, shared_cache_info.cacheUUID); + m_dyld_process_info_release(info); + } + } + if (m_dyld_shared_cache_file_path) { + const char *cache_path = m_dyld_shared_cache_file_path(); + if (cache_path) + shared_cache_path = cache_path; + } + if (!uuid_is_null(uuid)) + return true; + return false; +} + +bool MachProcess::GetInferiorSharedCacheFilepath( + std::string &inferior_sc_path) { + inferior_sc_path.clear(); + + if (!m_dyld_process_create_for_task || + !m_dyld_process_snapshot_create_for_process || + !m_dyld_process_snapshot_get_shared_cache || + !m_dyld_shared_cache_for_each_file || !m_dyld_process_snapshot_dispose || + !m_dyld_process_dispose) + return false; + + __block std::string sc_path; + kern_return_t kr; + void *process = m_dyld_process_create_for_task(m_task.TaskPort(), &kr); + if (kr != KERN_SUCCESS) + return false; + void *snapshot = m_dyld_process_snapshot_create_for_process(process, &kr); + if (kr != KERN_SUCCESS) + return false; + void *cache = m_dyld_process_snapshot_get_shared_cache(snapshot); + + // The shared cache is a collection of files on disk, this callback + // will iterate over all of them. + // The first filepath provided is the base filename of the cache. + __block bool done = false; + m_dyld_shared_cache_for_each_file(cache, ^(const char *path) { + if (done) { + return; + } + done = true; + sc_path = path; + }); + m_dyld_process_snapshot_dispose(snapshot); + m_dyld_process_dispose(process); + + inferior_sc_path = sc_path; + if (!sc_path.empty()) + return true; + return false; +} + +// From dyld's internal dyld_process_info.h: -JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { +JSONGenerator::ObjectSP +MachProcess::GetInferiorSharedCacheInfo(nub_process_t pid) { JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary()); - kern_return_t kern_ret; + uuid_t inferior_sc_uuid; if (m_dyld_process_info_create && m_dyld_process_info_get_cache) { + kern_return_t kern_ret; dyld_process_info info = m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret); if (info) { @@ -1197,6 +1290,7 @@ JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { uuid_string_t uuidstr; uuid_unparse_upper(shared_cache_info.cacheUUID, uuidstr); + uuid_copy(inferior_sc_uuid, shared_cache_info.cacheUUID); reply_sp->AddStringItem("shared_cache_uuid", uuidstr); reply_sp->AddBooleanItem("no_shared_cache", shared_cache_info.noCache); @@ -1206,6 +1300,29 @@ JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) { m_dyld_process_info_release(info); } } + + // If debugserver and the inferior are have the same cache UUID, + // use the simple call to get the filepath to debugserver's shared + // cache, return that. + uuid_t debugserver_sc_uuid; + std::string debugserver_sc_path; + bool found_sc_filepath = false; + if (GetDebugserverSharedCacheInfo(debugserver_sc_uuid, debugserver_sc_path)) { + if (uuid_compare(inferior_sc_uuid, debugserver_sc_uuid) == 0 && + !debugserver_sc_path.empty()) { + reply_sp->AddStringItem("shared_cache_path", debugserver_sc_path); + found_sc_filepath = true; + } + } + + // Use SPI that are only available on newer OSes to fetch the + // filepath of the shared cache of the inferior, if available. + if (!found_sc_filepath) { + std::string inferior_sc_path; + if (GetInferiorSharedCacheFilepath(inferior_sc_path)) + reply_sp->AddStringItem("shared_cache_path", inferior_sc_path); + } + return reply_sp; } @@ -1739,7 +1856,7 @@ bool MachProcess::Detach() { ReplyToAllExceptions(); } - m_task.ShutDownExcecptionThread(); + m_task.ShutDownExceptionThread(); // Detach from our process errno = 0; @@ -2853,12 +2970,6 @@ pid_t MachProcess::AttachForDebug( if (err.Success()) { m_flags |= eMachProcessFlagsAttached; - // Sleep a bit to let the exception get received and set our process - // status - // to stopped. - ::usleep(250000); - DNBLog("[LaunchAttach] (%d) Done napping after ptrace(PT_ATTACHEXC)'ing", - getpid()); DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", pid); return m_pid; } else { diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.h b/lldb/tools/debugserver/source/MacOSX/MachTask.h index c4a20b8..40fdbe9 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.h +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.h @@ -68,7 +68,7 @@ public: bool ExceptionPortIsValid() const; kern_return_t SaveExceptionPortInfo(); kern_return_t RestoreExceptionPortInfo(); - kern_return_t ShutDownExcecptionThread(); + void ShutDownExceptionThread(); bool StartExceptionThread( const RNBContext::IgnoredExceptions &ignored_exceptions, DNBError &err); @@ -81,9 +81,7 @@ public: void TaskPortChanged(task_t task); task_t TaskPort() const { return m_task; } task_t TaskPortForProcessID(DNBError &err, bool force = false); - static task_t TaskPortForProcessID(pid_t pid, DNBError &err, - uint32_t num_retries = 10, - uint32_t usec_interval = 10000); + static task_t TaskPortForProcessID(pid_t pid, DNBError &err); MachProcess *Process() { return m_process; } const MachProcess *Process() const { return m_process; } diff --git a/lldb/tools/debugserver/source/MacOSX/MachTask.mm b/lldb/tools/debugserver/source/MacOSX/MachTask.mm index 21156fe..22ad0c4 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachTask.mm +++ b/lldb/tools/debugserver/source/MacOSX/MachTask.mm @@ -145,10 +145,8 @@ bool MachTask::ExceptionPortIsValid() const { //---------------------------------------------------------------------- void MachTask::Clear() { // Do any cleanup needed for this task - if (m_exception_thread) - ShutDownExcecptionThread(); + ShutDownExceptionThread(); m_task = TASK_NULL; - m_exception_thread = 0; m_exception_port = MACH_PORT_NULL; m_exec_will_be_suspended = false; m_do_double_resume = false; @@ -523,14 +521,15 @@ task_t MachTask::TaskPortForProcessID(DNBError &err, bool force) { //---------------------------------------------------------------------- // MachTask::TaskPortForProcessID //---------------------------------------------------------------------- -task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err, - uint32_t num_retries, - uint32_t usec_interval) { +task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err) { + static constexpr uint32_t k_num_retries = 10; + static constexpr uint32_t k_usec_delay = 10000; + if (pid != INVALID_NUB_PROCESS) { DNBError err; mach_port_t task_self = mach_task_self(); task_t task = TASK_NULL; - for (uint32_t i = 0; i < num_retries; i++) { + for (uint32_t i = 0; i < k_num_retries; i++) { DNBLog("[LaunchAttach] (%d) about to task_for_pid(%d)", getpid(), pid); err = ::task_for_pid(task_self, pid, &task); @@ -557,7 +556,7 @@ task_t MachTask::TaskPortForProcessID(pid_t pid, DNBError &err, } // Sleep a bit and try again - ::usleep(usec_interval); + ::usleep(k_usec_delay); } } return TASK_NULL; @@ -684,8 +683,11 @@ bool MachTask::StartExceptionThread( return false; } -kern_return_t MachTask::ShutDownExcecptionThread() { +void MachTask::ShutDownExceptionThread() { DNBError err; + + if (!m_exception_thread) + return; err = RestoreExceptionPortInfo(); @@ -701,6 +703,8 @@ kern_return_t MachTask::ShutDownExcecptionThread() { if (DNBLogCheckLogBit(LOG_TASK) || err.Fail()) err.LogThreaded("::pthread_join ( thread = %p, value_ptr = NULL)", m_exception_thread); + + m_exception_thread = nullptr; // Deallocate our exception port that we used to track our child process mach_port_t task_self = mach_task_self(); @@ -712,7 +716,7 @@ kern_return_t MachTask::ShutDownExcecptionThread() { m_exec_will_be_suspended = false; m_do_double_resume = false; - return err.Status(); + return; } void *MachTask::ExceptionThread(void *arg) { diff --git a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp index 9d0d60f..c8dce75a 100644 --- a/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp +++ b/lldb/tools/debugserver/source/MacOSX/MachVMRegion.cpp @@ -14,6 +14,12 @@ #include "DNBLog.h" #include <cassert> #include <mach/mach_vm.h> +#include <mach/vm_statistics.h> + +// From <mach/vm_statistics.h>, but not on older OSs. +#ifndef VM_MEMORY_SANITIZER +#define VM_MEMORY_SANITIZER 99 +#endif MachVMRegion::MachVMRegion(task_t task) : m_task(task), m_addr(INVALID_NUB_ADDRESS), m_err(), diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index bebf1a7..4810771 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -477,18 +477,17 @@ bool AddPythonDLLToSearchPath() { #endif #ifdef LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME -/// Returns whether `python3x.dll` is in the DLL search path. +/// Returns true if `python3x.dll` can be loaded. bool IsPythonDLLInPath() { #define WIDEN2(x) L##x #define WIDEN(x) WIDEN2(x) - WCHAR foundPath[MAX_PATH]; - DWORD result = - SearchPathW(nullptr, WIDEN(LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME), nullptr, - MAX_PATH, foundPath, nullptr); + HMODULE h = LoadLibraryW(WIDEN(LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME)); + if (!h) + return false; + FreeLibrary(h); + return true; #undef WIDEN2 #undef WIDEN - - return result > 0; } #endif @@ -508,12 +507,12 @@ void SetupPythonRuntimeLibrary() { if (AddPythonDLLToSearchPath() && IsPythonDLLInPath()) return; #endif - llvm::errs() << "error: unable to find '" - << LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME << "'.\n"; + WithColor::error() << "unable to find '" + << LLDB_PYTHON_RUNTIME_LIBRARY_FILENAME << "'.\n"; return; #elif defined(LLDB_PYTHON_DLL_RELATIVE_PATH) if (!AddPythonDLLToSearchPath()) - llvm::errs() << "error: unable to find the Python runtime library.\n"; + WithColor::error() << "unable to find the Python runtime library.\n"; #endif } #endif diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index fa940b7..237c304 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_library(lldbDAP DAP.cpp DAPError.cpp DAPLog.cpp + DAPSessionManager.cpp EventHelper.cpp ExceptionBreakpoint.cpp FifoFiles.cpp diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index f009a90..58c9922 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "CommandPlugins.h" #include "DAPLog.h" #include "EventHelper.h" #include "ExceptionBreakpoint.h" @@ -121,7 +122,7 @@ static std::string capitalize(llvm::StringRef str) { llvm::StringRef DAP::debug_adapter_path = ""; -DAP::DAP(Log *log, const ReplMode default_repl_mode, +DAP::DAP(Log &log, const ReplMode default_repl_mode, std::vector<std::string> pre_init_commands, bool no_lldbinit, llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop) : log(log), transport(transport), broadcaster("lldb-dap"), @@ -133,8 +134,6 @@ DAP::DAP(Log *log, const ReplMode default_repl_mode, RegisterRequests(); } -DAP::~DAP() = default; - void DAP::PopulateExceptionBreakpoints() { if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) { exception_breakpoints.emplace_back(*this, "cpp_catch", "C++ Catch", @@ -242,10 +241,12 @@ llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { } void DAP::StopEventHandlers() { - if (event_thread.joinable()) { - broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); - event_thread.join(); - } + event_thread_sp.reset(); + + // Clean up expired event threads from the session manager. + DAPSessionManager::GetInstance().ReleaseExpiredEventThreads(); + + // Still handle the progress thread normally since it's per-DAP instance. if (progress_event_thread.joinable()) { broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread); progress_event_thread.join(); @@ -260,8 +261,7 @@ void DAP::SendJSON(const llvm::json::Value &json) { Message message; llvm::json::Path::Root root; if (!fromJSON(json, message, root)) { - DAP_LOG_ERROR(log, root.getError(), "({1}) encoding failed: {0}", - m_client_name); + DAP_LOG_ERROR(log, root.getError(), "encoding failed: {0}"); return; } Send(message); @@ -271,23 +271,24 @@ Id DAP::Send(const Message &message) { std::lock_guard<std::mutex> guard(call_mutex); Message msg = std::visit( [this](auto &&msg) -> Message { - if (msg.seq == kCalculateSeq) - msg.seq = seq++; + if (msg.seq == kCalculateSeq) { + seq++; + msg.seq = seq; + } + assert(msg.seq > 0 && "message sequence must be greater than zero."); return msg; }, Message(message)); if (const protocol::Event *event = std::get_if<protocol::Event>(&msg)) { if (llvm::Error err = transport.Send(*event)) - DAP_LOG_ERROR(log, std::move(err), "({0}) sending event failed", - m_client_name); + DAP_LOG_ERROR(log, std::move(err), "sending event failed: {0}"); return event->seq; } if (const Request *req = std::get_if<Request>(&msg)) { if (llvm::Error err = transport.Send(*req)) - DAP_LOG_ERROR(log, std::move(err), "({0}) sending request failed", - m_client_name); + DAP_LOG_ERROR(log, std::move(err), "sending request failed: {0}"); return req->seq; } @@ -307,8 +308,7 @@ Id DAP::Send(const Message &message) { }) : transport.Send(*resp); if (err) - DAP_LOG_ERROR(log, std::move(err), "({0}) sending response failed", - m_client_name); + DAP_LOG_ERROR(log, std::move(err), "sending response failed: {0}"); return resp->seq; } @@ -657,18 +657,20 @@ std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) { if (!frame.IsValid()) return std::nullopt; - const lldb::SBAddress frame_pc = frame.GetPCAddress(); - if (DisplayAssemblySource(debugger, frame_pc)) + const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry(); + if (DisplayAssemblySource(debugger, frame_line_entry)) { + const lldb::SBAddress frame_pc = frame.GetPCAddress(); return ResolveAssemblySource(frame_pc); + } - return CreateSource(frame.GetLineEntry().GetFileSpec()); + return CreateSource(frame_line_entry.GetFileSpec()); } std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) { - if (DisplayAssemblySource(debugger, address)) + lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address); + if (DisplayAssemblySource(debugger, line_entry)) return ResolveAssemblySource(address); - lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address); if (!line_entry.IsValid()) return std::nullopt; @@ -814,7 +816,8 @@ void DAP::SetTarget(const lldb::SBTarget target) { lldb::SBTarget::eBroadcastBitModulesLoaded | lldb::SBTarget::eBroadcastBitModulesUnloaded | lldb::SBTarget::eBroadcastBitSymbolsLoaded | - lldb::SBTarget::eBroadcastBitSymbolsChanged); + lldb::SBTarget::eBroadcastBitSymbolsChanged | + lldb::SBTarget::eBroadcastBitNewTargetCreated); listener.StartListeningForEvents(this->broadcaster, eBroadcastBitStopEventThread); } @@ -848,8 +851,7 @@ bool DAP::HandleObject(const Message &M) { dispatcher.Set("error", llvm::Twine("unhandled-command:" + req->command).str()); - DAP_LOG(log, "({0}) error: unhandled command '{1}'", m_client_name, - req->command); + DAP_LOG(log, "error: unhandled command '{0}'", req->command); return false; // Fail } @@ -995,35 +997,33 @@ void DAP::Received(const protocol::Request &request) { // effort attempt to interrupt. std::lock_guard<std::mutex> guard(m_active_request_mutex); if (m_active_request && cancel_args->requestId == m_active_request->seq) { - DAP_LOG(log, "({0}) interrupting inflight request (command={1} seq={2})", - m_client_name, m_active_request->command, m_active_request->seq); + DAP_LOG(log, "interrupting inflight request (command={0} seq={1})", + m_active_request->command, m_active_request->seq); debugger.RequestInterrupt(); } } std::lock_guard<std::mutex> guard(m_queue_mutex); - DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name, - request.command, request.seq); + DAP_LOG(log, "queued (command={0} seq={1})", request.command, request.seq); m_queue.push_back(request); m_queue_cv.notify_one(); } void DAP::Received(const protocol::Response &response) { std::lock_guard<std::mutex> guard(m_queue_mutex); - DAP_LOG(log, "({0}) queued (command={1} seq={2})", m_client_name, - response.command, response.request_seq); + DAP_LOG(log, "queued (command={0} seq={1})", response.command, + response.request_seq); m_queue.push_back(response); m_queue_cv.notify_one(); } void DAP::OnError(llvm::Error error) { - DAP_LOG_ERROR(log, std::move(error), "({1}) received error: {0}", - m_client_name); + DAP_LOG_ERROR(log, std::move(error), "transport error: {0}"); TerminateLoop(/*failed=*/true); } void DAP::OnClosed() { - DAP_LOG(log, "({0}) received EOF", m_client_name); + DAP_LOG(log, "transport closed"); TerminateLoop(); } @@ -1049,16 +1049,14 @@ void DAP::TransportHandler() { auto handle = transport.RegisterMessageHandler(m_loop, *this); if (!handle) { DAP_LOG_ERROR(log, handle.takeError(), - "({1}) registering message handler failed: {0}", - m_client_name); + "registering message handler failed: {0}"); std::lock_guard<std::mutex> guard(m_queue_mutex); m_error_occurred = true; return; } if (Status status = m_loop.Run(); status.Fail()) { - DAP_LOG_ERROR(log, status.takeError(), "({1}) MainLoop run failed: {0}", - m_client_name); + DAP_LOG_ERROR(log, status.takeError(), "MainLoop run failed: {0}"); std::lock_guard<std::mutex> guard(m_queue_mutex); m_error_occurred = true; return; @@ -1301,13 +1299,99 @@ protocol::Capabilities DAP::GetCustomCapabilities() { } void DAP::StartEventThread() { - event_thread = std::thread(&DAP::EventThread, this); + // Get event thread for this debugger (creates it if it doesn't exist). + event_thread_sp = DAPSessionManager::GetInstance().GetEventThreadForDebugger( + debugger, this); } void DAP::StartProgressEventThread() { progress_event_thread = std::thread(&DAP::ProgressEventThread, this); } +void DAP::StartEventThreads() { + if (clientFeatures.contains(eClientFeatureProgressReporting)) + StartProgressEventThread(); + + StartEventThread(); +} + +llvm::Error DAP::InitializeDebugger(int debugger_id, + lldb::user_id_t target_id) { + // Find the existing debugger by ID + debugger = lldb::SBDebugger::FindDebuggerWithID(debugger_id); + if (!debugger.IsValid()) { + return llvm::createStringError( + "Unable to find existing debugger for debugger ID"); + } + + // Find the target within the debugger by its globally unique ID + lldb::SBTarget target = debugger.FindTargetByGloballyUniqueID(target_id); + if (!target.IsValid()) { + return llvm::createStringError( + "Unable to find existing target for target ID"); + } + + // Set the target for this DAP session. + SetTarget(target); + StartEventThreads(); + return llvm::Error::success(); +} + +llvm::Error DAP::InitializeDebugger() { + debugger = lldb::SBDebugger::Create(/*argument_name=*/false); + + // Configure input/output/error file descriptors. + debugger.SetInputFile(in); + target = debugger.GetDummyTarget(); + + llvm::Expected<int> out_fd = out.GetWriteFileDescriptor(); + if (!out_fd) + return out_fd.takeError(); + debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); + + llvm::Expected<int> err_fd = err.GetWriteFileDescriptor(); + if (!err_fd) + return err_fd.takeError(); + debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); + + // The sourceInitFile option is not part of the DAP specification. It is an + // extension used by the test suite to prevent sourcing `.lldbinit` and + // changing its behavior. The CLI flag --no-lldbinit takes precedence over + // the DAP parameter. + bool should_source_init_files = !no_lldbinit && sourceInitFile; + if (should_source_init_files) { + debugger.SkipLLDBInitFiles(false); + debugger.SkipAppInitFiles(false); + lldb::SBCommandReturnObject init; + auto interp = debugger.GetCommandInterpreter(); + interp.SourceInitFileInGlobalDirectory(init); + interp.SourceInitFileInHomeDirectory(init); + } + + // Run initialization commands. + if (llvm::Error err = RunPreInitCommands()) + return err; + + auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand( + "lldb-dap", "Commands for managing lldb-dap."); + + if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) { + cmd.AddCommand( + "start-debugging", new StartDebuggingCommand(*this), + "Sends a startDebugging request from the debug adapter to the client " + "to start a child debug session of the same type as the caller."); + } + + cmd.AddCommand( + "repl-mode", new ReplModeCommand(*this), + "Get or set the repl behavior of lldb-dap evaluation requests."); + cmd.AddCommand("send-event", new SendEventCommand(*this), + "Sends an DAP event to the client."); + + StartEventThreads(); + return llvm::Error::success(); +} + void DAP::ProgressEventThread() { lldb::SBListener listener("lldb-dap.progress.listener"); debugger.GetBroadcaster().AddListener( @@ -1317,7 +1401,7 @@ void DAP::ProgressEventThread() { lldb::SBEvent event; bool done = false; while (!done) { - if (listener.WaitForEvent(1, event)) { + if (listener.WaitForEvent(UINT32_MAX, event)) { const auto event_mask = event.GetType(); if (event.BroadcasterMatchesRef(broadcaster)) { if (event_mask & eBroadcastBitStopProgressThread) { @@ -1368,195 +1452,6 @@ void DAP::ProgressEventThread() { } } -// All events from the debugger, target, process, thread and frames are -// received in this function that runs in its own thread. We are using a -// "FILE *" to output packets back to VS Code and they have mutexes in them -// them prevent multiple threads from writing simultaneously so no locking -// is required. -void DAP::EventThread() { - llvm::set_thread_name("lldb.DAP.client." + m_client_name + ".event_handler"); - lldb::SBEvent event; - lldb::SBListener listener = debugger.GetListener(); - broadcaster.AddListener(listener, eBroadcastBitStopEventThread); - debugger.GetBroadcaster().AddListener( - listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning); - - // listen for thread events. - listener.StartListeningForEventClass( - debugger, lldb::SBThread::GetBroadcasterClassName(), - lldb::SBThread::eBroadcastBitStackChanged); - - bool done = false; - while (!done) { - if (listener.WaitForEvent(1, event)) { - const auto event_mask = event.GetType(); - if (lldb::SBProcess::EventIsProcessEvent(event)) { - lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); - if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { - auto state = lldb::SBProcess::GetStateFromEvent(event); - switch (state) { - case lldb::eStateConnected: - case lldb::eStateDetached: - case lldb::eStateInvalid: - case lldb::eStateUnloaded: - break; - case lldb::eStateAttaching: - case lldb::eStateCrashed: - case lldb::eStateLaunching: - case lldb::eStateStopped: - case lldb::eStateSuspended: - // Only report a stopped event if the process was not - // automatically restarted. - if (!lldb::SBProcess::GetRestartedFromEvent(event)) { - SendStdOutStdErr(*this, process); - if (llvm::Error err = SendThreadStoppedEvent(*this)) - DAP_LOG_ERROR(log, std::move(err), - "({1}) reporting thread stopped: {0}", - m_client_name); - } - break; - case lldb::eStateRunning: - case lldb::eStateStepping: - WillContinue(); - SendContinuedEvent(*this); - break; - case lldb::eStateExited: - lldb::SBStream stream; - process.GetStatus(stream); - SendOutput(OutputType::Console, stream.GetData()); - - // When restarting, we can get an "exited" event for the process we - // just killed with the old PID, or even with no PID. In that case - // we don't have to terminate the session. - if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || - process.GetProcessID() == restarting_process_id) { - restarting_process_id = LLDB_INVALID_PROCESS_ID; - } else { - // Run any exit LLDB commands the user specified in the - // launch.json - RunExitCommands(); - SendProcessExitedEvent(*this, process); - SendTerminatedEvent(); - done = true; - } - break; - } - } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || - (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { - SendStdOutStdErr(*this, process); - } - } else if (lldb::SBTarget::EventIsTargetEvent(event)) { - if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || - event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || - event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { - const uint32_t num_modules = - lldb::SBTarget::GetNumModulesFromEvent(event); - const bool remove_module = - event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded; - - // NOTE: Both mutexes must be acquired to prevent deadlock when - // handling `modules_request`, which also requires both locks. - lldb::SBMutex api_mutex = GetAPIMutex(); - const std::scoped_lock<lldb::SBMutex, std::mutex> guard( - api_mutex, modules_mutex); - for (uint32_t i = 0; i < num_modules; ++i) { - lldb::SBModule module = - lldb::SBTarget::GetModuleAtIndexFromEvent(i, event); - - std::optional<protocol::Module> p_module = - CreateModule(target, module, remove_module); - if (!p_module) - continue; - - llvm::StringRef module_id = p_module->id; - - const bool module_exists = modules.contains(module_id); - if (remove_module && module_exists) { - modules.erase(module_id); - Send(protocol::Event{ - "module", ModuleEventBody{std::move(p_module).value(), - ModuleEventBody::eReasonRemoved}}); - } else if (module_exists) { - Send(protocol::Event{ - "module", ModuleEventBody{std::move(p_module).value(), - ModuleEventBody::eReasonChanged}}); - } else if (!remove_module) { - modules.insert(module_id); - Send(protocol::Event{ - "module", ModuleEventBody{std::move(p_module).value(), - ModuleEventBody::eReasonNew}}); - } - } - } - } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { - if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { - auto event_type = - lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); - auto bp = Breakpoint( - *this, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); - // If the breakpoint was set through DAP, it will have the - // BreakpointBase::kDAPBreakpointLabel. Regardless of whether - // locations were added, removed, or resolved, the breakpoint isn't - // going away and the reason is always "changed". - if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || - event_type & lldb::eBreakpointEventTypeLocationsRemoved || - event_type & lldb::eBreakpointEventTypeLocationsResolved) && - bp.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { - // As the DAP client already knows the path of this breakpoint, we - // don't need to send it back as part of the "changed" event. This - // avoids sending paths that should be source mapped. Note that - // CreateBreakpoint doesn't apply source mapping and certain - // implementation ignore the source part of this event anyway. - protocol::Breakpoint protocol_bp = bp.ToProtocolBreakpoint(); - - // "source" is not needed here, unless we add adapter data to be - // saved by the client. - if (protocol_bp.source && !protocol_bp.source->adapterData) - protocol_bp.source = std::nullopt; - - llvm::json::Object body; - body.try_emplace("breakpoint", protocol_bp); - body.try_emplace("reason", "changed"); - - llvm::json::Object bp_event = CreateEventObject("breakpoint"); - bp_event.try_emplace("body", std::move(body)); - - SendJSON(llvm::json::Value(std::move(bp_event))); - } - } - - } else if (lldb::SBThread::EventIsThreadEvent(event)) { - HandleThreadEvent(event); - } else if (event_mask & lldb::eBroadcastBitError || - event_mask & lldb::eBroadcastBitWarning) { - lldb::SBStructuredData data = - lldb::SBDebugger::GetDiagnosticFromEvent(event); - if (!data.IsValid()) - continue; - std::string type = GetStringValue(data.GetValueForKey("type")); - std::string message = GetStringValue(data.GetValueForKey("message")); - SendOutput(OutputType::Important, - llvm::formatv("{0}: {1}", type, message).str()); - } else if (event.BroadcasterMatchesRef(broadcaster)) { - if (event_mask & eBroadcastBitStopEventThread) { - done = true; - } - } - } - } -} - -void DAP::HandleThreadEvent(const lldb::SBEvent &event) { - uint32_t event_type = event.GetType(); - - if (event_type & lldb::SBThread::eBroadcastBitStackChanged) { - const lldb::SBThread evt_thread = lldb::SBThread::GetThreadFromEvent(event); - SendInvalidatedEvent(*this, {InvalidatedEventBody::eAreaStacks}, - evt_thread.GetThreadID()); - } -} - std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints( const protocol::Source &source, const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) { diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index b4f111e..0113922 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -10,6 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_DAP_H #include "DAPForward.h" +#include "DAPSessionManager.h" #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "InstructionBreakpoint.h" @@ -32,11 +33,9 @@ #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "lldb/Host/MainLoop.h" -#include "lldb/Utility/Status.h" #include "lldb/lldb-types.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -47,6 +46,7 @@ #include <condition_variable> #include <cstdint> #include <deque> +#include <map> #include <memory> #include <mutex> #include <optional> @@ -81,10 +81,12 @@ enum class ReplMode { Variable = 0, Command, Auto }; using DAPTransport = lldb_private::transport::JSONTransport<ProtocolDescriptor>; struct DAP final : public DAPTransport::MessageHandler { + friend class DAPSessionManager; + /// Path to the lldb-dap binary itself. static llvm::StringRef debug_adapter_path; - Log *log; + Log &log; DAPTransport &transport; lldb::SBFile in; OutputRedirector out; @@ -157,6 +159,11 @@ struct DAP final : public DAPTransport::MessageHandler { /// Whether to disable sourcing .lldbinit files. bool no_lldbinit; + /// Stores whether the initialize request specified a value for + /// lldbExtSourceInitFile. Used by the test suite to prevent sourcing + /// `.lldbinit` and changing its behavior. + bool sourceInitFile = true; + /// The initial thread list upon attaching. std::vector<protocol::Thread> initial_thread_list; @@ -185,12 +192,12 @@ struct DAP final : public DAPTransport::MessageHandler { /// Transport for this debug session. /// \param[in] loop /// Main loop associated with this instance. - DAP(Log *log, const ReplMode default_repl_mode, + DAP(Log &log, const ReplMode default_repl_mode, std::vector<std::string> pre_init_commands, bool no_lldbinit, llvm::StringRef client_name, DAPTransport &transport, lldb_private::MainLoop &loop); - ~DAP(); + ~DAP() override = default; /// DAP is not copyable. /// @{ @@ -408,9 +415,33 @@ struct DAP final : public DAPTransport::MessageHandler { lldb::SBMutex GetAPIMutex() const { return target.GetAPIMutex(); } + /// Get the client name for this DAP session. + llvm::StringRef GetClientName() const { return m_client_name; } + void StartEventThread(); void StartProgressEventThread(); + /// DAP debugger initialization functions. + /// @{ + + /// Perform complete DAP initialization for a new debugger. + llvm::Error InitializeDebugger(); + + /// Perform complete DAP initialization by reusing an existing debugger and + /// target. + /// + /// \param[in] debugger_id + /// The ID of the existing debugger to reuse. + /// + /// \param[in] target_id + /// The globally unique ID of the existing target to reuse. + llvm::Error InitializeDebugger(int debugger_id, lldb::user_id_t target_id); + + /// Start event handling threads based on client capabilities. + void StartEventThreads(); + + /// @} + /// Sets the given protocol `breakpoints` in the given `source`, while /// removing any existing breakpoints in the given source if they are not in /// `breakpoint`. @@ -453,11 +484,11 @@ private: /// Event threads. /// @{ - void EventThread(); - void HandleThreadEvent(const lldb::SBEvent &event); void ProgressEventThread(); - std::thread event_thread; + /// Event thread is a shared pointer in case we have a multiple + /// DAP instances sharing the same event thread. + std::shared_ptr<ManagedEventThread> event_thread_sp; std::thread progress_event_thread; /// @} diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h index 6620d5f..e7fbbf6 100644 --- a/lldb/tools/lldb-dap/DAPForward.h +++ b/lldb/tools/lldb-dap/DAPForward.h @@ -28,6 +28,7 @@ namespace lldb { class SBAttachInfo; class SBBreakpoint; class SBBreakpointLocation; +class SBBroadcaster; class SBCommandInterpreter; class SBCommandReturnObject; class SBCommunication; diff --git a/lldb/tools/lldb-dap/DAPLog.cpp b/lldb/tools/lldb-dap/DAPLog.cpp index f66784e..f3c8040 100644 --- a/lldb/tools/lldb-dap/DAPLog.cpp +++ b/lldb/tools/lldb-dap/DAPLog.cpp @@ -8,22 +8,27 @@ #include "DAPLog.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Chrono.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include <chrono> #include <mutex> -#include <system_error> using namespace llvm; namespace lldb_dap { -Log::Log(StringRef filename, std::error_code &EC) : m_stream(filename, EC) {} +void Log::Emit(StringRef message) { Emit(message, "", 0); } -void Log::WriteMessage(StringRef message) { - std::lock_guard<std::mutex> lock(m_mutex); - std::chrono::duration<double> now{ - std::chrono::system_clock::now().time_since_epoch()}; - m_stream << formatv("{0:f9} ", now.count()).str() << message << "\n"; +void Log::Emit(StringRef message, StringRef file, size_t line) { + std::lock_guard<Log::Mutex> lock(m_mutex); + const llvm::sys::TimePoint<> time = std::chrono::system_clock::now(); + m_stream << formatv("[{0:%H:%M:%S.%L}]", time) << " "; + if (!file.empty()) + m_stream << sys::path::filename(file) << ":" << line << " "; + if (!m_prefix.empty()) + m_stream << m_prefix; + m_stream << message << "\n"; m_stream.flush(); } diff --git a/lldb/tools/lldb-dap/DAPLog.h b/lldb/tools/lldb-dap/DAPLog.h index 484001a..2170e66 100644 --- a/lldb/tools/lldb-dap/DAPLog.h +++ b/lldb/tools/lldb-dap/DAPLog.h @@ -11,33 +11,28 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" -#include "llvm/Support/FormatAdapters.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" #include <mutex> #include <string> -#include <system_error> // Write a message to log, if logging is enabled. #define DAP_LOG(log, ...) \ do { \ - ::lldb_dap::Log *log_private = (log); \ - if (log_private) { \ - log_private->WriteMessage(::llvm::formatv(__VA_ARGS__).str()); \ - } \ + ::lldb_dap::Log &log_private = (log); \ + log_private.Emit(::llvm::formatv(__VA_ARGS__).str(), __FILE__, __LINE__); \ } while (0) // Write message to log, if error is set. In the log message refer to the error // with {0}. Error is cleared regardless of whether logging is enabled. #define DAP_LOG_ERROR(log, error, ...) \ do { \ - ::lldb_dap::Log *log_private = (log); \ + ::lldb_dap::Log &log_private = (log); \ ::llvm::Error error_private = (error); \ - if (log_private && error_private) { \ - log_private->WriteMessage( \ - ::lldb_dap::FormatError(::std::move(error_private), __VA_ARGS__)); \ - } else \ - ::llvm::consumeError(::std::move(error_private)); \ + if (error_private) \ + log_private.Emit( \ + ::lldb_dap::FormatError(::std::move(error_private), __VA_ARGS__), \ + __FILE__, __LINE__); \ } while (0) namespace lldb_dap { @@ -46,14 +41,32 @@ namespace lldb_dap { /// `DAP_LOG_ERROR` helpers. class Log final { public: - /// Creates a log file with the given filename. - Log(llvm::StringRef filename, std::error_code &EC); + using Mutex = std::mutex; - void WriteMessage(llvm::StringRef message); + Log(llvm::raw_ostream &stream, Mutex &mutex) + : m_stream(stream), m_mutex(mutex) {} + Log(llvm::StringRef prefix, const Log &log) + : m_prefix(prefix), m_stream(log.m_stream), m_mutex(log.m_mutex) {} + + /// Retuns a new Log instance with the associated prefix for all messages. + inline Log WithPrefix(llvm::StringRef prefix) const { + std::string full_prefix = + m_prefix.empty() ? prefix.str() : m_prefix + prefix.str(); + full_prefix += " "; + return Log(full_prefix, *this); + } + + /// Emit writes a message to the underlying stream. + void Emit(llvm::StringRef message); + + /// Emit writes a message to the underlying stream, including the file and + /// line the message originated from. + void Emit(llvm::StringRef message, llvm::StringRef file, size_t line); private: - std::mutex m_mutex; - llvm::raw_fd_ostream m_stream; + std::string m_prefix; + llvm::raw_ostream &m_stream; + Mutex &m_mutex; }; template <typename... Args> diff --git a/lldb/tools/lldb-dap/DAPSessionManager.cpp b/lldb/tools/lldb-dap/DAPSessionManager.cpp new file mode 100644 index 0000000..3fbdc26 --- /dev/null +++ b/lldb/tools/lldb-dap/DAPSessionManager.cpp @@ -0,0 +1,143 @@ +//===----------------------------------------------------------------------===// +// +// 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 "DAPSessionManager.h" +#include "DAP.h" +#include "EventHelper.h" +#include "lldb/API/SBBroadcaster.h" +#include "lldb/API/SBEvent.h" +#include "lldb/API/SBTarget.h" +#include "lldb/Host/MainLoopBase.h" +#include "llvm/Support/Threading.h" +#include "llvm/Support/WithColor.h" + +#include <chrono> +#include <mutex> + +namespace lldb_dap { + +ManagedEventThread::ManagedEventThread(lldb::SBBroadcaster broadcaster, + std::thread t) + : m_broadcaster(broadcaster), m_event_thread(std::move(t)) {} + +ManagedEventThread::~ManagedEventThread() { + if (m_event_thread.joinable()) { + m_broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); + m_event_thread.join(); + } +} + +DAPSessionManager &DAPSessionManager::GetInstance() { + static std::once_flag initialized; + static DAPSessionManager *instance = + nullptr; // NOTE: intentional leak to avoid issues with C++ destructor + // chain + + std::call_once(initialized, []() { instance = new DAPSessionManager(); }); + + return *instance; +} + +void DAPSessionManager::RegisterSession(lldb_private::MainLoop *loop, + DAP *dap) { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + m_active_sessions[loop] = dap; +} + +void DAPSessionManager::UnregisterSession(lldb_private::MainLoop *loop) { + std::unique_lock<std::mutex> lock(m_sessions_mutex); + m_active_sessions.erase(loop); + std::notify_all_at_thread_exit(m_sessions_condition, std::move(lock)); +} + +std::vector<DAP *> DAPSessionManager::GetActiveSessions() { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + std::vector<DAP *> sessions; + for (const auto &[loop, dap] : m_active_sessions) + if (dap) + sessions.emplace_back(dap); + return sessions; +} + +void DAPSessionManager::DisconnectAllSessions() { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + m_client_failed = false; + for (auto [loop, dap] : m_active_sessions) { + if (dap) { + if (llvm::Error error = dap->Disconnect()) { + m_client_failed = true; + llvm::WithColor::error() << "DAP client disconnected failed: " + << llvm::toString(std::move(error)) << "\n"; + } + loop->AddPendingCallback( + [](lldb_private::MainLoopBase &loop) { loop.RequestTermination(); }); + } + } +} + +llvm::Error DAPSessionManager::WaitForAllSessionsToDisconnect() { + std::unique_lock<std::mutex> lock(m_sessions_mutex); + m_sessions_condition.wait(lock, [this] { return m_active_sessions.empty(); }); + + // Check if any disconnection failed and return appropriate error. + if (m_client_failed) + return llvm::make_error<llvm::StringError>( + "disconnecting all clients failed", llvm::inconvertibleErrorCode()); + + return llvm::Error::success(); +} + +std::shared_ptr<ManagedEventThread> +DAPSessionManager::GetEventThreadForDebugger(lldb::SBDebugger debugger, + DAP *requesting_dap) { + lldb::user_id_t debugger_id = debugger.GetID(); + std::lock_guard<std::mutex> lock(m_sessions_mutex); + + // Try to use shared event thread, if it exists. + if (auto it = m_debugger_event_threads.find(debugger_id); + it != m_debugger_event_threads.end()) { + if (std::shared_ptr<ManagedEventThread> thread_sp = it->second.lock()) + return thread_sp; + // Our weak pointer has expired. + m_debugger_event_threads.erase(it); + } + + // Create a new event thread and store it. + auto new_thread_sp = std::make_shared<ManagedEventThread>( + requesting_dap->broadcaster, + std::thread(EventThread, debugger, requesting_dap->broadcaster, + requesting_dap->m_client_name, + std::ref(requesting_dap->log))); + m_debugger_event_threads[debugger_id] = new_thread_sp; + return new_thread_sp; +} + +DAP *DAPSessionManager::FindDAPForTarget(lldb::SBTarget target) { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + + for (const auto &[loop, dap] : m_active_sessions) + if (dap && dap->target.IsValid() && dap->target == target) + return dap; + + return nullptr; +} + +void DAPSessionManager::ReleaseExpiredEventThreads() { + std::lock_guard<std::mutex> lock(m_sessions_mutex); + for (auto it = m_debugger_event_threads.begin(); + it != m_debugger_event_threads.end();) { + // Check if the weak_ptr has expired (no DAP instances are using it + // anymore). + if (it->second.expired()) { + it = m_debugger_event_threads.erase(it); + } else { + ++it; + } + } +} + +} // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/DAPSessionManager.h b/lldb/tools/lldb-dap/DAPSessionManager.h new file mode 100644 index 0000000..ad76b08 --- /dev/null +++ b/lldb/tools/lldb-dap/DAPSessionManager.h @@ -0,0 +1,119 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file contains the declarations of the DAPSessionManager and +/// ManagedEventThread classes, which are used to multiple concurrent DAP +/// sessions in a single lldb-dap process. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H +#define LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H + +#include "lldb/API/SBBroadcaster.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBTarget.h" +#include "lldb/Host/MainLoop.h" +#include "lldb/lldb-types.h" +#include "llvm/Support/Error.h" +#include <condition_variable> +#include <map> +#include <memory> +#include <mutex> +#include <optional> +#include <thread> +#include <vector> + +namespace lldb_dap { + +// Forward declarations +struct DAP; + +class ManagedEventThread { +public: + // Constructor declaration + ManagedEventThread(lldb::SBBroadcaster broadcaster, std::thread t); + + ~ManagedEventThread(); + + ManagedEventThread(const ManagedEventThread &) = delete; + ManagedEventThread &operator=(const ManagedEventThread &) = delete; + +private: + lldb::SBBroadcaster m_broadcaster; + std::thread m_event_thread; +}; + +/// Global DAP session manager that manages multiple concurrent DAP sessions in +/// a single lldb-dap process. Handles session lifecycle tracking, coordinates +/// shared debugger event threads, and facilitates target handoff between +/// sessions for dynamically created targets. +class DAPSessionManager { +public: + /// Get the singleton instance of the DAP session manager. + static DAPSessionManager &GetInstance(); + + /// Register a DAP session. + void RegisterSession(lldb_private::MainLoop *loop, DAP *dap); + + /// Unregister a DAP session. Called by sessions when they complete their + /// disconnection, which unblocks WaitForAllSessionsToDisconnect(). + void UnregisterSession(lldb_private::MainLoop *loop); + + /// Get all active DAP sessions. + std::vector<DAP *> GetActiveSessions(); + + /// Disconnect all registered sessions by calling Disconnect() on + /// each and requesting their event loops to terminate. Used during + /// shutdown to force all sessions to begin disconnecting. + void DisconnectAllSessions(); + + /// Block until all sessions disconnect and unregister. Returns an error if + /// DisconnectAllSessions() was called and any disconnection failed. + llvm::Error WaitForAllSessionsToDisconnect(); + + /// Get or create event thread for a specific debugger. + std::shared_ptr<ManagedEventThread> + GetEventThreadForDebugger(lldb::SBDebugger debugger, DAP *requesting_dap); + + /// Find the DAP instance that owns the given target. + DAP *FindDAPForTarget(lldb::SBTarget target); + + /// Static convenience method for FindDAPForTarget. + static DAP *FindDAP(lldb::SBTarget target) { + return GetInstance().FindDAPForTarget(target); + } + + /// Clean up expired event threads from the collection. + void ReleaseExpiredEventThreads(); + +private: + DAPSessionManager() = default; + ~DAPSessionManager() = default; + + // Non-copyable and non-movable. + DAPSessionManager(const DAPSessionManager &) = delete; + DAPSessionManager &operator=(const DAPSessionManager &) = delete; + DAPSessionManager(DAPSessionManager &&) = delete; + DAPSessionManager &operator=(DAPSessionManager &&) = delete; + + bool m_client_failed = false; + std::mutex m_sessions_mutex; + std::condition_variable m_sessions_condition; + std::map<lldb_private::MainLoop *, DAP *> m_active_sessions; + + /// Map from debugger ID to its event thread, used when multiple DAP sessions + /// share the same debugger instance. + std::map<lldb::user_id_t, std::weak_ptr<ManagedEventThread>> + m_debugger_event_threads; +}; + +} // namespace lldb_dap + +#endif // LLDB_TOOLS_LLDB_DAP_DAPSESSIONMANAGER_H diff --git a/lldb/tools/lldb-dap/EventHelper.cpp b/lldb/tools/lldb-dap/EventHelper.cpp index 12d9e21..01d4547 100644 --- a/lldb/tools/lldb-dap/EventHelper.cpp +++ b/lldb/tools/lldb-dap/EventHelper.cpp @@ -7,16 +7,28 @@ //===----------------------------------------------------------------------===// #include "EventHelper.h" +#include "Breakpoint.h" +#include "BreakpointBase.h" #include "DAP.h" #include "DAPError.h" +#include "DAPLog.h" +#include "DAPSessionManager.h" +#include "Handler/ResponseHandler.h" #include "JSONUtils.h" #include "LLDBUtils.h" #include "Protocol/ProtocolEvents.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" +#include "ProtocolUtils.h" +#include "lldb/API/SBEvent.h" #include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBListener.h" #include "lldb/API/SBPlatform.h" +#include "lldb/API/SBStream.h" #include "llvm/Support/Error.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Threading.h" +#include <mutex> #include <utility> #if defined(_WIN32) @@ -306,4 +318,312 @@ void SendMemoryEvent(DAP &dap, lldb::SBValue variable) { dap.Send(protocol::Event{"memory", std::move(body)}); } +// Event handler functions that are called by EventThread. +// These handlers extract the necessary objects from events and find the +// appropriate DAP instance to handle them, maintaining compatibility with +// the original DAP::Handle*Event pattern while supporting multi-session +// debugging. + +static void HandleProcessEvent(const lldb::SBEvent &event, bool &process_exited, + Log &log) { + lldb::SBProcess process = lldb::SBProcess::GetProcessFromEvent(event); + + // Find the DAP instance that owns this process's target. + DAP *dap = DAPSessionManager::FindDAP(process.GetTarget()); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for process {0}", + process.GetProcessID()); + return; + } + + const uint32_t event_mask = event.GetType(); + + if (event_mask & lldb::SBProcess::eBroadcastBitStateChanged) { + auto state = lldb::SBProcess::GetStateFromEvent(event); + switch (state) { + case lldb::eStateConnected: + case lldb::eStateDetached: + case lldb::eStateInvalid: + case lldb::eStateUnloaded: + break; + case lldb::eStateAttaching: + case lldb::eStateCrashed: + case lldb::eStateLaunching: + case lldb::eStateStopped: + case lldb::eStateSuspended: + // Only report a stopped event if the process was not + // automatically restarted. + if (!lldb::SBProcess::GetRestartedFromEvent(event)) { + SendStdOutStdErr(*dap, process); + if (llvm::Error err = SendThreadStoppedEvent(*dap)) + DAP_LOG_ERROR(dap->log, std::move(err), + "({1}) reporting thread stopped: {0}", + dap->GetClientName()); + } + break; + case lldb::eStateRunning: + case lldb::eStateStepping: + dap->WillContinue(); + SendContinuedEvent(*dap); + break; + case lldb::eStateExited: + lldb::SBStream stream; + process.GetStatus(stream); + dap->SendOutput(OutputType::Console, stream.GetData()); + + // When restarting, we can get an "exited" event for the process we + // just killed with the old PID, or even with no PID. In that case + // we don't have to terminate the session. + if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || + process.GetProcessID() == dap->restarting_process_id) { + dap->restarting_process_id = LLDB_INVALID_PROCESS_ID; + } else { + // Run any exit LLDB commands the user specified in the + // launch.json + dap->RunExitCommands(); + SendProcessExitedEvent(*dap, process); + dap->SendTerminatedEvent(); + process_exited = true; + } + break; + } + } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || + (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { + SendStdOutStdErr(*dap, process); + } +} + +static void HandleTargetEvent(const lldb::SBEvent &event, Log &log) { + lldb::SBTarget target = lldb::SBTarget::GetTargetFromEvent(event); + + // Find the DAP instance that owns this target. + DAP *dap = DAPSessionManager::FindDAP(target); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for target"); + return; + } + + const uint32_t event_mask = event.GetType(); + if (event_mask & lldb::SBTarget::eBroadcastBitModulesLoaded || + event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsLoaded || + event_mask & lldb::SBTarget::eBroadcastBitSymbolsChanged) { + const uint32_t num_modules = lldb::SBTarget::GetNumModulesFromEvent(event); + const bool remove_module = + event_mask & lldb::SBTarget::eBroadcastBitModulesUnloaded; + + // NOTE: Both mutexes must be acquired to prevent deadlock when + // handling `modules_request`, which also requires both locks. + lldb::SBMutex api_mutex = dap->GetAPIMutex(); + const std::scoped_lock<lldb::SBMutex, std::mutex> guard(api_mutex, + dap->modules_mutex); + for (uint32_t i = 0; i < num_modules; ++i) { + lldb::SBModule module = + lldb::SBTarget::GetModuleAtIndexFromEvent(i, event); + + std::optional<protocol::Module> p_module = + CreateModule(dap->target, module, remove_module); + if (!p_module) + continue; + + llvm::StringRef module_id = p_module->id; + + const bool module_exists = dap->modules.contains(module_id); + if (remove_module && module_exists) { + dap->modules.erase(module_id); + dap->Send(protocol::Event{ + "module", protocol::ModuleEventBody{ + std::move(p_module).value(), + protocol::ModuleEventBody::eReasonRemoved}}); + } else if (module_exists) { + dap->Send(protocol::Event{ + "module", protocol::ModuleEventBody{ + std::move(p_module).value(), + protocol::ModuleEventBody::eReasonChanged}}); + } else if (!remove_module) { + dap->modules.insert(module_id); + dap->Send(protocol::Event{ + "module", + protocol::ModuleEventBody{std::move(p_module).value(), + protocol::ModuleEventBody::eReasonNew}}); + } + } + } else if (event_mask & lldb::SBTarget::eBroadcastBitNewTargetCreated) { + // For NewTargetCreated events, GetTargetFromEvent returns the parent + // target, and GetCreatedTargetFromEvent returns the newly created target. + lldb::SBTarget created_target = + lldb::SBTarget::GetCreatedTargetFromEvent(event); + + if (!target.IsValid() || !created_target.IsValid()) { + DAP_LOG(log, "Received NewTargetCreated event but parent or " + "created target is invalid"); + return; + } + + // Send a startDebugging reverse request with the debugger and target + // IDs. The new DAP instance will use these IDs to find the existing + // debugger and target via FindDebuggerWithID and + // FindTargetByGloballyUniqueID. + llvm::json::Object configuration; + configuration.try_emplace("type", "lldb"); + configuration.try_emplace("debuggerId", + created_target.GetDebugger().GetID()); + configuration.try_emplace("targetId", created_target.GetGloballyUniqueID()); + configuration.try_emplace("name", created_target.GetTargetSessionName()); + + llvm::json::Object request; + request.try_emplace("request", "attach"); + request.try_emplace("configuration", std::move(configuration)); + + dap->SendReverseRequest<LogFailureResponseHandler>("startDebugging", + std::move(request)); + } +} + +static void HandleBreakpointEvent(const lldb::SBEvent &event, Log &log) { + const uint32_t event_mask = event.GetType(); + if (!(event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged)) + return; + + lldb::SBBreakpoint bp = lldb::SBBreakpoint::GetBreakpointFromEvent(event); + if (!bp.IsValid()) + return; + + // Find the DAP instance that owns this breakpoint's target. + DAP *dap = DAPSessionManager::FindDAP(bp.GetTarget()); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for breakpoint"); + return; + } + + auto event_type = lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); + auto breakpoint = Breakpoint(*dap, bp); + // If the breakpoint was set through DAP, it will have the + // BreakpointBase::kDAPBreakpointLabel. Regardless of whether + // locations were added, removed, or resolved, the breakpoint isn't + // going away and the reason is always "changed". + if ((event_type & lldb::eBreakpointEventTypeLocationsAdded || + event_type & lldb::eBreakpointEventTypeLocationsRemoved || + event_type & lldb::eBreakpointEventTypeLocationsResolved) && + breakpoint.MatchesName(BreakpointBase::kDAPBreakpointLabel)) { + // As the DAP client already knows the path of this breakpoint, we + // don't need to send it back as part of the "changed" event. This + // avoids sending paths that should be source mapped. Note that + // CreateBreakpoint doesn't apply source mapping and certain + // implementation ignore the source part of this event anyway. + protocol::Breakpoint protocol_bp = breakpoint.ToProtocolBreakpoint(); + + // "source" is not needed here, unless we add adapter data to be + // saved by the client. + if (protocol_bp.source && !protocol_bp.source->adapterData) + protocol_bp.source = std::nullopt; + + llvm::json::Object body; + body.try_emplace("breakpoint", protocol_bp); + body.try_emplace("reason", "changed"); + + llvm::json::Object bp_event = CreateEventObject("breakpoint"); + bp_event.try_emplace("body", std::move(body)); + + dap->SendJSON(llvm::json::Value(std::move(bp_event))); + } +} + +static void HandleThreadEvent(const lldb::SBEvent &event, Log &log) { + uint32_t event_type = event.GetType(); + + if (!(event_type & lldb::SBThread::eBroadcastBitStackChanged)) + return; + + lldb::SBThread thread = lldb::SBThread::GetThreadFromEvent(event); + if (!thread.IsValid()) + return; + + // Find the DAP instance that owns this thread's process/target. + DAP *dap = DAPSessionManager::FindDAP(thread.GetProcess().GetTarget()); + if (!dap) { + DAP_LOG(log, "Unable to find DAP instance for thread"); + return; + } + + SendInvalidatedEvent(*dap, {protocol::InvalidatedEventBody::eAreaStacks}, + thread.GetThreadID()); +} + +static void HandleDiagnosticEvent(const lldb::SBEvent &event, Log &log) { + // Global debugger events - send to all DAP instances. + std::vector<DAP *> active_instances = + DAPSessionManager::GetInstance().GetActiveSessions(); + for (DAP *dap_instance : active_instances) { + if (!dap_instance) + continue; + + lldb::SBStructuredData data = + lldb::SBDebugger::GetDiagnosticFromEvent(event); + if (!data.IsValid()) + continue; + + std::string type = GetStringValue(data.GetValueForKey("type")); + std::string message = GetStringValue(data.GetValueForKey("message")); + dap_instance->SendOutput(OutputType::Important, + llvm::formatv("{0}: {1}", type, message).str()); + } +} + +// Note: EventThread() is architecturally different from the other functions in +// this file. While the functions above are event helpers that operate on a +// single DAP instance (taking `DAP &dap` as a parameter), EventThread() is a +// shared event processing loop that: +// 1. Listens to events from a shared debugger instance +// 2. Dispatches events to the appropriate handler, which internally finds the +// DAP instance using DAPSessionManager::FindDAP() +// 3. Handles events for multiple different DAP sessions +// This allows multiple DAP sessions to share a single debugger and event +// thread, which is essential for the target handoff mechanism where child +// processes/targets are debugged in separate DAP sessions. +// +// All events from the debugger, target, process, thread and frames are +// received in this function that runs in its own thread. We are using a +// "FILE *" to output packets back to VS Code and they have mutexes in them +// them prevent multiple threads from writing simultaneously so no locking +// is required. +void EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster, + llvm::StringRef client_name, Log &log) { + llvm::set_thread_name("lldb.DAP.client." + client_name + ".event_handler"); + lldb::SBListener listener = debugger.GetListener(); + broadcaster.AddListener(listener, eBroadcastBitStopEventThread); + debugger.GetBroadcaster().AddListener( + listener, lldb::eBroadcastBitError | lldb::eBroadcastBitWarning); + + // listen for thread events. + listener.StartListeningForEventClass( + debugger, lldb::SBThread::GetBroadcasterClassName(), + lldb::SBThread::eBroadcastBitStackChanged); + + lldb::SBEvent event; + bool done = false; + while (!done) { + if (!listener.WaitForEvent(UINT32_MAX, event)) + continue; + + const uint32_t event_mask = event.GetType(); + if (lldb::SBProcess::EventIsProcessEvent(event)) { + HandleProcessEvent(event, /*&process_exited=*/done, log); + } else if (lldb::SBTarget::EventIsTargetEvent(event)) { + HandleTargetEvent(event, log); + } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { + HandleBreakpointEvent(event, log); + } else if (lldb::SBThread::EventIsThreadEvent(event)) { + HandleThreadEvent(event, log); + } else if (event_mask & lldb::eBroadcastBitError || + event_mask & lldb::eBroadcastBitWarning) { + HandleDiagnosticEvent(event, log); + } else if (event.BroadcasterMatchesRef(broadcaster)) { + if (event_mask & eBroadcastBitStopEventThread) { + done = true; + } + } + } +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/EventHelper.h b/lldb/tools/lldb-dap/EventHelper.h index be783d0..b46d5ae 100644 --- a/lldb/tools/lldb-dap/EventHelper.h +++ b/lldb/tools/lldb-dap/EventHelper.h @@ -42,6 +42,17 @@ void SendInvalidatedEvent( void SendMemoryEvent(DAP &dap, lldb::SBValue variable); +/// Event thread function that handles debugger events for multiple DAP sessions +/// sharing the same debugger instance. This runs in its own thread and +/// dispatches events to the appropriate DAP instance. +/// +/// \param debugger The debugger instance to listen for events from. +/// \param broadcaster The broadcaster for stop event thread notifications. +/// \param client_name The client name for thread naming/logging purposes. +/// \param log The log instance for logging. +void EventThread(lldb::SBDebugger debugger, lldb::SBBroadcaster broadcaster, + llvm::StringRef client_name, Log &log); + } // namespace lldb_dap #endif diff --git a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp index 490513f..f0996eb 100644 --- a/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/AttachRequestHandler.cpp @@ -17,6 +17,7 @@ #include "lldb/lldb-defines.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include <cstdint> using namespace llvm; using namespace lldb_dap::protocol; @@ -29,14 +30,31 @@ namespace lldb_dap { /// Since attaching is debugger/runtime specific, the arguments for this request /// are not part of this specification. Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { + // Initialize DAP debugger and related components if not sharing previously + // launched debugger. + std::optional<int> debugger_id = args.debuggerId; + std::optional<lldb::user_id_t> target_id = args.targetId; + + // Validate that both debugger_id and target_id are provided together. + if (debugger_id.has_value() != target_id.has_value()) { + return llvm::createStringError( + "Both debuggerId and targetId must be specified together for debugger " + "reuse, or both must be omitted to create a new debugger"); + } + + if (Error err = debugger_id && target_id + ? dap.InitializeDebugger(*debugger_id, *target_id) + : dap.InitializeDebugger()) + return err; + // Validate that we have a well formed attach request. if (args.attachCommands.empty() && args.coreFile.empty() && args.configuration.program.empty() && args.pid == LLDB_INVALID_PROCESS_ID && - args.gdbRemotePort == LLDB_DAP_INVALID_PORT) + args.gdbRemotePort == LLDB_DAP_INVALID_PORT && !target_id.has_value()) return make_error<DAPError>( "expected one of 'pid', 'program', 'attachCommands', " - "'coreFile' or 'gdb-remote-port' to be specified"); + "'coreFile', 'gdb-remote-port', or target_id to be specified"); // Check if we have mutually exclusive arguments. if ((args.pid != LLDB_INVALID_PROCESS_ID) && @@ -64,7 +82,20 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { dap.ConfigureSourceMaps(); lldb::SBError error; - lldb::SBTarget target = dap.CreateTarget(error); + lldb::SBTarget target; + if (target_id) { + // Use the unique target ID to get the target. + target = dap.debugger.FindTargetByGloballyUniqueID(*target_id); + if (!target.IsValid()) { + error.SetErrorString( + llvm::formatv("invalid target_id {0} in attach config", *target_id) + .str() + .c_str()); + } + } else { + target = dap.CreateTarget(error); + } + if (error.Fail()) return ToError(error); @@ -114,7 +145,7 @@ Error AttachRequestHandler::Run(const AttachRequestArguments &args) const { connect_url += std::to_string(args.gdbRemotePort); dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", error); - } else { + } else if (!target_id.has_value()) { // Attach by pid or process name. lldb::SBAttachInfo attach_info; if (args.pid != LLDB_INVALID_PROCESS_ID) diff --git a/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp index cd93711..0e5c2b2 100644 --- a/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/CompileUnitsRequestHandler.cpp @@ -60,12 +60,12 @@ void CompileUnitsRequestHandler::operator()( llvm::json::Object body; llvm::json::Array units; const auto *arguments = request.getObject("arguments"); - const std::string module_id = - GetString(arguments, "moduleId").value_or("").str(); + const llvm::StringRef module_id = + GetString(arguments, "moduleId").value_or(""); int num_modules = dap.target.GetNumModules(); for (int i = 0; i < num_modules; i++) { auto curr_module = dap.target.GetModuleAtIndex(i); - if (module_id == curr_module.GetUUIDString()) { + if (module_id == llvm::StringRef(curr_module.GetUUIDString())) { int num_units = curr_module.GetNumCompileUnits(); for (int j = 0; j < num_units; j++) { auto curr_unit = curr_module.GetCompileUnitAtIndex(j); diff --git a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp index 87b93fc..245d92c 100644 --- a/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/DataBreakpointInfoRequestHandler.cpp @@ -7,15 +7,33 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "DAPError.h" #include "EventHelper.h" #include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "lldb/API/SBAddress.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "llvm/ADT/StringExtras.h" #include <optional> namespace lldb_dap { +static bool IsRW(DAP &dap, lldb::addr_t load_addr) { + if (!lldb::SBAddress(load_addr, dap.target).IsValid()) + return false; + lldb::SBMemoryRegionInfo region; + lldb::SBError err = + dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); + // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this + // request if SBProcess::GetMemoryRegionInfo returns error. + if (err.Success()) { + if (!(region.IsReadable() || region.IsWritable())) { + return false; + } + } + return true; +} + /// Obtains information on a possible data breakpoint that could be set on an /// expression or variable. Clients should only call this request if the /// corresponding capability supportsDataBreakpoints is true. @@ -23,7 +41,6 @@ llvm::Expected<protocol::DataBreakpointInfoResponseBody> DataBreakpointInfoRequestHandler::Run( const protocol::DataBreakpointInfoArguments &args) const { protocol::DataBreakpointInfoResponseBody response; - lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId); lldb::SBValue variable = dap.variables.FindVariable( args.variablesReference.value_or(0), args.name); std::string addr, size; @@ -43,7 +60,8 @@ DataBreakpointInfoRequestHandler::Run( addr = llvm::utohexstr(load_addr); size = llvm::utostr(byte_size); } - } else if (args.variablesReference.value_or(0) == 0 && frame.IsValid()) { + } else if (lldb::SBFrame frame = dap.GetLLDBFrame(args.frameId); + args.variablesReference.value_or(0) == 0 && frame.IsValid()) { lldb::SBValue value = frame.EvaluateExpression(args.name.c_str()); if (value.GetError().Fail()) { lldb::SBError error = value.GetError(); @@ -58,17 +76,10 @@ DataBreakpointInfoRequestHandler::Run( if (data.IsValid()) { size = llvm::utostr(data.GetByteSize()); addr = llvm::utohexstr(load_addr); - lldb::SBMemoryRegionInfo region; - lldb::SBError err = - dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); - // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this - // request if SBProcess::GetMemoryRegionInfo returns error. - if (err.Success()) { - if (!(region.IsReadable() || region.IsWritable())) { - is_data_ok = false; - response.description = "memory region for address " + addr + - " has no read or write permissions"; - } + if (!IsRW(dap, load_addr)) { + is_data_ok = false; + response.description = "memory region for address " + addr + + " has no read or write permissions"; } } else { is_data_ok = false; @@ -76,6 +87,17 @@ DataBreakpointInfoRequestHandler::Run( "unable to get byte size for expression: " + args.name; } } + } else if (args.asAddress) { + size = llvm::utostr(args.bytes.value_or(dap.target.GetAddressByteSize())); + lldb::addr_t load_addr = LLDB_INVALID_ADDRESS; + if (llvm::StringRef(args.name).getAsInteger<lldb::addr_t>(0, load_addr)) + return llvm::make_error<DAPError>(args.name + " is not a valid address", + llvm::inconvertibleErrorCode(), false); + addr = llvm::utohexstr(load_addr); + if (!IsRW(dap, load_addr)) + return llvm::make_error<DAPError>("memory region for address " + addr + + " has no read or write permissions", + llvm::inconvertibleErrorCode(), false); } else { is_data_ok = false; response.description = "variable not found: " + args.name; @@ -86,7 +108,10 @@ DataBreakpointInfoRequestHandler::Run( response.accessTypes = {protocol::eDataBreakpointAccessTypeRead, protocol::eDataBreakpointAccessTypeWrite, protocol::eDataBreakpointAccessTypeReadWrite}; - response.description = size + " bytes at " + addr + " " + args.name; + if (args.asAddress) + response.description = size + " bytes at " + addr; + else + response.description = size + " bytes at " + addr + " " + args.name; } return response; diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp index e155684..ec26bb6 100644 --- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp @@ -10,148 +10,31 @@ #include "EventHelper.h" #include "JSONUtils.h" #include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" +#include "lldb/lldb-enumerations.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +using namespace llvm; +using namespace lldb_dap; +using namespace lldb_dap::protocol; namespace lldb_dap { -// "EvaluateRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Evaluate request; value of command field is 'evaluate'. -// Evaluates the given expression in the context of the -// top most stack frame. The expression has access to any -// variables and arguments that are in scope.", -// "properties": { -// "command": { -// "type": "string", -// "enum": [ "evaluate" ] -// }, -// "arguments": { -// "$ref": "#/definitions/EvaluateArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "EvaluateArguments": { -// "type": "object", -// "description": "Arguments for 'evaluate' request.", -// "properties": { -// "expression": { -// "type": "string", -// "description": "The expression to evaluate." -// }, -// "frameId": { -// "type": "integer", -// "description": "Evaluate the expression in the scope of this stack -// frame. If not specified, the expression is evaluated -// in the global scope." -// }, -// "context": { -// "type": "string", -// "_enum": [ "watch", "repl", "hover" ], -// "enumDescriptions": [ -// "evaluate is run in a watch.", -// "evaluate is run from REPL console.", -// "evaluate is run from a data hover." -// ], -// "description": "The context in which the evaluate request is run." -// }, -// "format": { -// "$ref": "#/definitions/ValueFormat", -// "description": "Specifies details on how to format the Evaluate -// result." -// } -// }, -// "required": [ "expression" ] -// }, -// "EvaluateResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'evaluate' request.", -// "properties": { -// "body": { -// "type": "object", -// "properties": { -// "result": { -// "type": "string", -// "description": "The result of the evaluate request." -// }, -// "type": { -// "type": "string", -// "description": "The optional type of the evaluate result." -// }, -// "presentationHint": { -// "$ref": "#/definitions/VariablePresentationHint", -// "description": "Properties of a evaluate result that can be -// used to determine how to render the result in -// the UI." -// }, -// "variablesReference": { -// "type": "number", -// "description": "If variablesReference is > 0, the evaluate -// result is structured and its children can be -// retrieved by passing variablesReference to the -// VariablesRequest." -// }, -// "namedVariables": { -// "type": "number", -// "description": "The number of named child variables. The -// client can use this optional information to -// present the variables in a paged UI and fetch -// them in chunks." -// }, -// "indexedVariables": { -// "type": "number", -// "description": "The number of indexed child variables. The -// client can use this optional information to -// present the variables in a paged UI and fetch -// them in chunks." -// }, -// "valueLocationReference": { -// "type": "integer", -// "description": "A reference that allows the client to request -// the location where the returned value is -// declared. For example, if a function pointer is -// returned, the adapter may be able to look up the -// function's location. This should be present only -// if the adapter is likely to be able to resolve -// the location.\n\nThis reference shares the same -// lifetime as the `variablesReference`. See -// 'Lifetime of Object References' in the -// Overview section for details." -// } -// "memoryReference": { -// "type": "string", -// "description": "A memory reference to a location appropriate -// for this result. For pointer type eval -// results, this is generally a reference to the -// memory address contained in the pointer. This -// attribute may be returned by a debug adapter -// if corresponding capability -// `supportsMemoryReferences` is true." -// }, -// }, -// "required": [ "result", "variablesReference" ] -// } -// }, -// "required": [ "body" ] -// }] -// } -void EvaluateRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - llvm::json::Object body; - const auto *arguments = request.getObject("arguments"); - lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); - std::string expression = - GetString(arguments, "expression").value_or("").str(); - const llvm::StringRef context = GetString(arguments, "context").value_or(""); +/// Evaluates the given expression in the context of a stack frame. +/// +/// The expression has access to any variables and arguments that are in scope. +Expected<EvaluateResponseBody> +EvaluateRequestHandler::Run(const EvaluateArguments &arguments) const { + EvaluateResponseBody body; + lldb::SBFrame frame = dap.GetLLDBFrame(arguments.frameId); + std::string expression = arguments.expression; bool repeat_last_command = expression.empty() && dap.last_nonempty_var_expression.empty(); - if (context == "repl" && + if (arguments.context == protocol::eEvaluateContextRepl && (repeat_last_command || (!expression.empty() && dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) { @@ -165,70 +48,62 @@ void EvaluateRequestHandler::operator()( } bool required_command_failed = false; - std::string result = RunLLDBCommands( + body.result = RunLLDBCommands( dap.debugger, llvm::StringRef(), {expression}, required_command_failed, /*parse_command_directives=*/false, /*echo_commands=*/false); + return body; + } - EmplaceSafeString(body, "result", result); - body.try_emplace("variablesReference", (int64_t)0); - } else { - if (context == "repl") { - // If the expression is empty and the last expression was for a - // variable, set the expression to the previous expression (repeat the - // evaluation); otherwise save the current non-empty expression for the - // next (possibly empty) variable expression. - if (expression.empty()) - expression = dap.last_nonempty_var_expression; - else - dap.last_nonempty_var_expression = expression; - } - // Always try to get the answer from the local variables if possible. If - // this fails, then if the context is not "hover", actually evaluate an - // expression using the expression parser. - // - // "frame variable" is more reliable than the expression parser in - // many cases and it is faster. - lldb::SBValue value = frame.GetValueForVariablePath( - expression.data(), lldb::eDynamicDontRunTarget); - - // Freeze dry the value in case users expand it later in the debug console - if (value.GetError().Success() && context == "repl") - value = value.Persist(); - - if (value.GetError().Fail() && context != "hover") - value = frame.EvaluateExpression(expression.data()); - - if (value.GetError().Fail()) { - response["success"] = llvm::json::Value(false); - // This error object must live until we're done with the pointer returned - // by GetCString(). - lldb::SBError error = value.GetError(); - const char *error_cstr = error.GetCString(); - if (error_cstr && error_cstr[0]) - EmplaceSafeString(response, "message", error_cstr); - else - EmplaceSafeString(response, "message", "evaluate failed"); - } else { - VariableDescription desc(value, - dap.configuration.enableAutoVariableSummaries); - EmplaceSafeString(body, "result", desc.GetResult(context)); - EmplaceSafeString(body, "type", desc.display_type_name); - int64_t var_ref = 0; - if (value.MightHaveChildren() || ValuePointsToCode(value)) - var_ref = dap.variables.InsertVariable( - value, /*is_permanent=*/context == "repl"); - if (value.MightHaveChildren()) - body.try_emplace("variablesReference", var_ref); - else - body.try_emplace("variablesReference", (int64_t)0); - if (lldb::addr_t addr = value.GetLoadAddress(); - addr != LLDB_INVALID_ADDRESS) - body.try_emplace("memoryReference", EncodeMemoryReference(addr)); - if (ValuePointsToCode(value)) - body.try_emplace("valueLocationReference", var_ref); - } + if (arguments.context == eEvaluateContextRepl) { + // If the expression is empty and the last expression was for a + // variable, set the expression to the previous expression (repeat the + // evaluation); otherwise save the current non-empty expression for the + // next (possibly empty) variable expression. + if (expression.empty()) + expression = dap.last_nonempty_var_expression; + else + dap.last_nonempty_var_expression = expression; } - response.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(response))); + + // Always try to get the answer from the local variables if possible. If + // this fails, then if the context is not "hover", actually evaluate an + // expression using the expression parser. + // + // "frame variable" is more reliable than the expression parser in + // many cases and it is faster. + lldb::SBValue value = frame.GetValueForVariablePath( + expression.data(), lldb::eDynamicDontRunTarget); + + // Freeze dry the value in case users expand it later in the debug console + if (value.GetError().Success() && arguments.context == eEvaluateContextRepl) + value = value.Persist(); + + if (value.GetError().Fail() && arguments.context != eEvaluateContextHover) + value = frame.EvaluateExpression(expression.data()); + + if (value.GetError().Fail()) + return ToError(value.GetError(), /*show_user=*/false); + + const bool hex = arguments.format ? arguments.format->hex : false; + + VariableDescription desc(value, dap.configuration.enableAutoVariableSummaries, + hex); + + body.result = desc.GetResult(arguments.context); + body.type = desc.display_type_name; + + if (value.MightHaveChildren() || ValuePointsToCode(value)) + body.variablesReference = dap.variables.InsertVariable( + value, /*is_permanent=*/arguments.context == eEvaluateContextRepl); + + if (lldb::addr_t addr = value.GetLoadAddress(); addr != LLDB_INVALID_ADDRESS) + body.memoryReference = EncodeMemoryReference(addr); + + if (ValuePointsToCode(value) && + body.variablesReference != LLDB_DAP_INVALID_VAR_REF) + body.valueLocationReference = PackLocation(body.variablesReference, true); + + return body; } + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp index c1c2adb..ddf55e6f 100644 --- a/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/ExceptionInfoRequestHandler.cpp @@ -7,168 +7,75 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "EventHelper.h" -#include "JSONUtils.h" +#include "DAPError.h" +#include "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" #include "RequestHandler.h" #include "lldb/API/SBStream.h" +using namespace lldb_dap::protocol; + namespace lldb_dap { -// "ExceptionInfoRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Retrieves the details of the exception that -// caused this event to be raised. Clients should only call this request if -// the corresponding capability `supportsExceptionInfoRequest` is true.", -// "properties": { -// "command": { -// "type": "string", -// "enum": [ "exceptionInfo" ] -// }, -// "arguments": { -// "$ref": "#/definitions/ExceptionInfoArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "ExceptionInfoArguments": { -// "type": "object", -// "description": "Arguments for `exceptionInfo` request.", -// "properties": { -// "threadId": { -// "type": "integer", -// "description": "Thread for which exception information should be -// retrieved." -// } -// }, -// "required": [ "threadId" ] -// }, -// "ExceptionInfoResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to `exceptionInfo` request.", -// "properties": { -// "body": { -// "type": "object", -// "properties": { -// "exceptionId": { -// "type": "string", -// "description": "ID of the exception that was thrown." -// }, -// "description": { -// "type": "string", -// "description": "Descriptive text for the exception." -// }, -// "breakMode": { -// "$ref": "#/definitions/ExceptionBreakMode", -// "description": "Mode that caused the exception notification to -// be raised." -// }, -// "details": { -// "$ref": "#/definitions/ExceptionDetails", -// "description": "Detailed information about the exception." -// } -// }, -// "required": [ "exceptionId", "breakMode" ] -// } -// }, -// "required": [ "body" ] -// }] -// } -// "ExceptionDetails": { -// "type": "object", -// "description": "Detailed information about an exception that has -// occurred.", "properties": { -// "message": { -// "type": "string", -// "description": "Message contained in the exception." -// }, -// "typeName": { -// "type": "string", -// "description": "Short type name of the exception object." -// }, -// "fullTypeName": { -// "type": "string", -// "description": "Fully-qualified type name of the exception object." -// }, -// "evaluateName": { -// "type": "string", -// "description": "An expression that can be evaluated in the current -// scope to obtain the exception object." -// }, -// "stackTrace": { -// "type": "string", -// "description": "Stack trace at the time the exception was thrown." -// }, -// "innerException": { -// "type": "array", -// "items": { -// "$ref": "#/definitions/ExceptionDetails" -// }, -// "description": "Details of the exception contained by this exception, -// if any." -// } -// } -// }, -void ExceptionInfoRequestHandler::operator()( - const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); - const auto *arguments = request.getObject("arguments"); - llvm::json::Object body; - lldb::SBThread thread = dap.GetLLDBThread(*arguments); - if (thread.IsValid()) { - auto stopReason = thread.GetStopReason(); - if (stopReason == lldb::eStopReasonSignal) - body.try_emplace("exceptionId", "signal"); - else if (stopReason == lldb::eStopReasonBreakpoint) { - ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); - if (exc_bp) { - EmplaceSafeString(body, "exceptionId", exc_bp->GetFilter()); - EmplaceSafeString(body, "description", exc_bp->GetLabel()); - } else { - body.try_emplace("exceptionId", "exception"); - } +/// Retrieves the details of the exception that caused this event to be raised. +/// +/// Clients should only call this request if the corresponding capability +/// `supportsExceptionInfoRequest` is true. +llvm::Expected<ExceptionInfoResponseBody> +ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const { + + lldb::SBThread thread = dap.GetLLDBThread(args.threadId); + if (!thread.IsValid()) + return llvm::make_error<DAPError>( + llvm::formatv("Invalid thread id: {}", args.threadId).str()); + + ExceptionInfoResponseBody response; + response.breakMode = eExceptionBreakModeAlways; + const lldb::StopReason stop_reason = thread.GetStopReason(); + switch (stop_reason) { + case lldb::eStopReasonSignal: + response.exceptionId = "signal"; + break; + case lldb::eStopReasonBreakpoint: { + const ExceptionBreakpoint *exc_bp = + dap.GetExceptionBPFromStopReason(thread); + if (exc_bp) { + response.exceptionId = exc_bp->GetFilter(); + response.description = exc_bp->GetLabel(); } else { - body.try_emplace("exceptionId", "exception"); + response.exceptionId = "exception"; } - if (!ObjectContainsKey(body, "description")) { - char description[1024]; - if (thread.GetStopDescription(description, sizeof(description))) { - EmplaceSafeString(body, "description", description); - } + } break; + default: + response.exceptionId = "exception"; + } + + lldb::SBStream stream; + if (response.description.empty()) { + if (thread.GetStopDescription(stream)) { + response.description = {stream.GetData(), stream.GetSize()}; } - body.try_emplace("breakMode", "always"); - auto exception = thread.GetCurrentException(); - if (exception.IsValid()) { - llvm::json::Object details; - lldb::SBStream stream; - if (exception.GetDescription(stream)) { - EmplaceSafeString(details, "message", stream.GetData()); - } + } - auto exceptionBacktrace = thread.GetCurrentExceptionBacktrace(); - if (exceptionBacktrace.IsValid()) { - lldb::SBStream stream; - exceptionBacktrace.GetDescription(stream); - for (uint32_t i = 0; i < exceptionBacktrace.GetNumFrames(); i++) { - lldb::SBFrame frame = exceptionBacktrace.GetFrameAtIndex(i); - frame.GetDescription(stream); - } - EmplaceSafeString(details, "stackTrace", stream.GetData()); - } + if (lldb::SBValue exception = thread.GetCurrentException()) { + stream.Clear(); + response.details = ExceptionDetails{}; + if (exception.GetDescription(stream)) { + response.details->message = {stream.GetData(), stream.GetSize()}; + } + + if (lldb::SBThread exception_backtrace = + thread.GetCurrentExceptionBacktrace()) { + stream.Clear(); + exception_backtrace.GetDescription(stream); - body.try_emplace("details", std::move(details)); + for (uint32_t idx = 0; idx < exception_backtrace.GetNumFrames(); idx++) { + lldb::SBFrame frame = exception_backtrace.GetFrameAtIndex(idx); + frame.GetDescription(stream); + } + response.details->stackTrace = {stream.GetData(), stream.GetSize()}; } - // auto excInfoCount = thread.GetStopReasonDataCount(); - // for (auto i=0; i<excInfoCount; ++i) { - // uint64_t exc_data = thread.GetStopReasonDataAtIndex(i); - // } - } else { - response["success"] = llvm::json::Value(false); } - response.try_emplace("body", std::move(body)); - dap.SendJSON(llvm::json::Value(std::move(response))); + return response; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp index 9069de4..2d30e08 100644 --- a/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/InitializeRequestHandler.cpp @@ -21,63 +21,9 @@ using namespace lldb_dap::protocol; /// Initialize request; value of command field is 'initialize'. llvm::Expected<InitializeResponse> InitializeRequestHandler::Run( const InitializeRequestArguments &arguments) const { + // Store initialization arguments for later use in Launch/Attach. dap.clientFeatures = arguments.supportedFeatures; - - // Do not source init files until in/out/err are configured. - dap.debugger = lldb::SBDebugger::Create(false); - dap.debugger.SetInputFile(dap.in); - dap.target = dap.debugger.GetDummyTarget(); - - llvm::Expected<int> out_fd = dap.out.GetWriteFileDescriptor(); - if (!out_fd) - return out_fd.takeError(); - dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); - - llvm::Expected<int> err_fd = dap.err.GetWriteFileDescriptor(); - if (!err_fd) - return err_fd.takeError(); - dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); - - auto interp = dap.debugger.GetCommandInterpreter(); - - // The sourceInitFile option is not part of the DAP specification. It is an - // extension used by the test suite to prevent sourcing `.lldbinit` and - // changing its behavior. The CLI flag --no-lldbinit takes precedence over - // the DAP parameter. - bool should_source_init_files = - !dap.no_lldbinit && arguments.lldbExtSourceInitFile.value_or(true); - if (should_source_init_files) { - dap.debugger.SkipLLDBInitFiles(false); - dap.debugger.SkipAppInitFiles(false); - lldb::SBCommandReturnObject init; - interp.SourceInitFileInGlobalDirectory(init); - interp.SourceInitFileInHomeDirectory(init); - } - - if (llvm::Error err = dap.RunPreInitCommands()) - return err; - - auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand( - "lldb-dap", "Commands for managing lldb-dap."); - if (arguments.supportedFeatures.contains( - eClientFeatureStartDebuggingRequest)) { - cmd.AddCommand( - "start-debugging", new StartDebuggingCommand(dap), - "Sends a startDebugging request from the debug adapter to the client " - "to start a child debug session of the same type as the caller."); - } - cmd.AddCommand( - "repl-mode", new ReplModeCommand(dap), - "Get or set the repl behavior of lldb-dap evaluation requests."); - cmd.AddCommand("send-event", new SendEventCommand(dap), - "Sends an DAP event to the client."); - - if (arguments.supportedFeatures.contains(eClientFeatureProgressReporting)) - dap.StartProgressEventThread(); - - // Start our event thread so we can receive events from the debugger, target, - // process and more. - dap.StartEventThread(); + dap.sourceInitFile = arguments.lldbExtSourceInitFile; return dap.GetCapabilities(); } diff --git a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp index 553cbea..329f0a7 100644 --- a/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/LaunchRequestHandler.cpp @@ -22,6 +22,10 @@ namespace lldb_dap { /// Launch request; value of command field is 'launch'. Error LaunchRequestHandler::Run(const LaunchRequestArguments &arguments) const { + // Initialize DAP debugger. + if (Error err = dap.InitializeDebugger()) + return err; + // Validate that we have a well formed launch request. if (!arguments.launchCommands.empty() && arguments.console != protocol::eConsoleInternal) diff --git a/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp index 99917b2..9419f1a 100644 --- a/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/PauseRequestHandler.cpp @@ -8,53 +8,20 @@ #include "DAP.h" #include "EventHelper.h" -#include "JSONUtils.h" +#include "LLDBUtils.h" +#include "Protocol/ProtocolRequests.h" #include "RequestHandler.h" namespace lldb_dap { -// "PauseRequest": { -// "allOf": [ { "$ref": "#/definitions/Request" }, { -// "type": "object", -// "description": "Pause request; value of command field is 'pause'. The -// request suspenses the debuggee. The debug adapter first sends the -// PauseResponse and then a StoppedEvent (event type 'pause') after the -// thread has been paused successfully.", "properties": { -// "command": { -// "type": "string", -// "enum": [ "pause" ] -// }, -// "arguments": { -// "$ref": "#/definitions/PauseArguments" -// } -// }, -// "required": [ "command", "arguments" ] -// }] -// }, -// "PauseArguments": { -// "type": "object", -// "description": "Arguments for 'pause' request.", -// "properties": { -// "threadId": { -// "type": "integer", -// "description": "Pause execution for this thread." -// } -// }, -// "required": [ "threadId" ] -// }, -// "PauseResponse": { -// "allOf": [ { "$ref": "#/definitions/Response" }, { -// "type": "object", -// "description": "Response to 'pause' request. This is just an -// acknowledgement, so no body field is required." -// }] -// } -void PauseRequestHandler::operator()(const llvm::json::Object &request) const { - llvm::json::Object response; - FillResponse(request, response); +/// The request suspenses the debuggee. The debug adapter first sends the +/// PauseResponse and then a StoppedEvent (event type 'pause') after the thread +/// has been paused successfully. +llvm::Error +PauseRequestHandler::Run(const protocol::PauseArguments &args) const { lldb::SBProcess process = dap.target.GetProcess(); lldb::SBError error = process.Stop(); - dap.SendJSON(llvm::json::Value(std::move(response))); + return ToError(error); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index 977a247..fdce33d 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -292,24 +292,31 @@ public: Run(const std::optional<protocol::DisconnectArguments> &args) const override; }; -class EvaluateRequestHandler : public LegacyRequestHandler { +class EvaluateRequestHandler + : public RequestHandler<protocol::EvaluateArguments, + llvm::Expected<protocol::EvaluateResponseBody>> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "evaluate"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::EvaluateResponseBody> + Run(const protocol::EvaluateArguments &) const override; FeatureSet GetSupportedFeatures() const override { return {protocol::eAdapterFeatureEvaluateForHovers}; } }; -class ExceptionInfoRequestHandler : public LegacyRequestHandler { +class ExceptionInfoRequestHandler final + : public RequestHandler< + protocol::ExceptionInfoArguments, + llvm::Expected<protocol::ExceptionInfoResponseBody>> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "exceptionInfo"; } FeatureSet GetSupportedFeatures() const override { return {protocol::eAdapterFeatureExceptionInfoRequest}; } - void operator()(const llvm::json::Object &request) const override; + llvm::Expected<protocol::ExceptionInfoResponseBody> + Run(const protocol::ExceptionInfoArguments &args) const override; }; class InitializeRequestHandler @@ -428,6 +435,9 @@ class DataBreakpointInfoRequestHandler public: using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "dataBreakpointInfo"; } + FeatureSet GetSupportedFeatures() const override { + return {protocol::eAdapterFeatureDataBreakpointBytes}; + } llvm::Expected<protocol::DataBreakpointInfoResponseBody> Run(const protocol::DataBreakpointInfoArguments &args) const override; }; @@ -482,11 +492,12 @@ public: Run(const std::optional<protocol::ModulesArguments> &args) const override; }; -class PauseRequestHandler : public LegacyRequestHandler { +class PauseRequestHandler + : public RequestHandler<protocol::PauseArguments, protocol::PauseResponse> { public: - using LegacyRequestHandler::LegacyRequestHandler; + using RequestHandler::RequestHandler; static llvm::StringLiteral GetCommand() { return "pause"; } - void operator()(const llvm::json::Object &request) const override; + llvm::Error Run(const protocol::PauseArguments &args) const override; }; class ScopesRequestHandler final diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 1a3a670..40b4f5b 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -11,6 +11,7 @@ #include "ExceptionBreakpoint.h" #include "LLDBUtils.h" #include "Protocol/ProtocolBase.h" +#include "Protocol/ProtocolRequests.h" #include "ProtocolUtils.h" #include "lldb/API/SBAddress.h" #include "lldb/API/SBCompileUnit.h" @@ -553,9 +554,8 @@ llvm::json::Value CreateStackFrame(DAP &dap, lldb::SBFrame &frame, lldb::SBModule module = frame.GetModule(); if (module.IsValid()) { - std::string uuid = module.GetUUIDString(); - if (!uuid.empty()) - object.try_emplace("moduleId", uuid); + if (const llvm::StringRef uuid = module.GetUUIDString(); !uuid.empty()) + object.try_emplace("moduleId", uuid.str()); } return llvm::json::Value(std::move(object)); @@ -676,7 +676,14 @@ llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, EmplaceSafeString(body, "description", desc_str); } } break; - case lldb::eStopReasonWatchpoint: + case lldb::eStopReasonWatchpoint: { + body.try_emplace("reason", "data breakpoint"); + lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0); + body.try_emplace("hitBreakpointIds", + llvm::json::Array{llvm::json::Value(bp_id)}); + EmplaceSafeString(body, "description", + llvm::formatv("data breakpoint {0}", bp_id).str()); + } break; case lldb::eStopReasonInstrumentation: body.try_emplace("reason", "breakpoint"); break; @@ -817,10 +824,10 @@ VariableDescription::VariableDescription(lldb::SBValue v, evaluate_name = llvm::StringRef(evaluateStream.GetData()).str(); } -std::string VariableDescription::GetResult(llvm::StringRef context) { +std::string VariableDescription::GetResult(protocol::EvaluateContext context) { // In repl context, the results can be displayed as multiple lines so more // detailed descriptions can be returned. - if (context != "repl") + if (context != protocol::eEvaluateContextRepl) return display_value; if (!v.IsValid()) diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 0c865a3..329dc8a 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -10,7 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H #include "DAPForward.h" -#include "Protocol/ProtocolTypes.h" +#include "Protocol/ProtocolRequests.h" #include "lldb/API/SBCompileUnit.h" #include "lldb/API/SBFormat.h" #include "lldb/API/SBType.h" @@ -28,7 +28,7 @@ namespace lldb_dap { -/// Emplace a StringRef in a json::Object after enusring that the +/// Emplace a StringRef in a json::Object after ensuring that the /// string is valid UTF8. If not, first call llvm::json::fixUTF8 /// before emplacing. /// @@ -351,7 +351,7 @@ struct VariableDescription { std::optional<std::string> custom_name = {}); /// Returns a description of the value appropriate for the specified context. - std::string GetResult(llvm::StringRef context); + std::string GetResult(protocol::EvaluateContext context); }; /// Does the given variable have an associated value location? diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp index 4db6caa..e2ba2ee 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.cpp +++ b/lldb/tools/lldb-dap/LLDBUtils.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "LLDBUtils.h" +#include "DAPError.h" #include "JSONUtils.h" #include "lldb/API/SBCommandInterpreter.h" #include "lldb/API/SBCommandReturnObject.h" @@ -17,6 +18,7 @@ #include "lldb/API/SBThread.h" #include "lldb/lldb-enumerations.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Error.h" #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" @@ -214,13 +216,14 @@ GetStopDisassemblyDisplay(lldb::SBDebugger &debugger) { return result; } -llvm::Error ToError(const lldb::SBError &error) { +llvm::Error ToError(const lldb::SBError &error, bool show_user) { if (error.Success()) return llvm::Error::success(); - return llvm::createStringError( - std::error_code(error.GetError(), std::generic_category()), - error.GetCString()); + return llvm::make_error<DAPError>( + /*message=*/error.GetCString(), + /*EC=*/std::error_code(error.GetError(), std::generic_category()), + /*show_user=*/show_user); } std::string GetStringValue(const lldb::SBStructuredData &data) { diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h index 9db721a..a29d3d8 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.h +++ b/lldb/tools/lldb-dap/LLDBUtils.h @@ -243,7 +243,7 @@ private: lldb::StopDisassemblyType GetStopDisassemblyDisplay(lldb::SBDebugger &debugger); /// Take ownership of the stored error. -llvm::Error ToError(const lldb::SBError &error); +llvm::Error ToError(const lldb::SBError &error, bool show_user = true); /// Provides the string value if this data structure is a string type. std::string GetStringValue(const lldb::SBStructuredData &data); diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h index 42c6c88..09ce680 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolBase.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolBase.h @@ -31,11 +31,11 @@ namespace lldb_dap::protocol { // MARK: Base Protocol /// Message unique identifier type. -using Id = int64_t; +using Id = uint64_t; /// A unique identifier that indicates the `seq` field should be calculated by /// the current session. -static constexpr Id kCalculateSeq = INT64_MAX; +static constexpr Id kCalculateSeq = UINT64_MAX; /// A client or debug adapter initiated request. struct Request { diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp index b939335..95ecc7e 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.cpp @@ -8,6 +8,7 @@ #include "Protocol/ProtocolRequests.h" #include "JSONUtils.h" +#include "Protocol/ProtocolTypes.h" #include "lldb/lldb-defines.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringMap.h" @@ -215,12 +216,13 @@ bool fromJSON(const json::Value &Params, InitializeRequestArguments &IRA, } return OM.map("adapterID", IRA.adapterID) && - OM.map("clientID", IRA.clientID) && - OM.map("clientName", IRA.clientName) && OM.map("locale", IRA.locale) && - OM.map("linesStartAt1", IRA.linesStartAt1) && - OM.map("columnsStartAt1", IRA.columnsStartAt1) && + OM.mapOptional("clientID", IRA.clientID) && + OM.mapOptional("clientName", IRA.clientName) && + OM.mapOptional("locale", IRA.locale) && + OM.mapOptional("linesStartAt1", IRA.linesStartAt1) && + OM.mapOptional("columnsStartAt1", IRA.columnsStartAt1) && OM.mapOptional("pathFormat", IRA.pathFormat) && - OM.map("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile); + OM.mapOptional("$__lldb_sourceInitFile", IRA.lldbExtSourceInitFile); } bool fromJSON(const json::Value &Params, Configuration &C, json::Path P) { @@ -316,7 +318,9 @@ bool fromJSON(const json::Value &Params, AttachRequestArguments &ARA, O.mapOptional("waitFor", ARA.waitFor) && O.mapOptional("gdb-remote-port", ARA.gdbRemotePort) && O.mapOptional("gdb-remote-hostname", ARA.gdbRemoteHostname) && - O.mapOptional("coreFile", ARA.coreFile); + O.mapOptional("coreFile", ARA.coreFile) && + O.mapOptional("targetId", ARA.targetId) && + O.mapOptional("debuggerId", ARA.debuggerId); } bool fromJSON(const json::Value &Params, ContinueArguments &CA, json::Path P) { @@ -625,4 +629,76 @@ llvm::json::Value toJSON(const ModuleSymbolsResponseBody &DGMSR) { return result; } +bool fromJSON(const json::Value &Params, ExceptionInfoArguments &Args, + json::Path Path) { + json::ObjectMapper O(Params, Path); + return O && O.map("threadId", Args.threadId); +} + +json::Value toJSON(const ExceptionInfoResponseBody &ERB) { + json::Object result{{"exceptionId", ERB.exceptionId}, + {"breakMode", ERB.breakMode}}; + + if (!ERB.description.empty()) + result.insert({"description", ERB.description}); + if (ERB.details.has_value()) + result.insert({"details", *ERB.details}); + return result; +} + +static bool fromJSON(const llvm::json::Value &Params, EvaluateContext &C, + llvm::json::Path P) { + auto rawContext = Params.getAsString(); + if (!rawContext) { + P.report("expected a string"); + return false; + } + C = StringSwitch<EvaluateContext>(*rawContext) + .Case("watch", EvaluateContext::eEvaluateContextWatch) + .Case("repl", EvaluateContext::eEvaluateContextRepl) + .Case("hover", EvaluateContext::eEvaluateContextHover) + .Case("clipboard", EvaluateContext::eEvaluateContextClipboard) + .Case("variables", EvaluateContext::eEvaluateContextVariables) + .Default(eEvaluateContextUnknown); + return true; +} + +bool fromJSON(const llvm::json::Value &Params, EvaluateArguments &Args, + llvm::json::Path P) { + json::ObjectMapper O(Params, P); + return O && O.map("expression", Args.expression) && + O.mapOptional("frameId", Args.frameId) && + O.mapOptional("line", Args.line) && + O.mapOptional("column", Args.column) && + O.mapOptional("source", Args.source) && + O.mapOptional("context", Args.context) && + O.mapOptional("format", Args.format); +} + +llvm::json::Value toJSON(const EvaluateResponseBody &Body) { + json::Object result{{"result", Body.result}, + {"variablesReference", Body.variablesReference}}; + + if (!Body.type.empty()) + result.insert({"type", Body.type}); + if (Body.presentationHint) + result.insert({"presentationHint", Body.presentationHint}); + if (Body.namedVariables) + result.insert({"namedVariables", Body.namedVariables}); + if (Body.indexedVariables) + result.insert({"indexedVariables", Body.indexedVariables}); + if (!Body.memoryReference.empty()) + result.insert({"memoryReference", Body.memoryReference}); + if (Body.valueLocationReference != LLDB_DAP_INVALID_VALUE_LOC) + result.insert({"valueLocationReference", Body.valueLocationReference}); + + return result; +} + +bool fromJSON(const llvm::json::Value &Params, PauseArguments &Args, + llvm::json::Path Path) { + json::ObjectMapper O(Params, Path); + return O && O.map("threadId", Args.threadId); +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h index a85a68b..dc84e90 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolRequests.h @@ -108,23 +108,23 @@ struct InitializeRequestArguments { std::string adapterID; /// The ID of the client using this adapter. - std::optional<std::string> clientID; + std::string clientID; /// The human-readable name of the client using this adapter. - std::optional<std::string> clientName; + std::string clientName; /// The ISO-639 locale of the client using this adapter, e.g. en-US or de-CH. - std::optional<std::string> locale; + std::string locale; /// Determines in what format paths are specified. The default is `path`, /// which is the native format. PathFormat pathFormat = ePatFormatPath; /// If true all line numbers are 1-based (default). - std::optional<bool> linesStartAt1; + bool linesStartAt1 = true; /// If true all column numbers are 1-based (default). - std::optional<bool> columnsStartAt1; + bool columnsStartAt1 = true; /// The set of supported features reported by the client. llvm::DenseSet<ClientFeature> supportedFeatures; @@ -133,7 +133,7 @@ struct InitializeRequestArguments { /// @{ /// Source init files when initializing lldb::SBDebugger. - std::optional<bool> lldbExtSourceInitFile; + bool lldbExtSourceInitFile = true; /// @} }; @@ -350,6 +350,12 @@ struct AttachRequestArguments { /// Path to the core file to debug. std::string coreFile; + /// Unique ID of an existing target to attach to. + std::optional<lldb::user_id_t> targetId; + + /// ID of an existing debugger instance to use. + std::optional<int> debuggerId; + /// @} }; bool fromJSON(const llvm::json::Value &, AttachRequestArguments &, @@ -1039,6 +1045,156 @@ struct ModuleSymbolsResponseBody { }; llvm::json::Value toJSON(const ModuleSymbolsResponseBody &); +struct ExceptionInfoArguments { + /// Thread for which exception information should be retrieved. + lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; +}; +bool fromJSON(const llvm::json::Value &, ExceptionInfoArguments &, + llvm::json::Path); + +struct ExceptionInfoResponseBody { + /// ID of the exception that was thrown. + std::string exceptionId; + + /// Descriptive text for the exception. + std::string description; + + /// Mode that caused the exception notification to be raised. + ExceptionBreakMode breakMode = eExceptionBreakModeNever; + + /// Detailed information about the exception. + std::optional<ExceptionDetails> details; +}; +llvm::json::Value toJSON(const ExceptionInfoResponseBody &); + +/// The context in which the evaluate request is used. +enum EvaluateContext : unsigned { + /// An unspecified or unknown evaluate context. + eEvaluateContextUnknown = 0, + /// 'watch': evaluate is called from a watch view context. + eEvaluateContextWatch = 1, + /// 'repl': evaluate is called from a REPL context. + eEvaluateContextRepl = 2, + /// 'hover': evaluate is called to generate the debug hover contents. + /// This value should only be used if the corresponding capability + /// `supportsEvaluateForHovers` is true. + eEvaluateContextHover = 3, + /// 'clipboard': evaluate is called to generate clipboard contents. + /// This value should only be used if the corresponding capability + /// `supportsClipboardContext` is true. + eEvaluateContextClipboard = 4, + /// 'variables': evaluate is called from a variables view context. + eEvaluateContextVariables = 5, +}; + +/// Arguments for `evaluate` request. +struct EvaluateArguments { + /// The expression to evaluate. + std::string expression; + + /// Evaluate the expression in the scope of this stack frame. If not + /// specified, the expression is evaluated in the global scope. + uint64_t frameId = LLDB_DAP_INVALID_FRAME_ID; + + /// The contextual line where the expression should be evaluated. In the + /// 'hover' context, this should be set to the start of the expression being + /// hovered. + uint32_t line = LLDB_INVALID_LINE_NUMBER; + + /// The contextual column where the expression should be evaluated. This may + /// be provided if `line` is also provided. + /// + /// It is measured in UTF-16 code units and the client capability + /// `columnsStartAt1` determines whether it is 0- or 1-based. + uint32_t column = LLDB_INVALID_COLUMN_NUMBER; + + /// The contextual source in which the `line` is found. This must be provided + /// if `line` is provided. + std::optional<Source> source; + + /// The context in which the evaluate request is used. + /// Values: + /// 'watch': evaluate is called from a watch view context. + /// 'repl': evaluate is called from a REPL context. + /// 'hover': evaluate is called to generate the debug hover contents. + /// This value should only be used if the corresponding capability + /// `supportsEvaluateForHovers` is true. + /// 'clipboard': evaluate is called to generate clipboard contents. + /// This value should only be used if the corresponding capability + /// `supportsClipboardContext` is true. + /// 'variables': evaluate is called from a variables view context. + /// etc. + EvaluateContext context = eEvaluateContextUnknown; + + /// Specifies details on how to format the result. + /// The attribute is only honored by a debug adapter if the corresponding + /// capability `supportsValueFormattingOptions` is true. + std::optional<ValueFormat> format; +}; +bool fromJSON(const llvm::json::Value &, EvaluateArguments &, llvm::json::Path); + +/// Response to 'evaluate' request. +struct EvaluateResponseBody { + /// The result of the evaluate request. + std::string result; + + /// The type of the evaluate result. + /// This attribute should only be returned by a debug adapter if the + /// corresponding capability `supportsVariableType` is true. + std::string type; + + /// Properties of an evaluate result that can be used to determine how to + /// render the result in the UI. + std::optional<VariablePresentationHint> presentationHint; + + /// If `variablesReference` is > 0, the evaluate result is structured and its + /// children can be retrieved by passing `variablesReference` to the + /// `variables` request as long as execution remains suspended. See 'Lifetime + /// of Object References' in the Overview section for details. + int64_t variablesReference = 0; + + /// The number of named child variables. + /// The client can use this information to present the variables in a paged + /// UI and fetch them in chunks. + /// The value should be less than or equal to 2147483647 (2^31-1). + uint32_t namedVariables = 0; + + /// The number of indexed child variables. + /// The client can use this information to present the variables in a paged + /// UI and fetch them in chunks. + /// The value should be less than or equal to 2147483647 (2^31-1). + uint32_t indexedVariables = 0; + + /// A memory reference to a location appropriate for this result. + /// For pointer type eval results, this is generally a reference to the + /// memory address contained in the pointer. + /// This attribute may be returned by a debug adapter if corresponding + /// capability `supportsMemoryReferences` is true. + std::string memoryReference; + + /// A reference that allows the client to request the location where the + /// returned value is declared. For example, if a function pointer is + /// returned, the adapter may be able to look up the function's location. + /// This should be present only if the adapter is likely to be able to + /// resolve the location. + /// + /// This reference shares the same lifetime as the `variablesReference`. See + /// 'Lifetime of Object References' in the Overview section for details. + uint64_t valueLocationReference = LLDB_DAP_INVALID_VALUE_LOC; +}; +llvm::json::Value toJSON(const EvaluateResponseBody &); + +/// Arguments for `pause` request. +struct PauseArguments { + /// Pause execution for this thread. + lldb::tid_t threadId = LLDB_INVALID_THREAD_ID; +}; +bool fromJSON(const llvm::json::Value &, PauseArguments &, llvm::json::Path); + +/// Response to `pause` request. This is just an acknowledgement, so no body +/// field is required. +using PauseResponse = VoidResponse; + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp index dc8edaa..9500701 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.cpp @@ -1136,4 +1136,37 @@ bool fromJSON(const json::Value &Param, Variable &V, json::Path Path) { Path, /*required=*/false); } +json::Value toJSON(const ExceptionBreakMode Mode) { + switch (Mode) { + case eExceptionBreakModeNever: + return "never"; + case eExceptionBreakModeAlways: + return "always"; + case eExceptionBreakModeUnhandled: + return "unhandled"; + case eExceptionBreakModeUserUnhandled: + return "userUnhandled"; + } + llvm_unreachable("unhandled exception breakMode."); +} + +json::Value toJSON(const ExceptionDetails &ED) { + json::Object result; + + if (!ED.message.empty()) + result.insert({"message", ED.message}); + if (!ED.typeName.empty()) + result.insert({"typeName", ED.typeName}); + if (!ED.fullTypeName.empty()) + result.insert({"fullTypeName", ED.fullTypeName}); + if (!ED.evaluateName.empty()) + result.insert({"evaluateName", ED.evaluateName}); + if (!ED.stackTrace.empty()) + result.insert({"stackTrace", ED.stackTrace}); + if (!ED.innerException.empty()) + result.insert({"innerException", ED.innerException}); + + return result; +} + } // namespace lldb_dap::protocol diff --git a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h index 7077df9..ee103dd 100644 --- a/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h +++ b/lldb/tools/lldb-dap/Protocol/ProtocolTypes.h @@ -28,8 +28,9 @@ #include <optional> #include <string> -#define LLDB_DAP_INVALID_VARRERF UINT64_MAX +#define LLDB_DAP_INVALID_VAR_REF INT64_MAX #define LLDB_DAP_INVALID_SRC_REF 0 +#define LLDB_DAP_INVALID_VALUE_LOC 0 namespace lldb_dap::protocol { @@ -461,7 +462,7 @@ struct Scope { /// remains suspended. See 'Lifetime of Object References' in the Overview /// section for details. //// - uint64_t variablesReference = LLDB_DAP_INVALID_VARRERF; + uint64_t variablesReference = LLDB_DAP_INVALID_VAR_REF; /// The number of named variables in this scope. /// The client can use this information to present the variables in a paged UI @@ -1007,6 +1008,36 @@ struct Variable { llvm::json::Value toJSON(const Variable &); bool fromJSON(const llvm::json::Value &, Variable &, llvm::json::Path); +enum ExceptionBreakMode : unsigned { + eExceptionBreakModeNever, + eExceptionBreakModeAlways, + eExceptionBreakModeUnhandled, + eExceptionBreakModeUserUnhandled, +}; +llvm::json::Value toJSON(ExceptionBreakMode); + +struct ExceptionDetails { + /// Message contained in the exception. + std::string message; + + /// Short type name of the exception object. + std::string typeName; + + /// Fully-qualified type name of the exception object. + std::string fullTypeName; + + /// An expression that can be evaluated in the current scope to obtain the + /// exception object. + std::string evaluateName; + + /// Stack trace at the time the exception was thrown. + std::string stackTrace; + + /// Details of the exception contained by this exception, if any. + std::vector<ExceptionDetails> innerException; +}; +llvm::json::Value toJSON(const ExceptionDetails &); + } // namespace lldb_dap::protocol #endif diff --git a/lldb/tools/lldb-dap/ProtocolUtils.cpp b/lldb/tools/lldb-dap/ProtocolUtils.cpp index 868c67c..acf31b0 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.cpp +++ b/lldb/tools/lldb-dap/ProtocolUtils.cpp @@ -27,7 +27,7 @@ using namespace lldb_dap::protocol; namespace lldb_dap { static bool ShouldDisplayAssemblySource( - lldb::SBAddress address, + lldb::SBLineEntry line_entry, lldb::StopDisassemblyType stop_disassembly_display) { if (stop_disassembly_display == lldb::eStopDisassemblyTypeNever) return false; @@ -37,7 +37,6 @@ static bool ShouldDisplayAssemblySource( // A line entry of 0 indicates the line is compiler generated i.e. no source // file is associated with the frame. - auto line_entry = address.GetLineEntry(); auto file_spec = line_entry.GetFileSpec(); if (!file_spec.IsValid() || line_entry.GetLine() == 0 || line_entry.GetLine() == LLDB_INVALID_LINE_NUMBER) @@ -174,10 +173,10 @@ bool IsAssemblySource(const protocol::Source &source) { } bool DisplayAssemblySource(lldb::SBDebugger &debugger, - lldb::SBAddress address) { + lldb::SBLineEntry line_entry) { const lldb::StopDisassemblyType stop_disassembly_display = GetStopDisassemblyDisplay(debugger); - return ShouldDisplayAssemblySource(address, stop_disassembly_display); + return ShouldDisplayAssemblySource(line_entry, stop_disassembly_display); } std::string GetLoadAddressString(const lldb::addr_t addr) { diff --git a/lldb/tools/lldb-dap/ProtocolUtils.h b/lldb/tools/lldb-dap/ProtocolUtils.h index a1f7ae0..f4d576b 100644 --- a/lldb/tools/lldb-dap/ProtocolUtils.h +++ b/lldb/tools/lldb-dap/ProtocolUtils.h @@ -53,7 +53,8 @@ std::optional<protocol::Source> CreateSource(const lldb::SBFileSpec &file); /// Checks if the given source is for assembly code. bool IsAssemblySource(const protocol::Source &source); -bool DisplayAssemblySource(lldb::SBDebugger &debugger, lldb::SBAddress address); +bool DisplayAssemblySource(lldb::SBDebugger &debugger, + lldb::SBLineEntry line_entry); /// Get the address as a 16-digit hex string, e.g. "0x0000000000012345" std::string GetLoadAddressString(const lldb::addr_t addr); diff --git a/lldb/tools/lldb-dap/Transport.cpp b/lldb/tools/lldb-dap/Transport.cpp index 8f71f88..b351238 100644 --- a/lldb/tools/lldb-dap/Transport.cpp +++ b/lldb/tools/lldb-dap/Transport.cpp @@ -17,13 +17,13 @@ using namespace lldb_private; namespace lldb_dap { -Transport::Transport(llvm::StringRef client_name, lldb_dap::Log *log, - lldb::IOObjectSP input, lldb::IOObjectSP output) - : HTTPDelimitedJSONTransport(input, output), m_client_name(client_name), - m_log(log) {} +Transport::Transport(lldb_dap::Log &log, lldb::IOObjectSP input, + lldb::IOObjectSP output) + : HTTPDelimitedJSONTransport(input, output), m_log(log) {} void Transport::Log(llvm::StringRef message) { - DAP_LOG(m_log, "({0}) {1}", m_client_name, message); + // Emit the message directly, since this log was forwarded. + m_log.Emit(message); } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Transport.h b/lldb/tools/lldb-dap/Transport.h index 58c48c1..b20a934 100644 --- a/lldb/tools/lldb-dap/Transport.h +++ b/lldb/tools/lldb-dap/Transport.h @@ -35,15 +35,14 @@ class Transport final : public lldb_private::transport::HTTPDelimitedJSONTransport< ProtocolDescriptor> { public: - Transport(llvm::StringRef client_name, lldb_dap::Log *log, - lldb::IOObjectSP input, lldb::IOObjectSP output); + Transport(lldb_dap::Log &log, lldb::IOObjectSP input, + lldb::IOObjectSP output); virtual ~Transport() = default; void Log(llvm::StringRef message) override; private: - llvm::StringRef m_client_name; - lldb_dap::Log *m_log; + lldb_dap::Log &m_log; }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Watchpoint.cpp b/lldb/tools/lldb-dap/Watchpoint.cpp index 0acc980..e730e71 100644 --- a/lldb/tools/lldb-dap/Watchpoint.cpp +++ b/lldb/tools/lldb-dap/Watchpoint.cpp @@ -45,6 +45,7 @@ protocol::Breakpoint Watchpoint::ToProtocolBreakpoint() { breakpoint.message = m_error.GetCString(); } else { breakpoint.verified = true; + breakpoint.id = m_wp.GetID(); } return breakpoint; diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 05dce28..8e07c55 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -778,6 +778,10 @@ "description": "Custom commands that are executed instead of attaching to a process ID or to a process by name. These commands may optionally create a new target and must perform an attach. A valid process must exist after these commands complete or the \"attach\" will fail.", "default": [] }, + "targetId": { + "type": "number", + "description": "The globally unique target id to attach to. Used when a target is dynamically created." + }, "initCommands": { "type": "array", "items": { diff --git a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts index 7060638..433d48fa 100644 --- a/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts +++ b/lldb/tools/lldb-dap/src-ts/debug-adapter-factory.ts @@ -6,6 +6,7 @@ import * as fs from "node:fs/promises"; import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message"; import { ErrorWithNotification } from "./ui/error-with-notification"; import { LogFilePathProvider, LogType } from "./logging"; +import { expandUser } from "./utils"; const exec = util.promisify(child_process.execFile); @@ -116,8 +117,9 @@ async function getDAPExecutable( configuration: vscode.DebugConfiguration, ): Promise<string> { // Check if the executable was provided in the launch configuration. - const launchConfigPath = configuration["debugAdapterExecutable"]; + let launchConfigPath = configuration["debugAdapterExecutable"]; if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) { + launchConfigPath = expandUser(launchConfigPath); if (!(await isExecutable(launchConfigPath))) { throw new ErrorWithNotification( `Debug adapter path "${launchConfigPath}" is not a valid file. The path comes from your launch configuration.`, @@ -129,7 +131,7 @@ async function getDAPExecutable( // Check if the executable was provided in the extension's configuration. const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder); - const configPath = config.get<string>("executable-path"); + const configPath = expandUser(config.get<string>("executable-path") ?? ""); if (configPath && configPath.length !== 0) { if (!(await isExecutable(configPath))) { throw new ErrorWithNotification( diff --git a/lldb/tools/lldb-dap/src-ts/utils.ts b/lldb/tools/lldb-dap/src-ts/utils.ts new file mode 100644 index 0000000..efebe0b --- /dev/null +++ b/lldb/tools/lldb-dap/src-ts/utils.ts @@ -0,0 +1,41 @@ +import * as os from "os"; +import * as path from "path"; + +/** + * Expands the character `~` to the user's home directory + */ +export function expandUser(file_path: string): string { + if (os.platform() == "win32") { + return file_path; + } + + if (!file_path) { + return ""; + } + + if (!file_path.startsWith("~")) { + return file_path; + } + + const path_len = file_path.length; + if (path_len == 1) { + return os.homedir(); + } + + if (file_path.charAt(1) == path.sep) { + return path.join(os.homedir(), file_path.substring(1)); + } + + const sep_index = file_path.indexOf(path.sep); + const user_name_end = sep_index == -1 ? file_path.length : sep_index; + const user_name = file_path.substring(1, user_name_end); + try { + if (user_name == os.userInfo().username) { + return path.join(os.homedir(), file_path.substring(user_name_end)); + } + } catch (err) { + return file_path; + } + + return file_path; +} diff --git a/lldb/tools/lldb-dap/tool/lldb-dap.cpp b/lldb/tools/lldb-dap/tool/lldb-dap.cpp index f10ed12..6d4eaf1 100644 --- a/lldb/tools/lldb-dap/tool/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/tool/lldb-dap.cpp @@ -11,6 +11,7 @@ #include "DAPLog.h" #include "EventHelper.h" #include "Handler/RequestHandler.h" +#include "Handler/ResponseHandler.h" #include "RunInTerminal.h" #include "Transport.h" #include "lldb/API/SBDebugger.h" @@ -50,9 +51,7 @@ #include <cstddef> #include <cstdio> #include <cstdlib> -#include <exception> #include <fcntl.h> -#include <map> #include <memory> #include <mutex> #include <string> @@ -410,7 +409,7 @@ validateConnection(llvm::StringRef conn) { } static llvm::Error serveConnection( - const Socket::SocketProtocol &protocol, const std::string &name, Log *log, + const Socket::SocketProtocol &protocol, llvm::StringRef name, Log &log, const ReplMode default_repl_mode, const std::vector<std::string> &pre_init_commands, bool no_lldbinit, std::optional<std::chrono::seconds> connection_timeout_seconds) { @@ -445,12 +444,8 @@ static llvm::Error serveConnection( g_connection_timeout_time_point, connection_timeout_seconds.value()); std::condition_variable dap_sessions_condition; - std::mutex dap_sessions_mutex; - std::map<MainLoop *, DAP *> dap_sessions; unsigned int clientCount = 0; - auto handle = listener->Accept(g_loop, [=, &dap_sessions_condition, - &dap_sessions_mutex, &dap_sessions, - &clientCount]( + auto handle = listener->Accept(g_loop, [=, &log, &clientCount]( std::unique_ptr<Socket> sock) { // Reset the keep alive timer, because we won't be killing the server // while this connection is being served. @@ -458,18 +453,19 @@ static llvm::Error serveConnection( ResetConnectionTimeout(g_connection_timeout_mutex, g_connection_timeout_time_point); std::string client_name = llvm::formatv("client_{0}", clientCount++).str(); - DAP_LOG(log, "({0}) client connected", client_name); - lldb::IOObjectSP io(std::move(sock)); // Move the client into a background thread to unblock accepting the next // client. - std::thread client([=, &dap_sessions_condition, &dap_sessions_mutex, - &dap_sessions]() { + std::thread client([=, &log]() { llvm::set_thread_name(client_name + ".runloop"); + + Log client_log = log.WithPrefix("(" + client_name + ")"); + DAP_LOG(client_log, "client connected"); + MainLoop loop; - Transport transport(client_name, log, io, io); - DAP dap(log, default_repl_mode, pre_init_commands, no_lldbinit, + Transport transport(client_log, io, io); + DAP dap(client_log, default_repl_mode, pre_init_commands, no_lldbinit, client_name, transport, loop); if (auto Err = dap.ConfigureIO()) { @@ -478,10 +474,8 @@ static llvm::Error serveConnection( return; } - { - std::scoped_lock<std::mutex> lock(dap_sessions_mutex); - dap_sessions[&loop] = &dap; - } + // Register the DAP session with the global manager. + DAPSessionManager::GetInstance().RegisterSession(&loop, &dap); if (auto Err = dap.Loop()) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -489,11 +483,9 @@ static llvm::Error serveConnection( ") error: "); } - DAP_LOG(log, "({0}) client disconnected", client_name); - std::unique_lock<std::mutex> lock(dap_sessions_mutex); - dap_sessions.erase(&loop); - std::notify_all_at_thread_exit(dap_sessions_condition, std::move(lock)); - + DAP_LOG(client_log, "client disconnected"); + // Unregister the DAP session from the global manager. + DAPSessionManager::GetInstance().UnregisterSession(&loop); // Start the countdown to kill the server at the end of each connection. if (connection_timeout_seconds) TrackConnectionTimeout(g_loop, g_connection_timeout_mutex, @@ -512,33 +504,13 @@ static llvm::Error serveConnection( return status.takeError(); } - DAP_LOG( - log, - "lldb-dap server shutdown requested, disconnecting remaining clients..."); + DAP_LOG(log, "server shutting down, disconnecting remaining clients"); - bool client_failed = false; - { - std::scoped_lock<std::mutex> lock(dap_sessions_mutex); - for (auto [loop, dap] : dap_sessions) { - if (llvm::Error error = dap->Disconnect()) { - client_failed = true; - llvm::WithColor::error() << "DAP client disconnected failed: " - << llvm::toString(std::move(error)) << "\n"; - } - loop->AddPendingCallback( - [](MainLoopBase &loop) { loop.RequestTermination(); }); - } - } - - // Wait for all clients to finish disconnecting. - std::unique_lock<std::mutex> lock(dap_sessions_mutex); - dap_sessions_condition.wait(lock, [&] { return dap_sessions.empty(); }); + // Disconnect all active sessions using the global manager. + DAPSessionManager::GetInstance().DisconnectAllSessions(); - if (client_failed) - return llvm::make_error<llvm::StringError>( - "disconnecting all clients failed", llvm::inconvertibleErrorCode()); - - return llvm::Error::success(); + // Wait for all clients to finish disconnecting and return any errors. + return DAPSessionManager::GetInstance().WaitForAllSessionsToDisconnect(); } int main(int argc, char *argv[]) { @@ -669,17 +641,19 @@ int main(int argc, char *argv[]) { } #endif - std::unique_ptr<Log> log = nullptr; - const char *log_file_path = getenv("LLDBDAP_LOG"); - if (log_file_path) { - std::error_code EC; - log = std::make_unique<Log>(log_file_path, EC); - if (EC) { - llvm::logAllUnhandledErrors(llvm::errorCodeToError(EC), llvm::errs(), - "Failed to create log file: "); + std::unique_ptr<llvm::raw_ostream> log_os; + if (const char *log_file_path = getenv("LLDBDAP_LOG"); log_file_path) { + int FD; + if (std::error_code EC = + llvm::sys::fs::openFileForWrite(log_file_path, FD)) { + llvm::errs() << "Failed to open log file: " << log_file_path << ": " + << EC.message() << "\n"; return EXIT_FAILURE; } + log_os = std::make_unique<llvm::raw_fd_ostream>(FD, /*shouldClose=*/true); } + Log::Mutex mutex; + Log log(log_os ? *log_os : llvm::nulls(), mutex); // Initialize LLDB first before we do anything. lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling(); @@ -693,7 +667,7 @@ int main(int argc, char *argv[]) { // Create a memory monitor. This can return nullptr if the host platform is // not supported. std::unique_ptr<lldb_private::MemoryMonitor> memory_monitor = - lldb_private::MemoryMonitor::Create([log = log.get()]() { + lldb_private::MemoryMonitor::Create([&log]() { DAP_LOG(log, "memory pressure detected"); lldb::SBDebugger::MemoryPressureDetected(); }); @@ -727,7 +701,7 @@ int main(int argc, char *argv[]) { Socket::SocketProtocol protocol; std::string name; std::tie(protocol, name) = *maybeProtoclAndName; - if (auto Err = serveConnection(protocol, name, log.get(), default_repl_mode, + if (auto Err = serveConnection(protocol, name, log, default_repl_mode, pre_init_commands, no_lldbinit, connection_timeout_seconds)) { llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), @@ -764,8 +738,9 @@ int main(int argc, char *argv[]) { constexpr llvm::StringLiteral client_name = "stdio"; MainLoop loop; - Transport transport(client_name, log.get(), input, output); - DAP dap(log.get(), default_repl_mode, pre_init_commands, no_lldbinit, + Log client_log = log.WithPrefix("(stdio)"); + Transport transport(client_log, input, output); + DAP dap(client_log, default_repl_mode, pre_init_commands, no_lldbinit, client_name, transport, loop); // stdout/stderr redirection to the IDE's console @@ -775,16 +750,22 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + // Register the DAP session with the global manager for stdio mode. + // This is needed for the event handling to find the correct DAP instance. + DAPSessionManager::GetInstance().RegisterSession(&loop, &dap); + // used only by TestVSCode_redirection_to_console.py if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr) redirection_test(); if (auto Err = dap.Loop()) { - DAP_LOG(log.get(), "({0}) DAP session error: {1}", client_name, + DAP_LOG(client_log, "DAP session error: {0}", llvm::toStringWithoutConsuming(Err)); llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), "DAP session error: "); + DAPSessionManager::GetInstance().UnregisterSession(&loop); return EXIT_FAILURE; } + DAPSessionManager::GetInstance().UnregisterSession(&loop); return EXIT_SUCCESS; } diff --git a/lldb/tools/lldb-server/CMakeLists.txt b/lldb/tools/lldb-server/CMakeLists.txt index 1d8dc72..fb55c64 100644 --- a/lldb/tools/lldb-server/CMakeLists.txt +++ b/lldb/tools/lldb-server/CMakeLists.txt @@ -2,6 +2,10 @@ set(LLVM_TARGET_DEFINITIONS LLGSOptions.td) tablegen(LLVM LLGSOptions.inc -gen-opt-parser-defs) add_public_tablegen_target(LLGSOptionsTableGen) +set(LLVM_TARGET_DEFINITIONS PlatformOptions.td) +tablegen(LLVM PlatformOptions.inc -gen-opt-parser-defs) +add_public_tablegen_target(PlatformOptionsTableGen) + set(LLDB_PLUGINS) if(CMAKE_SYSTEM_NAME MATCHES "Linux|Android") @@ -67,6 +71,7 @@ add_lldb_tool(lldb-server add_dependencies(lldb-server LLGSOptionsTableGen + PlatformOptionsTableGen ${tablegen_deps} ) target_include_directories(lldb-server PRIVATE "${LLDB_SOURCE_DIR}/source") diff --git a/lldb/tools/lldb-server/PlatformOptions.td b/lldb/tools/lldb-server/PlatformOptions.td new file mode 100644 index 0000000..eedd1d8 --- /dev/null +++ b/lldb/tools/lldb-server/PlatformOptions.td @@ -0,0 +1,75 @@ +include "llvm/Option/OptParser.td" + +class F<string name>: Flag<["--", "-"], name>; +class R<list<string> prefixes, string name> + : Option<prefixes, name, KIND_REMAINING_ARGS>; + +multiclass SJ<string name, string help> { + def NAME: Separate<["--", "-"], name>, + HelpText<help>; + def NAME # _eq: Joined<["--", "-"], name # "=">, + Alias<!cast<Separate>(NAME)>; +} + +def grp_connect : OptionGroup<"connection">, HelpText<"CONNECTION OPTIONS">; + +defm listen: SJ<"listen", "Host and port to listen on. Format: [host]:port or protocol://[host]:port (e.g., tcp://localhost:1234, unix:///path/to/socket). Short form: -L">, + MetaVarName<"<[host]:port>">, + Group<grp_connect>; +def: Separate<["-"], "L">, Alias<listen>, + Group<grp_connect>; + +defm socket_file: SJ<"socket-file", "Write listening socket information (port number for TCP or path for Unix domain sockets) to the specified file. Short form: -f">, + MetaVarName<"<path>">, + Group<grp_connect>; +def: Separate<["-"], "f">, Alias<socket_file>, + Group<grp_connect>; + +defm gdbserver_port: SJ<"gdbserver-port", "Port to use for spawned gdbserver instances. If 0 or unspecified, a port will be chosen automatically. Short form: -P">, + MetaVarName<"<port>">, + Group<grp_connect>; +def: Separate<["-"], "P">, Alias<gdbserver_port>, + Group<grp_connect>; + +defm child_platform_fd: SJ<"child-platform-fd", "File descriptor for communication with parent platform process (internal use only).">, + MetaVarName<"<fd>">, + Group<grp_connect>, + Flags<[HelpHidden]>; + +def grp_general : OptionGroup<"general options">, HelpText<"GENERAL OPTIONS">; + +def server: F<"server">, + HelpText<"Run in server mode, accepting multiple client connections sequentially. Without this flag, the server exits after handling the first connection.">, + Group<grp_general>; + +defm log_channels: SJ<"log-channels", "Channels to log. A colon-separated list of entries. Each entry starts with a channel followed by a space-separated list of categories. Common channels: lldb, gdb-remote, platform, process. Short form: -c">, + MetaVarName<"<channel1 categories...:channel2 categories...>">, + Group<grp_general>; +def: Separate<["-"], "c">, Alias<log_channels>, + Group<grp_general>; + +defm log_file: SJ<"log-file", "Destination file to log to. If empty, log to stderr. Short form: -l">, + MetaVarName<"<file>">, + Group<grp_general>; +def: Separate<["-"], "l">, Alias<log_file>, + Group<grp_general>; + +def debug: F<"debug">, + HelpText<"(Unused, kept for backward compatibility)">, + Group<grp_general>, + Flags<[HelpHidden]>; + +def verbose: F<"verbose">, + HelpText<"(Unused, kept for backward compatibility)">, + Group<grp_general>, + Flags<[HelpHidden]>; + +def help: F<"help">, + HelpText<"Display this help message and exit.">, + Group<grp_general>; +def: Flag<["-"], "h">, Alias<help>, + Group<grp_general>; + +def REM : R<["--"], "">, + HelpText<"Arguments to pass to launched gdbserver instances.">, + MetaVarName<"program args">; diff --git a/lldb/tools/lldb-server/lldb-platform.cpp b/lldb/tools/lldb-server/lldb-platform.cpp index 0bd9285..59b1eb4 100644 --- a/lldb/tools/lldb-server/lldb-platform.cpp +++ b/lldb/tools/lldb-server/lldb-platform.cpp @@ -21,6 +21,9 @@ #include <fstream> #include <optional> +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ScopedPrinter.h" #include "llvm/Support/WithColor.h" @@ -56,22 +59,69 @@ using namespace llvm; // of target CPUs. For now, let's just use 100. static const int backlog = 100; static const int socket_error = -1; -static int g_debug = 0; -static int g_verbose = 0; -static int g_server = 0; - -// option descriptors for getopt_long_only() -static struct option g_long_options[] = { - {"debug", no_argument, &g_debug, 1}, - {"verbose", no_argument, &g_verbose, 1}, - {"log-file", required_argument, nullptr, 'l'}, - {"log-channels", required_argument, nullptr, 'c'}, - {"listen", required_argument, nullptr, 'L'}, - {"gdbserver-port", required_argument, nullptr, 'P'}, - {"socket-file", required_argument, nullptr, 'f'}, - {"server", no_argument, &g_server, 1}, - {"child-platform-fd", required_argument, nullptr, 2}, - {nullptr, 0, nullptr, 0}}; + +namespace { +using namespace llvm::opt; + +enum ID { + OPT_INVALID = 0, // This is not an option ID. +#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__), +#include "PlatformOptions.inc" +#undef OPTION +}; + +#define OPTTABLE_STR_TABLE_CODE +#include "PlatformOptions.inc" +#undef OPTTABLE_STR_TABLE_CODE + +#define OPTTABLE_PREFIXES_TABLE_CODE +#include "PlatformOptions.inc" +#undef OPTTABLE_PREFIXES_TABLE_CODE + +static constexpr opt::OptTable::Info InfoTable[] = { +#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__), +#include "PlatformOptions.inc" +#undef OPTION +}; + +class PlatformOptTable : public opt::GenericOptTable { +public: + PlatformOptTable() + : opt::GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {} + + void PrintHelp(llvm::StringRef Name) { + std::string Usage = + (Name + " [options] --listen <[host]:port> [[--] program args...]") + .str(); + + std::string Title = "lldb-server platform"; + + OptTable::printHelp(llvm::outs(), Usage.c_str(), Title.c_str()); + + llvm::outs() << R"( +DESCRIPTION + Acts as a platform server for remote debugging. When LLDB clients connect, + the platform server handles platform operations (file transfers, process + launching) and spawns debug server instances (lldb-server gdbserver) to + handle actual debugging sessions. + + By default, the server exits after handling one connection. Use --server + to keep running and accept multiple connections sequentially. + +EXAMPLES + # Listen on port 1234, exit after first connection + lldb-server platform --listen tcp://0.0.0.0:1234 + + # Listen on port 5555, accept multiple connections + lldb-server platform --server --listen tcp://localhost:5555 + + # Listen on Unix domain socket + lldb-server platform --listen unix:///tmp/lldb-server.sock + +)"; + } +}; +} // namespace #if defined(__APPLE__) #define LOW_PORT (IPPORT_RESERVED) @@ -97,12 +147,11 @@ static void signal_handler(int signo) { } #endif -static void display_usage(const char *progname, const char *subcommand) { - fprintf(stderr, "Usage:\n %s %s [--log-file log-file-name] [--log-channels " - "log-channel-list] [--port-file port-file-path] --server " - "--listen port\n", - progname, subcommand); - exit(0); +static void display_usage(PlatformOptTable &Opts, const char *progname, + const char *subcommand) { + std::string Name = + (llvm::sys::path::filename(progname) + " " + subcommand).str(); + Opts.PrintHelp(Name); } static Status parse_listen_host_port(Socket::SocketProtocol &protocol, @@ -261,7 +310,8 @@ static Status spawn_process(const char *progname, const FileSpec &prog, const Socket *conn_socket, uint16_t gdb_port, const lldb_private::Args &args, const std::string &log_file, - const StringRef log_channels, MainLoop &main_loop) { + const StringRef log_channels, MainLoop &main_loop, + bool multi_client) { Status error; SharedSocket shared_socket(conn_socket, error); if (error.Fail()) @@ -297,9 +347,12 @@ static Status spawn_process(const char *progname, const FileSpec &prog, launch_info.SetLaunchInSeparateProcessGroup(false); - if (g_server) + // Set up process monitor callback based on whether we're in server mode. + if (multi_client) + // In server mode: empty callback (don't terminate when child exits). launch_info.SetMonitorProcessCallback([](lldb::pid_t, int, int) {}); else + // In single-client mode: terminate main loop when child exits. launch_info.SetMonitorProcessCallback([&main_loop](lldb::pid_t, int, int) { main_loop.AddPendingCallback( [](MainLoopBase &loop) { loop.RequestTermination(); }); @@ -371,107 +424,101 @@ int main_platform(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); signal(SIGHUP, signal_handler); #endif - int long_option_index = 0; - Status error; - std::string listen_host_port; - int ch; - std::string log_file; - StringRef - log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" + // Special handling for 'help' as first argument. + if (argc > 0 && strcmp(argv[0], "help") == 0) { + PlatformOptTable Opts; + display_usage(Opts, progname, subcommand); + return EXIT_SUCCESS; + } + Status error; shared_fd_t fd = SharedSocket::kInvalidFD; - uint16_t gdbserver_port = 0; - FileSpec socket_file; - bool show_usage = false; - int option_error = 0; - std::string short_options(OptionParser::GetShortOptionString(g_long_options)); + PlatformOptTable Opts; + BumpPtrAllocator Alloc; + StringSaver Saver(Alloc); + bool HasError = false; -#if __GLIBC__ - optind = 0; -#else - optreset = 1; - optind = 1; -#endif + opt::InputArgList Args = + Opts.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](llvm::StringRef Msg) { + WithColor::error() << Msg << "\n"; + HasError = true; + }); - while ((ch = getopt_long_only(argc, argv, short_options.c_str(), - g_long_options, &long_option_index)) != -1) { - switch (ch) { - case 0: // Any optional that auto set themselves will return 0 - break; + std::string Name = + (llvm::sys::path::filename(progname) + " " + subcommand).str(); + std::string HelpText = + "Use '" + Name + " --help' for a complete list of options.\n"; - case 'L': - listen_host_port.append(optarg); - break; + if (HasError) { + llvm::errs() << HelpText; + return EXIT_FAILURE; + } - case 'l': // Set Log File - if (optarg && optarg[0]) - log_file.assign(optarg); - break; + if (Args.hasArg(OPT_help)) { + display_usage(Opts, progname, subcommand); + return EXIT_SUCCESS; + } - case 'c': // Log Channels - if (optarg && optarg[0]) - log_channels = StringRef(optarg); - break; + // Parse arguments. + std::string listen_host_port = Args.getLastArgValue(OPT_listen).str(); + std::string log_file = Args.getLastArgValue(OPT_log_file).str(); + StringRef log_channels = Args.getLastArgValue(OPT_log_channels); + bool multi_client = Args.hasArg(OPT_server); + [[maybe_unused]] bool debug = Args.hasArg(OPT_debug); + [[maybe_unused]] bool verbose = Args.hasArg(OPT_verbose); + + if (Args.hasArg(OPT_socket_file)) { + socket_file.SetFile(Args.getLastArgValue(OPT_socket_file), + FileSpec::Style::native); + } - case 'f': // Socket file - if (optarg && optarg[0]) - socket_file.SetFile(optarg, FileSpec::Style::native); - break; + if (Args.hasArg(OPT_gdbserver_port)) { + if (!llvm::to_integer(Args.getLastArgValue(OPT_gdbserver_port), + gdbserver_port)) { + WithColor::error() << "invalid --gdbserver-port value\n"; + return EXIT_FAILURE; + } + } - case 'P': - case 'm': - case 'M': { - uint16_t portnum; - if (!llvm::to_integer(optarg, portnum)) { - WithColor::error() << "invalid port number string " << optarg << "\n"; - option_error = 2; - break; - } - // Note the condition gdbserver_port > HIGH_PORT is valid in case of using - // --child-platform-fd. Check gdbserver_port later. - if (ch == 'P') - gdbserver_port = portnum; - else if (gdbserver_port == 0) - gdbserver_port = portnum; - } break; - - case 2: { - uint64_t _fd; - if (!llvm::to_integer(optarg, _fd)) { - WithColor::error() << "invalid fd " << optarg << "\n"; - option_error = 6; - } else - fd = (shared_fd_t)_fd; - } break; - - case 'h': /* fall-through is intentional */ - case '?': - show_usage = true; - break; + if (Args.hasArg(OPT_child_platform_fd)) { + uint64_t _fd; + if (!llvm::to_integer(Args.getLastArgValue(OPT_child_platform_fd), _fd)) { + WithColor::error() << "invalid --child-platform-fd value\n"; + return EXIT_FAILURE; } + fd = (shared_fd_t)_fd; } if (!LLDBServerUtilities::SetupLogging(log_file, log_channels, 0)) return -1; // Print usage and exit if no listening port is specified. - if (listen_host_port.empty() && fd == SharedSocket::kInvalidFD) - show_usage = true; + if (listen_host_port.empty() && fd == SharedSocket::kInvalidFD) { + WithColor::error() << "either --listen or --child-platform-fd is required\n" + << HelpText; + return EXIT_FAILURE; + } - if (show_usage || option_error) { - display_usage(progname, subcommand); - exit(option_error); + // Get remaining arguments for inferior. + std::vector<llvm::StringRef> Inputs; + for (opt::Arg *Arg : Args.filtered(OPT_INPUT)) + Inputs.push_back(Arg->getValue()); + if (opt::Arg *Arg = Args.getLastArg(OPT_REM)) { + for (const char *Val : Arg->getValues()) + Inputs.push_back(Val); } - // Skip any options we consumed with getopt_long_only. - argc -= optind; - argv += optind; lldb_private::Args inferior_arguments; - inferior_arguments.SetArguments(argc, const_cast<const char **>(argv)); + if (!Inputs.empty()) { + std::vector<const char *> args_ptrs; + for (const auto &Input : Inputs) + args_ptrs.push_back(Input.data()); + inferior_arguments.SetArguments(args_ptrs.size(), args_ptrs.data()); + } FileSpec debugserver_path = GetDebugserverPath(); if (!debugserver_path) { @@ -514,7 +561,7 @@ int main_platform(int argc, char *argv[]) { platform.SetConnection( std::make_unique<ConnectionFileDescriptor>(std::move(socket))); client_handle(platform, inferior_arguments); - return 0; + return EXIT_SUCCESS; } if (gdbserver_port != 0 && @@ -522,7 +569,7 @@ int main_platform(int argc, char *argv[]) { WithColor::error() << llvm::formatv("Port number {0} is not in the " "valid user port range of {1} - {2}\n", gdbserver_port, LOW_PORT, HIGH_PORT); - return 1; + return EXIT_FAILURE; } Socket::SocketProtocol protocol = Socket::ProtocolUnixDomain; @@ -559,7 +606,7 @@ int main_platform(int argc, char *argv[]) { if (error.Fail()) { fprintf(stderr, "failed to write socket id to %s: %s\n", socket_file.GetPath().c_str(), error.AsCString()); - return 1; + return EXIT_FAILURE; } } @@ -577,22 +624,22 @@ int main_platform(int argc, char *argv[]) { llvm::Expected<std::vector<MainLoopBase::ReadHandleUP>> platform_handles = platform_sock->Accept( main_loop, [progname, gdbserver_port, &inferior_arguments, log_file, - log_channels, &main_loop, + log_channels, &main_loop, multi_client, &platform_handles](std::unique_ptr<Socket> sock_up) { printf("Connection established.\n"); Status error = spawn_process( progname, HostInfo::GetProgramFileSpec(), sock_up.get(), gdbserver_port, inferior_arguments, log_file, log_channels, - main_loop); + main_loop, multi_client); if (error.Fail()) { Log *log = GetLog(LLDBLog::Platform); LLDB_LOGF(log, "spawn_process failed: %s", error.AsCString()); WithColor::error() << "spawn_process failed: " << error.AsCString() << "\n"; - if (!g_server) + if (!multi_client) main_loop.RequestTermination(); } - if (!g_server) + if (!multi_client) platform_handles->clear(); }); if (!platform_handles) { @@ -616,5 +663,5 @@ int main_platform(int argc, char *argv[]) { fprintf(stderr, "lldb-server exiting...\n"); - return 0; + return EXIT_SUCCESS; } diff --git a/lldb/tools/lldb-test/lldb-test.cpp b/lldb/tools/lldb-test/lldb-test.cpp index 3f198d9..84e83da 100644 --- a/lldb/tools/lldb-test/lldb-test.cpp +++ b/lldb/tools/lldb-test/lldb-test.cpp @@ -523,9 +523,10 @@ Error opts::symbols::findFunctions(lldb_private::Module &Module) { ContextOr->IsValid() ? *ContextOr : CompilerDeclContext(); List.Clear(); - lldb_private::Module::LookupInfo lookup_info( - ConstString(Name), getFunctionNameFlags(), eLanguageTypeUnknown); - Symfile.FindFunctions(lookup_info, ContextPtr, true, List); + std::vector<lldb_private::Module::LookupInfo> lookup_infos = + lldb_private::Module::LookupInfo::MakeLookupInfos( + ConstString(Name), getFunctionNameFlags(), eLanguageTypeUnknown); + Symfile.FindFunctions(lookup_infos, ContextPtr, true, List); } outs() << formatv("Found {0} functions:\n", List.GetSize()); StreamString Stream; diff --git a/lldb/unittests/Core/CMakeLists.txt b/lldb/unittests/Core/CMakeLists.txt index 6e609a6..d69432d 100644 --- a/lldb/unittests/Core/CMakeLists.txt +++ b/lldb/unittests/Core/CMakeLists.txt @@ -7,7 +7,9 @@ add_lldb_unittest(LLDBCoreTests DumpRegisterInfoTest.cpp FormatEntityTest.cpp MangledTest.cpp + ModuleListTest.cpp ModuleSpecTest.cpp + ModuleTest.cpp PluginManagerTest.cpp ProgressReportTest.cpp RichManglingContextTest.cpp diff --git a/lldb/unittests/Core/MangledTest.cpp b/lldb/unittests/Core/MangledTest.cpp index cbc0c5d..706e678 100644 --- a/lldb/unittests/Core/MangledTest.cpp +++ b/lldb/unittests/Core/MangledTest.cpp @@ -636,6 +636,16 @@ DemanglingPartsTestCase g_demangling_parts_test_cases[] = { /*.basename=*/"operator()", /*.scope=*/"dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::", /*.qualifiers=*/" const", + }, + {"_Z4funcILN3foo4EnumE1EEvv", + { + /*.BasenameRange=*/{5, 9}, /*.TemplateArgumentsRange=*/{9, 23}, /*.ScopeRange=*/{5, 5}, + /*.ArgumentsRange=*/{23, 25}, /*.QualifiersRange=*/{25, 25}, /*.NameQualifiersRange=*/{0, 0}, + /*.PrefixRange=*/{0, 0}, /*.SuffixRange=*/{0, 0} + }, + /*.basename=*/"func", + /*.scope=*/"", + /*.qualifiers=*/"", } // clang-format on }; diff --git a/lldb/unittests/Core/ModuleListTest.cpp b/lldb/unittests/Core/ModuleListTest.cpp new file mode 100644 index 0000000..3c70b0a --- /dev/null +++ b/lldb/unittests/Core/ModuleListTest.cpp @@ -0,0 +1,178 @@ +//===-- ModuleListTest.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 "lldb/Core/ModuleList.h" +#include "TestingSupport/SubsystemRAII.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/UUID.h" + +#include "Plugins/ObjectFile/ELF/ObjectFileELF.h" + +#include "gtest/gtest.h" + +using namespace lldb; +using namespace lldb_private; + +// Test that when we already have a module in the shared_module_list with a +// specific UUID, the next call to GetSharedModule with a module_spec with the +// same UUID should return the existing module instead of creating a new one. +TEST(ModuleListTest, GetSharedModuleReusesExistingModuleWithSameUUID) { + SubsystemRAII<FileSystem, ObjectFileELF> subsystems; + + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000010 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + // First, let's verify that calling GetSharedModule twice with the same + // module_spec returns the same module pointer + + ModuleSP first_module; + bool first_did_create = false; + Status error_first = + ModuleList::GetSharedModule(ExpectedFile->moduleSpec(), first_module, + nullptr, &first_did_create, false); + + // Second call with the same spec + ModuleSP second_module; + bool second_did_create = false; + Status error_second = + ModuleList::GetSharedModule(ExpectedFile->moduleSpec(), second_module, + nullptr, &second_did_create, false); + + if (error_first.Success() && error_second.Success()) { + // If both succeeded, verify they're the same module + EXPECT_EQ(first_module.get(), second_module.get()) + << "GetSharedModule should return the same module for the same spec"; + EXPECT_TRUE(first_did_create) << "First call should create the module"; + EXPECT_FALSE(second_did_create) + << "Second call should reuse the existing module"; + } +} + +// Test that UUID-based lookup finds existing modules +TEST(ModuleListTest, FindSharedModuleByUUID) { + SubsystemRAII<FileSystem, ObjectFileELF> subsystems; + + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000010 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + // Create and add a module to the shared module list using the moduleSpec() + ModuleSP created_module; + bool did_create = false; + Status error = ModuleList::GetSharedModule( + ExpectedFile->moduleSpec(), created_module, nullptr, &did_create, false); + + if (error.Success() && created_module) { + // Get the UUID of the created module + UUID module_uuid = created_module->GetUUID(); + + if (module_uuid.IsValid()) { + // Now try to find the module by UUID + ModuleSP found_module = ModuleList::FindSharedModule(module_uuid); + + ASSERT_NE(found_module.get(), nullptr) + << "FindSharedModule should find the module by UUID"; + EXPECT_EQ(found_module.get(), created_module.get()) + << "FindSharedModule should return the same module instance"; + EXPECT_EQ(found_module->GetUUID(), module_uuid) + << "Found module should have the same UUID"; + } + } +} + +// Test that GetSharedModule with UUID finds existing module even with different +// path +TEST(ModuleListTest, GetSharedModuleByUUIDIgnoresPath) { + SubsystemRAII<FileSystem, ObjectFileELF> subsystems; + + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + AddressAlign: 0x0000000000000010 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + // Create and add a module to the shared module list + ModuleSP first_module; + bool first_did_create = false; + Status first_error = + ModuleList::GetSharedModule(ExpectedFile->moduleSpec(), first_module, + nullptr, &first_did_create, false); + + if (first_error.Success() && first_module) { + UUID module_uuid = first_module->GetUUID(); + + if (module_uuid.IsValid()) { + // Now try to get a module with the same UUID but different path + ModuleSpec second_spec; + second_spec.GetFileSpec() = FileSpec("/different/path/to/module.so"); + second_spec.GetArchitecture() = ArchSpec("x86_64-pc-linux"); + second_spec.GetUUID() = module_uuid; + + ModuleSP second_module; + bool second_did_create = false; + Status second_error = ModuleList::GetSharedModule( + second_spec, second_module, nullptr, &second_did_create, false); + + if (second_error.Success() && second_module) { + // If we got a module back, check if it's the same one + bool is_same_module = (second_module.get() == first_module.get()); + + // Document the behavior: ideally UUID should take precedence + // and return the existing module + EXPECT_TRUE(is_same_module) + << "GetSharedModule with matching UUID should return existing " + "module, " + << "even with different path (per PR #160199)"; + + if (is_same_module) { + EXPECT_FALSE(second_did_create) + << "Should not create a new module when UUID matches"; + } + } + } + } +} diff --git a/lldb/unittests/Core/ModuleTest.cpp b/lldb/unittests/Core/ModuleTest.cpp new file mode 100644 index 0000000..011554d --- /dev/null +++ b/lldb/unittests/Core/ModuleTest.cpp @@ -0,0 +1,125 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Core/Module.h" +#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h" +#include "Plugins/ObjectFile/ELF/ObjectFileELF.h" +#include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h" +#include "TestingSupport/SubsystemRAII.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Target/Language.h" +#include "gtest/gtest.h" +#include <optional> + +using namespace lldb; +using namespace lldb_private; + +// Test that Module::FindFunctions correctly finds C++ mangled symbols +// even when multiple language plugins are registered. +TEST(ModuleTest, FindFunctionsCppMangledName) { + // Create a mock language. The point of this language is to return something + // in GetFunctionNameInfo that would interfere with the C++ language plugin, + // were they sharing the same LookupInfo. + class MockLanguageWithBogusLookupInfo : public Language { + public: + MockLanguageWithBogusLookupInfo() = default; + ~MockLanguageWithBogusLookupInfo() override = default; + + lldb::LanguageType GetLanguageType() const override { + // The language here doesn't really matter, it just has to be something + // that is not C/C++/ObjC. + return lldb::eLanguageTypeSwift; + } + + llvm::StringRef GetPluginName() override { return "mock-bogus-language"; } + + bool IsSourceFile(llvm::StringRef file_path) const override { + return file_path.ends_with(".swift"); + } + + std::pair<lldb::FunctionNameType, std::optional<ConstString>> + GetFunctionNameInfo(ConstString name) const override { + // Say that every function is a selector. + return {lldb::eFunctionNameTypeSelector, ConstString("BOGUS_BASENAME")}; + } + + static void Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), "Mock Language", + CreateInstance); + } + + static void Terminate() { PluginManager::UnregisterPlugin(CreateInstance); } + + static lldb_private::Language *CreateInstance(lldb::LanguageType language) { + if (language == lldb::eLanguageTypeSwift) + return new MockLanguageWithBogusLookupInfo(); + return nullptr; + } + + static llvm::StringRef GetPluginNameStatic() { + return "mock-bogus-language"; + } + }; + SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab, + CPlusPlusLanguage, MockLanguageWithBogusLookupInfo> + subsystems; + + // Create a simple ELF module with std::vector::size() as the only symbol. + auto ExpectedFile = TestFile::fromYaml(R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1000 + AddressAlign: 0x10 + Size: 0x100 +Symbols: + - Name: _ZNSt6vectorIiE4sizeEv + Type: STT_FUNC + Section: .text + Value: 0x1030 + Size: 0x20 +... +)"); + ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded()); + + auto module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec()); + + // Verify both C++ and our mock language are registered. + Language *cpp_lang = Language::FindPlugin(lldb::eLanguageTypeC_plus_plus); + Language *mock_lang = Language::FindPlugin(lldb::eLanguageTypeSwift); + ASSERT_NE(cpp_lang, nullptr) << "C++ language plugin should be registered"; + ASSERT_NE(mock_lang, nullptr) + << "Mock Swift language plugin should be registered"; + + ModuleFunctionSearchOptions function_options; + function_options.include_symbols = true; + + ConstString symbol_name("_ZNSt6vectorIiE4sizeEv"); + SymbolContextList results; + module_sp->FindFunctions(symbol_name, CompilerDeclContext(), + eFunctionNameTypeAuto, function_options, results); + + // Assert that we found one symbol. + ASSERT_EQ(results.GetSize(), 1u); + + auto result = results[0]; + auto name = result.GetFunctionName(); + // Assert that the symbol we found is what we expected. + ASSERT_EQ(name, "std::vector<int>::size()"); + ASSERT_EQ(result.GetLanguage(), eLanguageTypeC_plus_plus); +} diff --git a/lldb/unittests/DAP/CMakeLists.txt b/lldb/unittests/DAP/CMakeLists.txt index b1fdef1..9fef37e 100644 --- a/lldb/unittests/DAP/CMakeLists.txt +++ b/lldb/unittests/DAP/CMakeLists.txt @@ -1,6 +1,8 @@ add_lldb_unittest(DAPTests ClientLauncherTest.cpp DAPErrorTest.cpp + DAPLogTest.cpp + DAPSessionManagerTest.cpp DAPTest.cpp DAPTypesTest.cpp FifoFilesTest.cpp @@ -8,6 +10,7 @@ add_lldb_unittest(DAPTests Handler/ContinueTest.cpp JSONUtilsTest.cpp LLDBUtilsTest.cpp + ProtocolRequestsTest.cpp ProtocolTypesTest.cpp ProtocolUtilsTest.cpp TestBase.cpp diff --git a/lldb/unittests/DAP/DAPLogTest.cpp b/lldb/unittests/DAP/DAPLogTest.cpp new file mode 100644 index 0000000..2756e77 --- /dev/null +++ b/lldb/unittests/DAP/DAPLogTest.cpp @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// 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 "DAPLog.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace lldb_dap; +using namespace llvm; +using namespace testing; + +static llvm::StringRef last_line(llvm::StringRef str) { + size_t index = str.find_last_of('\n', str.size() - 1); + if (index == llvm::StringRef::npos) + return str; + return str.substr(index + 1); +} + +#define TIMESTAMP_PATTERN "\\[[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}\\] " + +TEST(DAPLog, Emit) { + Log::Mutex mux; + std::string outs; + raw_string_ostream os(outs); + Log log(os, mux); + Log inner_log = log.WithPrefix("my_prefix:"); + + log.Emit("Hi"); + EXPECT_THAT(last_line(outs), MatchesRegex(TIMESTAMP_PATTERN "Hi\n")); + + inner_log.Emit("foobar"); + EXPECT_THAT(last_line(outs), + MatchesRegex(TIMESTAMP_PATTERN "my_prefix: foobar\n")); + + log.Emit("Hello from a file/line.", "file.cpp", 42); + EXPECT_THAT( + last_line(outs), + MatchesRegex(TIMESTAMP_PATTERN "file.cpp:42 Hello from a file/line.\n")); + + inner_log.Emit("Hello from a file/line.", "file.cpp", 42); + EXPECT_THAT(last_line(outs), + MatchesRegex(TIMESTAMP_PATTERN + "file.cpp:42 my_prefix: Hello from a file/line.\n")); + + log.WithPrefix("a").WithPrefix("b").WithPrefix("c").Emit("msg"); + EXPECT_THAT(last_line(outs), MatchesRegex(TIMESTAMP_PATTERN "a b c msg\n")); +} diff --git a/lldb/unittests/DAP/DAPSessionManagerTest.cpp b/lldb/unittests/DAP/DAPSessionManagerTest.cpp new file mode 100644 index 0000000..b840d31 --- /dev/null +++ b/lldb/unittests/DAP/DAPSessionManagerTest.cpp @@ -0,0 +1,103 @@ +//===-- DAPSessionManagerTest.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 "DAPSessionManager.h" +#include "TestBase.h" +#include "lldb/API/SBDebugger.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace lldb_dap; +using namespace lldb; +using namespace lldb_dap_tests; + +class DAPSessionManagerTest : public DAPTestBase {}; + +TEST_F(DAPSessionManagerTest, GetInstanceReturnsSameSingleton) { + DAPSessionManager &instance1 = DAPSessionManager::GetInstance(); + DAPSessionManager &instance2 = DAPSessionManager::GetInstance(); + + EXPECT_EQ(&instance1, &instance2); +} + +// UnregisterSession uses std::notify_all_at_thread_exit, so it must be called +// from a separate thread to properly release the mutex on thread exit. +TEST_F(DAPSessionManagerTest, RegisterAndUnregisterSession) { + DAPSessionManager &manager = DAPSessionManager::GetInstance(); + + // Initially not registered. + std::vector<DAP *> sessions_before = manager.GetActiveSessions(); + EXPECT_EQ( + std::count(sessions_before.begin(), sessions_before.end(), dap.get()), 0); + + manager.RegisterSession(&loop, dap.get()); + + // Should be in active sessions after registration. + std::vector<DAP *> sessions_after = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions_after.begin(), sessions_after.end(), dap.get()), + 1); + + // Unregister. + std::thread unregister_thread([&]() { manager.UnregisterSession(&loop); }); + + unregister_thread.join(); + + // There should no longer be active sessions. + std::vector<DAP *> sessions_final = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions_final.begin(), sessions_final.end(), dap.get()), + 0); +} + +TEST_F(DAPSessionManagerTest, DisconnectAllSessions) { + DAPSessionManager &manager = DAPSessionManager::GetInstance(); + + manager.RegisterSession(&loop, dap.get()); + + std::vector<DAP *> sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1); + + manager.DisconnectAllSessions(); + + // DisconnectAllSessions shutdown but doesn't wait for + // sessions to complete or remove them from the active sessions map. + sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1); + + std::thread unregister_thread([&]() { manager.UnregisterSession(&loop); }); + unregister_thread.join(); +} + +TEST_F(DAPSessionManagerTest, WaitForAllSessionsToDisconnect) { + DAPSessionManager &manager = DAPSessionManager::GetInstance(); + + manager.RegisterSession(&loop, dap.get()); + + std::vector<DAP *> sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 1); + + // Unregister after a delay to test blocking behavior. + std::thread unregister_thread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + manager.UnregisterSession(&loop); + }); + + // WaitForAllSessionsToDisconnect should block until unregistered. + auto start = std::chrono::steady_clock::now(); + llvm::Error err = manager.WaitForAllSessionsToDisconnect(); + EXPECT_FALSE(err); + auto duration = std::chrono::steady_clock::now() - start; + + // Verify it waited at least 100ms. + EXPECT_GE(duration, std::chrono::milliseconds(100)); + + // Session should be unregistered now. + sessions = manager.GetActiveSessions(); + EXPECT_EQ(std::count(sessions.begin(), sessions.end(), dap.get()), 0); + + unregister_thread.join(); +} diff --git a/lldb/unittests/DAP/ProtocolRequestsTest.cpp b/lldb/unittests/DAP/ProtocolRequestsTest.cpp new file mode 100644 index 0000000..c830690 --- /dev/null +++ b/lldb/unittests/DAP/ProtocolRequestsTest.cpp @@ -0,0 +1,195 @@ +//===-- ProtocolRequestsTest.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 "Protocol/ProtocolRequests.h" +#include "Protocol/ProtocolTypes.h" +#include "TestingSupport/TestUtilities.h" +#include "llvm/Testing/Support/Error.h" +#include <gtest/gtest.h> + +using namespace llvm; +using namespace lldb_dap::protocol; +using lldb_private::PrettyPrint; +using llvm::json::parse; + +TEST(ProtocolRequestsTest, ExceptionInfoArguments) { + llvm::Expected<ExceptionInfoArguments> expected = + parse<ExceptionInfoArguments>(R"({ + "threadId": 3434 + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->threadId, 3434U); + + // Check required keys; + EXPECT_THAT_EXPECTED(parse<ExceptionInfoArguments>(R"({})"), + FailedWithMessage("missing value at (root).threadId")); + + EXPECT_THAT_EXPECTED(parse<ExceptionInfoArguments>(R"({"id": 10})"), + FailedWithMessage("missing value at (root).threadId")); +} + +TEST(ProtocolRequestsTest, ExceptionInfoResponseBody) { + ExceptionInfoResponseBody body; + body.exceptionId = "signal"; + body.breakMode = eExceptionBreakModeAlways; + + // Check required keys. + Expected<json::Value> expected = parse( + R"({ + "exceptionId": "signal", + "breakMode": "always" + })"); + + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected), PrettyPrint(body)); + + // Check optional keys. + body.description = "SIGNAL SIGWINCH"; + body.breakMode = eExceptionBreakModeNever; + body.details = ExceptionDetails{}; + body.details->message = "some message"; + + Expected<json::Value> expected_opt = parse( + R"({ + "exceptionId": "signal", + "description": "SIGNAL SIGWINCH", + "breakMode": "never", + "details": { + "message": "some message" + } + })"); + + ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected_opt), PrettyPrint(body)); +} + +TEST(ProtocolRequestsTest, EvaluateArguments) { + llvm::Expected<EvaluateArguments> expected = parse<EvaluateArguments>(R"({ + "expression": "hello world", + "context": "repl" + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->expression, "hello world"); + EXPECT_EQ(expected->context, eEvaluateContextRepl); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<EvaluateArguments>(R"({})"), + FailedWithMessage("missing value at (root).expression")); +} + +TEST(ProtocolRequestsTest, EvaluateResponseBody) { + EvaluateResponseBody body; + body.result = "hello world"; + body.variablesReference = 7; + + // Check required keys. + Expected<json::Value> expected = parse(R"({ + "result": "hello world", + "variablesReference": 7 + })"); + + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected), PrettyPrint(body)); + + // Check optional keys. + body.result = "'abc'"; + body.type = "string"; + body.variablesReference = 42; + body.namedVariables = 1; + body.indexedVariables = 2; + body.memoryReference = "0x123"; + body.valueLocationReference = 22; + + Expected<json::Value> expected_opt = parse(R"({ + "result": "'abc'", + "type": "string", + "variablesReference": 42, + "namedVariables": 1, + "indexedVariables": 2, + "memoryReference": "0x123", + "valueLocationReference": 22 + })"); + + ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); + EXPECT_EQ(PrettyPrint(*expected_opt), PrettyPrint(body)); +} + +TEST(ProtocolRequestsTest, InitializeRequestArguments) { + llvm::Expected<InitializeRequestArguments> expected = + parse<InitializeRequestArguments>(R"({"adapterID": "myid"})"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->adapterID, "myid"); + + // Check optional keys. + expected = parse<InitializeRequestArguments>(R"({ + "adapterID": "myid", + "clientID": "myclientid", + "clientName": "lldb-dap-unit-tests", + "locale": "en-US", + "linesStartAt1": true, + "columnsStartAt1": true, + "pathFormat": "uri", + "supportsVariableType": true, + "supportsVariablePaging": true, + "supportsRunInTerminalRequest": true, + "supportsMemoryReferences": true, + "supportsProgressReporting": true, + "supportsInvalidatedEvent": true, + "supportsMemoryEvent": true, + "supportsArgsCanBeInterpretedByShell": true, + "supportsStartDebuggingRequest": true, + "supportsANSIStyling": true + })"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->adapterID, "myid"); + EXPECT_EQ(expected->clientID, "myclientid"); + EXPECT_EQ(expected->clientName, "lldb-dap-unit-tests"); + EXPECT_EQ(expected->locale, "en-US"); + EXPECT_EQ(expected->linesStartAt1, true); + EXPECT_EQ(expected->columnsStartAt1, true); + EXPECT_EQ(expected->pathFormat, ePathFormatURI); + EXPECT_EQ(expected->supportedFeatures.contains(eClientFeatureVariableType), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureRunInTerminalRequest), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureMemoryReferences), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureProgressReporting), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureInvalidatedEvent), + true); + EXPECT_EQ(expected->supportedFeatures.contains(eClientFeatureMemoryEvent), + true); + EXPECT_EQ(expected->supportedFeatures.contains( + eClientFeatureArgsCanBeInterpretedByShell), + true); + EXPECT_EQ( + expected->supportedFeatures.contains(eClientFeatureStartDebuggingRequest), + true); + EXPECT_EQ(expected->supportedFeatures.contains(eClientFeatureANSIStyling), + true); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<InitializeRequestArguments>(R"({})"), + FailedWithMessage("missing value at (root).adapterID")); +} + +TEST(ProtocolRequestsTest, PauseRequestArguments) { + llvm::Expected<PauseArguments> expected = + parse<PauseArguments>(R"({"threadId": 123})"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(expected->threadId, 123U); + + // Check required keys. + EXPECT_THAT_EXPECTED(parse<PauseArguments>(R"({})"), + FailedWithMessage("missing value at (root).threadId")); +} diff --git a/lldb/unittests/DAP/ProtocolTypesTest.cpp b/lldb/unittests/DAP/ProtocolTypesTest.cpp index 8170abd..6a4620a 100644 --- a/lldb/unittests/DAP/ProtocolTypesTest.cpp +++ b/lldb/unittests/DAP/ProtocolTypesTest.cpp @@ -1129,3 +1129,50 @@ TEST(ProtocolTypesTest, DataBreakpointInfoArguments) { EXPECT_THAT_EXPECTED(parse<DataBreakpointInfoArguments>(R"({"name":"data"})"), llvm::Succeeded()); } + +TEST(ProtocolTypesTest, ExceptionBreakMode) { + const std::vector<std::pair<ExceptionBreakMode, llvm::StringRef>> test_cases = + {{ExceptionBreakMode::eExceptionBreakModeAlways, "always"}, + {ExceptionBreakMode::eExceptionBreakModeNever, "never"}, + {ExceptionBreakMode::eExceptionBreakModeUnhandled, "unhandled"}, + {ExceptionBreakMode::eExceptionBreakModeUserUnhandled, "userUnhandled"}}; + + for (const auto [value, expected] : test_cases) { + json::Value const serialized = toJSON(value); + ASSERT_EQ(serialized.kind(), llvm::json::Value::Kind::String); + EXPECT_EQ(serialized.getAsString(), expected); + } +} + +TEST(ProtocolTypesTest, ExceptionDetails) { + ExceptionDetails details; + + // Check required keys. + Expected<json::Value> expected = parse(R"({})"); + ASSERT_THAT_EXPECTED(expected, llvm::Succeeded()); + EXPECT_EQ(pp(*expected), pp(details)); + + // Check optional keys. + details.message = "SIGABRT exception"; + details.typeName = "signal"; + details.fullTypeName = "SIGABRT"; + details.evaluateName = "process handle SIGABRT"; + details.stackTrace = "some stacktrace"; + ExceptionDetails inner_details; + inner_details.message = "inner message"; + details.innerException = {std::move(inner_details)}; + + Expected<json::Value> expected_opt = parse(R"({ + "message": "SIGABRT exception", + "typeName": "signal", + "fullTypeName": "SIGABRT", + "evaluateName": "process handle SIGABRT", + "stackTrace": "some stacktrace", + "innerException": [{ + "message": "inner message" + }] + })"); + + ASSERT_THAT_EXPECTED(expected_opt, llvm::Succeeded()); + EXPECT_EQ(pp(*expected_opt), pp(details)); +} diff --git a/lldb/unittests/DAP/TestBase.cpp b/lldb/unittests/DAP/TestBase.cpp index 8cb4599..e57963e 100644 --- a/lldb/unittests/DAP/TestBase.cpp +++ b/lldb/unittests/DAP/TestBase.cpp @@ -7,7 +7,10 @@ //===----------------------------------------------------------------------===// #include "TestBase.h" +#include "DAP.h" #include "DAPLog.h" +#include "Handler/RequestHandler.h" +#include "Handler/ResponseHandler.h" #include "TestingSupport/TestUtilities.h" #include "lldb/API/SBDefines.h" #include "lldb/API/SBStructuredData.h" @@ -19,7 +22,6 @@ #include "gtest/gtest.h" #include <cstdio> #include <memory> -#include <system_error> using namespace llvm; using namespace lldb; @@ -35,10 +37,9 @@ using lldb_private::Pipe; void TransportBase::SetUp() { std::tie(to_client, to_server) = TestDAPTransport::createPair(); - std::error_code EC; - log = std::make_unique<Log>("-", EC); + log = std::make_unique<Log>(llvm::outs(), log_mutex); dap = std::make_unique<DAP>( - /*log=*/log.get(), + /*log=*/*log, /*default_repl_mode=*/ReplMode::Auto, /*pre_init_commands=*/std::vector<std::string>(), /*no_lldbinit=*/false, @@ -72,6 +73,7 @@ void DAPTestBase::TearDown() { void DAPTestBase::SetUpTestSuite() { lldb::SBError error = SBDebugger::InitializeWithErrorHandling(); + EXPECT_TRUE(error.IsValid()); EXPECT_TRUE(error.Success()); } void DAPTestBase::TeatUpTestSuite() { SBDebugger::Terminate(); } diff --git a/lldb/unittests/DAP/TestBase.h b/lldb/unittests/DAP/TestBase.h index c32f3a7..f1c7e6b 100644 --- a/lldb/unittests/DAP/TestBase.h +++ b/lldb/unittests/DAP/TestBase.h @@ -8,6 +8,8 @@ #include "DAP.h" #include "DAPLog.h" +#include "Handler/RequestHandler.h" +#include "Handler/ResponseHandler.h" #include "Protocol/ProtocolBase.h" #include "TestingSupport/Host/JSONTransportTestUtilities.h" #include "TestingSupport/SubsystemRAII.h" @@ -60,6 +62,7 @@ protected: lldb_private::MainLoop::ReadHandleUP handles[2]; std::unique_ptr<lldb_dap::Log> log; + lldb_dap::Log::Mutex log_mutex; std::unique_ptr<TestDAPTransport> to_client; MockMessageHandler<lldb_dap::ProtocolDescriptor> client; diff --git a/lldb/unittests/Editline/EditlineTest.cpp b/lldb/unittests/Editline/EditlineTest.cpp index 2875f4e..2afc336 100644 --- a/lldb/unittests/Editline/EditlineTest.cpp +++ b/lldb/unittests/Editline/EditlineTest.cpp @@ -9,6 +9,8 @@ #include "lldb/Host/Config.h" #include "lldb/Host/File.h" #include "lldb/Host/HostInfo.h" +#include "lldb/lldb-forward.h" +#include "llvm/Testing/Support/Error.h" #if LLDB_ENABLE_LIBEDIT @@ -25,7 +27,6 @@ #include "TestingSupport/SubsystemRAII.h" #include "lldb/Host/Editline.h" #include "lldb/Host/FileSystem.h" -#include "lldb/Host/Pipe.h" #include "lldb/Host/PseudoTerminal.h" #include "lldb/Host/StreamFile.h" #include "lldb/Utility/Status.h" @@ -37,27 +38,6 @@ namespace { const size_t TIMEOUT_MILLIS = 5000; } -class FilePointer { -public: - FilePointer() = delete; - - FilePointer(const FilePointer &) = delete; - - FilePointer(FILE *file_p) : _file_p(file_p) {} - - ~FilePointer() { - if (_file_p != nullptr) { - const int close_result = fclose(_file_p); - EXPECT_EQ(0, close_result); - } - } - - operator FILE *() { return _file_p; } - -private: - FILE *_file_p; -}; - /** Wraps an Editline class, providing a simple way to feed input (as if from the keyboard) and receive output from Editline. @@ -90,44 +70,39 @@ private: std::recursive_mutex output_mutex; std::unique_ptr<lldb_private::Editline> _editline_sp; - PseudoTerminal _pty; - int _pty_primary_fd = -1; - int _pty_secondary_fd = -1; - - std::unique_ptr<FilePointer> _el_secondary_file; + lldb::FileSP _el_primary_file; + lldb::FileSP _el_secondary_file; }; -EditlineAdapter::EditlineAdapter() - : _editline_sp(), _pty(), _el_secondary_file() { +EditlineAdapter::EditlineAdapter() : _editline_sp(), _el_secondary_file() { lldb_private::Status error; + PseudoTerminal pty; // Open the first primary pty available. - EXPECT_THAT_ERROR(_pty.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded()); + EXPECT_THAT_ERROR(pty.OpenFirstAvailablePrimary(O_RDWR), llvm::Succeeded()); + // Open the corresponding secondary pty. + EXPECT_THAT_ERROR(pty.OpenSecondary(O_RDWR), llvm::Succeeded()); // Grab the primary fd. This is a file descriptor we will: // (1) write to when we want to send input to editline. // (2) read from when we want to see what editline sends back. - _pty_primary_fd = _pty.GetPrimaryFileDescriptor(); + _el_primary_file.reset( + new NativeFile(pty.ReleasePrimaryFileDescriptor(), + lldb_private::NativeFile::eOpenOptionReadWrite, true)); - // Open the corresponding secondary pty. - EXPECT_THAT_ERROR(_pty.OpenSecondary(O_RDWR), llvm::Succeeded()); - _pty_secondary_fd = _pty.GetSecondaryFileDescriptor(); - - _el_secondary_file.reset(new FilePointer(fdopen(_pty_secondary_fd, "rw"))); - EXPECT_FALSE(nullptr == *_el_secondary_file); - if (*_el_secondary_file == nullptr) - return; + _el_secondary_file.reset( + new NativeFile(pty.ReleaseSecondaryFileDescriptor(), + lldb_private::NativeFile::eOpenOptionReadWrite, true)); lldb::LockableStreamFileSP output_stream_sp = - std::make_shared<LockableStreamFile>(*_el_secondary_file, - NativeFile::Unowned, output_mutex); + std::make_shared<LockableStreamFile>(_el_secondary_file, output_mutex); lldb::LockableStreamFileSP error_stream_sp = - std::make_shared<LockableStreamFile>(*_el_secondary_file, - NativeFile::Unowned, output_mutex); + std::make_shared<LockableStreamFile>(_el_secondary_file, output_mutex); // Create an Editline instance. _editline_sp.reset(new lldb_private::Editline( - "gtest editor", *_el_secondary_file, output_stream_sp, error_stream_sp, + "gtest editor", _el_secondary_file->GetStream(), output_stream_sp, + error_stream_sp, /*color=*/false)); _editline_sp->SetPrompt("> "); @@ -140,7 +115,7 @@ EditlineAdapter::EditlineAdapter() void EditlineAdapter::CloseInput() { if (_el_secondary_file != nullptr) - _el_secondary_file.reset(nullptr); + _el_secondary_file->Close(); } bool EditlineAdapter::SendLine(const std::string &line) { @@ -148,19 +123,14 @@ bool EditlineAdapter::SendLine(const std::string &line) { if (!IsValid()) return false; + std::string out = line + "\n"; + // Write the line out to the pipe connected to editline's input. - ssize_t input_bytes_written = - ::write(_pty_primary_fd, line.c_str(), - line.length() * sizeof(std::string::value_type)); - - const char *eoln = "\n"; - const size_t eoln_length = strlen(eoln); - input_bytes_written = - ::write(_pty_primary_fd, eoln, eoln_length * sizeof(char)); - - EXPECT_NE(-1, input_bytes_written) << strerror(errno); - EXPECT_EQ(eoln_length * sizeof(char), size_t(input_bytes_written)); - return eoln_length * sizeof(char) == size_t(input_bytes_written); + size_t num_bytes = out.length() * sizeof(std::string::value_type); + EXPECT_THAT_ERROR(_el_primary_file->Write(out.c_str(), num_bytes).takeError(), + llvm::Succeeded()); + EXPECT_EQ(num_bytes, out.length() * sizeof(std::string::value_type)); + return true; } bool EditlineAdapter::SendLines(const std::vector<std::string> &lines) { @@ -215,7 +185,7 @@ bool EditlineAdapter::IsInputComplete(lldb_private::Editline *editline, } void EditlineAdapter::ConsumeAllOutput() { - FilePointer output_file(fdopen(_pty_primary_fd, "r")); + FILE *output_file = _el_primary_file->GetStream(); int ch; while ((ch = fgetc(output_file)) != EOF) { diff --git a/lldb/unittests/Expression/CMakeLists.txt b/lldb/unittests/Expression/CMakeLists.txt index 2600557..0e0b002 100644 --- a/lldb/unittests/Expression/CMakeLists.txt +++ b/lldb/unittests/Expression/CMakeLists.txt @@ -10,6 +10,7 @@ add_lldb_unittest(ExpressionTests DWARFExpressionTest.cpp CppModuleConfigurationTest.cpp ExpressionTest.cpp + ValueMatcher.cpp LINK_COMPONENTS Support diff --git a/lldb/unittests/Expression/ClangParserTest.cpp b/lldb/unittests/Expression/ClangParserTest.cpp index fab4487..c949026 100644 --- a/lldb/unittests/Expression/ClangParserTest.cpp +++ b/lldb/unittests/Expression/ClangParserTest.cpp @@ -8,7 +8,7 @@ #include "clang/Basic/Version.h" #include "clang/Config/config.h" -#include "clang/Driver/Driver.h" +#include "clang/Options/OptionUtils.h" #include "Plugins/ExpressionParser/Clang/ClangHost.h" #include "TestingSupport/SubsystemRAII.h" @@ -43,7 +43,7 @@ TEST_F(ClangHostTest, ComputeClangResourceDirectory) { std::string path_to_liblldb = "C:\\foo\\bar\\lib\\"; #endif std::string path_to_clang_dir = - clang::driver::Driver::GetResourcesPath(path_to_liblldb + "liblldb"); + clang::GetResourcesPath(path_to_liblldb + "liblldb"); llvm::SmallString<256> path_to_clang_lib_dir_real; llvm::sys::fs::real_path(path_to_clang_dir, path_to_clang_lib_dir_real); diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp index 9d11060..f264fb3 100644 --- a/lldb/unittests/Expression/DWARFExpressionTest.cpp +++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp @@ -5,8 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// - #include "lldb/Expression/DWARFExpression.h" +#include "ValueMatcher.h" +#include <unordered_map> #ifdef ARCH_AARCH64 #include "Plugins/ABI/AArch64/ABISysV_arm64.h" #endif @@ -39,32 +40,128 @@ using namespace lldb_private; using namespace llvm::dwarf; namespace { -struct MockProcess : Process { - MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp) - : Process(target_sp, listener_sp) {} +/// A mock implementation of DWARFExpression::Delegate for testing. +/// This class provides default implementations of all delegate methods, +/// with the DWARF version being configurable via the constructor. +class MockDwarfDelegate : public DWARFExpression::Delegate { +public: + static constexpr uint16_t DEFAULT_DWARF_VERSION = 5; + static MockDwarfDelegate Dwarf5() { return MockDwarfDelegate(5); } + static MockDwarfDelegate Dwarf2() { return MockDwarfDelegate(2); } - llvm::StringRef GetPluginName() override { return "mock process"; } + MockDwarfDelegate() : MockDwarfDelegate(DEFAULT_DWARF_VERSION) {} + explicit MockDwarfDelegate(uint16_t version) : m_dwarf_version(version) {} - bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override { - return false; - }; + uint16_t GetVersion() const override { return m_dwarf_version; } - Status DoDestroy() override { return {}; } + dw_addr_t GetBaseAddress() const override { return 0; } - void RefreshStateAfterStop() override {} + uint8_t GetAddressByteSize() const override { return 4; } - bool DoUpdateThreadList(ThreadList &old_thread_list, - ThreadList &new_thread_list) override { + llvm::Expected<std::pair<uint64_t, bool>> + GetDIEBitSizeAndSign(uint64_t relative_die_offset) const override { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "GetDIEBitSizeAndSign not implemented"); + } + + dw_addr_t ReadAddressFromDebugAddrSection(uint32_t index) const override { + return 0; + } + + lldb::offset_t GetVendorDWARFOpcodeSize(const DataExtractor &data, + const lldb::offset_t data_offset, + const uint8_t op) const override { + return LLDB_INVALID_OFFSET; + } + + bool ParseVendorDWARFOpcode(uint8_t op, const DataExtractor &opcodes, + lldb::offset_t &offset, RegisterContext *reg_ctx, + lldb::RegisterKind reg_kind, + DWARFExpression::Stack &stack) const override { return false; + } + +private: + uint16_t m_dwarf_version; +}; + +/// Mock memory implementation for testing. +/// Stores predefined memory contents indexed by {address, size} pairs. +class MockMemory { +public: + /// Represents a memory read request with an address and size. + /// Used as a key in the memory map to look up predefined test data. + struct Request { + lldb::addr_t addr; + size_t size; + + bool operator==(const Request &other) const { + return addr == other.addr && size == other.size; + } + + /// Hash function for Request to enable its use in unordered_map. + struct Hash { + size_t operator()(const Request &req) const { + size_t h1 = std::hash<lldb::addr_t>{}(req.addr); + size_t h2 = std::hash<size_t>{}(req.size); + return h1 ^ (h2 << 1); + } + }; }; - size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, + typedef std::unordered_map<Request, std::vector<uint8_t>, Request::Hash> Map; + MockMemory() = default; + MockMemory(Map memory) : m_memory(std::move(memory)) { + // Make sure the requested memory size matches the returned value. + for ([[maybe_unused]] auto &[req, bytes] : m_memory) { + assert(bytes.size() == req.size); + } + } + + llvm::Expected<std::vector<uint8_t>> ReadMemory(lldb::addr_t addr, + size_t size) { + if (!m_memory.count({addr, size})) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "MockMemory::ReadMemory {address=0x%" PRIx64 ", size=%zu} not found", + addr, size); + } + return m_memory[{addr, size}]; + } + +private: + std::unordered_map<Request, std::vector<uint8_t>, Request::Hash> m_memory; +}; + +/// A Process whose `ReadMemory` override queries MockMemory. +struct MockProcess : Process { + using addr_t = lldb::addr_t; + + MockMemory m_memory; + + MockProcess(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, + MockMemory memory) + : Process(target_sp, listener_sp), m_memory(std::move(memory)) {} + size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size, Status &error) override { - for (size_t i = 0; i < size; ++i) - ((char *)buf)[i] = (vm_addr + i) & 0xff; - error.Clear(); + auto expected_memory = m_memory.ReadMemory(vm_addr, size); + if (!expected_memory) { + error = Status::FromError(expected_memory.takeError()); + return 0; + } + assert(expected_memory->size() == size); + std::memcpy(buf, expected_memory->data(), expected_memory->size()); return size; } + size_t ReadMemory(addr_t addr, void *buf, size_t size, + Status &status) override { + return DoReadMemory(addr, buf, size, status); + } + bool CanDebug(lldb::TargetSP, bool) override { return true; } + Status DoDestroy() override { return Status(); } + llvm::StringRef GetPluginName() override { return ""; } + void RefreshStateAfterStop() override {} + bool DoUpdateThreadList(ThreadList &, ThreadList &) override { return false; } }; class MockThread : public Thread { @@ -135,40 +232,19 @@ private: }; } // namespace -static llvm::Expected<Scalar> Evaluate(llvm::ArrayRef<uint8_t> expr, - lldb::ModuleSP module_sp = {}, - DWARFUnit *unit = nullptr, - ExecutionContext *exe_ctx = nullptr, - RegisterContext *reg_ctx = nullptr) { - DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, - /*addr_size*/ 4); - - llvm::Expected<Value> result = DWARFExpression::Evaluate( - exe_ctx, reg_ctx, module_sp, extractor, unit, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - if (!result) - return result.takeError(); - - switch (result->GetValueType()) { - case Value::ValueType::Scalar: - return result->GetScalar(); - case Value::ValueType::LoadAddress: - return LLDB_INVALID_ADDRESS; - case Value::ValueType::HostAddress: { - // Convert small buffers to scalars to simplify the tests. - DataBufferHeap &buf = result->GetBuffer(); - if (buf.GetByteSize() <= 8) { - uint64_t val = 0; - memcpy(&val, buf.GetBytes(), buf.GetByteSize()); - return Scalar(llvm::APInt(buf.GetByteSize() * 8, val, false)); - } - } - [[fallthrough]]; - default: - break; - } - return llvm::createStringError("unsupported value type"); +static llvm::Expected<Value> Evaluate(llvm::ArrayRef<uint8_t> expr, + lldb::ModuleSP module_sp = {}, + DWARFExpression::Delegate *unit = nullptr, + ExecutionContext *exe_ctx = nullptr, + RegisterContext *reg_ctx = nullptr) { + DataExtractor extractor( + expr.data(), expr.size(), lldb::eByteOrderLittle, + /*addr_size*/ exe_ctx ? exe_ctx->GetAddressByteSize() : 4); + + return DWARFExpression::Evaluate(exe_ctx, reg_ctx, module_sp, extractor, unit, + lldb::eRegisterKindLLDB, + /*initial_value_ptr=*/nullptr, + /*object_address_ptr=*/nullptr); } class DWARFExpressionTester : public YAMLModuleTester { @@ -177,18 +253,11 @@ public: : YAMLModuleTester(yaml_data, cu_index) {} using YAMLModuleTester::YAMLModuleTester; - llvm::Expected<Scalar> Eval(llvm::ArrayRef<uint8_t> expr) { + llvm::Expected<Value> Eval(llvm::ArrayRef<uint8_t> expr) { return ::Evaluate(expr, m_module_sp, m_dwarf_unit); } }; -/// Unfortunately Scalar's operator==() is really picky. -static Scalar GetScalar(unsigned bits, uint64_t value, bool sign) { - Scalar scalar(value); - scalar.TruncOrExtendTo(bits, sign); - return scalar; -} - /// This is needed for the tests that use a mock process. class DWARFExpressionMockProcessTest : public ::testing::Test { public: @@ -204,46 +273,23 @@ public: } }; -struct PlatformTargetDebugger { - lldb::PlatformSP platform_sp; - lldb::TargetSP target_sp; - lldb::DebuggerSP debugger_sp; -}; - -/// A helper function to create <Platform, Target, Debugger> objects with the -/// "aarch64-pc-linux" ArchSpec. -static PlatformTargetDebugger CreateTarget() { - ArchSpec arch("aarch64-pc-linux"); - Platform::SetHostPlatform( - platform_linux::PlatformLinux::CreateInstance(true, &arch)); - lldb::PlatformSP platform_sp; - lldb::TargetSP target_sp; - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - debugger_sp->GetTargetList().CreateTarget( - *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp); - return PlatformTargetDebugger{platform_sp, target_sp, debugger_sp}; -} - -// NB: This class doesn't use the override keyword to avoid -// -Winconsistent-missing-override warnings from the compiler. The -// inconsistency comes from the overriding definitions in the MOCK_*** macros. +/// Mock target implementation for testing. +/// Provides predefined memory contents via MockMemory instead of reading from +/// a real process. class MockTarget : public Target { public: MockTarget(Debugger &debugger, const ArchSpec &target_arch, - const lldb::PlatformSP &platform_sp) - : Target(debugger, target_arch, platform_sp, true) {} - - MOCK_METHOD2(ReadMemory, - llvm::Expected<std::vector<uint8_t>>(lldb::addr_t addr, - size_t size)); + const lldb::PlatformSP &platform_sp, MockMemory memory) + : Target(debugger, target_arch, platform_sp, true), + m_memory(std::move(memory)) {} size_t ReadMemory(const Address &addr, void *dst, size_t dst_len, Status &error, bool force_live_memory = false, lldb::addr_t *load_addr_ptr = nullptr, - bool *did_read_live_memory = nullptr) /*override*/ { - auto expected_memory = this->ReadMemory(addr.GetOffset(), dst_len); + bool *did_read_live_memory = nullptr) override { + auto expected_memory = m_memory.ReadMemory(addr.GetOffset(), dst_len); if (!expected_memory) { - llvm::consumeError(expected_memory.takeError()); + error = Status::FromError(expected_memory.takeError()); return 0; } const size_t bytes_read = expected_memory->size(); @@ -251,52 +297,110 @@ public: std::memcpy(dst, expected_memory->data(), bytes_read); return bytes_read; } + +private: + MockMemory m_memory; }; +struct TestContext { + lldb::PlatformSP platform_sp; + lldb::TargetSP target_sp; + lldb::DebuggerSP debugger_sp; + lldb::ProcessSP process_sp; + lldb::ThreadSP thread_sp; + lldb::RegisterContextSP reg_ctx_sp; +}; + +/// A helper function to create TestContext objects with the +/// given triple, memory, and register contents. +static bool CreateTestContext(TestContext *ctx, llvm::StringRef triple, + std::optional<RegisterValue> reg_value = {}, + std::optional<MockMemory> process_memory = {}, + std::optional<MockMemory> target_memory = {}) { + ArchSpec arch(triple); + lldb::PlatformSP platform_sp = + platform_linux::PlatformLinux::CreateInstance(true, &arch); + Platform::SetHostPlatform(platform_sp); + lldb::TargetSP target_sp; + lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); + + Status status; + if (target_memory) + target_sp = std::make_shared<MockTarget>(*debugger_sp, arch, platform_sp, + std::move(*target_memory)); + else + status = debugger_sp->GetTargetList().CreateTarget( + *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp); + + EXPECT_TRUE(status.Success()); + if (!status.Success()) + return false; + + lldb::ProcessSP process_sp; + if (!process_memory) + process_memory = MockMemory(); + process_sp = std::make_shared<MockProcess>( + target_sp, Listener::MakeListener("dummy"), std::move(*process_memory)); + + auto thread_sp = std::make_shared<MockThread>(*process_sp); + + process_sp->GetThreadList().AddThread(thread_sp); + + lldb::RegisterContextSP reg_ctx_sp; + if (reg_value) { + reg_ctx_sp = std::make_shared<MockRegisterContext>(*thread_sp, *reg_value); + thread_sp->SetRegisterContext(reg_ctx_sp); + } + + *ctx = TestContext{platform_sp, target_sp, debugger_sp, + process_sp, thread_sp, reg_ctx_sp}; + return true; +} + TEST(DWARFExpression, DW_OP_pick) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 0}), - llvm::HasValue(0)); + ExpectScalar(0)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 1}), - llvm::HasValue(1)); + ExpectScalar(1)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1, DW_OP_lit0, DW_OP_pick, 2}), llvm::Failed()); } TEST(DWARFExpression, DW_OP_const) { // Extend to address size. - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), llvm::HasValue(0x88)); + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x88}), ExpectScalar(0x88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1s, 0x88}), - llvm::HasValue(0xffffff88)); + ExpectScalar(0xffffff88)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x47, 0x88}), - llvm::HasValue(0x8847)); + ExpectScalar(0x8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2s, 0x47, 0x88}), - llvm::HasValue(0xffff8847)); + ExpectScalar(0xffff8847)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4u, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const4s, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x88474244)); + ExpectScalar(0x88474244)); // Truncate to address size. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8u, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_const8s, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}), - llvm::HasValue(0x33221100)); + ExpectScalar(0x33221100)); // Don't truncate to address size for compatibility with clang (pr48087). EXPECT_THAT_EXPECTED( Evaluate({DW_OP_constu, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0x01010101010101)); + ExpectScalar(0x01010101010101)); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_consts, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}), - llvm::HasValue(0xffff010101010101)); + ExpectScalar(0xffff010101010101)); } TEST(DWARFExpression, DW_OP_skip) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const1u, 0x42, DW_OP_skip, 0x02, 0x00, DW_OP_const1u, 0xff}), - llvm::HasValue(0x42)); + ExpectScalar(0x42)); } TEST(DWARFExpression, DW_OP_bra) { @@ -309,7 +413,7 @@ TEST(DWARFExpression, DW_OP_bra) { DW_OP_const1u, 0xff, // push 0xff }), // clang-format on - llvm::HasValue(0x42)); + ExpectScalar(0x42)); EXPECT_THAT_ERROR(Evaluate({DW_OP_bra, 0x01, 0x00}).takeError(), llvm::Failed()); @@ -414,42 +518,42 @@ DWARF: EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Zero-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4u, 0x11, 0x22, 0x33, 0x44, // DW_OP_convert, offs_uint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0x44332211, not_signed))); + ExpectScalar(64, 0x44332211, not_signed)); // Sign-extend to 64 bits. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(64, 0xffffffffffeeddcc, is_signed))); + ExpectScalar(64, 0xffffffffffeeddcc, is_signed)); // Sign-extend, then truncate. EXPECT_THAT_EXPECTED( t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, offs_uint32_t, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to default unspecified (pointer-sized) type. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 0xcc, 0xdd, 0xee, 0xff, // DW_OP_convert, offs_sint64_t, // DW_OP_convert, 0x00, DW_OP_stack_value}), - llvm::HasValue(GetScalar(32, 0xffeeddcc, not_signed))); + ExpectScalar(32, 0xffeeddcc, not_signed)); // Truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_uchar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', not_signed))); + ExpectScalar(8, 'A', not_signed)); // Also truncate to 8 bits. EXPECT_THAT_EXPECTED(t.Eval({DW_OP_const4s, 'A', 'B', 'C', 'D', DW_OP_convert, offs_schar, DW_OP_stack_value}), - llvm::HasValue(GetScalar(8, 'A', is_signed))); + ExpectScalar(8, 'A', is_signed)); // // Errors. @@ -476,36 +580,41 @@ TEST(DWARFExpression, DW_OP_stack_value) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_stack_value}), llvm::Failed()); } +// This test shows that the dwarf version is used by the expression evaluation. +// Note that the different behavior tested here is not meant to imply that this +// is the correct interpretation of dwarf2 vs. dwarf5, but rather it was picked +// as an easy example that evaluates differently based on the dwarf version. +TEST(DWARFExpression, dwarf_version) { + MockDwarfDelegate dwarf2 = MockDwarfDelegate::Dwarf2(); + MockDwarfDelegate dwarf5 = MockDwarfDelegate::Dwarf5(); + + // In dwarf2 the constant on top of the stack is treated as a value. + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1}, {}, &dwarf2), ExpectScalar(1)); + + // In dwarf5 the constant on top of the stack is implicitly converted to an + // address. + EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit1}, {}, &dwarf5), + ExpectLoadAddress(1)); +} + TEST(DWARFExpression, DW_OP_piece) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_const2u, 0x11, 0x22, DW_OP_piece, 2, DW_OP_const2u, 0x33, 0x44, DW_OP_piece, 2}), - llvm::HasValue(GetScalar(32, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); EXPECT_THAT_EXPECTED( Evaluate({DW_OP_piece, 1, DW_OP_const1u, 0xff, DW_OP_piece, 1}), // Note that the "00" should really be "undef", but we can't // represent that yet. - llvm::HasValue(GetScalar(16, 0xff00, true))); -} - -TEST(DWARFExpression, DW_OP_piece_host_address) { - static const uint8_t expr_data[] = {DW_OP_lit2, DW_OP_stack_value, - DW_OP_piece, 40}; - llvm::ArrayRef<uint8_t> expr(expr_data, sizeof(expr_data)); - DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle, 4); + ExpectHostAddress({0x00, 0xff})); // This tests if ap_int is extended to the right width. // expect 40*8 = 320 bits size. - llvm::Expected<Value> result = - DWARFExpression::Evaluate(nullptr, nullptr, nullptr, extractor, nullptr, - lldb::eRegisterKindDWARF, nullptr, nullptr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_EQ(result->GetBuffer().GetByteSize(), 40ul); - const uint8_t *data = result->GetBuffer().GetBytes(); - ASSERT_EQ(data[0], 2); - for (int i = 1; i < 40; i++) { - ASSERT_EQ(data[i], 0); - } + std::vector<uint8_t> expected_host_buffer(40, 0); + expected_host_buffer[0] = 2; + + EXPECT_THAT_EXPECTED( + Evaluate({{DW_OP_lit2, DW_OP_stack_value, DW_OP_piece, 40}}), + ExpectHostAddress(expected_host_buffer)); } TEST(DWARFExpression, DW_OP_implicit_value) { @@ -513,7 +622,7 @@ TEST(DWARFExpression, DW_OP_implicit_value) { EXPECT_THAT_EXPECTED( Evaluate({DW_OP_implicit_value, bytes, 0x11, 0x22, 0x33, 0x44}), - llvm::HasValue(GetScalar(8 * bytes, 0x44332211, true))); + ExpectHostAddress({0x11, 0x22, 0x33, 0x44})); } TEST(DWARFExpression, DW_OP_unknown) { @@ -527,72 +636,36 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_deref) { EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit0, DW_OP_deref}), llvm::Failed()); // Set up a mock process. - ArchSpec arch("i386-pc-linux"); - Platform::SetHostPlatform( - platform_linux::PlatformLinux::CreateInstance(true, &arch)); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget( - *debugger_sp, "", arch, eLoadDependentsNo, platform_sp, target_sp); - ASSERT_TRUE(target_sp); - ASSERT_TRUE(target_sp->GetArchitecture().IsValid()); - ASSERT_TRUE(platform_sp); - lldb::ListenerSP listener_sp(Listener::MakeListener("dummy")); - lldb::ProcessSP process_sp = - std::make_shared<MockProcess>(target_sp, listener_sp); - ASSERT_TRUE(process_sp); - - ExecutionContext exe_ctx(process_sp); + MockMemory::Map memory = { + {{0x4, 4}, {0x4, 0x5, 0x6, 0x7}}, + }; + TestContext test_ctx; + ASSERT_TRUE( + CreateTestContext(&test_ctx, "i386-pc-linux", {}, std::move(memory))); + + ExecutionContext exe_ctx(test_ctx.process_sp); // Implicit location: *0x4. EXPECT_THAT_EXPECTED( Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(32, 0x07060504, false)); // Memory location: *(*0x4). - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4, DW_OP_deref}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(LLDB_INVALID_ADDRESS))); + ExpectLoadAddress(0x07060504)); // Memory location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. EXPECT_THAT_EXPECTED(Evaluate({DW_OP_lit4}, {}, {}, &exe_ctx), - llvm::HasValue(Scalar(4))); - // Implicit location: *0x4. - // Evaluate returns LLDB_INVALID_ADDRESS for all load addresses. - EXPECT_THAT_EXPECTED( - Evaluate({DW_OP_lit4, DW_OP_deref, DW_OP_stack_value}, {}, {}, &exe_ctx), - llvm::HasValue(GetScalar(32, 0x07060504, false))); + ExpectScalar(Scalar(4))); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr) { // Set up a wasm target - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); + TestContext test_ctx; + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm")); - ExecutionContext exe_ctx(target_sp, false); + ExecutionContext exe_ctx(test_ctx.target_sp, false); // DW_OP_addr takes a single operand of address size width: - uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size*/ 4); - - llvm::Expected<Value> result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx*/ nullptr, /*module_sp*/ {}, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr*/ nullptr, - /*object_address_ptr*/ nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_addr, 0x40, 0x0, 0x0, 0x0}, {}, {}, &exe_ctx), + ExpectLoadAddress(0x40)); } TEST_F(DWARFExpressionMockProcessTest, WASM_DW_OP_addr_index) { @@ -644,20 +717,9 @@ DWARF: dwarf_cu->ExtractDIEsIfNeeded(); // Set up a wasm target - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); - - ExecutionContext exe_ctx(target_sp, false); + TestContext test_ctx; + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm")); + ExecutionContext exe_ctx(test_ctx.target_sp, false); auto evaluate = [&](DWARFExpression &expr) -> llvm::Expected<Value> { DataExtractor extractor; @@ -676,15 +738,11 @@ DWARF: DWARFExpression expr(extractor); llvm::Expected<Value> result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0x5678u); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x5678u)); ASSERT_TRUE(expr.Update_DW_OP_addr(dwarf_cu, 0xdeadbeef)); result = evaluate(expr); - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result->GetScalar().UInt(), 0xdeadbeefu); + EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0xdeadbeefu)); } class CustomSymbolFileDWARF : public SymbolFileDWARF { @@ -778,11 +836,12 @@ static auto testExpressionVendorExtensions(lldb::ModuleSP module_sp, RegisterContext *reg_ctx) { // Test that expression extensions can be evaluated, for example // DW_OP_WASM_location which is not currently handled by DWARFExpression: - EXPECT_THAT_EXPECTED(Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 - 0x04, 0x00, 0x00, // index:u32 - 0x00, DW_OP_stack_value}, - module_sp, &dwarf_unit, nullptr, reg_ctx), - llvm::HasValue(GetScalar(32, 42, false))); + EXPECT_THAT_EXPECTED( + Evaluate({DW_OP_WASM_location, 0x03, // WASM_GLOBAL:0x03 + 0x04, 0x00, 0x00, // index:u32 + 0x00, DW_OP_stack_value}, + module_sp, &dwarf_unit, nullptr, reg_ctx), + ExpectScalar(32, 42, false, Value::ContextType::RegisterInfo)); // Test that searches for opcodes work in the presence of extensions: uint8_t expr[] = {DW_OP_WASM_location, 0x03, 0x04, 0x00, 0x00, 0x00, @@ -883,28 +942,10 @@ Sections: subsystems; // Set up a wasm target. - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); - // Set up a mock process and thread. - lldb::ListenerSP listener_sp(Listener::MakeListener("dummy")); - lldb::ProcessSP process_sp = - std::make_shared<MockProcess>(target_sp, listener_sp); - ASSERT_TRUE(process_sp); - MockThread thread(*process_sp); + TestContext test_ctx; const uint32_t kExpectedValue = 42; - lldb::RegisterContextSP reg_ctx_sp = std::make_shared<MockRegisterContext>( - thread, RegisterValue(kExpectedValue)); - thread.SetRegisterContext(reg_ctx_sp); + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm", + RegisterValue(kExpectedValue))); llvm::Expected<TestFile> file = TestFile::fromYaml(yamldata); EXPECT_THAT_EXPECTED(file, llvm::Succeeded()); @@ -913,7 +954,8 @@ Sections: SymbolFileWasm sym_file_wasm(obj_file_sp, nullptr); auto *dwarf_unit = sym_file_wasm.DebugInfo().GetUnitAtIndex(0); - testExpressionVendorExtensions(module_sp, *dwarf_unit, reg_ctx_sp.get()); + testExpressionVendorExtensions(module_sp, *dwarf_unit, + test_ctx.reg_ctx_sp.get()); } TEST(DWARFExpression, ExtensionsSplitSymbols) { @@ -1082,28 +1124,10 @@ Sections: subsystems; // Set up a wasm target. - ArchSpec arch("wasm32-unknown-unknown-wasm"); - lldb::PlatformSP host_platform_sp = - platform_linux::PlatformLinux::CreateInstance(true, &arch); - ASSERT_TRUE(host_platform_sp); - Platform::SetHostPlatform(host_platform_sp); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::TargetSP target_sp; - lldb::PlatformSP platform_sp; - debugger_sp->GetTargetList().CreateTarget(*debugger_sp, "", arch, - lldb_private::eLoadDependentsNo, - platform_sp, target_sp); - // Set up a mock process and thread. - lldb::ListenerSP listener_sp(Listener::MakeListener("dummy")); - lldb::ProcessSP process_sp = - std::make_shared<MockProcess>(target_sp, listener_sp); - ASSERT_TRUE(process_sp); - MockThread thread(*process_sp); + TestContext test_ctx; const uint32_t kExpectedValue = 42; - lldb::RegisterContextSP reg_ctx_sp = std::make_shared<MockRegisterContext>( - thread, RegisterValue(kExpectedValue)); - thread.SetRegisterContext(reg_ctx_sp); + ASSERT_TRUE(CreateTestContext(&test_ctx, "wasm32-unknown-unknown-wasm", + RegisterValue(kExpectedValue))); llvm::Expected<TestFile> skeleton_file = TestFile::fromYaml(skeleton_yamldata); @@ -1119,7 +1143,8 @@ Sections: SymbolFileWasm sym_file_wasm(obj_file_sp, nullptr); auto *dwarf_unit = sym_file_wasm.DebugInfo().GetUnitAtIndex(0); - testExpressionVendorExtensions(sym_module_sp, *dwarf_unit, reg_ctx_sp.get()); + testExpressionVendorExtensions(sym_module_sp, *dwarf_unit, + test_ctx.reg_ctx_sp.get()); } TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { @@ -1128,66 +1153,23 @@ TEST_F(DWARFExpressionMockProcessTest, DW_OP_piece_file_addr) { using ::testing::Return; // Set up a mock process. - ArchSpec arch("i386-pc-linux"); - Platform::SetHostPlatform( - platform_linux::PlatformLinux::CreateInstance(true, &arch)); - lldb::DebuggerSP debugger_sp = Debugger::CreateInstance(); - ASSERT_TRUE(debugger_sp); - lldb::PlatformSP platform_sp; - auto target_sp = - std::make_shared<MockTarget>(*debugger_sp, arch, platform_sp); - ASSERT_TRUE(target_sp); - ASSERT_TRUE(target_sp->GetArchitecture().IsValid()); - - EXPECT_CALL(*target_sp, ReadMemory(0x40, 1)) - .WillOnce(Return(ByMove(std::vector<uint8_t>{0x11}))); - EXPECT_CALL(*target_sp, ReadMemory(0x50, 1)) - .WillOnce(Return(ByMove(std::vector<uint8_t>{0x22}))); + TestContext test_ctx; + MockMemory::Map memory = { + {{0x40, 1}, {0x11}}, + {{0x50, 1}, {0x22}}, + }; + ASSERT_TRUE( + CreateTestContext(&test_ctx, "i386-pc-linux", {}, {}, std::move(memory))); + ASSERT_TRUE(test_ctx.target_sp->GetArchitecture().IsValid()); - ExecutionContext exe_ctx(static_cast<lldb::TargetSP>(target_sp), false); + ExecutionContext exe_ctx(test_ctx.target_sp, false); uint8_t expr[] = {DW_OP_addr, 0x40, 0x0, 0x0, 0x0, DW_OP_piece, 1, DW_OP_addr, 0x50, 0x0, 0x0, 0x0, DW_OP_piece, 1}; - DataExtractor extractor(expr, sizeof(expr), lldb::eByteOrderLittle, - /*addr_size=*/4); - llvm::Expected<Value> result = DWARFExpression::Evaluate( - &exe_ctx, /*reg_ctx=*/nullptr, /*module_sp=*/{}, extractor, - /*unit=*/nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - - ASSERT_THAT_EXPECTED(result, llvm::Succeeded()); - ASSERT_EQ(result->GetValueType(), Value::ValueType::HostAddress); - ASSERT_THAT(result->GetBuffer().GetData(), ElementsAre(0x11, 0x22)); + EXPECT_THAT_EXPECTED(Evaluate(expr, {}, {}, &exe_ctx), + ExpectHostAddress({0x11, 0x22})); } -/// A Process whose `ReadMemory` override queries a DenseMap. -struct MockProcessWithMemRead : Process { - using addr_t = lldb::addr_t; - - llvm::DenseMap<addr_t, addr_t> memory_map; - - MockProcessWithMemRead(lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, - llvm::DenseMap<addr_t, addr_t> &&memory_map) - : Process(target_sp, listener_sp), memory_map(memory_map) {} - size_t DoReadMemory(addr_t vm_addr, void *buf, size_t size, - Status &error) override { - assert(memory_map.contains(vm_addr)); - assert(size == sizeof(addr_t)); - *reinterpret_cast<addr_t *>(buf) = memory_map[vm_addr]; - return sizeof(addr_t); - } - size_t ReadMemory(addr_t addr, void *buf, size_t size, - Status &status) override { - return DoReadMemory(addr, buf, size, status); - } - bool CanDebug(lldb::TargetSP, bool) override { return true; } - Status DoDestroy() override { return Status(); } - llvm::StringRef GetPluginName() override { return ""; } - void RefreshStateAfterStop() override {} - bool DoUpdateThreadList(ThreadList &, ThreadList &) override { return false; } -}; - class DWARFExpressionMockProcessTestWithAArch : public DWARFExpressionMockProcessTest { public: @@ -1213,43 +1195,129 @@ public: /// The expression DW_OP_breg22, 0, DW_OP_deref should produce that same value, /// without clearing the top byte 0xff. TEST_F(DWARFExpressionMockProcessTestWithAArch, DW_op_deref_no_ptr_fixing) { - llvm::DenseMap<lldb::addr_t, lldb::addr_t> memory; constexpr lldb::addr_t expected_value = ((0xffULL) << 56) | 0xabcdefULL; constexpr lldb::addr_t addr = 42; - memory[addr] = expected_value; + MockMemory::Map memory = { + {{addr, sizeof(addr)}, {0xef, 0xcd, 0xab, 0x00, 0x00, 0x00, 0x00, 0xff}}}; - PlatformTargetDebugger test_setup = CreateTarget(); - lldb::ProcessSP process_sp = std::make_shared<MockProcessWithMemRead>( - test_setup.target_sp, Listener::MakeListener("dummy"), std::move(memory)); - auto thread = std::make_shared<MockThread>(*process_sp); - lldb::RegisterContextSP reg_ctx_sp = - std::make_shared<MockRegisterContext>(*thread, RegisterValue(addr)); - thread->SetRegisterContext(reg_ctx_sp); - process_sp->GetThreadList().AddThread(thread); + TestContext test_ctx; + ASSERT_TRUE(CreateTestContext(&test_ctx, "aarch64-pc-linux", + RegisterValue(addr), std::move(memory))); auto evaluate_expr = [&](auto &expr_data) { - DataExtractor extractor(expr_data, sizeof(expr_data), - lldb::eByteOrderLittle, - /*addr_size*/ 8); - DWARFExpression expr(extractor); - - ExecutionContext exe_ctx(process_sp); - llvm::Expected<Value> result = DWARFExpression::Evaluate( - &exe_ctx, reg_ctx_sp.get(), /*module_sp*/ nullptr, extractor, - /*unit*/ nullptr, lldb::eRegisterKindLLDB, - /*initial_value_ptr=*/nullptr, - /*object_address_ptr=*/nullptr); - return result; + ExecutionContext exe_ctx(test_ctx.process_sp); + return Evaluate(expr_data, {}, {}, &exe_ctx, test_ctx.reg_ctx_sp.get()); }; uint8_t expr_reg[] = {DW_OP_breg22, 0}; llvm::Expected<Value> result_reg = evaluate_expr(expr_reg); - ASSERT_THAT_EXPECTED(result_reg, llvm::Succeeded()); - ASSERT_EQ(result_reg->GetValueType(), Value::ValueType::LoadAddress); - ASSERT_EQ(result_reg->GetScalar().ULongLong(), addr); + EXPECT_THAT_EXPECTED(result_reg, ExpectLoadAddress(addr)); uint8_t expr_deref[] = {DW_OP_breg22, 0, DW_OP_deref}; llvm::Expected<Value> result_deref = evaluate_expr(expr_deref); - ASSERT_THAT_EXPECTED(result_deref, llvm::Succeeded()); - ASSERT_EQ(result_deref->GetScalar().ULongLong(), expected_value); + EXPECT_THAT_EXPECTED(result_deref, ExpectLoadAddress(expected_value)); +} + +TEST_F(DWARFExpressionMockProcessTest, deref_register) { + TestContext test_ctx; + constexpr uint32_t reg_r0 = 0x504; + MockMemory::Map memory = { + {{0x004, 4}, {0x1, 0x2, 0x3, 0x4}}, + {{0x504, 4}, {0xa, 0xb, 0xc, 0xd}}, + {{0x505, 4}, {0x5, 0x6, 0x7, 0x8}}, + }; + ASSERT_TRUE(CreateTestContext(&test_ctx, "i386-pc-linux", + RegisterValue(reg_r0), memory, memory)); + + ExecutionContext exe_ctx(test_ctx.process_sp); + MockDwarfDelegate delegate = MockDwarfDelegate::Dwarf5(); + auto Eval = [&](llvm::ArrayRef<uint8_t> expr_data) { + ExecutionContext exe_ctx(test_ctx.process_sp); + return Evaluate(expr_data, {}, &delegate, &exe_ctx, + test_ctx.reg_ctx_sp.get()); + }; + + // Reads from the register r0. + // Sets the context to RegisterInfo so we know this is a register location. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0}), + ExpectScalar(reg_r0, Value::ContextType::RegisterInfo)); + + // Reads from the location(register r0). + // Clears the context so we know this is a value not a location. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0, DW_OP_deref}), + ExpectLoadAddress(reg_r0, Value::ContextType::Invalid)); + + // Reads from the location(register r0) and adds the value to the host buffer. + // The evaluator should implicitly convert it to a memory location when + // added to a composite value and should add the contents of memory[r0] + // to the host buffer. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0, DW_OP_deref, DW_OP_piece, 4}), + ExpectHostAddress({0xa, 0xb, 0xc, 0xd})); + + // Reads from the location(register r0) and truncates the value to one byte. + // Clears the context so we know this is a value not a location. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_reg0, DW_OP_deref_size, 1}), + ExpectLoadAddress(reg_r0 & 0xff, Value::ContextType::Invalid)); + + // Reads from the location(register r0) and truncates to one byte then adds + // the value to the host buffer. The evaluator should implicitly convert it to + // a memory location when added to a composite value and should add the + // contents of memory[r0 & 0xff] to the host buffer. + EXPECT_THAT_EXPECTED(Eval({DW_OP_reg0, DW_OP_deref_size, 1, DW_OP_piece, 4}), + ExpectHostAddress({0x1, 0x2, 0x3, 0x4})); + + // Reads from the register r0 + 1. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_breg0, 1}), + ExpectLoadAddress(reg_r0 + 1, Value::ContextType::Invalid)); + + // Reads from address r0 + 1, which contains the bytes [5,6,7,8]. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_breg0, 1, DW_OP_deref}), + ExpectLoadAddress(0x08070605, Value::ContextType::Invalid)); +} + +TEST_F(DWARFExpressionMockProcessTest, deref_implicit_value) { + TestContext test_ctx; + MockMemory::Map memory = { + {{0x4, 1}, {0x1}}, + {{0x4, 4}, {0x1, 0x2, 0x3, 0x4}}, + }; + ASSERT_TRUE(CreateTestContext(&test_ctx, "i386-pc-linux", {}, memory)); + + ExecutionContext exe_ctx(test_ctx.process_sp); + MockDwarfDelegate delegate = MockDwarfDelegate::Dwarf5(); + auto Eval = [&](llvm::ArrayRef<uint8_t> expr_data) { + ExecutionContext exe_ctx(test_ctx.process_sp); + return Evaluate(expr_data, {}, &delegate, &exe_ctx, + test_ctx.reg_ctx_sp.get()); + }; + + // Creates an implicit location with a value of 4. + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_stack_value}), + ExpectScalar(0x4)); + + // Creates an implicit location with a value of 4. The deref reads the value + // out of the location and implicitly converts it to a load address. + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_stack_value, DW_OP_deref}), + ExpectLoadAddress(0x4)); + + // Creates an implicit location with a value of 0x504 (uleb128(0x504) = + // 0xa84). The deref reads the low byte out of the location and implicitly + // converts it to a load address. + EXPECT_THAT_EXPECTED( + Eval({DW_OP_constu, 0x84, 0xa, DW_OP_stack_value, DW_OP_deref_size, 1}), + ExpectLoadAddress(0x4)); + + // The tests below are similar to the ones above, but there is no implicit + // location created by a stack_value operation. They are provided here as a + // reference to contrast with the above tests. + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4}), ExpectLoadAddress(0x4)); + + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_deref}), + ExpectLoadAddress(0x04030201)); + + EXPECT_THAT_EXPECTED(Eval({DW_OP_lit4, DW_OP_deref_size, 1}), + ExpectLoadAddress(0x01)); } diff --git a/lldb/unittests/Expression/ValueMatcher.cpp b/lldb/unittests/Expression/ValueMatcher.cpp new file mode 100644 index 0000000..ee7ccae --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.cpp @@ -0,0 +1,205 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ValueMatcher.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/InterleavedRange.h" +#include "llvm/Support/raw_os_ostream.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; + +static void FormatValueDetails(llvm::raw_ostream &os, + Value::ValueType value_type, + Value::ContextType context_type, + const Scalar &scalar, + llvm::ArrayRef<uint8_t> buffer_data) { + os << "Value("; + os << "value_type=" << Value::GetValueTypeAsCString(value_type); + os << ", context_type=" << Value::GetContextTypeAsCString(context_type); + + if (value_type == Value::ValueType::HostAddress) { + auto bytes_to_print = buffer_data.take_front(16); + os << ", buffer=["; + llvm::interleave( + bytes_to_print, + [&](uint8_t byte) { + os << llvm::format("%02x", static_cast<unsigned>(byte)); + }, + [&]() { os << " "; }); + if (buffer_data.size() > 16) + os << " ..."; + os << "] (" << buffer_data.size() << " bytes)"; + } else { + os << ", value=" << scalar; + } + os << ")"; +} + +void lldb_private::PrintTo(const Value &val, std::ostream *os) { + if (!os) + return; + + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, val.GetValueType(), val.GetContextType(), + val.GetScalar(), val.GetBuffer().GetData()); +} + +bool ValueMatcher::MatchAndExplain(const Value &val, + std::ostream *stream) const { + if (stream) { + llvm::raw_os_ostream os(*stream); + return MatchAndExplainImpl(val, os); + } + + llvm::raw_null_ostream os; + return MatchAndExplainImpl(val, os); +} + +// Match the provided value and explain any mismatches using +// the raw_ostream. We use the llvm::raw_ostream here to simplify the formatting +// of Scalar values which already know how to print themselves to that stream. +bool ValueMatcher::MatchAndExplainImpl(const Value &val, + llvm::raw_ostream &os) const { + if (val.GetValueType() != m_value_type) { + os << "value_type mismatch: expected " + << Value::GetValueTypeAsCString(m_value_type) << ", got " + << Value::GetValueTypeAsCString(val.GetValueType()) << " "; + return false; + } + + if (val.GetContextType() != m_context_type) { + os << "context_type mismatch: expected " + << Value::GetContextTypeAsCString(m_context_type) << ", got " + << Value::GetContextTypeAsCString(val.GetContextType()) << " "; + return false; + } + + if (m_value_type == Value::ValueType::HostAddress) { + const DataBufferHeap &buffer = val.GetBuffer(); + const size_t buffer_size = buffer.GetByteSize(); + if (buffer_size != m_expected_bytes.size()) { + os << "buffer size mismatch: expected " << m_expected_bytes.size() + << ", got " << buffer_size << " "; + return false; + } + + const uint8_t *data = buffer.GetBytes(); + for (size_t i = 0; i < buffer_size; ++i) { + if (data[i] != m_expected_bytes[i]) { + os << "byte mismatch at index " << i << ": expected " + << llvm::format("0x%02x", static_cast<unsigned>(m_expected_bytes[i])) + << ", got " << llvm::format("0x%02x", static_cast<unsigned>(data[i])) + << " "; + return false; + } + } + } else { + // For Scalar, FileAddress, and LoadAddress compare m_value. + const Scalar &actual_scalar = val.GetScalar(); + if (actual_scalar != m_expected_scalar) { + os << "scalar value mismatch: expected " << m_expected_scalar << ", got " + << actual_scalar; + return false; + } + } + + return true; +} + +void ValueMatcher::DescribeTo(std::ostream *os) const { + if (!os) + return; + llvm::raw_os_ostream raw_os(*os); + FormatValueDetails(raw_os, m_value_type, m_context_type, m_expected_scalar, + m_expected_bytes); +} + +void ValueMatcher::DescribeNegationTo(std::ostream *os) const { + if (!os) + return; + *os << "value does not match"; +} + +testing::Matcher<Value> +lldb_private::MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_scalar, context_type); +} + +testing::Matcher<Value> +lldb_private::MatchHostValue(Value::ValueType value_type, + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) { + return ValueMatcher(value_type, expected_bytes, context_type); +} + +testing::Matcher<Value> +lldb_private::IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::Scalar, expected_scalar, + context_type); +} + +testing::Matcher<Value> +lldb_private::IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::LoadAddress, expected_address, + context_type); +} + +testing::Matcher<Value> +lldb_private::IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return MatchScalarValue(Value::ValueType::FileAddress, expected_address, + context_type); +} + +testing::Matcher<Value> +lldb_private::IsHostValue(const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) { + return MatchHostValue(Value::ValueType::HostAddress, expected_bytes, + context_type); +} + +Scalar lldb_private::GetScalar(unsigned bits, uint64_t value, bool sign) { + Scalar scalar(value); + scalar.TruncOrExtendTo(bits, sign); + return scalar; +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type) { + return llvm::HasValue(IsScalar(expected_scalar, context_type)); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type) { + return ExpectScalar(GetScalar(bits, value, sign), context_type); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectLoadAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsLoadAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectFileAddress(const Scalar &expected_address, + Value::ContextType context_type) { + return llvm::HasValue(IsFileAddress(expected_address, context_type)); +} + +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +lldb_private::ExpectHostAddress(const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) { + return llvm::HasValue(IsHostValue(expected_bytes, context_type)); +} diff --git a/lldb/unittests/Expression/ValueMatcher.h b/lldb/unittests/Expression/ValueMatcher.h new file mode 100644 index 0000000..3ca7b15 --- /dev/null +++ b/lldb/unittests/Expression/ValueMatcher.h @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// \file +/// This file contains the definition of the ValueMatcher class which is a used +/// to match lldb_private::Value in gtest assert/expect macros. It also contains +/// several helper functions to create matchers for common Value types. +/// +/// The ValueMatcher class was created using the gtest guide found here: +// https://google.github.io/googletest/gmock_cook_book.html#writing-new-monomorphic-matchers +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H +#define LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H + +#include "lldb/Core/Value.h" +#include "lldb/Utility/Scalar.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" +#include <cstdint> +#include <vector> + +namespace lldb_private { + +/// Custom printer for Value objects to make test failures more readable. +void PrintTo(const Value &val, std::ostream *os); + +/// Custom matcher for Value. +/// +/// It matches against an expected value_type, and context_type. +/// For HostAddress value types it will match the expected contents of +/// the host buffer. For other value types it matches against an expected +/// scalar value. +class ValueMatcher { +public: + ValueMatcher(Value::ValueType value_type, const Scalar &expected_scalar, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_scalar(expected_scalar) { + assert(value_type == Value::ValueType::Scalar || + value_type == Value::ValueType::FileAddress || + value_type == Value::ValueType::LoadAddress); + } + + ValueMatcher(Value::ValueType value_type, + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type) + : m_value_type(value_type), m_context_type(context_type), + m_expected_bytes(expected_bytes) { + assert(value_type == Value::ValueType::HostAddress); + } + + // Typedef to hook into the gtest matcher machinery. + using is_gtest_matcher = void; + + bool MatchAndExplain(const Value &val, std::ostream *os) const; + + void DescribeTo(std::ostream *os) const; + + void DescribeNegationTo(std::ostream *os) const; + +private: + Value::ValueType m_value_type = Value::ValueType::Invalid; + Value::ContextType m_context_type = Value::ContextType::Invalid; + Scalar m_expected_scalar; + std::vector<uint8_t> m_expected_bytes; + + bool MatchAndExplainImpl(const Value &val, llvm::raw_ostream &os) const; +}; + +/// Matcher for Value with Scalar, FileAddress, or LoadAddress types. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchScalarValue(...))); +testing::Matcher<Value> MatchScalarValue(Value::ValueType value_type, + const Scalar &expected_scalar, + Value::ContextType context_type); + +/// Matcher for Value with HostAddress type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(MatchHostValue(...))); +testing::Matcher<Value> +MatchHostValue(Value::ValueType value_type, + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type); + +/// Helper to match a Scalar value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsScalar(42))); +testing::Matcher<Value> IsScalar(const Scalar &expected_scalar, + Value::ContextType context_type); + +/// Helper to match a LoadAddress value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsLoadAddress(0x1000))); +testing::Matcher<Value> IsLoadAddress(const Scalar &expected_address, + Value::ContextType context_type); + +/// Helper to match a FileAddress value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsFileAddress(Scalar(0x1000)))); +testing::Matcher<Value> IsFileAddress(const Scalar &expected_address, + Value::ContextType context_type); + +/// Helper to match a HostAddress value and context type. +/// Use with llvm::HasValue() to match Expected<Value>: +/// EXPECT_THAT_EXPECTED(result, llvm::HasValue(IsHostValue({0x11, 0x22}))); +testing::Matcher<Value> IsHostValue(const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type); + +/// Helper to create a scalar because Scalar's operator==() is really picky. +Scalar GetScalar(unsigned bits, uint64_t value, bool sign); + +/// Helper that combines IsScalar with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(42)); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +ExpectScalar(const Scalar &expected_scalar, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines GetScalar with ExpectScalar to get a precise scalar. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectScalar(8, 42, true)); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> +ExpectScalar(unsigned bits, uint64_t value, bool sign, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsLoadAddress with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectLoadAddress(0x1000)); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> ExpectLoadAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsFileAddress with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectFileAddress(Scalar(0x2000))); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> ExpectFileAddress( + const Scalar &expected_address, + Value::ContextType context_type = Value::ContextType::Invalid); + +/// Helper that combines IsHostValue with llvm::HasValue for Expected<Value>. +/// Use it on an Expected<Value> like this: +/// EXPECT_THAT_EXPECTED(result, ExpectHostAddress({0x11, 0x22})); +llvm::detail::ValueMatchesPoly<testing::Matcher<Value>> ExpectHostAddress( + const std::vector<uint8_t> &expected_bytes, + Value::ContextType context_type = Value::ContextType::Invalid); + +} // namespace lldb_private + +#endif // LLDB_UNITTESTS_EXPRESSION_VALUEMATCHER_H diff --git a/lldb/unittests/Host/FileTest.cpp b/lldb/unittests/Host/FileTest.cpp index d973d19..85697c4 100644 --- a/lldb/unittests/Host/FileTest.cpp +++ b/lldb/unittests/Host/FileTest.cpp @@ -8,6 +8,7 @@ #include "lldb/Host/File.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FileUtilities.h" #include "llvm/Support/Path.h" @@ -35,7 +36,7 @@ TEST(File, GetWaitableHandleFileno) { FILE *stream = fdopen(fd, "r"); ASSERT_TRUE(stream); - NativeFile file(stream, true); + NativeFile file(stream, File::eOpenOptionReadWrite, true); #ifdef _WIN32 EXPECT_EQ(file.GetWaitableHandle(), (HANDLE)_get_osfhandle(fd)); #else @@ -67,3 +68,22 @@ TEST(File, GetStreamFromDescriptor) { EXPECT_EQ(file.GetWaitableHandle(), (file_t)fd); #endif } + +TEST(File, ReadOnlyModeNotWritable) { + const auto *Info = testing::UnitTest::GetInstance()->current_test_info(); + llvm::SmallString<128> name; + int fd; + llvm::sys::fs::createTemporaryFile(llvm::Twine(Info->test_case_name()) + "-" + + Info->name(), + "test", fd, name); + + llvm::FileRemover remover(name); + ASSERT_GE(fd, 0); + + NativeFile file(fd, File::eOpenOptionReadOnly, true); + ASSERT_TRUE(file.IsValid()); + llvm::StringLiteral buf = "Hello World"; + size_t bytes_written = buf.size(); + Status error = file.Write(buf.data(), bytes_written); + EXPECT_EQ(error.Fail(), true); +} diff --git a/lldb/unittests/Host/common/CMakeLists.txt b/lldb/unittests/Host/common/CMakeLists.txt index 2934e6f0..8aa2dfb 100644 --- a/lldb/unittests/Host/common/CMakeLists.txt +++ b/lldb/unittests/Host/common/CMakeLists.txt @@ -1,4 +1,5 @@ set (FILES + DiagnosticsRenderingTest.cpp ZipFileResolverTest.cpp ) diff --git a/lldb/unittests/Utility/DiagnosticsRenderingTest.cpp b/lldb/unittests/Host/common/DiagnosticsRenderingTest.cpp index 4e5e0bb..851b478 100644 --- a/lldb/unittests/Utility/DiagnosticsRenderingTest.cpp +++ b/lldb/unittests/Host/common/DiagnosticsRenderingTest.cpp @@ -1,4 +1,4 @@ -#include "lldb/Utility/DiagnosticsRendering.h" +#include "lldb/Host/common/DiagnosticsRendering.h" #include "lldb/Utility/StreamString.h" #include "gtest/gtest.h" diff --git a/lldb/unittests/Interpreter/TestOptions.cpp b/lldb/unittests/Interpreter/TestOptions.cpp index 93474e3..e23dc0d 100644 --- a/lldb/unittests/Interpreter/TestOptions.cpp +++ b/lldb/unittests/Interpreter/TestOptions.cpp @@ -17,13 +17,13 @@ TEST(OptionsTest, CreateOptionParsingError) { ASSERT_THAT_ERROR( CreateOptionParsingError("yippee", 'f', "fun", "unable to convert 'yippee' to boolean"), - llvm::FailedWithMessage("Invalid value ('yippee') for -f (fun): unable " + llvm::FailedWithMessage("invalid value ('yippee') for -f (fun): unable " "to convert 'yippee' to boolean")); ASSERT_THAT_ERROR( CreateOptionParsingError("52", 'b', "bean-count"), - llvm::FailedWithMessage("Invalid value ('52') for -b (bean-count)")); + llvm::FailedWithMessage("invalid value ('52') for -b (bean-count)")); ASSERT_THAT_ERROR(CreateOptionParsingError("c", 'm'), - llvm::FailedWithMessage("Invalid value ('c') for -m")); + llvm::FailedWithMessage("invalid value ('c') for -m")); } diff --git a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp index 8c36f54..41df35f 100644 --- a/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp +++ b/lldb/unittests/Language/CPlusPlus/CPlusPlusLanguageTest.cpp @@ -30,6 +30,10 @@ TEST(CPlusPlusLanguage, MethodNameParsing) { {"foo::~bar(baz)", "", "foo", "~bar", "(baz)", "", "foo::~bar"}, {"a::b::c::d(e,f)", "", "a::b::c", "d", "(e,f)", "", "a::b::c::d"}, {"void f(int)", "void", "", "f", "(int)", "", "f"}, + {"std::vector<int>foo::bar()", "std::vector<int>", "foo", "bar", "()", "", + "foo::bar"}, + {"int foo::bar::func01(int a, double b)", "int", "foo::bar", "func01", + "(int a, double b)", "", "foo::bar::func01"}, // Operators {"std::basic_ostream<char, std::char_traits<char> >& " @@ -65,6 +69,12 @@ TEST(CPlusPlusLanguage, MethodNameParsing) { "const", "std::__1::ranges::__begin::__fn::operator()[abi:v160000]<char const, " "18ul>"}, + {"bool Ball[abi:BALL]<int>::operator<<[abi:operator]<int>(int)", "bool", + "Ball[abi:BALL]<int>", "operator<<[abi:operator]<int>", "(int)", "", + "Ball[abi:BALL]<int>::operator<<[abi:operator]<int>"}, + {"bool Ball[abi:BALL]<int>::operator>>[abi:operator]<int>(int)", "bool", + "Ball[abi:BALL]<int>", "operator>>[abi:operator]<int>", "(int)", "", + "Ball[abi:BALL]<int>::operator>>[abi:operator]<int>"}, // Internal classes {"operator<<(Cls, Cls)::Subclass::function()", "", "operator<<(Cls, Cls)::Subclass", "function", "()", "", @@ -101,6 +111,8 @@ TEST(CPlusPlusLanguage, MethodNameParsing) { "std::forward<decltype(nullptr)>"}, // Templates + {"vector<int > foo::bar::func(int)", "vector<int >", "foo::bar", "func", + "(int)", "", "foo::bar::func"}, {"void llvm::PM<llvm::Module, llvm::AM<llvm::Module>>::" "addPass<llvm::VP>(llvm::VP)", "void", "llvm::PM<llvm::Module, llvm::AM<llvm::Module>>", @@ -341,7 +353,7 @@ TEST(CPlusPlusLanguage, ExtractContextAndIdentifier) { llvm::StringRef context, basename; for (const auto &test : test_cases) { EXPECT_TRUE(CPlusPlusLanguage::ExtractContextAndIdentifier( - test.input.c_str(), context, basename)); + test.input, context, basename)); EXPECT_EQ(test.context, context.str()); EXPECT_EQ(test.basename, basename.str()); } diff --git a/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp b/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp index 70baa7e..4b018a29 100644 --- a/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp +++ b/lldb/unittests/Language/ObjC/ObjCLanguageTest.cpp @@ -112,3 +112,46 @@ TEST(ObjCLanguage, InvalidMethodNameParsing) { EXPECT_FALSE(lax_method.has_value()); } } + +struct ObjCMethodTestCase { + llvm::StringRef name; + bool is_valid; +}; + +struct ObjCMethodNameTextFiture + : public testing::TestWithParam<ObjCMethodTestCase> {}; + +static ObjCMethodTestCase g_objc_method_name_test_cases[] = { + {"", false}, + {"+[Uh oh!", false}, + {"-[Definitely not...", false}, + {"[Nice try ] :)", false}, + {"+MaybeIfYouSquintYourEyes]", false}, + {"?[Tricky]", false}, + {"[]", false}, + {"-[a", false}, + {"+[a", false}, + {"-]a]", false}, + {"+]a]", false}, + + // FIXME: should these count as valid? + {"+[]", true}, + {"-[]", true}, + {"-[[]", true}, + {"+[[]", true}, + {"+[a ]", true}, + {"-[a ]", true}, + + // Valid names + {"+[a a]", true}, + {"-[a a]", true}, +}; + +TEST_P(ObjCMethodNameTextFiture, TestIsPossibleObjCMethodName) { + // Tests ObjCLanguage::IsPossibleObjCMethodName + auto [name, expect_valid] = GetParam(); + EXPECT_EQ(ObjCLanguage::IsPossibleObjCMethodName(name), expect_valid); +} + +INSTANTIATE_TEST_SUITE_P(ObjCMethodNameTests, ObjCMethodNameTextFiture, + testing::ValuesIn(g_objc_method_name_test_cases)); diff --git a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp index 012eae0..966b37e 100644 --- a/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp +++ b/lldb/unittests/Process/gdb-remote/GDBRemoteCommunicationClientTest.cpp @@ -326,7 +326,7 @@ TEST_F(GDBRemoteCommunicationClientTest, SendSignalsToIgnore) { TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { const lldb::addr_t addr = 0xa000; - MemoryRegionInfo region_info; + lldb_private::MemoryRegionInfo region_info; std::future<Status> result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); }); @@ -343,13 +343,16 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { EXPECT_TRUE(result.get().Success()); EXPECT_EQ(addr, region_info.GetRange().GetRangeBase()); EXPECT_EQ(0x2000u, region_info.GetRange().GetByteSize()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetReadable()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetWritable()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetExecutable()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.GetReadable()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.GetWritable()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.GetExecutable()); EXPECT_EQ("/foo/bar.so", region_info.GetName().GetStringRef()); - EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.GetMemoryTagged()); - EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.IsStackMemory()); - EXPECT_EQ(MemoryRegionInfo::eDontKnow, region_info.IsShadowStack()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow, + region_info.GetMemoryTagged()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow, + region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eDontKnow, + region_info.IsShadowStack()); result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); @@ -358,9 +361,9 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;flags:;type:stack;"); EXPECT_TRUE(result.get().Success()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.GetMemoryTagged()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.IsStackMemory()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.IsShadowStack()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.GetMemoryTagged()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.IsShadowStack()); result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); @@ -369,9 +372,10 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;flags: mt zz mt ss ;type:ha,ha,stack;"); EXPECT_TRUE(result.get().Success()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.GetMemoryTagged()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.IsStackMemory()); - EXPECT_EQ(MemoryRegionInfo::eYes, region_info.IsShadowStack()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, + region_info.GetMemoryTagged()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eYes, region_info.IsShadowStack()); result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); @@ -380,12 +384,12 @@ TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfo) { HandlePacket(server, "qMemoryRegionInfo:a000", "start:a000;size:2000;type:heap;"); EXPECT_TRUE(result.get().Success()); - EXPECT_EQ(MemoryRegionInfo::eNo, region_info.IsStackMemory()); + EXPECT_EQ(lldb_private::MemoryRegionInfo::eNo, region_info.IsStackMemory()); } TEST_F(GDBRemoteCommunicationClientTest, GetMemoryRegionInfoInvalidResponse) { const lldb::addr_t addr = 0x4000; - MemoryRegionInfo region_info; + lldb_private::MemoryRegionInfo region_info; std::future<Status> result = std::async(std::launch::async, [&] { return client.GetMemoryRegionInfo(addr, region_info); }); diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 3d0e2d8..5694aee 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -137,6 +137,11 @@ lldb_private::python::LLDBSWIGPython_CastPyObjectToSBStream(PyObject *data) { } void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBThread(PyObject *data) { + return nullptr; +} + +void * lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrame(PyObject *data) { return nullptr; } @@ -161,6 +166,11 @@ void *lldb_private::python::LLDBSWIGPython_CastPyObjectToSBExecutionContext( return nullptr; } +void * +lldb_private::python::LLDBSWIGPython_CastPyObjectToSBFrameList(PyObject *data) { + return nullptr; +} + lldb::ValueObjectSP lldb_private::python::SWIGBridge::LLDBSWIGPython_GetValueObjectSPFromSBValue( void *data) { @@ -329,6 +339,11 @@ lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::ProcessSP) { return python::PythonObject(); } +python::PythonObject +lldb_private::python::SWIGBridge::ToSWIGWrapper(lldb::StackFrameListSP) { + return python::PythonObject(); +} + python::PythonObject lldb_private::python::SWIGBridge::ToSWIGWrapper( const lldb_private::StructuredDataImpl &) { return python::PythonObject(); diff --git a/lldb/unittests/Signals/UnixSignalsTest.cpp b/lldb/unittests/Signals/UnixSignalsTest.cpp index 582e441..3bd4aed 100644 --- a/lldb/unittests/Signals/UnixSignalsTest.cpp +++ b/lldb/unittests/Signals/UnixSignalsTest.cpp @@ -148,6 +148,18 @@ TEST(UnixSignalsTest, GetAsString) { signals.GetSignalDescription(16, 3, 0x1233, 0x1234, 0x5678)); } +TEST(UnixSignalsTest, GetNumberDescription) { + TestSignals signals; + + ASSERT_EQ("DESC2", signals.GetSignalNumberDescription(2)); + ASSERT_EQ("DESC4", signals.GetSignalNumberDescription(4)); + ASSERT_EQ("DESC8", signals.GetSignalNumberDescription(8)); + ASSERT_EQ("DESC16", signals.GetSignalNumberDescription(16)); + + // Unknown signal number. + ASSERT_EQ("", signals.GetSignalNumberDescription(100)); +} + TEST(UnixSignalsTest, VersionChange) { TestSignals signals; diff --git a/lldb/unittests/Symbol/LineTableTest.cpp b/lldb/unittests/Symbol/LineTableTest.cpp index eadab40..ca63c6f 100644 --- a/lldb/unittests/Symbol/LineTableTest.cpp +++ b/lldb/unittests/Symbol/LineTableTest.cpp @@ -176,10 +176,12 @@ Sections: if (!text_sp) return createStringError("No .text"); - auto cu_up = std::make_unique<CompileUnit>(module_sp, /*user_data=*/nullptr, - /*support_file_sp=*/nullptr, - /*uid=*/0, eLanguageTypeC, - /*is_optimized=*/eLazyBoolNo); + auto cu_up = std::make_unique<CompileUnit>( + module_sp, + /*user_data=*/nullptr, + /*support_file_nsp=*/std::make_shared<SupportFile>(), + /*uid=*/0, eLanguageTypeC, + /*is_optimized=*/eLazyBoolNo); LineTable *line_table = new LineTable(cu_up.get(), std::move(line_sequences)); cu_up->SetLineTable(line_table); cast<FakeSymbolFile>(module_sp->GetSymbolFile()) diff --git a/lldb/unittests/Symbol/TestClangASTImporter.cpp b/lldb/unittests/Symbol/TestClangASTImporter.cpp index f1b3d79..07c4208 100644 --- a/lldb/unittests/Symbol/TestClangASTImporter.cpp +++ b/lldb/unittests/Symbol/TestClangASTImporter.cpp @@ -287,7 +287,7 @@ TEST_F(TestClangASTImporter, RecordLayoutFromOrigin) { clang_utils::SourceASTWithRecord source; auto *dwarf_parser = - static_cast<DWARFASTParserClang *>(source.ast->GetDWARFParser()); + llvm::cast<DWARFASTParserClang>(source.ast->GetDWARFParser()); auto &importer = dwarf_parser->GetClangASTImporter(); // Set the layout for the origin decl in the origin ClangASTImporter. diff --git a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp index cef3a25a..6a753b6 100644 --- a/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp +++ b/lldb/unittests/SymbolFile/DWARF/DWARFASTParserClangTests.cpp @@ -42,6 +42,52 @@ public: return keys; } }; + +/// Helper structure for DWARFASTParserClang tests that want to parse DWARF +/// generated using yaml2obj. On construction parses the supplied YAML data +/// into a DWARF module and thereafter vends a DWARFASTParserClang and +/// TypeSystemClang that are guaranteed to live for the duration of this object. +class DWARFASTParserClangYAMLTester { +public: + DWARFASTParserClangYAMLTester(llvm::StringRef yaml_data) + : m_module_tester(yaml_data) {} + + DWARFDIE GetCUDIE() { + DWARFUnit *unit = m_module_tester.GetDwarfUnit(); + assert(unit); + + const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); + assert(cu_entry->Tag() == DW_TAG_compile_unit); + + return DWARFDIE(unit, cu_entry); + } + + DWARFASTParserClang &GetParser() { + auto *parser = GetTypeSystem().GetDWARFParser(); + + assert(llvm::isa_and_nonnull<DWARFASTParserClang>(parser)); + + return *llvm::cast<DWARFASTParserClang>(parser); + } + + TypeSystemClang &GetTypeSystem() { + ModuleSP module_sp = m_module_tester.GetModule(); + assert(module_sp); + + SymbolFile *symfile = module_sp->GetSymbolFile(); + assert(symfile); + + TypeSystemSP ts_sp = llvm::cantFail( + symfile->GetTypeSystemForLanguage(lldb::eLanguageTypeC_plus_plus)); + + assert(llvm::isa_and_nonnull<TypeSystemClang>(ts_sp.get())); + + return llvm::cast<TypeSystemClang>(*ts_sp); + } + +private: + YAMLModuleTester m_module_tester; +}; } // namespace // If your implementation needs to dereference the dummy pointers we are @@ -99,7 +145,6 @@ DWARF: - Value: 0x0000000000000001 - AbbrCode: 0x00000000 )"; - YAMLModuleTester t(yamldata); ASSERT_TRUE((bool)t.GetDwarfUnit()); @@ -248,17 +293,9 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); + DWARFASTParserClangYAMLTester tester(yamldata); - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFDIE cu_die = tester.GetCUDIE(); std::vector<std::string> found_function_types; // The DWARF above is just a list of functions. Parse all of them to @@ -267,7 +304,8 @@ DWARF: ASSERT_EQ(func.Tag(), DW_TAG_subprogram); SymbolContext sc; bool new_type = false; - lldb::TypeSP type = ast_parser.ParseTypeFromDWARF(sc, func, &new_type); + lldb::TypeSP type = + tester.GetParser().ParseTypeFromDWARF(sc, func, &new_type); found_function_types.push_back( type->GetForwardCompilerType().GetTypeName().AsCString()); } @@ -394,18 +432,9 @@ DWARF: - AbbrCode: 0x00 # end of child tags of 0x0c ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); DWARFDIE ptrauth_variable = cu_die.GetFirstChild(); ASSERT_EQ(ptrauth_variable.Tag(), DW_TAG_variable); DWARFDIE ptrauth_type = @@ -415,7 +444,7 @@ DWARF: SymbolContext sc; bool new_type = false; lldb::TypeSP type_sp = - ast_parser.ParseTypeFromDWARF(sc, ptrauth_type, &new_type); + tester.GetParser().ParseTypeFromDWARF(sc, ptrauth_type, &new_type); CompilerType compiler_type = type_sp->GetForwardCompilerType(); ASSERT_EQ(compiler_type.GetPtrAuthKey(), 0U); ASSERT_EQ(compiler_type.GetPtrAuthAddressDiversity(), false); @@ -554,24 +583,17 @@ TEST_F(DWARFASTParserClangTests, TestDefaultTemplateParamParsing) { auto BufferOrError = llvm::MemoryBuffer::getFile( GetInputFilePath("DW_AT_default_value-test.yaml"), /*IsText=*/true); ASSERT_TRUE(BufferOrError); - YAMLModuleTester t(BufferOrError.get()->getBuffer()); - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(BufferOrError.get()->getBuffer()); + DWARFDIE cu_die = tester.GetCUDIE(); llvm::SmallVector<lldb::TypeSP, 2> types; for (DWARFDIE die : cu_die.children()) { if (die.Tag() == DW_TAG_class_type) { SymbolContext sc; bool new_type = false; - types.push_back(ast_parser.ParseTypeFromDWARF(sc, die, &new_type)); + types.push_back( + tester.GetParser().ParseTypeFromDWARF(sc, die, &new_type)); } } @@ -605,23 +627,14 @@ TEST_F(DWARFASTParserClangTests, TestSpecDeclExistsError) { auto BufferOrError = llvm::MemoryBuffer::getFile( GetInputFilePath("DW_AT_spec_decl_exists-test.yaml"), /*IsText=*/true); ASSERT_TRUE(BufferOrError); - YAMLModuleTester t(BufferOrError.get()->getBuffer()); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(BufferOrError.get()->getBuffer()); + DWARFDIE cu_die = tester.GetCUDIE(); llvm::SmallVector<lldb::TypeSP, 2> specializations; for (DWARFDIE die : cu_die.children()) { SymbolContext sc; bool new_type = false; - auto type = ast_parser.ParseTypeFromDWARF(sc, die, &new_type); + auto type = tester.GetParser().ParseTypeFromDWARF(sc, die, &new_type); llvm::StringRef die_name = llvm::StringRef(die.GetName()); if (die_name.starts_with("_Optional_payload")) { specializations.push_back(std::move(type)); @@ -730,18 +743,8 @@ DWARF: - AbbrCode: 0x00 # end of child tags of 0x0c ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); DWARFDIE decl_die; DWARFDIE def_die; @@ -762,6 +765,8 @@ DWARF: ParsedDWARFTypeAttributes attrs(def_die); ASSERT_TRUE(attrs.decl.IsValid()); + DWARFASTParserClang &ast_parser = tester.GetParser(); + SymbolContext sc; bool new_type = false; lldb::TypeSP type_sp = ast_parser.ParseTypeFromDWARF(sc, decl_die, &new_type); @@ -906,18 +911,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -932,7 +927,8 @@ DWARF: auto param_die = decl_die.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(decl_die, context_die)); + EXPECT_EQ(param_die, + tester.GetParser().GetObjectParameter(decl_die, context_die)); } { @@ -945,8 +941,8 @@ DWARF: auto param_die = subprogram_definition.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(subprogram_definition, - context_die)); + EXPECT_EQ(param_die, tester.GetParser().GetObjectParameter( + subprogram_definition, context_die)); } } @@ -1076,18 +1072,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -1105,7 +1091,7 @@ DWARF: auto param_die = subprogram_definition.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); EXPECT_EQ(param_die, - ast_parser.GetObjectParameter(subprogram_definition, {})); + tester.GetParser().GetObjectParameter(subprogram_definition, {})); } TEST_F(DWARFASTParserClangTests, TestParseSubroutine_ExplicitObjectParameter) { @@ -1243,21 +1229,15 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto ts_or_err = cu_die.GetDWARF()->GetTypeSystemForLanguage(eLanguageTypeC_plus_plus); ASSERT_TRUE(static_cast<bool>(ts_or_err)); llvm::consumeError(ts_or_err.takeError()); auto *parser = - static_cast<DWARFASTParserClang *>((*ts_or_err)->GetDWARFParser()); + llvm::cast<DWARFASTParserClang>((*ts_or_err)->GetDWARFParser()); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -1419,22 +1399,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto ts_or_err = - cu_die.GetDWARF()->GetTypeSystemForLanguage(eLanguageTypeC_plus_plus); - ASSERT_TRUE(static_cast<bool>(ts_or_err)); - llvm::consumeError(ts_or_err.takeError()); - - auto *ts = static_cast<TypeSystemClang *>(ts_or_err->get()); - auto *parser = static_cast<DWARFASTParserClang *>(ts->GetDWARFParser()); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto subprogram = cu_die.GetFirstChild(); ASSERT_TRUE(subprogram.IsValid()); @@ -1442,11 +1408,13 @@ DWARF: SymbolContext sc; bool new_type; - auto type_sp = parser->ParseTypeFromDWARF(sc, subprogram, &new_type); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, subprogram, &new_type); ASSERT_NE(type_sp, nullptr); - auto result = ts->GetTranslationUnitDecl()->lookup( - clang_utils::getDeclarationName(*ts, "func")); + TypeSystemClang &ts = tester.GetTypeSystem(); + auto result = ts.GetTranslationUnitDecl()->lookup( + clang_utils::getDeclarationName(ts, "func")); ASSERT_TRUE(result.isSingleResult()); auto const *func = llvm::cast<clang::FunctionDecl>(result.front()); @@ -1609,19 +1577,8 @@ DWARF: - AbbrCode: 0x0 ... )"; - - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto context_die = cu_die.GetFirstChild(); ASSERT_TRUE(context_die.IsValid()); @@ -1640,7 +1597,8 @@ DWARF: auto param_die = sub1.GetFirstChild().GetSibling(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(sub1, context_die)); + EXPECT_EQ(param_die, + tester.GetParser().GetObjectParameter(sub1, context_die)); } // Object parameter is at constant index 0 @@ -1648,7 +1606,8 @@ DWARF: auto param_die = sub2.GetFirstChild(); ASSERT_TRUE(param_die.IsValid()); - EXPECT_EQ(param_die, ast_parser.GetObjectParameter(sub2, context_die)); + EXPECT_EQ(param_die, + tester.GetParser().GetObjectParameter(sub2, context_die)); } } @@ -1711,19 +1670,8 @@ DWARF: - Value: 0x02 ... )"; - - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto type_die = cu_die.GetFirstChild(); ASSERT_TRUE(type_die.IsValid()); @@ -1734,8 +1682,8 @@ DWARF: EXPECT_EQ(attrs.data_bit_size.value_or(0), 2U); SymbolContext sc; - auto type_sp = - ast_parser.ParseTypeFromDWARF(sc, type_die, /*type_is_new_ptr=*/nullptr); + auto type_sp = tester.GetParser().ParseTypeFromDWARF( + sc, type_die, /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ(llvm::expectedToOptional(type_sp->GetByteSize(nullptr)).value_or(0), @@ -1857,27 +1805,17 @@ DWARF: ... )"; - - YAMLModuleTester t(yamldata); - - DWARFUnit *unit = t.GetDwarfUnit(); - ASSERT_NE(unit, nullptr); - const DWARFDebugInfoEntry *cu_entry = unit->DIE().GetDIE(); - ASSERT_EQ(cu_entry->Tag(), DW_TAG_compile_unit); - ASSERT_EQ(unit->GetDWARFLanguageType(), DW_LANG_C_plus_plus); - DWARFDIE cu_die(unit, cu_entry); - - auto holder = std::make_unique<clang_utils::TypeSystemClangHolder>("ast"); - auto &ast_ctx = *holder->GetAST(); - DWARFASTParserClangStub ast_parser(ast_ctx); + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); auto type_die = cu_die.GetFirstChild(); ASSERT_TRUE(type_die.IsValid()); { SymbolContext sc; - auto type_sp = ast_parser.ParseTypeFromDWARF(sc, type_die, - /*type_is_new_ptr=*/nullptr); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ( @@ -1891,8 +1829,9 @@ DWARF: { type_die = type_die.GetSibling(); SymbolContext sc; - auto type_sp = ast_parser.ParseTypeFromDWARF(sc, type_die, - /*type_is_new_ptr=*/nullptr); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ( @@ -1906,8 +1845,9 @@ DWARF: { type_die = type_die.GetSibling(); SymbolContext sc; - auto type_sp = ast_parser.ParseTypeFromDWARF(sc, type_die, - /*type_is_new_ptr=*/nullptr); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ( @@ -1922,8 +1862,9 @@ DWARF: { type_die = type_die.GetSibling(); SymbolContext sc; - auto type_sp = ast_parser.ParseTypeFromDWARF(sc, type_die, - /*type_is_new_ptr=*/nullptr); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ( @@ -1938,8 +1879,9 @@ DWARF: { type_die = type_die.GetSibling(); SymbolContext sc; - auto type_sp = ast_parser.ParseTypeFromDWARF(sc, type_die, - /*type_is_new_ptr=*/nullptr); + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, type_die, + /*type_is_new_ptr=*/nullptr); ASSERT_NE(type_sp, nullptr); EXPECT_EQ( @@ -1953,3 +1895,226 @@ DWARF: EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "_BitInt(64)"); } } + +TEST_F(DWARFASTParserClangTests, TestTemplateAlias_NoSimpleTemplateNames) { + // Tests that we correctly parse the DW_TAG_template_alias generated by + // -gno-simple-template-names. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 +DWARF: + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Code: 0x2 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Code: 0x3 + Tag: DW_TAG_template_alias + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Code: 0x4 + Tag: DW_TAG_template_type_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + + debug_info: + - Version: 5 + UnitType: DW_UT_compile + AddrSize: 8 + Entries: + +# DW_TAG_compile_unit +# DW_AT_language (DW_LANG_C_plus_plus) + + - AbbrCode: 0x1 + Values: + - Value: 0x04 + +# DW_TAG_base_type +# DW_AT_name ('int') + + - AbbrCode: 0x2 + Values: + - CStr: int + +# DW_TAG_template_alias +# DW_AT_name ('Foo<int>') +# DW_AT_type ('int') +# DW_TAG_template_type_parameter +# DW_AT_name ('T') +# DW_AT_type ('int') + + - AbbrCode: 0x3 + Values: + - CStr: Foo<int> + - Value: 0xf + + - AbbrCode: 0x4 + Values: + - CStr: T + - Value: 0xf + + - AbbrCode: 0x0 + - AbbrCode: 0x0 +... +)"; + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); + + auto alias_die = cu_die.GetFirstChild().GetSibling(); + ASSERT_EQ(alias_die.Tag(), DW_TAG_template_alias); + + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, alias_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_TRUE(type_sp->IsTypedef()); + EXPECT_EQ(type_sp->GetName(), "Foo<int>"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "Foo<int>"); +} + +TEST_F(DWARFASTParserClangTests, + TestTemplateAlias_InStruct_NoSimpleTemplateNames) { + // Tests that we correctly parse the DW_TAG_template_alias scoped inside a + // DW_TAG_structure_type *declaration* generated by + // -gno-simple-template-names. This tests the codepath the forcefully + // completes the context of the alias via PrepareContextToReceiveMembers. + + const char *yamldata = R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_EXEC + Machine: EM_AARCH64 +DWARF: + debug_abbrev: + - ID: 0 + Table: + - Code: 0x1 + Tag: DW_TAG_compile_unit + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_language + Form: DW_FORM_data2 + - Code: 0x2 + Tag: DW_TAG_base_type + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Code: 0x3 + Tag: DW_TAG_structure_type + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_declaration + Form: DW_FORM_flag_present + - Code: 0x4 + Tag: DW_TAG_template_alias + Children: DW_CHILDREN_yes + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + - Code: 0x5 + Tag: DW_TAG_template_type_parameter + Children: DW_CHILDREN_no + Attributes: + - Attribute: DW_AT_name + Form: DW_FORM_string + - Attribute: DW_AT_type + Form: DW_FORM_ref4 + + debug_info: + - Version: 5 + UnitType: DW_UT_compile + AddrSize: 8 + Entries: + +# DW_TAG_compile_unit +# DW_AT_language (DW_LANG_C_plus_plus) + + - AbbrCode: 0x1 + Values: + - Value: 0x04 + +# DW_TAG_base_type +# DW_AT_name ('int') + + - AbbrCode: 0x2 + Values: + - CStr: int + +# DW_TAG_structure_type +# DW_AT_name ('Foo') + + - AbbrCode: 0x3 + Values: + - CStr: Foo + +# DW_TAG_template_alias +# DW_AT_name ('Bar<int>') +# DW_AT_type ('int') +# DW_TAG_template_type_parameter +# DW_AT_name ('T') +# DW_AT_type ('int') + + - AbbrCode: 0x4 + Values: + - CStr: Bar<int> + - Value: 0xf + + - AbbrCode: 0x5 + Values: + - CStr: T + - Value: 0xf + + - AbbrCode: 0x0 + - AbbrCode: 0x0 + - AbbrCode: 0x0 +... +)"; + DWARFASTParserClangYAMLTester tester(yamldata); + DWARFDIE cu_die = tester.GetCUDIE(); + + auto alias_die = cu_die.GetFirstChild().GetSibling().GetFirstChild(); + ASSERT_EQ(alias_die.Tag(), DW_TAG_template_alias); + + SymbolContext sc; + auto type_sp = + tester.GetParser().ParseTypeFromDWARF(sc, alias_die, + /*type_is_new_ptr=*/nullptr); + ASSERT_NE(type_sp, nullptr); + + EXPECT_TRUE(type_sp->IsTypedef()); + EXPECT_EQ(type_sp->GetName(), "Bar<int>"); + EXPECT_EQ(type_sp->GetForwardCompilerType().GetTypeName(), "Foo::Bar<int>"); +} diff --git a/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp b/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp index 17284b6..cd6db5f 100644 --- a/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp +++ b/lldb/unittests/SymbolFile/NativePDB/UdtRecordCompleterTests.cpp @@ -99,7 +99,7 @@ Member *AddField(Member *member, StringRef name, uint64_t byte_offset, std::make_unique<Member>(name, byte_offset * 8, byte_size * 8, clang::QualType(), lldb::eAccessPublic, 0); field->kind = kind; - field->base_offset = base_offset; + field->base_offset = base_offset * 8; member->fields.push_back(std::move(field)); return member->fields.back().get(); } @@ -111,6 +111,9 @@ TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { CollectMember("m2", 0, 4); CollectMember("m3", 0, 1); CollectMember("m4", 0, 8); + CollectMember("m5", 8, 8); + CollectMember("m6", 16, 4); + CollectMember("m7", 16, 8); ConstructRecord(); // struct { @@ -120,6 +123,11 @@ TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { // m3; // m4; // }; + // m5; + // union { + // m6; + // m7; + // }; // }; Record record; record.start_offset = 0; @@ -128,6 +136,10 @@ TEST_F(UdtRecordCompleterRecordTests, TestAnonymousUnionInStruct) { AddField(u, "m2", 0, 4, Member::Field); AddField(u, "m3", 0, 1, Member::Field); AddField(u, "m4", 0, 8, Member::Field); + AddField(&record.record, "m5", 8, 8, Member::Field); + Member *u2 = AddField(&record.record, "", 16, 0, Member::Union); + AddField(u2, "m6", 16, 4, Member::Field); + AddField(u2, "m7", 16, 8, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } @@ -243,3 +255,41 @@ TEST_F(UdtRecordCompleterRecordTests, TestNestedUnionStructInUnion) { AddField(s2, "m4", 2, 4, Member::Field); EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); } + +TEST_F(UdtRecordCompleterRecordTests, TestNestedStructInUnionInStructInUnion) { + SetKind(Member::Kind::Union); + CollectMember("m1", 0, 4); + CollectMember("m2", 0, 2); + CollectMember("m3", 0, 2); + CollectMember("m4", 2, 4); + CollectMember("m5", 6, 2); + CollectMember("m6", 6, 2); + CollectMember("m7", 8, 2); + ConstructRecord(); + + // union { + // m1; + // m2; + // struct { + // m3; + // m4; + // union { + // m5; + // m6; + // }; + // m7; + // }; + // }; + Record record; + record.start_offset = 0; + AddField(&record.record, "m1", 0, 4, Member::Field); + AddField(&record.record, "m2", 0, 2, Member::Field); + Member *s = AddField(&record.record, "", 0, 0, Member::Struct); + AddField(s, "m3", 0, 2, Member::Field); + AddField(s, "m4", 2, 4, Member::Field); + Member *u = AddField(s, "", 6, 0, Member::Union); + AddField(u, "m5", 6, 2, Member::Field); + AddField(u, "m6", 6, 2, Member::Field); + AddField(s, "m7", 8, 2, Member::Field); + EXPECT_EQ(WrappedRecord(this->record), WrappedRecord(record)); +} diff --git a/lldb/unittests/Target/LocateModuleCallbackTest.cpp b/lldb/unittests/Target/LocateModuleCallbackTest.cpp index 6ffa41b..d727cea 100644 --- a/lldb/unittests/Target/LocateModuleCallbackTest.cpp +++ b/lldb/unittests/Target/LocateModuleCallbackTest.cpp @@ -362,7 +362,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackFailureNoCache) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); ASSERT_FALSE(m_module_sp); } @@ -383,7 +383,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackFailureCached) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_FALSE(m_module_sp->GetSymbolFileFileSpec()); @@ -409,7 +409,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackNoFiles) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_FALSE(m_module_sp->GetSymbolFileFileSpec()); @@ -435,7 +435,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackNonExistentModule) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_FALSE(m_module_sp->GetSymbolFileFileSpec()); @@ -464,7 +464,7 @@ TEST_F(LocateModuleCallbackTest, GetOrCreateModuleCallbackNonExistentSymbol) { }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_TRUE(m_module_sp->GetSymbolFileFileSpec().GetPath().empty()); @@ -622,7 +622,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_EQ(m_module_sp->GetSymbolFileFileSpec(), @@ -650,7 +650,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_EQ(m_module_sp->GetSymbolFileFileSpec(), @@ -682,7 +682,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); CheckModule(m_module_sp); ASSERT_EQ(m_module_sp->GetFileSpec(), uuid_view); ASSERT_EQ(m_module_sp->GetSymbolFileFileSpec(), @@ -709,7 +709,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); ASSERT_FALSE(m_module_sp); } @@ -731,7 +731,7 @@ TEST_F(LocateModuleCallbackTest, }); m_module_sp = m_target_sp->GetOrCreateModule(m_module_spec, /*notify=*/false); - ASSERT_EQ(callback_call_count, 2); + ASSERT_EQ(callback_call_count, 3); ASSERT_FALSE(m_module_sp); } diff --git a/lldb/unittests/Target/MemoryTest.cpp b/lldb/unittests/Target/MemoryTest.cpp index e444f68..131a3ca 100644 --- a/lldb/unittests/Target/MemoryTest.cpp +++ b/lldb/unittests/Target/MemoryTest.cpp @@ -48,6 +48,8 @@ public: } Status DoDestroy() override { return {}; } void RefreshStateAfterStop() override {} + // Required by Target::ReadMemory() to call Process::ReadMemory() + bool IsAlive() override { return true; } size_t DoReadMemory(lldb::addr_t vm_addr, void *buf, size_t size, Status &error) override { if (m_bytes_left == 0) @@ -61,7 +63,7 @@ public: m_bytes_left -= size; } - memset(buf, 'B', num_bytes_to_write); + memset(buf, m_filler, num_bytes_to_write); return num_bytes_to_write; } bool DoUpdateThreadList(ThreadList &old_thread_list, @@ -72,8 +74,10 @@ public: // Test-specific additions size_t m_bytes_left; + int m_filler = 'B'; MemoryCache &GetMemoryCache() { return m_memory_cache; } void SetMaxReadSize(size_t size) { m_bytes_left = size; } + void SetFiller(int filler) { m_filler = filler; } }; } // namespace @@ -85,6 +89,18 @@ TargetSP CreateTarget(DebuggerSP &debugger_sp, ArchSpec &arch) { return target_sp; } +static ProcessSP CreateProcess(lldb::TargetSP target_sp) { + ListenerSP listener_sp(Listener::MakeListener("dummy")); + ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp); + + struct TargetHack : public Target { + void SetProcess(ProcessSP process) { m_process_sp = process; } + }; + static_cast<TargetHack *>(target_sp.get())->SetProcess(process_sp); + + return process_sp; +} + TEST_F(MemoryTest, TesetMemoryCacheRead) { ArchSpec arch("x86_64-apple-macosx-"); @@ -96,8 +112,7 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) { TargetSP target_sp = CreateTarget(debugger_sp, arch); ASSERT_TRUE(target_sp); - ListenerSP listener_sp(Listener::MakeListener("dummy")); - ProcessSP process_sp = std::make_shared<DummyProcess>(target_sp, listener_sp); + ProcessSP process_sp = CreateProcess(target_sp); ASSERT_TRUE(process_sp); DummyProcess *process = static_cast<DummyProcess *>(process_sp.get()); @@ -227,6 +242,58 @@ TEST_F(MemoryTest, TesetMemoryCacheRead) { // old cache } +TEST_F(MemoryTest, TestReadInteger) { + ArchSpec arch("x86_64-apple-macosx-"); + + Platform::SetHostPlatform(PlatformRemoteMacOSX::CreateInstance(true, &arch)); + + DebuggerSP debugger_sp = Debugger::CreateInstance(); + ASSERT_TRUE(debugger_sp); + + TargetSP target_sp = CreateTarget(debugger_sp, arch); + ASSERT_TRUE(target_sp); + + ProcessSP process_sp = CreateProcess(target_sp); + ASSERT_TRUE(process_sp); + + DummyProcess *process = static_cast<DummyProcess *>(process_sp.get()); + Status error; + + process->SetFiller(0xff); + process->SetMaxReadSize(256); + // The ReadSignedIntegerFromMemory() methods return int64_t. Check that they + // extend the sign correctly when reading 32-bit values. + EXPECT_EQ(-1, + target_sp->ReadSignedIntegerFromMemory(Address(0), 4, 0, error)); + EXPECT_EQ(-1, process->ReadSignedIntegerFromMemory(0, 4, 0, error)); + // Check reading 64-bit values as well. + EXPECT_EQ(-1, + target_sp->ReadSignedIntegerFromMemory(Address(0), 8, 0, error)); + EXPECT_EQ(-1, process->ReadSignedIntegerFromMemory(0, 8, 0, error)); + + // ReadUnsignedIntegerFromMemory() should not extend the sign. + EXPECT_EQ(0xffffffffULL, + target_sp->ReadUnsignedIntegerFromMemory(Address(0), 4, 0, error)); + EXPECT_EQ(0xffffffffULL, + process->ReadUnsignedIntegerFromMemory(0, 4, 0, error)); + EXPECT_EQ(0xffffffffffffffffULL, + target_sp->ReadUnsignedIntegerFromMemory(Address(0), 8, 0, error)); + EXPECT_EQ(0xffffffffffffffffULL, + process->ReadUnsignedIntegerFromMemory(0, 8, 0, error)); + + // Check reading positive values. + process->GetMemoryCache().Clear(); + process->SetFiller(0x7f); + process->SetMaxReadSize(256); + EXPECT_EQ(0x7f7f7f7fLL, + target_sp->ReadSignedIntegerFromMemory(Address(0), 4, 0, error)); + EXPECT_EQ(0x7f7f7f7fLL, process->ReadSignedIntegerFromMemory(0, 4, 0, error)); + EXPECT_EQ(0x7f7f7f7f7f7f7f7fLL, + target_sp->ReadSignedIntegerFromMemory(Address(0), 8, 0, error)); + EXPECT_EQ(0x7f7f7f7f7f7f7f7fLL, + process->ReadSignedIntegerFromMemory(0, 8, 0, error)); +} + /// A process class that, when asked to read memory from some address X, returns /// the least significant byte of X. class DummyReaderProcess : public Process { diff --git a/lldb/unittests/Target/RemoteAwarePlatformTest.cpp b/lldb/unittests/Target/RemoteAwarePlatformTest.cpp index 3278674..cfcec69 100644 --- a/lldb/unittests/Target/RemoteAwarePlatformTest.cpp +++ b/lldb/unittests/Target/RemoteAwarePlatformTest.cpp @@ -32,15 +32,12 @@ public: ProcessSP(ProcessAttachInfo &, Debugger &, Target *, Status &)); MOCK_METHOD0(CalculateTrapHandlerSymbolNames, void()); - MOCK_METHOD2(ResolveExecutable, - std::pair<bool, ModuleSP>(const ModuleSpec &, - const FileSpecList *)); - Status - ResolveExecutable(const ModuleSpec &module_spec, - lldb::ModuleSP &exe_module_sp, - const FileSpecList *module_search_paths_ptr) /*override*/ + MOCK_METHOD1(ResolveExecutable, + std::pair<bool, ModuleSP>(const ModuleSpec &)); + Status ResolveExecutable(const ModuleSpec &module_spec, + lldb::ModuleSP &exe_module_sp) /*override*/ { // NOLINT(modernize-use-override) - auto pair = ResolveExecutable(module_spec, module_search_paths_ptr); + auto pair = ResolveExecutable(module_spec); exe_module_sp = pair.second; return pair.first ? Status() : Status::FromErrorString("error"); } @@ -80,14 +77,14 @@ TEST_F(RemoteAwarePlatformTest, TestResolveExecutabelOnClientByPlatform) { static const ArchSpec process_host_arch; EXPECT_CALL(platform, GetSupportedArchitectures(process_host_arch)) .WillRepeatedly(Return(std::vector<ArchSpec>())); - EXPECT_CALL(platform, ResolveExecutable(_, _)) + EXPECT_CALL(platform, ResolveExecutable(_)) .WillRepeatedly(Return(std::make_pair(true, expected_executable))); platform.SetRemotePlatform(std::make_shared<TargetPlatformTester>(false)); ModuleSP resolved_sp; lldb_private::Status status = - platform.ResolveExecutable(executable_spec, resolved_sp, nullptr); + platform.ResolveExecutable(executable_spec, resolved_sp); ASSERT_TRUE(status.Success()); EXPECT_EQ(expected_executable.get(), resolved_sp.get()); diff --git a/lldb/unittests/TestingSupport/TestUtilities.cpp b/lldb/unittests/TestingSupport/TestUtilities.cpp index b53822e..d164c22 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.cpp +++ b/lldb/unittests/TestingSupport/TestUtilities.cpp @@ -20,6 +20,11 @@ using namespace lldb_private; extern const char *TestMainArgv0; std::once_flag TestUtilities::g_debugger_initialize_flag; + +std::string lldb_private::PrettyPrint(const llvm::json::Value &value) { + return llvm::formatv("{0:2}", value).str(); +} + std::string lldb_private::GetInputFilePath(const llvm::Twine &name) { llvm::SmallString<128> result = llvm::sys::path::parent_path(TestMainArgv0); llvm::sys::fs::make_absolute(result); diff --git a/lldb/unittests/TestingSupport/TestUtilities.h b/lldb/unittests/TestingSupport/TestUtilities.h index cc93a68..f05d176 100644 --- a/lldb/unittests/TestingSupport/TestUtilities.h +++ b/lldb/unittests/TestingSupport/TestUtilities.h @@ -30,6 +30,10 @@ } namespace lldb_private { + +/// Returns a pretty printed json string of a `llvm::json::Value`. +std::string PrettyPrint(const llvm::json::Value &E); + std::string GetInputFilePath(const llvm::Twine &name); class TestUtilities { diff --git a/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp b/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp index eaf23fd..b72abc1 100644 --- a/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp +++ b/lldb/unittests/UnwindAssembly/ARM64/TestArm64InstEmulation.cpp @@ -856,3 +856,233 @@ TEST_F(TestArm64InstEmulation, TestCFAResetToSP) { EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); } + +TEST_F(TestArm64InstEmulation, TestPrologueStartsWithStrD8) { + ArchSpec arch("aarch64"); + std::unique_ptr<UnwindAssemblyInstEmulation> engine( + static_cast<UnwindAssemblyInstEmulation *>( + UnwindAssemblyInstEmulation::CreateInstance(arch))); + ASSERT_NE(nullptr, engine); + + const UnwindPlan::Row *row; + AddressRange sample_range; + UnwindPlan unwind_plan(eRegisterKindLLDB); + UnwindPlan::Row::AbstractRegisterLocation regloc; + + // The sample function is built with 'clang --target aarch64 -O1': + // + // int bar(float x); + // int foo(float x) { + // return bar(x) + bar(x); + // } + // + // The function uses one floating point register and spills it with + // 'str d8, [sp, #-0x20]!'. + + // clang-format off + uint8_t data[] = { + // prologue + 0xe8, 0x0f, 0x1e, 0xfc, // 0: fc1e0fe8 str d8, [sp, #-0x20]! + 0xfd, 0xfb, 0x00, 0xa9, // 4: a900fbfd stp x29, x30, [sp, #0x8] + 0xf3, 0x0f, 0x00, 0xf9, // 8: f9000ff3 str x19, [sp, #0x18] + 0xfd, 0x23, 0x00, 0x91, // 12: 910023fd add x29, sp, #0x8 + + // epilogue + 0xfd, 0xfb, 0x40, 0xa9, // 16: a940fbfd ldp x29, x30, [sp, #0x8] + 0xf3, 0x0f, 0x40, 0xf9, // 20: f9400ff3 ldr x19, [sp, #0x18] + 0xe8, 0x07, 0x42, 0xfc, // 24: fc4207e8 ldr d8, [sp], #0x20 + 0xc0, 0x03, 0x5f, 0xd6, // 28: d65f03c0 ret + }; + // clang-format on + + // UnwindPlan we expect: + // 0: CFA=sp +0 => + // 4: CFA=sp+32 => d8=[CFA-32] + // 8: CFA=sp+32 => fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + // 12: CFA=sp+32 => x19=[CFA-8] fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + // 16: CFA=fp+24 => x19=[CFA-8] fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + // 20: CFA=sp+32 => x19=[CFA-8] fp=<same> lr=<same> d8=[CFA-32] + // 24: CFA=sp+32 => x19=<same> fp=<same> lr=<same> d8=[CFA-32] + // 28: CFA=sp +0 => x19=<same> fp=<same> lr=<same> d8=<same> + + sample_range = AddressRange(0x1000, sizeof(data)); + + EXPECT_TRUE(engine->GetNonCallSiteUnwindPlanFromAssembly( + sample_range, data, sizeof(data), unwind_plan)); + + // 4: CFA=sp+32 => d8=[CFA-32] + row = unwind_plan.GetRowForFunctionOffset(4); + EXPECT_EQ(4, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(32, row->GetCFAValue().GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(fpu_d8_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-32, regloc.GetOffset()); + + // 16: CFA=fp+24 => x19=[CFA-8] fp=[CFA-24] lr=[CFA-16] d8=[CFA-32] + row = unwind_plan.GetRowForFunctionOffset(16); + EXPECT_EQ(16, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_fp_arm64); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(24, row->GetCFAValue().GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(gpr_x19_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-8, regloc.GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(gpr_fp_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-24, regloc.GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(gpr_lr_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-16, regloc.GetOffset()); + + EXPECT_TRUE(row->GetRegisterInfo(fpu_d8_arm64, regloc)); + EXPECT_TRUE(regloc.IsAtCFAPlusOffset()); + EXPECT_EQ(-32, regloc.GetOffset()); + + // 28: CFA=sp +0 => x19=<same> fp=<same> lr=<same> d8=<same> + row = unwind_plan.GetRowForFunctionOffset(28); + EXPECT_EQ(28, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset() == true); + EXPECT_EQ(0, row->GetCFAValue().GetOffset()); + + if (row->GetRegisterInfo(gpr_x19_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } + if (row->GetRegisterInfo(gpr_fp_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } + if (row->GetRegisterInfo(gpr_lr_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } + if (row->GetRegisterInfo(fpu_d8_arm64, regloc)) { + EXPECT_TRUE(regloc.IsSame()); + } +} + +TEST_F(TestArm64InstEmulation, TestMidFunctionEpilogueAndBackwardsJump) { + ArchSpec arch("arm64-apple-ios15"); + std::unique_ptr<UnwindAssemblyInstEmulation> engine( + static_cast<UnwindAssemblyInstEmulation *>( + UnwindAssemblyInstEmulation::CreateInstance(arch))); + ASSERT_NE(nullptr, engine); + + const UnwindPlan::Row *row; + AddressRange sample_range; + UnwindPlan unwind_plan(eRegisterKindLLDB); + UnwindPlan::Row::AbstractRegisterLocation regloc; + + // clang-format off + uint8_t data[] = { + 0xff, 0xc3, 0x00, 0xd1, // <+0>: sub sp, sp, #0x30 + 0xfd, 0x7b, 0x02, 0xa9, // <+4>: stp x29, x30, [sp, #0x20] + 0xfd, 0x83, 0x00, 0x91, // <+8>: add x29, sp, #0x20 + 0x1f, 0x04, 0x00, 0xf1, // <+12>: cmp x0, #0x1 + 0x21, 0x01, 0x00, 0x54, // <+16>: b.ne ; <+52> DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + 0xfd, 0x7b, 0x42, 0xa9, // <+20>: ldp x29, x30, [sp, #0x20] + 0xff, 0xc3, 0x00, 0x91, // <+24>: add sp, sp, #0x30 + 0xc0, 0x03, 0x5f, 0xd6, // <+28>: ret + // AFTER_EPILOGUE + 0x37, 0x00, 0x80, 0xd2, // <+32>: mov x23, #0x1 + 0xf6, 0x5f, 0x41, 0xa9, // <+36>: ldp x22, x23, [sp, #0x10] + 0xfd, 0x7b, 0x42, 0xa9, // <+40>: ldp x29, x30, [sp, #0x20] + 0xff, 0xc3, 0x00, 0x91, // <+44>: add sp, sp, #0x30 + 0xc0, 0x03, 0x5f, 0xd6, // <+48>: ret + // DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + 0xf6, 0x5f, 0x01, 0xa9, // <+52>: stp x22, x23, [sp, #0x10] + 0x36, 0x00, 0x80, 0xd2, // <+56>: mov x22, #0x1 + 0x37, 0x00, 0x80, 0xd2, // <+60>: mov x23, #0x1 + 0xf8, 0xff, 0xff, 0x17, // <+64>: b ; <+32> AFTER_EPILOGUE + }; + + // UnwindPlan we expect: + // row[0]: 0: CFA=sp +0 => + // row[1]: 4: CFA=sp+48 => + // row[2]: 8: CFA=sp+16 => fp=[CFA-16] lr=[CFA-8] + // row[3]: 12: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] + // row[4]: 24: CFA=sp+48 => fp=<same> lr=<same> + // + // This must come from +56 + // row[5]: 32: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] x22=[CFA-32], x23=[CFA-24] + // row[6]: 40: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] x22=same, x23 = same + // row[6]: 44: CFA=sp+48 => fp=same lr=same x22=same, x23 = same + // row[6]: 48: CFA=sp0 => fp=same lr=same x22=same, x23 = same + // + // row[x]: 52: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] + // row[x]: 56: CFA=fp+16 => fp=[CFA-16] lr=[CFA-8] x22=[CFA-32], x23=[CFA-24] + // clang-format on + + sample_range = AddressRange(0x1000, sizeof(data)); + + EXPECT_TRUE(engine->GetNonCallSiteUnwindPlanFromAssembly( + sample_range, data, sizeof(data), unwind_plan)); + + // At the end of prologue (+12), CFA = fp + 16. + // <+0>: sub sp, sp, #0x30 + // <+4>: stp x29, x30, [sp, #0x20] + // <+8>: add x29, sp, #0x20 + row = unwind_plan.GetRowForFunctionOffset(12); + EXPECT_EQ(12, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_EQ(row->GetCFAValue().GetRegisterNumber(), gpr_fp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 16); + + // +16 and +20 are the same as +12. + // <+12>: cmp x0, #0x1 + // <+16>: b.ne ; <+52> DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + EXPECT_EQ(12, unwind_plan.GetRowForFunctionOffset(16)->GetOffset()); + EXPECT_EQ(12, unwind_plan.GetRowForFunctionOffset(20)->GetOffset()); + + // After restoring $fp to caller's value, CFA = $sp + 48 + // <+20>: ldp x29, x30, [sp, #0x20] + row = unwind_plan.GetRowForFunctionOffset(24); + EXPECT_EQ(24, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 48); + + // $sp has been restored + // <+24>: add sp, sp, #0x30 + row = unwind_plan.GetRowForFunctionOffset(28); + EXPECT_EQ(28, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_TRUE(row->GetCFAValue().GetRegisterNumber() == gpr_sp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 0); + + // Row for offset +32 should not inherit the state of the `ret` instruction + // in +28. Instead, it should inherit the state of the branch in +64. + // Check for register x22, which is available in row +64. + // <+28>: ret + // <+32>: mov x23, #0x1 + row = unwind_plan.GetRowForFunctionOffset(32); + EXPECT_EQ(32, row->GetOffset()); + { + UnwindPlan::Row::AbstractRegisterLocation loc; + EXPECT_TRUE(row->GetRegisterInfo(gpr_x22_arm64, loc)); + EXPECT_TRUE(loc.IsAtCFAPlusOffset()); + EXPECT_EQ(loc.GetOffset(), -32); + } + + // Check that the state of this branch + // <+16>: b.ne ; <+52> DO_SOMETHING_AND_GOTO_AFTER_EPILOGUE + // was forwarded to the branch target: + // <+52>: stp x22, x23, [sp, #0x10] + row = unwind_plan.GetRowForFunctionOffset(52); + EXPECT_EQ(52, row->GetOffset()); + EXPECT_TRUE(row->GetCFAValue().IsRegisterPlusOffset()); + EXPECT_EQ(row->GetCFAValue().GetRegisterNumber(), gpr_fp_arm64); + EXPECT_EQ(row->GetCFAValue().GetOffset(), 16); + + row = unwind_plan.GetRowForFunctionOffset(64); + { + UnwindPlan::Row::AbstractRegisterLocation loc; + EXPECT_TRUE(row->GetRegisterInfo(gpr_x22_arm64, loc)); + EXPECT_TRUE(loc.IsAtCFAPlusOffset()); + EXPECT_EQ(loc.GetOffset(), -32); + } +} diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt index aed4177..4cbe15b 100644 --- a/lldb/unittests/Utility/CMakeLists.txt +++ b/lldb/unittests/Utility/CMakeLists.txt @@ -10,7 +10,6 @@ add_lldb_unittest(UtilityTests DataBufferTest.cpp DataEncoderTest.cpp DataExtractorTest.cpp - DiagnosticsRenderingTest.cpp EnvironmentTest.cpp EventTest.cpp FileSpecListTest.cpp @@ -48,6 +47,7 @@ add_lldb_unittest(UtilityTests UserIDResolverTest.cpp UUIDTest.cpp VASprintfTest.cpp + VirtualDataExtractorTest.cpp VMRangeTest.cpp XcodeSDKTest.cpp diff --git a/lldb/unittests/Utility/RegisterValueTest.cpp b/lldb/unittests/Utility/RegisterValueTest.cpp index 6239dbe..7b27e84 100644 --- a/lldb/unittests/Utility/RegisterValueTest.cpp +++ b/lldb/unittests/Utility/RegisterValueTest.cpp @@ -57,13 +57,12 @@ TEST(RegisterValueTest, GetScalarValue) { APInt(128, 0x7766554433221100))); } -static const Scalar etalon128(APInt(128, 0xffeeddccbbaa9988ull) << 64 | - APInt(128, 0x7766554433221100ull)); - -void TestSetValueFromData128(void *src, const lldb::ByteOrder endianness) { - RegisterInfo ri{"uint128_register", +void TestSetValueFromData(const Scalar &etalon, void *src, size_t src_byte_size, + const lldb::ByteOrder endianness, + const RegisterValue::Type register_value_type) { + RegisterInfo ri{"test", nullptr, - 16, + static_cast<uint32_t>(src_byte_size), 0, lldb::Encoding::eEncodingUint, lldb::Format::eFormatDefault, @@ -71,26 +70,289 @@ void TestSetValueFromData128(void *src, const lldb::ByteOrder endianness) { nullptr, nullptr, nullptr}; - DataExtractor src_extractor(src, 16, endianness, 8); + DataExtractor src_extractor(src, src_byte_size, endianness, 8); RegisterValue rv; EXPECT_TRUE(rv.SetValueFromData(ri, src_extractor, 0, false).Success()); Scalar s; EXPECT_TRUE(rv.GetScalarValue(s)); - EXPECT_EQ(s, etalon128); + EXPECT_EQ(rv.GetType(), register_value_type); + EXPECT_EQ(s, etalon); +} + +static const Scalar etalon7(APInt(32, 0x0000007F)); + +TEST(RegisterValueTest, SetValueFromData_7_le) { + uint8_t src[] = {0x7F}; + TestSetValueFromData(etalon7, src, 1, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt8); +} + +TEST(RegisterValueTest, SetValueFromData_7_be) { + uint8_t src[] = {0x7F}; + TestSetValueFromData(etalon7, src, 1, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt8); +} + +static const Scalar etalon8(APInt(32, 0x000000FE)); + +TEST(RegisterValueTest, SetValueFromData_8_le) { + uint8_t src[] = {0xFE}; + TestSetValueFromData(etalon8, src, 1, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt8); +} + +TEST(RegisterValueTest, SetValueFromData_8_be) { + uint8_t src[] = {0xFE}; + TestSetValueFromData(etalon8, src, 1, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt8); +} + +static const Scalar etalon9(APInt(32, 0x000001FE)); + +TEST(RegisterValueTest, SetValueFromData_9_le) { + uint8_t src[] = {0xFE, 0x01}; + TestSetValueFromData(etalon9, src, 2, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt16); +} + +TEST(RegisterValueTest, SetValueFromData_9_be) { + uint8_t src[] = {0x01, 0xFE}; + TestSetValueFromData(etalon9, src, 2, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt16); +} + +static const Scalar etalon15(APInt(32, 0x00007FED)); + +TEST(RegisterValueTest, SetValueFromData_15_le) { + uint8_t src[] = {0xED, 0x7F}; + TestSetValueFromData(etalon15, src, 2, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt16); +} + +TEST(RegisterValueTest, SetValueFromData_15_be) { + uint8_t src[] = {0x7F, 0xED}; + TestSetValueFromData(etalon15, src, 2, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt16); +} + +static const Scalar etalon16(APInt(32, 0x0000FEDC)); + +TEST(RegisterValueTest, SetValueFromData_16_le) { + uint8_t src[] = {0xDC, 0xFE}; + TestSetValueFromData(etalon16, src, 2, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt16); +} + +TEST(RegisterValueTest, SetValueFromData_16_be) { + uint8_t src[] = {0xFE, 0xDC}; + TestSetValueFromData(etalon16, src, 2, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt16); +} + +static const Scalar etalon17(APInt(32, 0x0001FEDC)); + +TEST(RegisterValueTest, SetValueFromData_17_le) { + uint8_t src[] = {0xDC, 0xFE, 0x01}; + TestSetValueFromData(etalon17, src, 3, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); +} + +TEST(RegisterValueTest, SetValueFromData_17_be) { + uint8_t src[] = {0x01, 0xFE, 0xDC}; + TestSetValueFromData(etalon17, src, 3, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon24(APInt(32, 0x00FEDCBA)); + +TEST(RegisterValueTest, SetValueFromData_24_le) { + uint8_t src[] = {0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon24, src, 3, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); +} + +TEST(RegisterValueTest, SetValueFromData_24_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA}; + TestSetValueFromData(etalon24, src, 3, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon31(APInt(32, 0x7EDCBA98)); + +TEST(RegisterValueTest, SetValueFromData_31_le) { + uint8_t src[] = {0x98, 0xBA, 0xDC, 0x7E}; + TestSetValueFromData(etalon31, src, 4, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); +} + +TEST(RegisterValueTest, SetValueFromData_31_be) { + uint8_t src[] = {0x7E, 0xDC, 0xBA, 0x98}; + TestSetValueFromData(etalon31, src, 4, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon32(APInt(32, 0xFEDCBA98)); + +TEST(RegisterValueTest, SetValueFromData_32_le) { + uint8_t src[] = {0x98, 0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon32, src, 4, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt32); } -// Test that the "RegisterValue::SetValueFromData" method works correctly -// with 128-bit little-endian data that represents an integer. +TEST(RegisterValueTest, SetValueFromData_32_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA, 0x98}; + TestSetValueFromData(etalon32, src, 4, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt32); +} + +static const Scalar etalon33(APInt(64, 0x00000001FEDCBA98)); + +TEST(RegisterValueTest, SetValueFromData_33_le) { + uint8_t src[] = {0x98, 0xBA, 0xDC, 0xFE, 0x01}; + TestSetValueFromData(etalon33, src, 5, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_33_be) { + uint8_t src[] = {0x01, 0xFE, 0xDC, 0xBA, 0x98}; + TestSetValueFromData(etalon33, src, 5, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon40(APInt(64, 0x000000FEDCBA9876)); + +TEST(RegisterValueTest, SetValueFromData_40_le) { + uint8_t src[] = {0x76, 0x98, 0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon40, src, 5, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_40_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA, 0x98, 0x76}; + TestSetValueFromData(etalon40, src, 5, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon63(APInt(64, 0x7EDCBA9876543210)); + +TEST(RegisterValueTest, SetValueFromData_63_le) { + uint8_t src[] = {0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0x7E}; + TestSetValueFromData(etalon63, src, 8, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_63_be) { + uint8_t src[] = {0x7E, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; + TestSetValueFromData(etalon63, src, 8, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon64(APInt(64, 0xFEDCBA9876543210)); + +TEST(RegisterValueTest, SetValueFromData_64_le) { + uint8_t src[] = {0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE}; + TestSetValueFromData(etalon64, src, 8, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUInt64); +} + +TEST(RegisterValueTest, SetValueFromData_64_be) { + uint8_t src[] = {0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10}; + TestSetValueFromData(etalon64, src, 8, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUInt64); +} + +static const Scalar etalon65(APInt(72, 0x0000000000000001ull) << 1 * 64 | + APInt(72, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_65_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x01}; + TestSetValueFromData(etalon65, src, 9, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_65_be) { + uint8_t src[] = {0x01, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon65, src, 9, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon127(APInt(128, 0x7f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(128, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_127_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x7f}; + TestSetValueFromData(etalon127, src, 16, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_127_be) { + uint8_t src[] = {0x7f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon127, src, 16, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon128(APInt(128, 0x0f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(128, 0x0706050403020100ull) << 0 * 64); + TEST(RegisterValueTest, SetValueFromData_128_le) { - uint8_t src[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; - TestSetValueFromData128(src, lldb::ByteOrder::eByteOrderLittle); + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; + TestSetValueFromData(etalon128, src, 16, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); } -// Test that the "RegisterValue::SetValueFromData" method works correctly -// with 128-bit big-endian data that represents an integer. TEST(RegisterValueTest, SetValueFromData_128_be) { - uint8_t src[] = {0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, - 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00}; - TestSetValueFromData128(src, lldb::ByteOrder::eByteOrderBig); + uint8_t src[] = {0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon128, src, 16, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon256(APInt(256, 0x1f1e1d1c1b1a1918ull) << 3 * 64 | + APInt(256, 0x1716151413121110ull) << 2 * 64 | + APInt(256, 0x0f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(256, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_256_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f}; + TestSetValueFromData(etalon256, src, 32, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_256_be) { + uint8_t src[] = {0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon256, src, 32, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); +} + +static const Scalar etalon257(APInt(512, 0x0000000000000001ull) << 4 * 64 | + APInt(512, 0x1f1e1d1c1b1a1918ull) << 3 * 64 | + APInt(512, 0x1716151413121110ull) << 2 * 64 | + APInt(512, 0x0f0e0d0c0b0a0908ull) << 1 * 64 | + APInt(512, 0x0706050403020100ull) << 0 * 64); + +TEST(RegisterValueTest, SetValueFromData_257_le) { + uint8_t src[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x01}; + TestSetValueFromData(etalon257, src, 33, lldb::ByteOrder::eByteOrderLittle, + RegisterValue::eTypeUIntN); +} + +TEST(RegisterValueTest, SetValueFromData_257_be) { + uint8_t src[] = {0x01, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, + 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, + 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; + TestSetValueFromData(etalon257, src, 33, lldb::ByteOrder::eByteOrderBig, + RegisterValue::eTypeUIntN); } diff --git a/lldb/unittests/Utility/VirtualDataExtractorTest.cpp b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp new file mode 100644 index 0000000..09f3edb --- /dev/null +++ b/lldb/unittests/Utility/VirtualDataExtractorTest.cpp @@ -0,0 +1,583 @@ +//===----------------------------------------------------------------------===// +// +// 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 "lldb/Utility/VirtualDataExtractor.h" +#include "lldb/Utility/DataBufferHeap.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +using Table = VirtualDataExtractor::LookupTable; +using Entry = Table::Entry; + +TEST(VirtualDataExtractorTest, BasicConstruction) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + EXPECT_EQ(extractor->GetByteSize(), 8U); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffset) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + const void *data = extractor->GetData(&virtual_offset, 4); + + ASSERT_NE(data, nullptr); + EXPECT_EQ(virtual_offset, 0x1004U); + EXPECT_EQ(memcmp(data, buffer, 4), 0); +} + +TEST(VirtualDataExtractorTest, GetDataAtVirtualOffsetInvalid) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Try to read from an invalid virtual address. + offset_t virtual_offset = 0x2000; + const void *data = extractor->GetData(&virtual_offset, 4); + + EXPECT_EQ(data, nullptr); +} + +TEST(VirtualDataExtractorTest, GetU8AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x34U); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, GetU16AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x7856U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU32AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64AtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU64(&virtual_offset), 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetAddressAtVirtualOffset) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetAddress(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, BigEndian) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderBig, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x1234U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0x5678U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, MultipleEntries) { + // Create a buffer with distinct patterns for each section. + uint8_t buffer[] = { + 0x01, 0x02, 0x03, 0x04, // Physical offset 0-3. + 0x11, 0x12, 0x13, 0x14, // Physical offset 4-7. + 0x21, 0x22, 0x23, 0x24 // Physical offset 8-11. + }; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 4, 0), // Virt 0x1000-0x1004 + Entry(0x2000, 4, 4), // Virt 0x2000-0x2004 + Entry(0x3000, 4, 8)}); // Virt 0x3000-0x3004 + + // Test reading from first virtual range. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x01U); + + // Test reading from second virtual range. + virtual_offset = 0x2000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x11U); + + // Test reading from third virtual range. + virtual_offset = 0x3000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x21U); +} + +TEST(VirtualDataExtractorTest, NonContiguousVirtualAddresses) { + uint8_t buffer[] = {0xAA, 0xBB, 0xCC, 0xDD}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 2, 0), // Virt 0x1000-0x1002 + Entry(0x5000, 2, 2)}); // Virt 0x5000-0x5002 + + // Test reading from first virtual range. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0xBBAAU); + + // Test reading from second virtual range (non-contiguous). + virtual_offset = 0x5000; + EXPECT_EQ(extractor->GetU16(&virtual_offset), 0xDDCCU); + + // Test that gap between ranges is invalid. + virtual_offset = 0x3000; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); +} + +TEST(VirtualDataExtractorTest, SharedDataBuffer) { + // Test with shared_ptr to DataBuffer. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + auto data_sp = std::make_shared<DataBufferHeap>(buffer, sizeof(buffer)); + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + data_sp, eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); +} + +TEST(VirtualDataExtractorTest, NullPointerHandling) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test that passing nullptr returns default values. + EXPECT_EQ(extractor->GetU8(nullptr), 0U); + EXPECT_EQ(extractor->GetU16(nullptr), 0U); + EXPECT_EQ(extractor->GetU32(nullptr), 0U); + EXPECT_EQ(extractor->GetU64(nullptr), 0U); + EXPECT_EQ(extractor->GetAddress(nullptr), 0U); + EXPECT_EQ(extractor->GetData(nullptr, 4), nullptr); +} + +TEST(VirtualDataExtractorTest, OffsetMapping) { + // Test that virtual to physical offset mapping works correctly. + uint8_t buffer[] = {0x00, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; + + // Map virtual address 0x1000 to physical offset 4 (skipping first 4 bytes). + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 4)}); + + offset_t virtual_offset = 0x1000; + // Should read from physical offset 4, not 0. + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0xDDCCBBAAU); +} + +TEST(VirtualDataExtractorTest, GetU8Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU8_unchecked(&virtual_offset), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + EXPECT_EQ(extractor->GetU8_unchecked(&virtual_offset), 0x34U); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, GetU16Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x7856U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU32Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32_unchecked(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + EXPECT_EQ(extractor->GetU32_unchecked(&virtual_offset), 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU64_unchecked(&virtual_offset), + 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetMaxU64Unchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test various byte sizes. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 1), 0x12U); + EXPECT_EQ(virtual_offset, 0x1001U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 2), 0x3412U); + EXPECT_EQ(virtual_offset, 0x1002U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 4), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); + + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64_unchecked(&virtual_offset, 8), + 0xF0DEBC9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetAddressUnchecked) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetAddress_unchecked(&virtual_offset), 0x78563412U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, UncheckedWithBigEndian) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderBig, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x1234U); + EXPECT_EQ(virtual_offset, 0x1002U); + + EXPECT_EQ(extractor->GetU16_unchecked(&virtual_offset), 0x5678U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetCStr) { + // Create buffer with null-terminated strings. + uint8_t buffer[] = {'H', 'e', 'l', 'l', 'o', '\0', 'W', 'o', + 'r', 'l', 'd', '\0', 'F', 'o', 'o', '\0'}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 6, 0), Entry(0x2000, 12, 6)}); + + // Test reading first string. + offset_t virtual_offset = 0x1000; + const char *str1 = extractor->GetCStr(&virtual_offset); + ASSERT_NE(str1, nullptr); + EXPECT_STREQ(str1, "Hello"); + EXPECT_EQ(virtual_offset, 0x1006U); // After "Hello\0" + + // Test reading second string. + virtual_offset = 0x2000; + const char *str2 = extractor->GetCStr(&virtual_offset); + ASSERT_NE(str2, nullptr); + EXPECT_STREQ(str2, "World"); + EXPECT_EQ(virtual_offset, 0x2006U); // After "World\0" +} + +TEST(VirtualDataExtractorTest, GetFloat) { + // Create buffer with float value (IEEE 754 single precision). + // 3.14159f in little endian: 0xDB 0x0F 0x49 0x40 + uint8_t buffer[] = {0xDB, 0x0F, 0x49, 0x40}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + offset_t virtual_offset = 0x1000; + float value = extractor->GetFloat(&virtual_offset); + EXPECT_NEAR(value, 3.14159f, 0.00001f); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetDouble) { + // Create buffer with double value (IEEE 754 double precision). + // 3.14159265358979 in little endian + uint8_t buffer[] = {0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 8, 0)}); + + offset_t virtual_offset = 0x1000; + double value = extractor->GetDouble(&virtual_offset); + EXPECT_NEAR(value, 3.14159265358979, 0.00000000000001); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetULEB128) { + // ULEB128 encoding: 0x624 (1572 decimal) = 0xA4 0x0C + uint8_t buffer[] = {0xA4, 0x0C, 0xFF, 0x00, 0x7F, 0x80, 0x01}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 7, 0)}); + + // Test reading first ULEB128 value (1572). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 1572U); + EXPECT_EQ(virtual_offset, 0x1002U); + + // Test reading second ULEB128 value (127). + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 127U); + EXPECT_EQ(virtual_offset, 0x1005U); + + // Test reading third ULEB128 value (128). + EXPECT_EQ(extractor->GetULEB128(&virtual_offset), 128U); + EXPECT_EQ(virtual_offset, 0x1007U); +} + +TEST(VirtualDataExtractorTest, GetSLEB128) { + // SLEB128 encoding: -123 = 0x85 0x7F, 123 = 0xFB 0x00 + uint8_t buffer[] = {0x85, 0x7F, 0xFB, 0x00}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test reading negative SLEB128 value (-123). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetSLEB128(&virtual_offset), -123); + EXPECT_EQ(virtual_offset, 0x1002U); + + // Test reading positive SLEB128 value (123). + EXPECT_EQ(extractor->GetSLEB128(&virtual_offset), 123); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU8Array) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 4 bytes. + offset_t virtual_offset = 0x1000; + uint8_t dst[4] = {0}; + void *result = extractor->GetU8(&virtual_offset, dst, 4); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x01U); + EXPECT_EQ(dst[1], 0x02U); + EXPECT_EQ(dst[2], 0x03U); + EXPECT_EQ(dst[3], 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, GetU16Array) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 3 uint16_t values. + offset_t virtual_offset = 0x1000; + uint16_t dst[3] = {0}; + void *result = extractor->GetU16(&virtual_offset, dst, 3); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x3412U); + EXPECT_EQ(dst[1], 0x7856U); + EXPECT_EQ(dst[2], 0xBC9AU); + EXPECT_EQ(virtual_offset, 0x1006U); +} + +TEST(VirtualDataExtractorTest, GetU32Array) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading array of 2 uint32_t values. + offset_t virtual_offset = 0x1000; + uint32_t dst[2] = {0}; + void *result = extractor->GetU32(&virtual_offset, dst, 2); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x78563412U); + EXPECT_EQ(dst[1], 0xF0DEBC9AU); + EXPECT_EQ(virtual_offset, 0x1008U); +} + +TEST(VirtualDataExtractorTest, GetU64Array) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 8, Table{Entry(0x1000, 16, 0)}); + + // Test reading array of 2 uint64_t values. + offset_t virtual_offset = 0x1000; + uint64_t dst[2] = {0}; + void *result = extractor->GetU64(&virtual_offset, dst, 2); + ASSERT_NE(result, nullptr); + EXPECT_EQ(dst[0], 0x0807060504030201ULL); + EXPECT_EQ(dst[1], 0x1817161514131211ULL); + EXPECT_EQ(virtual_offset, 0x1010U); +} + +TEST(VirtualDataExtractorTest, GetMaxU64WithVariableSizes) { + uint8_t buffer[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 8, 0)}); + + // Test reading 3-byte value. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64(&virtual_offset, 3), 0x563412U); + EXPECT_EQ(virtual_offset, 0x1003U); + + // Test reading 5-byte value. + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxU64(&virtual_offset, 5), 0x9A78563412ULL); + EXPECT_EQ(virtual_offset, 0x1005U); +} + +TEST(VirtualDataExtractorTest, GetMaxS64) { + // Test with negative number (sign extension). + uint8_t buffer[] = {0xFF, 0xFF, 0xFF, 0xFF}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Test reading 1-byte signed value (-1). + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxS64(&virtual_offset, 1), -1); + EXPECT_EQ(virtual_offset, 0x1001U); + + // Test reading 2-byte signed value (-1). + virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetMaxS64(&virtual_offset, 2), -1); + EXPECT_EQ(virtual_offset, 0x1002U); +} + +TEST(VirtualDataExtractorTest, CannotReadAcrossEntryBoundaries) { + // Create buffer with two separate regions. + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04, 0x11, 0x12, 0x13, 0x14}; + + // First entry: virtual 0x1000-0x1004 maps to physical 0-4. + // Second entry: virtual 0x2000-0x2004 maps to physical 4-8. + // Note: there's a gap in virtual addresses (0x1004-0x2000). + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, + Table{Entry(0x1000, 4, 0), Entry(0x2000, 4, 4)}); + + // Verify we can read within the first entry. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Verify we can read within the second entry. + virtual_offset = 0x2000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x14131211U); + EXPECT_EQ(virtual_offset, 0x2004U); + + // Verify we CANNOT read in the gap between entries. + // This address is not in any lookup table entry. + virtual_offset = 0x1500; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1500U); + + // Verify we CANNOT read data pointer from the gap. + virtual_offset = 0x1800; + const void *data = extractor->GetData(&virtual_offset, 1); + EXPECT_EQ(data, nullptr); + EXPECT_EQ(virtual_offset, 0x1800U); // Offset unchanged. + + // Verify we can read individual bytes within each entry. + virtual_offset = 0x1003; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Verify we CANNOT read past the end of an entry. + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1004U); +} + +TEST(VirtualDataExtractorTest, ReadExactlyAtEntryEnd) { + uint8_t buffer[] = {0x01, 0x02, 0x03, 0x04}; + + lldb::DataExtractorSP extractor = std::make_shared<VirtualDataExtractor>( + buffer, sizeof(buffer), eByteOrderLittle, 4, Table{Entry(0x1000, 4, 0)}); + + // Reading exactly to the end of an entry should work. + offset_t virtual_offset = 0x1000; + EXPECT_EQ(extractor->GetU32(&virtual_offset), 0x04030201U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // But reading one byte past the end should fail. + virtual_offset = 0x1004; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0U); + EXPECT_EQ(virtual_offset, 0x1004U); + + // Reading from just before the end should work for smaller sizes. + virtual_offset = 0x1003; + EXPECT_EQ(extractor->GetU8(&virtual_offset), 0x04U); + EXPECT_EQ(virtual_offset, 0x1004U); +} |
