I'm writing a pretty printer in python for gdb, and am slowly getting the hang of the methodology. Trying to find actual documentation as to how this system works with examples of what is expected coming out of the methods is like pulling teeth. I've found bits and pieces here and there, but nothing that is all inclusive. Some of the info that I've figured out is through trial and error, which is slow going.
So far, it looks like a pretty printer's to_string()
is only allowed to return a string (sure), but the children()
can return a string
or a pair of string
and value
, where value
is either a python value or a value object described here, which is a wrapper of a c/c++ object that's being printed. I had actually hoped that I could return a pretty printer object and have that be called, but alas, that is not to be. I could return a string, but I want the payload elements to be collapsible in an IDE like VSCode, and for that I need to return a value object. The equivalent to this is a Synthetic Item in Natvis.
I've got a c++ class that is a buffer. Raw, it contains a byte vector and I need it to be processed in a way that will be readable.
Give the constraints, that I've gleaned, if I can wrap a pointer in a proxy value object using a pseudo-type, I might be able to break down the bytes into useable units. Here's a hardcoded example of what I'm talking about:
#include <cstdint>
struct alignas(std::uint16_t) buffer {
enum id : char { id1, id2 };
// structure is: payload_size, id, payload[]
char buf[11] = { 2, id1, 1, 0, 2, 3
, 0, id1
, 1, id2, 1
};
char* end = std::end(buf);
};
int main() {
buffer b;
return 0;
}
Putting a breakpoint on the return 0;
on a big-endian machine, I would like to have something like the following show up:
(gdb) p b
$1 = buffer @ 0xaddre55 = { id1[2] = {1, 2, 3}, id1[0] = {}, id2 = {1} }
Here is what I got so far for the pretty printer python code:
class bufferPacketPrinter:
def __init__(self, p_begin, p_end) -> None:
self.p_begin = p_begin # begining of packet
self.p_end = p_end # end of packet
self.cmd_id = self.p_begin[1].cast('buffer::id')
self.payload_size = self.p_begin[0].cast('unsigned char').cast('int')
def to_string(self):
return 'packet {}[{}]' \
.format(self.cmd_id, self.payload_size)
def children(self):
payload = self.p_begin + 2
if self.cmd_id == 'id1':
if self.payload_size == 0:
return '{}'
elif self.payload_size == 3:
yield payload.cast(gdb.lookup_type('std::uint16_t').pointer())
payload += 2
yield payload[0].cast(gdb.lookup_type('unsigned char')).cast(gdb.lookup_type('int'))
payload += 1
return payload[0].cast(gdb.lookup_type('unsigned char')).cast(gdb.lookup_type('int'))
elif self.cmd_id == 'id2':
if self.payload_size == 1:
return payload[0]
return 'Invalid payload size of ' + str(self.payload_size)
class bufferPrinter:
def __init__(self, val) -> None:
self.val = val
self.begin = self.val['buf'].cast(gdb.lookup_type('char').pointer())
self.end = self.val['end']
def to_string(self):
return 'buffer @ {}'.format(self.val.address)
def children(self):
payload_size = self.begin[0].cast('unsigned char').cast('int')
while self.begin != self.end:
yield ??? # <=== Here is where the magic that I need is to happen
self.begin += 2 + payload_size
(I'm still learning python as well as this API, so if there are any errors, please let me know.)
The second last line yield ???
is what I am stuck on. Any ideas? If this isn't the way to do it, let me know of another way.