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..0c1afa80ab81c --- /dev/null +++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp @@ -0,0 +1,343 @@ +//===-- 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); + + // 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? + 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..00fd27cbaeda5 --- /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.test_target.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.test_target = self.createTestTarget() + self.runCmd("run") + + process = self.test_target.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.test_target = self.createTestTarget() + self.runCmd("run") + + process = self.test_target.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/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..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,18 +1,23 @@ # 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 +# 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:18 +# 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.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test deleted file mode 100644 index c614fc09c1178..0000000000000 --- a/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str.test +++ /dev/null @@ -1,22 +0,0 @@ -# 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: %lldb -b -s %s %t.out | FileCheck %s -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 -bt -# CHECK: * frame #{{.*}}`__bounds_safety_soft_trap_s(reason="indexing above upper bound in 'array[index]'") -# 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 - -# Resume execution -c -# CHECK: BoundsSafety check FAILED: message:"indexing above upper bound in 'array[index]'" -# 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_no_plugin.test b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test new file mode 100644 index 0000000000000..a0abaecec0ddc --- /dev/null +++ b/lldb/test/Shell/BoundsSafety/boundssafety_soft_trap_call_with_str_no_plugin.test @@ -0,0 +1,29 @@ +# 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 + +# 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 + +# 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_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: message:"indexing above upper bound in 'array[index]'" +# CHECK-NEXT: Execution continued +# CHECK: Process {{[0-9]+}} exited with status = 0 +q