diff --git a/lldb/include/lldb/Core/Section.h b/lldb/include/lldb/Core/Section.h index f0f5a0b3499c0..5cad7b10305cd 100644 --- a/lldb/include/lldb/Core/Section.h +++ b/lldb/include/lldb/Core/Section.h @@ -46,6 +46,8 @@ class SectionList { /// Create an empty list. SectionList() = default; + SectionList(const SectionList &lhs); + SectionList &operator=(const SectionList &rhs); size_t AddSection(const lldb::SectionSP §ion_sp); @@ -96,6 +98,17 @@ class SectionList { /// information. uint64_t GetDebugInfoSize() const; + // Callback to decide which of two matching sections should be used in the + // merged output. + using MergeCallback = + std::function; + + // Function that merges two different sections into a new output list. All + // unique sections will be checked for conflict and resolved using the + // supplied merging callback. + static SectionList Merge(SectionList &lhs, SectionList &rhs, + MergeCallback filter); + protected: collection m_sections; }; diff --git a/lldb/source/Core/Section.cpp b/lldb/source/Core/Section.cpp index 02d9d86fe5374..f941a78d3f8e7 100644 --- a/lldb/source/Core/Section.cpp +++ b/lldb/source/Core/Section.cpp @@ -473,6 +473,8 @@ bool Section::ContainsOnlyDebugInfo() const { #pragma mark SectionList +SectionList::SectionList(const SectionList &rhs) : m_sections(rhs.m_sections) {} + SectionList &SectionList::operator=(const SectionList &rhs) { if (this != &rhs) m_sections = rhs.m_sections; @@ -683,6 +685,33 @@ uint64_t SectionList::GetDebugInfoSize() const { return debug_info_size; } +SectionList SectionList::Merge(SectionList &lhs, SectionList &rhs, + MergeCallback filter) { + SectionList output_list; + + // Iterate through all the sections in lhs and see if we have matches in + // the rhs list. + for (const auto &lhs_section : lhs) { + auto rhs_section = rhs.FindSectionByName(lhs_section->GetName()); + if (rhs_section) + output_list.AddSection(filter(lhs_section, rhs_section)); + else + output_list.AddSection(lhs_section); + } + + // Now that we've visited all possible duplicates, we can iterate over + // the rhs and take any values not in lhs. + for (const auto &rhs_section : rhs) { + auto lhs_section = lhs.FindSectionByName(rhs_section->GetName()); + // Because we already visited everything overlapping between rhs + // and lhs, any section not in lhs is unique and can be output. + if (!lhs_section) + output_list.AddSection(rhs_section); + } + + return output_list; +} + namespace llvm { namespace json { diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp index 097c91b623e8f..1413a5c61dedc 100644 --- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp +++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.cpp @@ -130,6 +130,29 @@ class ELFRelocation { RelocUnion reloc; }; + +lldb::SectionSP MergeSections(lldb::SectionSP lhs, lldb::SectionSP rhs) { + assert(lhs && rhs); + + lldb::ModuleSP lhs_module_parent = lhs->GetModule(); + lldb::ModuleSP rhs_module_parent = rhs->GetModule(); + assert(lhs_module_parent && rhs_module_parent); + + // Do a sanity check, these should be the same. + if (lhs->GetFileAddress() != rhs->GetFileAddress()) + lhs_module_parent->ReportWarning( + "Mismatch addresses for section {0} when " + "merging with {1}, expected: {2:x}, " + "actual: {3:x}", + lhs->GetTypeAsCString(), + rhs_module_parent->GetFileSpec().GetPathAsConstString().GetCString(), + lhs->GetByteSize(), rhs->GetByteSize()); + + // We want to take the greater of two sections. If LHS and RHS are both + // SHT_NOBITS, we should default to LHS. If RHS has a bigger section, + // indicating it has data that wasn't stripped, we should take that instead. + return rhs->GetFileSize() > lhs->GetFileSize() ? rhs : lhs; +} } // end anonymous namespace ELFRelocation::ELFRelocation(unsigned type) { @@ -1967,10 +1990,10 @@ void ObjectFileELF::CreateSections(SectionList &unified_section_list) { provider.AddSection(std::move(*InfoOr), std::move(section_sp)); } - // For eTypeDebugInfo files, the Symbol Vendor will take care of updating the - // unified section list. - if (GetType() != eTypeDebugInfo) - unified_section_list = *m_sections_up; + // Merge the two adding any new sections, and overwriting any existing + // sections that are SHT_NOBITS + unified_section_list = + SectionList::Merge(unified_section_list, *m_sections_up, MergeSections); // 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). diff --git a/lldb/test/API/python_api/unified_section_list/Makefile b/lldb/test/API/python_api/unified_section_list/Makefile new file mode 100644 index 0000000000000..431e716ab8f69 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/Makefile @@ -0,0 +1,5 @@ +CXX_SOURCES := main.cpp + +SPLIT_DEBUG_SYMBOLS := YES + +include Makefile.rules diff --git a/lldb/test/API/python_api/unified_section_list/TestModuleUnifiedSectionList.py b/lldb/test/API/python_api/unified_section_list/TestModuleUnifiedSectionList.py new file mode 100644 index 0000000000000..93b23d0ba81cb --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/TestModuleUnifiedSectionList.py @@ -0,0 +1,285 @@ +""" +Test Unified Section List merging. +""" + +import os +import shutil + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +from lldbsuite.test.lldbutil import symbol_type_to_str + + +class ModuleUnifiedSectionList(TestBase): + @skipUnlessPlatform(["linux", "freebsd", "netbsd"]) + def test_unified_section_list(self): + self.build() + exe = self.getBuildArtifact("a.out") + debug_info = self.getBuildArtifact("a.out.debug") + new_dir = os.path.join(os.path.dirname(debug_info), "new_dir") + os.mkdir(new_dir) + renamed_debug_info = os.path.join(new_dir, "renamed.debug") + os.rename(debug_info, renamed_debug_info) + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + self.assertGreater(target.GetNumModules(), 0) + + main_exe_module = target.GetModuleAtIndex(0) + eh_frame = main_exe_module.FindSection(".eh_frame") + self.assertTrue(eh_frame.IsValid()) + self.assertGreater(eh_frame.size, 0) + + # Should be stripped in main executable. + debug_info_section = main_exe_module.FindSection(".debug_info") + self.assertFalse(debug_info_section.IsValid()) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {renamed_debug_info}", res) + self.assertTrue(res.Succeeded()) + + # Should be stripped in .debuginfo but be present in main executable. + main_exe_module = target.GetModuleAtIndex(0) + eh_frame = main_exe_module.FindSection(".eh_frame") + self.assertTrue(eh_frame.IsValid()) + self.assertGreater(eh_frame.size, 0) + + # Should be unified and both sections should have contents. + debug_info_section = main_exe_module.FindSection(".debug_info") + self.assertTrue(debug_info_section.IsValid()) + self.assertGreater(debug_info_section.file_size, 0) + + def test_unified_section_list_overwrite_larger_section(self): + """ + Test the merging of an ELF file with another ELF File where all the new sections are bigger, validating we + overwrite .comment from SHT_NOBITS to the new SHT_PROGBITS section and the smaller .text with the larger + .text + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # First we verify out .text section is the expected BEC0FFEE + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEE")) + + # .comment in main.yaml should be SHT_NOBITS, and size 0 + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + self.assertEqual(comment_before_merge.data.size, 0) + + # yamlize the main.largertext.yaml and force symbol loading + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.largertext.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # verify we took the larger .text section + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + self.assertGreater(text_after_merge.data.size, text_before_merge.data.size) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + # in main.largertext.yaml comment is not SHT_NOBITS, and so we should see + # the size > 0 and equal to BAADF00D + comment_after_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_after_merge.IsValid()) + comment_content_after_merge = comment_after_merge.data.ReadRawData( + error, 0, comment_after_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00D")) + + def test_unified_section_list_overwrite_smaller_section(self): + """ + Test the merging of an ELF file with another ELF File where all the existing sections are bigger, validating we don't + overwrite with the SHT_NOBITS for .comment or the smaller .text section. + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.largertext.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # Same as above test but inverse, verify our larger .text section + # is the expected BEC0FFEE palindrome + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + # Comment is SHT_PROGBITS on the larger yaml and should remain + # the same after merge. + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + comment_content = comment_before_merge.data.ReadRawData( + error, 0, comment_before_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content, bytes.fromhex("BAADF00D")) + + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # Verify we didn't replace the sections after merge.s + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + self.assertEqual(text_after_merge.data.size, text_before_merge.data.size) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + comment_after_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_after_merge.IsValid()) + comment_content_after_merge = comment_after_merge.data.ReadRawData( + error, 0, comment_after_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00D")) + + def test_unified_section_list_overwrite_mixed_merge(self): + """ + Test the merging of an ELF file with another ELF File where the lhs has a larger .comment section + and the RHS has a larger .text section. + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.largercomment.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # Verify we have the expected smaller BEC0FFEE + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEE")) + + # Verify we have the larger palindromic comment + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + comment_content = comment_before_merge.data.ReadRawData( + error, 0, comment_before_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content, bytes.fromhex("BAADF00DF00DBAAD")) + + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.largertext.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # Verify we replaced .text + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEEEEFF0CEB")) + + # Verify .comment is still the same. + comment_after_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_after_merge.IsValid()) + comment_content_after_merge = comment_after_merge.data.ReadRawData( + error, 0, comment_after_merge.data.size + ) + + self.assertTrue(error.Success()) + self.assertEqual(comment_content_after_merge, bytes.fromhex("BAADF00DF00DBAAD")) + + def test_unified_section_list_overwrite_equal_size(self): + """ + Test the merging of an ELF file with an ELF file with sections of the same size with different values + .text + """ + exe = self.getBuildArtifact("a.out") + self.yaml2obj("main.yaml", exe) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, VALID_TARGET) + main_exe_module = target.GetModuleAtIndex(0) + + # First we verify out .text section is the expected BEC0FFEE + text_before_merge = main_exe_module.FindSection(".text") + self.assertTrue(text_before_merge.IsValid()) + error = lldb.SBError() + section_content = text_before_merge.data.ReadRawData( + error, 0, text_before_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content, bytes.fromhex("BEC0FFEE")) + + # .comment in main.yaml should be SHT_NOBITS, and size 0 + comment_before_merge = main_exe_module.FindSection(".comment") + self.assertTrue(comment_before_merge.IsValid()) + self.assertEqual(comment_before_merge.data.size, 0) + + # yamlize the main with the .text reversed from BEC0FFEE + # to EEFF0CEB. We should still keep our .text with BEC0FFEE + debug_info = self.getBuildArtifact("a.out.debug") + self.yaml2obj("main.reversedtext.yaml", debug_info) + + ci = self.dbg.GetCommandInterpreter() + res = lldb.SBCommandReturnObject() + ci.HandleCommand(f"target symbols add {debug_info}", res) + self.assertTrue(res.Succeeded()) + + # verify .text did not change + main_exe_module_after_merge = target.GetModuleAtIndex(0) + text_after_merge = main_exe_module_after_merge.FindSection(".text") + self.assertTrue(text_after_merge.IsValid()) + section_content_after_merge = text_after_merge.data.ReadRawData( + error, 0, text_after_merge.data.size + ) + self.assertTrue(error.Success()) + self.assertEqual(section_content_after_merge, bytes.fromhex("BEC0FFEE")) + + # verify comment did not change + comment_afer_merge = main_exe_module_after_merge.FindSection(".comment") + self.assertTrue(comment_afer_merge.IsValid()) + self.assertEqual(comment_afer_merge.data.size, 0) diff --git a/lldb/test/API/python_api/unified_section_list/main.cpp b/lldb/test/API/python_api/unified_section_list/main.cpp new file mode 100644 index 0000000000000..45fd52eeeb303 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.cpp @@ -0,0 +1,3 @@ +#include + +int main() { printf("Hello World\n"); } diff --git a/lldb/test/API/python_api/unified_section_list/main.largercomment.yaml b/lldb/test/API/python_api/unified_section_list/main.largercomment.yaml new file mode 100644 index 0000000000000..f7860063e151a --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.largercomment.yaml @@ -0,0 +1,46 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEE + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 + Content: BAADF00DF00DBAAD +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/python_api/unified_section_list/main.largertext.yaml b/lldb/test/API/python_api/unified_section_list/main.largertext.yaml new file mode 100644 index 0000000000000..6450e6769db69 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.largertext.yaml @@ -0,0 +1,46 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEEEEFF0CEB + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 + Content: BAADF00D +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/python_api/unified_section_list/main.reversedtext.yaml b/lldb/test/API/python_api/unified_section_list/main.reversedtext.yaml new file mode 100644 index 0000000000000..57206666046a4 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.reversedtext.yaml @@ -0,0 +1,45 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEE + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_NOBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +... diff --git a/lldb/test/API/python_api/unified_section_list/main.yaml b/lldb/test/API/python_api/unified_section_list/main.yaml new file mode 100644 index 0000000000000..57206666046a4 --- /dev/null +++ b/lldb/test/API/python_api/unified_section_list/main.yaml @@ -0,0 +1,45 @@ +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 + Entry: 0x1040 +ProgramHeaders: + - Type: PT_PHDR + Flags: [ PF_R ] + VAddr: 0x40 + Align: 0x8 + Offset: 0x40 + - Type: PT_LOAD + Flags: [ PF_R ] + FirstSec: .text + LastSec: .fini + Align: 0x1000 + Offset: 0x0 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1040 + AddressAlign: 0x10 + Content: BEC0FFEE + - Name: .fini + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Address: 0x1140 + AddressAlign: 0x4 + Content: DEADBEEF + - Name: .comment + Type: SHT_NOBITS + Flags: [ SHF_ALLOC ] + Address: 0x3140 + AddressAlign: 0x4 +Symbols: + - Name: main + Type: STT_FUNC + Section: .text + Binding: STB_GLOBAL + Value: 0x1130 + Size: 0xF +...