drgn icon indicating copy to clipboard operation
drgn copied to clipboard

Add hooks for customizing object printing

Open septatrix opened this issue 10 months ago • 4 comments

It would be great if one could implement custom formatters for types. There are a few scenarios where this would be helpful:

  • Data which is stored in a struct but has a better, human-readable representation. One simple example would be timestamp where unixtime or struct tm (from C++ but similar things are done in C) have more digestible/compact representations. Or converting magic values (flags, enums, etc) to their textual representation
  • Printing nested data structures or large structs quickly fills the screen making it hard to spot the necessary information. Adding custom formatters one could specify e.g. the depth, or elide some content after certain thresholds have been reached.
  • Highlight unexpected values. If one wants to get very fancy they could use ANSI colors to highlight unexpectedly high values or other things they want to be able to quickly spot. (Doing the ANSI stuff would of course not be the responsibility of this library).

Being able to register custom callback for each data type which are invoked when their string should be printed would allow more powerful visualization. Or maybe as some kind of "transform" function which takes a struct and returns a JSON such that drgn would still handle indentation and such?

Currently one could achieve a similar result by invoking such handlers manually but that is more involved for nested structures and is quite cumbersome

septatrix avatar Mar 06 '25 22:03 septatrix

An even more powerful solution would be to register classes for types. This way one could register a subclass of drgn.Type and customize str, repr, but more importantly could also add custom methods on that type. The API could look something like this:

@drgn.register_type("struct my_custom_struct")
class MyCustomStruct(drgn.Type):
    def __str__(self, <maybe some context>) -> str:
        dt = date.fromtimestamp(self.time)
        return f"{self.id} {self.maybe_large_string[:16]} {dt.isoformat()}"

    def find_child(self, id: id) -> MyOtherCustomType: ...

    def to_graphviz_dot(self) -> str: ...

septatrix avatar Mar 06 '25 23:03 septatrix

As a nice side effect this would also allow people to utilize python type hints to a greater extend instead of passing drgn.Type/drgn.Object everywhere

septatrix avatar Mar 06 '25 23:03 septatrix

I must've missed this one before, sorry.

I really like the idea of customizing printing of objects with certain types. I've wanted this before for struct list_head in the kernel (to annotate empty lists, for example).

I'm less keen on subclassing. I don't think a highly object oriented interface meshes well with drgn's philosophy of "drgn code kind of looks like C".

Instead, the object printing customization could be done as a plugin hook. We could allow registering hooks keyed by type name, or hooks that get called for every object regardless of type, or maybe even both.

osandov avatar Apr 04 '25 20:04 osandov

I'm less keen on subclassing. I don't think a highly object oriented interface meshes well with drgn's philosophy of "drgn code kind of looks like C".

I understand not wanting to make this OOP but I do not think this would be happening here. You could only ever register one "extension type" and not some deep hierarchy. The advantage is that one has an ergonomic way to call python methods on these objects and type checkers can better check those and provide autocompletion.

The neat side effect is that it automatically grants us a great interface for the string customization and other potential integration that alternatives like wrapper classes could never achieve

septatrix avatar Apr 05 '25 08:04 septatrix