summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorMatt Valentine-House <matt@eightbitraptor.com>2022-07-13 13:18:03 +0100
committerPeter Zhu <peter@peterzhu.ca>2022-08-18 13:25:32 -0400
commitf1ccfa0c2c200c9443fbfc3f1ac3acbdd3e35559 (patch)
tree0ff12dcff017bd8a5ac4929fbe318ba766b57ce4 /misc
parentd903e7672637d5a834847820a4e18b00ee30f380 (diff)
[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.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/6129
Diffstat (limited to 'misc')
-rw-r--r--misc/commands/command_template.py25
-rw-r--r--misc/constants.py4
-rwxr-xr-xmisc/lldb_cruby.py29
-rw-r--r--misc/rb_base_command.py68
4 files changed, 121 insertions, 5 deletions
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 @@
+# 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 @@
+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 @@
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):
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 @@
+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_dummy_gdb_enums"):
+ enum = enum.GetType()
+ members = enum.GetEnumMembers()
+ for i in range(0, members.GetSize()):
+ member = members.GetTypeEnumMemberAtIndex(i)
+ name = member.GetName()
+ value = member.GetValueAsUnsigned()
+ g[name] = value
+
+ if name.startswith("RUBY_T_"):
+ value_types.append(name)
+ g["value_types"] = value_types