diff --git a/lldb/bindings/interface/SBTarget.i b/lldb/bindings/interface/SBTarget.i --- a/lldb/bindings/interface/SBTarget.i +++ b/lldb/bindings/interface/SBTarget.i @@ -412,6 +412,9 @@ uint32_t GetCodeByteSize (); + uint32_t + GetMaximumNumberOfChildrenToDisplay() const; + lldb::SBError SetSectionLoadAddress (lldb::SBSection section, lldb::addr_t section_base_addr); diff --git a/lldb/examples/synthetic/gnu_libstdcpp.py b/lldb/examples/synthetic/gnu_libstdcpp.py --- a/lldb/examples/synthetic/gnu_libstdcpp.py +++ b/lldb/examples/synthetic/gnu_libstdcpp.py @@ -9,6 +9,14 @@ # before relying on these formatters to do the right thing for your setup +def ForwardListSummaryProvider(valobj, dict): + list_capping_size = valobj.GetTarget().GetMaximumNumberOfChildrenToDisplay() + text = "size=" + str(valobj.GetNumChildren()) + if valobj.GetNumChildren() > list_capping_size: + return "(capped) " + text + else: + return text + class AbstractListSynthProvider: def __init__(self, valobj, dict, has_prev): ''' @@ -20,6 +28,7 @@ self.valobj = valobj self.count = None self.has_prev = has_prev + self.list_capping_size = self.valobj.GetTarget().GetMaximumNumberOfChildrenToDisplay() logger >> "Providing synthetic children for a list named " + \ str(valobj.GetName()) @@ -99,6 +108,9 @@ '_M_next').GetValueAsUnsigned(0) != self.get_end_of_list_address(): size = size + 1 current = current.GetChildMemberWithName('_M_next') + # In case of forward_list capping size is the limit of the children to be displayed + if not self.has_prev and size >= self.list_capping_size: + break return size except: logger >> "Error determining the size" diff --git a/lldb/include/lldb/API/SBTarget.h b/lldb/include/lldb/API/SBTarget.h --- a/lldb/include/lldb/API/SBTarget.h +++ b/lldb/include/lldb/API/SBTarget.h @@ -336,6 +336,11 @@ /// unit from the Architecture's code bus uint32_t GetCodeByteSize(); + /// Gets the target.max-children-count value + /// It should be used to limit the number of + /// children of large data structures to be displayed. + uint32_t GetMaximumNumberOfChildrenToDisplay() const; + /// Set the base load address for a module section. /// /// \param[in] section diff --git a/lldb/source/API/SBTarget.cpp b/lldb/source/API/SBTarget.cpp --- a/lldb/source/API/SBTarget.cpp +++ b/lldb/source/API/SBTarget.cpp @@ -1745,6 +1745,16 @@ return 0; } +uint32_t SBTarget::GetMaximumNumberOfChildrenToDisplay() const { + LLDB_RECORD_METHOD_CONST_NO_ARGS(uint32_t, SBTarget, GetMaximumNumberOfChildrenToDisplay); + + TargetSP target_sp(GetSP()); + if(target_sp){ + return target_sp->GetMaximumNumberOfChildrenToDisplay(); + } + return 0; +} + uint32_t SBTarget::GetAddressByteSize() { LLDB_RECORD_METHOD_NO_ARGS(uint32_t, SBTarget, GetAddressByteSize); @@ -2679,6 +2689,7 @@ LLDB_REGISTER_METHOD(const char *, SBTarget, GetTriple, ()); LLDB_REGISTER_METHOD(uint32_t, SBTarget, GetDataByteSize, ()); LLDB_REGISTER_METHOD(uint32_t, SBTarget, GetCodeByteSize, ()); + LLDB_REGISTER_METHOD_CONST(uint32_t, SBTarget, GetMaximumNumberOfChildrenToDisplay,()); LLDB_REGISTER_METHOD(uint32_t, SBTarget, GetAddressByteSize, ()); LLDB_REGISTER_METHOD(lldb::SBModule, SBTarget, GetModuleAtIndex, (uint32_t)); diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -961,7 +961,7 @@ cpp_category_sp->GetRegexTypeSummariesContainer()->Add( RegularExpression("^std::(__cxx11::)?forward_list<.+>(( )?&)?$"), TypeSummaryImplSP( - new StringSummaryFormat(stl_summary_flags, "size=${svar%#}"))); + new ScriptSummaryFormat(stl_summary_flags, "lldb.formatters.cpp.gnu_libstdcpp.ForwardListSummaryProvider"))); AddCXXSynthetic( cpp_category_sp, diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/TestDataFormatterGenericForwardList.py @@ -52,6 +52,38 @@ '[4] = 55555', '}']) + self.expect("settings show target.max-children-count", matching=True, + substrs=['target.max-children-count (int) = 256']) + + self.expect("frame variable thousand_elts",matching=False, + substrs=[ + '[256]', + '[333]', + '[444]', + '[555]', + '[666]', + '...' + ]) + self.runCmd( + "settings set target.max-children-count 3", + check=False) + + self.expect("frame variable thousand_elts",matching=False, + substrs=[ + '[3]', + '[4]', + '[5]', + ]) + + self.expect("frame variable thousand_elts",matching=True, + substrs=[ + 'size=256', + '[0]', + '[1]', + '[2]', + '...' + ]) + @add_test_categories(["libstdcxx"]) def test_libstdcpp(self): self.do_test(USE_LIBSTDCPP) diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/main.cpp --- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/main.cpp +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/forward_list/main.cpp @@ -2,6 +2,9 @@ int main() { std::forward_list empty{}, one_elt{47}, - five_elts{1, 22, 333, 4444, 55555}; + five_elts{1, 22, 333, 4444, 55555}, thousand_elts{}; + for(int i = 0; i<1000;i++){ + thousand_elts.push_front(i); + } return 0; // break here } diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/TestDataFormatterGenericList.py @@ -0,0 +1,215 @@ +""" +Test lldb data formatter subsystem. +""" + + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +USE_LIBSTDCPP = "USE_LIBSTDCPP" +USE_LIBCPP = "USE_LIBCPP" + +class GenericListDataFormatterTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + # Find the line numbers to break at for the different tests. + self.line = line_number('main.cpp', '// Set break point at this line.') + self.optional_line = line_number( + 'main.cpp', '// Optional break point at this line.') + self.final_line = line_number( + 'main.cpp', '// Set final break point at this line.') + + def do_test_with_run_command(self, stdlib_type): + """Test that that file and class static variables display correctly.""" + self.build(dictionary={stdlib_type: "1"}) + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line( + self, "main.cpp", self.line, num_expected_locations=-1) + + self.runCmd("run", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + # This is the function to remove the custom formats in order to have a + # clean slate for the next test case. + def cleanup(): + self.runCmd('type format clear', check=False) + self.runCmd('type summary clear', check=False) + self.runCmd('type filter clear', check=False) + self.runCmd('type synth clear', check=False) + self.runCmd( + "settings set target.max-children-count 256", + check=False) + + # Execute the cleanup function during test case tear down. + self.addTearDownHook(cleanup) + + self.runCmd("frame variable numbers_list --show-types") + + self.runCmd("type format add -f hex int") + + self.expect("frame variable numbers_list --raw", matching=False, + substrs=['size=0', + '{}']) + self.expect( + "frame variable &numbers_list._M_impl._M_node --raw", + matching=False, + substrs=[ + 'size=0', + '{}']) + + self.expect("frame variable numbers_list", + substrs=['size=0', + '{}']) + + self.expect("p numbers_list", + substrs=['size=0', + '{}']) + + self.runCmd("n") + + self.expect("frame variable numbers_list", + substrs=['size=1', + '[0] = ', + '0x12345678']) + + self.runCmd("n") + self.runCmd("n") + self.runCmd("n") + + self.expect("frame variable numbers_list", + substrs=['size=4', + '[0] = ', + '0x12345678', + '[1] =', + '0x11223344', + '[2] =', + '0xbeeffeed', + '[3] =', + '0x00abba00']) + + self.runCmd("n") + self.runCmd("n") + + self.expect("frame variable numbers_list", + substrs=['size=6', + '[0] = ', + '0x12345678', + '0x11223344', + '0xbeeffeed', + '0x00abba00', + '[4] =', + '0x0abcdef0', + '[5] =', + '0x0cab0cab']) + + self.expect("p numbers_list", + substrs=['size=6', + '[0] = ', + '0x12345678', + '0x11223344', + '0xbeeffeed', + '0x00abba00', + '[4] =', + '0x0abcdef0', + '[5] =', + '0x0cab0cab']) + + # check access-by-index + self.expect("frame variable numbers_list[0]", + substrs=['0x12345678']) + self.expect("frame variable numbers_list[1]", + substrs=['0x11223344']) + + # but check that expression does not rely on us + self.expect("expression numbers_list[0]", matching=False, error=True, + substrs=['0x12345678']) + + # check that MightHaveChildren() gets it right + self.assertTrue( + self.frame().FindVariable("numbers_list").MightHaveChildren(), + "numbers_list.MightHaveChildren() says False for non empty!") + + self.runCmd("n") + + self.expect("frame variable numbers_list", + substrs=['size=0', + '{}']) + + self.runCmd("n") + self.runCmd("n") + self.runCmd("n") + self.runCmd("n") + + self.expect("frame variable numbers_list", + substrs=['size=4', + '[0] = ', '1', + '[1] = ', '2', + '[2] = ', '3', + '[3] = ', '4']) + + self.runCmd("type format delete int") + + self.runCmd("n") + + self.expect("frame variable text_list", + substrs=['size=0', + '{}']) + + lldbutil.run_break_set_by_file_and_line( + self, "main.cpp", self.final_line, num_expected_locations=-1) + + self.runCmd("c", RUN_SUCCEEDED) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + self.expect("frame variable text_list", + substrs=['size=4', + '[0]', 'goofy', + '[1]', 'is', + '[2]', 'smart', + '[3]', '!!!']) + + self.expect("p text_list", + substrs=['size=4', + '\"goofy\"', + '\"is\"', + '\"smart\"', + '\"!!!\"']) + + # check access-by-index + self.expect("frame variable text_list[0]", + substrs=['goofy']) + self.expect("frame variable text_list[3]", + substrs=['!!!']) + + # but check that expression does not rely on us + self.expect("expression text_list[0]", matching=False, error=True, + substrs=['goofy']) + + # check that MightHaveChildren() gets it right + self.assertTrue( + self.frame().FindVariable("text_list").MightHaveChildren(), + "text_list.MightHaveChildren() says False for non empty!") + + @add_test_categories(["libstdcxx"]) + def test_with_run_command_libstdcpp(self): + self.do_test_with_run_command(USE_LIBSTDCPP) + + @add_test_categories(["libc++"]) + def test_with_run_command_libcpp(self): + self.do_test_with_run_command(USE_LIBCPP) \ No newline at end of file diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/Makefile b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/TestDataFormatterGenericListLoop.py @@ -0,0 +1,77 @@ +""" +Test that the debugger handles loops in std::list (which can appear as a result of e.g. memory +corruption). +""" + + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +USE_LIBSTDCPP = "USE_LIBSTDCPP" +USE_LIBCPP = "USE_LIBCPP" + +class GenericListDataFormatterTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + def do_test_with_run_command(self, stdlib_type): + self.build(dictionary={stdlib_type: "1"}) + exe = self.getBuildArtifact("a.out") + target = self.dbg.CreateTarget(exe) + self.assertTrue(target and target.IsValid(), "Target is valid") + + file_spec = lldb.SBFileSpec("main.cpp", False) + breakpoint1 = target.BreakpointCreateBySourceRegex( + '// Set break point at this line.', file_spec) + self.assertTrue(breakpoint1 and breakpoint1.IsValid()) + breakpoint2 = target.BreakpointCreateBySourceRegex( + '// Set second break point at this line.', file_spec) + self.assertTrue(breakpoint2 and breakpoint2.IsValid()) + + # Run the program, it should stop at breakpoint 1. + process = target.LaunchSimple( + None, None, self.get_process_working_directory()) + self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID) + self.assertEqual( + len(lldbutil.get_threads_stopped_at_breakpoint(process, breakpoint1)), 1) + + # verify our list is displayed correctly + self.expect( + "frame variable numbers_list", + substrs=[ + '[0] = 1', + '[1] = 2', + '[2] = 3', + '[3] = 4', + '[5] = 6']) + + # Continue to breakpoint 2. + process.Continue() + self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID) + self.assertEqual( + len(lldbutil.get_threads_stopped_at_breakpoint(process, breakpoint2)), 1) + + # The list is now inconsistent. However, we should be able to get the first three + # elements at least (and most importantly, not crash). + self.expect( + "frame variable numbers_list", + substrs=[ + '[0] = 1', + '[1] = 2', + '[2] = 3']) + + # Run to completion. + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited, PROCESS_EXITED) + + @add_test_categories(["libstdcxx"]) + def test_with_run_command_libstdcpp(self): + self.do_test_with_run_command(USE_LIBSTDCPP) + + @add_test_categories(["libc++"]) + def test_with_run_command_libcpp(self): + self.do_test_with_run_command(USE_LIBCPP) \ No newline at end of file diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/loop/main.cpp @@ -0,0 +1,28 @@ +// Evil hack: To simulate memory corruption, we want to fiddle with some internals of std::list. +// Make those accessible to us. +#define private public +#define protected public + +#include +#include +#include + +int main() +{ + std::list numbers_list{1,2,3,4,5,6,7,8,9,10}; + printf("// Set break point at this line."); + std::list::iterator it1=numbers_list.begin(); + while (it1 != numbers_list.end()){ + *it1++; + } + *it1++; + *it1++; + *it1++; + assert(*it1 == 3); + *it1++; + *it1++; + assert(*it1 == 5); + + // Any attempt to free the list will probably crash the program. Let's just leak it. + return 0; // Set second break point at this line. +} diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp new file mode 100644 --- /dev/null +++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/list/main.cpp @@ -0,0 +1,34 @@ +#include +#include + +typedef std::list int_list; +typedef std::list string_list; + +int main() +{ + int_list numbers_list; + + numbers_list.push_back(0x12345678); // Set break point at this line. + numbers_list.push_back(0x11223344); + numbers_list.push_back(0xBEEFFEED); + numbers_list.push_back(0x00ABBA00); + numbers_list.push_back(0x0ABCDEF0); + numbers_list.push_back(0x0CAB0CAB); + + numbers_list.clear(); + + numbers_list.push_back(1); + numbers_list.push_back(2); + numbers_list.push_back(3); + numbers_list.push_back(4); + + string_list text_list; + text_list.push_back(std::string("goofy")); // Optional break point at this line. + text_list.push_back(std::string("is")); + text_list.push_back(std::string("smart")); + + text_list.push_back(std::string("!!!")); + + return 0; // Set final break point at this line. +} +