From a46e1c79b6fee9d776f0c2ec52200f0f224ef954 Mon Sep 17 00:00:00 2001 From: Michael Christensen Date: Fri, 23 Sep 2022 09:36:38 -0700 Subject: [PATCH] Add StringData pretty printer, skeleton for ArrayData; move walkstk class Summary: This adds the pretty printer for the `HPHP::StringData` class. It also adds the `ArrayData` printer for the `kVecKind` (just the metadata until I implement the iterator), and moves the `walkstk` class to its own module and adds a `Command` base class to handle some of the boilerplate of adding the commands to the debugger. The main purpose is to get a good framework going for adding additional printers and commands, and to be able to (re)load them in lldb for debugging/altering the scripts themselves as we develop them further. Reviewed By: arnabde03 Differential Revision: D39641653 fbshipit-source-id: dff2e44b03f6dbbcec520cf99c925e9cd07ec071 --- hphp/tools/lldb/hhvm.py | 46 ++++++++++--------- hphp/tools/lldb/pretty.py | 111 ++++++++++++++++++++++++++++++++++++++++++++++ hphp/tools/lldb/stack.py | 43 ++++++++++++++++++ hphp/tools/lldb/utils.py | 21 +++++++++ 4 files changed, 200 insertions(+), 21 deletions(-) rewrite hphp/tools/lldb/hhvm.py (97%) create mode 100644 hphp/tools/lldb/pretty.py create mode 100644 hphp/tools/lldb/stack.py create mode 100644 hphp/tools/lldb/utils.py diff --git a/hphp/tools/lldb/hhvm.py b/hphp/tools/lldb/hhvm.py dissimilarity index 97% index 31768e6343e..e985f1015bc 100644 --- a/hphp/tools/lldb/hhvm.py +++ b/hphp/tools/lldb/hhvm.py @@ -1,21 +1,25 @@ -#!/usr/bin/env python3 - -import lldb - -class WalkstkCommand: - def __init__(self, debugger, internal_dict): - pass - def __call__(self, debugger, command, exe_ctx, result): - pass - def get_short_help(self): - return "Traverse the interleaved VM and native stacks" - def get_long_help(self): - return """\ -The output backtrace has the following format: - - # @ : [at :] - -`walkstk' depends on the custom HHVM unwinder defined in unwind.py.""" - -def __lldb_init_module(debugger, dict): - debugger.HandleCommand('command script add -c hhvm.WalkstkCommand "walkstk"') +# Copyright 2022-present Facebook. All Rights Reserved. + +""" The main module you load in your .lldbinit. """ + + +import lldb +import sys + +import pretty +import stack + + +def __lldb_init_module(debugger, internal_dict): + """ Load up the commands and pretty printers of each module. + + Arguments: + debugger: Current debugger object + internal_dict: Dict for current script session. For internal use by LLDB only. + + Returns: + None + """ + top = sys.modules[__name__].__name__ + pretty.__lldb_init_module(debugger, internal_dict, top) + stack.__lldb_init_module(debugger, internal_dict, top) diff --git a/hphp/tools/lldb/pretty.py b/hphp/tools/lldb/pretty.py new file mode 100644 index 00000000000..25ed24a7871 --- /dev/null +++ b/hphp/tools/lldb/pretty.py @@ -0,0 +1,111 @@ +# Copyright 2022-present Facebook. All Rights Reserved. + +""" Pretty printers for HPHP types """ + +import lldb +import typing + + +Formatters = [] + + +def format(datatype: str, regex: bool = True): + """ Wrapper for pretty printer functions. + + Add the command needed to register the pretty printer with the LLDB debugger + session once started, to the global Formatters list.. + + Arguments: + datatype: the name of the data type being formatted + regex: whether the datatype in string is a regex + + Returns: + The original function. + """ + def inner(func): + Formatters.append(lambda top_module: + f'type summary add {"-x" if regex else ""} ' + f'-F {top_module + "." if top_module else ""}pretty.{func.__name__} {datatype}' + ) + return func + return inner + + +@format("HPHP::StringData", regex = False) +def pp_StringData(val_obj: lldb.SBValue, _internal_dict) -> typing.Optional[str]: + """ Pretty print HPHP::StringData + + Arguments: + val_obj: an SBValue wrapping an HPHP::StringData + internal_dict: an LLDB support object not to be used + + Returns: + A string representing the StringData, or None if there was an error. + """ + if val_obj.type.IsPointerType(): + return '' + + addr = val_obj.load_addr + if addr == lldb.LLDB_INVALID_ADDRESS: + return None + addr += val_obj.size + m_len = val_obj.children[1].GetChildMemberWithName("m_len").unsigned + error = lldb.SBError() + # +1 for null terminator, it seems + cstring = val_obj.process.ReadCStringFromMemory(addr, m_len + 1, error) + if error.Success(): + return cstring + else: + print('error: ', error) + + +@format("HPHP::ArrayData") +def pp_ArrayData(val_obj: lldb.SBValue, _internal_dict) -> typing.Optional[str]: + """ Pretty print HPHP::ArrayData + + TODO Currently only supports ArrayKind::kVecKind. + + Arguments: + val_obj: an SBValue wrapping an HPHP::ArrayData + internal_dict: an LLDB support object not to be used + + Returns: + A string representing the ArrayData, or None if there was an error. + """ + if val_obj.type.IsPointerType(): + return '' + + array_kind = val_obj.target.FindFirstType("HPHP::ArrayData::ArrayKind") + array_kind_enums = array_kind.GetEnumMembers() + heap_obj = val_obj.children[0].children[0] # HPHP::HeapObject + m_kind = heap_obj.GetChildMemberWithName("m_kind").Cast(array_kind) + + # TODO Try and just compare enums: left is lldb.SBValue, right is lldb.SBTypeEnumMember + if m_kind.unsigned != array_kind_enums['kVecKind'].unsigned: + return val_obj + # Just Vec kind right now + + m_size = val_obj.GetChildMemberWithName("m_size").unsigned + m_count = heap_obj.GetChildMemberWithName("m_count").unsigned + + # TODO show elements + + return f"ArrayData[{m_kind.name}]: {m_size} element(s) refcount={m_count}" + + +def __lldb_init_module(debugger: lldb.SBDebugger, _internal_dict, top_module=""): + """ Register the pretty printers in this file with the LLDB debugger. + + Defining this in this module (in addition to the main hhvm module) allows + this script to be imported into LLDB separately; LLDB looks for a function with + this name as module load time. + + Arguments: + debugger: Current debugger object + _internal_dict: Dict for current script session. For internal use by LLDB only. + + Returns: + None + """ + for cmd in Formatters: + debugger.HandleCommand(cmd(top_module)) diff --git a/hphp/tools/lldb/stack.py b/hphp/tools/lldb/stack.py new file mode 100644 index 00000000000..6aa59be0c92 --- /dev/null +++ b/hphp/tools/lldb/stack.py @@ -0,0 +1,43 @@ +# Copyright 2022-present Facebook. All Rights Reserved + +import optparse +import utils + +class WalkstkCommand(utils.Command): + command = "walkstk" + + @classmethod + def create_options(cls): + parser = optparse.OptionParser() + return parser + + def __init__(self, debugger, internal_dict): + pass + def __call__(self, debugger, command, exe_ctx, result): + pass + def get_short_help(self): + return "Traverse the interleaved VM and native stacks" + def get_long_help(self): + return f"""\ +The output backtrace has the following format: + + # @ : [at :] + +{self.command} depends on the custom HHVM unwinder defined in unwind.py.""" + + +def __lldb_init_module(debugger, _internal_dict, top_module=""): + """ Register the stack-related commands in this file with the LLDB debugger. + + Defining this in this module (in addition to the main hhvm module) allows + this script to be imported into LLDB separately; LLDB looks for a function with + this name as module load time. + + Arguments: + debugger: Current debugger object + _internal_dict: Dict for current script session. For internal use by LLDB only. + + Returns: + None + """ + WalkstkCommand.register_lldb_command(debugger, __name__, top_module) diff --git a/hphp/tools/lldb/utils.py b/hphp/tools/lldb/utils.py new file mode 100644 index 00000000000..e9f3668bfef --- /dev/null +++ b/hphp/tools/lldb/utils.py @@ -0,0 +1,21 @@ +# Copyright 2022-present Facebook. All Rights Reserved + +import abc + +class Command(abc.ABC): + @classmethod + def register_lldb_command(cls, debugger, module_name, top_module=""): + parser = cls.create_options() + cls.__doc__ = parser.format_help() + command = f"command script add -o -c {top_module + '.' if top_module else ''}{module_name}.{cls.__name__} {cls.command}" + debugger.HandleCommand(command) + + @classmethod + @abc.abstractmethod + def create_options(cls): + ... + + @property + @abc.abstractmethod + def command(self): + ... -- 2.11.4.GIT