Author: Med Ismail Bennani Date: 2026-04-07T11:55:30-07:00 New Revision: a030dfb53b21bd900aeed7830f408a79501418dc
URL: https://github.com/llvm/llvm-project/commit/a030dfb53b21bd900aeed7830f408a79501418dc DIFF: https://github.com/llvm/llvm-project/commit/a030dfb53b21bd900aeed7830f408a79501418dc.diff LOG: [lldb] Add --provider option to thread backtrace (#181071) Added: lldb/test/API/functionalities/scripted_frame_provider/thread_filter/Makefile lldb/test/API/functionalities/scripted_frame_provider/thread_filter/TestFrameProviderThreadFilter.py lldb/test/API/functionalities/scripted_frame_provider/thread_filter/frame_provider.py lldb/test/API/functionalities/scripted_frame_provider/thread_filter/main.cpp Modified: lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h lldb/include/lldb/Target/Target.h lldb/include/lldb/Target/Thread.h lldb/include/lldb/lldb-enumerations.h lldb/source/Commands/CommandObjectTarget.cpp lldb/source/Commands/CommandObjectThread.cpp lldb/source/Commands/Options.td lldb/source/Interpreter/CommandInterpreter.cpp lldb/source/Target/Target.cpp lldb/source/Target/Thread.cpp lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/TestFrameProviderPassThroughPrefix.py lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/frame_provider.py Removed: ################################################################################ diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h index 701491b71fb27..600b9639c4d4d 100644 --- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h +++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h @@ -258,6 +258,7 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = { { lldb::eArgTypeFilename, "filename", lldb::eDiskFileCompletion, {}, { nullptr, false }, "The name of a file (can include path)." }, { lldb::eArgTypeFormat, "format", lldb::CompletionType::eNoCompletion, {}, { FormatHelpTextCallback, true }, nullptr }, { lldb::eArgTypeFrameIndex, "frame-index", lldb::eFrameIndexCompletion, {}, { nullptr, false }, "Index into a thread's list of frames." }, + { lldb::eArgTypeFrameProviderIDRange, "frame-provider-id-range", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "A single frame provider ID, a range of IDs (e.g., '0', '0-2', '0 to 2'), or '*'/'all' to show every provider. ID 0 is the base unwinder, 1+ are synthetic providers." }, { lldb::eArgTypeFullName, "fullname", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "Help text goes here." }, { lldb::eArgTypeFunctionName, "function-name", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of a function." }, { lldb::eArgTypeFunctionOrSymbol, "function-or-symbol", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of a function or symbol." }, diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index 16f21ad9d8271..25ab90e906aeb 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -41,6 +41,7 @@ #include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timeout.h" #include "lldb/lldb-public.h" +#include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringRef.h" namespace lldb_private { @@ -813,7 +814,7 @@ class Target : public std::enable_shared_from_this<Target>, void ClearScriptedFrameProviderDescriptors(); /// Get all scripted frame provider descriptors for this target. - const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> & + const llvm::MapVector<uint32_t, ScriptedFrameProviderDescriptor> & GetScriptedFrameProviderDescriptors() const; protected: @@ -1821,9 +1822,10 @@ class Target : public std::enable_shared_from_this<Target>, TypeSystemMap m_scratch_type_system_map; /// Map of scripted frame provider descriptors for this target. - /// Keys are the provider descriptors ids, values are the descriptors. - /// Used to initialize frame providers for new threads. - llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> + /// Keys are the provider descriptor IDs, values are the descriptors. + /// Insertion order is preserved so that equal-priority providers chain + /// in registration order. + llvm::MapVector<uint32_t, ScriptedFrameProviderDescriptor> m_frame_provider_descriptors; mutable std::recursive_mutex m_frame_provider_descriptors_mutex; diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h index c698dd8015885..8b69e83d94ca4 100644 --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -1324,6 +1324,25 @@ class Thread : public std::enable_shared_from_this<Thread>, /// Returns true if any host thread is currently inside a provider. bool IsAnyProviderActive(); + /// Get the ordered chain of provider descriptors and their frame list IDs. + /// + /// Each element is a pair of: + /// - \b ScriptedFrameProviderDescriptor: metadata for the provider + /// (class name, description, priority, thread specs). + /// - \b frame_list_id_t: the sequential frame list identifier assigned + /// to that provider in the chain (1 for the first provider, 2 for the + /// second, etc.). ID 0 is reserved for the base unwinder and is never + /// present in this vector. + /// + /// The vector is ordered by provider chain position (registration order + /// adjusted by priority). It persists across \c ClearStackFrames() so that + /// provider IDs remain stable for the lifetime of the thread. + const std::vector< + std::pair<ScriptedFrameProviderDescriptor, lldb::frame_list_id_t>> & + GetProviderChainIds() const { + return m_provider_chain_ids; + } + protected: friend class ThreadPlan; friend class ThreadList; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 11b7808bb1b61..4d31c4db5c20b 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -596,6 +596,7 @@ enum CommandArgumentType { eArgTypeFilename, eArgTypeFormat, eArgTypeFrameIndex, + eArgTypeFrameProviderIDRange, eArgTypeFullName, eArgTypeFunctionName, eArgTypeFunctionOrSymbol, diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index f9b794d5124c8..aaed3051b0898 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -5472,7 +5472,7 @@ class CommandObjectTargetFrameProviderList : public CommandObjectParsed { return; } - result.AppendMessageWithFormat("%u frame provider(s) registered:\n\n", + result.AppendMessageWithFormat("%zu frame provider(s) registered:\n\n", descriptors.size()); for (const auto &entry : descriptors) { diff --git a/lldb/source/Commands/CommandObjectThread.cpp b/lldb/source/Commands/CommandObjectThread.cpp index 6786741cd04b6..87dd28445365d 100644 --- a/lldb/source/Commands/CommandObjectThread.cpp +++ b/lldb/source/Commands/CommandObjectThread.cpp @@ -92,6 +92,76 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { case 'u': m_filtered_backtrace = false; break; + case 'p': { + // Parse provider range using same format as breakpoint IDs. + // Supports: "N", "N-M", "N to M", "*", "all". + llvm::StringRef trimmed = option_arg.trim(); + if (trimmed == "*" || trimmed.equals_insensitive("all")) { + m_show_all_providers = true; + m_provider_specific_backtrace = true; + break; + } + + std::string option_lower = option_arg.lower(); + static constexpr llvm::StringLiteral range_specifiers[] = {"-", "to"}; + + llvm::StringRef range_from; + llvm::StringRef range_to; + bool is_range = false; + + // Try to find a range specifier. + for (auto specifier : range_specifiers) { + size_t idx = option_lower.find(specifier); + if (idx == std::string::npos) + continue; + + range_from = llvm::StringRef(option_lower).take_front(idx).trim(); + range_to = llvm::StringRef(option_lower) + .drop_front(idx + specifier.size()) + .trim(); + + if (!range_from.empty() && !range_to.empty()) { + is_range = true; + break; + } + } + + if (is_range) { + // Parse both start and end IDs. + if (range_from.getAsInteger(0, m_provider_start_id)) { + error = Status::FromErrorStringWithFormat( + "invalid start provider ID for option '%c': %s", short_option, + range_from.data()); + break; + } + if (range_to.getAsInteger(0, m_provider_end_id)) { + error = Status::FromErrorStringWithFormat( + "invalid end provider ID for option '%c': %s", short_option, + range_to.data()); + break; + } + + // Validate range. + if (m_provider_start_id > m_provider_end_id) { + error = Status::FromErrorStringWithFormat( + "invalid provider range for option '%c': start ID %u > end " + "ID %u", + short_option, m_provider_start_id, m_provider_end_id); + break; + } + } else { + // Single provider ID. + if (option_arg.getAsInteger(0, m_provider_start_id)) { + error = Status::FromErrorStringWithFormat( + "invalid provider ID for option '%c': %s", short_option, + option_arg.data()); + break; + } + m_provider_end_id = m_provider_start_id; + } + + m_provider_specific_backtrace = true; + } break; default: llvm_unreachable("Unimplemented option"); } @@ -103,10 +173,14 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { m_start = 0; m_extended_backtrace = false; m_filtered_backtrace = true; + m_provider_start_id = 0; + m_provider_end_id = 0; + m_provider_specific_backtrace = false; + m_show_all_providers = false; } llvm::ArrayRef<OptionDefinition> GetDefinitions() override { - return llvm::ArrayRef(g_thread_backtrace_options); + return g_thread_backtrace_options; } // Instance variables to hold the values for command options. @@ -114,6 +188,10 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { uint32_t m_start; bool m_extended_backtrace; bool m_filtered_backtrace; + lldb::frame_list_id_t m_provider_start_id; + lldb::frame_list_id_t m_provider_end_id; + bool m_provider_specific_backtrace; + bool m_show_all_providers; }; CommandObjectThreadBacktrace(CommandInterpreter &interpreter) @@ -124,6 +202,9 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { "Use the thread-index \"all\" to see all threads.\n" "Use the thread-index \"unique\" to see threads grouped by unique " "call stacks.\n" + "Use '--provider <id>' or '--provider <start>-<end>' to view " + "synthetic frame providers (0=base unwinder, 1+=synthetic). " + "Range specifiers '-', 'to', 'To', 'TO' are supported.\n" "Use 'settings set frame-format' to customize the printing of " "frames in the backtrace and 'settings set thread-format' to " "customize the thread header.\n" @@ -227,13 +308,115 @@ class CommandObjectThreadBacktrace : public CommandObjectIterateOverThreads { } Thread *thread = thread_sp.get(); - Stream &strm = result.GetOutputStream(); - // Only dump stack info if we processing unique stacks. - const bool only_stacks = m_unique_stacks; + // Check if provider filtering is requested. + if (m_options.m_provider_specific_backtrace) { + // Disallow 'bt --provider' from within a scripted frame provider. + // A provider's get_frame_at_index running 'bt --provider' would + // try to evaluate the very provider that is mid-construction, + // leading to infinite recursion. + if (thread->IsAnyProviderActive()) { + result.AppendErrorWithFormat( + "cannot use '--provider' option while a scripted frame provider is " + "being constructed on this thread\n"); + return false; + } + + // Print thread status header, like regular bt. This also ensures the + // frame list is initialized and any providers are loaded. + thread->GetStatus(strm, /*start_frame=*/0, /*num_frames=*/0, + /*num_frames_with_source=*/0, /*stop_format=*/true, + /*show_hidden=*/false, /*only_stacks=*/false); + + if (m_options.m_show_all_providers) { + // Show all providers: unwinder (0) through the last in the chain. + m_options.m_provider_start_id = 0; + const auto &chain = thread->GetProviderChainIds(); + m_options.m_provider_end_id = chain.empty() ? 0 : chain.back().second; + } + + // Provider filter mode: show sequential views for each provider in range. + bool first_provider = true; + for (lldb::frame_list_id_t provider_id = m_options.m_provider_start_id; + provider_id <= m_options.m_provider_end_id; ++provider_id) { + + // Get the frame list for this provider. + lldb::StackFrameListSP frame_list_sp = + thread->GetFrameListByIdentifier(provider_id); + + if (!frame_list_sp) { + // Provider doesn't exist - skip silently. + continue; + } + + // Add blank line between providers for readability. + if (!first_provider) + strm.PutChar('\n'); + first_provider = false; + + // Print provider header. + strm.Printf("=== Provider %u", provider_id); + + // Get provider metadata for header. + if (provider_id == 0) { + strm.Printf(": Base Unwinder ===\n"); + } else { + // Find the descriptor in the provider chain. + const auto &provider_chain = thread->GetProviderChainIds(); + std::string provider_name = "Unknown"; + std::string provider_desc; + std::optional<uint32_t> provider_priority; + + for (const auto &[descriptor, id] : provider_chain) { + if (id == provider_id) { + provider_name = descriptor.GetName().str(); + provider_desc = descriptor.GetDescription(); + provider_priority = descriptor.GetPriority(); + break; + } + } + + strm.Printf(": %s", provider_name.c_str()); + if (provider_priority.has_value()) { + strm.Printf(" (priority: %u)", *provider_priority); + } + strm.Printf(" ===\n"); + + if (!provider_desc.empty()) { + strm.Printf("Description: %s\n", provider_desc.c_str()); + } + } + + // Print the backtrace for this provider. + const uint32_t num_frames_with_source = 0; + const StackFrameSP selected_frame_sp = + thread->GetSelectedFrame(DoNoSelectMostRelevantFrame); + const char *selected_frame_marker = selected_frame_sp ? "->" : nullptr; + + size_t num_frames = frame_list_sp->GetStatus( + strm, m_options.m_start, m_options.m_count, + /*show_frame_info=*/true, num_frames_with_source, + /*show_unique=*/false, + /*show_hidden=*/!m_options.m_filtered_backtrace, + selected_frame_marker); + + if (num_frames == 0) { + strm.Printf("(No frames available)\n"); + } + } - // Don't show source context when doing backtraces. + if (first_provider) { + result.AppendErrorWithFormat("no provider found in range %u-%u\n", + m_options.m_provider_start_id, + m_options.m_provider_end_id); + return false; + } + return true; + } + + // Original behavior: show default backtrace. + const bool only_stacks = m_unique_stacks; const uint32_t num_frames_with_source = 0; const bool stop_format = true; if (!thread->GetStatus(strm, m_options.m_start, m_options.m_count, diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 78bde66199659..54e9f022880c4 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -1919,6 +1919,15 @@ let Command = "thread backtrace" in { Group<1>, Desc<"Do not filter out frames according " "to installed frame recognizers">; + def thread_backtrace_provider + : Option<"provider", "p">, + Group<1>, + Arg<"FrameProviderIDRange">, + Desc<"Show backtrace from specific frame provider(s). " + "Use a single ID (e.g., '2') for one provider, " + "a range (e.g., '0-2', '0 to 2') for multiple, " + "or '*'/'all' to show every provider. " + "ID 0 is the base unwinder, 1+ are synthetic providers.">; } let Command = "thread step scope" in { diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index eeb1ae0ff3eb8..6218591b3a20b 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -424,6 +424,15 @@ void CommandInterpreter::Initialize() { if (cmd_obj_sp) AddAlias("bt", cmd_obj_sp)->SetSyntax(cmd_obj_sp->GetSyntax()); + cmd_obj_sp = GetCommandSPExact("thread backtrace"); + if (cmd_obj_sp) { + if (auto *sys_bt = AddAlias("sys_bt", cmd_obj_sp, "--provider 0")) { + sys_bt->SetHelp("Show the base unwinder backtrace (without frame " + "providers). Equivalent to 'thread backtrace " + "--provider 0'."); + } + } + cmd_obj_sp = GetCommandSPExact("target create"); if (cmd_obj_sp) AddAlias("file", cmd_obj_sp); diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index a45945e1107cb..cba04a67a00cc 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -3762,7 +3762,7 @@ void Target::ClearScriptedFrameProviderDescriptors() { InvalidateThreadFrameProviders(); } -const llvm::DenseMap<uint32_t, ScriptedFrameProviderDescriptor> & +const llvm::MapVector<uint32_t, ScriptedFrameProviderDescriptor> & Target::GetScriptedFrameProviderDescriptors() const { std::lock_guard<std::recursive_mutex> guard( m_frame_provider_descriptors_mutex); diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 60785d5c230db..fab9d2f3eada5 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1538,15 +1538,16 @@ StackFrameListSP Thread::GetStackFrameList() { thread_descriptors.push_back(&descriptor); } - // Sort by priority (lower number = higher priority). - llvm::sort(thread_descriptors, - [](const ScriptedFrameProviderDescriptor *a, - const ScriptedFrameProviderDescriptor *b) { - // nullopt (no priority) sorts last (UINT32_MAX). - uint32_t priority_a = a->GetPriority().value_or(UINT32_MAX); - uint32_t priority_b = b->GetPriority().value_or(UINT32_MAX); - return priority_a < priority_b; - }); + // Stable sort by priority so equal-priority providers keep + // their registration (insertion) order. + llvm::stable_sort( + thread_descriptors, [](const ScriptedFrameProviderDescriptor *a, + const ScriptedFrameProviderDescriptor *b) { + // nullopt (no priority) sorts last (UINT32_MAX). + uint32_t priority_a = a->GetPriority().value_or(UINT32_MAX); + uint32_t priority_b = b->GetPriority().value_or(UINT32_MAX); + return priority_a < priority_b; + }); // Load ALL matching providers in priority order. for (const auto *descriptor : thread_descriptors) { @@ -1603,8 +1604,14 @@ Thread::GetFrameListByIdentifier(lldb::frame_list_id_t id) { auto it = m_frame_lists_by_id.find(id); if (it != m_frame_lists_by_id.end()) { - return it->second.lock(); - } + auto sp = it->second.lock(); + LLDB_LOG(GetLog(LLDBLog::Thread), + "GetFrameListByIdentifier({0}): found={1}, locked={2}", id, true, + sp != nullptr); + return sp; + } + LLDB_LOG(GetLog(LLDBLog::Thread), "GetFrameListByIdentifier({0}): found={1}", + id, false); return nullptr; } @@ -1630,6 +1637,13 @@ llvm::Error Thread::LoadScriptedFrameProvider( input_frames = std::make_shared<SyntheticStackFrameList>( *this, last_provider_frames, m_prev_frames_sp, true, last_provider, last_id); + // Register this intermediate frame list so 'bt --provider <id>' can + // show each provider's output independently. + m_frame_lists_by_id.insert({last_id, input_frames}); + LLDB_LOG(GetLog(LLDBLog::Thread), + "Registered intermediate frame list for provider id={0}, " + "use_count={1}", + last_id, input_frames.use_count()); } // Protect provider construction (__init__) from re-entrancy. If the @@ -1644,12 +1658,11 @@ llvm::Error Thread::LoadScriptedFrameProvider( if (!provider_or_err) return provider_or_err.takeError(); - if (m_next_provider_id == std::numeric_limits<lldb::frame_list_id_t>::max()) + lldb::frame_list_id_t provider_id = m_next_provider_id++; + if (m_next_provider_id == + 0) // Wrapped past max; skip 0 (reserved for unwinder). m_next_provider_id = 1; - else - m_next_provider_id++; - lldb::frame_list_id_t provider_id = m_next_provider_id; m_frame_providers.insert({provider_id, *provider_or_err}); // Add to the provider chain. @@ -1682,6 +1695,7 @@ void Thread::ClearScriptedFrameProvider() { std::lock_guard<std::recursive_mutex> guard(m_frame_mutex); m_frame_providers.clear(); m_provider_chain_ids.clear(); + m_frame_lists_by_id.clear(); m_next_provider_id = 1; // Reset counter. m_unwinder_frames_sp.reset(); m_curr_frames_sp.reset(); @@ -1718,10 +1732,11 @@ void Thread::ClearStackFrames() { m_curr_frames_sp.reset(); m_unwinder_frames_sp.reset(); - // Clear the provider instances, but keep the chain configuration - // (m_provider_chain_ids and m_next_provider_id) so provider IDs - // remain stable across ClearStackFrames() calls. + // Clear the provider instances and reset the ID counter, but keep the + // chain configuration (m_provider_chain_ids) so providers are re-loaded + // with consistent IDs on the next GetStackFrameList() call. m_frame_providers.clear(); + m_next_provider_id = 1; m_frame_lists_by_id.clear(); m_extended_info.reset(); m_extended_info_fetched = false; diff --git a/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/TestFrameProviderPassThroughPrefix.py b/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/TestFrameProviderPassThroughPrefix.py index 43994ef1a8cd4..071a7382dcab9 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/TestFrameProviderPassThroughPrefix.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/TestFrameProviderPassThroughPrefix.py @@ -13,11 +13,17 @@ class FrameProviderPassThroughPrefixTestCase(TestBase): NO_DEBUG_INFO_TESTCASE = True + # The frame list IDs used by 'bt --provider' are internal sequential IDs: + # 0 = base unwinder, 1 = first provider, 2 = second provider, etc. + # These are NOT the descriptor IDs returned by RegisterScriptedFrameProvider. + UNWINDER_FRAME_LIST_ID = 0 + FIRST_PROVIDER_FRAME_LIST_ID = 1 + SECOND_PROVIDER_FRAME_LIST_ID = 2 + def setUp(self): TestBase.setUp(self) self.source = "main.c" - @expectedFailureAll(oslist=["linux"], archs=["arm$"]) @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") def test_pass_through_with_prefix(self): """ @@ -83,6 +89,397 @@ def test_pass_through_with_prefix(self): f"Frame {i} should be '{expected}' after provider", ) + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_shows_unwinder_frames(self): + """ + Test that 'bt --provider 0' shows the base unwinder frames + (without the provider prefix) even after a provider is registered. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), f"Should register provider successfully: {error}" + ) + + # 'bt --provider 0' should show the base unwinder frames without prefix. + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider 0", result) + self.assertTrue(result.Succeeded(), "bt --provider 0 should succeed") + output = result.GetOutput() + self.assertIn("Base Unwinder", output) + self.assertIn("baz", output) + self.assertNotIn("my_custom_", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_shows_provider_frames(self): + """ + Test that 'bt --provider <id>' shows the provider's transformed frames + with the prefix applied. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), f"Should register provider successfully: {error}" + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # 'bt --provider <id>' should show the provider's prefixed frames. + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + f"bt --provider {self.FIRST_PROVIDER_FRAME_LIST_ID}", result + ) + self.assertTrue( + result.Succeeded(), + f"bt --provider {self.FIRST_PROVIDER_FRAME_LIST_ID} should succeed", + ) + output = result.GetOutput() + self.assertIn("PrefixPassThroughProvider", output) + self.assertIn("my_custom_baz", output) + self.assertIn("my_custom_bar", output) + self.assertIn("my_custom_foo", output) + self.assertIn("my_custom_main", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_range(self): + """ + Test that 'bt --provider 0-<id>' shows both the base unwinder + and provider frames sequentially. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), f"Should register provider successfully: {error}" + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # 'bt --provider 0-<id>' should show both sections. + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + f"bt --provider 0-{self.FIRST_PROVIDER_FRAME_LIST_ID}", result + ) + self.assertTrue( + result.Succeeded(), + f"bt --provider 0-{self.FIRST_PROVIDER_FRAME_LIST_ID} should succeed", + ) + output = result.GetOutput() + # Should contain both the base unwinder and the provider sections. + self.assertIn("Base Unwinder", output) + self.assertIn("PrefixPassThroughProvider", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_range_not_starting_at_zero(self): + """ + Test that 'bt --provider <id>-<id>' works when the range doesn't + include the base unwinder (provider 0). + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), f"Should register provider successfully: {error}" + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Range that only includes the provider, not the base unwinder. + fid = self.FIRST_PROVIDER_FRAME_LIST_ID + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + f"bt --provider {fid}-{fid}", result + ) + self.assertTrue( + result.Succeeded(), + f"bt --provider {fid}-{fid} should succeed", + ) + output = result.GetOutput() + self.assertNotIn("Base Unwinder", output) + self.assertIn("PrefixPassThroughProvider", output) + self.assertIn("my_custom_baz", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_range_with_to_separator(self): + """ + Test that 'bt --provider 0 to <id>' works with the 'to' separator. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), f"Should register provider successfully: {error}" + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Use 'to' separator instead of '-'. + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand( + f"bt --provider '0 to {self.FIRST_PROVIDER_FRAME_LIST_ID}'", result + ) + self.assertTrue( + result.Succeeded(), + f"bt --provider '0 to {self.FIRST_PROVIDER_FRAME_LIST_ID}' should succeed", + ) + output = result.GetOutput() + self.assertIn("Base Unwinder", output) + self.assertIn("PrefixPassThroughProvider", output) + + def test_bt_provider_invalid_id(self): + """ + Test that 'bt --provider <invalid>' fails with an error when the + provider ID doesn't exist. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider 999", result) + self.assertFalse(result.Succeeded(), "bt --provider 999 should fail") + + def test_bt_provider_invalid_range(self): + """ + Test that 'bt --provider N-M' where N > M fails with an error + about an invalid range. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider 5-2", result) + self.assertFalse(result.Succeeded(), "bt --provider 5-2 should fail") + self.assertIn("invalid provider range", result.GetError()) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_star_shows_all(self): + """ + Test that 'bt --provider *' shows all providers including the + base unwinder and any registered providers. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Should register provider: {error}") + + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider '*'", result) + self.assertTrue(result.Succeeded(), "bt --provider '*' should succeed") + output = result.GetOutput() + self.assertIn("Base Unwinder", output) + self.assertIn("PrefixPassThroughProvider", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_all_shows_all(self): + """ + Test that 'bt --provider all' shows all providers including the + base unwinder and any registered providers. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Should register provider: {error}") + + target.RegisterScriptedFrameProvider( + "frame_provider.UpperCasePassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Should register upper provider: {error}") + + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider all", result) + self.assertTrue(result.Succeeded(), "bt --provider all should succeed") + output = result.GetOutput() + self.assertIn("Base Unwinder", output) + self.assertIn("PrefixPassThroughProvider", output) + self.assertIn("UpperCasePassThroughProvider", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_multiple_providers(self): + """ + Test 'bt --provider' with two chained providers. Register + PrefixPassThroughProvider (adds 'my_custom_' prefix) then + UpperCasePassThroughProvider (upper-cases names). Verify each + provider's view is correct and that ranges across providers work. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + # Register first provider: adds 'my_custom_' prefix. + error = lldb.SBError() + target.RegisterScriptedFrameProvider( + "frame_provider.PrefixPassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Should register prefix provider: {error}") + + # Register second provider: upper-cases everything. + target.RegisterScriptedFrameProvider( + "frame_provider.UpperCasePassThroughProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Should register upper provider: {error}") + + # bt --provider 0: base unwinder, original names. + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider 0", result) + self.assertTrue(result.Succeeded()) + output = result.GetOutput() + self.assertIn("Base Unwinder", output) + self.assertIn("baz", output) + + # bt --provider all: should show all providers. + result = lldb.SBCommandReturnObject() + self.dbg.GetCommandInterpreter().HandleCommand("bt --provider all", result) + self.assertTrue(result.Succeeded()) + output = result.GetOutput() + self.assertIn("Base Unwinder", output) + self.assertIn("UpperCasePassThroughProvider", output) + # UpperCase wraps Prefix (registration order is preserved), so the + # outermost output should have fully upper-cased prefixed names. + self.assertIn("MY_CUSTOM_BAZ", output) + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_star_from_within_provider(self): + """ + Test that running 'bt --provider *' re-entrantly from within a + scripted frame provider's get_frame_at_index does not deadlock + or crash. + + BtProviderStarProvider runs 'bt --provider *' on its first + get_frame_at_index call and stores the output, then passes through + all frames with a 'reentrant_' prefix. We verify: + 1. The provider completes without hanging. + 2. Frame 0 has the 'reentrant_' prefix. + """ + self.build() + + target, process, thread, _ = lldbutil.run_to_source_breakpoint( + self, "break here", lldb.SBFileSpec(self.source) + ) + + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + provider_id = target.RegisterScriptedFrameProvider( + "frame_provider.BtProviderStarProvider", + lldb.SBStructuredData(), + error, + ) + self.assertTrue( + error.Success(), f"Should register provider successfully: {error}" + ) + self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") + + # Access frame 0 to trigger the provider (and the re-entrant bt call). + # Only frame 0 is checked because the re-entrant HandleCommand resets + # the selected frame on the unwinder list, which corrupts subsequent + # frame lookups via inlined depth adjustments. + frame = thread.GetFrameAtIndex(0) + self.assertEqual( + frame.GetFunctionName(), + "reentrant_baz", + "Frame 0 should be 'reentrant_baz' after provider", + ) + def test_provider_receives_parent_frames(self): """ Test that the provider's input_frames come from the parent @@ -116,11 +513,10 @@ def test_provider_receives_parent_frames(self): ) self.assertNotEqual(provider_id, 0, "Provider ID should be non-zero") - # No frame should contain 'danger_will_robinson_' — that would mean the provider - # was handed its own output list instead of the parent list. - expected_names = ["baz", "bar", "foo", "main"] - prefix = "my_custom_" - for i, name in enumerate(expected_names): + # No frame should contain 'danger_will_robinson_' — that would mean + # the provider was handed its own output list instead of the parent. + num_frames = thread.GetNumFrames() + for i in range(num_frames): frame = thread.GetFrameAtIndex(i) actual = frame.GetFunctionName() self.assertFalse( @@ -128,9 +524,3 @@ def test_provider_receives_parent_frames(self): f"Frame {i}: provider got its own output list " f"(expected bare parent frames, got '{actual}')", ) - expected = prefix + name - self.assertEqual( - actual, - expected, - f"Frame {i} should be '{expected}' after provider", - ) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/frame_provider.py index 5c82b5e43689c..18344208cac56 100644 --- a/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/frame_provider.py +++ b/lldb/test/API/functionalities/scripted_frame_provider/pass_through_prefix/frame_provider.py @@ -14,19 +14,18 @@ class PrefixedFrame(ScriptedFrame): """A frame that wraps a real frame but prefixes the function name.""" - def __init__(self, thread, idx, pc, function_name, prefix): + def __init__(self, thread, idx, function_name, prefix): args = lldb.SBStructuredData() super().__init__(thread, args) self.idx = idx - self.pc = pc self.function_name = prefix + function_name def get_id(self): return self.idx def get_pc(self): - return self.pc + return 0 def get_function_name(self): return self.function_name @@ -63,11 +62,70 @@ def get_frame_at_index(self, idx): if idx < len(self.input_frames): frame = self.input_frames[idx] function_name = frame.GetFunctionName() - pc = frame.GetPC() - return PrefixedFrame(self.thread, idx, pc, function_name, self.PREFIX) + return PrefixedFrame(self.thread, idx, function_name, self.PREFIX) return None +class UpperCasePassThroughProvider(ScriptedFrameProvider): + """ + Provider that passes through every frame from its parent StackFrameList + but upper-cases each function name. + + When chained after PrefixPassThroughProvider, the result should be + e.g. 'MY_CUSTOM_BAZ'. + """ + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + return "Provider that upper-cases all function names" + + def get_frame_at_index(self, idx): + if idx < len(self.input_frames): + frame = self.input_frames[idx] + function_name = frame.GetFunctionName() + return PrefixedFrame(self.thread, idx, function_name.upper(), "") + return None + + +class BtProviderStarProvider(ScriptedFrameProvider): + """ + Provider that runs 'bt --provider *' from within get_frame_at_index + to verify that re-entrant provider queries don't deadlock or crash. + + On the first call to get_frame_at_index, it runs the command and stores + the output. It then passes through all frames with a 'reentrant_' prefix. + """ + + PREFIX = "reentrant_" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + self.bt_provider_star_output = None + + @staticmethod + def get_description(): + return "Provider that runs 'bt --provider *' during frame construction" + + def get_frame_at_index(self, idx): + if idx >= len(self.input_frames): + return None + + # On the first frame request, run 'bt --provider *' re-entrantly. + if self.bt_provider_star_output is None: + debugger = self.target.GetDebugger() + ci = debugger.GetCommandInterpreter() + result = lldb.SBCommandReturnObject() + ci.HandleCommand("bt --provider '*'", result) + self.bt_provider_star_output = result.GetOutput() or "" + + frame = self.input_frames[idx] + function_name = frame.GetFunctionName() + return PrefixedFrame(self.thread, idx, function_name, self.PREFIX) + + class ValidatingPrefixProvider(ScriptedFrameProvider): """ Provider that prefixes function names AND validates it receives the @@ -94,7 +152,6 @@ def get_frame_at_index(self, idx): frame = self.input_frames[idx] function_name = frame.GetFunctionName() - pc = frame.GetPC() # For frames after the first, peek at the younger (already-provided) # frame in input_frames. If it already has our prefix, we were handed @@ -103,7 +160,7 @@ def get_frame_at_index(self, idx): younger = self.input_frames[idx - 1] if younger.GetFunctionName().startswith(self.PREFIX): return PrefixedFrame( - self.thread, idx, pc, function_name, "danger_will_robinson_" + self.thread, idx, function_name, "danger_will_robinson_" ) - return PrefixedFrame(self.thread, idx, pc, function_name, self.PREFIX) + return PrefixedFrame(self.thread, idx, function_name, self.PREFIX) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/Makefile b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/TestFrameProviderThreadFilter.py b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/TestFrameProviderThreadFilter.py new file mode 100644 index 0000000000000..fb41cb29f2b4f --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/TestFrameProviderThreadFilter.py @@ -0,0 +1,94 @@ +""" +Test bt --provider * in a multithreaded scenario with even/odd thread-filtered +providers and an uppercase provider chained on top. +""" + +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import TestBase +from lldbsuite.test import lldbutil + + +class FrameProviderThreadFilterTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + TestBase.setUp(self) + self.source = "main.cpp" + + def build_and_stop_all_threads(self): + """Build, set a breakpoint, and continue until all 3 worker threads + have hit it. Returns (target, process, worker_threads).""" + self.build() + target, process, thread, bkpt = lldbutil.run_to_source_breakpoint( + self, + "break in thread", + lldb.SBFileSpec(self.source), + only_one_thread=False, + ) + + # The breakpoint is on a one-shot line (fetch_add), so each hit is + # from a unique thread. Continue until all 3 have hit it. + while bkpt.GetHitCount() < 3: + process.Continue() + + # After 3 hits, all worker threads are alive: some in the spin loop, + # one at the breakpoint. Collect all non-main threads. + worker_threads = [] + for t in process: + for i in range(t.GetNumFrames()): + if "thread_work" in (t.GetFrameAtIndex(i).GetFunctionName() or ""): + worker_threads.append(t) + break + + self.assertEqual(len(worker_threads), 3, "Expected 3 worker threads") + return target, process, worker_threads + + def register_providers(self, target): + """Import the script and register all three providers in order.""" + script_path = os.path.join(self.getSourceDir(), "frame_provider.py") + self.runCmd("command script import " + script_path) + + error = lldb.SBError() + for cls in ("EvenThreadProvider", "OddThreadProvider", "UpperCaseProvider"): + target.RegisterScriptedFrameProvider( + "frame_provider." + cls, + lldb.SBStructuredData(), + error, + ) + self.assertTrue(error.Success(), f"Should register {cls}: {error}") + + @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr24778") + def test_bt_provider_star_with_thread_filter(self): + """ + Register EvenThreadProvider, OddThreadProvider, and UpperCaseProvider. + For each worker thread, 'bt --provider *' should show: + - Base Unwinder section with original names (thread_work). + - UpperCaseProvider section with the fully chained result. + - The non-applicable prefix provider should NOT appear. + The default 'bt' should show the final upper-cased + prefixed names. + """ + target, process, worker_threads = self.build_and_stop_all_threads() + self.register_providers(target) + + for thread in worker_threads: + prefix = "EVEN_THREAD_" if thread.GetIndexID() % 2 == 0 else "ODD_THREAD_" + excluded = ( + "OddThreadProvider" + if prefix == "EVEN_THREAD_" + else "EvenThreadProvider" + ) + + process.SetSelectedThread(thread) + + self.expect( + "bt --provider '*'", + substrs=["Base Unwinder", "thread_work", "UpperCaseProvider", prefix], + ) + self.expect( + "bt --provider '*'", + matching=False, + substrs=[excluded], + ) + self.expect("bt", substrs=[prefix]) diff --git a/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/frame_provider.py new file mode 100644 index 0000000000000..5fe27c8590713 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/frame_provider.py @@ -0,0 +1,110 @@ +""" +Frame providers for multithreaded thread-filter testing. + +EvenThreadProvider - applies only to even-indexed threads, prepends 'even_thread_'. +OddThreadProvider - applies only to odd-indexed threads, prepends 'odd_thread_'. +UpperCaseProvider - applies to ALL threads, upper-cases function names. + +When all three are registered (even, odd, uppercase -- in that order), chaining +produces: + Even threads: EVEN_THREAD_THREAD_WORK + Odd threads: ODD_THREAD_THREAD_WORK +""" + +import lldb +from lldb.plugins.scripted_process import ScriptedFrame +from lldb.plugins.scripted_frame_provider import ScriptedFrameProvider + + +class PrefixedFrame(ScriptedFrame): + """A frame that wraps a real frame but transforms the function name.""" + + def __init__(self, thread, idx, function_name): + args = lldb.SBStructuredData() + super().__init__(thread, args) + self.idx = idx + self.function_name = function_name + + def get_id(self): + return self.idx + + def get_pc(self): + return 0 + + def get_function_name(self): + return self.function_name + + def is_artificial(self): + return False + + def is_hidden(self): + return False + + def get_register_context(self): + return None + + +class EvenThreadProvider(ScriptedFrameProvider): + """Applies only to even-indexed threads; prepends 'even_thread_' prefix.""" + + PREFIX = "even_thread_" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def applies_to_thread(thread): + return thread.GetIndexID() % 2 == 0 + + @staticmethod + def get_description(): + return "Prefix for even threads" + + def get_frame_at_index(self, idx): + if idx < len(self.input_frames): + frame = self.input_frames[idx] + name = self.PREFIX + frame.GetFunctionName() + return PrefixedFrame(self.thread, idx, name) + return None + + +class OddThreadProvider(ScriptedFrameProvider): + """Applies only to odd-indexed threads; prepends 'odd_thread_' prefix.""" + + PREFIX = "odd_thread_" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def applies_to_thread(thread): + return thread.GetIndexID() % 2 != 0 + + @staticmethod + def get_description(): + return "Prefix for odd threads" + + def get_frame_at_index(self, idx): + if idx < len(self.input_frames): + frame = self.input_frames[idx] + name = self.PREFIX + frame.GetFunctionName() + return PrefixedFrame(self.thread, idx, name) + return None + + +class UpperCaseProvider(ScriptedFrameProvider): + """Applies to ALL threads; upper-cases all function names.""" + + def __init__(self, input_frames, args): + super().__init__(input_frames, args) + + @staticmethod + def get_description(): + return "Upper-case all function names" + + def get_frame_at_index(self, idx): + if idx < len(self.input_frames): + frame = self.input_frames[idx] + name = frame.GetFunctionName().upper() + return PrefixedFrame(self.thread, idx, name) + return None diff --git a/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/main.cpp b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/main.cpp new file mode 100644 index 0000000000000..3658357132503 --- /dev/null +++ b/lldb/test/API/functionalities/scripted_frame_provider/thread_filter/main.cpp @@ -0,0 +1,18 @@ +#include <thread> +#include <unistd.h> +#include <vector> + +#define NUM_THREADS 3 + +void thread_work() { + pause(); // break in thread +} + +int main() { + std::vector<std::thread> threads; + for (int i = 0; i < NUM_THREADS; i++) + threads.emplace_back(thread_work); + for (auto &t : threads) + t.join(); + return 0; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
