简体   繁体   中英

Registering a gdb pretty-printer for a specialization of std::unordered_map

I'm trying to register a pretty-printer for a specific specialization of std::unordered_map , but for some reason it always uses the standard pretty-printer for std::unordered_map in gdb instead of my specialized version.


Consider the following class

namespace ns {

class MyClass {
public:
    std::string name;
    int value = 0;
};

}  // namespace ns

I have a gdb pretty-printer defined for it as

# myprinters.py -> sourced in gdb
class MyClassPrinter:
    def __init__(self, val):
        self.name = val["name"]
        self.val = val["value"]

    def to_string(self):
        return f"MyClass(name={self.name}, value={self.val})"


import gdb.printing
pp = gdb.printing.RegexpCollectionPrettyPrinter('myprinters')
pp.add_printer('MyClass', 'MyClass', MyClassPrinter)

gdb.printing.register_pretty_printer(gdb.current_objfile(), pp, replace=True)

Now consider the main function below

int main(int argc, char *argv[]) {
    std::tuple<int, MyClass> mytuple{10, {"name10", 10}};
    std::unordered_map<int, MyClass> mymap;
    mymap.insert({10, {"name10", 10}});
    mymap.insert({15, {"name15", 15}});
    mymap.insert({25, {"name25", 25}});

    auto myobj = MyClass{"name5", 5};

    std::unordered_map<int, MyClass *> mymap2;
    mymap2.insert({10, new MyClass{"name10", 10}});  // don't worry about the new
    mymap2.insert({15, new MyClass{"name15", 15}});
    mymap2.insert({25, new MyClass{"name25", 25}});

    std::cout << "The end" << std::endl;
    return 0;
}

If a add a breakpoint in the cout line and print myobj I get $7 = MyClass(name="name5", value=5) as expected. Printing mytuple and mymap also works, since gdb has pretty-printers for the containers in STL. We get something like

$8 = std::tuple containing = {
  [1] = 10,
  [2] = MyClass(name="name10", value=10)
}
$9 = std::unordered_map with 3 elements = {
  [25] = MyClass(name="name25", value=25),
  [15] = MyClass(name="name15", value=15),
  [10] = MyClass(name="name10", value=10)
}

However, what I actually have are containers with MyClass* and not MyClass , such as the mymap2 variable. Printing that results in

$10 = std::unordered_map with 3 elements = {
  [25] = 0x555555576760,
  [15] = 0x555555576710,
  [10] = 0x555555576650
}

which is not very useful.

For my particular needs, I only need the "name" field of each MyClass object pointed in mymap2 . Then I created a pretty-printer for std::unordered_map<int, MyClass *> , but I'm not able to register it correctly (maybe the version for just std::unordered_map is taking precedence, but I could not correctly disable to test this hypothesis. I get an error when I try to disable the pretty-printer as suggested in this question ).

I tried with

class MyUnorderedMapOfMyClassPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "MyUnorderedMapOfMyClassPrinter"

and then added the line below to the python script defining my pretty-printers

pp.add_printer('MyUnorderedMapOfMyClassPrinter', 'std::unordered_map<int, ns::MyClass\*>', MyUnorderedMapOfMyClassPrinter)

to include the new pretty-printer.

But printing the unordered_map does not use my pretty-printer. It still uses the regular std::unordered_map pretty-printer from gdb.

If I do create a new type like

class MyUnorderedMap : public std::unordered_map<int, MyClass *> {};

and register a pretty-printer for MyUnorderedMap then it works. But I don't want to create another type just to be able to register a pretty-printer.

How can I register a pretty-printer for a specific specialization of std::unordered_map ?


Edit: I could not disable only the std::unordered_map pretty-printer, but I disabled all pretty-printers with disable pretty-printer in gdb, and then enabled only my pretty-printers with enable pretty-printer global myprinters . This allowed me try the regexp to register my pretty-printer and to make it work for a std::unordered_map specialization with with pp.add_printer('MyUnorderedMapOfMyClassPrinter', 'std::unordered_map<.*, ns::MyClass\*>', MyUnorderedMapOfMyClassPrinter) (note the "*" at the end, since I only want it to work for MyClass pointers).

Interesting, pp.add_printer('MyUnorderedMapOfMyClassPrinter', 'std::unordered_map<int, ns::MyClass\*>', MyUnorderedMapOfMyClassPrinter) did not work . I had to use .* instead of int to make it work for some reason.

However, when all pretty-printers are enabled the standard std::unordered_map pretty printer still takes precedence over my specialization. How can I make my pretty-printer takes precedence?


For making things easily reproducible, here is the full main.cpp file

#include <iostream>
#include <string>
#include <tuple>
#include <unordered_map>

namespace ns {

class MyClass {
public:
    std::string name;
    int value = 0;
};

}  // namespace ns

using namespace ns;

