blob: 84e6ad39961e21a307e76f593217137d1a6a67c0 [file] [log] [blame]
Antonio Maiorano14b5fb62022-10-27 20:17:45 +00001# 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
66from __future__ import print_function, division
67import sys
68import logging
69import re
70import lldb
71import types
72
73if 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
78else:
79 to_lldb_str = str
80
81string_encoding = "escape" # remove | unicode | escape
82
83log = logging.getLogger(__name__)
84
85module = sys.modules[__name__]
86tint_category = None
87
88
89def __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
111def 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
129def 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
140def 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
151def member(valobj, *chain):
152 '''Performs chained GetChildMemberWithName lookups'''
153 for name in chain:
154 valobj = valobj.GetChildMemberWithName(name)
155 return valobj
156
157
158class 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
215class 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
253class 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
279class 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 Maioranoc027f332022-11-07 14:00:53 +0000300class UtilsHashmapBasePrinter(Printer):
301 '''Base Printer for HashmapBase-derived types'''
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000302
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 Maioranoc027f332022-11-07 14:00:53 +0000329 entry = member(v, 'entry')
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000330
Antonio Maioranoc027f332022-11-07 14:00:53 +0000331 # 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 Maiorano14b5fb62022-10-27 20:17:45 +0000333 if kvp is None:
Antonio Maioranoc027f332022-11-07 14:00:53 +0000334 # If we failed, just output the slot and entry as is, which will use
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000335 # the default printer for std::optional.
Antonio Maioranoc027f332022-11-07 14:00:53 +0000336 kvp = slot, entry
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000337
338 return kvp[1].CreateChildAtOffset('[{}]'.format(kvp[0]), 0, kvp[1].GetType())
339
Antonio Maioranoc027f332022-11-07 14:00:53 +0000340 def try_read_std_optional(self, slot, entry):
341 return None
342
343
344class UtilsHashsetPrinter(UtilsHashmapBasePrinter):
345 '''Printer for Hashset<T, N, HASH, EQUAL>'''
346
347 def try_read_std_optional(self, slot, entry):
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000348 try:
349 # libc++
Antonio Maioranoc027f332022-11-07 14:00:53 +0000350 v = entry.EvaluateExpression('__val_')
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000351 if v.name is not None:
352 return slot, v
353
354 # libstdc++
Antonio Maioranoc027f332022-11-07 14:00:53 +0000355 v = entry.EvaluateExpression('_M_payload._M_payload._M_value')
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000356 if v.name is not None:
357 return slot, v
358 return None
359 except:
360 return None
361
362
Antonio Maioranoc027f332022-11-07 14:00:53 +0000363class UtilsHashmapPrinter(UtilsHashsetPrinter):
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000364 '''Printer for Hashmap<K, V, N, HASH, EQUAL>'''
365
Antonio Maioranoc027f332022-11-07 14:00:53 +0000366 def try_read_std_optional(self, slot, entry):
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000367 try:
368 # libc++
Antonio Maioranoc027f332022-11-07 14:00:53 +0000369 val = entry.EvaluateExpression('__val_')
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000370 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 Maioranoc027f332022-11-07 14:00:53 +0000376 val = entry.EvaluateExpression('_M_payload._M_payload._M_value')
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000377 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 Maioranoc027f332022-11-07 14:00:53 +0000381 return None
Antonio Maiorano14b5fb62022-10-27 20:17:45 +0000382 except:
Antonio Maioranoc027f332022-11-07 14:00:53 +0000383 return None