diff --git a/lldb/source/Plugins/ObjectFile/CMakeLists.txt b/lldb/source/Plugins/ObjectFile/CMakeLists.txt --- a/lldb/source/Plugins/ObjectFile/CMakeLists.txt +++ b/lldb/source/Plugins/ObjectFile/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(Breakpad) +add_subdirectory(COFF) add_subdirectory(ELF) add_subdirectory(JSON) add_subdirectory(Mach-O) diff --git a/lldb/source/Plugins/ObjectFile/COFF/CMakeLists.txt b/lldb/source/Plugins/ObjectFile/COFF/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/COFF/CMakeLists.txt @@ -0,0 +1,13 @@ +add_lldb_library(lldbPluginObjectFileCOFF PLUGIN + ObjectFileCOFF.cpp + + LINK_LIBS + lldbCore + lldbHost + lldbSymbol + lldbTarget + + LINK_COMPONENTS + BinaryFormat + Object + Support) diff --git a/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.h b/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.h @@ -0,0 +1,108 @@ +//===-- ObjectFileCOFF.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_OBJECTFILE_COFF_OBJECTFILECOFF_H +#define LLDB_SOURCE_PLUGINS_OBJECTFILE_COFF_OBJECTFILECOFF_H + +#include "lldb/Symbol/ObjectFile.h" + +#include "llvm/Object/COFF.h" + +class ObjectFileCOFF : public lldb_private::ObjectFile { + std::unique_ptr m_object; + lldb_private::UUID m_uuid; + + ObjectFileCOFF(const lldb::ModuleSP &module_sp, lldb::DataBufferSP data_sp, + lldb::offset_t data_offset, const lldb_private::FileSpec *file, + lldb::offset_t file_offset, lldb::offset_t length); + +public: + ~ObjectFileCOFF() override; + + static void Initialize(); + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "COFF"; } + static llvm::StringRef GetPluginDescriptionStatic() { + return "COFF Object File Reader"; + } + + static lldb_private::ObjectFile * + CreateInstance(const lldb::ModuleSP &module_sp, lldb::DataBufferSP data_sp, + lldb::offset_t data_offset, const lldb_private::FileSpec *file, + lldb::offset_t file_offset, lldb::offset_t length); + + static lldb_private::ObjectFile * + CreateMemoryInstance(const lldb::ModuleSP &module_sp, + lldb::WritableDataBufferSP data_sp, + const lldb::ProcessSP &process_sp, lldb::addr_t header); + + static size_t GetModuleSpecifications(const lldb_private::FileSpec &file, + lldb::DataBufferSP &data_sp, + lldb::offset_t data_offset, + lldb::offset_t file_offset, + lldb::offset_t length, + lldb_private::ModuleSpecList &specs); + + // LLVM RTTI support + static char ID; + bool isA(const void *ClassID) const override { + return ClassID == &ID || ObjectFile::isA(ClassID); + } + static bool classof(const ObjectFile *obj) { return obj->isA(&ID); } + + // PluginInterface protocol + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + // ObjectFile protocol + void Dump(lldb_private::Stream *stream) override; + + uint32_t GetAddressByteSize() const override; + + uint32_t GetDependentModules(lldb_private::FileSpecList &specs) override { + return 0; + } + + bool IsExecutable() const override { + // COFF is an object file format only, it cannot host an executable. + return false; + } + + lldb_private::ArchSpec GetArchitecture() override; + + void CreateSections(lldb_private::SectionList &) override; + + void ParseSymtab(lldb_private::Symtab &) override; + + bool IsStripped() override { + // FIXME(compnerd) see if there is a good way to identify a /Z7 v /Zi or /ZI + // build. + return false; + } + + lldb_private::UUID GetUUID() override { return m_uuid; } + + lldb::ByteOrder GetByteOrder() const override { + // Microsoft always uses little endian. + return lldb::ByteOrder::eByteOrderLittle; + } + + bool ParseHeader() override; + + lldb_private::ObjectFile::Type CalculateType() override { + // COFF is an object file format only, it cannot host an executable. + return lldb_private::ObjectFile::eTypeObjectFile; + } + + lldb_private::ObjectFile::Strata CalculateStrata() override { + // FIXME(compnerd) the object file may correspond to a kernel image. + return lldb_private::ObjectFile::eStrataUser; + } +}; + +#endif diff --git a/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp b/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/ObjectFile/COFF/ObjectFileCOFF.cpp @@ -0,0 +1,310 @@ +//===-- ObjectFileCOFF.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 "ObjectFileCOFF.h" + +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Utility/LLDBLog.h" + +#include "llvm/Support/FormatAdapters.h" + +using namespace lldb; +using namespace lldb_private; + +using namespace llvm; +using namespace llvm::object; + +namespace { +bool IsCOFFObjectFile(const DataBufferSP &data) { + return identify_magic(toStringRef(data->GetData())) == + file_magic::coff_object; +} +} // namespace + +LLDB_PLUGIN_DEFINE(ObjectFileCOFF) + +char ObjectFileCOFF::ID; + +ObjectFileCOFF::ObjectFileCOFF(const ModuleSP &module_sp, DataBufferSP data_sp, + offset_t data_offset, const FileSpec *file, + offset_t file_offset, offset_t length) + : ObjectFile(module_sp, file, file_offset, length, data_sp, data_offset) { + MemoryBufferRef buffer{toStringRef(data_sp->GetData()), + m_file.GetFilename().GetStringRef()}; + + Log *log = GetLog(LLDBLog::Object); + + Expected> binary = createBinary(buffer); + if (binary) + m_object = unique_dyn_cast(std::move(*binary)); + else + LLDB_LOG_ERROR(log, binary.takeError(), + "Failed to create binary for file ({1}): {0}", m_file); + + LLDB_LOG(log, + "{0} ObjectFileCOFF::ObjectFileCOFF module = {1} ({2}), file = {3}, " + "object = {4}", + this, GetModule().get(), GetModule()->GetSpecificationDescription(), + m_file.GetPath(), m_object.get()); +} + +ObjectFileCOFF::~ObjectFileCOFF() = default; + +void ObjectFileCOFF::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + GetPluginDescriptionStatic(), CreateInstance, + CreateMemoryInstance, GetModuleSpecifications); +} + +void ObjectFileCOFF::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +lldb_private::ObjectFile * +ObjectFileCOFF::CreateInstance(const ModuleSP &module_sp, DataBufferSP data_sp, + offset_t data_offset, const FileSpec *file, + offset_t file_offset, offset_t length) { + Log *log = GetLog(LLDBLog::Object); + + if (!data_sp) { + data_sp = MapFileData(*file, length, file_offset); + if (!data_sp) { + LLDB_LOG(log, + "Failed to create ObjectFileCOFF instance: cannot read file {0}", + file->GetPath()); + return nullptr; + } + data_offset = 0; + } + + assert(data_sp && "must have mapped file at this point"); + + if (!IsCOFFObjectFile(data_sp)) + return nullptr; + + if (data_sp->GetByteSize() < length) { + data_sp = MapFileData(*file, length, file_offset); + if (!data_sp) { + LLDB_LOG(log, + "Failed to create ObjectFileCOFF instance: cannot read file {0}", + file->GetPath()); + return nullptr; + } + data_offset = 0; + } + + return new ObjectFileCOFF(module_sp, data_sp, data_offset, file, file_offset, + length); +} + +lldb_private::ObjectFile *ObjectFileCOFF::CreateMemoryInstance( + const ModuleSP &module_sp, WritableDataBufferSP data_sp, + const ProcessSP &process_sp, addr_t header) { + // FIXME: do we need to worry about construction from a memory region? + return nullptr; +} + +size_t ObjectFileCOFF::GetModuleSpecifications( + const FileSpec &file, DataBufferSP &data_sp, offset_t data_offset, + offset_t file_offset, offset_t length, ModuleSpecList &specs) { + if (!IsCOFFObjectFile(data_sp)) + return 0; + + MemoryBufferRef buffer{toStringRef(data_sp->GetData()), + file.GetFilename().GetStringRef()}; + Expected> binary = createBinary(buffer); + if (!binary) { + Log *log = GetLog(LLDBLog::Object); + LLDB_LOG_ERROR(log, binary.takeError(), + "Failed to create binary for file ({1}): {0}", + file.GetFilename()); + return 0; + } + + std::unique_ptr object = + unique_dyn_cast(std::move(*binary)); + switch (static_cast(object->getMachine())) { + case COFF::IMAGE_FILE_MACHINE_I386: + specs.Append(ModuleSpec(file, ArchSpec("i686-unknown-windows-msvc"))); + return 1; + case COFF::IMAGE_FILE_MACHINE_AMD64: + specs.Append(ModuleSpec(file, ArchSpec("x86_64-unknown-windows-msvc"))); + return 1; + case COFF::IMAGE_FILE_MACHINE_ARMNT: + specs.Append(ModuleSpec(file, ArchSpec("armv7-unknown-windows-msvc"))); + return 1; + case COFF::IMAGE_FILE_MACHINE_ARM64: + specs.Append(ModuleSpec(file, ArchSpec("aarch64-unknown-windows-msvc"))); + return 1; + default: + return 0; + } +} + +void ObjectFileCOFF::Dump(Stream *stream) { + ModuleSP module(GetModule()); + if (!module) + return; + + std::lock_guard guard(module->GetMutex()); + + stream->Printf("%p: ", static_cast(this)); + stream->Indent(); + stream->PutCString("ObjectFileCOFF"); + *stream << ", file = '" << m_file + << "', arch = " << GetArchitecture().GetArchitectureName() << '\n'; + + if (SectionList *sections = GetSectionList()) + sections->Dump(stream->AsRawOstream(), stream->GetIndentLevel(), nullptr, + true, std::numeric_limits::max()); +} + +uint32_t ObjectFileCOFF::GetAddressByteSize() const { + return const_cast(this)->GetArchitecture().GetAddressByteSize(); +} + +ArchSpec ObjectFileCOFF::GetArchitecture() { + switch (static_cast(m_object->getMachine())) { + case COFF::IMAGE_FILE_MACHINE_I386: + return ArchSpec("i686-unknown-windows-msvc"); + case COFF::IMAGE_FILE_MACHINE_AMD64: + return ArchSpec("x86_64-unknown-windows-msvc"); + case COFF::IMAGE_FILE_MACHINE_ARMNT: + return ArchSpec("armv7-unknown-windows-msvc"); + case COFF::IMAGE_FILE_MACHINE_ARM64: + return ArchSpec("aarch64-unknown-windows-msvc"); + default: + return ArchSpec(); + } +} + +void ObjectFileCOFF::CreateSections(lldb_private::SectionList §ions) { + if (m_sections_up) + return; + + m_sections_up = std::make_unique(); + ModuleSP module(GetModule()); + if (!module) + return; + + std::lock_guard guard(module->GetMutex()); + + auto SectionType = [](const object::coff_section *Section) -> lldb::SectionType { + lldb::SectionType type = + StringSwitch(Section->Name) + .Case(".debug_abbrev", eSectionTypeDWARFDebugAbbrev) + .Case(".debug_info", eSectionTypeDWARFDebugInfo) + .Case(".debug_line", eSectionTypeDWARFDebugLine) + .Case(".debug_pubnames", eSectionTypeDWARFDebugPubNames) + .Case(".debug_pubtypes", eSectionTypeDWARFDebugPubTypes) + .Case(".debug_str", eSectionTypeDWARFDebugStr) + .StartsWith(".debug", eSectionTypeDebug) + .Case("clangast", eSectionTypeOther) + .Default(eSectionTypeInvalid); + if (type != eSectionTypeInvalid) + return type; + + if (Section->Characteristics & COFF::IMAGE_SCN_CNT_CODE) + return eSectionTypeCode; + if (Section->Characteristics & COFF::IMAGE_SCN_CNT_INITIALIZED_DATA) + return eSectionTypeData; + if (Section->Characteristics & COFF::IMAGE_SCN_CNT_UNINITIALIZED_DATA) + return Section->SizeOfRawData ? eSectionTypeData : eSectionTypeZeroFill; + return eSectionTypeOther; + }; + auto Permissions = [](const object::coff_section *Section) -> uint32_t { + uint32_t permissions = 0; + if (Section->Characteristics & COFF::IMAGE_SCN_MEM_EXECUTE) + permissions |= lldb::ePermissionsExecutable; + if (Section->Characteristics & COFF::IMAGE_SCN_MEM_READ) + permissions |= lldb::ePermissionsReadable; + if (Section->Characteristics & COFF::IMAGE_SCN_MEM_WRITE) + permissions |= lldb::ePermissionsWritable; + return permissions; + }; + + for (const auto &SecRef : m_object->sections()) { + const auto COFFSection = m_object->getCOFFSection(SecRef); + + SectionSP section = + std::make_unique
(module, this, + static_cast(SecRef.getIndex()), + ConstString(COFFSection->Name), + SectionType(COFFSection), + COFFSection->VirtualAddress, + COFFSection->VirtualSize, + COFFSection->PointerToRawData, + COFFSection->SizeOfRawData, + COFFSection->getAlignment(), + 0); + section->SetPermissions(Permissions(COFFSection)); + + m_sections_up->AddSection(section); + sections.AddSection(section); + } +} + +void ObjectFileCOFF::ParseSymtab(lldb_private::Symtab &symtab) { + Log *log = GetLog(LLDBLog::Object); + + SectionList *sections = GetSectionList(); + symtab.Reserve(symtab.GetNumSymbols() + m_object->getNumberOfSymbols()); + + auto SymbolType = [](const COFFSymbolRef &Symbol) -> lldb::SymbolType { + if (Symbol.getComplexType() == COFF::IMAGE_SYM_DTYPE_FUNCTION) + return eSymbolTypeCode; + if (Symbol.getBaseType() == COFF::IMAGE_SYM_TYPE_NULL && + Symbol.getComplexType() == COFF::IMAGE_SYM_DTYPE_NULL) + return eSymbolTypeData; + return eSymbolTypeInvalid; + }; + + for (const auto &SymRef : m_object->symbols()) { + const auto COFFSymRef = m_object->getCOFFSymbol(SymRef); + + Expected NameOrErr = SymRef.getName(); + if (auto error = NameOrErr.takeError()) { + LLDB_LOG(log, "ObjectFileCOFF: failed to get symbol name: {0}", + llvm::fmt_consume(std::move(error))); + continue; + } + + Symbol symbol; + symbol.GetMangled().SetValue(ConstString(*NameOrErr)); + + int16_t SecIdx = static_cast(COFFSymRef.getSectionNumber()); + if (SecIdx == COFF::IMAGE_SYM_ABSOLUTE) { + symbol.GetAddressRef() = Address{COFFSymRef.getValue()}; + symbol.SetType(eSymbolTypeAbsolute); + } else if (SecIdx >= 1) { + symbol.GetAddressRef() = Address(sections->GetSectionAtIndex(SecIdx - 1), + COFFSymRef.getValue()); + symbol.SetType(SymbolType(COFFSymRef)); + } + + symtab.AddSymbol(symbol); + } + + LLDB_LOG(log, "ObjectFileCOFF::ParseSymtab processed {0} symbols", + m_object->getNumberOfSymbols()); +} + +bool ObjectFileCOFF::ParseHeader() { + ModuleSP module(GetModule()); + if (!module) + return false; + + std::lock_guard guard(module->GetMutex()); + + m_data.SetByteOrder(eByteOrderLittle); + m_data.SetAddressByteSize(GetAddressByteSize()); + + return true; +}