================ @@ -0,0 +1,491 @@ +//===-- InstrumentationRuntimeBoundsSafety.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 "InstrumentationRuntimeBoundsSafety.h" + +#include "Plugins/Process/Utility/HistoryThread.h" +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Symbol/Block.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Symbol/Variable.h" +#include "lldb/Symbol/VariableList.h" +#include "lldb/Target/InstrumentationRuntimeStopInfo.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Target/SectionLoadList.h" +#include "lldb/Target/StopInfo.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" +#include "lldb/Utility/RegisterValue.h" +#include "lldb/Utility/RegularExpression.h" +#include "clang/CodeGen/ModuleBuilder.h" + +#include <memory> + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(InstrumentationRuntimeBoundsSafety) + +#define BOUNDS_SAFETY_SOFT_TRAP_MINIMAL "__bounds_safety_soft_trap" +#define BOUNDS_SAFETY_SOFT_TRAP_S "__bounds_safety_soft_trap_s" + +const std::vector<std::string> &getBoundsSafetySoftTrapRuntimeFuncs() { + static std::vector<std::string> Funcs = {BOUNDS_SAFETY_SOFT_TRAP_MINIMAL, + BOUNDS_SAFETY_SOFT_TRAP_S}; + + return Funcs; +} + +#define SOFT_TRAP_CATEGORY_PREFIX "Soft " +#define SOFT_TRAP_FALLBACK_CATEGORY \ + SOFT_TRAP_CATEGORY_PREFIX "Bounds check failed" + +class InstrumentationBoundsSafetyStopInfo : public StopInfo { +public: + ~InstrumentationBoundsSafetyStopInfo() override = default; + + lldb::StopReason GetStopReason() const override { + return lldb::eStopReasonInstrumentation; + } + + std::optional<uint32_t> + GetSuggestedStackFrameIndex(bool inlined_stack) override { + return m_value; + } + + const char *GetDescription() override { return m_description.c_str(); } + + bool DoShouldNotify(Event *event_ptr) override { return true; } + + static lldb::StopInfoSP + CreateInstrumentationBoundsSafetyStopInfo(Thread &thread) { + return StopInfoSP(new InstrumentationBoundsSafetyStopInfo(thread)); + } + +private: + InstrumentationBoundsSafetyStopInfo(Thread &thread); + + std::pair<std::optional<std::string>, std::optional<uint32_t>> + ComputeStopReasonAndSuggestedStackFrame(bool &warning_emitted_for_failure); + + std::pair<std::string, std::optional<uint32_t>> + ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure); + + std::pair<std::optional<std::string>, std::optional<uint32_t>> + ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo( + ThreadSP thread_sp, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure); +}; + +InstrumentationBoundsSafetyStopInfo::InstrumentationBoundsSafetyStopInfo( + Thread &thread) + : StopInfo(thread, 0) { + // No additional data describing the reason for stopping + m_extended_info = nullptr; + m_description = SOFT_TRAP_FALLBACK_CATEGORY; + + bool warning_emitted_for_failure = false; + auto [Description, MaybeSuggestedStackIndex] = + ComputeStopReasonAndSuggestedStackFrame(warning_emitted_for_failure); + if (Description) + m_description = Description.value(); + if (MaybeSuggestedStackIndex) + m_value = MaybeSuggestedStackIndex.value(); + + // Emit warning about the failure to compute the stop info if one wasn't + // already emitted + if ((!Description.has_value()) && !warning_emitted_for_failure) { + if (auto thread_sp = GetThread()) { + lldb::user_id_t debugger_id = + thread_sp->GetProcess()->GetTarget().GetDebugger().GetID(); + Debugger::ReportWarning( + "specific BoundsSafety trap reason could not be computed", + debugger_id); + } + } +} + +std::pair<std::optional<std::string>, std::optional<uint32_t>> +InstrumentationBoundsSafetyStopInfo::ComputeStopReasonAndSuggestedStackFrame( + bool &warning_emitted_for_failure) { + auto *log_category = GetLog(LLDBLog::InstrumentationRuntime); + ThreadSP thread_sp = GetThread(); + if (!thread_sp) { + LLDB_LOGF(log_category, "failed to get thread while stopped"); + return {}; + } + + lldb::user_id_t debugger_id = + thread_sp->GetProcess()->GetTarget().GetDebugger().GetID(); + + auto parent_sf = thread_sp->GetStackFrameAtIndex(1); + if (!parent_sf) { + LLDB_LOGF(log_category, "got nullptr when fetching stackframe at index 1"); + return {}; + } + + if (parent_sf->HasDebugInformation()) { + return ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + parent_sf, debugger_id, warning_emitted_for_failure); + } + + // If the debug info is missing we can still get some information + // from the parameter in the soft trap runtime call. + return ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo( + thread_sp, debugger_id, warning_emitted_for_failure); +} + +std::pair<std::string, std::optional<uint32_t>> +InstrumentationBoundsSafetyStopInfo:: + ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure) { + // First try to use debug info to understand the reason for trapping. The + // call stack will look something like this: + // + // ``` + // frame #0: `__bounds_safety_soft_trap_s(reason="") + // frame #1: `__clang_trap_msg$Bounds check failed$<reason>' + // frame #2: `bad_read(index=10) + // ``` + // .... + const auto *TrapReasonFuncName = parent_sf->GetFunctionName(); + + auto MaybeTrapReason = + clang::CodeGen::DemangleTrapReasonInDebugInfo(TrapReasonFuncName); + if (!MaybeTrapReason.has_value()) { + LLDB_LOGF( + GetLog(LLDBLog::InstrumentationRuntime), + "clang::CodeGen::DemangleTrapReasonInDebugInfo(\"%s\") call failed", + TrapReasonFuncName); + return {}; + } + auto category = MaybeTrapReason.value().first; + auto message = MaybeTrapReason.value().second; + + // TODO: Clang should probably be changed to emit the "Soft " prefix itself + std::string stop_reason; + llvm::raw_string_ostream ss(stop_reason); + ss << SOFT_TRAP_CATEGORY_PREFIX; + if (category.empty()) + ss << "<empty category>"; + else + ss << category; + if (message.empty()) { + // This is not a failure so leave `warning_emitted_for_failure` untouched. + Debugger::ReportWarning( + "specific BoundsSafety trap reason is not " + "available because the compiler omitted it from the debug info", + debugger_id); + } else { + ss << ": " << message; + } + // Use computed stop-reason and assume the parent of `parent_sf` is the + // the place in the user's code where the call to the soft trap runtime + // originated. + return std::make_pair(stop_reason, parent_sf->GetFrameIndex() + 1); +} + +std::pair<std::optional<std::string>, std::optional<uint32_t>> +InstrumentationBoundsSafetyStopInfo:: + ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo( + ThreadSP thread_sp, lldb::user_id_t debugger_id, + bool &warning_emitted_for_failure) { + + auto *log_category = GetLog(LLDBLog::InstrumentationRuntime); + auto softtrap_sf = thread_sp->GetStackFrameAtIndex(0); + if (!softtrap_sf) { + LLDB_LOGF(log_category, "got nullptr when fetching stackframe at index 0"); + return {}; + } + llvm::StringRef trap_reason_func_name = softtrap_sf->GetFunctionName(); + + if (trap_reason_func_name == BOUNDS_SAFETY_SOFT_TRAP_MINIMAL) { + // This function has no arguments so there's no additional information + // that would allow us to identify the trap reason. + // + // Use the fallback stop reason and the current frame. + // While we "could" set the suggested frame to our parent (where the + // bounds check failed), doing this leads to very misleading output in + // LLDB. E.g.: + // + // ``` + // 0x100003b40 <+104>: bl 0x100003d64 ; __bounds_safety_soft_trap + // -> 0x100003b44 <+108>: b 0x100003b48 ; <+112> + // ``` + // + // This makes it look we stopped after finishing the call to + // `__bounds_safety_soft_trap` but actually we are in the middle of the + // call. To avoid this confusion just use the current frame. + Debugger::ReportWarning( + "specific BoundsSafety trap reason is not available because debug " + "info is missing on the caller of '" BOUNDS_SAFETY_SOFT_TRAP_MINIMAL + "'", + debugger_id); + warning_emitted_for_failure = true; + return {}; + } + + // BOUNDS_SAFETY_SOFT_TRAP_S has one argument which is a pointer to a string + // describing the trap or a nullptr. + if (trap_reason_func_name != BOUNDS_SAFETY_SOFT_TRAP_S) { + LLDB_LOGF(log_category, + "unexpected function name. Expected \"%s\" but got \"%s\"", + BOUNDS_SAFETY_SOFT_TRAP_S, trap_reason_func_name.data()); + assert(0 && "hit breakpoint for unexpected function name"); + return {}; + } + + auto rc = thread_sp->GetRegisterContext(); + if (!rc) { + LLDB_LOGF(log_category, "failed to get register context"); + return {}; + } + + // FIXME: LLDB should have an API that tells us for the current target if + // `LLDB_REGNUM_GENERIC_ARG1` can be used. + // https://github.com/llvm/llvm-project/issues/168602 + // Don't try for architectures where examining the first register won't + // work. + auto process = thread_sp->GetProcess(); + if (!process) { + LLDB_LOGF(log_category, "failed to get process"); + return {}; + } + switch (process->GetTarget().GetArchitecture().GetCore()) { + case ArchSpec::eCore_x86_32_i386: + case ArchSpec::eCore_x86_32_i486: + case ArchSpec::eCore_x86_32_i486sx: + case ArchSpec::eCore_x86_32_i686: + // Technically some x86 calling conventions do use a register for + // passing the first argument but let's ignore that for now. + Debugger::ReportWarning( + "specific BoundsSafety trap reason cannot be inferred on x86 when " + "the caller of '" BOUNDS_SAFETY_SOFT_TRAP_S "' is missing debug info", + debugger_id); + warning_emitted_for_failure = true; + return {}; + default: { + } + }; + + // Examine the register for the first argument ---------------- JDevlieghere wrote:
```suggestion // Examine the register for the first argument. ``` https://github.com/llvm/llvm-project/pull/169117 _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
