diff --git a/lldb/source/Plugins/SymbolFile/CMakeLists.txt b/lldb/source/Plugins/SymbolFile/CMakeLists.txt --- a/lldb/source/Plugins/SymbolFile/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolFile/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(Breakpad) +add_subdirectory(CTF) add_subdirectory(DWARF) add_subdirectory(JSON) add_subdirectory(NativePDB) diff --git a/lldb/source/Plugins/SymbolFile/CTF/CMakeLists.txt b/lldb/source/Plugins/SymbolFile/CTF/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/SymbolFile/CTF/CMakeLists.txt @@ -0,0 +1,7 @@ +add_lldb_library(lldbPluginSymbolFileCTF PLUGIN + SymbolFileCTF.cpp + + LINK_LIBS + lldbCore + lldbSymbol + ) diff --git a/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.h b/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.h @@ -0,0 +1,286 @@ +//===-- SymbolFileCTF.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_SYMBOLFILE_CTF_SYMBOLFILECTF_H +#define LLDB_SOURCE_PLUGINS_SYMBOLFILE_CTF_SYMBOLFILECTF_H + +#include +#include +#include + +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/SymbolFile.h" + +namespace lldb_private { + +class SymbolFileCTF : public lldb_private::SymbolFileCommon { + /// LLVM RTTI support. + static char ID; + +public: + /// LLVM RTTI support. + /// \{ + bool isA(const void *ClassID) const override { + return ClassID == &ID || SymbolFileCommon::isA(ClassID); + } + static bool classof(const SymbolFile *obj) { return obj->isA(&ID); } + /// \} + + SymbolFileCTF(lldb::ObjectFileSP objfile_sp); + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "CTF"; } + + static llvm::StringRef GetPluginDescriptionStatic(); + + static lldb_private::SymbolFile * + CreateInstance(lldb::ObjectFileSP objfile_sp); + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + uint32_t CalculateAbilities() override; + + void InitializeObject() override; + + lldb::LanguageType ParseLanguage(CompileUnit &comp_unit) override { + return lldb::eLanguageTypeUnknown; + } + + bool ParseHeader(); + + size_t ParseFunctions(CompileUnit &comp_unit) override; + + size_t ParseObjects(CompileUnit &comp_unit); + + bool ParseLineTable(CompileUnit &comp_unit) override { return false; } + + bool ParseDebugMacros(CompileUnit &comp_unit) override { return false; } + + bool ParseSupportFiles(CompileUnit &comp_unit, + FileSpecList &support_files) override { + return false; + } + + size_t ParseTypes(CompileUnit &cu) override; + + bool ParseImportedModules( + const SymbolContext &sc, + std::vector &imported_modules) override { + return false; + } + + size_t ParseBlocksRecursive(Function &func) override { return 0; } + + size_t ParseVariablesForContext(const SymbolContext &sc) override; + + uint32_t CalculateNumCompileUnits() override { return 0; } + + lldb::CompUnitSP ParseCompileUnitAtIndex(uint32_t index) override; + + lldb::TypeSP GetTypeForUID(lldb::user_id_t type_uid); + void AddTypeForUID(lldb::user_id_t type_uid, lldb::TypeSP type); + + Type *ResolveTypeUID(lldb::user_id_t type_uid) override; + std::optional GetDynamicArrayInfoForUID( + lldb::user_id_t type_uid, + const lldb_private::ExecutionContext *exe_ctx) override { + return std::nullopt; + } + + bool CompleteType(CompilerType &compiler_type) override { return false; } + + uint32_t ResolveSymbolContext(const lldb_private::Address &so_addr, + lldb::SymbolContextItem resolve_scope, + lldb_private::SymbolContext &sc) override; + + void AddSymbols(Symtab &symtab) override; + + void GetTypes(lldb_private::SymbolContextScope *sc_scope, + lldb::TypeClass type_mask, + lldb_private::TypeList &type_list) override {} + + void + FindTypes(lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, + llvm::DenseSet &searched_symbol_files, + lldb_private::TypeMap &types) override; + + void FindTypesByRegex(const lldb_private::RegularExpression ®ex, + uint32_t max_matches, lldb_private::TypeMap &types); + + void FindFunctions(const lldb_private::Module::LookupInfo &lookup_info, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + bool include_inlines, + lldb_private::SymbolContextList &sc_list) override; + + void FindFunctions(const lldb_private::RegularExpression ®ex, + bool include_inlines, + lldb_private::SymbolContextList &sc_list) override; + + void + FindGlobalVariables(lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, + lldb_private::VariableList &variables) override; + + void FindGlobalVariables(const lldb_private::RegularExpression ®ex, + uint32_t max_matches, + lldb_private::VariableList &variables) override; + + enum TypeKind : uint32_t { + eUnknown = 0, + eInteger = 1, + eFloat = 2, + ePointer = 3, + eArray = 4, + eFunction = 5, + eStruct = 6, + eUnion = 7, + eEnum = 8, + eForward = 9, + eTypedef = 10, + eVolatile = 11, + eConst = 12, + eRestrict = 13, + eSlice = 14, + }; + +private: + enum IntEncoding : uint32_t { + eSigned = 0x1, + eChar = 0x2, + eBool = 0x4, + eVarArgs = 0x8, + }; + + struct ctf_preamble_t { + uint16_t magic; + uint8_t version; + uint8_t flags; + }; + + struct ctf_header_t { + ctf_preamble_t preamble; + uint32_t parlabel; + uint32_t parname; + uint32_t lbloff; + uint32_t objtoff; + uint32_t funcoff; + uint32_t typeoff; + uint32_t stroff; + uint32_t strlen; + }; + + struct ctf_type_t { + uint32_t name; + uint32_t info; + union { + uint32_t size; + uint32_t type; + }; + uint32_t lsizehi; + uint32_t lsizelo; + }; + + struct ctf_stype_t { + uint32_t name; + uint32_t info; + union { + uint32_t size; + uint32_t type; + }; + + bool IsLargeType() const { return size == 0xffff; } + uint32_t GetStructSize() const { + if (IsLargeType()) + return sizeof(ctf_type_t); + return sizeof(ctf_stype_t); + } + uint32_t GetType() const { return type; } + uint32_t GetSize() const { return size; } + }; + + struct ctf_member_t { + uint32_t name; + uint32_t type; + uint16_t offset; + uint16_t padding; + }; + + struct ctf_array_t { + uint32_t contents; + uint32_t index; + uint32_t nelems; + }; + + struct ctf_enum_t { + uint32_t name; + int32_t value; + }; + + llvm::Expected ParseType(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, uint32_t kind, + uint32_t variable_length, + uint32_t type, uint32_t size); + + llvm::Expected ParseInteger(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name); + + llvm::Expected ParseModifierType(lldb::offset_t &offset, + lldb::user_id_t uid, + uint32_t kind, uint32_t type); + + llvm::Expected ParseTypedef(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, + uint32_t type); + + llvm::Expected + ParseArray(lldb::offset_t &offset, lldb::user_id_t uid, llvm::StringRef name); + + llvm::Expected ParseEnum(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, + uint32_t elements, uint32_t size); + + llvm::Expected ParseFunction(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, + uint32_t num_args, uint32_t type); + + llvm::Expected ParseRecord(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, uint32_t kind, + uint32_t fields, uint32_t size); + + llvm::StringRef ReadString(lldb::offset_t offset) const; + + std::vector GetFieldSizes(lldb::offset_t field_offset, + uint32_t fields, uint32_t struct_size); + + DataExtractor m_data; + TypeSystemClang *m_ast; + lldb::CompUnitSP m_comp_unit_sp; + + std::optional m_header; + std::vector m_types; + std::vector m_functions; + std::vector m_variables; + + static constexpr uint16_t g_ctf_magic = 0xcff1; + static constexpr uint8_t g_ctf_version = 4; +}; +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SYMBOLFILE_CTF_SYMBOLFILECTF_H diff --git a/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp b/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/SymbolFile/CTF/SymbolFileCTF.cpp @@ -0,0 +1,936 @@ +//===-- SymbolFileCTF.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 "SymbolFileCTF.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/StreamBuffer.h" +#include "lldb/Symbol/CompileUnit.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/Symtab.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/TypeMap.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Utility/DataExtractor.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/RegularExpression.h" +#include "lldb/Utility/StreamString.h" +#include "lldb/Utility/Timer.h" +#include "llvm/Support/MemoryBuffer.h" + +#include "Plugins/ExpressionParser/Clang/ClangASTMetadata.h" +#include "Plugins/TypeSystem/Clang/TypeSystemClang.h" + +#include +#include + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(SymbolFileCTF) + +char SymbolFileCTF::ID; + +SymbolFileCTF::SymbolFileCTF(lldb::ObjectFileSP objfile_sp) + : SymbolFileCommon(std::move(objfile_sp)) {} + +void SymbolFileCTF::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance); +} + +void SymbolFileCTF::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +llvm::StringRef SymbolFileCTF::GetPluginDescriptionStatic() { + return "Compact C Type Format Symbol Reader"; +} + +SymbolFile *SymbolFileCTF::CreateInstance(ObjectFileSP objfile_sp) { + return new SymbolFileCTF(std::move(objfile_sp)); +} + +bool SymbolFileCTF::ParseHeader() { + if (m_header) + return true; + + Log *log = GetLog(LLDBLog::Symbols); + + ModuleSP module_sp(m_objfile_sp->GetModule()); + const SectionList *section_list = module_sp->GetSectionList(); + if (!section_list) + return false; + + SectionSP section_sp( + section_list->FindSectionByType(lldb::eSectionTypeCTF, true)); + if (!section_sp) + return false; + + m_objfile_sp->ReadSectionData(section_sp.get(), m_data); + + if (m_data.GetByteSize() == 0) + return false; + + StreamString module_desc; + GetObjectFile()->GetModule()->GetDescription(module_desc.AsRawOstream(), + lldb::eDescriptionLevelBrief); + LLDB_LOG(log, "Parsing Compact C Type format for {0}", module_desc.GetData()); + + lldb::offset_t offset = 0; + + // Parse CTF header. + constexpr size_t ctf_header_size = sizeof(ctf_header_t); + if (!m_data.ValidOffsetForDataOfSize(offset, ctf_header_size)) { + LLDB_LOG(log, "CTF parsing failed: insufficient data for CTF header"); + return false; + } + + m_header.emplace(); + + ctf_header_t &ctf_header = *m_header; + ctf_header.preamble.magic = m_data.GetU16(&offset); + ctf_header.preamble.version = m_data.GetU8(&offset); + ctf_header.preamble.flags = m_data.GetU8(&offset); + ctf_header.parlabel = m_data.GetU32(&offset); + ctf_header.parname = m_data.GetU32(&offset); + ctf_header.lbloff = m_data.GetU32(&offset); + ctf_header.objtoff = m_data.GetU32(&offset); + ctf_header.funcoff = m_data.GetU32(&offset); + ctf_header.typeoff = m_data.GetU32(&offset); + ctf_header.stroff = m_data.GetU32(&offset); + ctf_header.strlen = m_data.GetU32(&offset); + + // Validate the preamble. + if (ctf_header.preamble.magic != g_ctf_magic) { + LLDB_LOG(log, "CTF parsing failed: invalid magic: {0:x}", + ctf_header.preamble.magic); + return false; + } + + if (ctf_header.preamble.version != g_ctf_version) { + LLDB_LOG(log, "CTF parsing failed: unsupported version: {0}", + ctf_header.preamble.version); + return false; + } + + LLDB_LOG(log, "Parsed valid CTF preamble: version {0}, flags {1:x}", + ctf_header.preamble.version, ctf_header.preamble.flags); + + const lldb::offset_t header_offset = offset; + + // Validate the header. + if (!m_data.ValidOffset(header_offset + ctf_header.lbloff)) { + LLDB_LOG(log, + "CTF parsing failed: invalid label section offset in header: {0}", + ctf_header.lbloff); + return false; + } + + if (!m_data.ValidOffset(header_offset + ctf_header.objtoff)) { + LLDB_LOG(log, + "CTF parsing failed: invalid object section offset in header: {0}", + ctf_header.objtoff); + return false; + } + + if (!m_data.ValidOffset(header_offset + ctf_header.funcoff)) { + LLDB_LOG( + log, + "CTF parsing failed: invalid function section offset in header: {0}", + ctf_header.funcoff); + return false; + } + + if (!m_data.ValidOffset(header_offset + ctf_header.typeoff)) { + LLDB_LOG(log, + "CTF parsing failed: invalid type section offset in header: {0}", + ctf_header.typeoff); + return false; + } + + if (!m_data.ValidOffset(header_offset + ctf_header.stroff)) { + LLDB_LOG(log, + "CTF parsing failed: invalid string section offset in header: {0}", + ctf_header.stroff); + return false; + } + + const lldb::offset_t str_end_offset = + header_offset + ctf_header.stroff + ctf_header.strlen; + if (!m_data.ValidOffset(str_end_offset - 1)) { + LLDB_LOG(log, + "CTF parsing failed: invalid string section length in header: {0}", + ctf_header.strlen); + return false; + } + + if (header_offset + ctf_header.stroff + ctf_header.parlabel > + str_end_offset) { + LLDB_LOG(log, + "CTF parsing failed: invalid parent label offset: {0} exceeds end " + "of string section ({1})", + ctf_header.parlabel, str_end_offset); + return false; + } + + if (header_offset + ctf_header.stroff + ctf_header.parname > str_end_offset) { + LLDB_LOG(log, + "CTF parsing failed: invalid parent name offset: {0} exceeds end " + "of string section ({1})", + ctf_header.parname, str_end_offset); + return false; + } + + LLDB_LOG(log, + "Parsed valid CTF header: lbloff = {0}, objtoff = {1}, funcoff = " + "{2}, typeoff = {3}, stroff = {4}, strlen = {5}", + ctf_header.lbloff, ctf_header.objtoff, ctf_header.funcoff, + ctf_header.typeoff, ctf_header.stroff, ctf_header.strlen); + + return true; +} + +void SymbolFileCTF::InitializeObject() { + Log *log = GetLog(LLDBLog::Symbols); + + auto type_system_or_err = GetTypeSystemForLanguage(lldb::eLanguageTypeC); + if (auto err = type_system_or_err.takeError()) { + LLDB_LOG_ERROR(log, std::move(err), "Unable to get type system: {0}"); + return; + } + + auto ts = *type_system_or_err; + m_ast = llvm::dyn_cast_or_null(ts.get()); + LazyBool optimized = eLazyBoolNo; + m_comp_unit_sp = std::make_shared( + m_objfile_sp->GetModule(), nullptr, "", 0, eLanguageTypeC, optimized); + + ParseTypes(*m_comp_unit_sp); +} + +llvm::StringRef SymbolFileCTF::ReadString(lldb::offset_t str_offset) const { + lldb::offset_t offset = sizeof(ctf_header_t) + m_header->stroff + str_offset; + if (!m_data.ValidOffset(offset)) + return "(invalid)"; + const char *str = m_data.GetCStr(&offset); + if (str && !*str) + return "(anon)"; + return llvm::StringRef(str); +} + +/// Return the integer display representation encoded in the given data. +static uint32_t GetEncoding(uint32_t data) { + // Mask bits 24–31. + return ((data)&0xff000000) >> 24; +} + +/// Return the integral width in bits encoded in the given data. +static uint32_t GetBits(uint32_t data) { + // Mask bits 0-15. + return (data)&0x0000ffff; +} + +/// Return the type kind encoded in the given data. +uint32_t GetKind(uint32_t data) { + // Mask bits 26–31. + return ((data)&0xf800) >> 11; +} + +/// Return the variable length encoded in the given data. +uint32_t GetVLen(uint32_t data) { + // Mask bits 0–24. + return (data)&0x3ff; +} + +static uint32_t GetBytes(uint32_t bits) { return bits / sizeof(unsigned); } + +static clang::TagTypeKind TranslateRecordKind(SymbolFileCTF::TypeKind type) { + switch (type) { + case SymbolFileCTF::TypeKind::eStruct: + return clang::TTK_Struct; + case SymbolFileCTF::TypeKind::eUnion: + return clang::TTK_Union; + default: + lldbassert(false && "Invalid record kind!"); + return clang::TTK_Struct; + } +} + +llvm::Expected SymbolFileCTF::ParseInteger(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name) { + const uint32_t vdata = m_data.GetU32(&offset); + const uint32_t bits = GetBits(vdata); + const uint32_t encoding = GetEncoding(vdata); + + lldb::BasicType basic_type = TypeSystemClang::GetBasicTypeEnumeration(name); + if (basic_type == eBasicTypeInvalid) + return llvm::make_error( + llvm::formatv("unsupported integer type: no corresponding basic clang " + "type for '{0}'", + name), + llvm::inconvertibleErrorCode()); + + CompilerType compiler_type = m_ast->GetBasicType(basic_type); + + if (basic_type != eBasicTypeVoid) { + // Make sure the type we got is an integer type. + bool compiler_type_is_signed = false; + if (!compiler_type.IsIntegerType(compiler_type_is_signed)) + return llvm::make_error( + llvm::formatv( + "Found compiler type for '{0}' but it's not an integer type: {1}", + name, compiler_type.GetDisplayTypeName().GetStringRef()), + llvm::inconvertibleErrorCode()); + + // Make sure the signing matches between the CTF and the compiler type. + const bool type_is_signed = (encoding & IntEncoding::eSigned); + if (compiler_type_is_signed != type_is_signed) + return llvm::make_error( + llvm::formatv("Found integer compiler type for {0} but compiler type " + "is {1} and {0} is {2}", + name, compiler_type_is_signed ? "signed" : "unsigned", + type_is_signed ? "signed" : "unsigned"), + llvm::inconvertibleErrorCode()); + } + + Declaration decl; + return MakeType(uid, ConstString(name), GetBytes(bits), nullptr, + LLDB_INVALID_UID, lldb_private::Type::eEncodingIsUID, decl, + compiler_type, lldb_private::Type::ResolveState::Full); +} + +llvm::Expected +SymbolFileCTF::ParseModifierType(lldb::offset_t &offset, lldb::user_id_t uid, + uint32_t kind, uint32_t type) { + TypeSP ref_type = GetTypeForUID(type); + CompilerType compiler_type; + + switch (kind) { + case TypeKind::ePointer: + compiler_type = ref_type->GetFullCompilerType().GetPointerType(); + break; + case TypeKind::eConst: + compiler_type = ref_type->GetFullCompilerType().AddConstModifier(); + break; + case TypeKind::eVolatile: + compiler_type = ref_type->GetFullCompilerType().AddVolatileModifier(); + break; + case TypeKind::eRestrict: + compiler_type = ref_type->GetFullCompilerType().AddRestrictModifier(); + break; + default: + return llvm::make_error( + llvm::formatv("ParseModifierType called with unsupported kind: {0}", + kind), + llvm::inconvertibleErrorCode()); + } + + Declaration decl; + return MakeType(uid, ConstString(), 0, nullptr, LLDB_INVALID_UID, + Type::eEncodingIsUID, decl, compiler_type, + lldb_private::Type::ResolveState::Full); +} + +llvm::Expected SymbolFileCTF::ParseTypedef(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, + uint32_t type) { + TypeSP underlying_type = GetTypeForUID(type); + CompilerType target_ast_type = underlying_type->GetFullCompilerType(); + clang::DeclContext *decl_ctx = m_ast->GetTranslationUnitDecl(); + CompilerType ast_typedef = target_ast_type.CreateTypedef( + name.data(), m_ast->CreateDeclContext(decl_ctx), 0); + + Declaration decl; + return MakeType(uid, ConstString(name), 0, nullptr, LLDB_INVALID_UID, + lldb_private::Type::eEncodingIsUID, decl, ast_typedef, + lldb_private::Type::ResolveState::Full); +} + +llvm::Expected SymbolFileCTF::ParseArray(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name) { + ctf_array_t ctf_array; + ctf_array.contents = m_data.GetU32(&offset); + ctf_array.index = m_data.GetU32(&offset); + ctf_array.nelems = m_data.GetU32(&offset); + + TypeSP element_type = GetTypeForUID(ctf_array.contents); + std::optional element_size = element_type->GetByteSize(nullptr); + if (!element_size) + return llvm::make_error( + llvm::formatv("could not get element size of type: {0}", + ctf_array.contents), + llvm::inconvertibleErrorCode()); + + uint64_t size = ctf_array.nelems * *element_size; + + CompilerType compiler_type = m_ast->CreateArrayType( + element_type->GetFullCompilerType(), ctf_array.nelems, + /*is_gnu_vector*/ false); + + Declaration decl; + return MakeType(uid, ConstString(), size, nullptr, LLDB_INVALID_UID, + Type::eEncodingIsUID, decl, compiler_type, + lldb_private::Type::ResolveState::Full); +} + +llvm::Expected SymbolFileCTF::ParseEnum(lldb::offset_t &offset, + lldb::user_id_t uid, + llvm::StringRef name, + uint32_t elements, + uint32_t size) { + Declaration decl; + CompilerType enum_type = m_ast->CreateEnumerationType( + name, m_ast->GetTranslationUnitDecl(), OptionalClangModuleID(), decl, + m_ast->GetBasicType(eBasicTypeInt), + /*is_scoped=*/false); + + for (uint32_t i = 0; i < elements; ++i) { + ctf_enum_t ctf_enum; + ctf_enum.name = m_data.GetU32(&offset); + ctf_enum.value = m_data.GetU32(&offset); + + llvm::StringRef value_name = ReadString(ctf_enum.name); + const uint32_t value = ctf_enum.value; + + Declaration value_decl; + m_ast->AddEnumerationValueToEnumerationType(enum_type, value_decl, + value_name.data(), value, size); + } + + return MakeType(uid, ConstString(), 0, nullptr, LLDB_INVALID_UID, + Type::eEncodingIsUID, decl, enum_type, + lldb_private::Type::ResolveState::Full); +} + +llvm::Expected +SymbolFileCTF::ParseFunction(lldb::offset_t &offset, lldb::user_id_t uid, + llvm::StringRef name, uint32_t num_args, + uint32_t type) { + std::vector arg_types; + arg_types.reserve(num_args); + + bool is_variadic = false; + for (uint32_t i = 0; i < num_args; ++i) { + const uint32_t arg_uid = m_data.GetU32(&offset); + + // If the last argument is 0, this is a variadic function. + if (arg_uid == 0) { + is_variadic = true; + break; + } + + TypeSP arg_type = GetTypeForUID(arg_uid); + arg_types.push_back(arg_type->GetFullCompilerType()); + } + + // If the number of arguments is odd, a single uint32_t of padding is inserted + // to maintain alignment. + if (num_args % 2 == 1) + m_data.GetU32(&offset); + + TypeSP ret_type = GetTypeForUID(type); + CompilerType func_type = m_ast->CreateFunctionType( + ret_type->GetFullCompilerType(), arg_types.data(), arg_types.size(), + is_variadic, 0, clang::CallingConv::CC_C); + + Declaration decl; + return MakeType(uid, ConstString(name), 0, nullptr, LLDB_INVALID_UID, + Type::eEncodingIsUID, decl, func_type, + lldb_private::Type::ResolveState::Full); +} + +llvm::Expected +SymbolFileCTF::ParseRecord(lldb::offset_t &offset, lldb::user_id_t uid, + llvm::StringRef name, uint32_t kind, uint32_t fields, + uint32_t size) { + const clang::TagTypeKind tag_kind = + TranslateRecordKind(static_cast(kind)); + + CompilerType union_type = + m_ast->CreateRecordType(nullptr, OptionalClangModuleID(), eAccessPublic, + name.data(), tag_kind, eLanguageTypeC); + + m_ast->StartTagDeclarationDefinition(union_type); + for (uint32_t i = 0; i < fields; ++i) { + ctf_member_t ctf_member; + ctf_member.name = m_data.GetU32(&offset); + ctf_member.type = m_data.GetU32(&offset); + ctf_member.offset = m_data.GetU16(&offset); + ctf_member.padding = m_data.GetU16(&offset); + + llvm::StringRef member_name = ReadString(ctf_member.name); + const uint32_t member_type_uid = ctf_member.type; + + TypeSP member_type = GetTypeForUID(member_type_uid); + const uint32_t member_size = member_type->GetByteSize(nullptr).value_or(0); + TypeSystemClang::AddFieldToRecordType(union_type, member_name, + member_type->GetFullCompilerType(), + eAccessPublic, member_size); + } + m_ast->CompleteTagDeclarationDefinition(union_type); + + Declaration decl; + return MakeType(uid, ConstString(name), size, nullptr, LLDB_INVALID_UID, + lldb_private::Type::eEncodingIsUID, decl, union_type, + lldb_private::Type::ResolveState::Full); +} + +llvm::Expected SymbolFileCTF::ParseType( + lldb::offset_t &offset, lldb::user_id_t uid, llvm::StringRef name, + uint32_t kind, uint32_t variable_length, uint32_t type, uint32_t size) { + switch (kind) { + case TypeKind::eInteger: + return ParseInteger(offset, uid, name); + case TypeKind::eConst: + case TypeKind::ePointer: + case TypeKind::eRestrict: + case TypeKind::eVolatile: + return ParseModifierType(offset, uid, kind, type); + case TypeKind::eTypedef: + return ParseTypedef(offset, uid, name, type); + case TypeKind::eArray: + return ParseArray(offset, uid, name); + case TypeKind::eEnum: + return ParseEnum(offset, uid, name, variable_length, size); + case TypeKind::eFunction: + return ParseFunction(offset, uid, name, variable_length, size); + case TypeKind::eStruct: + case TypeKind::eUnion: + return ParseRecord(offset, uid, name, kind, variable_length, size); + case TypeKind::eFloat: + case TypeKind::eForward: + case TypeKind::eSlice: + case TypeKind::eUnknown: + offset += (variable_length * sizeof(uint32_t)); + break; + } + return llvm::make_error( + llvm::formatv("unsupported type (name = {0}, kind = {1}, vlength = {2})", + name, kind, variable_length), + llvm::inconvertibleErrorCode()); +} + +size_t SymbolFileCTF::ParseTypes(CompileUnit &cu) { + if (!ParseHeader()) + return 0; + + if (!m_types.empty()) + return 0; + + if (!m_ast) + return 0; + + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOG(log, "Parsing CTF types"); + + lldb::offset_t type_offset = sizeof(ctf_header_t) + m_header->typeoff; + const lldb::offset_t type_offset_end = + sizeof(ctf_header_t) + m_header->stroff; + + lldb::user_id_t type_uid = 1; + while (type_offset < type_offset_end) { + ctf_stype_t ctf_stype; + ctf_stype.name = m_data.GetU32(&type_offset); + ctf_stype.info = m_data.GetU32(&type_offset); + ctf_stype.size = m_data.GetU32(&type_offset); + + llvm::StringRef name = ReadString(ctf_stype.name); + const uint32_t kind = GetKind(ctf_stype.info); + const uint32_t variable_length = GetVLen(ctf_stype.info); + const uint32_t type = ctf_stype.GetType(); + const uint32_t size = ctf_stype.GetSize(); + + TypeSP type_sp; + llvm::Expected type_or_error = ParseType( + type_offset, type_uid, name, kind, variable_length, type, size); + if (!type_or_error) { + LLDB_LOG_ERROR(log, type_or_error.takeError(), + "Failed to parse type at offset {1}: {0}", type_offset); + } else { + type_sp = *type_or_error; + if (log) { + StreamString ss; + type_sp->Dump(&ss, true); + LLDB_LOGV(log, "Adding type {0}: {1}", type_uid, + llvm::StringRef(ss.GetString()).rtrim()); + } + } + + AddTypeForUID(type_uid++, type_sp); + } + + LLDB_LOG(log, "Parsed {0} CTF types", m_types.size()); + + return m_types.size(); +} + +size_t SymbolFileCTF::ParseFunctions(CompileUnit &cu) { + if (!ParseHeader()) + return 0; + + if (!m_functions.empty()) + return 0; + + if (!m_ast) + return 0; + + Symtab *symtab = GetObjectFile()->GetModule()->GetSymtab(); + if (!symtab) + return 0; + + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOG(log, "Parsing CTF functions"); + + lldb::offset_t function_offset = sizeof(ctf_header_t) + m_header->funcoff; + const lldb::offset_t function_offset_end = + sizeof(ctf_header_t) + m_header->typeoff; + + uint32_t symbol_idx = 0; + Declaration decl; + while (function_offset < function_offset_end) { + const uint32_t info = m_data.GetU32(&function_offset); + const uint16_t kind = GetKind(info); + const uint16_t variable_length = GetVLen(info); + + Symbol *symbol = symtab->FindSymbolWithType( + eSymbolTypeCode, Symtab::eDebugYes, Symtab::eVisibilityAny, symbol_idx); + + // Skip padding. + if (kind == TypeKind::eUnknown && variable_length == 0) + continue; + + // Skip unexpected kinds. + if (kind != TypeKind::eFunction) + continue; + + const uint32_t ret_uid = m_data.GetU32(&function_offset); + const uint32_t num_args = variable_length; + + std::vector arg_types; + arg_types.reserve(num_args); + + bool is_variadic = false; + for (uint32_t i = 0; i < variable_length; i++) { + const uint32_t arg_uid = m_data.GetU32(&function_offset); + + // If the last argument is 0, this is a variadic function. + if (arg_uid == 0) { + is_variadic = true; + break; + } + + TypeSP arg_type = GetTypeForUID(arg_uid); + arg_types.push_back(arg_type->GetFullCompilerType()); + } + + if (symbol) { + TypeSP ret_type = GetTypeForUID(ret_uid); + AddressRange func_range = + AddressRange(symbol->GetFileAddress(), symbol->GetByteSize(), + GetObjectFile()->GetModule()->GetSectionList()); + + // Create function type. + CompilerType func_type = m_ast->CreateFunctionType( + ret_type->GetFullCompilerType(), arg_types.data(), arg_types.size(), + is_variadic, 0, clang::CallingConv::CC_C); + lldb::user_id_t function_type_uid = m_types.size() + 1; + TypeSP type_sp = + MakeType(function_type_uid, symbol->GetName(), 0, nullptr, + LLDB_INVALID_UID, Type::eEncodingIsUID, decl, func_type, + lldb_private::Type::ResolveState::Full); + AddTypeForUID(function_type_uid, type_sp); + + // Create function. + lldb::user_id_t func_uid = m_functions.size(); + FunctionSP function_sp = std::make_shared( + &cu, func_uid, function_type_uid, symbol->GetMangled(), type_sp.get(), + func_range); + m_functions.emplace_back(function_sp); + cu.AddFunction(function_sp); + } + } + + LLDB_LOG(log, "CTF parsed {0} functions", m_functions.size()); + + return m_functions.size(); +} + +static DWARFExpression CreateDWARFExpression(ModuleSP module_sp, + const Symbol &symbol) { + if (!module_sp) + return DWARFExpression(); + + const ArchSpec &architecture = module_sp->GetArchitecture(); + ByteOrder byte_order = architecture.GetByteOrder(); + uint32_t address_size = architecture.GetAddressByteSize(); + uint32_t byte_size = architecture.GetDataByteSize(); + + StreamBuffer<32> stream(Stream::eBinary, address_size, byte_order); + stream.PutHex8(lldb_private::dwarf::DW_OP_addr); + stream.PutMaxHex64(symbol.GetFileAddress(), address_size, byte_order); + + DataBufferSP buffer = + std::make_shared(stream.GetData(), stream.GetSize()); + lldb_private::DataExtractor extractor(buffer, byte_order, address_size, + byte_size); + DWARFExpression result(extractor); + result.SetRegisterKind(eRegisterKindDWARF); + + return result; +} + +size_t SymbolFileCTF::ParseObjects(CompileUnit &comp_unit) { + if (!ParseHeader()) + return 0; + + if (!m_variables.empty()) + return 0; + + if (!m_ast) + return 0; + + ModuleSP module_sp = GetObjectFile()->GetModule(); + Symtab *symtab = module_sp->GetSymtab(); + if (!symtab) + return 0; + + Log *log = GetLog(LLDBLog::Symbols); + LLDB_LOG(log, "Parsing CTF objects"); + + lldb::offset_t object_offset = sizeof(ctf_header_t) + m_header->objtoff; + const lldb::offset_t object_offset_end = + sizeof(ctf_header_t) + m_header->funcoff; + + uint32_t symbol_idx = 0; + Declaration decl; + while (object_offset < object_offset_end) { + const uint32_t type_uid = m_data.GetU32(&object_offset); + + if (Symbol *symbol = + symtab->FindSymbolWithType(eSymbolTypeData, Symtab::eDebugYes, + Symtab::eVisibilityAny, symbol_idx)) { + + Variable::RangeList ranges; + ranges.Append(symbol->GetFileAddress(), symbol->GetByteSize()); + + auto type_sp = std::make_shared(*this, type_uid); + + DWARFExpressionList location( + module_sp, CreateDWARFExpression(module_sp, *symbol), nullptr); + + lldb::user_id_t variable_type_uid = m_variables.size(); + m_variables.emplace_back(std::make_shared( + variable_type_uid, symbol->GetName().AsCString(), + symbol->GetName().AsCString(), type_sp, eValueTypeVariableGlobal, + m_comp_unit_sp.get(), ranges, &decl, location, symbol->IsExternal(), + /*artificial=*/false, + /*location_is_constant_data*/ false)); + } + } + + LLDB_LOG(log, "Parsed {0} CTF objects", m_variables.size()); + + return m_variables.size(); +} + +uint32_t SymbolFileCTF::CalculateAbilities() { + if (!m_objfile_sp) + return 0; + + if (!ParseHeader()) + return 0; + + return VariableTypes | Functions | GlobalVariables; +} + +uint32_t SymbolFileCTF::ResolveSymbolContext(const Address &so_addr, + SymbolContextItem resolve_scope, + SymbolContext &sc) { + std::lock_guard guard(GetModuleMutex()); + if (m_objfile_sp->GetSymtab() == nullptr) + return 0; + + uint32_t resolved_flags = 0; + + // Resolve symbols. + if (resolve_scope & eSymbolContextSymbol) { + sc.symbol = m_objfile_sp->GetSymtab()->FindSymbolContainingFileAddress( + so_addr.GetFileAddress()); + if (sc.symbol) + resolved_flags |= eSymbolContextSymbol; + } + + // Resolve functions. + if (resolve_scope & eSymbolContextFunction) { + for (FunctionSP function_sp : m_functions) { + if (function_sp->GetAddressRange().ContainsFileAddress( + so_addr.GetFileAddress())) { + sc.function = function_sp.get(); + resolved_flags |= eSymbolContextFunction; + break; + } + } + } + + // Resolve variables. + if (resolve_scope & eSymbolContextVariable) { + for (VariableSP variable_sp : m_variables) { + if (variable_sp->LocationIsValidForAddress(so_addr.GetFileAddress())) { + sc.variable = variable_sp.get(); + break; + } + } + } + + return resolved_flags; +} + +CompUnitSP SymbolFileCTF::ParseCompileUnitAtIndex(uint32_t idx) { + if (idx == 0) + return m_comp_unit_sp; + return {}; +} + +size_t +SymbolFileCTF::ParseVariablesForContext(const lldb_private::SymbolContext &sc) { + return ParseObjects(*m_comp_unit_sp); +} + +void SymbolFileCTF::AddSymbols(Symtab &symtab) { + // CTF does not encode symbols. + // We rely on the existing symbol table to map symbols to type. +} + +void SymbolFileCTF::AddTypeForUID(lldb::user_id_t type_uid, lldb::TypeSP type) { + assert(type_uid == m_types.size() + 1); + m_types.emplace_back(type); +} + +TypeSP SymbolFileCTF::GetTypeForUID(lldb::user_id_t type_uid) { + if (type_uid > m_types.size()) + return {}; + + if (type_uid < 1) + return {}; + + return m_types[type_uid - 1]; +} + +lldb_private::Type *SymbolFileCTF::ResolveTypeUID(lldb::user_id_t type_uid) { + return GetTypeForUID(type_uid).get(); +} + +void SymbolFileCTF::FindTypes( + lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, + llvm::DenseSet &searched_symbol_files, + lldb_private::TypeMap &types) { + + searched_symbol_files.clear(); + searched_symbol_files.insert(this); + + size_t matches = 0; + for (TypeSP type_sp : m_types) { + if (matches == max_matches) + break; + if (type_sp && type_sp->GetName() == name) { + types.Insert(type_sp); + matches++; + } + } +} + +void SymbolFileCTF::FindTypesByRegex( + const lldb_private::RegularExpression ®ex, uint32_t max_matches, + lldb_private::TypeMap &types) { + ParseTypes(*m_comp_unit_sp); + + size_t matches = 0; + for (TypeSP type_sp : m_types) { + if (matches == max_matches) + break; + if (type_sp && regex.Execute(type_sp->GetName())) + types.Insert(type_sp); + matches++; + } +} + +void SymbolFileCTF::FindFunctions( + const lldb_private::Module::LookupInfo &lookup_info, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + bool include_inlines, lldb_private::SymbolContextList &sc_list) { + ParseFunctions(*m_comp_unit_sp); + + ConstString name = lookup_info.GetLookupName(); + for (FunctionSP function_sp : m_functions) { + if (function_sp && function_sp->GetName() == name) { + lldb_private::SymbolContext sc; + sc.comp_unit = m_comp_unit_sp.get(); + sc.function = function_sp.get(); + sc_list.Append(sc); + } + } +} + +void SymbolFileCTF::FindFunctions(const lldb_private::RegularExpression ®ex, + bool include_inlines, + lldb_private::SymbolContextList &sc_list) { + for (FunctionSP function_sp : m_functions) { + if (function_sp && regex.Execute(function_sp->GetName())) { + lldb_private::SymbolContext sc; + sc.comp_unit = m_comp_unit_sp.get(); + sc.function = function_sp.get(); + sc_list.Append(sc); + } + } +} + +void SymbolFileCTF::FindGlobalVariables( + lldb_private::ConstString name, + const lldb_private::CompilerDeclContext &parent_decl_ctx, + uint32_t max_matches, lldb_private::VariableList &variables) { + ParseObjects(*m_comp_unit_sp); + + size_t matches = 0; + for (VariableSP variable_sp : m_variables) { + if (matches == max_matches) + break; + if (variable_sp && variable_sp->GetName() == name) { + variables.AddVariable(variable_sp); + matches++; + } + } +} + +void SymbolFileCTF::FindGlobalVariables( + const lldb_private::RegularExpression ®ex, uint32_t max_matches, + lldb_private::VariableList &variables) { + ParseObjects(*m_comp_unit_sp); + + size_t matches = 0; + for (VariableSP variable_sp : m_variables) { + if (matches == max_matches) + break; + if (variable_sp && regex.Execute(variable_sp->GetName())) { + variables.AddVariable(variable_sp); + matches++; + } + } +} diff --git a/lldb/test/API/CMakeLists.txt b/lldb/test/API/CMakeLists.txt --- a/lldb/test/API/CMakeLists.txt +++ b/lldb/test/API/CMakeLists.txt @@ -82,6 +82,9 @@ if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Windows|Darwin") list(APPEND LLDB_TEST_COMMON_ARGS_VAR --env ARCHIVER=${CMAKE_AR} --env OBJCOPY=${CMAKE_OBJCOPY}) +else() + list(APPEND LLDB_TEST_COMMON_ARGS_VAR + --env OBJCOPY=${LLVM_TOOLS_BINARY_DIR}/llvm-objcopy${CMAKE_EXECUTABLE_SUFFIX}) endif() if (NOT "${LLDB_LIT_TOOLS_DIR}" STREQUAL "") diff --git a/lldb/test/API/macosx/ctf/Makefile b/lldb/test/API/macosx/ctf/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/ctf/Makefile @@ -0,0 +1,21 @@ +C_SOURCES := test.c +MAKE_DSYM := YES + +all: a.out a.ctf + +include Makefile.rules + +a.ctf: a.out.dSYM + ctfconvert -l a -o a.ctf a.out.dSYM/Contents/Resources/DWARF/a.out + $(OBJCOPY) \ + -R __DWARF,__debug_line \ + -R __DWARF,__debug_aranges \ + -R __DWARF,__debug_info \ + -R __DWARF,__debug_abbrev \ + -R __DWARF,__debug_str \ + -R __DWARF,__apple_names \ + -R __DWARF,__apple_namespac \ + -R __DWARF,__apple_types \ + -R __DWARF,__apple_objc \ + a.ctf a.ctf + rm -rf a.out.dSYM diff --git a/lldb/test/API/macosx/ctf/TestCTF.py b/lldb/test/API/macosx/ctf/TestCTF.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/ctf/TestCTF.py @@ -0,0 +1,71 @@ +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.lldbtest import * +from lldbsuite.test.decorators import * +import shutil +import os + + +class TestCTF(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def no_ctf_convert(self): + if not shutil.which("ctfconvert"): + return "ctfconvert not found in path" + return None + + def no_objcopy(self): + if not "OBJCOPY" in os.environ: + return "llvm-objcopy not found in environment" + return None + + @skipTestIfFn(no_ctf_convert) + @skipTestIfFn(no_objcopy) + @skipUnlessDarwin + def test(self): + self.build() + + lldbutil.run_to_name_breakpoint(self, "printf") + + symbol_file = self.getBuildArtifact("a.ctf") + + self.runCmd("target symbols add {}".format(symbol_file)) + self.expect( + "target variable foo", + substrs=[ + "(MyStructT) foo", + "i = 1", + "foo", + "'c'", + "[0] = 'c'", + "[1] = 'a'", + "[2] = 'b'", + "[3] = 'c'", + 'u = (i = 1, s = "")', + "f = 0x0000000000000000", + ], + ) + + self.expect("target variable foo.n.i", substrs=["(MyInt) foo.n.i = 1"]) + self.expect( + "target variable foo.n.s", substrs=["(const char *) foo.n.s", '"foo"'] + ) + self.expect( + "target variable foo.n.c", substrs=["(volatile char) foo.n.c = 'c'"] + ) + self.expect( + "target variable foo.n.a", + substrs=[ + "(char[4]:8) foo.n.a", + "[0] = 'c'", + "[1] = 'a'", + "[2] = 'b'", + "[3] = 'c'", + ], + ) + self.expect( + "target variable foo.n.u", substrs=['(MyUnionT) foo.n.u = (i = 1, s = "")'] + ) + self.expect( + "target variable foo.f", + substrs=["(void (*)(int)) foo.f = 0x0000000000000000"], + ) diff --git a/lldb/test/API/macosx/ctf/test.c b/lldb/test/API/macosx/ctf/test.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/macosx/ctf/test.c @@ -0,0 +1,50 @@ +#include + +typedef int MyInt; + +void populate(MyInt i); + +typedef enum MyEnum { + eOne = 1, + eTwo =2, + eThree = 3, +} MyEnumT; + +typedef union MyUnion { + MyInt i; + const char* s; +} MyUnionT; + +typedef struct MyNestedStruct { + MyInt i; + const char* s; + volatile char c; + char a[4]; + MyEnumT e; + MyUnionT u; +} MyNestedStructT; + +typedef struct MyStruct { + MyNestedStructT n; + void (*f)(int); +} MyStructT; + +MyStructT foo; + +void populate(MyInt i) { + foo.n.i = i; + foo.n.s = "foo"; + foo.n.c = 'c'; + foo.n.a[0] = 'a'; + foo.n.a[1] = 'b'; + foo.n.a[2] = 'c'; + foo.n.a[3] = 'd'; + foo.n.e = eOne; + foo.f = NULL; +} + +int main(int argc, char** argv) { + populate(argc); + printf("foo is at address: %p\n", (void*)&foo); // Break here + return 0; +}