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# pylint: disable=invalid-name
30
31""" Tests that ScriptedProcess mock behaves as expected. """
32
33import unittest.mock
34import io
35
36import lldb
37from lldbtest.testcase import LLDBTestCase
38from lldbmock.memorymock import RawMock
39from lldbmock.utils import lookup_type
40
41
42class ScriptedProcessTest(LLDBTestCase):
43    """ Scripted process unit test. """
44
45    def test_RawMock(self):
46        """ Install simple raw memory mock into a target. """
47
48        RAWMOCK_ADDR = 0xffffffff00000000
49
50        mock = RawMock(100)
51        mock.setData(b"lldb-process-mock\x00")
52        self.add_mock(RAWMOCK_ADDR, mock)
53
54        # Test is using LLDB command intentionaly.
55        res = self.run_command(f'x/s {RAWMOCK_ADDR:#x}')
56
57        self.assertTrue(res.Succeeded())
58        self.assertEqual(
59            res.GetOutput(),
60            f'{RAWMOCK_ADDR:#x}: "lldb-process-mock"\n'
61        )
62
63    def test_RawMockIO(self):
64        """ Populate simple raw memory mock from provided IO. """
65
66        RAWMOCK_ADDR = 0xffffffff50000000
67
68        mock = RawMock.fromBufferedIO(io.BytesIO(b"lldb-io-mock\x00"))
69        self.add_mock(RAWMOCK_ADDR, mock)
70
71        # Test is using LLDB command intentionaly.
72        res = self.run_command(f'x/s {RAWMOCK_ADDR:#x}')
73
74        self.assertTrue(res.Succeeded())
75        self.assertEqual(
76            res.GetOutput(),
77            f'{RAWMOCK_ADDR:#x}: "lldb-io-mock"\n'
78        )
79
80    def test_DuplicateMock(self):
81        """ Install same simple mock to two VA locations. """
82
83        mock = RawMock(100)
84        mock.setData(b"shared-mock\x00")
85        self.add_mock(0xffffffff10000000, mock)
86        self.add_mock(0xffffffff20000000, mock)
87
88        # Test both locations
89        for addr in ('0xffffffff10000000', '0xffffffff20000000'):
90            res = self.run_command(f'x/s {addr}')
91
92            self.assertTrue(res.Succeeded())
93            self.assertEqual(
94                res.GetOutput(),
95                f'{addr}: "shared-mock"\n'
96            )
97
98    def test_MockConflict(self):
99        """ Check that we can't add overlapping mocks. """
100
101        mock = RawMock(16)
102        self.add_mock(0x12345, mock)
103        with self.assertRaises(ValueError):
104            mock = RawMock(16)
105            self.add_mock(0x12346, mock)
106
107    def test_SimpleMock(self):
108        """ Mock instance of a simple type. """
109
110        UINT_ADDR = 0xffffffff11223344
111
112        self.create_mock('uint32_t', UINT_ADDR).setData(0x1234)
113
114        res = self.run_command(f'p/x *((uint32_t *){UINT_ADDR:#x})')
115
116        self.assertTrue(res.Succeeded())
117        self.assertEqual(res.GetOutput(), "(uint32_t) 0x00001234\n")
118
119    @unittest.skipIf(LLDBTestCase.kernel().startswith('mach.release'),
120                     "Not available in RELEASE embedded")
121    def test_CompoundMock(self):
122        """ Mock instance of simple structure. """
123
124        DOFHELPER_ADDR = 0xffffffff11220000
125
126        # Construct simple data structure mock.
127        self.create_mock('struct dof_helper', DOFHELPER_ADDR).fromDict({
128            'dofhp_mod': b'mock-mod',
129            'dofhp_addr': 0x1234,
130            'dofhp_dof': 0x5678
131        })
132
133        # Construct SBValue on top of the mock.
134        addr = self.target.ResolveLoadAddress(DOFHELPER_ADDR)
135        sbv = self.target.CreateValueFromAddress(
136            'test', addr, lookup_type('dof_helper_t'))
137
138        self.assertTrue(sbv.IsValid() and sbv.error.success)
139
140        # Check that LLDB SBAPI returns correct values from mock.
141        err = lldb.SBError()
142        self.assertEqual(
143            sbv.GetChildMemberWithName('dofhp_mod').GetData()
144               .GetString(err, 0),
145            "mock-mod"
146        )
147        self.assertEqual(
148            sbv.GetChildMemberWithName('dofhp_addr').GetValueAsUnsigned(),
149            0x1234
150        )
151        self.assertEqual(
152            sbv.GetChildMemberWithName('dofhp_dof').GetValueAsUnsigned(),
153            0x5678
154        )
155
156    @unittest.skipIf(LLDBTestCase.kernel().startswith('mach.release'),
157                     "Not available in RELEASE embedded")
158    def test_CompoundMock_UpdateProperty(self):
159        """ Test that mock can deserilize properties from update. """
160
161        mock = self.create_mock('struct dof_helper', 0xffffffff55555555)
162        mock.setData(
163            b'hello-mock' + b'\x00'*54 +
164            0xfeedface.to_bytes(length=8, byteorder='little') +
165            0xdeadbeef.to_bytes(length=8, byteorder='little'))
166
167        # Test that mock has de-serialized correctly whole blob above.
168        self.assertEqual(mock.dofhp_mod[:10], b'hello-mock')
169        self.assertEqual(mock.dofhp_addr, 0xfeedface)
170        self.assertEqual(mock.dofhp_dof, 0xdeadbeef)
171
172    def test_UnionMock(self):
173        """ Test that simple union/bitfield propagates property updates. """
174
175        mock = self.create_mock('kds_ptr', 0xffffffff30000000)
176
177        mock.buffer_index = 0b111111111111111111111  # 21-bits
178        self.assertEqual(mock.raw, 0x001fffff)
179
180        mock.buffer_index = 0
181        mock.offset = 0b11111111111  # 11-bits
182        self.assertEqual(mock.raw, 0xffe00000)
183
184        mock.raw = 0xffdffffe
185        self.assertEqual(mock.buffer_index, 0x001ffffe)
186        self.assertEqual(mock.offset, 0x7fe)
187
188    def test_MockArray(self):
189        """ Test simple mock of char array. """
190
191        STR_ADDR = 0xffffffff33004400
192        PTR_ADDR = 0xffffffff44000000
193
194        # Construct an array in memory.
195        arrtype = lookup_type('char').GetArrayType(256)
196        marray = self.create_mock(arrtype, STR_ADDR)
197        marray.setData(b'Hello World\x00')
198
199        # Create a pointer to the array
200        ptrtype = lookup_type('char').GetPointerType()
201        mstr = self.create_mock(ptrtype, PTR_ADDR)
202        mstr.setData(STR_ADDR)
203
204        # Let LLDB print it.
205        addr = self.target.ResolveLoadAddress(PTR_ADDR)
206        sbv = self.target.CreateValueFromAddress('str', addr, ptrtype)
207        self.assertTrue(sbv.IsValid() and sbv.error.success)
208
209        err = lldb.SBError()
210        self.assertEqual(sbv.GetPointeeData(0, 256).GetString(err, 0),
211                         'Hello World')
212
213    def test_MockTypedArray(self):
214        """ Test array of compound types. """
215
216        ARRAY_ADDR = 0xffffffff44003300
217
218        arrtype = lookup_type('proc').GetArrayType(10)
219        self.create_mock(arrtype, ARRAY_ADDR).fromDict({
220            '0': {
221                'p_comm': b'bar-foo\x00'
222            },
223            '1': {
224                'p_comm': b'foo-bar\x00'
225            }
226        })
227
228        res = self.run_command(f'p/x ((proc_t){ARRAY_ADDR:#x})[1].p_comm')
229        self.assertTrue(res.Succeeded())
230
231        # Check that elements don't overlap somehow
232        # (use SBValue to exercise LLDB's internals)
233        addr = self.target.ResolveLoadAddress(ARRAY_ADDR)
234        sbv = self.target.CreateValueFromAddress('proc_arr', addr, arrtype)
235        self.assertTrue(sbv.IsValid() and sbv.error.success)
236
237        err = lldb.SBError()
238        self.assertEqual(
239            sbv.GetChildAtIndex(0).GetChildMemberWithName('p_comm')
240               .GetData().GetString(err, 0),
241            'bar-foo'
242        )
243        self.assertEqual(
244            sbv.GetChildAtIndex(1).GetChildMemberWithName('p_comm')
245               .GetData().GetString(err, 0),
246            'foo-bar'
247        )
248
249    def test_NoNewAttributes(self):
250        """ Test that mock instances are properly frozen after creation. """
251
252        mock = self.create_mock(lookup_type('uint32_t'))
253
254        with self.assertRaises(TypeError):
255            mock.foo = 5
256
257    @unittest.skipIf(LLDBTestCase.kernel().startswith('mach.release'),
258                     "Not available in RELEASE embedded")
259    def test_NestedStruct(self):
260        """ Test that nested mocks properly serialize. """
261
262        PROVNAME_ADDR = 0xffffffff70707070
263        DTHELPER_ADDR = 0xffffffff80808080
264
265        # Setup mock with fake values.
266        arrtype = lookup_type('char').GetArrayType(256)
267        marray = self.create_mock(arrtype, PROVNAME_ADDR)
268        marray.setData(b'test-prov\x00')
269
270        sbtype = lookup_type('dtrace_helper_provdesc_t')
271        mock = self.create_mock(sbtype, DTHELPER_ADDR)
272
273        mock.dthpv_provname = PROVNAME_ADDR
274        mock.dthpv_pattr.dtpa_mod.dtat_name = 0x5
275
276        # Serializer should prevent overflowing a member's size.
277        with self.assertRaises(OverflowError):
278            mock.dthpv_pattr.dtpa_args.dtat_class = 0x7777
279
280        mock.dthpv_pattr.dtpa_args.dtat_class = 0x77
281
282        # Obtain SBValue and check modified members
283        addr = self.target.ResolveLoadAddress(DTHELPER_ADDR)
284        sbv = self.target.CreateValueFromAddress('test', addr, sbtype)
285        self.assertTrue(sbv.IsValid() and sbv.error.success)
286
287        err = lldb.SBError()
288        self.assertEqual(
289            sbv.GetChildMemberWithName('dthpv_provname')
290               .GetPointeeData(0, 256).GetString(err, 0),
291            'test-prov'
292        )
293        self.assertEqual(
294            sbv.GetValueForExpressionPath('.dthpv_pattr.dtpa_mod.dtat_name')
295               .GetValueAsUnsigned(),
296            0x5
297        )
298        self.assertEqual(
299            sbv.GetValueForExpressionPath('.dthpv_pattr.dtpa_args.dtat_class')
300               .GetValueAsUnsigned(),
301            0x77
302        )
303
304    @unittest.mock.patch('xnu.kern.globals.proc_struct_size', 2048)
305    def test_ProxyMock(self):
306        """ Test anonymous members forwarding. """
307
308        PROC_ADDR = 0xffffffff90909090
309        PROC_RO_ADDR = 0xffffff0040404040
310
311        mock = self.create_mock('proc', PROC_ADDR)
312
313        mock.p_list.le_next = 0x12345678
314        mock.p_smr_node.smrn_next = 0x12345678
315        mock.p_pid = 12345
316        mock.p_argc = 0x5
317        mock.p_textvp = 0xfeedface
318        mock.p_lflag = 0x00000002
319
320        mock.p_comm = b'foobar'  # Use forwarding property
321
322        task = self.create_mock('task', PROC_ADDR + 2048)
323
324        task.effective_policy.tep_sup_active = 0
325        task.effective_policy.tep_darwinbg = 0
326        task.effective_policy.tep_lowpri_cpu = 1
327        task.t_flags = 0x00800000
328
329        self.create_mock('proc_ro', PROC_RO_ADDR)
330
331        mock.p_proc_ro = PROC_RO_ADDR
332        task.bsd_info_ro = PROC_RO_ADDR
333
334        # Populate and test mock.
335        res = self.run_command(f'p/x ((proc_t){PROC_ADDR:#x})->p_comm')
336        self.assertEqual(res.GetOutput(), '(command_t) "foobar"\n')
337        self.assertTrue(res.Succeeded())
338
339        # Modify mock and test again.
340        mock.p_forkcopy.p_comm = b'barfoo'  # Sub-mock prop wins
341        self.invalidate_cache()
342
343        res = self.run_command(f'p/x ((proc_t){PROC_ADDR:#x})->p_comm')
344        self.assertEqual(res.GetOutput(), '(command_t) "barfoo"\n')
345        self.assertTrue(res.Succeeded())
346