diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp --- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp @@ -577,12 +577,19 @@ if (process_sp && lang_opts.ObjC) { if (auto *runtime = ObjCLanguageRuntime::Get(*process_sp)) { - if (runtime->GetRuntimeVersion() == - ObjCLanguageRuntime::ObjCRuntimeVersions::eAppleObjC_V2) + switch (runtime->GetRuntimeVersion()) { + case ObjCLanguageRuntime::ObjCRuntimeVersions::eAppleObjC_V2: lang_opts.ObjCRuntime.set(ObjCRuntime::MacOSX, VersionTuple(10, 7)); - else + break; + case ObjCLanguageRuntime::ObjCRuntimeVersions::eObjC_VersionUnknown: + case ObjCLanguageRuntime::ObjCRuntimeVersions::eAppleObjC_V1: lang_opts.ObjCRuntime.set(ObjCRuntime::FragileMacOSX, VersionTuple(10, 7)); + break; + case ObjCLanguageRuntime::ObjCRuntimeVersions::eGNUstep_libobjc2: + lang_opts.ObjCRuntime.set(ObjCRuntime::GNUstep, VersionTuple(2, 0)); + break; + } if (runtime->HasNewLiteralsAndIndexing()) lang_opts.DebuggerObjCLiteral = true; diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/CMakeLists.txt b/lldb/source/Plugins/LanguageRuntime/ObjC/CMakeLists.txt --- a/lldb/source/Plugins/LanguageRuntime/ObjC/CMakeLists.txt +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/CMakeLists.txt @@ -7,4 +7,6 @@ lldbTarget lldbUtility ) + add_subdirectory(AppleObjCRuntime) +add_subdirectory(GNUstepObjCRuntime) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/CMakeLists.txt b/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/CMakeLists.txt @@ -0,0 +1,19 @@ +add_lldb_library(lldbPluginGNUstepObjCRuntime PLUGIN + GNUstepObjCRuntime.cpp + + LINK_LIBS + lldbBreakpoint + lldbCore + lldbExpression + lldbHost + lldbInterpreter + lldbSymbol + lldbTarget + lldbUtility + lldbPluginExpressionParserClang + lldbPluginTypeSystemClang + CLANG_LIBS + clangAST + LINK_COMPONENTS + Support + ) diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.h b/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.h @@ -0,0 +1,111 @@ +//===-- GNUstepObjCRuntime.h ------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_GNUSTEPOBJCRUNTIME_GNUSTEPOBJCRUNTIME_H +#define LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_GNUSTEPOBJCRUNTIME_GNUSTEPOBJCRUNTIME_H + +#include "lldb/Target/LanguageRuntime.h" +#include "lldb/lldb-private.h" + +#include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#include + +namespace lldb_private { + +class GNUstepObjCRuntime : public lldb_private::ObjCLanguageRuntime { +public: + ~GNUstepObjCRuntime() override; + + // + // PluginManager, PluginInterface and LLVM RTTI implementation + // + + static char ID; + + static void Initialize(); + + static void Terminate(); + + static lldb_private::LanguageRuntime * + CreateInstance(Process *process, lldb::LanguageType language); + + static llvm::StringRef GetPluginNameStatic() { + return "gnustep-objc-libobjc2"; + } + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + void ModulesDidLoad(const ModuleList &module_list) override; + + bool isA(const void *ClassID) const override { + return ClassID == &ID || ObjCLanguageRuntime::isA(ClassID); + } + + static bool classof(const LanguageRuntime *runtime) { + return runtime->isA(&ID); + } + + // + // LanguageRuntime implementation + // + bool GetObjectDescription(Stream &str, Value &value, + ExecutionContextScope *exe_scope) override; + + bool GetObjectDescription(Stream &str, ValueObject &object) override; + + bool CouldHaveDynamicValue(ValueObject &in_value) override; + + bool GetDynamicTypeAndAddress(ValueObject &in_value, + lldb::DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, + Address &address, + Value::ValueType &value_type) override; + + TypeAndOrName FixUpDynamicType(const TypeAndOrName &type_and_or_name, + ValueObject &static_value) override; + + lldb::BreakpointResolverSP + CreateExceptionResolver(const lldb::BreakpointSP &bkpt, bool catch_bp, + bool throw_bp) override; + + lldb::ThreadPlanSP GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) override; + + // + // ObjCLanguageRuntime implementation + // + + bool IsModuleObjCLibrary(const lldb::ModuleSP &module_sp) override; + + bool ReadObjCLibrary(const lldb::ModuleSP &module_sp) override; + + bool HasReadObjCLibrary() override { return m_objc_module_sp != nullptr; } + + llvm::Expected> + CreateObjectChecker(std::string name, ExecutionContext &exe_ctx) override; + + ObjCRuntimeVersions GetRuntimeVersion() const override { + return ObjCRuntimeVersions::eGNUstep_libobjc2; + } + + void UpdateISAToDescriptorMapIfNeeded() override; + +protected: + // Call CreateInstance instead. + GNUstepObjCRuntime(Process *process); + + lldb::ModuleSP m_objc_module_sp; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_LANGUAGERUNTIME_OBJC_GNUSTEPOBJCRUNTIME_GNUSTEPOBJCRUNTIME_H diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/GNUstepObjCRuntime/GNUstepObjCRuntime.cpp @@ -0,0 +1,199 @@ +//===-- GNUstepObjCRuntime.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 "GNUstepObjCRuntime.h" + +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/ValueObject.h" +#include "lldb/Expression/UtilityFunction.h" +#include "lldb/Target/ExecutionContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Utility/ArchSpec.h" +#include "lldb/Utility/ConstString.h" + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(GNUstepObjCRuntime) + +char GNUstepObjCRuntime::ID = 0; + +void GNUstepObjCRuntime::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), "GNUstep Objective-C Language Runtime - libobjc2", + CreateInstance); +} + +void GNUstepObjCRuntime::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +LanguageRuntime *GNUstepObjCRuntime::CreateInstance(Process *process, + LanguageType language) { + if (language != eLanguageTypeObjC) + return nullptr; + if (!process) + return nullptr; + + Target &target = process->GetTarget(); + const llvm::Triple &TT = target.GetArchitecture().GetTriple(); + if (TT.getVendor() == llvm::Triple::VendorType::Apple) + return nullptr; + + const ModuleList &images = target.GetImages(); + if (TT.isOSBinFormatELF()) { + SymbolContextList eh_pers; + RegularExpression regex("__gnustep_objc[x]*_personality_v[0-9]+"); + images.FindSymbolsMatchingRegExAndType(regex, eSymbolTypeCode, eh_pers); + if (eh_pers.GetSize() == 0) + return nullptr; + } else if (TT.isOSWindows()) { + SymbolContextList objc_mandatory; + images.FindSymbolsWithNameAndType(ConstString("__objc_load"), + eSymbolTypeCode, objc_mandatory); + if (objc_mandatory.GetSize() == 0) + return nullptr; + } + + return new GNUstepObjCRuntime(process); +} + +GNUstepObjCRuntime::~GNUstepObjCRuntime() = default; + +GNUstepObjCRuntime::GNUstepObjCRuntime(Process *process) + : ObjCLanguageRuntime(process), m_objc_module_sp(nullptr) { + ReadObjCLibraryIfNeeded(process->GetTarget().GetImages()); +} + +bool GNUstepObjCRuntime::GetObjectDescription(Stream &str, + ValueObject &valobj) { + // TODO: ObjC has a generic way to do this + return false; +} +bool GNUstepObjCRuntime::GetObjectDescription( + Stream &strm, Value &value, ExecutionContextScope *exe_scope) { + // TODO: ObjC has a generic way to do this + return false; +} + +bool GNUstepObjCRuntime::CouldHaveDynamicValue(ValueObject &in_value) { + static constexpr bool check_cxx = false; + static constexpr bool check_objc = true; + return in_value.GetCompilerType().IsPossibleDynamicType(nullptr, check_cxx, + check_objc); +} + +bool GNUstepObjCRuntime::GetDynamicTypeAndAddress( + ValueObject &in_value, DynamicValueType use_dynamic, + TypeAndOrName &class_type_or_name, Address &address, + Value::ValueType &value_type) { + return false; +} + +TypeAndOrName +GNUstepObjCRuntime::FixUpDynamicType(const TypeAndOrName &type_and_or_name, + ValueObject &static_value) { + CompilerType static_type(static_value.GetCompilerType()); + Flags static_type_flags(static_type.GetTypeInfo()); + + TypeAndOrName ret(type_and_or_name); + if (type_and_or_name.HasType()) { + // The type will always be the type of the dynamic object. If our parent's + // type was a pointer, then our type should be a pointer to the type of the + // dynamic object. If a reference, then the original type should be + // okay... + CompilerType orig_type = type_and_or_name.GetCompilerType(); + CompilerType corrected_type = orig_type; + if (static_type_flags.AllSet(eTypeIsPointer)) + corrected_type = orig_type.GetPointerType(); + ret.SetCompilerType(corrected_type); + } else { + // If we are here we need to adjust our dynamic type name to include the + // correct & or * symbol + std::string corrected_name(type_and_or_name.GetName().GetCString()); + if (static_type_flags.AllSet(eTypeIsPointer)) + corrected_name.append(" *"); + // the parent type should be a correctly pointer'ed or referenc'ed type + ret.SetCompilerType(static_type); + ret.SetName(corrected_name.c_str()); + } + return ret; +} + +BreakpointResolverSP +GNUstepObjCRuntime::CreateExceptionResolver(const BreakpointSP &bkpt, + bool catch_bp, bool throw_bp) { + BreakpointResolverSP resolver_sp; + + if (throw_bp) + resolver_sp = std::make_shared( + bkpt, "objc_exception_throw", eFunctionNameTypeBase, + eLanguageTypeUnknown, Breakpoint::Exact, 0, eLazyBoolNo); + + return resolver_sp; +} + +llvm::Expected> +GNUstepObjCRuntime::CreateObjectChecker(std::string name, + ExecutionContext &exe_ctx) { + // TODO: This function is supposed to check whether an ObjC selector is + // present for an object. Might be implemented similar as in the Apple V2 + // runtime. + const char *function_template = R"( + extern "C" void + %s(void *$__lldb_arg_obj, void *$__lldb_arg_selector) {} + )"; + + char empty_function_code[2048]; + int len = ::snprintf(empty_function_code, sizeof(empty_function_code), + function_template, name.c_str()); + + assert(len < (int)sizeof(empty_function_code)); + UNUSED_IF_ASSERT_DISABLED(len); + + return GetTargetRef().CreateUtilityFunction(empty_function_code, name, + eLanguageTypeC, exe_ctx); +} + +ThreadPlanSP +GNUstepObjCRuntime::GetStepThroughTrampolinePlan(Thread &thread, + bool stop_others) { + // TODO: Implement this properly to avoid stepping into things like PLT stubs + return nullptr; +} + +void GNUstepObjCRuntime::UpdateISAToDescriptorMapIfNeeded() { + // TODO: Support lazily named and dynamically loaded Objective-C classes +} + +bool GNUstepObjCRuntime::IsModuleObjCLibrary(const ModuleSP &module_sp) { + if (!module_sp) + return false; + const FileSpec &module_file_spec = module_sp->GetFileSpec(); + if (!module_file_spec) + return false; + llvm::StringRef filename = module_file_spec.GetFilename().GetStringRef(); + return filename.starts_with("libobjc.so") || filename == "objc.dll"; +} + +bool GNUstepObjCRuntime::ReadObjCLibrary(const ModuleSP &module_sp) { + assert(m_objc_module_sp == nullptr && "Check HasReadObjCLibrary() first"); + m_objc_module_sp = module_sp; + + // Right now we don't use this, but we might want to check for debugger + // runtime support symbols like 'gdb_object_getClass' in the future. + return true; +} + +void GNUstepObjCRuntime::ModulesDidLoad(const ModuleList &module_list) { + ReadObjCLibraryIfNeeded(module_list); +} diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h --- a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h @@ -38,7 +38,8 @@ enum class ObjCRuntimeVersions { eObjC_VersionUnknown = 0, eAppleObjC_V1 = 1, - eAppleObjC_V2 = 2 + eAppleObjC_V2 = 2, + eGNUstep_libobjc2 = 3, }; typedef lldb::addr_t ObjCISA; diff --git a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp --- a/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.cpp @@ -235,6 +235,22 @@ m_isa_to_descriptor.begin(), m_isa_to_descriptor.end()); } +void ObjCLanguageRuntime::ReadObjCLibraryIfNeeded( + const ModuleList &module_list) { + if (!HasReadObjCLibrary()) { + std::lock_guard guard(module_list.GetMutex()); + + size_t num_modules = module_list.GetSize(); + for (size_t i = 0; i < num_modules; i++) { + auto mod = module_list.GetModuleAtIndex(i); + if (IsModuleObjCLibrary(mod)) { + ReadObjCLibrary(mod); + break; + } + } + } +} + ObjCLanguageRuntime::ObjCISA ObjCLanguageRuntime::GetParentClass(ObjCLanguageRuntime::ObjCISA isa) { ClassDescriptorSP objc_class_sp(GetClassDescriptorFromISA(isa)); diff --git a/lldb/test/Shell/Expr/objc-gnustep-print.m b/lldb/test/Shell/Expr/objc-gnustep-print.m --- a/lldb/test/Shell/Expr/objc-gnustep-print.m +++ b/lldb/test/Shell/Expr/objc-gnustep-print.m @@ -27,23 +27,39 @@ } @end -@interface TestObj : NSObject {} -- (int)ok; +@interface TestObj : NSObject { + int _int; + float _float; + char _char; + void *_ptr_void; + NSObject *_ptr_nsobject; + id _id_objc; +} +- (void)check_ivars_zeroed; +- (void)set_ivars; @end @implementation TestObj -- (int)ok { - return self ? 0 : 1; +- (void)check_ivars_zeroed { + ; +} +- (void)set_ivars { + _int = 1; + _float = 2.0f; + _char = '\3'; + _ptr_void = (void*)4; + _ptr_nsobject = (NSObject*)5; + _id_objc = (id)6; } @end -// RUN: %lldb -b -o "b objc-gnustep-print.m:35" -o "run" -o "p self" -o "p *self" -- %t | FileCheck %s --check-prefix=SELF +// RUN: %lldb -b -o "b objc-gnustep-print.m:43" -o "run" -o "p self" -o "p *self" -- %t | FileCheck %s --check-prefix=SELF // -// SELF: (lldb) b objc-gnustep-print.m:35 +// SELF: (lldb) b objc-gnustep-print.m:43 // SELF: Breakpoint {{.*}} at objc-gnustep-print.m // // SELF: (lldb) run // SELF: Process {{[0-9]+}} stopped -// SELF: -[TestObj ok](self=[[SELF_PTR:0x[0-9]+]]{{.*}}) at objc-gnustep-print.m:35 +// SELF: -[TestObj check_ivars_zeroed](self=[[SELF_PTR:0x[0-9]+]]{{.*}}) at objc-gnustep-print.m // // SELF: (lldb) p self // SELF: (TestObj *) $0 = [[SELF_PTR]] @@ -54,9 +70,38 @@ // SELF: isa // SELF: refcount // SELF: } +// SELF: _int = 0 +// SELF: _float = 0 +// SELF: _char = '\0' +// SELF: _ptr_void = 0x{{0*}} +// SELF: _ptr_nsobject = nil +// SELF: _id_objc = nil // SELF: } +// RUN: %lldb -b -o "b objc-gnustep-print.m:106" -o "run" -o "p t->_int" -o "p t->_float" -o "p t->_char" \ +// RUN: -o "p t->_ptr_void" -o "p t->_ptr_nsobject" -o "p t->_id_objc" -- %t | FileCheck %s --check-prefix=IVARS_SET +// +// IVARS_SET: (lldb) p t->_int +// IVARS_SET: (int) $0 = 1 +// +// IVARS_SET: (lldb) p t->_float +// IVARS_SET: (float) $1 = 2 +// +// IVARS_SET: (lldb) p t->_char +// IVARS_SET: (char) $2 = '\x03' +// +// IVARS_SET: (lldb) p t->_ptr_void +// IVARS_SET: (void *) $3 = 0x{{0*}}4 +// +// IVARS_SET: (lldb) p t->_ptr_nsobject +// IVARS_SET: (NSObject *) $4 = 0x{{0*}}5 +// +// IVARS_SET: (lldb) p t->_id_objc +// IVARS_SET: (id) $5 = 0x{{0*}}6 + int main() { TestObj *t = [TestObj new]; - return [t ok]; + [t check_ivars_zeroed]; + [t set_ivars]; + return 0; }