blob: e76544af143aa7d4fefaf6557daf1248ed545015 [file] [log] [blame] [edit]
//===-- ExceptionInfoRequestHandler.cpp -----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "DAP.h"
#include "DAPError.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "RequestHandler.h"
#include "SBAPIExtras.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBStructuredData.h"
#include "lldb/API/SBThread.h"
#include "lldb/API/SBThreadCollection.h"
#include "lldb/API/SBValue.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/BranchProbability.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
using namespace llvm;
using namespace lldb_dap;
using namespace lldb_dap::protocol;
namespace {
// See `InstrumentationRuntimeUBSan::RetrieveReportData`.
struct UBSanReport {
std::string description;
std::string summary;
std::string filename;
uint32_t column = LLDB_INVALID_COLUMN_NUMBER;
uint32_t line = LLDB_INVALID_LINE_NUMBER;
};
// See `InstrumentationRuntimeMainThreadChecker::RetrieveReportData`.
struct MainThreadCheckerReport {
std::string description;
std::string api_name;
std::string class_name;
std::string selector;
};
// See `ReportRetriever::RetrieveReportData`.
struct ASanReport {
std::string description;
lldb::addr_t address = LLDB_INVALID_ADDRESS;
lldb::addr_t program_counter = LLDB_INVALID_ADDRESS;
lldb::addr_t base_pointer = LLDB_INVALID_ADDRESS;
lldb::addr_t stack_pointer = LLDB_INVALID_ADDRESS;
std::string stop_type;
};
// FIXME: Support TSan, BoundsSafety formatting.
using RuntimeInstrumentReport =
std::variant<UBSanReport, MainThreadCheckerReport, ASanReport>;
static bool fromJSON(const json::Value &params, UBSanReport &report,
json::Path path) {
json::ObjectMapper O(params, path);
return O.mapOptional("description", report.description) &&
O.mapOptional("summary", report.summary) &&
O.mapOptional("filename", report.filename) &&
O.mapOptional("col", report.column) &&
O.mapOptional("line", report.line);
}
static bool fromJSON(const json::Value &params, MainThreadCheckerReport &report,
json::Path path) {
json::ObjectMapper O(params, path);
return O.mapOptional("description", report.description) &&
O.mapOptional("api_name", report.api_name) &&
O.mapOptional("class_name", report.class_name) &&
O.mapOptional("selector", report.selector);
}
static bool fromJSON(const json::Value &params, ASanReport &report,
json::Path path) {
json::ObjectMapper O(params, path);
return O.mapOptional("description", report.description) &&
O.mapOptional("address", report.address) &&
O.mapOptional("pc", report.program_counter) &&
O.mapOptional("bp", report.base_pointer) &&
O.mapOptional("sp", report.stack_pointer) &&
O.mapOptional("stop_type", report.stop_type);
}
static bool fromJSON(const json::Value &params, RuntimeInstrumentReport &report,
json::Path path) {
json::ObjectMapper O(params, path);
std::string instrumentation_class;
if (!O || !O.map("instrumentation_class", instrumentation_class))
return false;
if (instrumentation_class == "UndefinedBehaviorSanitizer") {
UBSanReport inner_report;
bool success = fromJSON(params, inner_report, path);
if (success)
report = std::move(inner_report);
return success;
}
if (instrumentation_class == "MainThreadChecker") {
MainThreadCheckerReport inner_report;
bool success = fromJSON(params, inner_report, path);
if (success)
report = std::move(inner_report);
return success;
}
if (instrumentation_class == "AddressSanitizer") {
ASanReport inner_report;
bool success = fromJSON(params, inner_report, path);
if (success)
report = std::move(inner_report);
return success;
}
// FIXME: Support additional runtime instruments with specific formatters.
return false;
}
} // end namespace
static raw_ostream &operator<<(raw_ostream &OS, const UBSanReport &report) {
if (!report.filename.empty()) {
OS << report.filename;
if (report.line != LLDB_INVALID_LINE_NUMBER) {
OS << ":" << report.line;
if (report.column != LLDB_INVALID_COLUMN_NUMBER)
OS << ":" << report.column;
}
OS << " ";
}
if (!report.description.empty())
OS << report.description << "\n";
if (!report.summary.empty())
OS << report.summary;
return OS;
}
static raw_ostream &operator<<(raw_ostream &OS,
const MainThreadCheckerReport &report) {
if (!report.description.empty())
OS << report.description << "\n";
if (!report.class_name.empty())
OS << "Class Name: " << report.class_name << "\n";
if (!report.selector.empty())
OS << "Selector: " << report.selector << "\n";
return OS;
}
static raw_ostream &operator<<(raw_ostream &OS, const ASanReport &report) {
if (!report.stop_type.empty())
OS << report.stop_type << ": ";
if (!report.description.empty())
OS << report.description << "\n";
if (report.address != LLDB_INVALID_ADDRESS)
OS << "Address: 0x" << llvm::utohexstr(report.address) << "\n";
if (report.program_counter != LLDB_INVALID_ADDRESS)
OS << "Program counter: 0x" << llvm::utohexstr(report.program_counter)
<< "\n";
if (report.base_pointer != LLDB_INVALID_ADDRESS)
OS << "Base pointer: 0x" << llvm::utohexstr(report.base_pointer) << "\n";
if (report.stack_pointer != LLDB_INVALID_ADDRESS)
OS << "Stack pointer: 0x" << llvm::utohexstr(report.stack_pointer) << "\n";
return OS;
}
static raw_ostream &operator<<(raw_ostream &OS,
const RuntimeInstrumentReport &report) {
std::visit([&](const auto &r) { OS << r; }, report);
return OS;
}
static std::string FormatExceptionId(DAP &dap, lldb::SBThread &thread) {
const lldb::StopReason stop_reason = thread.GetStopReason();
switch (stop_reason) {
case lldb::eStopReasonInstrumentation:
return "runtime-instrumentation";
case lldb::eStopReasonSignal:
return "signal";
case lldb::eStopReasonBreakpoint: {
const ExceptionBreakpoint *exc_bp =
dap.GetExceptionBPFromStopReason(thread);
if (exc_bp)
return exc_bp->GetFilter().str();
}
LLVM_FALLTHROUGH;
default:
return "exception";
}
}
static std::string FormatStopDescription(lldb::SBThread &thread) {
lldb::SBStream stream;
if (!thread.GetStopDescription(stream))
return "";
std::string desc;
raw_string_ostream OS(desc);
OS << stream;
return desc;
}
static std::string FormatExtendedStopInfo(lldb::SBThread &thread) {
lldb::SBStream stream;
if (!thread.GetStopReasonExtendedInfoAsJSON(stream))
return "";
std::string stop_info;
raw_string_ostream OS(stop_info);
Expected<RuntimeInstrumentReport> report =
json::parse<RuntimeInstrumentReport>(
{stream.GetData(), stream.GetSize()});
// Check if we can improve the formatting of the raw JSON report.
if (report) {
OS << *report;
std::visit(
[&](auto &&report) {
using T = std::decay_t<decltype(report)>;
if constexpr (std::is_same_v<T, ASanReport>) {
lldb::addr_t address = report.address;
lldb::SBProcess process = thread.GetProcess();
lldb::SBThreadCollection history_threads =
process.GetHistoryThreads(address);
lldb::SBStream stream;
OS << "Memory history associated with address: 0x"
<< llvm::utohexstr(address) << "\n";
for (const auto history_thread : history_threads) {
if (history_thread.GetStatus(stream))
OS << stream << "\n";
stream.Clear();
}
}
},
*report);
} else {
consumeError(report.takeError());
OS << stream;
}
return stop_info;
}
static std::string FormatCrashReport(lldb::SBThread &thread) {
lldb::SBStructuredData crash_info =
thread.GetProcess().GetExtendedCrashInformation();
if (!crash_info)
return "";
std::string report;
raw_string_ostream OS(report);
OS << "Extended Crash Information:\n" << crash_info;
return report;
}
static std::string FormatStackTrace(lldb::SBThread &thread) {
std::string stack_trace;
raw_string_ostream OS(stack_trace);
for (auto frame : thread)
OS << frame;
return stack_trace;
}
static std::optional<ExceptionDetails> FormatException(lldb::SBThread &thread) {
lldb::SBValue exception = thread.GetCurrentException();
if (!exception)
return {};
ExceptionDetails details;
if (const char *name = exception.GetName())
details.evaluateName = name;
if (const char *typeName = exception.GetDisplayTypeName())
details.typeName = typeName;
std::string message;
raw_string_ostream OS(message);
OS << exception;
details.message = std::move(message);
if (lldb::SBThread exception_backtrace =
thread.GetCurrentExceptionBacktrace())
details.stackTrace = FormatStackTrace(exception_backtrace);
return details;
}
static void
FormatRuntimeInstrumentStackTrace(lldb::SBThread &thread,
lldb::InstrumentationRuntimeType type,
std::optional<ExceptionDetails> &details) {
lldb::SBThreadCollection threads =
thread.GetStopReasonExtendedBacktraces(type);
for (auto thread : threads) {
ExceptionDetails current_details;
current_details.stackTrace = FormatStackTrace(thread);
if (!details)
details = std::move(current_details);
else
details->innerException.emplace_back(std::move(current_details));
}
}
/// Retrieves the details of the exception that caused this event to be raised.
///
/// Clients should only call this request if the corresponding capability
/// `supportsExceptionInfoRequest` is true.
Expected<ExceptionInfoResponseBody>
ExceptionInfoRequestHandler::Run(const ExceptionInfoArguments &args) const {
lldb::SBThread thread = dap.GetLLDBThread(args.threadId);
if (!thread.IsValid())
return make_error<DAPError>(
formatv("Invalid thread id: {}", args.threadId).str());
ExceptionInfoResponseBody body;
body.breakMode = eExceptionBreakModeAlways;
body.exceptionId = FormatExceptionId(dap, thread);
body.details = FormatException(thread);
body.description = FormatStopDescription(thread);
if (std::string stop_info = FormatExtendedStopInfo(thread);
!stop_info.empty())
body.description += formatv("\n\n{0}", stop_info);
if (std::string crash_report = FormatCrashReport(thread);
!crash_report.empty())
body.description += formatv("\n\n{0}", crash_report);
lldb::SBProcess process = thread.GetProcess();
for (uint32_t idx = 0; idx < lldb::eNumInstrumentationRuntimeTypes; idx++) {
auto type = static_cast<lldb::InstrumentationRuntimeType>(idx);
if (!process.IsInstrumentationRuntimePresent(type))
continue;
FormatRuntimeInstrumentStackTrace(thread, type, body.details);
}
return body;
}