LLDB mainline
InstrumentationRuntimeBoundsSafety.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10
13#include "lldb/Core/Debugger.h"
14#include "lldb/Core/Module.h"
16#include "lldb/Symbol/Block.h"
17#include "lldb/Symbol/Symbol.h"
25#include "lldb/Target/Target.h"
26#include "lldb/Target/Thread.h"
29#include "clang/CodeGen/ModuleBuilder.h"
30
31#include <memory>
32#include <type_traits>
33
34using namespace lldb;
35using namespace lldb_private;
36
38
39constexpr llvm::StringLiteral
40 BoundsSafetySoftTrapMinimal("__bounds_safety_soft_trap");
41constexpr llvm::StringLiteral
42 BoundsSafetySoftTrapStr("__bounds_safety_soft_trap_s");
43
44constexpr std::array<llvm::StringLiteral, 2>
48
49#define SOFT_TRAP_CATEGORY_PREFIX "Soft "
50#define SOFT_TRAP_FALLBACK_CATEGORY \
51 SOFT_TRAP_CATEGORY_PREFIX "Bounds check failed"
52
54 std::pair<std::optional<std::string>, std::optional<uint32_t>>;
55
57public:
59
63
64 std::optional<uint32_t>
65 GetSuggestedStackFrameIndex(bool inlined_stack) override {
66 return m_value;
67 }
68
69 const char *GetDescription() override { return m_description.c_str(); }
70
71 bool DoShouldNotify(Event *event_ptr) override { return true; }
72
73 static lldb::StopInfoSP
77
78private:
80
82 ComputeStopReasonAndSuggestedStackFrame(bool &warning_emitted_for_failure);
83
85 lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id,
86 bool &warning_emitted_for_failure);
87
89 ThreadSP thread_sp, lldb::user_id_t debugger_id,
90 bool &warning_emitted_for_failure);
91};
92
94 Thread &thread)
95 : StopInfo(thread, 0) {
96 // No additional data describing the reason for stopping.
97 m_extended_info = nullptr;
99
100 bool warning_emitted_for_failure = false;
101 auto [MaybeDescription, MaybeSuggestedStackIndex] =
102 ComputeStopReasonAndSuggestedStackFrame(warning_emitted_for_failure);
103 if (MaybeDescription)
104 m_description = MaybeDescription.value();
105 if (MaybeSuggestedStackIndex)
106 m_value = MaybeSuggestedStackIndex.value();
107
108 // Emit warning about the failure to compute the stop info if one wasn't
109 // already emitted.
110 if ((!MaybeDescription.has_value()) && !warning_emitted_for_failure) {
111 if (ThreadSP thread_sp = GetThread()) {
112 lldb::user_id_t debugger_id =
113 thread_sp->GetProcess()->GetTarget().GetDebugger().GetID();
115 "specific BoundsSafety trap reason could not be computed",
116 debugger_id);
117 }
118 }
119}
120
121// Helper functions to make it convenient to log a failure and then return.
122template <typename T, typename... ArgTys>
123[[nodiscard]] T LogBeforeReturn(ArgTys &&...Args) {
125 return T();
126}
127
128template <typename... ArgTys>
129[[nodiscard]] ComputedStopInfo LogFailedCSI(ArgTys &&...Args) {
131}
132
135 bool &warning_emitted_for_failure) {
136 ThreadSP thread_sp = GetThread();
137 if (!thread_sp)
138 return LogFailedCSI("failed to get thread while stopped");
139
140 lldb::user_id_t debugger_id =
141 thread_sp->GetProcess()->GetTarget().GetDebugger().GetID();
142
143 StackFrameSP parent_sf = thread_sp->GetStackFrameAtIndex(1);
144 if (!parent_sf)
145 return LogFailedCSI("got nullptr when fetching stackframe at index 1");
146
147 if (parent_sf->HasDebugInformation())
149 parent_sf, debugger_id, warning_emitted_for_failure);
150
151 // If the debug info is missing we can still get some information
152 // from the parameter in the soft trap runtime call.
154 thread_sp, debugger_id, warning_emitted_for_failure);
155}
156
159 lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id,
160 bool &warning_emitted_for_failure) {
161 // First try to use debug info to understand the reason for trapping. The
162 // call stack will look something like this:
163 //
164 // ```
165 // frame #0: `__bounds_safety_soft_trap_s(reason="")
166 // frame #1: `__clang_trap_msg$Bounds check failed$<reason>'
167 // frame #2: `bad_read(index=10)
168 // ```
169 // ....
170 const char *TrapReasonFuncName = parent_sf->GetFunctionName();
171
172 auto MaybeTrapReason =
173 clang::CodeGen::DemangleTrapReasonInDebugInfo(TrapReasonFuncName);
174 if (!MaybeTrapReason.has_value())
175 return LogFailedCSI(
176 "clang::CodeGen::DemangleTrapReasonInDebugInfo(\"{0}\") call failed",
177 TrapReasonFuncName);
178
179 llvm::StringRef category = MaybeTrapReason.value().first;
180 llvm::StringRef message = MaybeTrapReason.value().second;
181
182 // TODO: Clang should probably be changed to emit the "Soft " prefix itself
183 std::string stop_reason;
184 llvm::raw_string_ostream ss(stop_reason);
186 if (category.empty())
187 ss << "<empty category>";
188 else
189 ss << category;
190 if (message.empty()) {
191 // This is not a failure so leave `warning_emitted_for_failure` untouched.
193 "specific BoundsSafety trap reason is not "
194 "available because the compiler omitted it from the debug info",
195 debugger_id);
196 } else {
197 ss << ": " << message;
198 }
199 // Use computed stop-reason and assume the parent of `parent_sf` is the
200 // the place in the user's code where the call to the soft trap runtime
201 // originated.
202 return std::make_pair(stop_reason, parent_sf->GetFrameIndex() + 1);
203}
204
207 ThreadSP thread_sp, lldb::user_id_t debugger_id,
208 bool &warning_emitted_for_failure) {
209
210 StackFrameSP softtrap_sf = thread_sp->GetStackFrameAtIndex(0);
211 if (!softtrap_sf)
212 return LogFailedCSI("got nullptr when fetching stackframe at index 0");
213 llvm::StringRef trap_reason_func_name = softtrap_sf->GetFunctionName();
214
215 if (trap_reason_func_name == BoundsSafetySoftTrapMinimal) {
216 // This function has no arguments so there's no additional information
217 // that would allow us to identify the trap reason.
218 //
219 // Use the fallback stop reason and the current frame.
220 // While we "could" set the suggested frame to our parent (where the
221 // bounds check failed), doing this leads to very misleading output in
222 // LLDB. E.g.:
223 //
224 // ```
225 // 0x100003b40 <+104>: bl 0x100003d64 ; __bounds_safety_soft_trap
226 // -> 0x100003b44 <+108>: b 0x100003b48 ; <+112>
227 // ```
228 //
229 // This makes it look we stopped after finishing the call to
230 // `__bounds_safety_soft_trap` but actually we are in the middle of the
231 // call. To avoid this confusion just use the current frame.
232 std::string warning;
233 llvm::raw_string_ostream ss(warning);
234 ss << "specific BoundsSafety trap reason is not available because debug "
235 "info is missing on the caller of '"
237 Debugger::ReportWarning(warning.c_str(), debugger_id);
238 warning_emitted_for_failure = true;
239 return {};
240 }
241
242 // __bounds_safety_soft_trap_s has one argument which is a pointer to a string
243 // describing the trap or a nullptr.
244 if (trap_reason_func_name != BoundsSafetySoftTrapStr) {
245 assert(0 && "hit breakpoint for unexpected function name");
246 return LogFailedCSI(
247 "unexpected function name. Expected \"{0}\" but got \"{1}\"",
248 BoundsSafetySoftTrapStr.data(), trap_reason_func_name.data());
249 }
250
251 RegisterContextSP rc = thread_sp->GetRegisterContext();
252 if (!rc)
253 return LogFailedCSI("failed to get register context");
254
255 // FIXME: LLDB should have an API that tells us for the current target if
256 // `LLDB_REGNUM_GENERIC_ARG1` can be used.
257 // https://github.com/llvm/llvm-project/issues/168602
258 // Don't try for architectures where examining the first register won't
259 // work.
260 ProcessSP process = thread_sp->GetProcess();
261 if (!process)
262 return LogFailedCSI("failed to get process");
263
264 switch (process->GetTarget().GetArchitecture().GetCore()) {
269 // Technically some x86 calling conventions do use a register for
270 // passing the first argument but let's ignore that for now.
271 std::string warning;
272 llvm::raw_string_ostream ss(warning);
273 ss << "specific BoundsSafety trap reason cannot be inferred on x86 when "
274 "the caller of '"
275 << BoundsSafetySoftTrapStr << "' is missing debug info";
276 Debugger::ReportWarning(warning.c_str(), debugger_id);
277 warning_emitted_for_failure = true;
278 return {};
279 }
280 default: {
281 }
282 };
283
284 // Examine the register for the first argument.
285 const RegisterInfo *arg0_info = rc->GetRegisterInfo(
287 if (!arg0_info)
288 return LogFailedCSI(
289 "failed to get register info for LLDB_REGNUM_GENERIC_ARG1");
290 RegisterValue reg_value;
291 if (!rc->ReadRegister(arg0_info, reg_value))
292 return LogFailedCSI("failed to read register {0}", arg0_info->name);
293 uint64_t reg_value_as_int = reg_value.GetAsUInt64(UINT64_MAX);
294 if (reg_value_as_int == UINT64_MAX)
295 return LogFailedCSI("failed to read register {0} as a UInt64",
296 arg0_info->name);
297
298 if (reg_value_as_int == 0) {
299 // nullptr arg. The compiler will pass that if no trap reason string was
300 // available.
302 "specific BoundsSafety trap reason cannot be inferred because the "
303 "compiler omitted the reason",
304 debugger_id);
305 warning_emitted_for_failure = true;
306 return {};
307 }
308
309 // The first argument to the call is a pointer to a global C string
310 // containing the trap reason.
311 std::string out_string;
312 Status error_status;
313 thread_sp->GetProcess()->ReadCStringFromMemory(reg_value_as_int, out_string,
314 error_status);
315 if (error_status.Fail())
316 return LogFailedCSI("failed to read C string from address {0}",
317 (void *)reg_value_as_int);
318
320 "read C string from {0} found in register {1}: \"{2}\"",
321 (void *)reg_value_as_int, arg0_info->name, out_string.c_str());
322 std::string stop_reason;
323 llvm::raw_string_ostream SS(stop_reason);
325 if (!stop_reason.empty()) {
326 SS << ": " << out_string;
327 }
328 // Use the current frame as the suggested frame for the same reason as for
329 // `__bounds_safety_soft_trap`.
330 return {stop_reason, 0};
331}
332
336
343
346 "BoundsSafety instrumentation runtime plugin.",
348}
349
353
358
359const RegularExpression &
364
366 const lldb::ModuleSP module_sp) {
368 for (const auto &SoftTrapFunc : getBoundsSafetySoftTrapRuntimeFuncs()) {
369 ConstString test_sym(SoftTrapFunc);
370
371 if (module_sp->FindFirstSymbolWithNameAndType(test_sym,
373 LLDB_LOG(log_category, "found \"{0}\" in {1}",
374 test_sym.AsCString("<unknown symbol>"),
375 module_sp->GetObjectName().AsCString("<unknown module>"));
376 return true;
377 }
378 }
379 LLDB_LOG(log_category,
380 "did not find BoundsSafety soft trap functions in module {0}",
381 module_sp->GetObjectName().AsCString("<unknown module>"));
382 return false;
383}
384
386 void *baton, StoppointCallbackContext *context, user_id_t break_id,
387 user_id_t break_loc_id) {
388 assert(baton && "null baton");
389 if (!baton)
390 return false; ///< false => resume execution.
391
392 InstrumentationRuntimeBoundsSafety *const instance =
393 static_cast<InstrumentationRuntimeBoundsSafety *>(baton);
394
395 ProcessSP process_sp = instance->GetProcessSP();
396 if (!process_sp)
397 return LogBeforeReturn<bool>("failed to get process from baton");
398 ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP();
399 if (!thread_sp)
401 "failed to get thread from StoppointCallbackContext");
402
403 if (process_sp != context->exe_ctx_ref.GetProcessSP())
405 "process from baton ({0}) and StoppointCallbackContext ({1}) do "
406 "not match",
407 (void *)process_sp.get(),
408 (void *)context->exe_ctx_ref.GetProcessSP().get());
409
410 if (process_sp->GetModIDRef().IsLastResumeForUserExpression())
411 return LogBeforeReturn<bool>("IsLastResumeForUserExpression is true");
412
413 // Maybe the stop reason and stackframe selection should be done by
414 // a stackframe recognizer instead?
415 thread_sp->SetStopInfo(
417 CreateInstrumentationBoundsSafetyStopInfo(*thread_sp));
418 return true;
419}
420
422 if (IsActive())
423 return;
424
425 ProcessSP process_sp = GetProcessSP();
426 if (!process_sp)
427 return LogBeforeReturn<void>("could not get process during Activate()");
428
429 std::vector<std::string> breakpoints;
430 for (auto &breakpoint_func : getBoundsSafetySoftTrapRuntimeFuncs())
431 breakpoints.emplace_back(breakpoint_func);
432
433 BreakpointSP breakpoint = process_sp->GetTarget().CreateBreakpoint(
434 /*containingModules=*/nullptr,
435 /*containingSourceFiles=*/nullptr, breakpoints, eFunctionNameTypeFull,
437 /*m_offset=*/0,
438 /*skip_prologue*/ eLazyBoolNo,
439 /*internal=*/true,
440 /*request_hardware*/ false);
441
442 if (!breakpoint)
443 return LogBeforeReturn<void>("failed to create breakpoint");
444
445 if (!breakpoint->HasResolvedLocations()) {
446 assert(0 && "breakpoint has no resolved locations");
447 process_sp->GetTarget().RemoveBreakpointByID(breakpoint->GetID());
449 "breakpoint {0} for BoundsSafety soft traps did not resolve to "
450 "any locations",
451 breakpoint->GetID());
452 }
453
454 // Note: When `sync=true` the suggested stackframe is completely ignored. So
455 // we use `sync=false`. Is that a bug?
456 breakpoint->SetCallback(
458 /*sync=*/false);
459 breakpoint->SetBreakpointKind("bounds-safety-soft-trap");
460 SetBreakpointID(breakpoint->GetID());
462 "created breakpoint {0} for BoundsSafety soft traps",
463 breakpoint->GetID());
464 SetActive(true);
465}
466
468 SetActive(false);
470 if (ProcessSP process_sp = GetProcessSP()) {
471 bool success =
472 process_sp->GetTarget().RemoveBreakpointByID(GetBreakpointID());
473 LLDB_LOG(log_category,
474 "{0}removed breakpoint {1} for BoundsSafety soft traps",
475 success ? "" : "failed to ", GetBreakpointID());
476 } else {
477 LLDB_LOG(log_category, "no process available during Deactivate()");
478 }
479
481}
static llvm::raw_ostream & warning(Stream &strm)
ComputedStopInfo LogFailedCSI(ArgTys &&...Args)
#define SOFT_TRAP_FALLBACK_CATEGORY
std::pair< std::optional< std::string >, std::optional< uint32_t > > ComputedStopInfo
#define SOFT_TRAP_CATEGORY_PREFIX
constexpr llvm::StringLiteral BoundsSafetySoftTrapMinimal("__bounds_safety_soft_trap")
T LogBeforeReturn(ArgTys &&...Args)
constexpr std::array< llvm::StringLiteral, 2 > getBoundsSafetySoftTrapRuntimeFuncs()
constexpr llvm::StringLiteral BoundsSafetySoftTrapStr("__bounds_safety_soft_trap_s")
#define LLDB_LOG(log,...)
The LLDB_LOG* macros defined below are the way to emit log messages.
Definition Log.h:369
#define LLDB_PLUGIN_DEFINE(PluginName)
~InstrumentationBoundsSafetyStopInfo() override=default
ComputedStopInfo ComputeStopReasonAndSuggestedStackFrameWithoutDebugInfo(ThreadSP thread_sp, lldb::user_id_t debugger_id, bool &warning_emitted_for_failure)
ComputedStopInfo ComputeStopReasonAndSuggestedStackFrameWithDebugInfo(lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id, bool &warning_emitted_for_failure)
std::optional< uint32_t > GetSuggestedStackFrameIndex(bool inlined_stack) override
This gives the StopInfo a chance to suggest a stack frame to select.
static lldb::StopInfoSP CreateInstrumentationBoundsSafetyStopInfo(Thread &thread)
ComputedStopInfo ComputeStopReasonAndSuggestedStackFrame(bool &warning_emitted_for_failure)
A command line argument class.
Definition Args.h:33
A uniqued constant string class.
Definition ConstString.h:40
const char * AsCString(const char *value_if_empty=nullptr) const
Get the string value as a C string.
static void ReportWarning(std::string message, std::optional< lldb::user_id_t > debugger_id=std::nullopt, std::once_flag *once=nullptr)
Report warning events.
lldb::ThreadSP GetThreadSP() const
Get accessor that creates a strong reference from the weak thread reference contained in this object.
lldb::ProcessSP GetProcessSP() const
Get accessor that creates a strong reference from the weak process reference contained in this object...
static lldb::InstrumentationRuntimeSP CreateInstance(const lldb::ProcessSP &process_sp)
static bool NotifyBreakpointHit(void *baton, StoppointCallbackContext *context, lldb::user_id_t break_id, lldb::user_id_t break_loc_id)
bool CheckIfRuntimeIsValid(const lldb::ModuleSP module_sp) override
Check whether module_sp corresponds to a valid runtime library.
const RegularExpression & GetPatternForRuntimeLibrary() override
Return a regular expression which can be used to identify a valid version of the runtime library.
void Activate() override
Register a breakpoint in the runtime library and perform any other necessary initialization.
static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, ABICreateInstance create_callback)
static bool UnregisterPlugin(ABICreateInstance create_callback)
uint64_t GetAsUInt64(uint64_t fail_value=UINT64_MAX, bool *success_ptr=nullptr) const
An error handling class.
Definition Status.h:118
bool Fail() const
Test for error condition.
Definition Status.cpp:294
std::string m_description
Definition StopInfo.h:227
StructuredData::ObjectSP m_extended_info
Definition StopInfo.h:232
lldb::ThreadSP GetThread() const
Definition StopInfo.h:35
friend class Thread
Definition StopInfo.h:245
StopInfo(Thread &thread, uint64_t value)
Definition StopInfo.cpp:34
General Outline: When we hit a breakpoint we need to package up whatever information is needed to eva...
#define UINT64_MAX
#define LLDB_INVALID_BREAK_ID
#define LLDB_REGNUM_GENERIC_ARG1
A class that represents a running process on the host machine.
Log * GetLog(Cat mask)
Retrieve the Log object for the channel associated with the given log enum.
Definition Log.h:332
std::shared_ptr< lldb_private::StackFrame > StackFrameSP
std::shared_ptr< lldb_private::Thread > ThreadSP
@ eLanguageTypeUnknown
Unknown or invalid language value.
std::shared_ptr< lldb_private::Breakpoint > BreakpointSP
std::shared_ptr< lldb_private::Process > ProcessSP
InstrumentationRuntimeType
@ eInstrumentationRuntimeTypeBoundsSafety
uint64_t user_id_t
Definition lldb-types.h:82
std::shared_ptr< lldb_private::StopInfo > StopInfoSP
StopReason
Thread stop reasons.
@ eStopReasonInstrumentation
std::shared_ptr< lldb_private::RegisterContext > RegisterContextSP
std::shared_ptr< lldb_private::InstrumentationRuntime > InstrumentationRuntimeSP
std::shared_ptr< lldb_private::Module > ModuleSP
@ eRegisterKindGeneric
insn ptr reg, stack ptr reg, etc not specific to any particular target
Every register is described in detail including its name, alternate name (optional),...
const char * name
Name of this register, can't be NULL.