[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.
This commit is contained in:
Matt Valentine-House 2022-07-13 13:18:03 +01:00 committed by Peter Zhu
parent d903e76726
commit f1ccfa0c2c
Notes: git 2022-08-19 02:26:13 +09:00
5 changed files with 124 additions and 5 deletions

View file

@ -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")