mib created this revision.
mib added reviewers: jingham, JDevlieghere, bulbazord, aprantl.
mib added a project: LLDB.
Herald added a project: All.
mib requested review of this revision.
Herald added a subscriber: lldb-commits.

If we use a variable watchpoint with a condition using a scope variable,
if we go out-of-scope, the watpoint remains active which can the
expression evaluator to fail to parse the watchpoint condition (because
of the missing varible bindings).

This was discovered after `watchpoint_callback.test` started failing on
the green dragon bot.

This patch should address that issue by setting an internal breakpoint
on the return addresss of the current frame when creating a variable
watchpoint. The breakpoint has a callback that will disable the watchpoint
if the the breakpoint execution context matches the watchpoint execution
context.

This is only enabled for local variables.

This patch also re-enables the failing test following e1086384e584 
<https://reviews.llvm.org/rGe1086384e5841e861cd19d5d980394cfcf94ef98>.

rdar://109574319

Signed-off-by: Med Ismail Bennani <ism...@bennani.ma>


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D151366

Files:
  lldb/include/lldb/Breakpoint/Watchpoint.h
  lldb/source/Breakpoint/Watchpoint.cpp
  lldb/source/Commands/CommandObjectWatchpoint.cpp
  lldb/test/Shell/ScriptInterpreter/Lua/watchpoint_callback.test
  lldb/test/Shell/Watchpoint/Inputs/val.c
  lldb/test/Shell/Watchpoint/Inputs/watchpoint.in
  lldb/test/Shell/Watchpoint/LocalVariableWatchpointDisabler.test

Index: lldb/test/Shell/Watchpoint/LocalVariableWatchpointDisabler.test
===================================================================
--- /dev/null
+++ lldb/test/Shell/Watchpoint/LocalVariableWatchpointDisabler.test
@@ -0,0 +1,3 @@
+# XFAIL: system-netbsd
+# RUN: %clang_host -x c %S/Inputs/val.c -g -o %t
+# RUN: %lldb -b -s %S/Inputs/watchpoint.in %t 2>&1 | FileCheck %S/Inputs/watchpoint.in
Index: lldb/test/Shell/Watchpoint/Inputs/watchpoint.in
===================================================================
--- /dev/null
+++ lldb/test/Shell/Watchpoint/Inputs/watchpoint.in
@@ -0,0 +1,10 @@
+breakpoint set -p "Break here"
+r
+watchpoint set variable val
+c
+# CHECK: Watchpoint 1 hit:
+# CHECK-NEXT: old value: 0
+# CHECK-NEXT: new value: 10
+# CHECK-NEXT: stop reason = watchpoint 1
+c
+# CHECK: exited with status = 0 (0x00000000)
Index: lldb/test/Shell/Watchpoint/Inputs/val.c
===================================================================
--- /dev/null
+++ lldb/test/Shell/Watchpoint/Inputs/val.c
@@ -0,0 +1,7 @@
+int main() {
+  int val = 0;
+  // Break here
+  val++;
+  val++;
+  return 0;
+}
Index: lldb/test/Shell/ScriptInterpreter/Lua/watchpoint_callback.test
===================================================================
--- lldb/test/Shell/ScriptInterpreter/Lua/watchpoint_callback.test
+++ lldb/test/Shell/ScriptInterpreter/Lua/watchpoint_callback.test
@@ -1,4 +1,4 @@
-# XFAIL: system-netbsd, system-darwin
+# XFAIL: system-netbsd
 # RUN: %clang_host -x c %S/Inputs/val.c -g -o %t
 # RUN: %lldb -b -s %S/Inputs/watchpoint1.in --script-language lua %t 2>&1 | FileCheck %S/Inputs/watchpoint1.in
 # RUN: %lldb -b -s %S/Inputs/watchpoint2.in --script-language lua %t 2>&1 | FileCheck %S/Inputs/watchpoint2.in
Index: lldb/source/Commands/CommandObjectWatchpoint.cpp
===================================================================
--- lldb/source/Commands/CommandObjectWatchpoint.cpp
+++ lldb/source/Commands/CommandObjectWatchpoint.cpp
@@ -9,6 +9,7 @@
 #include "CommandObjectWatchpoint.h"
 #include "CommandObjectWatchpointCommand.h"
 
+#include <memory>
 #include <vector>
 
 #include "llvm/ADT/StringRef.h"
@@ -20,6 +21,7 @@
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
 #include "lldb/Interpreter/CommandReturnObject.h"
+#include "lldb/Symbol/Function.h"
 #include "lldb/Symbol/Variable.h"
 #include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/StackFrame.h"
