From 2e8a2f7ac262f43300ed8bd634638c352cb5c960 Mon Sep 17 00:00:00 2001 From: Dan Liew Date: Thu, 13 Nov 2025 16:36:27 -0800 Subject: [PATCH 1/4] [NFC][BoundsSafety][LLDB] Refactor the soft trap runtime tests This refactors the soft trap runtime test so the soft trap runtime implementation exists in its own file. This has several advantages: * It let's the runtime be built without debug info which will be the common case uses hit * It prevents the risk of infinite recursion because it isn't safe to build the soft trap runtime with -fbounds-safety soft trap mode enabled. --- .../Inputs/boundsSafetyMockSoftTrapRuntime.c | 21 +++++++++++++++++++ .../Inputs/boundsSafetySoftTraps.c | 13 ------------ .../boundssafety_soft_trap_call_minimal.test | 8 ++++--- .../boundssafety_soft_trap_call_with_str.test | 9 +++++--- 4 files changed, 32 insertions(+), 19 deletions(-) create mode 100644 lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c new file mode 100644 index 0000000000000..2b3f3d93f0f8b --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockSoftTrapRuntime.c @@ -0,0 +1,21 @@ +#include +#include +#include + +#if __CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION > 0 +#error API version changed +#endif + +#if __has_ptrcheck +#error Do not compile the runtime with -fbounds-safety enabled due to potential for infinite recursion +#endif + + + +void __bounds_safety_soft_trap_s(const char *reason) { + printf("BoundsSafety check FAILED: message:\"%s\"\n", reason? reason: ""); +} + +void __bounds_safety_soft_trap(void) { + printf("BoundsSafety check FAILED\n"); +} diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c index 5d0836f597bb4..70eaea4a30940 100644 --- a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetySoftTraps.c @@ -1,17 +1,4 @@ #include -#include - -#if __CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION > 0 -#error API version changed -#endif - -void __bounds_safety_soft_trap_s(const char *reason) { - printf("BoundsSafety check FAILED: message:\"%s\"\n", reason? reason: ""); -} - -void __bounds_safety_soft_trap(void) { - printf("BoundsSafety check FAILED\n"); -} int bad_read(int index) { int array[] = {0, 1, 2}; diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test index 231f7b18c9ef6..e4b2267afdee3 100644 --- a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test @@ -1,5 +1,7 @@ # UNSUPPORTED: system-windows -# RUN: %clang_host -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.out +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out # RUN: %lldb -b -s %s %t.out | FileCheck %s b __bounds_safety_soft_trap run @@ -10,9 +12,9 @@ run # Check that reason for bounds check failing can be seen in the stacktrace bt -# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{[ ]+}} +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{$}} # CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' -# CHECK-NEXT: frame #2{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:18 +# CHECK-NEXT: frame #2{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 # Resume execution c diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test index c614fc09c1178..6a67da6eeb358 100644 --- a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test @@ -1,5 +1,8 @@ # UNSUPPORTED: system-windows -# RUN: %clang_host -fbounds-safety -fbounds-safety-soft-traps=call-with-str -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.out +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out # RUN: %lldb -b -s %s %t.out | FileCheck %s b __bounds_safety_soft_trap_s run @@ -10,9 +13,9 @@ run # Check that reason for bounds check failing can be seen in the stacktrace bt -# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s(reason="indexing above upper bound in 'array[index]'") +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s # CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' -# CHECK-NEXT: frame #2{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:18 +# CHECK-NEXT: frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 # Resume execution c From c8af09bf04130c7f5dc2d20c6c77dbcca4d06b73 Mon Sep 17 00:00:00 2001 From: Dan Liew Date: Fri, 14 Nov 2025 15:33:51 -0800 Subject: [PATCH 2/4] [BoundsSafety][LLDB] Implement instrumentation plugin for -fbounds-safety soft traps This patch implements an instrumentation plugin for the `-fbounds-safety` soft trap mode first implemented in https://github.com/swiftlang/llvm-project/pull/11645 (rdar://158088757). The current implementation of -fbounds-safety traps works by emitting calls to runtime functions intended to log the occurrence of a soft trap. While the user could just set a breakpoint of these functions the instrumentation plugin sets it automatically and provides several additional features: When debug info is available: * It adjusts the stop reason to be the reason for trapping. This is extracted from the artificial frame in the debug info (similar to -fbounds-safety hard traps). * It adjusts the selected frame to be the frame where the soft trap occurred. When debug info is not available: * For the `call-with-str` soft trap mode the soft trap reason is read from the first argument register. * For the `call-minimal` soft trap mode the stop reason is adjusted to note its a bounds check failure but does not give further information because none is available. * In this situation the selected frame is not adjusted because in this mode the user will be looking at assembly and adjusting the frame makes things confusing. This patch includes shell and api tests. The shell tests seemed like the best way to test behavior when debug info is missing because those tests make it easy to disable building with debug info completely. rdar://163230807 --- lldb/include/lldb/lldb-enumerations.h | 1 + .../BoundsSafety/CMakeLists.txt | 13 + .../InstrumentationRuntimeBoundsSafety.cpp | 336 ++++++++++++++++++ .../InstrumentationRuntimeBoundsSafety.h | 61 ++++ .../InstrumentationRuntime/CMakeLists.txt | 3 + .../API/lang/BoundsSafety/soft_trap/Makefile | 10 + .../TestBoundsSafetyInstrumentationPlugin.py | 141 ++++++++ .../API/lang/BoundsSafety/soft_trap/main.c | 10 + .../soft_trap/mockSoftTrapRuntime.c | 17 + .../boundsSafetyMockCallSoftTrapRuntime.c | 8 + .../boundssafety_soft_trap_call_minimal.test | 17 +- ...ty_soft_trap_call_minimal_no_dbg_info.test | 26 ++ ...fety_soft_trap_call_minimal_no_plugin.test | 29 ++ .../boundssafety_soft_trap_call_str.test | 27 ++ ...y_soft_trap_call_with_str_no_dbg_info.test | 26 ++ ...ap_call_with_str_no_dbg_info_null_str.test | 29 ++ ...ty_soft_trap_call_with_str_no_plugin.test} | 8 +- 17 files changed, 753 insertions(+), 9 deletions(-) create mode 100644 lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt create mode 100644 lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp create mode 100644 lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h create mode 100644 lldb/test/API/lang/BoundsSafety/soft_trap/Makefile create mode 100644 lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py create mode 100644 lldb/test/API/lang/BoundsSafety/soft_trap/main.c create mode 100644 lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c create mode 100644 lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c create mode 100644 lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test create mode 100644 lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test create mode 100644 lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test create mode 100644 lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test create mode 100644 lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test rename lldb/test/Shell/BoundsSafety/{boundssafety_soft_trap_call_with_str.test => boundssafety_soft_trap_call_with_str_no_plugin.test} (81%) diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 202190e33898a..98510b9ec0c52 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -538,6 +538,7 @@ enum InstrumentationRuntimeType { eInstrumentationRuntimeTypeMainThreadChecker = 0x0003, eInstrumentationRuntimeTypeSwiftRuntimeReporting = 0x0004, eInstrumentationRuntimeTypeLibsanitizersAsan = 0x0005, + eInstrumentationRuntimeTypeBoundsSafety = 0x0006, eNumInstrumentationRuntimeTypes }; diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt new file mode 100644 index 0000000000000..adbd6c45e45af --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/CMakeLists.txt @@ -0,0 +1,13 @@ +add_lldb_library(lldbPluginInstrumentationRuntimeBoundsSafety PLUGIN + InstrumentationRuntimeBoundsSafety.cpp + + LINK_LIBS + lldbBreakpoint + lldbCore + lldbSymbol + lldbTarget + lldbPluginInstrumentationRuntimeUtility + + CLANG_LIBS + clangCodeGen + ) diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp new file mode 100644 index 0000000000000..3dd2217baa28a --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp @@ -0,0 +1,336 @@ +//===-- 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/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 + +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" + +std::vector &getBoundsSafetySoftTrapRuntimeFuncs() { + static std::vector 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 + 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: + std::pair> + ComputeStopReasonAndSuggestedStackFrameWithDebugInfo( + lldb::StackFrameSP parent_sf) { + // 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$' + // frame #2: `bad_read(index=10) + // ``` + // .... + const auto *TrapReasonFuncName = parent_sf->GetFunctionName(); + + auto MaybeTrapReason = + clang::CodeGen::DemangleTrapReasonInDebugInfo(TrapReasonFuncName); + if (!MaybeTrapReason.has_value()) + 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 << ""; + else + ss << category; + if (!message.empty()) { + 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> + ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo(ThreadSP thread_sp) { + auto softtrap_sf = thread_sp->GetStackFrameAtIndex(0); + if (!softtrap_sf) + 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. + 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) + return {}; + + auto rc = thread_sp->GetRegisterContext(); + if (!rc) + return {}; + + // Don't try for architectures where examining the first register won't + // work. + auto process = thread_sp->GetProcess(); + if (!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. + return {}; + default: { + } + }; + + // Examine the register for the first argument + auto *arg0_info = rc->GetRegisterInfo( + lldb::RegisterKind::eRegisterKindGeneric, LLDB_REGNUM_GENERIC_ARG1); + if (!arg0_info) + return {}; + RegisterValue reg_value; + if (!rc->ReadRegister(arg0_info, reg_value)) + return {}; + uint64_t reg_value_as_int = reg_value.GetAsUInt64(UINT64_MAX); + if (reg_value_as_int == UINT64_MAX || reg_value_as_int == 0) + return {}; + + // The first argument to the call is a pointer to a global C string + // containing the trap reason. + std::string out_string; + Status error_status; + thread_sp->GetProcess()->ReadCStringFromMemory(reg_value_as_int, out_string, + error_status); + if (error_status.Fail()) + return {}; + std::string stop_reason; + llvm::raw_string_ostream SS(stop_reason); + SS << SOFT_TRAP_FALLBACK_CATEGORY; + if (!stop_reason.empty()) { + SS << ": " << out_string; + } + // Use the current frame as the suggested frame for the same reason as for + // `BOUNDS_SAFETY_SOFT_TRAP_MINIMAL`. + return {stop_reason, 0}; + } + + std::pair, std::optional> + ComputeStopReasonAndSuggestedStackFrame() { + + ThreadSP thread_sp = GetThread(); + if (!thread_sp) + return {}; + + auto parent_sf = thread_sp->GetStackFrameAtIndex(1); + if (!parent_sf) + return {}; + + if (parent_sf->HasDebugInformation()) { + return ComputeStopReasonAndSuggestedStackFrameWithDebugInfo(parent_sf); + } + + // 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); + } + + InstrumentationBoundsSafetyStopInfo(Thread &thread) : StopInfo(thread, 0) { + // No additional data describing the reason for stopping + m_extended_info = nullptr; + m_description = SOFT_TRAP_FALLBACK_CATEGORY; + + auto [Description, MaybeSuggestedStackIndex] = + ComputeStopReasonAndSuggestedStackFrame(); + if (Description) + m_description = Description.value(); + if (MaybeSuggestedStackIndex) + m_value = MaybeSuggestedStackIndex.value(); + } +}; + +InstrumentationRuntimeBoundsSafety::~InstrumentationRuntimeBoundsSafety() { + Deactivate(); +} + +lldb::InstrumentationRuntimeSP +InstrumentationRuntimeBoundsSafety::CreateInstance( + const lldb::ProcessSP &process_sp) { + return InstrumentationRuntimeSP( + new InstrumentationRuntimeBoundsSafety(process_sp)); +} + +void InstrumentationRuntimeBoundsSafety::Initialize() { + PluginManager::RegisterPlugin(GetPluginNameStatic(), + "BoundsSafety instrumentation runtime plugin.", + CreateInstance, GetTypeStatic); +} + +void InstrumentationRuntimeBoundsSafety::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); +} + +lldb::InstrumentationRuntimeType +InstrumentationRuntimeBoundsSafety::GetTypeStatic() { + return lldb::eInstrumentationRuntimeTypeBoundsSafety; +} + +const RegularExpression & +InstrumentationRuntimeBoundsSafety::GetPatternForRuntimeLibrary() { + static RegularExpression regex; + return regex; +} + +bool InstrumentationRuntimeBoundsSafety::CheckIfRuntimeIsValid( + const lldb::ModuleSP module_sp) { + + for (const auto &SoftTrapFunc : getBoundsSafetySoftTrapRuntimeFuncs()) { + ConstString test_sym(SoftTrapFunc); + + if (module_sp->FindFirstSymbolWithNameAndType(test_sym, + lldb::eSymbolTypeAny)) + return true; + } + return false; +} + +bool InstrumentationRuntimeBoundsSafety::NotifyBreakpointHit( + void *baton, StoppointCallbackContext *context, user_id_t break_id, + user_id_t break_loc_id) { + assert(baton && "null baton"); + if (!baton) + return false; ///< false => resume execution. + + InstrumentationRuntimeBoundsSafety *const instance = + static_cast(baton); + + ProcessSP process_sp = instance->GetProcessSP(); + ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP(); + if (!process_sp || !thread_sp || + process_sp != context->exe_ctx_ref.GetProcessSP()) + return false; + + if (process_sp->GetModIDRef().IsLastResumeForUserExpression()) + return false; + + thread_sp->SetStopInfo( + InstrumentationBoundsSafetyStopInfo:: + CreateInstrumentationBoundsSafetyStopInfo(*thread_sp)); + return true; +} + +void InstrumentationRuntimeBoundsSafety::Activate() { + if (IsActive()) + return; + + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) + return; + + auto breakpoint = process_sp->GetTarget().CreateBreakpoint( + /*containingModules=*/nullptr, + /*containingSourceFiles=*/nullptr, getBoundsSafetySoftTrapRuntimeFuncs(), + eFunctionNameTypeFull, eLanguageTypeUnknown, + /*m_offset=*/0, + /*skip_prologue*/ eLazyBoolNo, + /*internal=*/true, + /*request_hardware*/ false); + + if (!breakpoint) + return; + + // Note: When `sync=true` the suggested stackframe is completely ignored. So + // we use `sync=false`. Is that a bug? + breakpoint->SetCallback( + InstrumentationRuntimeBoundsSafety::NotifyBreakpointHit, this, + /*sync=*/false); + breakpoint->SetBreakpointKind("bounds-safety-soft-trap"); + SetBreakpointID(breakpoint->GetID()); + SetActive(true); +} + +void InstrumentationRuntimeBoundsSafety::Deactivate() { + SetActive(false); + if (ProcessSP process_sp = GetProcessSP()) + process_sp->GetTarget().RemoveBreakpointByID(GetBreakpointID()); + + SetBreakpointID(LLDB_INVALID_BREAK_ID); +} diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h new file mode 100644 index 0000000000000..425f9c583beaa --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h @@ -0,0 +1,61 @@ +//===-- InstrumentationRuntimeBoundsSafetySoftTrap.h-------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_INSTRUMENTATIONRUNTIME_BOUNDS_SAFETY_SOFT_TRAP_H +#define LLDB_SOURCE_PLUGINS_INSTRUMENTATIONRUNTIME_BOUNDS_SAFETY_SOFT_TRAP_H + +#include "lldb/Target/ABI.h" +#include "lldb/Target/InstrumentationRuntime.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +class InstrumentationRuntimeBoundsSafety + : public lldb_private::InstrumentationRuntime { +public: + ~InstrumentationRuntimeBoundsSafety() override; + + static lldb::InstrumentationRuntimeSP + CreateInstance(const lldb::ProcessSP &process_sp); + + static void Initialize(); + + static void Terminate(); + + static llvm::StringRef GetPluginNameStatic() { return "BoundsSafety"; } + + static lldb::InstrumentationRuntimeType GetTypeStatic(); + + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + + virtual lldb::InstrumentationRuntimeType GetType() { return GetTypeStatic(); } + +private: + InstrumentationRuntimeBoundsSafety(const lldb::ProcessSP &process_sp) + : lldb_private::InstrumentationRuntime(process_sp) {} + + const RegularExpression &GetPatternForRuntimeLibrary() override; + + bool CheckIfRuntimeIsValid(const lldb::ModuleSP module_sp) override; + + void Activate() override; + + void Deactivate(); + + static bool NotifyBreakpointHit(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id); + + bool MatchAllModules() override { return true; } +}; + +} // namespace lldb_private + +#endif diff --git a/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt b/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt index 3c60bddd9078a..cef9f2130cce5 100644 --- a/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt +++ b/lldb/source/Plugins/InstrumentationRuntime/CMakeLists.txt @@ -2,6 +2,9 @@ set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND InstrumentationRuntime) add_subdirectory(ASan) add_subdirectory(ASanLibsanitizers) +# TO_UPSTREAM(BoundsSafety) ON +add_subdirectory(BoundsSafety) +# TO_UPSTREAM(BoundsSafety) OFF add_subdirectory(MainThreadChecker) add_subdirectory(TSan) add_subdirectory(UBSan) diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/Makefile b/lldb/test/API/lang/BoundsSafety/soft_trap/Makefile new file mode 100644 index 0000000000000..5e83e7ac6d93f --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/Makefile @@ -0,0 +1,10 @@ +# FIXME: mockSoftTrapRuntime.c shouldn't really be built with -fbounds-safety +C_SOURCES := main.c mockSoftTrapRuntime.c + +soft-trap-test-minimal: CFLAGS_EXTRAS := -fbounds-safety -fbounds-safety-soft-traps=call-minimal +soft-trap-test-minimal: all + +soft-trap-test-with-str: CFLAGS_EXTRAS := -fbounds-safety -fbounds-safety-soft-traps=call-with-str +soft-trap-test-with-str: all + +include Makefile.rules diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py new file mode 100644 index 0000000000000..67629dbb1492c --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py @@ -0,0 +1,141 @@ +""" +Test the BoundsSafety instrumentation plugin +""" +import lldb +from lldbsuite.test.lldbtest import * + + +STOP_REASON_MAX_LEN = 100 +SOFT_TRAP_FUNC_MINIMAL = '__bounds_safety_soft_trap' +SOFT_TRAP_FUNC_WITH_STR = '__bounds_safety_soft_trap_s' + + +class BoundsSafetyTestSoftTrapPlugin(TestBase): + def _check_stop_reason_impl(self, + expected_soft_trap_func:str, + expected_stop_reason:str, + expected_func_name:str, + expected_file_name:str, + expected_line_num:int): + process = self.dbg.GetSelectedTarget().process + thread = process.GetSelectedThread() + self.assertEqual( + thread.GetStopReason(), + lldb.eStopReasonInstrumentation, + ) + stop_reason = thread.GetStopDescription(STOP_REASON_MAX_LEN) + self.assertEqual( + stop_reason, + expected_stop_reason + ) + stop_frame = thread.GetSelectedFrame() + self.assertEqual( + stop_frame.name, + expected_func_name + ) + # The stop frame isn't frame 1 because that frame is the artificial + # frame containing the trap reason. + self.assertEqual( + stop_frame.idx, + 2 + ) + soft_trap_func_frame = thread.GetFrameAtIndex(0) + self.assertEqual( + soft_trap_func_frame.name, + expected_soft_trap_func + ) + file_name = stop_frame.GetLineEntry().GetFileSpec().basename + self.assertEqual( + file_name, + expected_file_name + ) + line = stop_frame.GetLineEntry().line + self.assertEqual( + line, + expected_line_num + ) + + def check_state_soft_trap_minimal(self, + stop_reason:str, + func_name:str, + file_name:str, + line_num:int): + """ + Check the program state is as expected when hitting + a soft trap from -fbounds-safety-soft-traps=call-minimal + """ + self._check_stop_reason_impl(SOFT_TRAP_FUNC_MINIMAL, + expected_stop_reason=stop_reason, + expected_func_name=func_name, + expected_file_name=file_name, + expected_line_num=line_num) + + def check_state_soft_trap_with_str(self, + stop_reason:str, + func_name:str, + file_name:str, + line_num:int): + """ + Check the program state is as expected when hitting + a soft trap from -fbounds-safety-soft-traps=call-with_str + """ + self._check_stop_reason_impl(SOFT_TRAP_FUNC_WITH_STR, + expected_stop_reason=stop_reason, + expected_func_name=func_name, + expected_file_name=file_name, + expected_line_num=line_num) + + def test_call_minimal(self): + """ + Test the plugin on code built with + -fbounds-safety-soft-traps=call-minimal + """ + self.build(make_targets=['soft-trap-test-minimal']) + self.createTestTarget() + self.runCmd("run") + + process = self.dbg.GetSelectedTarget().process + + # First soft trap hit + self.check_state_soft_trap_minimal( + "Soft Bounds check failed: indexing above upper bound in 'buffer[2]'", + "main", "main.c", 7) + + process.Continue() + + # Second soft trap hit + self.check_state_soft_trap_minimal( + "Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'", + "main", "main.c", 8) + + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) + + + def test_call_with_str(self): + """ + Test the plugin on code built with + -fbounds-safety-soft-traps=call-with-str + """ + self.build(make_targets=['soft-trap-test-with-str']) + self.createTestTarget() + self.runCmd("run") + + process = self.dbg.GetSelectedTarget().process + + # First soft trap hit + self.check_state_soft_trap_with_str( + "Soft Bounds check failed: indexing above upper bound in 'buffer[2]'", + "main", "main.c", 7) + + process.Continue() + + # Second soft trap hit + self.check_state_soft_trap_with_str( + "Soft Bounds check failed: indexing below lower bound in 'buffer[-1]'", + "main", "main.c", 8) + + process.Continue() + self.assertEqual(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/main.c b/lldb/test/API/lang/BoundsSafety/soft_trap/main.c new file mode 100644 index 0000000000000..518afaaa02e8c --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/main.c @@ -0,0 +1,10 @@ +#include + +int main(void) { + int pad; + int buffer[] = {0, 1}; + int pad2; + int tmp = buffer[2]; // access past upper bound + tmp = buffer[-1]; // access below lower bound + return 0; +} diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c b/lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c new file mode 100644 index 0000000000000..2cfbd24234eff --- /dev/null +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/mockSoftTrapRuntime.c @@ -0,0 +1,17 @@ +#include +#include +#include + +#if __CLANG_BOUNDS_SAFETY_SOFT_TRAP_API_VERSION > 0 +#error API version changed +#endif + +// FIXME: The runtimes really shouldn't be built with `-fbounds-safety` in +// soft trap mode because of the risk of infinite recursion. However, +// there's currently no way to have source files built with different flags + +void __bounds_safety_soft_trap_s(const char *reason) { + printf("BoundsSafety check FAILED: message:\"%s\"\n", reason ? reason : ""); +} + +void __bounds_safety_soft_trap(void) { printf("BoundsSafety check FAILED\n"); } diff --git a/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c new file mode 100644 index 0000000000000..698cf272386c2 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/Inputs/boundsSafetyMockCallSoftTrapRuntime.c @@ -0,0 +1,8 @@ +#include +#include + +int main(void) { + __bounds_safety_soft_trap_s(0); + printf("Execution continued\n"); + return 0; +} diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test index e4b2267afdee3..b1b2e8767f8ac 100644 --- a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal.test @@ -1,20 +1,23 @@ # UNSUPPORTED: system-windows # RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case # RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o # RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out # RUN: %lldb -b -s %s %t.out | FileCheck %s -b __bounds_safety_soft_trap + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + run -# TODO: We should probably teach LLDB to recognize soft traps, set the stop -# reason appropriately and set a breakpoint automatically (rdar://163230807). -# CHECK: * thread #{{.*}} stop reason = breakpoint 1.1 +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed: indexing above upper bound in 'array[index]'{{$}} -# Check that reason for bounds check failing can be seen in the stacktrace +# Check that the `bad_read` frame is selected bt -# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK: frame #{{.*}}`__bounds_safety_soft_trap{{$}} # CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' -# CHECK-NEXT: frame #2{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 +# CHECK-NEXT: * frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 # Resume execution c diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test new file mode 100644 index 0000000000000..b6101f5137bb5 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_dbg_info.test @@ -0,0 +1,26 @@ +# UNSUPPORTED: system-windows +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed{{$}} + +# Check that the `__bounds_safety_soft_trap` frame is selected +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK-NEXT: frame #{{.*}}`bad_read + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test new file mode 100644 index 0000000000000..c5e1c3e757b3a --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_minimal_no_plugin.test @@ -0,0 +1,29 @@ +# UNSUPPORTED: system-windows +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-minimal -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# Run without the plugin. A user might want to do this so they can set their +# own custom breakpoint with custom stopping behavior (e.g. stop after n hits). +plugin disable instrumentation-runtime.BoundsSafety +# CHECK: [-] BoundsSafety + +b __bounds_safety_soft_trap +run + +# CHECK: * thread #{{.*}} stop reason = breakpoint 1.1 + +# Check that reason for bounds check failing can be seen in the stacktrace +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' +# CHECK-NEXT: frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test new file mode 100644 index 0000000000000..a01b2a12126fa --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_str.test @@ -0,0 +1,27 @@ +# UNSUPPORTED: system-windows +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -g -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed: indexing above upper bound in 'array[index]'{{$}} + +# Check that the `bad_read` frame is selected +bt +# CHECK: frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`__clang_trap_msg$Bounds check failed$indexing above upper bound in 'array[index]' +# CHECK-NEXT: * frame #{{.*}}`bad_read(index=10) at boundsSafetySoftTraps.c:5 + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test new file mode 100644 index 0000000000000..38022da0e5d31 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info.test @@ -0,0 +1,26 @@ +# UNSUPPORTED: system-windows +# RUN: %clang_host -c -fbounds-safety -fbounds-safety-soft-traps=call-with-str -O0 %S/Inputs/boundsSafetySoftTraps.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +run + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed: indexing above upper bound in 'array[index]'{{$}} + +# Check that the `__bounds_safety_soft_trap_s` frame is selected +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`bad_read + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test new file mode 100644 index 0000000000000..c3c809c3b74a8 --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_dbg_info_null_str.test @@ -0,0 +1,29 @@ +# UNSUPPORTED: system-windows +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockCallSoftTrapRuntime.c -o %t.o +# Note: Building the runtime without debug info is intentional because this is the common case +# RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o +# RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s + +# This test relies on this plugin being enabled +plugin list instrumentation-runtime.BoundsSafety +# CHECK: [+] BoundsSafety + +run + +# This exists to check that the instrumentation correctly handles +# `__bounds_safety_soft_trap_s()` being called with a nullptr argument. + +# CHECK: * thread #{{.*}} stop reason = Soft Bounds check failed{{$}} + +# Check that the `__bounds_safety_soft_trap_s` frame is selected +bt +# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s{{$}} +# CHECK-NEXT: frame #{{.*}}`main + +# Resume execution +c +# CHECK: BoundsSafety check FAILED +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q diff --git a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test similarity index 81% rename from lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test rename to lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test index 6a67da6eeb358..a0abaecec0ddc 100644 --- a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test @@ -4,11 +4,15 @@ # RUN: %clang_host -c -O0 %S/Inputs/boundsSafetyMockSoftTrapRuntime.c -o %t.softtrap_runtime.o # RUN: %clang_host %t.o %t.softtrap_runtime.o -o %t.out # RUN: %lldb -b -s %s %t.out | FileCheck %s + +# Run without the plugin. A user might want to do this so they can set their +# own custom breakpoint with custom stopping behavior (e.g. stop after n hits). +plugin disable instrumentation-runtime.BoundsSafety +# CHECK: [-] BoundsSafety + b __bounds_safety_soft_trap_s run -# TODO: We should probably teach LLDB to recognize soft traps, set the stop -# reason appropriately and set a breakpoint automatically (rdar://163230807). # CHECK: * thread #{{.*}} stop reason = breakpoint 1.1 # Check that reason for bounds check failing can be seen in the stacktrace From 41aed56ec887b6fb56e63e249804986a6f792740 Mon Sep 17 00:00:00 2001 From: Dan Liew Date: Mon, 17 Nov 2025 16:30:00 -0800 Subject: [PATCH 3/4] Clean up getting the target and process in `TestBoundsSafetyInstrumentationPlugin.py` --- .../soft_trap/TestBoundsSafetyInstrumentationPlugin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py index 67629dbb1492c..00fd27cbaeda5 100644 --- a/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py +++ b/lldb/test/API/lang/BoundsSafety/soft_trap/TestBoundsSafetyInstrumentationPlugin.py @@ -17,7 +17,7 @@ def _check_stop_reason_impl(self, expected_func_name:str, expected_file_name:str, expected_line_num:int): - process = self.dbg.GetSelectedTarget().process + process = self.test_target.process thread = process.GetSelectedThread() self.assertEqual( thread.GetStopReason(), @@ -91,10 +91,10 @@ def test_call_minimal(self): -fbounds-safety-soft-traps=call-minimal """ self.build(make_targets=['soft-trap-test-minimal']) - self.createTestTarget() + self.test_target = self.createTestTarget() self.runCmd("run") - process = self.dbg.GetSelectedTarget().process + process = self.test_target.process # First soft trap hit self.check_state_soft_trap_minimal( @@ -119,10 +119,10 @@ def test_call_with_str(self): -fbounds-safety-soft-traps=call-with-str """ self.build(make_targets=['soft-trap-test-with-str']) - self.createTestTarget() + self.test_target = self.createTestTarget() self.runCmd("run") - process = self.dbg.GetSelectedTarget().process + process = self.test_target.process # First soft trap hit self.check_state_soft_trap_with_str( From 3c3ccb062e36385c6fdd19aa15f0b9b1f029d82c Mon Sep 17 00:00:00 2001 From: Dan Liew Date: Mon, 17 Nov 2025 16:58:11 -0800 Subject: [PATCH 4/4] Check the breakpoint has resolved locations --- .../BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp index 3dd2217baa28a..0c1afa80ab81c 100644 --- a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp @@ -314,8 +314,15 @@ void InstrumentationRuntimeBoundsSafety::Activate() { /*internal=*/true, /*request_hardware*/ false); + // TODO: Once we have an instrumentation log handle we should log these + // failures (rdar://164920875). if (!breakpoint) return; + if (!breakpoint->HasResolvedLocations()) { + assert(0 && "breakpoint has no resolved locations"); + process_sp->GetTarget().RemoveBreakpointByID(breakpoint->GetID()); + return; + } // Note: When `sync=true` the suggested stackframe is completely ignored. So // we use `sync=false`. Is that a bug?