[Lldb-commits] [lldb] 0321dbc - [LLDB][GUI] Add Process Attach form

Greg Clayton via lldb-commits lldb-commits at lists.llvm.org
Thu Jul 15 15:12:45 PDT 2021


Author: Omar Emara
Date: 2021-07-15T15:12:27-07:00
New Revision: 0321dbc87e43e651c50998701f349d1791fa9cb6

URL: https://github.com/llvm/llvm-project/commit/0321dbc87e43e651c50998701f349d1791fa9cb6
DIFF: https://github.com/llvm/llvm-project/commit/0321dbc87e43e651c50998701f349d1791fa9cb6.diff

LOG: [LLDB][GUI] Add Process Attach form

This patch adds a form window to attach a process, either by PID or by
name. This patch also adds support for dynamic field visibility such
that the form delegate can hide or show certain fields based on some
conditions.

Reviewed By: clayborg

Differential Revision: https://reviews.llvm.org/D105655

Added: 
    

Modified: 
    lldb/source/Core/IOHandlerCursesGUI.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp
index fb18e7d90eaa8..d54c27cea0c80 100644
--- a/lldb/source/Core/IOHandlerCursesGUI.cpp
+++ b/lldb/source/Core/IOHandlerCursesGUI.cpp
@@ -40,6 +40,7 @@
 #if LLDB_ENABLE_CURSES
 #include "lldb/Breakpoint/BreakpointLocation.h"
 #include "lldb/Core/Module.h"
+#include "lldb/Core/PluginManager.h"
 #include "lldb/Core/ValueObject.h"
 #include "lldb/Core/ValueObjectRegister.h"
 #include "lldb/Symbol/Block.h"
@@ -607,9 +608,19 @@ class Window : public Surface {
       m_delete = del;
     }
   }
-  //
+
   // Get the rectangle in our parent window
   Rect GetBounds() const { return Rect(GetParentOrigin(), GetSize()); }
+
+  Rect GetCenteredRect(int width, int height) {
+    Size size = GetSize();
+    width = std::min(size.width, width);
+    height = std::min(size.height, height);
+    int x = (size.width - width) / 2;
+    int y = (size.height - height) / 2;
+    return Rect(Point(x, y), Size(width, height));
+  }
+
   int GetChar() { return ::wgetch(m_window); }
   Point GetParentOrigin() const { return Point(GetParentX(), GetParentY()); }
   int GetParentX() const { return getparx(m_window); }
@@ -1049,9 +1060,18 @@ class FieldDelegate {
 
   // Select the last element in the field if multiple elements exists.
   virtual void FieldDelegateSelectLastElement() { return; }
+
+  bool FieldDelegateIsVisible() { return m_is_visible; }
+
+  void FieldDelegateHide() { m_is_visible = false; }
+
+  void FieldDelegateShow() { m_is_visible = true; }
+
+protected:
+  bool m_is_visible = true;
 };
 
