|
| 1 | +# System_String.py |
| 2 | +# |
| 3 | +# GDB pretty-printer for System::String (Beef language) |
| 4 | +# |
| 5 | +# Memory layout (!BF_LARGE_STRINGS, i.e. int32/uint32 mode): |
| 6 | +# |
| 7 | +# class String { // [Ordered] Beef class |
| 8 | +# int32 mLength; // character count in char8 units |
| 9 | +# uint32 mAllocSizeAndFlags; // alloc size (lower 30 bits) | flags (upper 2 bits) |
| 10 | +# char8* mPtrOrBuffer; // pointer when cStrPtrFlag set; |
| 11 | +# // otherwise the char8 data lives inline starting |
| 12 | +# // at this field's address, extending past it |
| 13 | +# }; |
| 14 | +# |
| 15 | +# cSizeFlags = 0x3FFFFFFF -- mask to extract alloc size |
| 16 | +# cDynAllocFlag = 0x80000000 -- heap-allocated buffer; mPtrOrBuffer is the pointer |
| 17 | +# cStrPtrFlag = 0x40000000 -- mPtrOrBuffer is a pointer (heap OR static/fixed ref) |
| 18 | +# |
| 19 | +# Children: |
| 20 | +# [Length] always present |
| 21 | +# [AllocSize] when cDynAllocFlag is set (heap buffer) |
| 22 | +# [RefSize] when cStrPtrFlag set, cDynAllocFlag not set (non-owning pointer) |
| 23 | +# [InternalSize] when neither flag is set (inline buffer) |
| 24 | + |
| 25 | +import gdb |
| 26 | +import gdb.printing |
| 27 | + |
| 28 | +MAX_PREVIEW_BYTES = 256 |
| 29 | + |
| 30 | +_cSizeFlags = 0x3FFFFFFF |
| 31 | +_cDynAllocFlag = 0x80000000 |
| 32 | +_cStrPtrFlag = 0x40000000 |
| 33 | + |
| 34 | + |
| 35 | +def _safe_int(value, default=0): |
| 36 | + try: |
| 37 | + return int(value) |
| 38 | + except Exception: |
| 39 | + return default |
| 40 | + |
| 41 | + |
| 42 | +def _escape_bytes(data): |
| 43 | + try: |
| 44 | + return data.decode("utf-8") |
| 45 | + except Exception: |
| 46 | + return "".join( |
| 47 | + chr(b) if 32 <= b <= 126 and b not in (34, 92) |
| 48 | + else "\\x{:02x}".format(b) |
| 49 | + for b in data |
| 50 | + ) |
| 51 | + |
| 52 | + |
| 53 | +class BeefStringPrinter: |
| 54 | + def __init__(self, val): |
| 55 | + self.val = val |
| 56 | + |
| 57 | + def _decode(self): |
| 58 | + """Returns (length, has_str_ptr, has_dyn_alloc, alloc_size).""" |
| 59 | + length = _safe_int(self.val["mLength"], -1) |
| 60 | + alloc_flags = _safe_int(self.val["mAllocSizeAndFlags"], 0) |
| 61 | + has_str_ptr = bool(alloc_flags & _cStrPtrFlag) |
| 62 | + has_dyn = bool(alloc_flags & _cDynAllocFlag) |
| 63 | + alloc_size = alloc_flags & _cSizeFlags |
| 64 | + return length, has_str_ptr, has_dyn, alloc_size |
| 65 | + |
| 66 | + def to_string(self): |
| 67 | + print("String to_string called") |
| 68 | + |
| 69 | + try: |
| 70 | + length, has_str_ptr, has_dyn, alloc_size = self._decode() |
| 71 | + except Exception as e: |
| 72 | + return "<String field error: {}>".format(e) |
| 73 | + |
| 74 | + try: |
| 75 | + if (self.val["mLength"].is_optimized_out or |
| 76 | + self.val["mAllocSizeAndFlags"].is_optimized_out): |
| 77 | + return "<String optimized out>" |
| 78 | + except Exception: |
| 79 | + pass |
| 80 | + |
| 81 | + if length < 0: |
| 82 | + return "<String invalid length={}>".format(length) |
| 83 | + |
| 84 | + if length == 0: |
| 85 | + return "" |
| 86 | + |
| 87 | + preview_len = min(length, MAX_PREVIEW_BYTES) |
| 88 | + |
| 89 | + try: |
| 90 | + inferior = gdb.selected_inferior() |
| 91 | + |
| 92 | + if has_str_ptr: |
| 93 | + # mPtrOrBuffer holds a real pointer to the character data |
| 94 | + ptr_addr = _safe_int(self.val["mPtrOrBuffer"], 0) |
| 95 | + if ptr_addr == 0: |
| 96 | + return "<String null ptr length={}>".format(length) |
| 97 | + mem = inferior.read_memory(ptr_addr, preview_len) |
| 98 | + else: |
| 99 | + # Inline buffer: char8 data lives at the address of mPtrOrBuffer |
| 100 | + # itself and extends past it into appended allocation. |
| 101 | + buf_addr = int(self.val["mPtrOrBuffer"].address) |
| 102 | + mem = inferior.read_memory(buf_addr, preview_len) |
| 103 | + |
| 104 | + except gdb.MemoryError: |
| 105 | + return "<String unreadable memory length={}>".format(length) |
| 106 | + except Exception as e: |
| 107 | + return "<String read error: {}>".format(e) |
| 108 | + |
| 109 | + escaped = _escape_bytes(bytes(mem)) |
| 110 | + if length > MAX_PREVIEW_BYTES: |
| 111 | + escaped += "..." |
| 112 | + return escaped |
| 113 | + |
| 114 | + def display_hint(self): |
| 115 | + return "string" |
| 116 | + |
| 117 | + def children(self): |
| 118 | + try: |
| 119 | + length, has_str_ptr, has_dyn, alloc_size = self._decode() |
| 120 | + except Exception: |
| 121 | + return |
| 122 | + |
| 123 | + try: |
| 124 | + yield ("[Length]", self.val["mLength"]) |
| 125 | + except Exception: |
| 126 | + pass |
| 127 | + |
| 128 | + if has_dyn: |
| 129 | + yield ("[AllocSize]", alloc_size) |
| 130 | + elif has_str_ptr: |
| 131 | + yield ("[RefSize]", alloc_size) |
| 132 | + else: |
| 133 | + yield ("[InternalSize]", alloc_size) |
| 134 | + |
| 135 | + |
| 136 | +def _get_or_create_beef_collection(): |
| 137 | + """Return the existing global 'Beef' printer collection (or create it). |
| 138 | + Returns (collection, needs_registration).""" |
| 139 | + for pp in gdb.pretty_printers: |
| 140 | + if getattr(pp, 'name', None) == 'Beef': |
| 141 | + return pp, False |
| 142 | + return gdb.printing.RegexpCollectionPrettyPrinter("Beef"), True |
| 143 | + |
| 144 | + |
| 145 | +_beef_pp, _needs_reg = _get_or_create_beef_collection() |
| 146 | +_beef_pp.add_printer("System::String", "^System::String$", BeefStringPrinter) |
| 147 | +if _needs_reg: |
| 148 | + gdb.printing.register_pretty_printer(None, _beef_pp) |
| 149 | + |
| 150 | +print("[String] pretty-printers registered") |
0 commit comments