diff --git a/lldb/include/lldb/Host/LZMA.h b/lldb/include/lldb/Host/LZMA.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Host/LZMA.h @@ -0,0 +1,36 @@ +//===-- llvm/Support/Compression.h ---Compression----------------*- 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 liblldb_Host_LZMA_h_ +#define liblldb_Host_LZMA_h_ + +#include + +namespace llvm { +class Error; +} // End of namespace llvm + +namespace lldb_private { + +namespace lzma { + +bool isAvailable(); + +llvm::Error getUncompressedSize(const uint8_t *compressedBuffer, + uint64_t compressedBufferSize, + uint64_t &uncompressedSize); + +llvm::Error uncompress(const uint8_t *compressedBuffer, + uint64_t compressedBufferSize, uint8_t *uncompressedData, + uint64_t uncompressedSize); + +} // End of namespace lzma + +} // End of namespace lldb_private + +#endif // liblldb_Host_LZMA_h_ diff --git a/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/Makefile b/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/Makefile new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/Makefile @@ -0,0 +1,55 @@ +LEVEL = ../../make +C_SOURCES := main.c + +all: clean binary + +.PHONY: binary +binary: a.out + $(RM) -r $@ + cp a.out binary + + # The following section is adapted from GDB's official documentation: + # http://sourceware.org/gdb/current/onlinedocs/gdb/MiniDebugInfo.html#MiniDebugInfo + + # Extract the dynamic symbols from the main binary, there is no need + # to also have these in the normal symbol table. + $(LLVMNM) -D binary --format=posix --defined-only \ + | awk '{ print $$1 }' | sort > dynsyms + + # Extract all the text (i.e. function) symbols from the debuginfo. + # (Note that we actually also accept "D" symbols, for the benefit + # of platforms like PowerPC64 that use function descriptors.) + $(LLVMNM) binary --format=posix --defined-only \ + | awk '{ if ($$2 == "T" || $$2 == "t" || $$2 == "D") print $$1 }' \ + | sort > funcsyms + + # Keep all the function symbols not already in the dynamic symbol + # table. + comm -13 dynsyms funcsyms > keep_symbols + + # Separate full debug info into debug binary. + $(OBJCOPY) --only-keep-debug binary debug + + # Copy the full debuginfo, keeping only a minimal set of symbols and + # removing some unnecessary sections. + $(OBJCOPY) -S --remove-section .gdb_index --remove-section .comment \ + --keep-symbols=keep_symbols debug mini_debuginfo + + # Drop the full debug info from the original binary. + $(LLVMSTRIP) --strip-all -R .comment binary + + # Inject the compressed data into the .gnu_debugdata section of the + # original binary. + xz --keep mini_debuginfo + $(OBJCOPY) --add-section .gnu_debugdata=mini_debuginfo.xz binary + +clean:: + $(RM) -r mini_debuginfo \ + mini_debuginfo.xz \ + binary \ + debug \ + keep_symbols \ + funcsyms \ + dynsyms + +include $(LEVEL)/Makefile.rules diff --git a/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/TestMiniDebugInfo.py b/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/TestMiniDebugInfo.py new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/TestMiniDebugInfo.py @@ -0,0 +1,36 @@ +""" Testing debugging of a binary with "mini debuginfo" in .gnu_debugdata section. """ +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestMinidebugInfo(TestBase): + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + TestBase.setUp(self) + + @no_debug_info_test # Prevent the genaration of the dwarf version of this test + @add_test_categories(["dwo"]) + @skipUnlessPlatform(["linux"]) + def test_mini_debug_info(self): + """Test that we can set and hit a breakpoint on a symbol from .gnu_debugdata.""" + + self.build() + exe = self.getBuildArtifact("binary") + + self.target = self.dbg.CreateTarget(exe) + self.assertTrue(self.target, VALID_TARGET) + + main_bp = self.target.BreakpointCreateByName("multiplyByThree", "binary") + self.assertTrue(main_bp, VALID_BREAKPOINT) + + self.process = self.target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertTrue(self.process, PROCESS_IS_VALID) + + # The stop reason of the thread should be breakpoint. + self.assertTrue(self.process.GetState() == lldb.eStateStopped, + STOPPED_DUE_TO_BREAKPOINT) + diff --git a/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/main.c b/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/main.c new file mode 100644 --- /dev/null +++ b/lldb/packages/Python/lldbsuite/test/linux/minidebuginfo/main.c @@ -0,0 +1,6 @@ +int multiplyByThree(int num) { return num * 3; } + +int main(int argc, char *argv[]) { + (void)argv; + return multiplyByThree(argc); +} diff --git a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules --- a/lldb/packages/Python/lldbsuite/test/make/Makefile.rules +++ b/lldb/packages/Python/lldbsuite/test/make/Makefile.rules @@ -388,6 +388,8 @@ endif OBJCOPY ?= $(call replace_cc_with,objcopy) + LLVMNM ?= $(call replace_cc_with,llvm-nm) + LLVMSTRIP ?= $(call replace_cc_with,llvm-strip) ARCHIVER ?= $(call replace_cc_with,ar) override AR = $(ARCHIVER) endif diff --git a/lldb/source/Host/CMakeLists.txt b/lldb/source/Host/CMakeLists.txt --- a/lldb/source/Host/CMakeLists.txt +++ b/lldb/source/Host/CMakeLists.txt @@ -29,6 +29,7 @@ common/HostProcess.cpp common/HostThread.cpp common/LockFileBase.cpp + common/LZMA.cpp common/MainLoop.cpp common/MonitoringProcessLauncher.cpp common/NativeProcessProtocol.cpp @@ -157,6 +158,9 @@ if (NOT LLDB_DISABLE_LIBEDIT) list(APPEND EXTRA_LIBS ${libedit_LIBRARIES}) endif() +if (LLVM_ENABLE_LZMA) + list(APPEND EXTRA_LIBS ${LIBLZMA_LIBRARIES}) +endif() if (NOT LLDB_DISABLE_LIBEDIT) list(APPEND LLDB_LIBEDIT_LIBS ${libedit_LIBRARIES}) diff --git a/lldb/source/Host/common/LZMA.cpp b/lldb/source/Host/common/LZMA.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Host/common/LZMA.cpp @@ -0,0 +1,160 @@ +//===--- Compression.cpp - Compression implementation ---------------------===// +// +// 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 "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" + +#ifdef LLVM_ENABLE_LZMA +#include +#endif // LLVM_ENABLE_LZMA + +namespace lldb_private { + +namespace lzma { + +using llvm::Error; +using llvm::SmallVectorImpl; +using llvm::StringRef; + +#ifndef LLVM_ENABLE_LZMA +bool isAvailable() { return false; } +llvm::Error getUncompressedSize(const uint8_t *compressedBuffer, + uint64_t compressedBufferSize, + uint64_t &uncompressedSize) { + llvm_unreachable("lzma::getUncompressedSize is unavailable"); +} + +llvm::Error uncompress(const uint8_t *compressedBuffer, + uint64_t compressedBufferSize, uint8_t *uncompressedData, + uint64_t uncompressedSize) { + llvm_unreachable("lzma::uncompress is unavailable"); +} + +#else // LLVM_ENABLE_LZMA + +bool isAvailable() { return true; } + +static StringRef convertLZMACodeToString(lzma_ret Code) { + switch (Code) { + case LZMA_STREAM_END: + return "lzma error: LZMA_STREAM_END"; + case LZMA_NO_CHECK: + return "lzma error: LZMA_NO_CHECK"; + case LZMA_UNSUPPORTED_CHECK: + return "lzma error: LZMA_UNSUPPORTED_CHECK"; + case LZMA_GET_CHECK: + return "lzma error: LZMA_GET_CHECK"; + case LZMA_MEM_ERROR: + return "lzma error: LZMA_MEM_ERROR"; + case LZMA_MEMLIMIT_ERROR: + return "lzma error: LZMA_MEMLIMIT_ERROR"; + case LZMA_FORMAT_ERROR: + return "lzma error: LZMA_FORMAT_ERROR"; + case LZMA_OPTIONS_ERROR: + return "lzma error: LZMA_OPTIONS_ERROR"; + case LZMA_DATA_ERROR: + return "lzma error: LZMA_DATA_ERROR"; + case LZMA_BUF_ERROR: + return "lzma error: LZMA_BUF_ERROR"; + case LZMA_PROG_ERROR: + return "lzma error: LZMA_PROG_ERROR"; + default: + llvm_unreachable("unknown or unexpected lzma status code"); + } +} + +llvm::Error getUncompressedSize(const uint8_t *compressedBuffer, + uint64_t compressedBufferSize, + uint64_t &uncompressedSize) { + if (!compressedBuffer || compressedBufferSize == 0) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "address (%p) or size (%lu) of xz-compressed blob cannot be 0", + compressedBuffer, compressedBufferSize); + } + + auto opts = lzma_stream_flags{}; + if (compressedBufferSize < LZMA_STREAM_HEADER_SIZE) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "size of xz-compressed blob ({0} bytes) is smaller than the " + "LZMA_STREAM_HEADER_SIZE ({1} bytes)", + compressedBufferSize, LZMA_STREAM_HEADER_SIZE); + } + + // Decode xz footer. + auto xzerr = lzma_stream_footer_decode( + &opts, compressedBuffer + compressedBufferSize - LZMA_STREAM_HEADER_SIZE); + if (xzerr != LZMA_OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "lzma_stream_footer_decode()={%s}", + convertLZMACodeToString(xzerr).data()); + } + if (compressedBufferSize < (opts.backward_size + LZMA_STREAM_HEADER_SIZE)) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "xz-compressed buffer size ({%lu} bytes) too small (required at " + "least {%lu} bytes) ", + compressedBufferSize, (opts.backward_size + LZMA_STREAM_HEADER_SIZE)); + } + + // Decode xz index. + lzma_index *xzindex; + uint64_t memlimit(UINT64_MAX); + size_t inpos = 0; + xzerr = + lzma_index_buffer_decode(&xzindex, &memlimit, nullptr, + compressedBuffer + compressedBufferSize - + LZMA_STREAM_HEADER_SIZE - opts.backward_size, + &inpos, compressedBufferSize); + if (xzerr != LZMA_OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "lzma_index_buffer_decode()={%s}", + convertLZMACodeToString(xzerr).data()); + } + + // Get size of uncompressed file to construct an in-memory buffer of the + // same size on the calling end (if needed). + uncompressedSize = lzma_index_uncompressed_size(xzindex); + + // Deallocate xz index as it is no longer needed. + lzma_index_end(xzindex, nullptr); + + return Error::success(); +} + +llvm::Error uncompress(const uint8_t *compressedBuffer, + uint64_t compressedBufferSize, uint8_t *uncompressedData, + uint64_t uncompressedSize) { + if (!compressedBuffer || compressedBufferSize == 0) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "address (%p) or size (%lu) of xz-compressed blob cannot be 0", + compressedBuffer, compressedBufferSize); + } + + // Decompress xz buffer to buffer. + uint64_t memlimit(UINT64_MAX); + size_t inpos = 0; + inpos = 0; + size_t outpos = 0; + auto xzerr = lzma_stream_buffer_decode( + &memlimit, 0, nullptr, compressedBuffer, &inpos, compressedBufferSize, + uncompressedData, &outpos, uncompressedSize); + if (xzerr != LZMA_OK) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "lzma_stream_buffer_decode()={%s}", + convertLZMACodeToString(xzerr).data()); + } + + return Error::success(); +} +#endif // LLVM_ENABLE_LZMA + +} // end of namespace lzma +} // namespace lldb_private diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h @@ -154,6 +154,14 @@ void RelocateSection(lldb_private::Section *section) override; + /// Takes the .gnu_debugdata and returns the decompressed object file that is + /// stored within that section. + /// + /// \returns either the decompressed object file stored within the + /// .gnu_debugdata section or \c nullptr if an error occured or if there's no + /// section with that name. + std::shared_ptr GetGnuDebugDataObjectFile(); + protected: std::vector @@ -383,6 +391,8 @@ lldb_private::UUID &uuid); bool AnySegmentHasPhysicalAddress(); + + std::shared_ptr m_gnuDebugDataObjectFile; }; #endif // liblldb_ObjectFileELF_h_ diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -18,6 +18,7 @@ #include "lldb/Core/PluginManager.h" #include "lldb/Core/Section.h" #include "lldb/Host/FileSystem.h" +#include "lldb/Host/LZMA.h" #include "lldb/Symbol/DWARFCallFrameInfo.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Target/SectionLoadList.h" @@ -1844,6 +1845,66 @@ unified_section_list = *m_sections_up; } +std::shared_ptr ObjectFileELF::GetGnuDebugDataObjectFile() { + if (m_gnuDebugDataObjectFile != nullptr) { + return m_gnuDebugDataObjectFile; + } + + SectionSP section = + GetSectionList()->FindSectionByName(ConstString(".gnu_debugdata")); + if (!section) { + return nullptr; + } + + if (!lldb_private::lzma::isAvailable()) { + GetModule()->ReportWarning( + "No LZMA support found for reading .gnu_debugdata section"); + return nullptr; + } + + // Determine size of uncompressed data + auto data = DataExtractor(); + section->GetSectionData(data); + uint64_t uncompressedSize = 0; + auto err = lldb_private::lzma::getUncompressedSize(data.GetDataStart(), data.GetByteSize(), uncompressedSize); + if (err) { + GetModule()->ReportWarning( + "Failed to get size of uncompressed LZMA buffer from section %s: %s", + section->GetName().AsCString(), Status(std::move(err)).AsCString()); + return nullptr; + } + + // Uncompress into large enough buffer + std::vector uncompressedData(uncompressedSize); + err = + lldb_private::lzma::uncompress(data.GetDataStart(), data.GetByteSize(), + uncompressedData.data(), uncompressedSize); + if (err) { + GetModule()->ReportWarning( + "An error occured while decompression the section %s: %s", + section->GetName().AsCString(), Status(std::move(err)).AsCString()); + return nullptr; + } + + // Construct ObjectFileELF object from decompressed buffer + DataBufferSP gdd_data_buf( + new DataBufferHeap(uncompressedData.data(), uncompressedData.size())); + auto fspec = GetFileSpec().CopyByAppendingPathComponent( + llvm::StringRef("gnu_debugdata")); + m_gnuDebugDataObjectFile.reset(new ObjectFileELF( + GetModule(), gdd_data_buf, 0, &fspec, 0, gdd_data_buf->GetByteSize())); + + // This line is essential; otherwise a breakpoint can be set but not hit. + m_gnuDebugDataObjectFile->SetType(ObjectFile::eTypeDebugInfo); + + ArchSpec spec = m_gnuDebugDataObjectFile->GetArchitecture(); + if (spec && m_gnuDebugDataObjectFile->SetModulesArchitecture(spec)) { + return m_gnuDebugDataObjectFile; + } + + return nullptr; +} + // Find the arm/aarch64 mapping symbol character in the given symbol name. // Mapping symbols have the form of "$[.]*". Additionally we // recognize cases when the mapping symbol prefixed by an arbitrary string @@ -2649,19 +2710,25 @@ // while the reverse is not necessarily true. Section *symtab = section_list->FindSectionByType(eSectionTypeELFSymbolTable, true).get(); - if (!symtab) { - // The symtab section is non-allocable and can be stripped, so if it - // doesn't exist then use the dynsym section which should always be - // there. - symtab = - section_list->FindSectionByType(eSectionTypeELFDynamicSymbols, true) - .get(); - } if (symtab) { m_symtab_up.reset(new Symtab(symtab->GetObjectFile())); symbol_id += ParseSymbolTable(m_symtab_up.get(), symbol_id, symtab); } + // The symtab section is non-allocable and can be stripped. If both, .symtab + // and .dynsym exist, we load both. And if only .dymsym exists, we load it + // alone. + auto dynsym = + section_list->FindSectionByType(eSectionTypeELFDynamicSymbols, true) + .get(); + if (dynsym) { + if (!m_symtab_up) { + auto sec = symtab ? symtab : dynsym; + m_symtab_up.reset(new Symtab(sec->GetObjectFile())); + } + symbol_id += ParseSymbolTable(m_symtab_up.get(), symbol_id, dynsym); + } + // DT_JMPREL // If present, this entry's d_ptr member holds the address of // relocation diff --git a/lldb/source/Plugins/SymbolVendor/ELF/SymbolVendorELF.cpp b/lldb/source/Plugins/SymbolVendor/ELF/SymbolVendorELF.cpp --- a/lldb/source/Plugins/SymbolVendor/ELF/SymbolVendorELF.cpp +++ b/lldb/source/Plugins/SymbolVendor/ELF/SymbolVendorELF.cpp @@ -67,6 +67,30 @@ if (!obj_file) return nullptr; + std::unique_ptr symbol_vendor( + new SymbolVendorELF(module_sp)); + SectionList *module_section_list = module_sp->GetSectionList(); + + // 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). + // If there's none in the orignal object file, we add it to it. + if (auto gdd_obj_file = + obj_file->GetGnuDebugDataObjectFile()) { + if (auto gdd_objfile_section_list = gdd_obj_file->GetSectionList()) { + if (SectionSP symtab_section_sp = + gdd_objfile_section_list->FindSectionByType( + eSectionTypeELFSymbolTable, true)) { + SectionSP module_section_sp = module_section_list->FindSectionByType( + eSectionTypeELFSymbolTable, true); + if (module_section_sp) + module_section_list->ReplaceSection(module_section_sp->GetID(), + symtab_section_sp); + else + module_section_list->AddSection(symtab_section_sp); + } + } + } + lldb_private::UUID uuid = obj_file->GetUUID(); if (!uuid) return nullptr; @@ -111,11 +135,9 @@ // have stripped the code sections, etc. dsym_objfile_sp->SetType(ObjectFile::eTypeDebugInfo); - SymbolVendorELF *symbol_vendor = new SymbolVendorELF(module_sp); - // Get the module unified section list and add our debug sections to // that. - SectionList *module_section_list = module_sp->GetSectionList(); + module_section_list = module_sp->GetSectionList(); SectionList *objfile_section_list = dsym_objfile_sp->GetSectionList(); static const SectionType g_sections[] = { @@ -141,7 +163,8 @@ } symbol_vendor->AddSymbolFileRepresentation(dsym_objfile_sp); - return symbol_vendor; + + return symbol_vendor.release(); } // PluginInterface protocol diff --git a/lldb/test/CMakeLists.txt b/lldb/test/CMakeLists.txt --- a/lldb/test/CMakeLists.txt +++ b/lldb/test/CMakeLists.txt @@ -116,6 +116,10 @@ endif() endif() +if (LLVM_ENABLE_LZMA) + add_dependencies(lldb-test-deps llvm-nm llvm-strip) +endif() + set(LLDB_DOTEST_ARGS ${LLDB_TEST_COMMON_ARGS};${LLDB_TEST_USER_ARGS}) set_property(GLOBAL PROPERTY LLDB_DOTEST_ARGS_PROPERTY ${LLDB_DOTEST_ARGS}) diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt --- a/llvm/CMakeLists.txt +++ b/llvm/CMakeLists.txt @@ -50,6 +50,8 @@ VERSION ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH} LANGUAGES C CXX ASM) +include(CMakeDependentOption) + if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "No build type selected, default to Debug") set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (default Debug)" FORCE) @@ -345,6 +347,18 @@ option(LLVM_ENABLE_ZLIB "Use zlib for compression/decompression if available." ON) +find_package(LibLZMA) +cmake_dependent_option(LLVM_ENABLE_LZMA "Support LZMA compression" ON "LIBLZMA_FOUND" OFF) +set(LLVM_LIBLZMA_LIBRARIES) +if (LLVM_ENABLE_LZMA) + if (LIBLZMA_FOUND) + add_definitions(-DLLVM_ENABLE_LZMA) + include_directories(${LIBLZMA_INCLUDE_DIRS}) + else() + message(FATAL_ERROR "LZMA devel package is required when LLVM_ENABLE_LZMA is On.") + endif() +endif() + set(LLVM_Z3_INSTALL_DIR "" CACHE STRING "Install directory of the Z3 solver.") find_package(Z3 4.7.1)