[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.
2022-07-13 08:18:03 -04:00
|
|
|
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)
|
|
|
|
|
2022-08-18 06:44:25 -04:00
|
|
|
@classmethod
|
|
|
|
def lldb_init(cls, 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
|
|
|
|
|
[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.
2022-07-13 08:18:03 -04:00
|
|
|
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()):
|
2022-08-18 06:44:25 -04:00
|
|
|
RbBaseCommand.lldb_init(debugger)
|
[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.
2022-07-13 08:18:03 -04:00
|
|
|
|
|
|
|
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)
|