@@ -953,11 +955,15 @@
     if (watch_sp) {
       watch_sp->SetWatchSpec(command.GetArgumentAtIndex(0));
       watch_sp->SetWatchVariable(true);
-      if (var_sp && var_sp->GetDeclaration().GetFile()) {
-        StreamString ss;
-        // True to show fullpath for declaration file.
-        var_sp->GetDeclaration().DumpStopContext(&ss, true);
-        watch_sp->SetDeclInfo(std::string(ss.GetString()));
+      if (var_sp) {
+        if (var_sp->GetDeclaration().GetFile()) {
+          StreamString ss;
+          // True to show fullpath for declaration file.
+          var_sp->GetDeclaration().DumpStopContext(&ss, true);
+          watch_sp->SetDeclInfo(std::string(ss.GetString()));
+        }
+        if (var_sp->GetScope() == eValueTypeVariableLocal)
+          watch_sp->SetupVariableWatchpointDisabler(m_exe_ctx.GetFrameSP());
       }
       output_stream.Printf("Watchpoint created: ");
       watch_sp->GetDescription(&output_stream, lldb::eDescriptionLevelFull);
Index: lldb/source/Breakpoint/Watchpoint.cpp
===================================================================
--- lldb/source/Breakpoint/Watchpoint.cpp
+++ lldb/source/Breakpoint/Watchpoint.cpp
@@ -83,6 +83,94 @@
   SendWatchpointChangedEvent(eWatchpointEventTypeCommandChanged);
 }
 
+bool Watchpoint::SetupVariableWatchpointDisabler(StackFrameSP frame_sp) const {
+  ThreadSP thread_sp = frame_sp->GetThread();
+  if (!thread_sp)
+    return false;
+
+  uint32_t return_frame_index =
+      thread_sp->GetSelectedFrameIndex(DoNoSelectMostRelevantFrame) + 1;
+  if (return_frame_index >= LLDB_INVALID_FRAME_ID)
+    return false;
+
+  StackFrameSP return_frame_sp(
+      thread_sp->GetStackFrameAtIndex(return_frame_index));
+  if (!return_frame_sp)
+    return false;
+
+  ExecutionContext exe_ctx(return_frame_sp);
+  TargetSP target_sp = exe_ctx.GetTargetSP();
+  if (!target_sp)
+    return false;
+
+  Address return_address(return_frame_sp->GetFrameCodeAddress());
+  lldb::addr_t return_addr = return_address.GetLoadAddress(target_sp.get());
+  if (return_addr == LLDB_INVALID_ADDRESS)
+    return false;
+
+  BreakpointSP bp_sp = target_sp->CreateBreakpoint(
+      return_addr, /*internal=*/true, /*request_hardware=*/false);
+  if (!bp_sp || !bp_sp->HasResolvedLocations())
+    return false;
+
+  auto wvc_up = std::make_unique<WatchpointVariableContext>(GetID(), exe_ctx);
+  auto baton_sp = std::make_shared<WatchpointVariableBaton>(std::move(wvc_up));
+  bp_sp->SetCallback(VariableWatchpointDisabler, baton_sp);
+  bp_sp->SetOneShot(true);
+  bp_sp->SetBreakpointKind("variable watchpoint disabler");
+  return true;
+}
+
+bool Watchpoint::VariableWatchpointDisabler(void *baton,
+                                            StoppointCallbackContext *context,
+                                            user_id_t break_id,
+                                            user_id_t break_loc_id) {
+  assert(baton && "null baton");
+  if (!baton)
+    return false;
+
+  if (!context)
+    return false;
+
+  Log *log = GetLog(LLDBLog::Watchpoints);
+
+  WatchpointVariableContext *wvc =
+      static_cast<WatchpointVariableContext *>(baton);
+
+  LLDB_LOGF(log, "Watchpoint::%s called by breakpoint %" PRIu64 ".%" PRIu64,
+            __FUNCTION__, break_id, break_loc_id);
+
+  if (wvc->watch_id == LLDB_INVALID_WATCH_ID)
+    return false;
+
+  TargetSP target_sp = context->exe_ctx_ref.GetTargetSP();
+  if (!target_sp)
+    return false;
+
+  ProcessSP process_sp = target_sp->GetProcessSP();
+  if (!process_sp)
+    return true;
+
+  WatchpointSP watch_sp =
+      target_sp->GetWatchpointList().FindByID(wvc->watch_id);
+  if (!watch_sp)
+    return false;
+
+  if (wvc->exe_ctx == context->exe_ctx_ref) {
+    LLDB_LOGF(log,
+              "Watchpoint::%s callback for watchpoint %" PRId32
+              " matched internal breakpoint execution context",
+              __FUNCTION__, watch_sp->GetID());
+    process_sp->DisableWatchpoint(watch_sp.get());
+    return false;
+  }
+  LLDB_LOGF(log,
+            "Watchpoint::%s callback for watchpoint %" PRId32
+            " didn't match internal breakpoint execution context",
+            __FUNCTION__, watch_sp->GetID());
+  return false;
+}
+
 void Watchpoint::ClearCallback() {
   m_options.ClearCallback();
   SendWatchpointChangedEvent(eWatchpointEventTypeCommandChanged);
Index: lldb/include/lldb/Breakpoint/Watchpoint.h
===================================================================
--- lldb/include/lldb/Breakpoint/Watchpoint.h
+++ lldb/include/lldb/Breakpoint/Watchpoint.h
@@ -90,6 +90,28 @@
   void SetWatchVariable(bool val);
   bool CaptureWatchedValue(const ExecutionContext &exe_ctx);
 
+  struct WatchpointVariableContext {
+    WatchpointVariableContext(lldb::watch_id_t watch_id,
+                              ExecutionContext exe_ctx)
+        : watch_id(watch_id), exe_ctx(exe_ctx) {}
+    lldb::watch_id_t watch_id;
+    ExecutionContext exe_ctx;
+  };
+
+  class WatchpointVariableBaton : public TypedBaton<WatchpointVariableContext> {
+  public:
+    WatchpointVariableBaton(std::unique_ptr<WatchpointVariableContext> Data)
+        : TypedBaton(std::move(Data)) {}
+  };
+
+  bool SetupVariableWatchpointDisabler(lldb::StackFrameSP frame_sp) const;
+
+  /// Callback routine which disable the watchpoint set on a variable when it
+  /// goes out of scope.
+  static bool VariableWatchpointDisabler(
+      void *baton, lldb_private::StoppointCallbackContext *context,
+      lldb::user_id_t break_id, lldb::user_id_t break_loc_id);
+
   void GetDescription(Stream *s, lldb::DescriptionLevel level);
   void Dump(Stream *s) const override;
   void DumpSnapshots(Stream *s, const char *prefix = nullptr) const;
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to