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;
100
101 bool warning_emitted_for_failure = false;
102 auto [MaybeDescription, MaybeSuggestedStackIndex] =
103 ComputeStopReasonAndSuggestedStackFrame(warning_emitted_for_failure);
104 if (MaybeDescription)
105 m_description = MaybeDescription.value();
106 else
107 LLDB_LOG(log_category, "failed to compute description");
108
109 if (MaybeSuggestedStackIndex)
110 m_value = MaybeSuggestedStackIndex.value();
111 else
112 LLDB_LOG(log_category, "failed to compute suggested stack index");
113
114 // Emit warning about the failure to compute the stop info if one wasn't
115 // already emitted.
116 if ((!MaybeDescription.has_value()) && !warning_emitted_for_failure) {
117 if (ThreadSP thread_sp = GetThread()) {
118 lldb::user_id_t debugger_id =
119 thread_sp->GetProcess()->GetTarget().GetDebugger().GetID();
121 "specific BoundsSafety trap reason could not be computed",
122 debugger_id);
123 }
124 }
125
126 LLDB_LOG(log_category,
127 "computed InstrumentationBoundsSafetyStopInfo: stack index: {0}, "
128 "description:\"{1}\"",
130}
131
132// Helper functions to make it convenient to log a failure and then return.
133template <typename T, typename... ArgTys>
134[[nodiscard]] T LogBeforeReturn(ArgTys &&...Args) {
136 return T();
137}
138
139template <typename... ArgTys>
140[[nodiscard]] ComputedStopInfo LogFailedCSI(ArgTys &&...Args) {
142}
143
146 bool &warning_emitted_for_failure) {
147 ThreadSP thread_sp = GetThread();
149 if (!thread_sp)
150 return LogFailedCSI("failed to get thread while stopped");
151
152 lldb::user_id_t debugger_id =
153 thread_sp->GetProcess()->GetTarget().GetDebugger().GetID();
154
155 StackFrameSP parent_sf = thread_sp->GetStackFrameAtIndex(1);
156 if (!parent_sf)
157 return LogFailedCSI("got nullptr when fetching stackframe at index 1");
158
159 if (parent_sf->HasDebugInformation()) {
160 LLDB_LOG(log_category,
161 "frame {0} has debug info so trying to compute "
162 "BoundsSafety stop info from debug info",
163 parent_sf->GetFrameIndex());
165 parent_sf, debugger_id, warning_emitted_for_failure);
166 }
167
168 // If the debug info is missing we can still get some information
169 // from the parameter in the soft trap runtime call.
170 LLDB_LOG(log_category,
171 "frame {0} has no debug info so trying to compute "
172 "BoundsSafety stop info from registers",
173 parent_sf->GetFrameIndex());
175 thread_sp, debugger_id, warning_emitted_for_failure);
176}
177
180 lldb::StackFrameSP parent_sf, lldb::user_id_t debugger_id,
181 bool &warning_emitted_for_failure) {
182 // First try to use debug info to understand the reason for trapping. The
183 // call stack will look something like this:
184 //
185 // ```
186 // frame #0: `__bounds_safety_soft_trap_s(reason="")
187 // frame #1: `__clang_trap_msg$Bounds check failed$<reason>'
188 // frame #2: `bad_read(index=10)
189 // ```
190 // ....
191 const char *TrapReasonFuncName = parent_sf->GetFunctionName();
192
193 auto MaybeTrapReason =
194 clang::CodeGen::DemangleTrapReasonInDebugInfo(TrapReasonFuncName);
195 if (!MaybeTrapReason.has_value())
196 return LogFailedCSI(
197 "clang::CodeGen::DemangleTrapReasonInDebugInfo(\"{0}\") call failed",
198 TrapReasonFuncName);
199
200 llvm::StringRef category = MaybeTrapReason.value().first;
201 llvm::StringRef message = MaybeTrapReason.value().second;
202
203 // TODO: Clang should probably be changed to emit the "Soft " prefix itself
204 std::string stop_reason;
205 llvm::raw_string_ostream ss(stop_reason);
207 if (category.empty())
208 ss << "<empty category>";
209 else
210 ss << category;
211 if (message.empty()) {
212 // This is not a failure so leave `warning_emitted_for_failure` untouched.
214 "specific BoundsSafety trap reason is not "
215 "available because the compiler omitted it from the debug info",
216 debugger_id);
217 } else {
218 ss << ": " << message;
219 }
220 // Use computed stop-reason and assume the parent of `parent_sf` is the
221 // the place in the user's code where the call to the soft trap runtime
222 // originated.
223 return std::make_pair(stop_reason, parent_sf->GetFrameIndex() + 1);
224}
225
228 ThreadSP thread_sp, lldb::user_id_t debugger_id,
229 bool &warning_emitted_for_failure) {
230
231 StackFrameSP softtrap_sf = thread_sp->GetStackFrameAtIndex(0);
232 if (!softtrap_sf)
233 return LogFailedCSI("got nullptr when fetching stackframe at index 0");
234 llvm::StringRef trap_reason_func_name = softtrap_sf->GetFunctionName();
235
236 if (trap_reason_func_name == BoundsSafetySoftTrapMinimal) {
237 // This function has no arguments so there's no additional information
238 // that would allow us to identify the trap reason.
239 //
240 // Use the fallback stop reason and the current frame.
241 // While we "could" set the suggested frame to our parent (where the
242 // bounds check failed), doing this leads to very misleading output in
243 // LLDB. E.g.:
244 //
245 // ```
246 // 0x100003b40 <+104>: bl 0x100003d64 ; __bounds_safety_soft_trap
247 // -> 0x100003b44 <+108>: b 0x100003b48 ; <+112>
248 // ```
249 //
250 // This makes it look we stopped after finishing the call to
251 // `__bounds_safety_soft_trap` but actually we are in the middle of the
252 // call. To avoid this confusion just use the current frame.
253 std::string warning;
254 llvm::raw_string_ostream ss(warning);
255 ss << "specific BoundsSafety trap reason is not available because debug "
256 "info is missing on the caller of '"
258 Debugger::ReportWarning(warning.c_str(), debugger_id);
259 warning_emitted_for_failure = true;
260 return {};
261 }
262
263 // __bounds_safety_soft_trap_s has one argument which is a pointer to a string
264 // describing the trap or a nullptr.
265 if (trap_reason_func_name != BoundsSafetySoftTrapStr) {
266 assert(0 && "hit breakpoint for unexpected function name");
267 return LogFailedCSI(
268 "unexpected function name. Expected \"{0}\" but got \"{1}\"",
269 BoundsSafetySoftTrapStr.data(), trap_reason_func_name.data());
270 }
271
272 RegisterContextSP rc = thread_sp->GetRegisterContext();
273 if (!rc)
274 return LogFailedCSI("failed to get register context");
275
276 // FIXME: LLDB should have an API that tells us for the current target if
277 // `LLDB_REGNUM_GENERIC_ARG1` can be used.
278 // https://github.com/llvm/llvm-project/issues/168602
279 // Don't try for architectures where examining the first register won't
280 // work.
281 ProcessSP process = thread_sp->GetProcess();
282 if (!process)
283 return LogFailedCSI("failed to get process");
284
285 switch (process->GetTarget().GetArchitecture().GetCore()) {
290 // Technically some x86 calling conventions do use a register for
291 // passing the first argument but let's ignore that for now.
292 std::string warning;
293 llvm::raw_string_ostream ss(warning);
294 ss << "specific BoundsSafety trap reason cannot be inferred on x86 when "
295 "the caller of '"
296 << BoundsSafetySoftTrapStr << "' is missing debug info";
297 Debugger::ReportWarning(warning.c_str(), debugger_id);
298 warning_emitted_for_failure = true;
299 return {};
300 }
301 default: {
302 }
303 };
304
305 // Examine the register for the first argument.
306 const RegisterInfo *arg0_info = rc->GetRegisterInfo(
308 if (!arg0_info)
309 return LogFailedCSI(
310 "failed to get register info for LLDB_REGNUM_GENERIC_ARG1");
311 RegisterValue reg_value;
312 if (!rc->ReadRegister(arg0_info, reg_value))
313 return LogFailedCSI("failed to read register {0}", arg0_info->name);
314 uint64_t reg_value_as_int = reg_value.GetAsUInt64(UINT64_MAX);
315 if (reg_value_as_int == UINT64_MAX)
316 return LogFailedCSI("failed to read register {0} as a UInt64",
317 arg0_info->name);
318
319 if (reg_value_as_int == 0) {
320 // nullptr arg. The compiler will pass that if no trap reason string was
321 // available.
323 "specific BoundsSafety trap reason cannot be inferred because the "
324 "compiler omitted the reason",
325 debugger_id);
326 warning_emitted_for_failure = true;
327 return {};
328 }
329
330 // The first argument to the call is a pointer to a global C string
331 // containing the trap reason.
332 std::string out_string;
333 Status error_status;
334 thread_sp->GetProcess()->ReadCStringFromMemory(reg_value_as_int, out_string,
335 error_status);
336 if (error_status.Fail())
337 return LogFailedCSI("failed to read C string from address {0}",
338 (void *)reg_value_as_int);
339
341 "read C string from {0} found in register {1}: \"{2}\"",
342 (void *)reg_value_as_int, arg0_info->name, out_string.c_str());
343 std::string stop_reason;
344 llvm::raw_string_ostream SS(stop_reason);
346 if (!stop_reason.empty()) {
347 SS << ": " << out_string;
348 }
349 // Use the current frame as the suggested frame for the same reason as for
350 // `__bounds_safety_soft_trap`.
351 return {stop_reason, 0};
352}
353
357
364
367 "BoundsSafety instrumentation runtime plugin.",
369}
370
374
379
380const RegularExpression &
385
387 const lldb::ModuleSP module_sp) {
389 for (const auto &SoftTrapFunc : getBoundsSafetySoftTrapRuntimeFuncs()) {
390 ConstString test_sym(SoftTrapFunc);
391
392 if (module_sp->FindFirstSymbolWithNameAndType(test_sym,
394 LLDB_LOG(log_category, "found \"{0}\" in {1}",
395 test_sym.AsCString("<unknown symbol>"),
396 module_sp->GetFileSpec().GetPath());
397 return true;
398 }
399 }
400 LLDB_LOG(log_category,
401 "did not find BoundsSafety soft trap functions in module {0}",
402 module_sp->GetFileSpec().GetPath());
403 return false;
404}
405
407 void *baton, StoppointCallbackContext *context, user_id_t break_id,
408 user_id_t break_loc_id) {
409 assert(baton && "null baton");
410 if (!baton)
411 return false; ///< false => resume execution.
412
413 InstrumentationRuntimeBoundsSafety *const instance =
414 static_cast<InstrumentationRuntimeBoundsSafety *>(baton);
415
416 ProcessSP process_sp = instance->GetProcessSP();
417 if (!process_sp)
418 return LogBeforeReturn<bool>("failed to get process from baton");
419 ThreadSP thread_sp = context->exe_ctx_ref.GetThreadSP();
420 if (!thread_sp)
422 "failed to get thread from StoppointCallbackContext");
423
424 if (process_sp != context->exe_ctx_ref.GetProcessSP())
426 "process from baton ({0}) and StoppointCallbackContext ({1}) do "
427 "not match",
428 (void *)process_sp.get(),
429 (void *)context->exe_ctx_ref.GetProcessSP().get());
430
431 if (process_sp->GetModIDRef().IsLastResumeForUserExpression())
432 return LogBeforeReturn<bool>("IsLastResumeForUserExpression is true");
433
434 // Maybe the stop reason and stackframe selection should be done by
435 // a stackframe recognizer instead?
436 thread_sp->SetStopInfo(
438 CreateInstrumentationBoundsSafetyStopInfo(*thread_sp));
439 return true;
440}
441
443 if (IsActive())
444 return;
445
446 ProcessSP process_sp = GetProcessSP();
447 if (!process_sp)
448 return LogBeforeReturn<void>("could not get process during Activate()");
449
450 std::vector<std::string> breakpoints;
451 for (auto &breakpoint_func : getBoundsSafetySoftTrapRuntimeFuncs())
452 breakpoints.emplace_back(breakpoint_func);
453
454 BreakpointSP breakpoint = process_sp->GetTarget().CreateBreakpoint(
455 /*containingModules=*/nullptr,
456 /*containingSourceFiles=*/nullptr, breakpoints, eFunctionNameTypeFull,
458 /*m_offset=*/0,
459 /*skip_prologue*/ eLazyBoolNo,
460 /*internal=*/true,
461 /*request_hardware*/ false);
462
463 if (!breakpoint)
464 return LogBeforeReturn<void>("failed to create breakpoint");
465
466 if (!breakpoint->HasResolvedLocations()) {
467 assert(0 && "breakpoint has no resolved locations");
468 process_sp->GetTarget().RemoveBreakpointByID(breakpoint->GetID());
470 "breakpoint {0} for BoundsSafety soft traps did not resolve to "
471 "any locations",
472 breakpoint->GetID());
473 }
474
475 // Note: When `sync=true` the suggested stackframe is completely ignored. So
476 // we use `sync=false`. Is that a bug?
477 breakpoint->SetCallback(
479 /*sync=*/false);
480 breakpoint->SetBreakpointKind("bounds-safety-soft-trap");
481 SetBreakpointID(breakpoint->GetID());
483 "created breakpoint {0} for BoundsSafety soft traps",
484 breakpoint->GetID());
485 SetActive(true);
486}
487
489 SetActive(false);
491 if (ProcessSP process_sp = GetProcessSP()) {
492 bool success =
493 process_sp->GetTarget().RemoveBreakpointByID(GetBreakpointID());
494 // FIXME: GetBreakPointID() uses `lldb::user_id_t` which is an unsigned
495 // type but it should be using `break_id_t` which is a signed type. For now
496 // just use the right type in the format string so the breakpoint ID is
497 // printed correctly.
498 LLDB_LOG(log_category,
499 "{0}removed breakpoint {1} for BoundsSafety soft traps",
500 success ? "" : "failed to ",
501 static_cast<break_id_t>(GetBreakpointID()));
502 } else {
503 LLDB_LOG(log_category, "no process available during Deactivate()");
504 }
505
507}
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
int32_t break_id_t
Definition lldb-types.h:86
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.