From: "ianks (Ian Ker-Seymer)" Date: 2022-08-02T17:48:02+00:00 Subject: [ruby-core:109416] [Ruby master Feature#18910] Improve maintainability of LLDB helpers Issue #18910 has been updated by ianks (Ian Ker-Seymer). 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? �������� ---------------------------------------- Feature #18910: Improve maintainability of LLDB helpers https://bugs.ruby-lang.org/issues/18910#change-98566 * Author: eightbitraptor (Matthew Valentine-House) * Status: Open * Priority: Normal ---------------------------------------- **[Github PR: #6129](https://github.com/ruby/ruby/pull/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. -- https://bugs.ruby-lang.org/ Unsubscribe: