| // Copyright 2025 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <optional> |
| #include <utility> |
| |
| #include "src/developer/debug/ipc/records.h" |
| #include "src/developer/debug/zxdb/client/frame.h" |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/session.h" |
| #include "src/developer/debug/zxdb/client/source_file_provider_impl.h" |
| #include "src/developer/debug/zxdb/client/thread.h" |
| #include "src/developer/debug/zxdb/console/command_utils.h" |
| #include "src/developer/debug/zxdb/console/console_context.h" |
| #include "src/developer/debug/zxdb/console/format_context.h" |
| #include "src/developer/debug/zxdb/console/format_exception.h" |
| #include "src/developer/debug/zxdb/console/format_location.h" |
| #include "src/developer/debug/zxdb/console/format_target.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace zxdb { |
| |
| namespace { |
| |
| // We want to display full information for some exceptions like page faults, but debugger exceptions |
| // like single step and debug breakpoint exceptions don't need thhe full treatment to reduce noise |
| // when stepping. |
| bool ShouldDisplayFullExceptionInfo(const StopInfo& info) { |
| if (info.exception_type == debug_ipc::ExceptionType::kNone || |
| info.exception_type == debug_ipc::ExceptionType::kHardwareBreakpoint || |
| info.exception_type == debug_ipc::ExceptionType::kSoftwareBreakpoint || |
| info.exception_type == debug_ipc::ExceptionType::kWatchpoint || |
| info.exception_type == debug_ipc::ExceptionType::kSingleStep || |
| info.exception_type == debug_ipc::ExceptionType::kSynthetic) |
| return false; |
| return true; |
| } |
| |
| OutputBuffer DescribeHitBreakpoints(ConsoleContext* context, |
| const std::vector<fxl::WeakPtr<Breakpoint>>& hits) { |
| // Do two passes since some of the weak pointers may be gone. |
| std::vector<int> ids; |
| for (const auto& hit : hits) { |
| if (hit) |
| ids.push_back(context->IdForBreakpoint(hit.get())); |
| } |
| |
| OutputBuffer out; |
| if (ids.empty()) |
| return out; |
| |
| out.Append("on bp "); |
| for (size_t i = 0; i < ids.size(); i++) { |
| out.Append(Syntax::kSpecial, std::to_string(ids[i])); |
| if (i < ids.size() - 1) |
| out.Append(", "); |
| } |
| out.Append(" "); |
| return out; |
| } |
| |
| } // namespace |
| |
| // For comparison, GDB's printout for a breakpoint hit is: |
| // |
| // Breakpoint 1, main () at eraseme.c:4 |
| // 4 printf("Hello\n"); |
| // |
| // And LLDB's is: |
| // |
| // * thread #1: tid = 33767, 0x000055555555463e a.out`main + 4 at |
| // eraseme.c:4, name = 'a.out', stop reason = breakpoint 1.1 |
| // frame #0: 0x000055555555463e a.out`main + 4 at eraseme.c:4 |
| // 1 #include <stdio.h> |
| // 2 |
| // 3 int main() { |
| // -> 4 printf("Hello\n"); |
| // 5 return 1; |
| // 6 } |
| // |
| // When stepping, GDB prints out only the 2nd line with source info, and LLDB |
| // prints out the whole thing with "step over" for "stop reason". |
| OutputBuffer FormatThreadStop(ConsoleContext* context, const Thread* thread, |
| std::optional<StopInfo> info, bool override_show_exception_info) { |
| OutputBuffer out; |
| |
| std::optional<StopInfo> maybe_stop_info = std::move(info); |
| |
| if (!maybe_stop_info) { |
| maybe_stop_info = thread->CurrentStopInfo(); |
| } |
| |
| if (maybe_stop_info && ShouldDisplayFullExceptionInfo(*maybe_stop_info) && |
| !override_show_exception_info) { |
| out.Append(FormatException(context, thread, maybe_stop_info->exception_record)); |
| out.Append("\n"); |
| } |
| |
| out.Append("🛑 "); |
| |
| // Only print out the process/thread when there's more than one. |
| if (context->session()->system().GetTargets().size() > 1) { |
| const Target* target = context->GetActiveTarget(); |
| out.Append("process "); |
| out.Append(Syntax::kSpecial, std::to_string(context->IdForTarget(target))); |
| out.Append(" "); |
| } |
| if (thread->GetProcess()->GetThreads().size() > 1) { |
| out.Append("thread "); |
| out.Append(Syntax::kSpecial, std::to_string(context->IdForThread(thread))); |
| out.Append(" "); |
| } |
| |
| // Stop reason. |
| if (maybe_stop_info && !maybe_stop_info->hit_breakpoints.empty()) { |
| out.Append(DescribeHitBreakpoints(context, maybe_stop_info->hit_breakpoints)); |
| } else if (maybe_stop_info && |
| maybe_stop_info->exception_type == debug_ipc::ExceptionType::kGeneral && |
| !override_show_exception_info) { |
| // Show exception type for non-debug exceptions. Most exceptions are generated by the debugger |
| // internally so skip those to avoid noise. |
| out.Append(fxl::StringPrintf( |
| "on %s exception ", debug_ipc::ExceptionTypeToString(maybe_stop_info->exception_type))); |
| } |
| |
| // Frame (current position will always be frame 0). |
| const Stack& stack = thread->GetStack(); |
| if (stack.empty()) { |
| out.Append(" (no location information)\n"); |
| } else { |
| size_t frame_id = context->GetActiveFrameIdForThread(thread); |
| const Location& location = stack[frame_id]->GetLocation(); |
| |
| FormatLocationOptions location_options(thread->GetProcess()->GetTarget()); |
| location_options.func.name.bold_last = true; |
| out.Append(FormatLocation(location, location_options)); |
| |
| if (location.has_symbols()) { |
| out.Append("\n"); |
| } else { |
| out.Append(" (no symbol info)\n"); |
| } |
| |
| Err err = OutputSourceContext( |
| thread->GetProcess(), |
| std::make_unique<SourceFileProviderImpl>(thread->GetProcess()->GetTarget()->settings()), |
| location, context->GetSourceAffinityForThread(thread)); |
| if (err.has_error()) |
| out.Append(err); |
| } |
| return out; |
| } |
| |
| OutputBuffer FormatThreadConcise(const ConsoleContext* context, const Thread* thread) { |
| OutputBuffer out("Thread "); |
| out.Append(Syntax::kSpecial, std::to_string(context->IdForThread(thread))); |
| |
| out.Append(Syntax::kVariable, " state"); |
| out.Append("=" + FormatConsoleString( |
| ThreadStateToString(thread->GetState(), thread->GetBlockedReason()))); |
| |
| const char* id_name = debug::PlatformThreadIdName(context->session()->platform(), false); |
| out.Append(" "); |
| out.Append(Syntax::kVariable, id_name); |
| out.Append("=" + std::to_string(thread->GetKoid())); |
| |
| out.Append(Syntax::kVariable, " name"); |
| out.Append("=" + FormatConsoleString(thread->GetName())); |
| |
| return out; |
| } |
| |
| } // namespace zxdb |