[前][次][番号順一覧][スレッド一覧]

ruby-changes:72965

From: Matt <ko1@a...>
Date: Fri, 19 Aug 2022 02:25:56 +0900 (JST)
Subject: [ruby-changes:72965] f1ccfa0c2c (master): [ci-skip][Feature #18910][lldb] Provide class framework for lldb commands

https://git.ruby-lang.org/ruby.git/commit/?id=f1ccfa0c2c

From f1ccfa0c2c200c9443fbfc3f1ac3acbdd3e35559 Mon Sep 17 00:00:00 2001
From: Matt Valentine-House <matt@e...>
Date: Wed, 13 Jul 2022 13:18:03 +0100
Subject: [ci-skip][Feature #18910][lldb] Provide class framework for lldb
 commands

`lldb_cruby.py` manages lldb custom commands using functions. The file
is a large list of Python functions, and an init handler to map some of
the Python functions into the debugger, to enable execution of custom
logic during a debugging session.

Since LLDB 3.7 (September 2015) there has also been support for using
python classes rather than bare functions, as long as those classes
implement a specific interface.

This PR Introduces some more defined structure to the LLDB helper
functions by switching from the function based implementation to the
class based one, and providing an auto-loading mechanism by which new
functions can be loaded.

The intention behind this change is to make working with the LLDB
helpers easier, by reducing code duplication, providing a consistent
structure and a clearer API for developers.

The current function based approach has some advantages and
disadvantages

Advantages:

- Adding new code is easy.
- All the code is self contained and searchable.

Disadvantages:
- No visible organisation of the file contents. This means
  - Hard to tell which functions are utility functions and which are
    available to you in a debugging session
  - Lots of code duplication within lldb functions
- Large files quickly become intimidating to work with - for example,
  `lldb_disasm.py` was implemented as a seperate Python module because
  it was easier to start with a clean slate than add significant amounts
  of code to `lldb_cruby.py`

This PR attempts, to fix the disadvantages of the current approach and
maintain, or enhance, the benefits. The new structure of a command looks
like this;

 ```
 class TestCommand(RbBaseCommand):
    # program is the keyword the user will type in lldb to execute this command
    program = "test"

    # help_string will be displayed in lldb when the user uses the help functions
    help_string = "This is a test command to show how to implement lldb commands"

    # call is where our command logic will be implemented
    def call(self, debugger, command, exe_ctx, result):
        pass
  ```

If the command fulfils the following criteria it will then be
auto-loaded when an lldb session is started:

- The package file must exist inside the `commands` directory and the
  filename must end in `_command.py`
- The package must implement a class whose name ends in `Command`
- The class inherits from `RbBaseCommand` or at minimum a class that
  shares the same interface as `RbBaseCommand`  (at minimum this means
  defining `__init__` and `__call__`, and using `__call__` to call
  `call` which is defined in the subclasses).
- The class must have a class variable `package` that is a String. This
  is the name of the command you'll call in the `lldb` debugger.
---
 .gitignore                        |  3 ++
 misc/commands/command_template.py | 25 ++++++++++++++
 misc/constants.py                 |  4 +++
 misc/lldb_cruby.py                | 29 ++++++++++++++---
 misc/rb_base_command.py           | 68 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 124 insertions(+), 5 deletions(-)
 create mode 100644 misc/commands/command_template.py
 create mode 100644 misc/constants.py
 create mode 100644 misc/rb_base_command.py

diff --git a/.gitignore b/.gitignore
index 521f4ec807..c12ec27782 100644
--- a/.gitignore
+++ b/.gitignore
@@ -219,6 +219,9 @@ lcov*.info https://github.com/ruby/ruby/blob/trunk/.gitignore#L219
 /lib/ruby/[1-9]*.*
 /lib/ruby/vendor_ruby
 
+# /misc/
+/misc/**/__pycache__
+
 # /spec/bundler
 /.rspec_status
 
diff --git a/misc/commands/command_template.py b/misc/commands/command_template.py
new file mode 100644
index 0000000000..bbc4b09157
--- /dev/null
+++ b/misc/commands/command_template.py
@@ -0,0 +1,25 @@ https://github.com/ruby/ruby/blob/trunk/misc/commands/command_template.py#L1
+# This is a command template for implementing a helper function inside LLDB. To
+# use this file
+#   1. Copy it and rename the copy so it ends with `_command.py`.
+#   2. Rename the class to something descriptive that ends with Command.
+#   3. Change the program variable to be a descriptive command name
+#   4. Ensure you are inheriting from RbBaseCommand or another command that 
+#      implements the same interfact
+
+# This test command inherits from RbBaseCommand which provides access to Ruby
+# globals and utility helpers
+class TestCommand(RbBaseCommand):
+    # program is the keyword the user will type in lldb to execute this command
+    program = "test"
+
+    # help_string will be displayed in lldb when the user uses the help functions
+    help_string = "This is a test command to show how to implement lldb commands"
+
+    # call is where our command logic will be implemented
+    def call(self, debugger, command, exe_ctx, result):
+        # This method will be called once the LLDB environment has been setup.
+        # You will have access to self.target, self.process, self.frame, and 
+        # self.thread
+        # 
+        # This is where we should implement our command logic
+        pass
diff --git a/misc/constants.py b/misc/constants.py
new file mode 100644
index 0000000000..ec3050a399
--- /dev/null
+++ b/misc/constants.py
@@ -0,0 +1,4 @@ https://github.com/ruby/ruby/blob/trunk/misc/constants.py#L1
+HEAP_PAGE_ALIGN_LOG = 16
+HEAP_PAGE_ALIGN_MASK = (~(~0 << HEAP_PAGE_ALIGN_LOG))
+HEAP_PAGE_ALIGN = (1 << HEAP_PAGE_ALIGN_LOG)
+HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN
diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py
index c38b9c62a0..de8628754c 100755
--- a/misc/lldb_cruby.py
+++ b/misc/lldb_cruby.py
@@ -9,15 +9,16 @@ https://github.com/ruby/ruby/blob/trunk/misc/lldb_cruby.py#L9
 from __future__ import print_function
 import lldb
 import os
+import inspect
+import sys
 import shlex
 import platform
+import glob
 
-HEAP_PAGE_ALIGN_LOG = 16
-
-HEAP_PAGE_ALIGN_MASK = (~(~0 << HEAP_PAGE_ALIGN_LOG))
-HEAP_PAGE_ALIGN = (1 << HEAP_PAGE_ALIGN_LOG)
-HEAP_PAGE_SIZE = HEAP_PAGE_ALIGN
+from constants import *
 
+# BEGIN FUNCTION STYLE DECLS
+# This will be refactored to use class style decls in the misc/commands dir
 class BackTrace:
     VM_FRAME_MAGIC_METHOD = 0x11110001
     VM_FRAME_MAGIC_BLOCK = 0x22220001
@@ -740,9 +741,27 @@ def rb_rclass_ext(debugger, command, result, internal_dict): https://github.com/ruby/ruby/blob/trunk/misc/lldb_cruby.py#L741
     rclass_addr = target.EvaluateExpression(command).Cast(uintptr_t)
     rclass_ext_addr = (rclass_addr.GetValueAsUnsigned() + rclass_t.GetByteSize())
     debugger.HandleCommand("p *(rb_classext_t *)%0#x" % rclass_ext_addr)
+# END FUNCTION STYLE DECLS
+
+
+load_dir, _ = os.path.split(os.path.realpath(__file__))
+
+for fname in glob.glob(f"{load_dir}/commands/*_command.py"):
+    _, basename = os.path.split(fname)
+    mname, _ = os.path.splitext(basename)
 
+    exec(f"import commands.{mname}")
 
 def __lldb_init_module(debugger, internal_dict):
+    # Register all classes that subclass RbBaseCommand
+
+    for memname, mem in inspect.getmembers(sys.modules["lldb_rb.rb_base_command"]):
+        if inspect.isclass(mem):
+            for sclass in mem.__subclasses__():
+                sclass.register_lldb_command(debugger, f"{__name__}.{sclass.__module__}")
+
+
+    ## FUNCTION INITS - These should be removed when converted to class commands
     debugger.HandleCommand("command script add -f lldb_cruby.lldb_rp rp")
     debugger.HandleCommand("command script add -f lldb_cruby.count_objects rb_count_objects")
     debugger.HandleCommand("command script add -f lldb_cruby.stack_dump_raw SDR")
diff --git a/misc/rb_base_command.py b/misc/rb_base_command.py
new file mode 100644
index 0000000000..44b2996d80
--- /dev/null
+++ b/misc/rb_base_command.py
@@ -0,0 +1,68 @@ https://github.com/ruby/ruby/blob/trunk/misc/rb_base_command.py#L1
+import lldb
+from pydoc import locate
+
+class RbBaseCommand:
+    @classmethod
+    def register_lldb_command(cls, debugger, module_name):
+        # Add any commands contained in this module to LLDB
+        command = f"command script add -c {module_name}.{cls.__name__} {cls.program}"
+        debugger.HandleCommand(command)
+
+    def __init__(self, debugger, _internal_dict):
+        self.internal_dict = _internal_dict
+
+    def __call__(self, debugger, command, exe_ctx, result):
+        if not ("RUBY_Qfalse" in globals()):
+            self._lldb_init(debugger)
+
+        self.build_environment(debugger)
+        self.call(debugger, command, exe_ctx, result)
+
+    def call(self, debugger, command, exe_ctx, result):
+        raise NotImplementedError("subclasses must implement call")
+
+    def get_short_help(self):
+        return self.__class__.help_string
+
+    def get_long_help(self):
+        return self.__class__.help_string
+
+    def build_environment(self, debugger):
+        self.target = debugger.GetSelectedTarget()
+        self.process = self.target.GetProcess()
+        self.thread = self.process.GetSelectedThread()
+        self.frame = self.thread.GetSelectedFrame()
+
+    def _append_command_output(self, debugger, command, result):
+        output1 = result.GetOutput()
+        debugger.GetCommandInterpreter().HandleCommand(command, result)
+        output2 = result.GetOutput()
+        result.Clear()
+        result.write(output1)
+        result.write(output2)
+
+    def _lldb_init(self, debugger):
+        target = debugger.GetSelectedTarget()
+        global SIZEOF_VALUE
+        SIZEOF_VALUE = target.FindFirstType("VALUE").GetByteSize()
+
+        value_types = []
+        g = globals()
+
+        imemo_types = target.FindFirstType("enum imemo_type")
+
+        for member in imemo_types.GetEnumMembers():
+            g[member.GetName()] = member.GetValueAsUnsigned()
+
+        for enum in target.FindFirstGlobalVariable("ruby_ (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]