Project

General

Profile

Actions

Feature #18910

closed

Improve maintainability of LLDB helpers

Added by eightbitraptor (Matthew Valentine-House) 3 months ago. Updated about 2 months ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:109195]

Description

Github PR: #6129

Summary

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.

Background

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

What are we doing here?

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;

# Our class 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"

   # Init is required by the API but we don't need to do anything here
   def __init__(self, debugger, _internal_dict):
       pass

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

       # this call to super sets up the Ruby globals, ie. everything that currently
       # done by the following code in the current lldb helpers
       #   if not ('RUBY_Qfalse' in globals()):
       #       lldb_init(debugger)
       #
       super().__call__(self, debugger, command, exe_ctx, result)

       # Set up some commonly used variables. Nearly every existing lldb command
       # currently does this by hand.
       target, process, thread, frame = self.build_environment(debugger)

       # rest of our code goes here

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 must implement the lldb API (at minimum this means defining __init__ and __call__, but also optionally get_short_help and get_long_help)
  • 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.

Advantages/Disadvantages of this approach

Advantages:

  • Less code duplication.
    • We can now use inheritance and mix-ins to provide shared functionality between commands.
    • Commands are easier to read and reason about, only code relevant to the command behaviour is in __call__ most everything else can be abstracted away.
  • Easier to see at a glance what functions are available, what they do, and what commands they're mapped to, rather than jumping around a large file

Disadvantages

  • Slightly more boilerplate code required to add new commands - although I've attempted to mitigate this by providing a template - commands/command_template.py that can be copied and renamed to use as a starting point.

Notes

  • Only two smaller commands have been ported to this new structure at the moment: heap_page, and rclass_ext. I've kept these commits seperate in order to show what work is required to port existing commands over to the new style.
  • More commands will be ported over individually in seperate PR's, until eventually we'll have no more function based commands at all and can deprecate them.
  • The debugger instance passed to __call__ is different to the one passed to __init__ - and attempting to store one in an ivar and use it later doesn't work for me. This means I can't call super in the initialiser to setup the thread variables, and have them exist and work by the time the function is called. I'd like to find a way around this issue and call super in the __init__ to clean up the __call__ a little more.

Updated by ianks (Ian Ker-Seymer) 2 months ago

On a related note, I’ve often found myself wanting access to these helpers when developing native extensions. I’ve copy-pasta’d them in the past, but I wonder if Ruby could support including them for debug builds?

Updated by eightbitraptor (Matthew Valentine-House) 2 months ago

ianks (Ian Ker-Seymer) wrote in #note-1:

On a related note, I’ve often found myself wanting access to these helpers when developing native extensions. I’ve copy-pasta’d them in the past, but I wonder if Ruby could support including them for debug builds?

One thing that I'd like to do in a future PR is experiment with having these LLDB helpers inside a gem. Despite the slightly odd experience of having a Ruby gem that contained only Python code, we'd get a couple of benefits. 1) We'd be able to tie the helpers to a specific version of Ruby - they're very closely coupled to Ruby internals, and can be brittle when implementation details in the VM change. and 2) It would then be more easily usable for folks writing native extensions. What do you think?

Updated by ianks (Ian Ker-Seymer) 2 months ago

eightbitraptor (Matthew Valentine-House) wrote in #note-2:

ianks (Ian Ker-Seymer) wrote in #note-1:

On a related note, I’ve often found myself wanting access to these helpers when developing native extensions. I’ve copy-pasta’d them in the past, but I wonder if Ruby could support including them for debug builds?

One thing that I'd like to do in a future PR is experiment with having these LLDB helpers inside a gem. Despite the slightly odd experience of having a Ruby gem that contained only Python code, we'd get a couple of benefits. 1) We'd be able to tie the helpers to a specific version of Ruby - they're very closely coupled to Ruby internals, and can be brittle when implementation details in the VM change. and 2) It would then be more easily usable for folks writing native extensions. What do you think?

👍🏻

Actions #4

Updated by eightbitraptor (Matthew Valentine-House) about 2 months ago

  • Status changed from Open to Closed

Applied in changeset git|f1ccfa0c2c200c9443fbfc3f1ac3acbdd3e35559.


[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.
Actions

Also available in: Atom PDF