Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 1 | # Copyright 2022 The Tint Authors. |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | # Pretty printers for the Tint project. |
| 16 | # |
| 17 | # If using lldb from command line, add a line to your ~/.lldbinit to import the printers: |
| 18 | # |
| 19 | # command script import /path/to/dawn/src/tint/tint_lldb.py |
| 20 | # |
| 21 | # |
| 22 | # If using VS Code on MacOS with the Microsoft C/C++ extension, add the following to |
| 23 | # your launch.json (make sure you specify an absolute path to tint_lldb.py): |
| 24 | # |
| 25 | # "name": "Launch", |
| 26 | # "type": "cppdbg", |
| 27 | # "request": "launch", |
| 28 | # ... |
| 29 | # "setupCommands": [ |
| 30 | # { |
| 31 | # "description": "Load tint pretty printers", |
| 32 | # "ignoreFailures": false, |
| 33 | # "text": "command script import /path/to/dawn/src/tint/tint_lldb.py, |
| 34 | # } |
| 35 | # ] |
| 36 | # |
| 37 | # If using VS Code with the CodeLLDB extension (https://github.com/vadimcn/vscode-lldb), |
| 38 | # add the following to your launch.json: |
| 39 | # |
| 40 | # "name": "Launch", |
| 41 | # "type": "lldb", |
| 42 | # "request": "launch", |
| 43 | # ... |
| 44 | # "initCommands": [ |
| 45 | # "command script import /path/to/dawn/src/tint/tint_lldb.py" |
| 46 | # ] |
| 47 | |
| 48 | # Based on pretty printers for: |
| 49 | # Rust: https://github.com/vadimcn/vscode-lldb/blob/master/formatters/rust.py |
| 50 | # Dlang: https://github.com/Pure-D/dlang-debug/blob/master/lldb_dlang.py |
| 51 | # |
| 52 | # |
| 53 | # Tips for debugging using VS Code: |
| 54 | # |
| 55 | # - Set a breakpoint where you can view the types you want to debug/write pretty printers for. |
| 56 | # - Debug Console: -exec command script import /path/to/dawn/src/tint/tint_lldb.py |
| 57 | # - You can re-run the above command to reload the printers after modifying the python script. |
| 58 | |
| 59 | # - Useful docs: |
| 60 | # Formattesr: https://lldb.llvm.org/use/variable.html |
| 61 | # Python API: https://lldb.llvm.org/python_api.html |
| 62 | # Especially: |
| 63 | # SBType: https://lldb.llvm.org/python_api/lldb.SBType.html |
| 64 | # SBValue: https://lldb.llvm.org/python_api/lldb.SBValue.html |
| 65 | |
| 66 | from __future__ import print_function, division |
| 67 | import sys |
| 68 | import logging |
| 69 | import re |
| 70 | import lldb |
| 71 | import types |
| 72 | |
| 73 | if sys.version_info[0] == 2: |
| 74 | # python2-based LLDB accepts utf8-encoded ascii strings only. |
| 75 | def to_lldb_str(s): return s.encode( |
| 76 | 'utf8', 'backslashreplace') if isinstance(s, unicode) else s |
| 77 | range = xrange |
| 78 | else: |
| 79 | to_lldb_str = str |
| 80 | |
| 81 | string_encoding = "escape" # remove | unicode | escape |
| 82 | |
| 83 | log = logging.getLogger(__name__) |
| 84 | |
| 85 | module = sys.modules[__name__] |
| 86 | tint_category = None |
| 87 | |
| 88 | |
| 89 | def __lldb_init_module(debugger, dict): |
| 90 | global tint_category |
| 91 | |
| 92 | tint_category = debugger.CreateCategory('tint') |
| 93 | tint_category.SetEnabled(True) |
| 94 | |
| 95 | attach_synthetic_to_type( |
| 96 | UtilsSlicePrinter, r'^tint::utils::Slice<.+>$', True) |
| 97 | |
| 98 | attach_synthetic_to_type( |
| 99 | UtilsVectorPrinter, r'^tint::utils::Vector<.+>$', True) |
| 100 | |
| 101 | attach_synthetic_to_type( |
| 102 | UtilsVectorRefPrinter, r'^tint::utils::VectorRef<.+>$', True) |
| 103 | |
| 104 | attach_synthetic_to_type( |
| 105 | UtilsHashsetPrinter, r'^tint::utils::Hashset<.+>$', True) |
| 106 | |
| 107 | attach_synthetic_to_type( |
| 108 | UtilsHashmapPrinter, r'^tint::utils::Hashmap<.+>$', True) |
| 109 | |
| 110 | |
| 111 | def attach_synthetic_to_type(synth_class, type_name, is_regex=False): |
| 112 | global module, tint_category |
| 113 | synth = lldb.SBTypeSynthetic.CreateWithClassName( |
| 114 | __name__ + '.' + synth_class.__name__) |
| 115 | synth.SetOptions(lldb.eTypeOptionCascade) |
| 116 | ret = tint_category.AddTypeSynthetic( |
| 117 | lldb.SBTypeNameSpecifier(type_name, is_regex), synth) |
| 118 | log.debug('attaching synthetic %s to "%s", is_regex=%s -> %s', |
| 119 | synth_class.__name__, type_name, is_regex, ret) |
| 120 | |
| 121 | def summary_fn(valobj, dict): return get_synth_summary( |
| 122 | synth_class, valobj, dict) |
| 123 | # LLDB accesses summary fn's by name, so we need to create a unique one. |
| 124 | summary_fn.__name__ = '_get_synth_summary_' + synth_class.__name__ |
| 125 | setattr(module, summary_fn.__name__, summary_fn) |
| 126 | attach_summary_to_type(summary_fn, type_name, is_regex) |
| 127 | |
| 128 | |
| 129 | def attach_summary_to_type(summary_fn, type_name, is_regex=False): |
| 130 | global module, tint_category |
| 131 | summary = lldb.SBTypeSummary.CreateWithFunctionName( |
| 132 | __name__ + '.' + summary_fn.__name__) |
| 133 | summary.SetOptions(lldb.eTypeOptionCascade) |
| 134 | ret = tint_category.AddTypeSummary( |
| 135 | lldb.SBTypeNameSpecifier(type_name, is_regex), summary) |
| 136 | log.debug('attaching summary %s to "%s", is_regex=%s -> %s', |
| 137 | summary_fn.__name__, type_name, is_regex, ret) |
| 138 | |
| 139 | |
| 140 | def get_synth_summary(synth_class, valobj, dict): |
| 141 | '''' |
| 142 | get_summary' is annoyingly not a part of the standard LLDB synth provider API. |
| 143 | This trick allows us to share data extraction logic between synth providers and their sibling summary providers. |
| 144 | ''' |
| 145 | synth = synth_class(valobj.GetNonSyntheticValue(), dict) |
| 146 | synth.update() |
| 147 | summary = synth.get_summary() |
| 148 | return to_lldb_str(summary) |
| 149 | |
| 150 | |
| 151 | def member(valobj, *chain): |
| 152 | '''Performs chained GetChildMemberWithName lookups''' |
| 153 | for name in chain: |
| 154 | valobj = valobj.GetChildMemberWithName(name) |
| 155 | return valobj |
| 156 | |
| 157 | |
| 158 | class Printer(object): |
| 159 | '''Base class for Printers''' |
| 160 | |
| 161 | def __init__(self, valobj, dict={}): |
| 162 | self.valobj = valobj |
| 163 | self.initialize() |
| 164 | |
| 165 | def initialize(self): |
| 166 | return None |
| 167 | |
| 168 | def update(self): |
| 169 | return False |
| 170 | |
| 171 | def num_children(self): |
| 172 | return 0 |
| 173 | |
| 174 | def has_children(self): |
| 175 | return False |
| 176 | |
| 177 | def get_child_at_index(self, index): |
| 178 | return None |
| 179 | |
| 180 | def get_child_index(self, name): |
| 181 | return None |
| 182 | |
| 183 | def get_summary(self): |
| 184 | return None |
| 185 | |
| 186 | def member(self, *chain): |
| 187 | '''Performs chained GetChildMemberWithName lookups''' |
| 188 | return member(self.valobj, *chain) |
| 189 | |
| 190 | def template_params(self): |
| 191 | '''Returns list of template params values (as strings)''' |
| 192 | type_name = self.valobj.GetTypeName() |
| 193 | params = [] |
| 194 | level = 0 |
| 195 | start = 0 |
| 196 | for i, c in enumerate(type_name): |
| 197 | if c == '<': |
| 198 | level += 1 |
| 199 | if level == 1: |
| 200 | start = i + 1 |
| 201 | elif c == '>': |
| 202 | level -= 1 |
| 203 | if level == 0: |
| 204 | params.append(type_name[start:i].strip()) |
| 205 | elif c == ',' and level == 1: |
| 206 | params.append(type_name[start:i].strip()) |
| 207 | start = i + 1 |
| 208 | return params |
| 209 | |
| 210 | def template_param_at(self, index): |
| 211 | '''Returns template param value at index (as string)''' |
| 212 | return self.template_params()[index] |
| 213 | |
| 214 | |
| 215 | class UtilsSlicePrinter(Printer): |
| 216 | '''Printer for tint::utils::Slice<T>''' |
| 217 | |
| 218 | def initialize(self): |
| 219 | self.len = self.valobj.GetChildMemberWithName('len') |
| 220 | self.cap = self.valobj.GetChildMemberWithName('cap') |
| 221 | self.data = self.valobj.GetChildMemberWithName('data') |
| 222 | self.elem_type = self.data.GetType().GetPointeeType() |
| 223 | self.elem_size = self.elem_type.GetByteSize() |
| 224 | |
| 225 | def get_summary(self): |
| 226 | return 'length={} capacity={}'.format(self.len.GetValueAsUnsigned(), self.cap.GetValueAsUnsigned()) |
| 227 | |
| 228 | def num_children(self): |
| 229 | # NOTE: VS Code on MacOS hangs if we try to expand something too large, so put an artificial limit |
| 230 | # until we can figure out how to know if this is a valid instance. |
| 231 | return min(self.len.GetValueAsUnsigned(), 256) |
| 232 | |
| 233 | def has_children(self): |
| 234 | return True |
| 235 | |
| 236 | def get_child_at_index(self, index): |
| 237 | try: |
| 238 | if not 0 <= index < self.num_children(): |
| 239 | return None |
| 240 | # TODO: return self.value_at(index) |
| 241 | offset = index * self.elem_size |
| 242 | return self.data.CreateChildAtOffset('[%s]' % index, offset, self.elem_type) |
| 243 | except Exception as e: |
| 244 | log.error('%s', e) |
| 245 | raise |
| 246 | |
| 247 | def value_at(self, index): |
| 248 | '''Returns array value at index''' |
| 249 | offset = index * self.elem_size |
| 250 | return self.data.CreateChildAtOffset('[%s]' % index, offset, self.elem_type) |
| 251 | |
| 252 | |
| 253 | class UtilsVectorPrinter(Printer): |
| 254 | '''Printer for tint::utils::Vector<T, N>''' |
| 255 | |
| 256 | def initialize(self): |
| 257 | self.slice = self.member('impl_', 'slice') |
| 258 | self.slice_printer = UtilsSlicePrinter(self.slice) |
| 259 | self.fixed_size = int(self.template_param_at(1)) |
| 260 | self.cap = self.slice_printer.member('cap') |
| 261 | |
| 262 | def get_summary(self): |
| 263 | using_heap = self.cap.GetValueAsUnsigned() > self.fixed_size |
| 264 | return 'heap={} {}'.format(using_heap, self.slice_printer.get_summary()) |
| 265 | |
| 266 | def num_children(self): |
| 267 | return self.slice_printer.num_children() |
| 268 | |
| 269 | def has_children(self): |
| 270 | return self.slice_printer.has_children() |
| 271 | |
| 272 | def get_child_at_index(self, index): |
| 273 | return self.slice_printer.get_child_at_index(index) |
| 274 | |
| 275 | def make_slice_printer(self): |
| 276 | return UtilsSlicePrinter(self.slice) |
| 277 | |
| 278 | |
| 279 | class UtilsVectorRefPrinter(Printer): |
| 280 | '''Printer for tint::utils::VectorRef<T>''' |
| 281 | |
| 282 | def initialize(self): |
| 283 | self.slice = self.member('slice_') |
| 284 | self.slice_printer = UtilsSlicePrinter(self.slice) |
| 285 | self.can_move = self.member('can_move_') |
| 286 | |
| 287 | def get_summary(self): |
| 288 | return 'can_move={} {}'.format(self.can_move.GetValue(), self.slice_printer.get_summary()) |
| 289 | |
| 290 | def num_children(self): |
| 291 | return self.slice_printer.num_children() |
| 292 | |
| 293 | def has_children(self): |
| 294 | return self.slice_printer.has_children() |
| 295 | |
| 296 | def get_child_at_index(self, index): |
| 297 | return self.slice_printer.get_child_at_index(index) |
| 298 | |
| 299 | |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 300 | class UtilsHashmapBasePrinter(Printer): |
| 301 | '''Base Printer for HashmapBase-derived types''' |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 302 | |
| 303 | def initialize(self): |
| 304 | self.slice = UtilsVectorPrinter( |
| 305 | self.member('slots_')).make_slice_printer() |
| 306 | |
| 307 | self.try_read_std_optional_func = self.try_read_std_optional |
| 308 | |
| 309 | def update(self): |
| 310 | self.valid_slots = [] |
| 311 | for slot in range(0, self.slice.num_children()): |
| 312 | v = self.slice.value_at(slot) |
| 313 | if member(v, 'hash').GetValueAsUnsigned() != 0: |
| 314 | self.valid_slots.append(slot) |
| 315 | return False |
| 316 | |
| 317 | def get_summary(self): |
| 318 | return 'length={}'.format(self.num_children()) |
| 319 | |
| 320 | def num_children(self): |
| 321 | return len(self.valid_slots) |
| 322 | |
| 323 | def has_children(self): |
| 324 | return True |
| 325 | |
| 326 | def get_child_at_index(self, index): |
| 327 | slot = self.valid_slots[index] |
| 328 | v = self.slice.value_at(slot) |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 329 | entry = member(v, 'entry') |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 330 | |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 331 | # entry is a std::optional, let's try to extract its value for display |
| 332 | kvp = self.try_read_std_optional_func(slot, entry) |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 333 | if kvp is None: |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 334 | # If we failed, just output the slot and entry as is, which will use |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 335 | # the default printer for std::optional. |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 336 | kvp = slot, entry |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 337 | |
| 338 | return kvp[1].CreateChildAtOffset('[{}]'.format(kvp[0]), 0, kvp[1].GetType()) |
| 339 | |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 340 | def try_read_std_optional(self, slot, entry): |
| 341 | return None |
| 342 | |
| 343 | |
| 344 | class UtilsHashsetPrinter(UtilsHashmapBasePrinter): |
| 345 | '''Printer for Hashset<T, N, HASH, EQUAL>''' |
| 346 | |
| 347 | def try_read_std_optional(self, slot, entry): |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 348 | try: |
| 349 | # libc++ |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 350 | v = entry.EvaluateExpression('__val_') |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 351 | if v.name is not None: |
| 352 | return slot, v |
| 353 | |
| 354 | # libstdc++ |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 355 | v = entry.EvaluateExpression('_M_payload._M_payload._M_value') |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 356 | if v.name is not None: |
| 357 | return slot, v |
| 358 | return None |
| 359 | except: |
| 360 | return None |
| 361 | |
| 362 | |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 363 | class UtilsHashmapPrinter(UtilsHashsetPrinter): |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 364 | '''Printer for Hashmap<K, V, N, HASH, EQUAL>''' |
| 365 | |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 366 | def try_read_std_optional(self, slot, entry): |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 367 | try: |
| 368 | # libc++ |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 369 | val = entry.EvaluateExpression('__val_') |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 370 | k = val.EvaluateExpression('key') |
| 371 | v = val.EvaluateExpression('value') |
| 372 | if k.name is not None and v.name is not None: |
| 373 | return k.GetValue(), v |
| 374 | |
| 375 | # libstdc++ |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 376 | val = entry.EvaluateExpression('_M_payload._M_payload._M_value') |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 377 | k = val.EvaluateExpression('key') |
| 378 | v = val.EvaluateExpression('value') |
| 379 | if k.name is not None and v.name is not None: |
| 380 | return k.GetValue(), v |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 381 | return None |
Antonio Maiorano | 14b5fb6 | 2022-10-27 20:17:45 +0000 | [diff] [blame] | 382 | except: |
Antonio Maiorano | c027f33 | 2022-11-07 14:00:53 +0000 | [diff] [blame] | 383 | return None |