blob: 4b4c8dc78601355cd1cea45a45bf17d9d6720f4c [file] [log] [blame] [edit]
// 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