xref: /xnu-11417.140.69/tools/lldbmacros/tests/lldbmock/memorymock.py (revision 43a90889846e00bfb5cf1d255cdc0a701a1e05a4)
1##
2# Copyright (c) 2023 Apple Inc. All rights reserved.
3#
4# @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5#
6# This file contains Original Code and/or Modifications of Original Code
7# as defined in and that are subject to the Apple Public Source License
8# Version 2.0 (the 'License'). You may not use this file except in
9# compliance with the License. The rights granted to you under the License
10# may not be used to create, or enable the creation or redistribution of,
11# unlawful or unlicensed copies of an Apple operating system, or to
12# circumvent, violate, or enable the circumvention or violation of, any
13# terms of an Apple operating system software license agreement.
14#
15# Please obtain a copy of the License at
16# http://www.opensource.apple.com/apsl/ and read it before using this file.
17#
18# The Original Code and all software distributed under the License are
19# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23# Please see the License for the specific language governing rights and
24# limitations under the License.
25#
26# @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27##
28
29""" Mocking framework for LLDB scripted process target.
30
31    The goal of this module is to provide a mock object that behaves like
32    original SBType in target but serializes its properties into memory.
33
34    That allows injection of artificial type instances into testing target.
35"""
36
37from abc import ABC, abstractmethod
38import typing
39import lldb
40import io
41from lldbmock.utils import lookup_type, Singleton
42
43
44#
45# Data serializers
46#
47# A goal of a serializer is to convert between Python's native type and byte
48# array used internally by mocking layer. This allows users to more easily operate
49# on mock properties.
50#
51# For example:
52#
53#      mock.numeric_property = 0x12345
54#
55# The value above ends up encoded as little endian at correct offset in the
56# mocked type memory.
57#
58
59
60class Serializer(ABC):
61    """ Value serializer. """
62
63    @abstractmethod
64    def accepts(self, value: typing.Any) -> bool:
65        """ Checks that value instance is supported. """
66
67    @abstractmethod
68    def serialize(self, value: typing.Any, size: int) -> bytes:
69        """ Serializes value to raw bytes"""
70
71    @abstractmethod
72    def deserialize(self, data: bytes, offs: int, size: int) -> typing.Any:
73        """ Deserialize value from raw bytes. """
74
75    @abstractmethod
76    def default(self):
77        """ Return default value for a property. """
78
79    @staticmethod
80    def createSerializerForType(sbtype: lldb.SBType) -> 'Serializer':
81        """ Construct serializer for given SBType. """
82
83        flags = sbtype.GetTypeFlags()
84
85        if (flags & (lldb.eTypeIsInteger | lldb.eTypeIsPointer)) != 0:
86            return NumericSerializer()
87
88        # Default: No serializer enforces bytes instances as input.
89        return NoSerializer()
90
91
92class NoSerializer(Serializer, metaclass=Singleton):
93    """ No transformation, only enforces bytes as input. """
94
95    def default(self):
96        return b''
97
98    def accepts(self, value: typing.Any) -> bool:
99        if isinstance(value, bytes) or isinstance(value, bytearray):
100            return True
101
102        return False
103
104    def serialize(self, value: typing.Any, size: int) -> bytes:
105        return value[:size]
106
107    def deserialize(self, data: bytes, offs: int, size: int) -> typing.Any:
108        return data[offs : offs + size]
109
110
111class NumericSerializer(Serializer, metaclass=Singleton):
112    """ Serializes python's numeric (integral) types to bytes. """
113
114    def default(self):
115        return 0
116
117    def accepts(self, value: typing.Any) -> bool:
118        if isinstance(value, int):
119            return True
120
121        return False
122
123    def serialize(self, value: typing.Any, size: int) -> bytes:
124        return value.to_bytes(length=size, byteorder='little')
125
126    def deserialize(self, data: bytes, offs: int, size: int) -> typing.Any:
127        return int.from_bytes(data[offs: offs + size], byteorder='little')
128
129
130#
131# Mock class properties
132#
133# Mock does not create attributes on an instance class. Instead a MockProperty
134# is created on a base class that contains enough metadata to find a value of
135# a property in class instance's buffer.
136#
137# To achieve this a MockProperty implements Python's descriptor protocol and
138# overrides __get__/__set__ methods. Every access results in data being
139# serialized or deserialized from the instance's buffer.
140#
141#        +-----------------+                     +-----------------+
142#        | Mock base class |                     | Mock instance   |
143#        +-----------------+                     +-----------------+
144#        |                 |                     | Buffer          |
145#        |                 |                     |                 |
146#        |                 |<--------------------|                 |
147#        |                 |                     |                 |
148#        +-----------------+                     |    +-------+    |
149#        | MockProperty    |                     |    +-------+    |
150#        +-----------------+                     |        |        |
151#        |        |        |                     |        |        |
152#        +--------|--------+                     +--------|--------+
153#                 +---------------------------------------+
154#
155#
156# It is allowed to create overlaping properties. This helps in solving support
157# for union types.
158
159class MockProperty:
160    """ Serializable property on the mock object.
161
162        A property maintains size/offset based on DWARF so it knows where to
163        serialize its own data inside an owner's buffer.
164    """
165
166    def __init__(self, offs, sz, serializer=NoSerializer()):
167        """ Create property with given ofset, size and serializer. """
168
169        self._offs = offs
170        self._sz = sz
171        self._attrname = None
172        self.serializer = serializer
173
174    def __set__(self, instance, value):
175        """ Updates shadow attribute on target's instance. """
176
177        # Enforce that value is serializable.
178        if not self.serializer.accepts(value):
179            raise AttributeError("Unsupported value for this property")
180
181        # Serialize value to instance's buffer
182        data = self.serializer.serialize(value, self._sz)
183        instance._buf[self._offs: self._offs + min(self._sz, len(data))] = data
184
185    def __get__(self, instance, owner = None):
186        """ Retruns value from the shadow attribute on an instance. """
187
188        return self.serializer.deserialize(instance._buf, self._offs, self._sz)
189
190    def __delete__(self, instance):
191        """ Deletes property. """
192
193        # It is not possible to delete a type's member dynamically.
194        # The property mirros that behavior.
195        raise AttributeError("MockProperty instances can't be deleted.")
196
197    def __set_name__(self, owner, name):
198        """ Registers owning class property name. """
199
200        self._attrname = f'_{name}'
201
202    def deserialize(self, data: bytes) -> typing.Any:
203        """ De-serializes value of a property from data. """
204
205        return self.serializer.deserialize(data, self._offs, self._sz)
206
207
208class BitArray:
209    """ Simple wrapper around bytearray that provites bit access.
210
211        Note: The implementation is limited to mock requirements.
212              Not suitable for general purpose use.
213    """
214
215    def __init__(self, bytes):
216        self._buf = bytes
217
218    def __getbit(self, idx):
219        byte = self._buf[idx // 8]
220        return byte & (1 << (idx % 8)) != 0
221
222    def __setbit(self, idx, value):
223        if value:
224            self._buf[idx // 8] |= (1 << idx % 8)
225        else:
226            self._buf[idx // 8] &= ~(1 << idx % 8)
227
228    def __getitem__(self, key):
229        if isinstance(key, slice):
230            return [self[ii] for ii in range(*key.indices(len(self)))]
231
232        return self.__getbit(key)
233
234    def __setitem__(self, key, value):
235        if isinstance(key, slice):
236            raise NotImplementedError("Not donbe yet")
237
238        self.__setbit(key, value)
239
240    def __len__(self):
241        return len(self._buf) * 8
242
243    def __str__(self):
244        s = "b"
245        for byte in self._buf:
246            for bit in range(0, 7):
247                s += "1" if byte & (1 << bit) else "0"
248
249        return s
250
251
252class BitfieldProperty(MockProperty):
253    """ Similar to MockProperty but all operations are in bits.
254
255        This type of property is used exclusively to implement bitfields.
256    """
257
258    def __init__(self, offs, sz, serializer=NoSerializer()):
259        # Enforce NumericSerializer for bitfields.
260        super().__init__(offs, sz, NumericSerializer())
261
262        self._bsz = (sz + 8) >> 3
263
264    def __set__(self, instance, value):
265        """ Updates shadow attribute on target's instance. """
266
267        # Enforce that value is serializable.
268        if not self.serializer.accepts(value):
269            raise AttributeError("Unsupported value for this property")
270
271        # Serialize value to instance's buffer
272        data = self.serializer.serialize(value, self._bsz)
273        barr = BitArray(instance._buf)
274
275        offs = 0
276        for b in data:
277            for i in range(0, 8):
278                barr[self._offs + offs] = b & (1 << i) != 0
279
280                if offs == self._sz - 1:
281                    break
282
283                offs += 1
284
285    def __get__(self, instance, owner = None):
286        """ Retruns value from the shadow attribute on an instance. """
287
288        barrb = BitArray(instance._buf)
289        ba = bytearray(self._bsz)
290        barr = BitArray(ba)
291
292        newoffs = 0
293        for offs in range(self._offs, self._offs + self._sz):
294            barr[newoffs] = barrb[offs]
295            newoffs += 1
296
297        return self.serializer.deserialize(ba, 0, self._bsz)
298
299#
300# Proxied properties
301#
302# Proxy properties are used to expose members of anonymous structures or unions
303# at the top-level instance. ProxyProperty implements descriptor protocol like
304# MockProperty. Instead of keeping metadata about buffer location it delegates
305# all opertions to the proxy target. A proxy target's instance is stored in
306# mock instance attribute.
307#
308#        +-----------------+                     +-----------------+
309#        | Mock base class |                     | Mock instance   |
310#        +-----------------+                     +-----------------+
311#        |                 |                     | Buffer          |
312#        |                 |                     |                 |
313#        |                 |<--------------------|                 |
314#        |                 |                     |                 |
315#        +-----------------+                     |                 |
316#        | ProxyProperty   |----------+          |                 |
317#        +-----------------+          |          +-----------------+
318#        |                 |          +--------->| ProxyDesination |
319#        +-----------------+                     +-----------------+
320#                                                         |
321#  +------------------------------------------------------+
322#  |
323#  |     +-----------------+                     +-------------------+
324#  |     | Sub-mock base   |                     | Sub-mock instance |
325#  |     | class           |                     +-------------------+
326#  |     +-----------------+                     |                   |
327#  |     |                 |                     |                   |
328#  |     |                 |<--------------------|                   |
329#  |     |                 |                     |                   |
330#  |     +-----------------+                     |     +-------+     |
331#  +---->| MockProperty    |                     |     +-------+     |
332#        +-----------------+                     |         |         |
333#        |        |        |                     |         |         |
334#        +--------|--------+                     +---------|---------+
335#                 +----------------------------------------+
336#
337
338
339class ProxyProperty:
340    """ Proxies requests to a property on an unrelated class instance.
341
342        This property alows to expose anon struct/union members in a top
343        level mock class (Similar to what compilers are doing).
344    """
345
346    def __init__(self):
347        """ Initializes unound proxy property. """
348
349        self._attrproxy = None
350        self._attrname = None
351
352    def __set__(self, instance, value):
353        """ Forwards set operation to proxy. """
354
355        proxy = getattr(instance, self._attrproxy)
356        setattr(proxy, self._attrname, value)
357
358    def __get__(self, instance, owner = None):
359        """ Forwards get operation to proxy. """
360
361        proxy = getattr(instance, self._attrproxy)
362        return getattr(proxy, self._attrname)
363
364    def __delete__(self, instance):
365        """ Deletes property. """
366
367        raise AttributeError("ProxyProperty instances can't be deleted.")
368
369    def __set_name__(self, owner, name):
370        """ Registers owning class property name. """
371
372        self._attrname = name
373        self._attrproxy = f'_$proxy_{name}'
374
375    def setProxy(self, instance, proxy):
376        """ Bind this descriptor to proxy target. """
377
378        setattr(instance, self._attrproxy, proxy)
379
380
381#
382# Base mock classes
383#
384# Every mock class is derived from BaseMock class which provides common
385# behavior / logic.
386#
387# One of the goals is to detect broken references between test/macro and kernel
388# structures. It is required to disallow creation of new attributes on an instance
389# by mock client. For that reason a mock instance can be finalized (frozen).
390# This prevents user from accidentally creating members that does not exist in
391# original structure.
392#
393
394
395class BaseMock(ABC):
396    """ Abstract base class serving as a base of every scripted process mock. """
397
398    __frozen = False
399
400    def __init__(self, size: int):
401        """ Init mock with given offset and size. """
402
403        self._size = size
404        self.log = lldb.test_logger.getChild(self.__class__.__name__)
405
406
407    def __setattr__(self, key: str, value: typing.Any) -> None:
408        """ Sets new attribute value or creates one (if not frozen). """
409
410        # Raise exception if new attribute is being created on frozen mock.
411        if self.__frozen and not hasattr(self, key):
412            self.log.debug("Frozen mock missing attribute %s", key)
413            raise TypeError(f"Can't add {key} as {self} is a frozen")
414
415        return super().__setattr__(key, value)
416
417    def freeze(self):
418        """ Freeze the mock so no additional attributes can be created. """
419
420        self.__frozen = True
421
422    # Please be conservative when adding methods / attributes here.
423    # Each such method may conflict with members a mock sub-class may create.
424
425    @property
426    def size(self):
427        """ Size in bytes of this mock. """
428
429        return self._size
430
431    def fromDict(self, members):
432        """ Initialize mock members from dictionary. """
433
434        for k, v in members.items():
435            if isinstance(v, dict):
436                getattr(self, k).fromDict(v)
437
438            setattr(self, k, v)
439
440        return self
441
442    @abstractmethod
443    def getData(self):
444        """ Returns byte representation of the mock. """
445
446    @abstractmethod
447    def setData(self, data):
448        """ Restores mock attributes from bytes. """
449
450        # Take care when implementing this method as sub-mocks may reference
451        # existing data instance. Replacing underlying buffer with new one
452        # may result in data no longer being shared with sub-mocks.
453
454
455class RawMock(BaseMock):
456    """ Simple mock that wraps raw data that are going to be placed in memory.
457
458        This mock does not have any attributes. It is possible to provide a
459        serializer to allow converstion from types like string.
460    """
461
462    def __init__(self, size: int, serializer=NoSerializer()):
463        """ A mock that holds raw bytes for given offset/size range. """
464
465        super().__init__(size)
466        self._data = serializer.default()
467        self.serializer = serializer
468        self.freeze()
469
470    def getData(self):
471        """ Returns memory view based on the data in this mock. """
472
473        return memoryview(self._data)
474
475    def setData(self, data):
476        """ Sets value of the mock. """
477
478        if self.serializer:
479            self._data = self.serializer.serialize(data, self.size)
480        else:
481            self._data = data
482
483    def fromDict(self, members):
484        """ Not supported by raw memory mock """
485        raise NotImplementedError("RawMock can't be populated from dict.")
486
487    @staticmethod
488    def fromBufferedIO(fromIO: io.BufferedIOBase) -> 'RawMock':
489        """ Populate mock data from I/O intance. """
490
491        data = fromIO.read()
492        mock = RawMock(len(data))
493        mock.setData(data)
494
495        return mock
496
497class ArrayMock(BaseMock):
498    """ Inserts array of mocks into target's memory.
499
500        High-level wrapper that constructs array of mocks of given type.
501        All mocks share same underlying buffer.
502    """
503
504    def __init__(self, sbtype: lldb.SBType, parentBuf=None):
505        """ """
506        super().__init__(sbtype.GetByteSize())
507
508        # Top level array mock will allocate buffer. Otherwise it will distribute
509        # sub-mocks across parent's buffer.
510        if parentBuf:
511            self._data = memoryview(parentBuf)[:self._size]
512        else:
513            self._data = bytearray(self._size)
514
515        elem_sbtype = sbtype.GetArrayElementType()
516        self._count = self._size // elem_sbtype.GetByteSize()
517
518        self._arrmocks = []
519        offs = 0
520        for _ in range(self._count):
521            submock = MockFactory.createFromType(elem_sbtype, 0,
522                        memoryview(self._data)[offs: offs + sbtype.GetByteSize()])
523            self._arrmocks.append(submock)
524            offs += elem_sbtype.GetByteSize()
525
526    def getData(self):
527        return memoryview(self._data)
528
529    def setData(self, data):
530        self._data[0: len(data)] = data
531
532    def fromDict(self, members):
533        for k, v in members.items():
534            idx = int(k)
535            self._arrmocks[idx].fromDict(v)
536
537        return self
538
539    def __getitem__(self, key):
540        """ Returns sub-mock at given index. """
541        if isinstance(key, slice):
542            return [self[ii] for ii in range(*key.indices(len(self)))]
543
544        return self._arrmocks[key]
545
546
547class MemoryMock(BaseMock):
548    """ Inserts serialized MemoryMock directly into target's memory. """
549
550    def __init__(self, sbtype: lldb.SBType, buf):
551        super().__init__(sbtype.GetByteSize())
552        self._sbtype = sbtype
553        self._anon_mocks = []
554        self._buf = buf
555
556    def setData(self, data: bytes):
557        """ Set underlying buffer and reconstruct mock values. """
558        self.log.debug("setData refereshing mocks")
559
560        # Setting data on a sub-mock is not allowed.
561        # Sub-mocks are always using memoryviews.
562        if isinstance(self._buf, memoryview):
563            raise AttributeError("Can't set data on a sub-mock.")
564
565        self._buf[0: len(data)] = data
566
567    def getData(self):
568        """ Return memory view of mock's data buffer. """
569
570        return memoryview(self._buf)
571
572
573#
574# Mock factories
575#
576# Abstracts away mock creation from the actual mock instances. Most of the
577# factories are singletons.
578#
579
580class MockFactory(ABC):
581    """ Abstract base class factory.
582    """
583
584    def __init__(self):
585        """ Initialize factory. """
586        self.log = lldb.test_logger.getChild(self.__class__.__name__)
587
588    @abstractmethod
589    def create_mock(self, sbtype: lldb.SBType, offs: int = 0, parent_buf = None):
590        """ Constructs concrete mock class instance for given SBType. """
591
592    @staticmethod
593    def createFromType(mocktype: typing.Union[str, lldb.SBType],
594                       offs: int = 0, parent_buf = None) -> 'MemoryMock':
595        """ Top-level factory method available to users. """
596
597        # Lookup type to be created
598        sbtype = lookup_type(mocktype)
599        if not sbtype or not sbtype.IsValid():
600            raise AttributeError("Unknown type")
601
602        # Resolve typedefs to canconical type (to avoid typedefs)
603        sbtype = sbtype.GetCanonicalType()
604
605        # Select factory based on type.
606        factory = SimpleTypeFactory()
607        type_class = sbtype.GetTypeClass()
608
609        # Structures/Unions
610        if type_class == lldb.eTypeClassStruct or type_class == lldb.eTypeClassUnion:
611            factory = CompoundTypeFactory()
612
613        # Arrays
614        if type_class == lldb.eTypeClassArray:
615            factory = ArrayTypeFactory()
616
617        # Use factory's method to create mock class instance.
618        return factory.create_mock(sbtype, offs, parent_buf)
619
620
621class SimpleTypeFactory(MockFactory, metaclass=Singleton):
622    """ Constructs mocking class for a simple types. """
623
624    def create_mock(self, sbtype: lldb.SBType, offs: int = 0, parent_buf = None):
625        """ Create RawMock instance for simple types."""
626
627        self.log.debug("Creating mock for %s", sbtype.GetName())
628
629        # Simple type does not have any members.
630        if sbtype.GetNumberOfFields() > 0:
631            raise AttributeError("Not a simple type")
632
633        # Return new mock instance and freeze it.
634        mock = RawMock(size=sbtype.GetByteSize(),
635                       serializer=Serializer.createSerializerForType(sbtype))
636        mock.freeze()
637        return mock
638
639
640class ArrayTypeFactory(MockFactory, metaclass=Singleton):
641    """ Constructs mocking class for array types. """
642
643    def create_mock(self, sbtype: lldb.SBType, offs: int = 0, parent_buf = None):
644        """ Delegate array type creation and construct an array. """
645
646        self.log.debug("Creating mock for %s", sbtype.GetName())
647        mock = ArrayMock(sbtype)
648        mock.freeze()
649        return mock
650
651
652#
653# Compound type mocks
654#
655# A compound mock tries to convert SBType to a native Python class. Such conversion
656# happens in three phases:
657#
658#    1. SBType gets converted to new Python class defintion with MockProperties.
659#    2. Mock is created as an instance of the new class.
660#    3. Postprocessing resolves binding / nesting.
661#    4. Mocks are frozen and returned to a client.
662#
663# It may be required to repeat steps above multiple times to recrete whole
664# type hierarchy. The top level mock is byte array provider for the whole
665# hierarchy of sub-mocks. This avoids the need for complex sync between class
666# attributes and final byte array holding serialized copy of the type value.
667# At the same time it makes handling of unions easier because it is possible
668# to simply create overlaping sub-mocks that share same parent's buffer area.
669#
670# Example - Simple type:
671#
672#    struct node {
673#        uint64_t memA;
674#        uint64_t memB;
675#    }
676#
677# First a Mock_node class is created by factory. This class gets two properties
678#    - MockProperty<int> memA which spans indexes (0 .. 7)
679#    - MockProperty<int> memB which spans indexes (8 .. 15)
680#
681# An instance of the Mock_node class is created which will hold the buffer
682# Any operation on a MockProperty will result in serializer/deserializer being
683# invoked directly on the associated buffer area.
684#
685# Example - Nested structures:
686#
687#    struct node_t {
688#        struct {
689#             uint64_t member;
690#        } sub_node;
691#    }
692#
693# Nested structures are handled by creation of sub-mock for given type
694# of a compound member. The only difference is that sub-mock instance will not
695# have its own buffer. Instead it is given view of parent's buffer arrea covered
696# by the sub-mock. This way all changes are propagated to the parent and all
697# offsets within sub-mock can be applied directly as the view itself is offset
698# from the start of buffer.
699#
700# Example - Anonymous members
701#
702#    struct node_t {
703#        struct {
704#            uint64_t member;
705#        }
706#    }
707#
708# This is more complex example because there is no way how to reference anon
709# type in the structure. However C compiler allows accessing such members by
710# thier names.
711#
712# CompoundMock supports anonymous members in a following way:
713#
714#     1. A nested sub-mock is created like in example above.
715#     3. A ProxyProperty is added to top level mock (unbound) for every anon
716#        member's child.
717#     2. Reference is kept in parent's _anon_mocks (becasue there is no member)
718#     4. Sub-mocks are instantiated
719#     5. Proxy lookup will find sub-mock instances providing a member and
720#        bind top-level proxies to it.
721#
722# See ProxyProperty comment above for illustration.
723#
724# This way a top-level mock delegates property handling to a submock. Submock
725# then propagates all changes back to top-level mock's buffer.
726#
727
728
729class CompoundTypeFactory(MockFactory, metaclass=Singleton):
730    """ Constructs mock class and instance for non-trivial types. """
731
732    cache = {}
733
734    def _make_property(self, member: lldb.SBTypeMember):
735        """ Creates correct property type based on type member. """
736
737        if member.IsBitfield():
738            self.log.debug("Creating BitfieldProperty for %s", member.GetName())
739            return BitfieldProperty(
740                member.GetOffsetInBits(),
741                member.GetBitfieldSizeInBits(),
742                Serializer.createSerializerForType(member.GetType()))
743
744        self.log.debug("Creating MockProperty for %s", member.GetName())
745        return MockProperty(
746            member.GetOffsetInBytes(),
747            member.GetType().GetByteSize(),
748            Serializer.createSerializerForType(member.GetType()))
749
750    def _createMockClass(self, sbtype: lldb.SBType, offs: int = 0):
751        """ Converts SBType into a new mock base class. """
752
753        self.log.debug("Creating mock class for %s", sbtype.GetName())
754
755        # Try cache first
756        clsname = f'MockBase_{sbtype.GetName()}_{offs}'
757        cls = self.cache.get(clsname, None)
758        if cls is not None:
759            self.log.debug("Found in cache %s", clsname)
760            return cls
761
762        attrs = {}
763
764        # Add all direct members as MockProperty.
765        for t in sbtype.get_members_array():
766
767            # Create MockProperty for members with simple data types.
768            if t.GetType().GetNumberOfFields() == 0:
769                attrs[t.GetName()] = self._make_property(t)
770
771
772            # Skip creation of proxied properties for sub-mocks. There is no need
773            # to build a chain. Top-level mock will bind directly to desired
774            # sub-mock.
775            if offs != 0:
776                continue
777
778            # Create ProxyProperty for anonymous sub-members so we can later
779            # establish the forwarding to the mock that overlays this area.
780            if t.GetType().IsAnonymousType():
781
782                def visit_anon(sbtype):
783                    for at in sbtype.get_members_array():
784                        if at.GetType().IsAnonymousType():
785                            yield from visit_anon(at.GetType())
786                        else:
787                            yield at
788
789                for at in visit_anon(t.GetType()):
790                    self.log.debug("Adding anon proxy for %s", at.GetName())
791                    attrs[at.GetName()] = ProxyProperty()
792
793            # Compound types are not handled at mock class level. They are
794            # going to be added to the class instance.
795
796        # Insert class into cache. anon types are ignored as there may be
797        # name conflicts.
798        self.log.debug("Created new mock class %s", clsname)
799
800        clsname = f'MockBase_{sbtype.GetName()}_{offs}'
801        cls = type(clsname, (MemoryMock,), attrs)
802
803        # No caching of anon types
804        if sbtype.IsAnonymousType():
805            self.log.debug("Anon type not caching")
806            return cls
807
808        if clsname in self.cache:
809            raise AttributeError("Already in cache !!")
810
811        self.cache[clsname] = cls
812        return cls
813
814    def create_mock(self, sbtype: lldb.SBType, offs: int = 0, parent_buf = None):
815        """ Creates mock instance of mock class created from SBType. """
816
817        if sbtype.GetNumberOfFields() == 0:
818            raise AttributeError("Not a compound type")
819
820        # Top level mocks are buffer providers
821        if not parent_buf:
822            buf = bytearray(sbtype.GetByteSize())
823            self.log.debug("%s size is %d", sbtype.GetName(), sbtype.GetByteSize())
824        else:
825            buf = memoryview(parent_buf)[offs: offs + sbtype.GetByteSize()]
826
827        # Construct new mock base class.
828        mock_class = self._createMockClass(sbtype, offs)
829        mock_inst = mock_class(sbtype, buf)
830
831        # Pre-populate all properties with default values.
832        for prop, value in mock_class.__dict__.items():
833            if isinstance(value, MockProperty):
834                self.log.debug(f'Setting default for {prop}')
835                setattr(mock_inst, prop, value.serializer.default())
836
837        # Instantiate regular sub-mocks
838        for t in sbtype.get_members_array():
839            if t.GetType().GetNumberOfFields() == 0:
840                continue
841
842            # Create sub-mocks for regular structures/unions
843            submock = MockFactory.createFromType(t.GetType(), t.GetOffsetInBytes(), buf)
844            if not t.GetType().IsAnonymousType():
845                self.log.debug("Creating anon sub-mock %s at %d",
846                               t.GetName(), t.GetOffsetInBytes())
847                setattr(mock_inst, t.GetName(), submock)
848                continue
849
850            # Create and connect instances
851            mock_inst._anon_mocks.append(submock)
852
853        # Resolve proxies
854        resolve = {
855            k:p for (k, p)
856            in mock_class.__dict__.items()
857            if isinstance(p, ProxyProperty)
858        }
859
860        # Assumption is there are no conflicts (compiler would refuse to build the code)
861
862        def visit_anon_mocks(mock):
863            for m in mock._anon_mocks:
864                yield from m._anon_mocks
865                yield m
866
867        for am in visit_anon_mocks(mock_inst):
868            for t in am._sbtype.get_members_array():
869                if t.GetName() in resolve:
870                    resolve[t.GetName()].setProxy(mock_inst, am)
871                    self.log.debug("Resolving %s", t.GetName())
872                    del resolve[t.GetName()]
873
874        # Fail if there are unresolved members.
875        if resolve:
876            raise TypeError('Unresolved proxies in a mock class')
877
878        # Construct mock instance
879        mock_inst.freeze()
880        return mock_inst
881