[CMake][Win] Only build DumpRenderTree when WebKit Legacy is enabled
[WebKit-https.git] / Tools / lldb / lldb_dump_class_layout.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright (C) 2018 Apple Inc. All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 # 1.  Redistributions of source code must retain the above copyright
10 #     notice, this list of conditions and the following disclaimer.
11 # 2.  Redistributions in binary form must reproduce the above copyright
12 #     notice, this list of conditions and the following disclaimer in the
13 #     documentation and/or other materials provided with the distribution.
14 #
15 # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
16 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
19 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26 import os
27 import re
28 import subprocess
29 import sys
30
31 from webkitpy.common.system.systemhost import SystemHost
32 sys.path.append(SystemHost().path_to_lldb_python_directory())
33 import lldb
34
35 # lldb Python reference:
36 # <https://lldb.llvm.org/python_reference/>
37
38
39 class AnsiColors:
40     BLUE = '\033[94m'
41     WARNING = '\033[93m'
42     ENDCOLOR = '\033[0m'
43
44
45 class ClassLayoutBase(object):
46
47     MEMBER_NAME_KEY = 'name'
48     MEMBER_TYPE_KEY = 'type'
49     MEMBER_TYPE_CLASS = 'type_class'  # lldb.eTypeClassStruct etc. Values here: <https://lldb.llvm.org/python_reference/_lldb%27-module.html#eTypeClassAny>
50     MEMBER_CLASS_INSTANCE = 'class_instance'
51     MEMBER_BYTE_SIZE = 'byte_size'
52     MEMBER_OFFSET = 'offset'  # offset is local to this class.
53     MEMBER_OFFSET_IN_BITS = 'offset_in_bits'
54     MEMBER_IS_BITFIELD = 'is_bitfield'
55     MEMBER_BITFIELD_BIT_SIZE = 'bit_size'
56     PADDING_BYTES_TYPE = 'padding'
57     PADDING_BITS_TYPE = 'padding_bits'
58     PADDING_BITS_SIZE = 'padding_bits_size'
59     PADDING_NAME = ''
60
61     def __init__(self, typename):
62         self.typename = typename
63         self.total_byte_size = 0
64         self.total_pad_bytes = 0
65         self.data_members = []
66
67     def __ne__(self, other):
68         return not self.__eq__(other)
69
70     def __eq__(self, other):
71         if not isinstance(other, self.__class__):
72             return False
73
74         if self.total_byte_size != other.total_byte_size:
75             return False
76
77         if self.total_pad_bytes != other.total_pad_bytes:
78             return False
79
80         if len(self.data_members) != other.data_members:
81             return False
82
83         for i in range(len(self.data_members)):
84             self_member = self.data_members[i]
85             other_member = other.data_members[i]
86             if self_member != other_member:
87                 return False
88
89         return True
90
91     def _to_string_recursive(self, str_list, colorize, member_name=None, depth=0, total_offset=0):
92         type_start = AnsiColors.BLUE if colorize else ''
93         warn_start = AnsiColors.WARNING if colorize else ''
94         color_end = AnsiColors.ENDCOLOR if colorize else ''
95
96         if member_name:
97             str_list.append('%+4u <%3u> %s%s%s%s %s' % (total_offset, self.total_byte_size, '    ' * depth, type_start, self.typename, color_end, member_name))
98         else:
99             str_list.append('%+4u <%3u> %s%s%s%s' % (total_offset, self.total_byte_size, '    ' * depth, type_start, self.typename, color_end))
100
101         start_offset = total_offset
102
103         for data_member in self.data_members:
104             member_total_offset = start_offset + data_member[self.MEMBER_OFFSET]
105             if self.MEMBER_CLASS_INSTANCE in data_member:
106                 data_member[self.MEMBER_CLASS_INSTANCE]._to_string_recursive(str_list, colorize, data_member[self.MEMBER_NAME_KEY], depth + 1, member_total_offset)
107             else:
108                 byte_size = data_member[self.MEMBER_BYTE_SIZE]
109
110                 if self.MEMBER_IS_BITFIELD in data_member:
111                     num_bits = data_member[self.MEMBER_BITFIELD_BIT_SIZE]
112                     str_list.append('%+4u < :%1u> %s  %s %s : %d' % (member_total_offset, num_bits, '    ' * depth, data_member[self.MEMBER_TYPE_KEY], data_member[self.MEMBER_NAME_KEY], num_bits))
113                 elif data_member[self.MEMBER_TYPE_KEY] == self.PADDING_BYTES_TYPE:
114                     str_list.append('%+4u <%3u> %s  %s<PADDING: %d %s>%s' % (member_total_offset, byte_size, '    ' * depth, warn_start, byte_size, 'bytes' if byte_size > 1 else 'byte', color_end))
115                 elif data_member[self.MEMBER_TYPE_KEY] == self.PADDING_BITS_TYPE:
116                     padding_bits = data_member[self.PADDING_BITS_SIZE]
117                     str_list.append('%+4u < :%1u> %s  %s<UNUSED BITS: %d %s>%s' % (member_total_offset, padding_bits, '    ' * depth, warn_start, padding_bits, 'bits' if padding_bits > 1 else 'bit', color_end))
118                 else:
119                     str_list.append('%+4u <%3u> %s  %s %s' % (member_total_offset, byte_size, '    ' * depth, data_member[self.MEMBER_TYPE_KEY], data_member[self.MEMBER_NAME_KEY]))
120
121     def as_string_list(self, colorize=False):
122         str_list = []
123         self._to_string_recursive(str_list, colorize)
124         str_list.append('Total byte size: %d' % (self.total_byte_size))
125         str_list.append('Total pad bytes: %d' % (self.total_pad_bytes))
126         if self.total_pad_bytes > 0:
127             str_list.append('Padding percentage: %2.2f %%' % ((float(self.total_pad_bytes) / float(self.total_byte_size)) * 100.0))
128         return str_list
129
130     def as_string(self, colorize=False):
131         return '\n'.join(self.as_string_list(colorize))
132
133     def dump(self, colorize=True):
134         print self.as_string(colorize)
135
136
137 class ClassLayout(ClassLayoutBase):
138     "Stores the layout of a class or struct."
139
140     def __init__(self, target, type, containerClass=None, derivedClass=None):
141         super(ClassLayout, self).__init__(type.GetName())
142
143         self.target = target
144         self.type = type
145         self.total_byte_size = self.type.GetByteSize()
146         self.pointer_size = self.target.GetAddressByteSize()
147         self.total_pad_bytes = 0
148         self.data_members = []
149         self.virtual_base_classes = self._virtual_base_classes_dictionary()
150         self._parse(containerClass, derivedClass)
151         if containerClass == None and derivedClass == None:
152             self.total_pad_bytes = self._compute_padding()
153
154     def _has_polymorphic_non_virtual_base_class(self):
155         num_base_classes = self.type.GetNumberOfDirectBaseClasses()
156         for i in range(num_base_classes):
157             base_class = self.type.GetDirectBaseClassAtIndex(i)
158             if base_class.GetName() in self.virtual_base_classes:
159                 continue
160
161             if base_class.GetType().IsPolymorphicClass():
162                 return True
163
164         return False
165
166     def _virtual_base_classes_dictionary(self):
167         result = {}
168         num_virtual_base_classes = self.type.GetNumberOfVirtualBaseClasses()
169         for i in range(num_virtual_base_classes):
170             virtual_base = self.type.GetVirtualBaseClassAtIndex(i)
171             result[virtual_base.GetName()] = ClassLayout(self.target, virtual_base.GetType(), self)
172         return result
173
174     def _parse(self, containerClass=None, derivedClass=None):
175         # It's moot where we actually show the vtable pointer, but to match clang -fdump-record-layouts, assign it to the
176         # base-most polymorphic class (unless virtual inheritance is involved).
177         if self.type.IsPolymorphicClass() and not self._has_polymorphic_non_virtual_base_class():
178             data_member = {
179                 self.MEMBER_NAME_KEY : '__vtbl_ptr_type * _vptr',
180                 self.MEMBER_TYPE_KEY : '',
181                 self.MEMBER_BYTE_SIZE : self.pointer_size,
182                 self.MEMBER_OFFSET : 0
183             }
184             self.data_members.append(data_member)
185
186         num_direct_base_classes = self.type.GetNumberOfDirectBaseClasses()
187         if num_direct_base_classes > 0:
188             for i in range(num_direct_base_classes):
189                 direct_base = self.type.GetDirectBaseClassAtIndex(i)
190
191                 # virtual base classes are also considered direct base classes, but we need to skip those here.
192                 if direct_base.GetName() in self.virtual_base_classes:
193                     continue
194
195                 member_type = direct_base.GetType()
196                 member_typename = member_type.GetName()
197                 member_canonical_type = member_type.GetCanonicalType()
198                 member_type_class = member_canonical_type.GetTypeClass()
199
200                 member_name = direct_base.GetName()
201                 member_offset = direct_base.GetOffsetInBytes()
202                 member_byte_size = member_type.GetByteSize()
203
204                 base_class = ClassLayout(self.target, member_type, None, self)
205
206                 data_member = {
207                     self.MEMBER_NAME_KEY : member_name,
208                     self.MEMBER_TYPE_KEY : member_typename,
209                     self.MEMBER_TYPE_CLASS : member_type_class,
210                     self.MEMBER_CLASS_INSTANCE : base_class,
211                     self.MEMBER_BYTE_SIZE : member_byte_size,
212                     self.MEMBER_OFFSET : member_offset,
213                 }
214
215                 self.data_members.append(data_member)
216
217         num_fields = self.type.GetNumberOfFields()
218         for i in range(num_fields):
219             field = self.type.GetFieldAtIndex(i)
220
221             member_type = field.GetType()
222             member_typename = member_type.GetName()
223             member_canonical_type = member_type.GetCanonicalType()
224             member_type_class = member_canonical_type.GetTypeClass()
225
226             member_name = field.GetName()
227             member_offset = field.GetOffsetInBytes()
228             member_byte_size = member_type.GetByteSize()
229
230             data_member = {
231                 self.MEMBER_NAME_KEY : member_name,
232                 self.MEMBER_TYPE_KEY : member_typename,
233                 self.MEMBER_TYPE_CLASS : member_type_class,
234                 self.MEMBER_BYTE_SIZE : member_byte_size,
235                 self.MEMBER_OFFSET : member_offset,
236             }
237
238             if field.IsBitfield():
239                 data_member[self.MEMBER_IS_BITFIELD] = True
240                 data_member[self.MEMBER_BITFIELD_BIT_SIZE] = field.GetBitfieldSizeInBits()
241                 data_member[self.MEMBER_OFFSET_IN_BITS] = field.GetOffsetInBits()
242                 # For bitfields, member_byte_size was computed based on the field type without the bitfield modifiers, so compute from the number of bits.
243                 data_member[self.MEMBER_BYTE_SIZE] = (field.GetBitfieldSizeInBits() + 7) / 8
244             elif member_type_class == lldb.eTypeClassStruct or member_type_class == lldb.eTypeClassClass:
245                 nested_class = ClassLayout(self.target, member_type, self)
246                 data_member[self.MEMBER_CLASS_INSTANCE] = nested_class
247
248             self.data_members.append(data_member)
249
250         # "For each distinct base class that is specified virtual, the most derived object contains only one base class subobject of that type,
251         # even if the class appears many times in the inheritance hierarchy (as long as it is inherited virtual every time)."
252         num_virtual_base_classes = self.type.GetNumberOfVirtualBaseClasses()
253         if derivedClass == None and num_virtual_base_classes > 0:
254             for i in range(num_virtual_base_classes):
255                 virtual_base = self.type.GetVirtualBaseClassAtIndex(i)
256                 member_type = virtual_base.GetType()
257                 member_typename = member_type.GetName()
258                 member_canonical_type = member_type.GetCanonicalType()
259                 member_type_class = member_canonical_type.GetTypeClass()
260
261                 member_name = virtual_base.GetName()
262                 member_offset = virtual_base.GetOffsetInBytes()
263                 member_byte_size = member_type.GetByteSize()
264
265                 nested_class = ClassLayout(self.target, member_type, None, self)
266
267                 data_member = {
268                     self.MEMBER_NAME_KEY : member_name,
269                     self.MEMBER_TYPE_KEY : member_typename,
270                     self.MEMBER_TYPE_CLASS : member_type_class,
271                     self.MEMBER_CLASS_INSTANCE : nested_class,
272                     self.MEMBER_BYTE_SIZE : member_byte_size,
273                     self.MEMBER_OFFSET : member_offset,
274                 }
275
276                 self.data_members.append(data_member)
277
278     # clang -fdump-record-layouts shows "(empty)" for such classes, but I can't find any way to access this information via lldb.
279     def _probably_has_empty_base_class_optimization(self):
280         if self.total_byte_size > 1:
281             return False
282
283         if len(self.data_members) > 1:
284             return False
285
286         if len(self.data_members) == 1:
287             data_member = self.data_members[0]
288             if self.MEMBER_CLASS_INSTANCE in data_member:
289                 return data_member[self.MEMBER_CLASS_INSTANCE]._probably_has_empty_base_class_optimization()
290
291         return True
292
293     def _compute_padding_recursive(self, total_offset=0, depth=0, containerClass=None):
294         padding_bytes = 0
295         start_offset = total_offset
296         current_offset = total_offset
297
298         i = 0
299         while i < len(self.data_members):
300             data_member = self.data_members[i]
301             member_offset = data_member[self.MEMBER_OFFSET]
302
303             probably_empty_base_class = False
304             if self.MEMBER_CLASS_INSTANCE in data_member:
305                 probably_empty_base_class = member_offset == 0 and data_member[self.MEMBER_CLASS_INSTANCE]._probably_has_empty_base_class_optimization()
306
307             byte_size = data_member[self.MEMBER_BYTE_SIZE]
308
309             if not probably_empty_base_class:
310                 padding_size = start_offset + member_offset - current_offset
311
312                 if padding_size > 0:
313                     padding_member = {
314                         self.MEMBER_NAME_KEY : self.PADDING_NAME,
315                         self.MEMBER_TYPE_KEY : self.PADDING_BYTES_TYPE,
316                         self.MEMBER_BYTE_SIZE : padding_size,
317                         self.MEMBER_OFFSET : current_offset - start_offset,
318                     }
319
320                     self.data_members.insert(i, padding_member)
321                     padding_bytes += padding_size
322                     i += 1
323
324                 if self.MEMBER_IS_BITFIELD in data_member:
325                     next_member_is_bitfield = False
326                     if i < len(self.data_members) - 1:
327                         next_data_member = self.data_members[i + 1]
328                         next_member_is_bitfield = self.MEMBER_IS_BITFIELD in next_data_member
329
330                     if not next_member_is_bitfield:
331                         end_bit_offset = data_member[self.MEMBER_OFFSET_IN_BITS] + data_member[self.MEMBER_BITFIELD_BIT_SIZE]
332                         unused_bits = (8 - end_bit_offset) % 8
333                         if unused_bits:
334                             bit_padding_member = {
335                                 self.MEMBER_NAME_KEY : self.PADDING_NAME,
336                                 self.MEMBER_TYPE_KEY : self.PADDING_BITS_TYPE,
337                                 self.MEMBER_BYTE_SIZE : data_member[self.MEMBER_BYTE_SIZE],
338                                 self.PADDING_BITS_SIZE : unused_bits,
339                                 self.MEMBER_OFFSET : data_member[self.MEMBER_OFFSET],
340                             }
341                             self.data_members.insert(i + 1, bit_padding_member)
342                             i += 1
343
344                 current_offset = start_offset + member_offset
345
346             if self.MEMBER_CLASS_INSTANCE in data_member:
347                 [padding, offset] = data_member[self.MEMBER_CLASS_INSTANCE]._compute_padding_recursive(current_offset, depth + 1, self)
348                 padding_bytes += padding
349                 current_offset = offset
350             else:
351                 current_offset += byte_size
352
353             i += 1
354
355         # Look for padding at the end.
356         if containerClass == None:
357             padding_size = self.total_byte_size - current_offset
358             if padding_size > 0:
359                 padding_member = {
360                     self.MEMBER_NAME_KEY : self.PADDING_NAME,
361                     self.MEMBER_TYPE_KEY : self.PADDING_BYTES_TYPE,
362                     self.MEMBER_BYTE_SIZE : padding_size,
363                     self.MEMBER_OFFSET : current_offset - start_offset,
364                 }
365                 self.data_members.append(padding_member)
366                 padding_bytes += padding_size
367
368         return [padding_bytes, current_offset]
369
370     def _compute_padding(self):
371         [padding, offset] = self._compute_padding_recursive()
372         return padding
373
374
375 class LLDBDebuggerInstance:
376     "Wraps an instance of lldb.SBDebugger and vends ClassLayouts"
377
378     def __init__(self, binary_path, architecture):
379         self.binary_path = binary_path
380         self.architecture = architecture
381
382         self.debugger = lldb.SBDebugger.Create()
383         self.debugger.SetAsync(False)
384         architecture = self.architecture
385         if not architecture:
386             architecture = self._get_first_file_architecture()
387
388         self.target = self.debugger.CreateTargetWithFileAndArch(str(self.binary_path), architecture)
389         if not self.target:
390             print "Failed to make target for " + self.binary_path
391
392         self.module = self.target.GetModuleAtIndex(0)
393         if not self.module:
394             print "Failed to get first module in " + self.binary_path
395
396     def __del__(self):
397         if lldb:
398             lldb.SBDebugger.Destroy(self.debugger)
399
400     def _get_first_file_architecture(self):
401         p = re.compile('shared library +(\w+)$')
402         file_result = subprocess.check_output(["file", self.binary_path]).split('\n')
403         arches = []
404         for line in file_result:
405             match = p.search(line)
406             if match:
407                 arches.append(match.group(1))
408
409         if len(arches) > 0:
410             return arches[0]
411
412         return lldb.LLDB_ARCH_DEFAULT
413
414     def layout_for_classname(self, classname):
415         types = self.module.FindTypes(classname)
416         if types.GetSize():
417             # There can be more that one type with a given name, but for now just return the first one.
418             return ClassLayout(self.target, types.GetTypeAtIndex(0))
419
420         print 'error: no type matches "%s" in "%s"' % (classname, self.module.file)
421         return None