int main(int argc, char *argv[]) {
    std::tuple<int, MyClass> mytuple{10, {"name10", 10}};
    std::unordered_map<int, MyClass> mymap;
    mymap.insert({10, {"name10", 10}});
    mymap.insert({15, {"name15", 15}});
    mymap.insert({25, {"name25", 25}});

    auto myobj = MyClass{"name5", 5};

    std::unordered_map<int, MyClass *> mymap2;
    mymap2.insert({10, new MyClass{"name10", 10}});
    mymap2.insert({15, new MyClass{"name15", 15}});
    mymap2.insert({25, new MyClass{"name25", 25}});

    std::cout << "The end" << std::endl;

    return 0;
}

and the full myprinters.py file defining the pretty-printers

class MyClassPrinter:
    def __init__(self, val):
        self.name = str(val["name"])
        self.val = val["value"]

    def to_string(self):
        return f"MyClass(name={self.name}, value={self.val})"


class MyClassPointerPrinter:
    def __init__(self, val):
        self.ptr = val
        self.name = str(val.dereference()["name"])
        self.val = val.dereference()["value"]

    def to_string(self):
        return f"Pointer to MyClass(name={self.name}, value={self.val})"



class MyUnorderedMapOfMyClassPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "MyUnorderedMapOfMyClassPrinter"


import gdb.printing
pp = gdb.printing.RegexpCollectionPrettyPrinter('myprinters')
pp.add_printer('MyClass', '^ns::MyClass$', MyClassPrinter)
# pp.add_printer('MyClass', 'MyClass\*', MyClassPointerPrinter)
# pp.add_printer('MyClass', 'MyClass.?\*', MyClassPointerPrinter)
# pp.add_printer('MyClass', 'MyClass \*', MyClassPointerPrinter)
pp.add_printer('MyUnorderedMapOfMyClassPrinter', 'std::unordered_map<.*, ns::MyClass\*>', MyUnorderedMapOfMyClassPrinter)


gdb.printing.register_pretty_printer(gdb.current_objfile(), pp, replace=True)



def my_pp_func(val):
    if str(val.type) == "ns::MyClass *":
        return MyClassPointerPrinter(val)

gdb.pretty_printers.append(my_pp_func)

For me, the problem is that gdb.current_objfile() always returns None . Thus your pretty printer is registered as a global one, whereas all the standard ones are object level. Object level pretty-printers take precedence.

I have no idea why, but I was unable to make this function work.

It is possible to use this workaround:

gdb.printing.register_pretty_printer(gdb.objfiles()[0], pp, replace=True)

After more investigation and comments in the question from "n. 'pronouns' m.", I was able to solve the problem, although the question is still technically not solved.

In summary, with

class MyUnorderedMapOfMyClassPrinter:
    def __init__(self, val):
        self.val = val

    def to_string(self):
        return "MyUnorderedMapOfMyClassPrinter"


import gdb.printing
pp = gdb.printing.RegexpCollectionPrettyPrinter('myprinters')
pp.add_printer('MyClass', '^ns::MyClass$', MyClassPrinter)
pp.add_printer('MyUnorderedMapOfMyClassPrinter', 'std::unordered_map<.*, ns::MyClass\*>', MyUnorderedMapOfMyClassPrinter)

a pretty printer is registered for my specialization of unordered_map . However, the version for the general unordered_map is still used and if I print the mymap2 variable the string "MyUnorderedMapOfMyClassPrinter" is not shown as expected. If we disable all pretty printers and enable only our pretty-printers, then "MyUnorderedMapOfMyClassPrinter" is shown, thus confirming that it was correctly registered. That is why the question is technically not solved, since disabling all other pretty-printers is not a good solution.

But registering a pretty-printer for MyClass* does work, as suggested in the comments, as long as we use a lookup function instead of relying on the RegexpCollectionPrettyPrinter . This solves the original problem, since the gdb regular pretty-printer for unordered_map will now be enough.

More specifically, we can create a pretty-printer as

class MyClassPointerPrinter:
    def __init__(self, val):
        self.ptr = val
        self.name = str(val.dereference()["name"])
        self.val = val.dereference()["value"]

    def to_string(self):
        return f"Pointer to MyClass(name={self.name}, value={self.val})"

and register it with

def my_pp_func(val):
    if str(val.type) == "ns::MyClass *":
        return MyClassPointerPrinter(val)


gdb.pretty_printers.append(my_pp_func)

We can even extend this further and create a pretty-printer for any pointer . For instance, we can define a pretty printer as

class MyPointerPrettyPrinter:
    def __init__(self, val):
        self.ptr = val

    def to_string(self):
        default_visualizer = gdb.default_visualizer(self.ptr.dereference())
        return f"({self.ptr.type}) {self.ptr.format_string(raw=True)} -> {default_visualizer.to_string()}"

and register it with

def my_pointer_func(val):
    # This matches any pointer
    if val.type.code == gdb.TYPE_CODE_PTR:
        # Only if the pointed object has a registered pretty-printer we will use
        # our pointer pretty-printer
        if gdb.default_visualizer(val.dereference()) is not None:
            return MyPointerPrettyPrinter(val)


gdb.pretty_printers.append(my_pointer_func)

This will match any pointer and print something like "(ClassName *) 0x<pointer_address> -> OBJ_REP", where "OBJ_REP" is the pretty-printing of the pointed object. If there is no visualizer registered for ClassName , then only "(ClassName *) 0x<pointer_address>" is shown.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM