1 | //===-- Watchpoint.cpp ----------------------------------------------------===// |
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 | |
9 | #include "lldb/Breakpoint/Watchpoint.h" |
10 | |
11 | #include "lldb/Breakpoint/StoppointCallbackContext.h" |
12 | #include "lldb/Breakpoint/WatchpointResource.h" |
13 | #include "lldb/Core/Value.h" |
14 | #include "lldb/Core/ValueObject.h" |
15 | #include "lldb/Core/ValueObjectMemory.h" |
16 | #include "lldb/DataFormatters/DumpValueObjectOptions.h" |
17 | #include "lldb/Expression/UserExpression.h" |
18 | #include "lldb/Symbol/TypeSystem.h" |
19 | #include "lldb/Target/Process.h" |
20 | #include "lldb/Target/Target.h" |
21 | #include "lldb/Target/ThreadSpec.h" |
22 | #include "lldb/Utility/LLDBLog.h" |
23 | #include "lldb/Utility/Log.h" |
24 | #include "lldb/Utility/Stream.h" |
25 | |
26 | using namespace lldb; |
27 | using namespace lldb_private; |
28 | |
29 | Watchpoint::Watchpoint(Target &target, lldb::addr_t addr, uint32_t size, |
30 | const CompilerType *type, bool hardware) |
31 | : StoppointSite(0, addr, size, hardware), m_target(target), |
32 | m_enabled(false), m_is_hardware(hardware), m_is_watch_variable(false), |
33 | m_is_ephemeral(false), m_disabled_count(0), m_watch_read(0), |
34 | m_watch_write(0), m_watch_modify(0), m_ignore_count(0) { |
35 | |
36 | if (type && type->IsValid()) |
37 | m_type = *type; |
38 | else { |
39 | // If we don't have a known type, then we force it to unsigned int of the |
40 | // right size. |
41 | auto type_system_or_err = |
42 | target.GetScratchTypeSystemForLanguage(language: eLanguageTypeC); |
43 | if (auto err = type_system_or_err.takeError()) { |
44 | LLDB_LOG_ERROR(GetLog(LLDBLog::Watchpoints), std::move(err), |
45 | "Failed to set type: {0}" ); |
46 | } else { |
47 | if (auto ts = *type_system_or_err) { |
48 | if (size <= target.GetArchitecture().GetAddressByteSize()) { |
49 | m_type = |
50 | ts->GetBuiltinTypeForEncodingAndBitSize(encoding: eEncodingUint, bit_size: 8 * size); |
51 | } else { |
52 | CompilerType clang_uint8_type = |
53 | ts->GetBuiltinTypeForEncodingAndBitSize(encoding: eEncodingUint, bit_size: 8); |
54 | m_type = clang_uint8_type.GetArrayType(size); |
55 | } |
56 | } else |
57 | LLDB_LOG_ERROR(GetLog(LLDBLog::Watchpoints), std::move(err), |
58 | "Failed to set type: Typesystem is no longer live: {0}" ); |
59 | } |
60 | } |
61 | |
62 | // Set the initial value of the watched variable: |
63 | if (m_target.GetProcessSP()) { |
64 | ExecutionContext exe_ctx; |
65 | m_target.GetProcessSP()->CalculateExecutionContext(exe_ctx); |
66 | CaptureWatchedValue(exe_ctx); |
67 | } |
68 | } |
69 | |
70 | Watchpoint::~Watchpoint() = default; |
71 | |
72 | // This function is used when "baton" doesn't need to be freed |
73 | void Watchpoint::SetCallback(WatchpointHitCallback callback, void *baton, |
74 | bool is_synchronous) { |
75 | // The default "Baton" class will keep a copy of "baton" and won't free or |
76 | // delete it when it goes out of scope. |
77 | m_options.SetCallback(callback, baton_sp: std::make_shared<UntypedBaton>(args&: baton), |
78 | synchronous: is_synchronous); |
79 | |
80 | SendWatchpointChangedEvent(eventKind: eWatchpointEventTypeCommandChanged); |
81 | } |
82 | |
83 | // This function is used when a baton needs to be freed and therefore is |
84 | // contained in a "Baton" subclass. |
85 | void Watchpoint::SetCallback(WatchpointHitCallback callback, |
86 | const BatonSP &callback_baton_sp, |
87 | bool is_synchronous) { |
88 | m_options.SetCallback(callback, baton_sp: callback_baton_sp, synchronous: is_synchronous); |
89 | SendWatchpointChangedEvent(eventKind: eWatchpointEventTypeCommandChanged); |
90 | } |
91 | |
92 | bool Watchpoint::SetupVariableWatchpointDisabler(StackFrameSP frame_sp) const { |
93 | if (!frame_sp) |
94 | return false; |
95 | |
96 | ThreadSP thread_sp = frame_sp->GetThread(); |
97 | if (!thread_sp) |
98 | return false; |
99 | |
100 | uint32_t return_frame_index = |
101 | thread_sp->GetSelectedFrameIndex(select_most_relevant: DoNoSelectMostRelevantFrame) + 1; |
102 | if (return_frame_index >= LLDB_INVALID_FRAME_ID) |
103 | return false; |
104 | |
105 | StackFrameSP return_frame_sp( |
106 | thread_sp->GetStackFrameAtIndex(idx: return_frame_index)); |
107 | if (!return_frame_sp) |
108 | return false; |
109 | |
110 | ExecutionContext exe_ctx(return_frame_sp); |
111 | TargetSP target_sp = exe_ctx.GetTargetSP(); |
112 | if (!target_sp) |
113 | return false; |
114 | |
115 | Address return_address(return_frame_sp->GetFrameCodeAddress()); |
116 | lldb::addr_t return_addr = return_address.GetLoadAddress(target: target_sp.get()); |
117 | if (return_addr == LLDB_INVALID_ADDRESS) |
118 | return false; |
119 | |
120 | BreakpointSP bp_sp = target_sp->CreateBreakpoint( |
121 | load_addr: return_addr, /*internal=*/true, /*request_hardware=*/false); |
122 | if (!bp_sp || !bp_sp->HasResolvedLocations()) |
123 | return false; |
124 | |
125 | auto wvc_up = std::make_unique<WatchpointVariableContext>(args: GetID(), args&: exe_ctx); |
126 | auto baton_sp = std::make_shared<WatchpointVariableBaton>(args: std::move(wvc_up)); |
127 | bp_sp->SetCallback(callback: VariableWatchpointDisabler, callback_baton_sp: baton_sp); |
128 | bp_sp->SetOneShot(true); |
129 | bp_sp->SetBreakpointKind("variable watchpoint disabler" ); |
130 | return true; |
131 | } |
132 | |
133 | bool Watchpoint::VariableWatchpointDisabler(void *baton, |
134 | StoppointCallbackContext *context, |
135 | user_id_t break_id, |
136 | user_id_t break_loc_id) { |
137 | assert(baton && "null baton" ); |
138 | if (!baton || !context) |
139 | return false; |
140 | |
141 | Log *log = GetLog(mask: LLDBLog::Watchpoints); |
142 | |
143 | WatchpointVariableContext *wvc = |
144 | static_cast<WatchpointVariableContext *>(baton); |
145 | |
146 | LLDB_LOGF(log, "called by breakpoint %" PRIu64 ".%" PRIu64, break_id, |
147 | break_loc_id); |
148 | |
149 | if (wvc->watch_id == LLDB_INVALID_WATCH_ID) |
150 | return false; |
151 | |
152 | TargetSP target_sp = context->exe_ctx_ref.GetTargetSP(); |
153 | if (!target_sp) |
154 | return false; |
155 | |
156 | ProcessSP process_sp = target_sp->GetProcessSP(); |
157 | if (!process_sp) |
158 | return false; |
159 | |
160 | WatchpointSP watch_sp = |
161 | target_sp->GetWatchpointList().FindByID(watchID: wvc->watch_id); |
162 | if (!watch_sp) |
163 | return false; |
164 | |
165 | if (wvc->exe_ctx == context->exe_ctx_ref) { |
166 | LLDB_LOGF(log, |
167 | "callback for watchpoint %" PRId32 |
168 | " matched internal breakpoint execution context" , |
169 | watch_sp->GetID()); |
170 | process_sp->DisableWatchpoint(wp_sp: watch_sp); |
171 | return false; |
172 | } |
173 | LLDB_LOGF(log, |
174 | "callback for watchpoint %" PRId32 |
175 | " didn't match internal breakpoint execution context" , |
176 | watch_sp->GetID()); |
177 | return false; |
178 | } |
179 | |
180 | void Watchpoint::ClearCallback() { |
181 | m_options.ClearCallback(); |
182 | SendWatchpointChangedEvent(eventKind: eWatchpointEventTypeCommandChanged); |
183 | } |
184 | |
185 | void Watchpoint::SetDeclInfo(const std::string &str) { m_decl_str = str; } |
186 | |
187 | std::string Watchpoint::GetWatchSpec() { return m_watch_spec_str; } |
188 | |
189 | void Watchpoint::SetWatchSpec(const std::string &str) { |
190 | m_watch_spec_str = str; |
191 | } |
192 | |
193 | bool Watchpoint::IsHardware() const { |
194 | lldbassert(m_is_hardware || !HardwareRequired()); |
195 | return m_is_hardware; |
196 | } |
197 | |
198 | bool Watchpoint::IsWatchVariable() const { return m_is_watch_variable; } |
199 | |
200 | void Watchpoint::SetWatchVariable(bool val) { m_is_watch_variable = val; } |
201 | |
202 | bool Watchpoint::CaptureWatchedValue(const ExecutionContext &exe_ctx) { |
203 | ConstString g_watch_name("$__lldb__watch_value" ); |
204 | m_old_value_sp = m_new_value_sp; |
205 | Address watch_address(GetLoadAddress()); |
206 | if (!m_type.IsValid()) { |
207 | // Don't know how to report new & old values, since we couldn't make a |
208 | // scalar type for this watchpoint. This works around an assert in |
209 | // ValueObjectMemory::Create. |
210 | // FIXME: This should not happen, but if it does in some case we care about, |
211 | // we can go grab the value raw and print it as unsigned. |
212 | return false; |
213 | } |
214 | m_new_value_sp = ValueObjectMemory::Create( |
215 | exe_scope: exe_ctx.GetBestExecutionContextScope(), name: g_watch_name.GetStringRef(), |
216 | address: watch_address, ast_type: m_type); |
217 | m_new_value_sp = m_new_value_sp->CreateConstantValue(name: g_watch_name); |
218 | return (m_new_value_sp && m_new_value_sp->GetError().Success()); |
219 | } |
220 | |
221 | bool Watchpoint::WatchedValueReportable(const ExecutionContext &exe_ctx) { |
222 | if (!m_watch_modify || m_watch_read) |
223 | return true; |
224 | if (!m_type.IsValid()) |
225 | return true; |
226 | |
227 | ConstString g_watch_name("$__lldb__watch_value" ); |
228 | Address watch_address(GetLoadAddress()); |
229 | ValueObjectSP newest_valueobj_sp = ValueObjectMemory::Create( |
230 | exe_scope: exe_ctx.GetBestExecutionContextScope(), name: g_watch_name.GetStringRef(), |
231 | address: watch_address, ast_type: m_type); |
232 | newest_valueobj_sp = newest_valueobj_sp->CreateConstantValue(name: g_watch_name); |
233 | Status error; |
234 | |
235 | DataExtractor new_data; |
236 | DataExtractor old_data; |
237 | |
238 | newest_valueobj_sp->GetData(data&: new_data, error); |
239 | if (error.Fail()) |
240 | return true; |
241 | m_new_value_sp->GetData(data&: old_data, error); |
242 | if (error.Fail()) |
243 | return true; |
244 | |
245 | if (new_data.GetByteSize() != old_data.GetByteSize() || |
246 | new_data.GetByteSize() == 0) |
247 | return true; |
248 | |
249 | if (memcmp(s1: new_data.GetDataStart(), s2: old_data.GetDataStart(), |
250 | n: old_data.GetByteSize()) == 0) |
251 | return false; // Value has not changed, user requested modify watchpoint |
252 | |
253 | return true; |
254 | } |
255 | |
256 | // RETURNS - true if we should stop at this breakpoint, false if we |
257 | // should continue. |
258 | |
259 | bool Watchpoint::ShouldStop(StoppointCallbackContext *context) { |
260 | m_hit_counter.Increment(); |
261 | |
262 | return IsEnabled(); |
263 | } |
264 | |
265 | void Watchpoint::GetDescription(Stream *s, lldb::DescriptionLevel level) { |
266 | DumpWithLevel(s, description_level: level); |
267 | } |
268 | |
269 | void Watchpoint::Dump(Stream *s) const { |
270 | DumpWithLevel(s, description_level: lldb::eDescriptionLevelBrief); |
271 | } |
272 | |
273 | // If prefix is nullptr, we display the watch id and ignore the prefix |
274 | // altogether. |
275 | bool Watchpoint::DumpSnapshots(Stream *s, const char *prefix) const { |
276 | bool printed_anything = false; |
277 | |
278 | // For read watchpoints, don't display any before/after value changes. |
279 | if (m_watch_read && !m_watch_modify && !m_watch_write) |
280 | return printed_anything; |
281 | |
282 | s->Printf(format: "\n" ); |
283 | s->Printf(format: "Watchpoint %u hit:\n" , GetID()); |
284 | |
285 | StreamString values_ss; |
286 | if (prefix) |
287 | values_ss.Indent(s: prefix); |
288 | |
289 | if (m_old_value_sp) { |
290 | if (auto *old_value_cstr = m_old_value_sp->GetValueAsCString()) { |
291 | values_ss.Printf(format: "old value: %s" , old_value_cstr); |
292 | } else { |
293 | if (auto *old_summary_cstr = m_old_value_sp->GetSummaryAsCString()) |
294 | values_ss.Printf(format: "old value: %s" , old_summary_cstr); |
295 | else { |
296 | StreamString strm; |
297 | DumpValueObjectOptions options; |
298 | options.SetUseDynamicType(eNoDynamicValues) |
299 | .SetHideRootType(true) |
300 | .SetHideRootName(true) |
301 | .SetHideName(true); |
302 | m_old_value_sp->Dump(s&: strm, options); |
303 | if (strm.GetData()) |
304 | values_ss.Printf(format: "old value: %s" , strm.GetData()); |
305 | } |
306 | } |
307 | } |
308 | |
309 | if (m_new_value_sp) { |
310 | if (values_ss.GetSize()) |
311 | values_ss.Printf(format: "\n" ); |
312 | |
313 | if (auto *new_value_cstr = m_new_value_sp->GetValueAsCString()) |
314 | values_ss.Printf(format: "new value: %s" , new_value_cstr); |
315 | else { |
316 | if (auto *new_summary_cstr = m_new_value_sp->GetSummaryAsCString()) |
317 | values_ss.Printf(format: "new value: %s" , new_summary_cstr); |
318 | else { |
319 | StreamString strm; |
320 | DumpValueObjectOptions options; |
321 | options.SetUseDynamicType(eNoDynamicValues) |
322 | .SetHideRootType(true) |
323 | .SetHideRootName(true) |
324 | .SetHideName(true); |
325 | m_new_value_sp->Dump(s&: strm, options); |
326 | if (strm.GetData()) |
327 | values_ss.Printf(format: "new value: %s" , strm.GetData()); |
328 | } |
329 | } |
330 | } |
331 | |
332 | if (values_ss.GetSize()) { |
333 | s->Printf(format: "%s" , values_ss.GetData()); |
334 | printed_anything = true; |
335 | } |
336 | |
337 | return printed_anything; |
338 | } |
339 | |
340 | void Watchpoint::DumpWithLevel(Stream *s, |
341 | lldb::DescriptionLevel description_level) const { |
342 | if (s == nullptr) |
343 | return; |
344 | |
345 | assert(description_level >= lldb::eDescriptionLevelBrief && |
346 | description_level <= lldb::eDescriptionLevelVerbose); |
347 | |
348 | s->Printf(format: "Watchpoint %u: addr = 0x%8.8" PRIx64 |
349 | " size = %u state = %s type = %s%s%s" , |
350 | GetID(), GetLoadAddress(), m_byte_size, |
351 | IsEnabled() ? "enabled" : "disabled" , m_watch_read ? "r" : "" , |
352 | m_watch_write ? "w" : "" , m_watch_modify ? "m" : "" ); |
353 | |
354 | if (description_level >= lldb::eDescriptionLevelFull) { |
355 | if (!m_decl_str.empty()) |
356 | s->Printf(format: "\n declare @ '%s'" , m_decl_str.c_str()); |
357 | if (!m_watch_spec_str.empty()) |
358 | s->Printf(format: "\n watchpoint spec = '%s'" , m_watch_spec_str.c_str()); |
359 | if (IsEnabled()) { |
360 | if (ProcessSP process_sp = m_target.GetProcessSP()) { |
361 | auto &resourcelist = process_sp->GetWatchpointResourceList(); |
362 | size_t idx = 0; |
363 | s->Printf(format: "\n watchpoint resources:" ); |
364 | for (WatchpointResourceSP &wpres : resourcelist.Sites()) { |
365 | if (wpres->ConstituentsContains(wp: this)) { |
366 | s->Printf(format: "\n #%zu: " , idx); |
367 | wpres->Dump(s); |
368 | } |
369 | idx++; |
370 | } |
371 | } |
372 | } |
373 | |
374 | // Dump the snapshots we have taken. |
375 | DumpSnapshots(s, prefix: " " ); |
376 | |
377 | if (GetConditionText()) |
378 | s->Printf(format: "\n condition = '%s'" , GetConditionText()); |
379 | m_options.GetCallbackDescription(s, level: description_level); |
380 | } |
381 | |
382 | if (description_level >= lldb::eDescriptionLevelVerbose) { |
383 | s->Printf(format: "\n hit_count = %-4u ignore_count = %-4u" , GetHitCount(), |
384 | GetIgnoreCount()); |
385 | } |
386 | } |
387 | |
388 | bool Watchpoint::IsEnabled() const { return m_enabled; } |
389 | |
390 | // Within StopInfo.cpp, we purposely turn on the ephemeral mode right before |
391 | // temporarily disable the watchpoint in order to perform possible watchpoint |
392 | // actions without triggering further watchpoint events. After the temporary |
393 | // disabled watchpoint is enabled, we then turn off the ephemeral mode. |
394 | |
395 | void Watchpoint::TurnOnEphemeralMode() { m_is_ephemeral = true; } |
396 | |
397 | void Watchpoint::TurnOffEphemeralMode() { |
398 | m_is_ephemeral = false; |
399 | // Leaving ephemeral mode, reset the m_disabled_count! |
400 | m_disabled_count = 0; |
401 | } |
402 | |
403 | bool Watchpoint::IsDisabledDuringEphemeralMode() { |
404 | return m_disabled_count > 1 && m_is_ephemeral; |
405 | } |
406 | |
407 | void Watchpoint::SetEnabled(bool enabled, bool notify) { |
408 | if (!enabled) { |
409 | if (m_is_ephemeral) |
410 | ++m_disabled_count; |
411 | |
412 | // Don't clear the snapshots for now. |
413 | // Within StopInfo.cpp, we purposely do disable/enable watchpoint while |
414 | // performing watchpoint actions. |
415 | } |
416 | bool changed = enabled != m_enabled; |
417 | m_enabled = enabled; |
418 | if (notify && !m_is_ephemeral && changed) |
419 | SendWatchpointChangedEvent(eventKind: enabled ? eWatchpointEventTypeEnabled |
420 | : eWatchpointEventTypeDisabled); |
421 | } |
422 | |
423 | void Watchpoint::SetWatchpointType(uint32_t type, bool notify) { |
424 | int old_watch_read = m_watch_read; |
425 | int old_watch_write = m_watch_write; |
426 | int old_watch_modify = m_watch_modify; |
427 | m_watch_read = (type & LLDB_WATCH_TYPE_READ) != 0; |
428 | m_watch_write = (type & LLDB_WATCH_TYPE_WRITE) != 0; |
429 | m_watch_modify = (type & LLDB_WATCH_TYPE_MODIFY) != 0; |
430 | if (notify && |
431 | (old_watch_read != m_watch_read || old_watch_write != m_watch_write || |
432 | old_watch_modify != m_watch_modify)) |
433 | SendWatchpointChangedEvent(eventKind: eWatchpointEventTypeTypeChanged); |
434 | } |
435 | |
436 | bool Watchpoint::WatchpointRead() const { return m_watch_read != 0; } |
437 | |
438 | bool Watchpoint::WatchpointWrite() const { return m_watch_write != 0; } |
439 | |
440 | bool Watchpoint::WatchpointModify() const { return m_watch_modify != 0; } |
441 | |
442 | uint32_t Watchpoint::GetIgnoreCount() const { return m_ignore_count; } |
443 | |
444 | void Watchpoint::SetIgnoreCount(uint32_t n) { |
445 | bool changed = m_ignore_count != n; |
446 | m_ignore_count = n; |
447 | if (changed) |
448 | SendWatchpointChangedEvent(eventKind: eWatchpointEventTypeIgnoreChanged); |
449 | } |
450 | |
451 | bool Watchpoint::InvokeCallback(StoppointCallbackContext *context) { |
452 | return m_options.InvokeCallback(context, watch_id: GetID()); |
453 | } |
454 | |
455 | void Watchpoint::SetCondition(const char *condition) { |
456 | if (condition == nullptr || condition[0] == '\0') { |
457 | if (m_condition_up) |
458 | m_condition_up.reset(); |
459 | } else { |
460 | // Pass nullptr for expr_prefix (no translation-unit level definitions). |
461 | Status error; |
462 | m_condition_up.reset(p: m_target.GetUserExpressionForLanguage( |
463 | expr: condition, prefix: llvm::StringRef(), language: lldb::eLanguageTypeUnknown, |
464 | desired_type: UserExpression::eResultTypeAny, options: EvaluateExpressionOptions(), ctx_obj: nullptr, |
465 | error)); |
466 | if (error.Fail()) { |
467 | // FIXME: Log something... |
468 | m_condition_up.reset(); |
469 | } |
470 | } |
471 | SendWatchpointChangedEvent(eventKind: eWatchpointEventTypeConditionChanged); |
472 | } |
473 | |
474 | const char *Watchpoint::GetConditionText() const { |
475 | if (m_condition_up) |
476 | return m_condition_up->GetUserText(); |
477 | else |
478 | return nullptr; |
479 | } |
480 | |
481 | void Watchpoint::SendWatchpointChangedEvent( |
482 | lldb::WatchpointEventType eventKind) { |
483 | if (GetTarget().EventTypeHasListeners( |
484 | event_type: Target::eBroadcastBitWatchpointChanged)) { |
485 | auto data_sp = |
486 | std::make_shared<WatchpointEventData>(args&: eventKind, args: shared_from_this()); |
487 | GetTarget().BroadcastEvent(event_type: Target::eBroadcastBitWatchpointChanged, event_data_sp: data_sp); |
488 | } |
489 | } |
490 | |
491 | Watchpoint::WatchpointEventData::WatchpointEventData( |
492 | WatchpointEventType sub_type, const WatchpointSP &new_watchpoint_sp) |
493 | : m_watchpoint_event(sub_type), m_new_watchpoint_sp(new_watchpoint_sp) {} |
494 | |
495 | Watchpoint::WatchpointEventData::~WatchpointEventData() = default; |
496 | |
497 | llvm::StringRef Watchpoint::WatchpointEventData::GetFlavorString() { |
498 | return "Watchpoint::WatchpointEventData" ; |
499 | } |
500 | |
501 | llvm::StringRef Watchpoint::WatchpointEventData::GetFlavor() const { |
502 | return WatchpointEventData::GetFlavorString(); |
503 | } |
504 | |
505 | WatchpointSP &Watchpoint::WatchpointEventData::GetWatchpoint() { |
506 | return m_new_watchpoint_sp; |
507 | } |
508 | |
509 | WatchpointEventType |
510 | Watchpoint::WatchpointEventData::GetWatchpointEventType() const { |
511 | return m_watchpoint_event; |
512 | } |
513 | |
514 | void Watchpoint::WatchpointEventData::Dump(Stream *s) const {} |
515 | |
516 | const Watchpoint::WatchpointEventData * |
517 | Watchpoint::WatchpointEventData::GetEventDataFromEvent(const Event *event) { |
518 | if (event) { |
519 | const EventData *event_data = event->GetData(); |
520 | if (event_data && |
521 | event_data->GetFlavor() == WatchpointEventData::GetFlavorString()) |
522 | return static_cast<const WatchpointEventData *>(event->GetData()); |
523 | } |
524 | return nullptr; |
525 | } |
526 | |
527 | WatchpointEventType |
528 | Watchpoint::WatchpointEventData::GetWatchpointEventTypeFromEvent( |
529 | const EventSP &event_sp) { |
530 | const WatchpointEventData *data = GetEventDataFromEvent(event: event_sp.get()); |
531 | |
532 | if (data == nullptr) |
533 | return eWatchpointEventTypeInvalidType; |
534 | else |
535 | return data->GetWatchpointEventType(); |
536 | } |
537 | |
538 | WatchpointSP Watchpoint::WatchpointEventData::GetWatchpointFromEvent( |
539 | const EventSP &event_sp) { |
540 | WatchpointSP wp_sp; |
541 | |
542 | const WatchpointEventData *data = GetEventDataFromEvent(event: event_sp.get()); |
543 | if (data) |
544 | wp_sp = data->m_new_watchpoint_sp; |
545 | |
546 | return wp_sp; |
547 | } |
548 | |