================
@@ -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
+  auto *arg0_info = rc->GetRegisterInfo(
+      lldb::RegisterKind::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_ARG1);
+  if (!arg0_info) {
+    LLDB_LOGF(log_category,
+              "failed to get register info for LLDB_REGNUM_GENERIC_ARG1");
+    return {};
+  }
+  RegisterValue reg_value;
+  if (!rc->ReadRegister(arg0_info, reg_value)) {
+    LLDB_LOGF(log_category, "failed to read register %s", arg0_info->name);
+    return {};
+  }
+  uint64_t reg_value_as_int = reg_value.GetAsUInt64(UINT64_MAX);
+  if (reg_value_as_int == UINT64_MAX) {
+    LLDB_LOGF(log_category, "failed to read register %s as a UInt64",
+              arg0_info->name);
----------------
JDevlieghere wrote:

Generally we prefer `LLDB_LOG` (the llvm formatv variant) for new code.

https://github.com/llvm/llvm-project/pull/169117
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to