-typedef std::shared_ptr<FieldDelegate> FieldDelegateSP;
+typedef std::unique_ptr<FieldDelegate> FieldDelegateUP;
 
 class TextFieldDelegate : public FieldDelegate {
 public:
@@ -1226,7 +1246,6 @@ class TextFieldDelegate : public FieldDelegate {
 
   void SetError(const char *error) { m_error = error; }
 
-  // Returns the text content of the field.
   const std::string &GetText() { return m_content; }
 
 protected:
@@ -1633,7 +1652,7 @@ template <class T> class ListFieldDelegate : public FieldDelegate {
       m_selection_type = SelectionType::NewButton;
   }
 
-  HandleCharResult SelecteNext(int key) {
+  HandleCharResult SelectNext(int key) {
     if (m_selection_type == SelectionType::NewButton)
       return eKeyNotHandled;
 
@@ -1706,7 +1725,7 @@ template <class T> class ListFieldDelegate : public FieldDelegate {
       }
       break;
     case '\t':
-      SelecteNext(key);
+      SelectNext(key);
       return eKeyHandled;
     case KEY_SHIFT_TAB:
       SelectPrevious(key);
@@ -1811,7 +1830,15 @@ class FormDelegate {
 
   virtual ~FormDelegate() = default;
 
-  FieldDelegateSP &GetField(int field_index) { return m_fields[field_index]; }
+  virtual std::string GetName() = 0;
+
+  virtual void UpdateFieldsVisibility() { return; }
+
+  FieldDelegate *GetField(uint32_t field_index) {
+    if (field_index < m_fields.size())
+      return m_fields[field_index].get();
+    return nullptr;
+  }
 
   FormAction &GetAction(int action_index) { return m_actions[action_index]; }
 
@@ -1831,8 +1858,7 @@ class FormDelegate {
 
   TextFieldDelegate *AddTextField(const char *label, const char *content) {
     TextFieldDelegate *delegate = new TextFieldDelegate(label, content);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
@@ -1840,8 +1866,7 @@ class FormDelegate {
                                   bool need_to_exist = true) {
     FileFieldDelegate *delegate =
         new FileFieldDelegate(label, content, need_to_exist);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
@@ -1850,22 +1875,19 @@ class FormDelegate {
                                             bool need_to_exist = true) {
     DirectoryFieldDelegate *delegate =
         new DirectoryFieldDelegate(label, content, need_to_exist);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
   IntegerFieldDelegate *AddIntegerField(const char *label, int content) {
     IntegerFieldDelegate *delegate = new IntegerFieldDelegate(label, content);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
   BooleanFieldDelegate *AddBooleanField(const char *label, bool content) {
     BooleanFieldDelegate *delegate = new BooleanFieldDelegate(label, content);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
@@ -1873,8 +1895,7 @@ class FormDelegate {
                                         std::vector<std::string> choices) {
     ChoicesFieldDelegate *delegate =
         new ChoicesFieldDelegate(label, height, choices);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
@@ -1882,8 +1903,7 @@ class FormDelegate {
   ListFieldDelegate<T> *AddListField(const char *label, T default_field) {
     ListFieldDelegate<T> *delegate =
         new ListFieldDelegate<T>(label, default_field);
-    FieldDelegateSP delegate_sp = FieldDelegateSP(delegate);
-    m_fields.push_back(delegate_sp);
+    m_fields.push_back(FieldDelegateUP(delegate));
     return delegate;
   }
 
@@ -1894,7 +1914,7 @@ class FormDelegate {
   }
 
 protected:
-  std::vector<FieldDelegateSP> m_fields;
+  std::vector<FieldDelegateUP> m_fields;
   std::vector<FormAction> m_actions;
   // Optional error message. If empty, form is considered to have no error.
   std::string m_error;
@@ -1906,7 +1926,13 @@ class FormWindowDelegate : public WindowDelegate {
 public:
   FormWindowDelegate(FormDelegateSP &delegate_sp)
       : m_delegate_sp(delegate_sp), m_selection_index(0),
-        m_selection_type(SelectionType::Field), m_first_visible_line(0) {}
+        m_first_visible_line(0) {
+    assert(m_delegate_sp->GetNumberOfActions() > 0);
+    if (m_delegate_sp->GetNumberOfFields() > 0)
+      m_selection_type = SelectionType::Field;
+    else
+      m_selection_type = SelectionType::Action;
+  }
 
   // Signify which element is selected. If a field or an action is selected,
   // then m_selection_index signifies the particular field or action that is
@@ -1947,6 +1973,8 @@ class FormWindowDelegate : public WindowDelegate {
     int height = 0;
     height += GetErrorHeight();
     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
+      if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
+        continue;
       height += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
     }
     height += GetActionsHeight();
@@ -1957,11 +1985,13 @@ class FormWindowDelegate : public WindowDelegate {
     if (m_selection_type == SelectionType::Action)
       return ScrollContext(GetContentHeight() - 1);
 
-    FieldDelegateSP &field = m_delegate_sp->GetField(m_selection_index);
+    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
     ScrollContext context = field->FieldDelegateGetScrollContext();
 
     int offset = GetErrorHeight();
     for (int i = 0; i < m_selection_index; i++) {
+      if (!m_delegate_sp->GetField(i)->FieldDelegateIsVisible())
+        continue;
       offset += m_delegate_sp->GetField(i)->FieldDelegateGetHeight();
     }
     context.Offset(offset);
@@ -2017,8 +2047,10 @@ class FormWindowDelegate : public WindowDelegate {
     int width = surface.GetWidth();
     bool a_field_is_selected = m_selection_type == SelectionType::Field;
     for (int i = 0; i < m_delegate_sp->GetNumberOfFields(); i++) {
+      FieldDelegate *field = m_delegate_sp->GetField(i);
+      if (!field->FieldDelegateIsVisible())
+        continue;
       bool is_field_selected = a_field_is_selected && m_selection_index == i;
-      FieldDelegateSP &field = m_delegate_sp->GetField(i);
       int height = field->FieldDelegateGetHeight();
       Rect bounds = Rect(Point(0, line), Size(width, height));
       SubPad field_surface = SubPad(surface, bounds);
@@ -2079,10 +2111,12 @@ class FormWindowDelegate : public WindowDelegate {
   }
 
   bool WindowDelegateDraw(Window &window, bool force) override {
+    m_delegate_sp->UpdateFieldsVisibility();
 
     window.Erase();
 
-    window.DrawTitleBox(window.GetName(), "Press Esc to cancel");
+    window.DrawTitleBox(m_delegate_sp->GetName().c_str(),
+                        "Press Esc to cancel");
 
     Rect content_bounds = window.GetFrame();
     content_bounds.Inset(2, 2);
@@ -2092,7 +2126,22 @@ class FormWindowDelegate : public WindowDelegate {
     return true;
   }
 
-  HandleCharResult SelecteNext(int key) {
+  void SkipNextHiddenFields() {
+    while (true) {
+      if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
+        return;
+
+      if (m_selection_index == m_delegate_sp->GetNumberOfFields() - 1) {
+        m_selection_type = SelectionType::Action;
+        m_selection_index = 0;
+        return;
+      }
+
+      m_selection_index++;
+    }
+  }
+
+  HandleCharResult SelectNext(int key) {
     if (m_selection_type == SelectionType::Action) {
       if (m_selection_index < m_delegate_sp->GetNumberOfActions() - 1) {
         m_selection_index++;
@@ -2101,12 +2150,15 @@ class FormWindowDelegate : public WindowDelegate {
 
       m_selection_index = 0;
       m_selection_type = SelectionType::Field;
-      FieldDelegateSP &next_field = m_delegate_sp->GetField(m_selection_index);
-      next_field->FieldDelegateSelectFirstElement();
+      SkipNextHiddenFields();
+      if (m_selection_type == SelectionType::Field) {
+        FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
+        next_field->FieldDelegateSelectFirstElement();
+      }
       return eKeyHandled;
     }
 
-    FieldDelegateSP &field = m_delegate_sp->GetField(m_selection_index);
+    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
     if (!field->FieldDelegateOnLastOrOnlyElement()) {
       return field->FieldDelegateHandleChar(key);
     }
@@ -2120,14 +2172,32 @@ class FormWindowDelegate : public WindowDelegate {
     }
 
     m_selection_index++;
+    SkipNextHiddenFields();
 
-    FieldDelegateSP &next_field = m_delegate_sp->GetField(m_selection_index);
-    next_field->FieldDelegateSelectFirstElement();
+    if (m_selection_type == SelectionType::Field) {
+      FieldDelegate *next_field = m_delegate_sp->GetField(m_selection_index);
+      next_field->FieldDelegateSelectFirstElement();
+    }
 
     return eKeyHandled;
   }
 
-  HandleCharResult SelectePrevious(int key) {
+  void SkipPreviousHiddenFields() {
+    while (true) {
+      if (m_delegate_sp->GetField(m_selection_index)->FieldDelegateIsVisible())
+        return;
+
+      if (m_selection_index == 0) {
+        m_selection_type = SelectionType::Action;
+        m_selection_index = 0;
+        return;
+      }
+
+      m_selection_index--;
+    }
+  }
+
+  HandleCharResult SelectPrevious(int key) {
     if (m_selection_type == SelectionType::Action) {
       if (m_selection_index > 0) {
         m_selection_index--;
@@ -2135,13 +2205,16 @@ class FormWindowDelegate : public WindowDelegate {
       }
       m_selection_index = m_delegate_sp->GetNumberOfFields() - 1;
       m_selection_type = SelectionType::Field;
-      FieldDelegateSP &previous_field =
-          m_delegate_sp->GetField(m_selection_index);
-      previous_field->FieldDelegateSelectLastElement();
+      SkipPreviousHiddenFields();
+      if (m_selection_type == SelectionType::Field) {
+        FieldDelegate *previous_field =
+            m_delegate_sp->GetField(m_selection_index);
+        previous_field->FieldDelegateSelectLastElement();
+      }
       return eKeyHandled;
     }
 
-    FieldDelegateSP &field = m_delegate_sp->GetField(m_selection_index);
+    FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
     if (!field->FieldDelegateOnFirstOrOnlyElement()) {
       return field->FieldDelegateHandleChar(key);
     }
@@ -2155,10 +2228,13 @@ class FormWindowDelegate : public WindowDelegate {
     }
 
     m_selection_index--;
+    SkipPreviousHiddenFields();
 
-    FieldDelegateSP &previous_field =
-        m_delegate_sp->GetField(m_selection_index);
-    previous_field->FieldDelegateSelectLastElement();
+    if (m_selection_type == SelectionType::Field) {
+      FieldDelegate *previous_field =
+          m_delegate_sp->GetField(m_selection_index);
+      previous_field->FieldDelegateSelectLastElement();
+    }
 
     return eKeyHandled;
   }
@@ -2166,9 +2242,11 @@ class FormWindowDelegate : public WindowDelegate {
   void ExecuteAction(Window &window) {
     FormAction &action = m_delegate_sp->GetAction(m_selection_index);
     action.Execute(window);
-    m_first_visible_line = 0;
-    m_selection_index = 0;
-    m_selection_type = SelectionType::Field;
+    if (m_delegate_sp->HasError()) {
+      m_first_visible_line = 0;
+      m_selection_index = 0;
+      m_selection_type = SelectionType::Field;
+    }
   }
 
   HandleCharResult WindowDelegateHandleChar(Window &window, int key) override {
@@ -2182,9 +2260,9 @@ class FormWindowDelegate : public WindowDelegate {
       }
       break;
     case '\t':
-      return SelecteNext(key);
+      return SelectNext(key);
     case KEY_SHIFT_TAB:
-      return SelectePrevious(key);
+      return SelectPrevious(key);
     case KEY_ESCAPE:
       window.GetParent()->RemoveSubWindow(&window);
       return eKeyHandled;
@@ -2195,7 +2273,7 @@ class FormWindowDelegate : public WindowDelegate {
     // If the key wasn't handled and one of the fields is selected, pass the key
     // to that field.
     if (m_selection_type == SelectionType::Field) {
-      FieldDelegateSP &field = m_delegate_sp->GetField(m_selection_index);
+      FieldDelegate *field = m_delegate_sp->GetField(m_selection_index);
       return field->FieldDelegateHandleChar(key);
     }
 
@@ -2212,6 +2290,223 @@ class FormWindowDelegate : public WindowDelegate {
   int m_first_visible_line;
 };
 
+///////////////////////////
+// Form Delegate Instances
+///////////////////////////
+
+class DetachOrKillProcessFormDelegate : public FormDelegate {
+public:
+  DetachOrKillProcessFormDelegate(Process *process) : m_process(process) {
+    SetError("There is a running process, either detach or kill it.");
+
+    m_keep_stopped_field =
+        AddBooleanField("Keep process stopped when detaching.", false);
+
+    AddAction("Detach", [this](Window &window) { Detach(window); });
+    AddAction("Kill", [this](Window &window) { Kill(window); });
+  }
+
+  std::string GetName() override { return "Detach/Kill Process"; }
+
+  void Kill(Window &window) {
+    Status destroy_status(m_process->Destroy(false));
+    if (destroy_status.Fail()) {
+      SetError("Failed to kill process.");
+      return;
+    }
+    window.GetParent()->RemoveSubWindow(&window);
+  }
+
+  void Detach(Window &window) {
+    Status detach_status(m_process->Detach(m_keep_stopped_field->GetBoolean()));
+    if (detach_status.Fail()) {
+      SetError("Failed to detach from process.");
+      return;
+    }
+    window.GetParent()->RemoveSubWindow(&window);
+  }
+
+protected:
+  Process *m_process;
+  BooleanFieldDelegate *m_keep_stopped_field;
+};
+
+class ProcessAttachFormDelegate : public FormDelegate {
+public:
+  ProcessAttachFormDelegate(Debugger &debugger, WindowSP main_window_sp)
+      : m_debugger(debugger), m_main_window_sp(main_window_sp) {
+    std::vector<std::string> types;
+    types.push_back(std::string("Name"));
+    types.push_back(std::string("PID"));
+    m_type_field = AddChoicesField("Attach By", 2, types);
+    m_pid_field = AddIntegerField("PID", 0);
+    m_name_field =
+        AddTextField("Process Name", GetDefaultProcessName().c_str());
+    m_continue_field = AddBooleanField("Continue once attached.", false);
+    m_wait_for_field = AddBooleanField("Wait for process to launch.", false);
+    m_include_existing_field =
+        AddBooleanField("Include existing processes.", false);
+    m_show_advanced_field = AddBooleanField("Show advanced settings.", false);
+    m_plugin_field =
+        AddChoicesField("Plugin Name", 3, GetPossiblePluginNames());
+
+    AddAction("Attach", [this](Window &window) { Attach(window); });
+  }
+
+  std::string GetName() override { return "Attach Process"; }
+
+  void UpdateFieldsVisibility() override {
+    if (m_type_field->GetChoiceContent() == "Name") {
+      m_pid_field->FieldDelegateHide();
+      m_name_field->FieldDelegateShow();
+      m_wait_for_field->FieldDelegateShow();
+      if (m_wait_for_field->GetBoolean())
+        m_include_existing_field->FieldDelegateShow();
+      else
+        m_include_existing_field->FieldDelegateHide();
+    } else {
+      m_pid_field->FieldDelegateShow();
+      m_name_field->FieldDelegateHide();
+      m_wait_for_field->FieldDelegateHide();
+      m_include_existing_field->FieldDelegateHide();
+    }
+    if (m_show_advanced_field->GetBoolean())
+      m_plugin_field->FieldDelegateShow();
+    else
+      m_plugin_field->FieldDelegateHide();
+  }
+
+  // Get the basename of the target's main executable if available, empty string
+  // otherwise.
+  std::string GetDefaultProcessName() {
+    Target *target = m_debugger.GetSelectedTarget().get();
+    if (target == nullptr)
+      return "";
+
+    ModuleSP module_sp = target->GetExecutableModule();
+    if (!module_sp->IsExecutable())
+      return "";
+
+    return module_sp->GetFileSpec().GetFilename().AsCString();
+  }
+
+  std::vector<std::string> GetPossiblePluginNames() {
+    std::vector<std::string> names;
+    names.push_back("<default>");
+
+    size_t i = 0;
+    while (auto name = PluginManager::GetProcessPluginNameAtIndex(i++))
+      names.push_back(name);
+    return names;
+  }
+
+  bool StopRunningProcess() {
+    ExecutionContext exe_ctx =
+        m_debugger.GetCommandInterpreter().GetExecutionContext();
+
+    if (!exe_ctx.HasProcessScope())
+      return false;
+
+    Process *process = exe_ctx.GetProcessPtr();
+    if (!(process && process->IsAlive()))
+      return false;
+
+    FormDelegateSP form_delegate_sp =
+        FormDelegateSP(new DetachOrKillProcessFormDelegate(process));
+    Rect bounds = m_main_window_sp->GetCenteredRect(85, 8);
+    WindowSP form_window_sp = m_main_window_sp->CreateSubWindow(
+        form_delegate_sp->GetName().c_str(), bounds, true);
+    WindowDelegateSP window_delegate_sp =
+        WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
+    form_window_sp->SetDelegate(window_delegate_sp);
+
+    return true;
+  }
+
+  Target *GetTarget() {
+    Target *target = m_debugger.GetSelectedTarget().get();
+
+    if (target != nullptr)
+      return target;
+
+    TargetSP new_target_sp;
+    m_debugger.GetTargetList().CreateTarget(
+        m_debugger, "", "", eLoadDependentsNo, nullptr, new_target_sp);
+
+    target = new_target_sp.get();
+
+    if (target == nullptr)
+      SetError("Failed to create target.");
+
+    m_debugger.GetTargetList().SetSelectedTarget(new_target_sp);
+
+    return target;
+  }
+
+  ProcessAttachInfo GetAttachInfo() {
+    ProcessAttachInfo attach_info;
+    attach_info.SetContinueOnceAttached(m_continue_field->GetBoolean());
+    if (m_type_field->GetChoiceContent() == "Name") {
+      attach_info.GetExecutableFile().SetFile(m_name_field->GetText(),
+                                              FileSpec::Style::native);
+      attach_info.SetWaitForLaunch(m_wait_for_field->GetBoolean());
+      if (m_wait_for_field->GetBoolean())
+        attach_info.SetIgnoreExisting(!m_include_existing_field->GetBoolean());
+    } else {
+      attach_info.SetProcessID(m_pid_field->GetInteger());
+    }
+    if (m_plugin_field->GetChoiceContent() != "<default>")
+      attach_info.SetProcessPluginName(m_plugin_field->GetChoiceContent());
+
+    return attach_info;
+  }
+
+  void Attach(Window &window) {
+    ClearError();
+
+    bool process_is_running = StopRunningProcess();
+    if (process_is_running)
+      return;
+
+    Target *target = GetTarget();
+    if (HasError())
+      return;
+
+    StreamString stream;
+    ProcessAttachInfo attach_info = GetAttachInfo();
+    Status status = target->Attach(attach_info, &stream);
+
+    if (status.Fail()) {
+      SetError(status.AsCString());
+      return;
+    }
+
+    ProcessSP process_sp(target->GetProcessSP());
+    if (!process_sp) {
+      SetError("Attached sucessfully but target has no process.");
+      return;
+    }
+
+    if (attach_info.GetContinueOnceAttached())
+      process_sp->Resume();
+
+    window.GetParent()->RemoveSubWindow(&window);
+  }
+
+protected:
+  Debugger &m_debugger;
+  WindowSP m_main_window_sp;
+
+  ChoicesFieldDelegate *m_type_field;
+  IntegerFieldDelegate *m_pid_field;
+  TextFieldDelegate *m_name_field;
+  BooleanFieldDelegate *m_continue_field;
+  BooleanFieldDelegate *m_wait_for_field;
+  BooleanFieldDelegate *m_include_existing_field;
+  BooleanFieldDelegate *m_show_advanced_field;
+  ChoicesFieldDelegate *m_plugin_field;
+};
+
 class MenuDelegate {
 public:
   virtual ~MenuDelegate() = default;
@@ -4459,6 +4754,19 @@ class ApplicationDelegate : public WindowDelegate, public MenuDelegate {
     }
       return MenuActionResult::Handled;
 
+    case eMenuID_ProcessAttach: {
+      WindowSP main_window_sp = m_app.GetMainWindow();
+      FormDelegateSP form_delegate_sp = FormDelegateSP(
+          new ProcessAttachFormDelegate(m_debugger, main_window_sp));
+      Rect bounds = main_window_sp->GetCenteredRect(80, 22);
+      WindowSP form_window_sp = main_window_sp->CreateSubWindow(
+          form_delegate_sp->GetName().c_str(), bounds, true);
+      WindowDelegateSP window_delegate_sp =
+          WindowDelegateSP(new FormWindowDelegate(form_delegate_sp));
+      form_window_sp->SetDelegate(window_delegate_sp);
+      return MenuActionResult::Handled;
+    }
+
     case eMenuID_ProcessContinue: {
       ExecutionContext exe_ctx =
           m_debugger.GetCommandInterpreter().GetExecutionContext();


        


More information about the lldb-commits mailing list