1#
2# Copyright (C) 2022 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import ctypes
17from typing import List
18from .print_gfx_logs import process_minidump, Command, Stream, Header
19import mmap
20import unittest
21import sys
22
23
24def create_vk_destroy_instance_command(vk_instance: int, p_allocator: int) -> Command:
25    OP_vkDestroyInstance = 20001
26    opcode = OP_vkDestroyInstance
27    data = bytearray()
28    # seq number
29    seqno = 20
30    data += seqno.to_bytes(4, byteorder='big')
31    # VkInstance
32    data += vk_instance.to_bytes(8, sys.byteorder)
33    # VkAllocationCallbacks
34    data += p_allocator.to_bytes(8, sys.byteorder)
35    return Command(opcode=opcode, original_size=len(data) + 8, data=bytes(data))
36
37
38def create_command_data(commands: List[Command]) -> bytearray:
39    data = bytearray()
40    for command in commands:
41        data += command.opcode.to_bytes(4, sys.byteorder)
42        data += command.original_size.to_bytes(4, sys.byteorder)
43        data += command.data
44        assert(len(command.data) + 8 == command.original_size)
45        data += command.original_size.to_bytes(4, byteorder='little')
46    return data
47
48
49def create_dump(stream: Stream) -> bytearray:
50    data = create_command_data(stream.commands)
51    header = Header()
52    header.signature = b'GFXAPILOG\0'
53    header.version = 2
54    header.thread_id = stream.thread_id
55    # Convert Windows' GetSystemTimeAsFileTime to Unix timestamp
56    # https://stackoverflow.com/questions/1695288/getting-the-current-time-in-milliseconds-from-the-system-clock-in-windows
57    header.last_written_time = (stream.timestamp + 11644473600000) * 10_000
58    header.write_index = 0
59    header.committed_index = 0
60    header.capture_id = stream.capture_id
61    header.data_size = len(data)
62    res = b'\0' * ctypes.sizeof(header)
63    ctypes.memmove(res, ctypes.addressof(header), ctypes.sizeof(header))
64    return res + data
65
66
67class ProcessMinidumpTestCase(unittest.TestCase):
68    def test_single_command(self):
69        command = create_vk_destroy_instance_command(vk_instance=0x1234, p_allocator=0x7321)
70        stream = Stream(pos_in_file=0, timestamp=123456, thread_id=4726,
71                        capture_id=8261, commands=[command], error_message=None)
72        offset = 24
73        dump = b'\0' * offset + create_dump(stream)
74        streams = None
75        with mmap.mmap(-1, len(dump)) as mm:
76            mm.write(dump)
77            mm.seek(0)
78            streams = process_minidump(mm)
79        self.assertEqual(len(streams), 1)
80        self.assertEqual(streams[0].error_message, None)
81        self.assertEqual(streams[0].pos_in_file, offset)
82        self.assertEqual(streams[0].timestamp, stream.timestamp)
83        self.assertEqual(streams[0].thread_id, stream.thread_id)
84        self.assertEqual(streams[0].capture_id, stream.capture_id)
85        self.assertEqual(len(streams[0].commands), 1)
86        self.assertEqual(streams[0].commands[0].opcode, command.opcode)
87        self.assertEqual(streams[0].commands[0].original_size, command.original_size)
88        self.assertEqual(len(streams[0].commands[0].data), len(command.data))
89        self.assertEqual(streams[0].commands[0].data, command.data)
90
91    def test_multiple_commands(self):
92        commands = [create_vk_destroy_instance_command(
93            vk_instance=0x1234, p_allocator=0x7321),
94            create_vk_destroy_instance_command(
95            vk_instance=0x3621, p_allocator=0x7672)]
96        stream = Stream(pos_in_file=0, timestamp=123456, thread_id=4726,
97                        capture_id=8261, commands=commands, error_message=None)
98        dump = create_dump(stream)
99        streams = None
100        with mmap.mmap(-1, len(dump)) as mm:
101            mm.write(dump)
102            mm.seek(0)
103            streams = process_minidump(mm)
104        self.assertEqual(len(streams), 1)
105        self.assertEqual(streams[0].error_message, None)
106        self.assertEqual(len(streams[0].commands), len(commands))
107        for actual_command, expected_command in zip(streams[0].commands, commands):
108            self.assertEqual(actual_command.opcode, expected_command.opcode)
109            self.assertEqual(actual_command.original_size, expected_command.original_size)
110            self.assertEqual(len(actual_command.data), len(expected_command.data))
111            self.assertEqual(actual_command.data, expected_command.data)
112
113    def test_multiple_streams(self):
114        command = create_vk_destroy_instance_command(vk_instance=0x1234, p_allocator=0x7321)
115        streams = []
116        offsets = []
117        dump = bytearray()
118        for i in range(10):
119            stream = Stream(pos_in_file=0, timestamp=i, thread_id=i,
120                            capture_id=i, commands=[command], error_message=None)
121            streams.append(stream)
122            dump += b'\0' * i
123            offsets.append(len(dump))
124            dump += create_dump(stream)
125        actual_streams = None
126        with mmap.mmap(-1, len(dump)) as mm:
127            mm.write(dump)
128            mm.seek(0)
129            actual_streams = process_minidump(mm)
130        self.assertEqual(len(actual_streams), len(streams))
131        for i, (actual_stream, expected_stream) in enumerate(zip(actual_streams, streams)):
132            self.assertEqual(actual_stream.error_message, None)
133            self.assertEqual(actual_stream.pos_in_file, offsets[i])
134            self.assertEqual(actual_stream.timestamp, expected_stream.timestamp)
135            self.assertEqual(actual_stream.thread_id, expected_stream.thread_id)
136            self.assertEqual(actual_stream.capture_id, expected_stream.capture_id)
137            self.assertEqual(len(actual_stream.commands), len(expected_stream.commands))
138