Index: source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h =================================================================== --- source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h +++ source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h @@ -17,6 +17,7 @@ // Other libraries and framework includes // Project includes #include "lldb/Symbol/ObjectFile.h" +#include "llvm/Object/Binary.h" class ObjectFilePECOFF : public lldb_private::ObjectFile { public: @@ -215,6 +216,20 @@ typedef enum coff_data_dir_type { coff_data_dir_export_table = 0, coff_data_dir_import_table = 1, + coff_data_dir_resource_table, + coff_data_dir_exception_table, + coff_data_dir_certificate_table, + coff_data_dir_base_relocation_table, + coff_data_dir_debug, + coff_data_dir_architecture, + coff_data_dir_global_ptr, + coff_data_dir_tls_table, + coff_data_dir_load_config_table, + coff_data_dir_bound_import, + coff_data_dir_iat, + coff_data_dir_delay_import_descriptor, + coff_data_dir_clr_runtime_header, + coff_data_dir_reserved } coff_data_dir_type; typedef struct section_header { @@ -253,6 +268,14 @@ uint32_t address_of_name_ordinals; } export_directory_entry; + typedef struct import_directory_entry { + uint32_t lookup_table_rva; + uint32_t time_date_stamp; + uint32_t forwarder_chain; + uint32_t name_rva; + uint32_t address_table_rva; + } import_directory_entry; + static bool ParseDOSHeader(lldb_private::DataExtractor &data, dos_header_t &dos_header); static bool ParseCOFFHeader(lldb_private::DataExtractor &data, @@ -261,6 +284,8 @@ bool ParseCOFFOptionalHeader(lldb::offset_t *offset_ptr); bool ParseSectionHeaders(uint32_t offset); + uint32_t ParseDependentModules(); + static void DumpDOSHeader(lldb_private::Stream *s, const dos_header_t &header); static void DumpCOFFHeader(lldb_private::Stream *s, @@ -269,12 +294,17 @@ const coff_opt_header_t &header); void DumpSectionHeaders(lldb_private::Stream *s); void DumpSectionHeader(lldb_private::Stream *s, const section_header_t &sh); + void DumpDependentModules(lldb_private::Stream *s); + bool GetSectionName(std::string §_name, const section_header_t §); typedef std::vector SectionHeaderColl; typedef SectionHeaderColl::iterator SectionHeaderCollIter; typedef SectionHeaderColl::const_iterator SectionHeaderCollConstIter; +private: + bool CreateBinary(); + private: dos_header_t m_dos_header; coff_header_t m_coff_header; @@ -282,6 +312,9 @@ SectionHeaderColl m_sect_headers; lldb::addr_t m_image_base; lldb_private::Address m_entry_point_address; + mutable std::unique_ptr m_filespec_ap; + typedef llvm::object::OwningBinary OWNBINType; + mutable std::unique_ptr m_owningbin_up; }; #endif // liblldb_ObjectFilePECOFF_h_ Index: source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp =================================================================== --- source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -23,11 +23,14 @@ #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/FileSpec.h" +#include "lldb/Utility/Log.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/Timer.h" #include "lldb/Utility/UUID.h" #include "llvm/BinaryFormat/COFF.h" +#include "llvm/Object/COFFImportFile.h" +#include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" #define IMAGE_DOS_SIGNATURE 0x5A4D // MZ @@ -86,6 +89,10 @@ if (!objfile_ap || !objfile_ap->ParseHeader()) return nullptr; + // Cache coff binary. + if (!objfile_ap->CreateBinary()) + return nullptr; + return objfile_ap.release(); } @@ -131,9 +138,7 @@ specs.Append(ModuleSpec(file, spec)); spec.SetTriple("i686-pc-windows"); specs.Append(ModuleSpec(file, spec)); - } - else if (coff_header.machine == MachineArmNt) - { + } else if (coff_header.machine == MachineArmNt) { spec.SetTriple("arm-pc-windows"); specs.Append(ModuleSpec(file, spec)); } @@ -168,6 +173,40 @@ return lldb::eSymbolTypeInvalid; } +bool ObjectFilePECOFF::CreateBinary() { + if (m_owningbin_up.get()) + return true; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_OBJECT)); + + auto binary = llvm::object::createBinary(m_file.GetPath()); + if (!binary) { + if (log) + log->Printf("ObjectFilePECOFF::CreateBinary() - failed to create binary " + "for file (%s): %s", + m_file ? m_file.GetPath().c_str() : "", + errorToErrorCode(binary.takeError()).message().c_str()); + return false; + } + + // Make sure we only handle COFF format. + if (!binary->getBinary()->isCOFF() && + !binary->getBinary()->isCOFFImportFile()) + return false; + + m_owningbin_up = llvm::make_unique(std::move(*binary)); + if (log) + log->Printf("%p ObjectFilePECOFF::CreateBinary() module = %p (%s), file = " + "%s, binary = %p (Bin = %p)", + static_cast(this), + static_cast(GetModule().get()), + GetModule()->GetSpecificationDescription().c_str(), + m_file ? m_file.GetPath().c_str() : "", + static_cast(m_owningbin_up.get()), + static_cast(m_owningbin_up->getBinary())); + return true; +} + ObjectFilePECOFF::ObjectFilePECOFF(const lldb::ModuleSP &module_sp, DataBufferSP &data_sp, lldb::offset_t data_offset, @@ -176,7 +215,7 @@ lldb::offset_t length) : ObjectFile(module_sp, file, file_offset, length, data_sp, data_offset), m_dos_header(), m_coff_header(), m_coff_header_opt(), m_sect_headers(), - m_entry_point_address() { + m_entry_point_address(), m_filespec_ap(), m_owningbin_up() { ::memset(&m_dos_header, 0, sizeof(m_dos_header)); ::memset(&m_coff_header, 0, sizeof(m_coff_header)); ::memset(&m_coff_header_opt, 0, sizeof(m_coff_header_opt)); @@ -188,7 +227,7 @@ addr_t header_addr) : ObjectFile(module_sp, process_sp, header_addr, header_data_sp), m_dos_header(), m_coff_header(), m_coff_header_opt(), m_sect_headers(), - m_entry_point_address() { + m_entry_point_address(), m_filespec_ap(), m_owningbin_up() { ::memset(&m_dos_header, 0, sizeof(m_dos_header)); ::memset(&m_coff_header, 0, sizeof(m_coff_header)); ::memset(&m_coff_header_opt, 0, sizeof(m_coff_header_opt)); @@ -431,7 +470,7 @@ DataExtractor ObjectFilePECOFF::ReadImageData(uint32_t offset, size_t size) { if (m_file) { - // A bit of a hack, but we intend to write to this buffer, so we can't + // A bit of a hack, but we intend to write to this buffer, so we can't // mmap it. auto buffer_sp = MapFileData(m_file, size, offset); return DataExtractor(buffer_sp, GetByteOrder(), GetAddressByteSize()); @@ -799,8 +838,76 @@ bool ObjectFilePECOFF::GetUUID(UUID *uuid) { return false; } +uint32_t ObjectFilePECOFF::ParseDependentModules() { + ModuleSP module_sp(GetModule()); + if (!module_sp) + return 0; + + std::lock_guard guard(module_sp->GetMutex()); + if (m_filespec_ap.get()) + return m_filespec_ap->GetSize(); + + // Cache coff binary if it is not done yet. + if (!CreateBinary()) + return 0; + + Log *log(GetLogIfAllCategoriesSet(LIBLLDB_LOG_OBJECT)); + if (log) + log->Printf("%p ObjectFilePECOFF::ParseDependentModules() module = %p " + "(%s), binary = %p (Bin = %p)", + static_cast(this), static_cast(module_sp.get()), + module_sp->GetSpecificationDescription().c_str(), + static_cast(m_owningbin_up.get()), + m_owningbin_up.get() + ? static_cast(m_owningbin_up->getBinary()) + : nullptr); + + auto COFFObj = + llvm::dyn_cast(m_owningbin_up->getBinary()); + if (!COFFObj) + return 0; + + m_filespec_ap.reset(new FileSpecList()); + + for (const auto &entry : COFFObj->import_directories()) { + llvm::StringRef dll_name; + auto ec = entry.getName(dll_name); + // Report a bogus entry. + if (ec != std::error_code()) { + if (log) + log->Printf("ObjectFilePECOFF::ParseDependentModules() - failed to get " + "import directory entry name: %s", + ec.message().c_str()); + continue; + } + + // At this moment we only have the base name of the DLL. The full path can + // only be seen after the dynamic loading. Our best guess is to resolve + // it with the object file's directory. + FileSpec dll_fspec(dll_name, true); + auto obj_dir = module_sp->GetFileSpec().GetDirectory().AsCString(); + if (obj_dir) + dll_fspec.GetDirectory().SetCString(obj_dir); + + // TODO: Check the remote debugging case. + if (dll_fspec.Exists()) + m_filespec_ap->Append(dll_fspec); + else { + // System DLL or DLL not found in the object file directory. + m_filespec_ap->Append(FileSpec(dll_name, true)); + } + } + return m_filespec_ap->GetSize(); +} + uint32_t ObjectFilePECOFF::GetDependentModules(FileSpecList &files) { - return 0; + auto num_modules = ParseDependentModules(); + auto original_size = files.GetSize(); + + for (unsigned i = 0; i < num_modules; ++i) + files.AppendIfUnique(m_filespec_ap->GetFileSpecAtIndex(i)); + + return files.GetSize() - original_size; } lldb_private::Address ObjectFilePECOFF::GetEntryPointAddress() { @@ -857,6 +964,9 @@ s->EOL(); DumpSectionHeaders(s); s->EOL(); + + DumpDependentModules(s); + s->EOL(); } } @@ -1004,6 +1114,22 @@ } } +//---------------------------------------------------------------------- +// DumpDependentModules +// +// Dump all of the dependent modules to the specified output stream +//---------------------------------------------------------------------- +void ObjectFilePECOFF::DumpDependentModules(lldb_private::Stream *s) { + auto num_modules = ParseDependentModules(); + if (num_modules > 0) { + s->PutCString("Dependent Modules\n"); + for (unsigned i = 0; i < num_modules; ++i) { + auto spec = m_filespec_ap->GetFileSpecAtIndex(i); + s->Printf(" %s\n", spec.GetFilename().GetCString()); + } + } +} + bool ObjectFilePECOFF::IsWindowsSubsystem() { switch (m_coff_header_opt.subsystem) { case llvm::COFF::IMAGE_SUBSYSTEM_NATIVE: Index: unittests/ObjectFile/CMakeLists.txt =================================================================== --- unittests/ObjectFile/CMakeLists.txt +++ unittests/ObjectFile/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(ELF) +add_subdirectory(PECOFF) Index: unittests/ObjectFile/PECOFF/CMakeLists.txt =================================================================== --- /dev/null +++ unittests/ObjectFile/PECOFF/CMakeLists.txt @@ -0,0 +1,15 @@ +add_lldb_unittest(ObjectFilePECOFFTests + ObjectFilePECOFFTests.cpp + + LINK_LIBS + lldbPluginObjectFilePECOFF + lldbUtilityHelpers + lldbCore + ) + +set(test_inputs + DllA.dll + DllB.dll + BasicsTest.exe + ) +add_unittest_inputs(ObjectFilePECOFFTests "${test_inputs}") Index: unittests/ObjectFile/PECOFF/Inputs/BasicsTest.cpp =================================================================== --- /dev/null +++ unittests/ObjectFile/PECOFF/Inputs/BasicsTest.cpp @@ -0,0 +1,15 @@ +// compile with "clang-cl BasicsTest.cpp DllA.lib DllB.lib" + +int __declspec(dllimport) DllFuncA(int n); +int __declspec(dllimport) DllFuncB(int n); + +int GlobalVar = 10; + +int main(int argc, char ** argv) { + int x = DllFuncA(4); // x = 16 + int y = DllFuncB(8); // y = 16 + int z = DllFuncB(16); + --GlobalVar; + z += GlobalVar; // Z = 41 + return (x + y + z); // return 73 +} Index: unittests/ObjectFile/PECOFF/Inputs/DllA.cpp =================================================================== --- /dev/null +++ unittests/ObjectFile/PECOFF/Inputs/DllA.cpp @@ -0,0 +1,6 @@ +// Compile with "clang-cl /LDd DllA.cpp" + +int __declspec(dllexport) DllFuncA(int n) { + int rt = n * n; + return rt; +} Index: unittests/ObjectFile/PECOFF/Inputs/DllB.cpp =================================================================== --- /dev/null +++ unittests/ObjectFile/PECOFF/Inputs/DllB.cpp @@ -0,0 +1,6 @@ +// compile with "clang-cl /LDd DllB.cpp" + +int __declspec(dllexport) DllFuncB(int n) { + int rt = n + n; + return rt; +} Index: unittests/ObjectFile/PECOFF/ObjectFilePECOFFTests.cpp =================================================================== --- /dev/null +++ unittests/ObjectFile/PECOFF/ObjectFilePECOFFTests.cpp @@ -0,0 +1,134 @@ +//===-- ObjectFilePECOFFTests.cpp -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h" +#include "TestingSupport/TestUtilities.h" +#include "lldb/Core/Address.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ModuleSpec.h" +#include "lldb/Core/Section.h" +#include "lldb/Host/HostInfo.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Utility/FileSpec.h" +#include "llvm/Support/Path.h" +#include "gtest/gtest.h" + +using namespace lldb_private; + +class ObjectFilePECOFFTests : public testing::Test { +public: + void SetUp() override { + HostInfo::Initialize(); + ObjectFilePECOFF::Initialize(); + + m_BasicsTest_exe = GetInputFilePath("BasicsTest.exe"); + m_DllA = GetInputFilePath("DllA.dll"); + m_DllB = GetInputFilePath("DllB.dll"); + } + + void TearDown() override { + ObjectFilePECOFF::Terminate(); + HostInfo::Terminate(); + } + +protected: + std::string m_BasicsTest_exe; + std::string m_DllA; + std::string m_DllB; +}; + +TEST_F(ObjectFilePECOFFTests, TestPECOFFBasics) { + ModuleSpec spec{FileSpec(m_BasicsTest_exe, false)}; + auto module = std::make_shared(spec); + auto list = module->GetSectionList(); + ASSERT_NE(nullptr, list); + + EXPECT_NE(nullptr, list->FindSectionByName(ConstString(".text"))); + EXPECT_NE(nullptr, list->FindSectionByName(ConstString(".data"))); + EXPECT_NE(nullptr, list->FindSectionByName(ConstString(".reloc"))); + EXPECT_NE(nullptr, list->FindSectionByName(ConstString(".pdata"))); + + auto obj = module->GetObjectFile(); + ASSERT_NE(nullptr, obj); + + EXPECT_STREQ("pe-coff", obj->GetPluginName().AsCString()); + EXPECT_TRUE(obj->IsExecutable()); + + // TODO: Check UUID + + // BasicsTest.exe is compiled without debug information. No exports either. + auto fspecs = obj->GetDebugSymbolFilePaths(); + EXPECT_EQ(0u, fspecs.GetSize()); + + auto symtab = obj->GetSymtab(); + ASSERT_NE(nullptr, symtab); + + EXPECT_EQ(0u, symtab->GetNumSymbols()); +} + +TEST_F(ObjectFilePECOFFTests, TestDLL) { + ModuleSpec spec{FileSpec(m_DllA, false)}; + auto module = std::make_shared(spec); + auto list = module->GetSectionList(); + ASSERT_NE(nullptr, list); + + auto text = list->FindSectionByName(ConstString(".text")); + ASSERT_NE(nullptr, text); + + auto obj = module->GetObjectFile(); + ASSERT_NE(nullptr, obj); + + EXPECT_FALSE(obj->IsExecutable()); + + { + ModuleSpecList specs; + auto fspec = FileSpec(m_DllA, false); + ASSERT_EQ(1u, obj->GetModuleSpecifications(fspec, 0, 0, specs)); + } + + auto symtab = obj->GetSymtab(); + ASSERT_NE(nullptr, symtab); + + ASSERT_EQ(1u, symtab->GetNumSymbols()); + + // DllA.dll is compiled without any debug information. + // We expect the symtab is searched. + auto symbol = + module->FindFirstSymbolWithNameAndType(ConstString("?DllFuncA@@YAHH@Z")); + ASSERT_NE(nullptr, symbol); + + // We expect the symbol is valid. + EXPECT_GT(symbol->GetByteSize(), 0); + EXPECT_EQ(text, symbol->GetAddress().GetSection()); + + // We expect the symbol from symtab has no debug information. + auto Symbol = *symbol; + EXPECT_EQ(nullptr, Symbol.CalculateSymbolContextFunction()); + EXPECT_EQ(0u, Symbol.GetPrologueByteSize()); +} + +TEST_F(ObjectFilePECOFFTests, TestDependModules) { + ModuleSpec spec{FileSpec(m_BasicsTest_exe, false)}; + auto module = std::make_shared(spec); + auto obj = module->GetObjectFile(); + ASSERT_NE(nullptr, obj); + + FileSpecList deps; + auto num_of_deps = obj->GetDependentModules(deps); + ASSERT_EQ(3u, num_of_deps); + + std::vector Deps; + for (uint32_t i = 0; i < num_of_deps; ++i) + Deps.push_back(deps.GetFileSpecAtIndex(i).GetFilename().AsCString()); + llvm::sort(Deps.begin(), Deps.end()); + + EXPECT_STREQ("DllA.dll", Deps[0].c_str()); + EXPECT_STREQ("DllB.dll", Deps[1].c_str()); + EXPECT_STREQ("KERNEL32.dll", Deps[2].c_str()); +}