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""" Test case base class for tests running inside LLDB """ 30 31import unittest.result 32import sys 33import re 34from unittest import TestCase 35 36import lldb 37from lldbmock.memorymock import MockFactory, BaseMock 38 39 40class LLDBTestCase(TestCase): 41 """ LLDB unit test running inside LLDB instance. 42 43 This class ensures that a test will get an instance of the debugger attached 44 to a scripted process mock. Test can interact with LLDB directly through 45 SBAPIs available. 46 """ 47 48 COMPONENT = "xnu | debugging" 49 50 def run(self, result: unittest.TestResult) -> unittest.TestResult: 51 """ Run a test and slufh LLDB I/O caches. """ 52 53 self.invalidate_cache() 54 return super().run(result) 55 56 def __init__(self, methodName): 57 """ Initializes test case and logging. """ 58 59 super().__init__(methodName) 60 self.log = lldb.test_logger.getChild(self.__class__.__name__) 61 62 @property 63 def debugger(self): 64 """ Returns SBDebugger instance used during test execution. """ 65 66 return lldb.debugger 67 68 @property 69 def process(self): 70 """ Returns SBPRocess instance used during test execution. """ 71 72 return self.target.GetProcess() 73 74 @property 75 def spplugin(self): 76 """ Returns Scripted Process plugin used during execution. """ 77 78 return self.process.GetScriptedImplementation() 79 80 @property 81 def target(self): 82 """ Return target used during test execution. """ 83 84 return lldb.debugger.GetSelectedTarget() 85 86 def create_mock(self, sbtype: str, addr: int = None): 87 """ Returns instance of mock object matching sbtype. """ 88 89 self.log.debug("Creating mock from %s", sbtype) 90 mock = MockFactory.createFromType(sbtype) 91 92 if addr is not None: 93 self.add_mock(addr, mock) 94 95 return mock 96 97 def add_mock(self, addr: int, mock: BaseMock): 98 """ Insert mock instance to the target. """ 99 100 self.spplugin.add_mock(addr, mock) 101 102 def run_command(self, command: str) -> lldb.SBCommandReturnObject: 103 """ Runs LLDB command and returns result. """ 104 105 res = lldb.SBCommandReturnObject() 106 self.debugger.GetCommandInterpreter().HandleCommand(command, res) 107 return res 108 109 def invalidate_cache(self): 110 """ Invalidates cached I/O by simulating proces start/stop. """ 111 112 self.process.ForceScriptedState(lldb.eStateRunning) 113 self.process.ForceScriptedState(lldb.eStateStopped) 114 115 def reset_mocks(self): 116 """ Remove all registered mocks. """ 117 118 self.spplugin.reset_mocks() 119 120 # Helpers for skipIf() has to be static methods because they are called from 121 # decorator before a test class is instantiated. 122 123 @staticmethod 124 def variant(): 125 """ Return variant of kernel being loaded. """ 126 127 # Version string is a static variable in the kernel image. 128 # Use SBTarget to read it's memory as that's not mocked away 129 # by scripted process. 130 target = lldb.debugger.GetSelectedTarget() 131 version = target.FindGlobalVariables('version', 1).GetValueAtIndex(0) 132 err = lldb.SBError() 133 addr = target.ResolveLoadAddress(version.AddressOf().GetLoadAddress()) 134 135 # Filter first world from a triplet VARIANT_PLATFORM_SOC 136 verstr = target.ReadMemory(addr, version.GetByteSize(), err) 137 kerntgt = re.search("^.*/(.*)$", verstr.decode())[1] 138 return kerntgt.split('_')[0] 139 140 @staticmethod 141 def arch(): 142 """ Return current architecture. """ 143 144 return lldb.debugger.GetSelectedTarget().triple.split('-', 1)[0] 145 146 @staticmethod 147 def kernel(): 148 """ Return name of XNU module in current target. """ 149 150 target = lldb.debugger.GetSelectedTarget() 151 kernel = ( 152 m.file.basename 153 for m in target.module_iter() 154 if m.file.basename.startswith(('kernel', 'mach')) 155 ) 156 return next(kernel, None) 157 158 159 def getDescription(self): 160 """ Returns unindented doc string of currently tested method. """ 161 162 # Convert tabs to spaces (following the normal Python rules) 163 # and split into a list of lines: 164 lines = self._testMethodDoc.expandtabs().splitlines() 165 166 # Determine minimum indentation (first line doesn't count): 167 indent = sys.maxsize 168 for line in lines[1:]: 169 stripped = line.lstrip() 170 if stripped: 171 indent = min(indent, len(line) - len(stripped)) 172 173 # Remove indentation (first line is special): 174 trimmed = [lines[0].strip()] 175 if indent < sys.maxsize: 176 for line in lines[1:]: 177 trimmed.append(line[indent:].rstrip()) 178 179 # Strip off trailing and leading blank lines: 180 while trimmed and not trimmed[-1]: 181 trimmed.pop() 182 while trimmed and not trimmed[0]: 183 trimmed.pop(0) 184 185 # Return a single string: 186 return '\n'.join(trimmed) 187 188 @classmethod 189 def setUpClass(cls) -> None: 190 """ All mocks are reset per class instance fixture. """ 191 192 lldb.debugger.GetSelectedTarget().GetProcess() \ 193 .GetScriptedImplementation().reset_mocks() 194 return super().setUpClass() 195