xref: /xnu-12377.41.6/tools/lldbmacros/tests/lldb_test_process.py (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
1*bbb1b6f9SApple OSS Distributions##
2*bbb1b6f9SApple OSS Distributions# Copyright (c) 2023 Apple Inc. All rights reserved.
3*bbb1b6f9SApple OSS Distributions#
4*bbb1b6f9SApple OSS Distributions# @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5*bbb1b6f9SApple OSS Distributions#
6*bbb1b6f9SApple OSS Distributions# This file contains Original Code and/or Modifications of Original Code
7*bbb1b6f9SApple OSS Distributions# as defined in and that are subject to the Apple Public Source License
8*bbb1b6f9SApple OSS Distributions# Version 2.0 (the 'License'). You may not use this file except in
9*bbb1b6f9SApple OSS Distributions# compliance with the License. The rights granted to you under the License
10*bbb1b6f9SApple OSS Distributions# may not be used to create, or enable the creation or redistribution of,
11*bbb1b6f9SApple OSS Distributions# unlawful or unlicensed copies of an Apple operating system, or to
12*bbb1b6f9SApple OSS Distributions# circumvent, violate, or enable the circumvention or violation of, any
13*bbb1b6f9SApple OSS Distributions# terms of an Apple operating system software license agreement.
14*bbb1b6f9SApple OSS Distributions#
15*bbb1b6f9SApple OSS Distributions# Please obtain a copy of the License at
16*bbb1b6f9SApple OSS Distributions# http://www.opensource.apple.com/apsl/ and read it before using this file.
17*bbb1b6f9SApple OSS Distributions#
18*bbb1b6f9SApple OSS Distributions# The Original Code and all software distributed under the License are
19*bbb1b6f9SApple OSS Distributions# distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20*bbb1b6f9SApple OSS Distributions# EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21*bbb1b6f9SApple OSS Distributions# INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22*bbb1b6f9SApple OSS Distributions# FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23*bbb1b6f9SApple OSS Distributions# Please see the License for the specific language governing rights and
24*bbb1b6f9SApple OSS Distributions# limitations under the License.
25*bbb1b6f9SApple OSS Distributions#
26*bbb1b6f9SApple OSS Distributions# @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27*bbb1b6f9SApple OSS Distributions##
28*bbb1b6f9SApple OSS Distributions
29*bbb1b6f9SApple OSS Distributions""" LLDB Scripted Process designed for unit-testing mock support. """
30*bbb1b6f9SApple OSS Distributions
31*bbb1b6f9SApple OSS Distributionsimport unittest
32*bbb1b6f9SApple OSS Distributionsimport sys
33*bbb1b6f9SApple OSS Distributionsimport logging
34*bbb1b6f9SApple OSS Distributionsfrom collections import namedtuple
35*bbb1b6f9SApple OSS Distributionsfrom pathlib import Path
36*bbb1b6f9SApple OSS Distributions
37*bbb1b6f9SApple OSS Distributionsimport lldb
38*bbb1b6f9SApple OSS Distributionsimport lldbmock.memorymock
39*bbb1b6f9SApple OSS Distributionsfrom lldb.plugins.scripted_process import ScriptedProcess, ScriptedThread
40*bbb1b6f9SApple OSS Distributionsfrom lldbtest.unittest import LLDBTextTestRunner, LLDBJSONTestRunner
41*bbb1b6f9SApple OSS Distributionsfrom lldbtest.coverage import CoverageContext
42*bbb1b6f9SApple OSS Distributions
43*bbb1b6f9SApple OSS Distributions# location of this script
44*bbb1b6f9SApple OSS DistributionsSCRIPT_PATH = Path(__file__).parent
45*bbb1b6f9SApple OSS Distributions
46*bbb1b6f9SApple OSS Distributions# Configure logging.
47*bbb1b6f9SApple OSS Distributions# This script is loaded first so we can share root logger with other files.
48*bbb1b6f9SApple OSS Distributions
49*bbb1b6f9SApple OSS Distributionslogging.root.setLevel(logging.INFO)
50*bbb1b6f9SApple OSS Distributionslogging.basicConfig(level=logging.INFO)
51*bbb1b6f9SApple OSS Distributionslldb.test_logger = logging.getLogger("UnitTest")
52*bbb1b6f9SApple OSS Distributionslldb.test_logger.getChild("ScriptedProcess").info("Log initialized.")
53*bbb1b6f9SApple OSS Distributions
54*bbb1b6f9SApple OSS Distributions
55*bbb1b6f9SApple OSS Distributionsclass TestThread(ScriptedThread):
56*bbb1b6f9SApple OSS Distributions    """ Scripted thread that represents custom thread state. """
57*bbb1b6f9SApple OSS Distributions
58*bbb1b6f9SApple OSS Distributions
59*bbb1b6f9SApple OSS Distributionsclass TestProcess(ScriptedProcess):
60*bbb1b6f9SApple OSS Distributions    """ Scripted process that represents target's memory. """
61*bbb1b6f9SApple OSS Distributions
62*bbb1b6f9SApple OSS Distributions    LOG = lldb.test_logger.getChild("ScriptedProcess")
63*bbb1b6f9SApple OSS Distributions
64*bbb1b6f9SApple OSS Distributions    MockElem = namedtuple('MockElem', ['addr', 'mock'])
65*bbb1b6f9SApple OSS Distributions
66*bbb1b6f9SApple OSS Distributions    def __init__(self, ctx: lldb.SBExecutionContext, args: lldb.SBStructuredData):
67*bbb1b6f9SApple OSS Distributions        super().__init__(ctx, args)
68*bbb1b6f9SApple OSS Distributions
69*bbb1b6f9SApple OSS Distributions        self.verbose = args.GetValueForKey("verbose").GetBooleanValue()
70*bbb1b6f9SApple OSS Distributions        self.debug = args.GetValueForKey("debug").GetBooleanValue()
71*bbb1b6f9SApple OSS Distributions        self.json = args.GetValueForKey("json").GetStringValue(256)
72*bbb1b6f9SApple OSS Distributions        print(self.json)
73*bbb1b6f9SApple OSS Distributions        self._mocks = []
74*bbb1b6f9SApple OSS Distributions
75*bbb1b6f9SApple OSS Distributions    #
76*bbb1b6f9SApple OSS Distributions    # Testing framework API
77*bbb1b6f9SApple OSS Distributions    #
78*bbb1b6f9SApple OSS Distributions
79*bbb1b6f9SApple OSS Distributions    def add_mock(self, addr: int, mock: lldbmock.memorymock.BaseMock):
80*bbb1b6f9SApple OSS Distributions        # Do not allow overlaping mocks to keep logic simple.
81*bbb1b6f9SApple OSS Distributions        if any(me for me in self._mocks
82*bbb1b6f9SApple OSS Distributions               if me.addr <= addr < (me.addr + me.mock.size)):
83*bbb1b6f9SApple OSS Distributions            raise ValueError("Overlaping mock with")
84*bbb1b6f9SApple OSS Distributions
85*bbb1b6f9SApple OSS Distributions        self._mocks.append(TestProcess.MockElem(addr, mock))
86*bbb1b6f9SApple OSS Distributions
87*bbb1b6f9SApple OSS Distributions    def remove_mock(self, mock: lldbmock.memorymock.BaseMock):
88*bbb1b6f9SApple OSS Distributions        raise NotImplementedError("Mock removal not implemented yet")
89*bbb1b6f9SApple OSS Distributions
90*bbb1b6f9SApple OSS Distributions    def reset_mocks(self):
91*bbb1b6f9SApple OSS Distributions        """ Remove all mocks. """
92*bbb1b6f9SApple OSS Distributions        self._mocks = []
93*bbb1b6f9SApple OSS Distributions
94*bbb1b6f9SApple OSS Distributions    #
95*bbb1b6f9SApple OSS Distributions    # LLDB Scripted Process Implementation
96*bbb1b6f9SApple OSS Distributions    #
97*bbb1b6f9SApple OSS Distributions
98*bbb1b6f9SApple OSS Distributions    def get_memory_region_containing_address(
99*bbb1b6f9SApple OSS Distributions        self,
100*bbb1b6f9SApple OSS Distributions        addr: int
101*bbb1b6f9SApple OSS Distributions    ) -> lldb.SBMemoryRegionInfo:
102*bbb1b6f9SApple OSS Distributions        # A generic answer should work in our case
103*bbb1b6f9SApple OSS Distributions        return lldb.SBMemoryRegionInfo()
104*bbb1b6f9SApple OSS Distributions
105*bbb1b6f9SApple OSS Distributions    def read_memory_at_address(
106*bbb1b6f9SApple OSS Distributions        self,
107*bbb1b6f9SApple OSS Distributions        addr: int,
108*bbb1b6f9SApple OSS Distributions        size: int,
109*bbb1b6f9SApple OSS Distributions        error: lldb.SBError = lldb.SBError()
110*bbb1b6f9SApple OSS Distributions    ) -> lldb.SBData:
111*bbb1b6f9SApple OSS Distributions        """ Performs I/O read on top of set of mock structures.
112*bbb1b6f9SApple OSS Distributions            Undefined regions are set to 0.
113*bbb1b6f9SApple OSS Distributions        """
114*bbb1b6f9SApple OSS Distributions        data = lldb.SBData()
115*bbb1b6f9SApple OSS Distributions        rawdata = bytearray(size)
116*bbb1b6f9SApple OSS Distributions
117*bbb1b6f9SApple OSS Distributions        # Avoid delegating reads back to SBTarget. That leads to infinite
118*bbb1b6f9SApple OSS Distributions        # recursion as SBTarget calls to read from SBProcess instance.
119*bbb1b6f9SApple OSS Distributions
120*bbb1b6f9SApple OSS Distributions        # Overlay mocks on top of the I/O.
121*bbb1b6f9SApple OSS Distributions        for maddr, mock in (
122*bbb1b6f9SApple OSS Distributions            (me.addr, me.mock) for me
123*bbb1b6f9SApple OSS Distributions            in self._mocks):
124*bbb1b6f9SApple OSS Distributions
125*bbb1b6f9SApple OSS Distributions            # check for overlap
126*bbb1b6f9SApple OSS Distributions            start_addr = max(addr, maddr)
127*bbb1b6f9SApple OSS Distributions            end_addr = min(addr + size, maddr + mock.size)
128*bbb1b6f9SApple OSS Distributions
129*bbb1b6f9SApple OSS Distributions            if end_addr < start_addr:
130*bbb1b6f9SApple OSS Distributions                # no intersection of I/O and mock entry
131*bbb1b6f9SApple OSS Distributions                continue
132*bbb1b6f9SApple OSS Distributions
133*bbb1b6f9SApple OSS Distributions            offs = start_addr - maddr # In the mock space
134*bbb1b6f9SApple OSS Distributions            boffs = start_addr - addr # In mbuffer space
135*bbb1b6f9SApple OSS Distributions            sz = end_addr - start_addr # size to read
136*bbb1b6f9SApple OSS Distributions
137*bbb1b6f9SApple OSS Distributions            self.LOG.debug("overlap: %x +%d", offs, sz)
138*bbb1b6f9SApple OSS Distributions            self.LOG.debug("raw read %x +%d", addr, size)
139*bbb1b6f9SApple OSS Distributions            self.LOG.debug("final read %x +%d", start_addr - addr, sz)
140*bbb1b6f9SApple OSS Distributions            #self.LOG.debug("data: %s", mock.getData()[offs: offs + sz])
141*bbb1b6f9SApple OSS Distributions
142*bbb1b6f9SApple OSS Distributions            # Merge mock data into I/O buffer.
143*bbb1b6f9SApple OSS Distributions            rawdata[boffs: boffs + sz] = mock.getData()[offs:offs+sz]
144*bbb1b6f9SApple OSS Distributions
145*bbb1b6f9SApple OSS Distributions        data.SetDataWithOwnership(
146*bbb1b6f9SApple OSS Distributions            error,
147*bbb1b6f9SApple OSS Distributions            rawdata,
148*bbb1b6f9SApple OSS Distributions            lldb.eByteOrderLittle,
149*bbb1b6f9SApple OSS Distributions            8
150*bbb1b6f9SApple OSS Distributions        )
151*bbb1b6f9SApple OSS Distributions
152*bbb1b6f9SApple OSS Distributions        return data
153*bbb1b6f9SApple OSS Distributions
154*bbb1b6f9SApple OSS Distributions    def get_loaded_images(self) -> list:
155*bbb1b6f9SApple OSS Distributions        return self.loaded_images
156*bbb1b6f9SApple OSS Distributions
157*bbb1b6f9SApple OSS Distributions    def get_process_id(self) -> int:
158*bbb1b6f9SApple OSS Distributions        return 0
159*bbb1b6f9SApple OSS Distributions
160*bbb1b6f9SApple OSS Distributions    def is_alive(self) -> bool:
161*bbb1b6f9SApple OSS Distributions        return True
162*bbb1b6f9SApple OSS Distributions
163*bbb1b6f9SApple OSS Distributions    def get_scripted_thread_plugin(self) -> str:
164*bbb1b6f9SApple OSS Distributions        return __class__.__module__ + '.' + TestThread.__name__
165*bbb1b6f9SApple OSS Distributions
166*bbb1b6f9SApple OSS Distributions
167*bbb1b6f9SApple OSS Distributionsdef run_unit_tests(debugger, _command, _exe_ctx, _result, _internal_dict):
168*bbb1b6f9SApple OSS Distributions    """ Runs standart Python unit tests inside LLDB. """
169*bbb1b6f9SApple OSS Distributions
170*bbb1b6f9SApple OSS Distributions    # Obtain current plugin instance
171*bbb1b6f9SApple OSS Distributions    sp = debugger.GetSelectedTarget().GetProcess().GetScriptedImplementation()
172*bbb1b6f9SApple OSS Distributions
173*bbb1b6f9SApple OSS Distributions    # Enable debugging
174*bbb1b6f9SApple OSS Distributions    if sp.debug:
175*bbb1b6f9SApple OSS Distributions        logging.root.setLevel(logging.DEBUG)
176*bbb1b6f9SApple OSS Distributions        logging.basicConfig(level=logging.DEBUG)
177*bbb1b6f9SApple OSS Distributions
178*bbb1b6f9SApple OSS Distributions    log = logging.getLogger("ScriptedProcess")
179*bbb1b6f9SApple OSS Distributions    log.info("Running tests")
180*bbb1b6f9SApple OSS Distributions    log.info("Using path: %s", SCRIPT_PATH / "lldb_tests")
181*bbb1b6f9SApple OSS Distributions    tests = unittest.TestLoader().discover(SCRIPT_PATH / "lldb_tests")
182*bbb1b6f9SApple OSS Distributions
183*bbb1b6f9SApple OSS Distributions    # Select runner class
184*bbb1b6f9SApple OSS Distributions    RunnerClass = LLDBJSONTestRunner if sp.json else LLDBTextTestRunner
185*bbb1b6f9SApple OSS Distributions
186*bbb1b6f9SApple OSS Distributions    # Open output file if requested
187*bbb1b6f9SApple OSS Distributions    if sp.json:
188*bbb1b6f9SApple OSS Distributions        with open(f"{sp.json}-lldb.json", 'wt') as outfile:
189*bbb1b6f9SApple OSS Distributions            runner = RunnerClass(verbosity=2 if sp.verbose else 1, debug=sp.debug,
190*bbb1b6f9SApple OSS Distributions                                 stream=outfile)
191*bbb1b6f9SApple OSS Distributions            runner.run(tests)
192*bbb1b6f9SApple OSS Distributions    else:
193*bbb1b6f9SApple OSS Distributions        runner = RunnerClass(stream=sys.stderr, verbosity=2 if sp.verbose else 1,
194*bbb1b6f9SApple OSS Distributions                            debug=sp.debug)
195*bbb1b6f9SApple OSS Distributions        runner.run(tests)
196*bbb1b6f9SApple OSS Distributions
197*bbb1b6f9SApple OSS Distributionsdef __lldb_init_module(_debugger, _internal_dict):
198*bbb1b6f9SApple OSS Distributions    """ LLDB entry point """
199*bbb1b6f9SApple OSS Distributions
200*bbb1b6f9SApple OSS Distributions    # XNU has really bad import structure and it is easy to create circular
201*bbb1b6f9SApple OSS Distributions    # dependencies. Forcibly import XNU before tests are ran so the final
202*bbb1b6f9SApple OSS Distributions    # result is close to what imports from a dSYM would end up with.
203*bbb1b6f9SApple OSS Distributions    with CoverageContext():
204*bbb1b6f9SApple OSS Distributions        lldb.debugger.HandleCommand(
205*bbb1b6f9SApple OSS Distributions            f"command script import {SCRIPT_PATH / '../xnu.py'}")
206*bbb1b6f9SApple OSS Distributions
207*bbb1b6f9SApple OSS Distributions    logging.getLogger("ScriptedProcess").info("Running LLDB module init.")
208*bbb1b6f9SApple OSS Distributions    lldb.debugger.HandleCommand(f"command script add "
209*bbb1b6f9SApple OSS Distributions                                f"-f {__name__}.{run_unit_tests.__name__}"
210*bbb1b6f9SApple OSS Distributions                                f" run-unit-tests")
211