1#!/usr/bin/env python3
2#
3# Copyright (c) 2021 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7"""
8Log Parser for Dictionary-based Logging
9
10This uses the JSON database file to decode the input binary
11log data and print the log messages.
12"""
13
14import argparse
15import binascii
16import logging
17import sys
18
19import dictionary_parser
20from dictionary_parser.log_database import LogDatabase
21
22
23LOGGER_FORMAT = "%(message)s"
24logger = logging.getLogger("parser")
25
26LOG_HEX_SEP = "##ZLOGV1##"
27
28
29def parse_args():
30    """Parse command line arguments"""
31    argparser = argparse.ArgumentParser(allow_abbrev=False)
32
33    argparser.add_argument("dbfile", help="Dictionary Logging Database file")
34    argparser.add_argument("logfile", help="Log Data file")
35    argparser.add_argument("--hex", action="store_true",
36                           help="Log Data file is in hexadecimal strings")
37    argparser.add_argument("--rawhex", action="store_true",
38                           help="Log file only contains hexadecimal log data")
39    argparser.add_argument("--debug", action="store_true",
40                           help="Print extra debugging information")
41
42    return argparser.parse_args()
43
44
45def read_log_file(args):
46    """
47    Read the log from file
48    """
49    logdata = None
50
51    # Open log data file for reading
52    if args.hex:
53        if args.rawhex:
54            # Simply log file with only hexadecimal data
55            logdata = dictionary_parser.utils.convert_hex_file_to_bin(args.logfile)
56        else:
57            hexdata = ''
58
59            with open(args.logfile, "r", encoding="iso-8859-1") as hexfile:
60                for line in hexfile.readlines():
61                    hexdata += line.strip()
62
63            if LOG_HEX_SEP not in hexdata:
64                logger.error("ERROR: Cannot find start of log data, exiting...")
65                sys.exit(1)
66
67            idx = hexdata.index(LOG_HEX_SEP) + len(LOG_HEX_SEP)
68            hexdata = hexdata[idx:]
69
70            if len(hexdata) % 2 != 0:
71                # Make sure there are even number of characters
72                idx = int(len(hexdata) / 2) * 2
73                hexdata = hexdata[:idx]
74
75            idx = 0
76            while idx < len(hexdata):
77                # When running QEMU via west or ninja, there may be additional
78                # strings printed by QEMU, west or ninja (for example, QEMU
79                # is terminated, or user interrupted, etc). So we need to
80                # figure out where the end of log data stream by
81                # trying to convert from hex to bin.
82                idx += 2
83
84                try:
85                    binascii.unhexlify(hexdata[:idx])
86                except binascii.Error:
87                    idx -= 2
88                    break
89
90            logdata = binascii.unhexlify(hexdata[:idx])
91    else:
92        logfile = open(args.logfile, "rb")
93        if not logfile:
94            logger.error("ERROR: Cannot open binary log data file: %s, exiting...", args.logfile)
95            sys.exit(1)
96
97        logdata = logfile.read()
98
99        logfile.close()
100
101    return logdata
102
103
104def main():
105    """Main function of log parser"""
106    args = parse_args()
107
108    # Setup logging for parser
109    logging.basicConfig(format=LOGGER_FORMAT)
110    if args.debug:
111        logger.setLevel(logging.DEBUG)
112    else:
113        logger.setLevel(logging.INFO)
114
115    # Read from database file
116    database = LogDatabase.read_json_database(args.dbfile)
117    if database is None:
118        logger.error("ERROR: Cannot open database file: %s, exiting...", args.dbfile)
119        sys.exit(1)
120
121    logdata = read_log_file(args)
122    if logdata is None:
123        logger.error("ERROR: cannot read log from file: %s, exiting...", args.logfile)
124        sys.exit(1)
125
126    log_parser = dictionary_parser.get_parser(database)
127    if log_parser is not None:
128        logger.debug("# Build ID: %s", database.get_build_id())
129        logger.debug("# Target: %s, %d-bit", database.get_arch(), database.get_tgt_bits())
130        if database.is_tgt_little_endian():
131            logger.debug("# Endianness: Little")
132        else:
133            logger.debug("# Endianness: Big")
134
135        ret = log_parser.parse_log_data(logdata, debug=args.debug)
136        if not ret:
137            logger.error("ERROR: there were error(s) parsing log data")
138            sys.exit(1)
139    else:
140        logger.error("ERROR: Cannot find a suitable parser matching database version!")
141        sys.exit(1)
142
143
144if __name__ == "__main__":
145    main()
146