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