| #!/usr/bin/env python3 | |
| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """Parse device output to extract LLVM profile data. | |
| The profile data obtained from the device is raw. Thus, it must be indexed | |
| before it can be used to generate coverage reports. | |
| Typical usage: | |
| stty -F /dev/ttyUSB0 icanon | |
| cat /dev/ttyUSB0 | ./device_profile_data.py foo.profraw | |
| llvm-profdata merge -sparse foo.profraw -o foo.profdata | |
| llvm-cov show OBJECT_FILE -instr-profile=foo.profdata | |
| """ | |
| import argparse | |
| import zlib | |
| import re | |
| import sys | |
| def extract_profile_data(device_output): | |
| """Parse device output to extract LLVM profile data. | |
| This function returns the LLVM profile data as a byte array after | |
| verifying its length and checksum. | |
| Args: | |
| device_output: Device output. | |
| Returns: | |
| LLVM profile data. | |
| Raises: | |
| ValueError: If LLVM profile data cannot be detected in the device | |
| output or its length or checksum is incorrect. | |
| """ | |
| device_output = re.sub(r'^.*] (.*)$', | |
| r'\1', | |
| device_output, | |
| flags=re.MULTILINE) | |
| device_output = device_output.translate( | |
| device_output.maketrans('', '', '\r\n')) | |
| match = re.search( | |
| r""" | |
| LLVM\ profile\ data\ \(length:\ (?P<len>\d+)\ bytes,\ CRC32:\ (?P<crc>0x[0-9a-f]*)\): | |
| (?P<data> 0x [0-9a-f]+) | |
| \x04 | |
| """, device_output, re.VERBOSE) | |
| if not match: | |
| raise ValueError( | |
| 'Could not detect LLVM profile data in device output.') | |
| exp_length = int(match.group('len')) | |
| exp_checksum = int(match.group('crc'), 0) | |
| byte_array = int(match.group('data'), 0).to_bytes(len(match.group('data')) // 2 - 1, | |
| byteorder='little', | |
| signed=False) | |
| # Check length | |
| act_length = len(byte_array) | |
| if act_length != exp_length: | |
| raise ValueError(('Length check failed! ', | |
| f'Expected: {exp_length}, actual: {act_length}.')) | |
| # Check checksum | |
| act_checksum = zlib.crc32(byte_array) | |
| if act_checksum != exp_checksum: | |
| raise ValueError( | |
| ('Checksum check failed! ', | |
| f'Expected: {exp_checksum}, actual: {act_checksum}.')) | |
| return byte_array | |
| def main(): | |
| """Parses command line arguments and extracts the profile data from device | |
| output.""" | |
| argparser = argparse.ArgumentParser( | |
| description='Extract LLVM profile data from device output.') | |
| argparser.add_argument(dest='output_file', | |
| type=argparse.FileType('wb'), | |
| default=sys.stdout, | |
| help='output file for writing LLVM profile data') | |
| argparser.add_argument('--input_file', | |
| type=argparse.FileType('rb'), | |
| default=sys.stdin.buffer, | |
| help='device output') | |
| args = argparser.parse_args() | |
| args.output_file.write( | |
| extract_profile_data(args.input_file.read().decode('ascii', 'ignore'))) | |
| if __name__ == '__main__': | |
| main() |