diff --git a/ipc_logging/ipc_logging.py b/ipc_logging/ipc_logging.py deleted file mode 100644 index b360c163a41830cf8ae2f915c8ab89a2e39b6d4e..0000000000000000000000000000000000000000 --- a/ipc_logging/ipc_logging.py +++ /dev/null @@ -1,1850 +0,0 @@ -#!/usr/bin/env python -""" -Copyright (c) 2015, The Linux Foundation. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - * Neither the name of The Linux Foundation nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR -BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -IPC Logging Extraction Tool ---------------------------- - -Can be used on RAM dumps (files containing binary data that are produced after -a crash) to extract logs generated by IPC Logging. The dumps must be given in -the order they were written, so that all data can be found. -""" -import struct -import sys -import ctypes -import logging -import os -import collections -import math -from optparse import OptionParser - -MAGIC = 0x52784425L -NMAGIC = ctypes.c_uint32(~MAGIC).value - -CTX_MAGIC = 0x25874452 -CTX_NMAGIC = ctypes.c_uint32(~CTX_MAGIC).value - -def hex_to_str(chars, width, nLineLimit=None): - """ - Converts a binary string to a hex dump. - - :param chars: The string to convert. - :param width: The width of the hex output. - :param nLineLimit: The maximum length of the hex output, in lines. - - :return: The hex dump as a string. - """ - output = [] - offset = 0 - nLines = 0 - - while chars: - line = chars[:width] - chars = chars[width:] - strBlankHex = ' ' * (width - len(line)) - strHex = ' '.join("%02x" % ord(c) for c in line) + strBlankHex - strOut = "%08x %s %s" % (offset, strHex, quotechars(line)) - offset += width - output.append(strOut) - nLines += 1 - if nLineLimit and nLines > nLineLimit: - output.append("...") - break - - return '\n'.join(output) + '\n' - -def quotechars(chars): - return ''.join(['.', c][c.isalnum()] for c in chars) - -def str_to_hex(strHex): - """ - Converts a space-separated string of hex bytes into a binary string. - - :param strHex: The space-separated string of hex bytes. - - Example: - str_to_hex("01 02 0A 0B") ==> "\x01\x02\x0a\x0b" - - :return: The binary string. - """ - return ''.join([chr(int(x.strip(), 16)) for x in strHex.split()]) - -# Element types -#enum { -# TSV_TYPE_INVALID, -# TSV_TYPE_TIMESTAMP, -# TSV_TYPE_POINTER, -# TSV_TYPE_INT32, -# TSV_TYPE_BYTE_ARRAY, -#}; -TSV_TYPE_INVALID = 0 -TSV_TYPE_TIMESTAMP = 1 -TSV_TYPE_POINTER = 2 -TSV_TYPE_INT32 = 3 -TSV_TYPE_BYTE_ARRAY = 4 - -# Message types -#enum { -# TSV_TYPE_MSG_START = 1, -# TSV_TYPE_SKB = TSV_TYPE_MSG_START, -# TSV_TYPE_STRING, -# TSV_TYPE_MSG_END = TSV_TYPE_STRING, -#}; -TSV_TYPE_MSG_START = 1 -TSV_TYPE_SKB = TSV_TYPE_MSG_START -TSV_TYPE_STRING = 2 -TSV_TYPE_MSG_END = 2 - - -# TSV values -#struct tsv_header { -# unsigned char type; -# unsigned char size; /* size of data field */ -#}; -V0_PAGE_HDR_SIZE = 4*4 + 2*2 + 2*4 -V1_PAGE_HDR_SIZE = 0x40 -V1_PAGE_HDR_SIZE_64 = 0x48 -PAGE_SIZE = 4*1024 -FULL_PAGE_WRITE_OFFSET = 0xFFFF -CTX_READ_SIZE = 64 - -# Default header sizes by IPC Logging Version -# V0 was 32-bit only, so it is used in the 64-bit list as a placeholder -PAGE_HDR_SIZES = [V0_PAGE_HDR_SIZE, V1_PAGE_HDR_SIZE] -PAGE_HDR_SIZES_64 = [V0_PAGE_HDR_SIZE, V1_PAGE_HDR_SIZE_64] - -class LogTSV(object): - """Handles processing message/value headers""" - - def __init__(self): - pass - - def unpack(self, data): - """ - Unpacks a TSV message. This is a wrapper around the - :func:`~struct.unpack()` function from the :mod:`struct` module. - - :param data: The binary data to be unpacked. - - :return: The TSV message with the header stripped off. - """ - assert len(data) >= 2, "Invalid message size %d" % (len(data)) - self.msg_type, self.msg_size = struct.unpack('BB', data[0:2]) - return data[2:] - - def pack(self, msg_type, msg_size): - """ - Packs a new TSV message structure. This is a wrapper around the - :func:`~struct.pack()` function from the :mod:`struct` module. - - :param msg_type: The TSV msg_type to be packed into the new \ - TSV message structure. - :param msg_size: The TSV msg_size to be packed into the new \ - TSV message structure. - - :return: The TSV message structure packed into a binary string. - """ - return struct.pack('BB', msg_type, msg_size) - -class TsvTimestamp(object): - """Handles processing TSV Timestamps""" - - def unpack(self, data): - """ - Unpacks a TSV timestamp. This is a wrapper around the - :func:`~struct.unpack()` function from the :mod:`struct` module. - - :return: The unpacked timestamp. - - **Side Effects**: The unpacked timestamp is saved as an instance \ - variable, ``self.fTimestamp``, which can be accessed as a \ - string through the :meth:`__str__()` method of this class. \ - This method is used by :class:`LogMessageString` to get \ - the timestamp in order to dump it to the logs. - """ - tsv = LogTSV() - data = tsv.unpack(data) - assert tsv.msg_type == TSV_TYPE_TIMESTAMP, "timestamp" - assert tsv.msg_size == 8, "msg size" - - # read 64-bit timestamp - timestamp_ns, = struct.unpack('<Q', data[0:8]) - data = data[8:] - self.fTimestamp = timestamp_ns * 1.0E-9 - return data - - def pack(self, fTimestamp): - """ - Packs a timestamp into a new TSV structure. This is a wrapper around the - :func:`~struct.pack()` function from the :mod:`struct` module. - - :param fTimestamp: The TSV timestamp to be packed into a new TSV \ - message structure. - - :return: A TSV message structure with *fTimestamp* packed \ - into the binary string. - """ - self.fTimestamp = fTimestamp - tsv = LogTSV() - data = tsv.pack(TSV_TYPE_TIMESTAMP, 8) - data += struct.pack('<Q', fTimestamp * 1.0E9) - return data - - def __str__(self): - return '%16.9f' % (self.fTimestamp) - -class TsvByteArray(object): - """Handles processing TSV byte arrays.""" - - def unpack(self, data): - """ - Unpacks a TSV byte array. This is a wrapper around the - :func:`~struct.unpack()` function from the :mod:`struct` module. - - :param data: The binary data to be unpacked. - - :return: The header data of the TSV message. - - **Side Effects**: The byte array data is saved as an instance \ - variable, ``self.strByteArray``, which can be accessed as a \ - string through the :meth:`__str__()` method of this class. \ - This method is used by :class:`LogMessageString` to get \ - the byte array data as a string in order to dump it to the logs. - """ - tsv = LogTSV() - data = tsv.unpack(data) - assert tsv.msg_type == TSV_TYPE_BYTE_ARRAY, \ - "%d != %d" % (tsv.msg_type, TSV_TYPE_BYTE_ARRAY) - - # read byte array - self.strByteArray = data[:tsv.msg_size] - data = data[tsv.msg_size:] - return data - - def pack(self, data): - """ - Packs a new TSV message structure, using the TSV byte array in *data*. - This is a wrapper around the :func:`~struct.pack()` function from the - :mod:`struct` module. - - :param data: The data to be packed into the new TSV message structure. - - :return: The new structure as binary data, with *data* appended to the \ - end. - """ - tsv = LogTSV() - lst = tsv.pack(TSV_TYPE_BYTE_ARRAY, len(data)) - lst += data - return lst - - def __str__(self): - return self.strByteArray - -class LogMessageString(object): - """Handles processing TSV message strings.""" - - def unpack(self, data, tsv): - """ - Unpacks a new TSV message string. To do this, it is first - necessary to unpack the timestamp using :meth:`TsvTimestamp.unpack()`, - and proceed to use unpack the byte array from the result using - :meth:`TsvByteArray.unpack()`. The result of this call is what is - returned. - - :param data: The data containing the TSV string message to be unpacked - :param tsv: The :class:`LogTSV` object that was used to unpack *data* - - :return: The TSV message string data - - **Side Effects**: The timestamp and the message extracted from *data* \ - are stored as instance variables, and can be printed using the \ - :meth:`__str__` method of this class. This :meth:`__str__()` \ - method is used to dump the logs in their \ - '[timestamp] <message>' format. - """ - assert tsv.msg_type == TSV_TYPE_STRING, "invalid message type" - - # TSV_TYPE_STRING - message type - # TSV_TYPE_TIMESTAMP - # TSV_TYPE_BYTE_ARRAY - self.timestamp = TsvTimestamp() - data = self.timestamp.unpack(data) - - self.strMessage = TsvByteArray() - data = self.strMessage.unpack(data) - return data - - def pack(self, fTimestamp, strMessage): - """ - Packs a new TSV LogMessageString structure. - - :param fTimestamp: The timestamp to be packed into the structure - :param strMessage: The string message to be packed into the structure - - :return: The packed TSV LogMessageString structure - """ - self.timestamp = TsvTimestamp() - data =self.timestamp.pack(fTimestamp) - data += TsvByteArray().pack(strMessage) - - tsv = LogTSV() - data = tsv.pack(TSV_TYPE_STRING, len(data)) + data - self.strMessage = strMessage - return data - - def __str__(self): - return '[%s] %s' % (self.timestamp, self.strMessage) - -class LogContext(object): - """ - A single context in a version 1 or greater IPC Logging log. - - .. code-block:: c - - struct ipc_log_context { - uint32_t magic; - uint32_t nmagic; - uint32_t version; - uint16_t user_version; - uint16_t header_size; - uint64_t log_id; - - /* Local data structures below ... */ - }; - - The purpose of the log context is to contain the above information about - the log. One log consists of one to several pages. Thus, pages and contexts - are associated by log ID, the one piece of information that is common - between them in their C structs. - - """ - - headerBinaryFormat = '<IIIHHQ20s' - headerBinaryFormatSize = struct.calcsize(headerBinaryFormat) - - def __init__(self): - self.data = None - - def unpack(self, data): - """ - Unpacks an ipc_log_context structure, and assigns the contents - into instance variables of this class. This is a wrapper around - the :func:`~struct.unpack()` function from the :mod:`struct` module. - - :param data: The binary ipc_log_context structure to be unpacked. - - :return: :const:`True` if the unpack completes successfully, \ - :const:`False` if not. - - **Side Effects**: If the unpack is successful, the contexts are \ - assigned to instance variables. In this way, :meth:`unpack()` \ - acts as a sort of constructor. This is common to all the \ - unpack methods. - """ - self.data = data - self.magic, self.nmagic, self.version, self.user_version, \ - self.header_size, self.log_id, self.name = \ - struct.unpack(self.headerBinaryFormat, - self.data[0:self.headerBinaryFormatSize]) - self.name = self.name.rstrip('\0') - - if self.magic == CTX_MAGIC and self.nmagic == CTX_NMAGIC: - return True - - return False - - def pack(self, nVersion, nUserVersion, nHeaderSize, nLogId, nName): - """ - Packs a new ipc_log_context structure using the given elements. This - function is especially useful in unit testing, as it provides the - ability to create structures and then unpack them using the - :meth:`unpack()` method. - - :param nVersion: The IPC Logging version - :param nUserVersion: The user version - :param nHeaderSize: The header size - :param nLogId: The log ID - :param nName: The context name - - :return: The packed binary data. - """ - self.data = struct.pack(self.headerBinaryFormat, CTX_MAGIC, CTX_NMAGIC, - nVersion, nUserVersion, nHeaderSize, nLogId, nName) - - def log_info(self): - """ - If DEBUG logging is turned on (command line parameter -v), logs the - instance variables of this LogContext to the output file. See the Python - :mod:`logging` module documentation and the IPC Logging main function - for more info on logging. - - This method logs the ``version``, ``user_version``, ``log_id``, - ``header_size``, and ``name`` variables to the logging output file. - """ - logging.debug("\tversion: %d" % (self.version)) - logging.debug("\tuser version: %d" % (self.user_version)) - logging.debug("\tlog id: %x" % (self.log_id)) - logging.debug("\theader_size: %x" % (self.header_size)) - logging.debug("\tname: %s" % (self.name)) - -class LogPage(object): - """ - LogPage base class. New versions of IPC Logging will require new descendants - of this class, as their :meth:`~LogPage_v0.unpack()` methods will change, - among other things. In IPC Logging version 1, the children of - :class:`LogPage` include :class:`LogPage_v0` and :class:`LogPage_v1`. There - is another class, :class:`LogPageVersionUnknown`, which is not in this - inheritance hierarchy; this is because it does not use the methods or data - needed by the rest of the :class:`LogPage` classes. - - Note that this is not an abstract class. The reason for this is to allow - new methods to be added here without forcing child classes to define them. - """ - def __init__(self): - self.data = None - self.previousPage = None - self.previousData = None - self.nextPage = None - self.nextData = None - self.iterated = False - self.page_header_size = V0_PAGE_HDR_SIZE - self.unknown_messages_data = None - - def pack_continue(self, strPayload): - """ - Packs a new page structure. The *strPayload* can be empty and - additional payload can be added using :meth:`pack_continue()`. - - Returns: Overflow data (*strPayload* that didn't fit into the page) or - :const:`None` if all fit. - """ - self.data += strPayload - - if len(self.data) > PAGE_SIZE: - overflowData = self.data[PAGE_SIZE:] - self.data = self.data[0:PAGE_SIZE] - else: - return None - - def __iter__(self): - """Message iterator""" - self.iterated = True - self.previousData = None - - logging.debug("Iterating log id %d, page %d" % \ - (self.log_id, self.page_num)) - self.log_info() - - if self.read_offset == FULL_PAGE_WRITE_OFFSET \ - and self.write_offset == FULL_PAGE_WRITE_OFFSET: - # page is either empty or full, but only the page with - # valid read/write pointers knows if we have data - self.iter_data = self.data[self.page_header_size:] - logging.debug("\tfull/empty case") - elif self.write_offset == FULL_PAGE_WRITE_OFFSET: - # page is full - read_iter = self.read_offset + self.page_header_size - self.iter_data = self.data[read_iter:] - logging.debug("\tfull case") - elif self.read_offset == FULL_PAGE_WRITE_OFFSET: - # page hasn't been read, but has only been partially written - write_iter = self.write_offset + self.page_header_size - self.iter_data = self.data[self.page_header_size:write_iter] - logging.debug("\tpartial write, no read") - elif self.read_offset >= self.write_offset: - # almost full buffer - read_iter = self.read_offset + self.page_header_size - write_iter = self.write_offset + self.page_header_size - self.iter_data = self.data[read_iter:] - self.previousData = self.data[self.page_header_size:write_iter] - - if self.page_num == 0 and self.read_offset == self.write_offset: - self.iter_data = self.data[self.page_header_size:] - self.previousData = None - logging.debug("\talmost full - first page offsets equal") - - logging.debug("\talmost full") - else: - # almost empty buffer - read_iter = self.read_offset + self.page_header_size - write_iter = self.write_offset + self.page_header_size - self.iter_data = self.data[read_iter:write_iter] - logging.debug("\talmost empty") - - # prepend data from previous page - if self.previousPage and self.previousPage.nextData: - logging.debug("Pulling previous data len") - logging.debug(hex_to_str(self.previousPage.nextData, 16, 10)) - self.iter_data = self.previousPage.nextData + self.iter_data - - return PageIterator(self) - - def next(self): - # handles full-buffer condition where write pointer - # is behind the read pointer - if self.nextPage and self.nextPage.previousData: - self.iter_data += self.nextPage.previousData - self.nextPage.previousData = None - - if len(self.iter_data) < 2: - # not enough data to retrieve message header - logging.debug("Pushing data to continue\n%s" % \ - (hex_to_str(self.iter_data, 16, 10))) - self.nextData = self.iter_data - self.iter_data = None - raise StopIteration - - while True: - try: - tsv = LogTSV() - data = tsv.unpack(self.iter_data) - if tsv.msg_size > len(data): - # not enough data left to extract entire message - logging.debug("Pushing data to continue\n%s" % \ - (hex_to_str(self.iter_data, 16, 10))) - self.nextData = self.iter_data - self.iter_data = None - raise StopIteration - - # TODO - this needs to be replaced with a dictionary for the - # message deserialization types for custom deserialization - # functions - if tsv.msg_type == 0: - # no more messages - raise StopIteration - - if tsv.msg_type == TSV_TYPE_STRING: - self.iter_data = data - msg = LogMessageString() - self.iter_data = msg.unpack(self.iter_data, tsv) - return msg - else: - debug_str = "Unknown message type 0x%x\n%s" % \ - (tsv.msg_type, hex_to_str(self.iter_data, 16, 10)) - - logging.debug(debug_str) - assert False, debug_str - - except StopIteration: - raise - -class LogPageVersionUnknown(object): - """ - This class is used before the version of the log being parsed is known. It - only unpacks the magic numbers and the page number from the data that is - found by :func:`cmdParse()`, in order to determine the version. - - This class can only be used to discover whether the log is a version 0 log - or a version 1 (or greater) log. See :meth:`isVersionOneOrGreater()` for - more details. - """ - def __init__(self): - self.data = None - self.version = None - self.headerMagicFormat = '<II' - self.headerMagicFormatSize = struct.calcsize(self.headerMagicFormat) - self.versionDataFormat = '<I' - self.versionDataFormatSize = struct.calcsize(self.versionDataFormat) - - def unpack(self, data): - """ - Partially unpacks a page structure in order to inspect the magic number. - This is a wrapper around the :func:`~struct.unpack()` function from the - :mod:`struct` module. - - :param data: The binary data to be unpacked. - - :return: :const:`True` if the unpack is successful, :const:`False` if \ - not. - - **Side Effects**: The unpacked values are assigned to instance \ - variables of this LogPageVersionUnknown instance. - """ - self.data=data - - self.magic, self.nmagic = \ - struct.unpack(self.headerMagicFormat, - self.data[0:self.headerMagicFormatSize]) - - if self.magic == MAGIC and self.nmagic == NMAGIC: - return True - - return False - - def isVersionOneOrGreater(self, data): - """ - This method determines whether the log is version 0, or version 1 or - greater. The top bit of ``self.version_data`` will be 1 if the log - version is at least 1, and 0 if not. Note that this function should - only be called after calling :meth:`unpack()`. - - :return: :const:`True` if the version is 1 or greater, \ - :const:`False` otherwise. - - **Side Effects**: This method unpacks the version data and stores \ - the result in an instance variable. - """ - self.data=data - endSize = self.headerMagicFormatSize + self.versionDataFormatSize - version_data = \ - struct.unpack(self.versionDataFormat, - self.data[self.headerMagicFormatSize:endSize]) - self.version_data = version_data[0] - mask = 0x80000000 - result = (self.version_data & mask) >= 0x80000000 - return result - -class LogPage_v0(LogPage): - """ - A single page in a version 0 IPC Logging log. This class is a descendant of - :class:`LogPage`. - - .. code-block:: c - - struct ipc_log_page_header { - uint32_t magic; - uint32_t nmagic; /* inverse of magic number */ - uint32_t log_id; /* owner of log */ - uint32_t page_num; - uint16_t read_offset; - uint16_t write_offset; - struct list_head list; - }; - """ - def __init__(self): - # Call the parent constructor - super(self.__class__, self).__init__() - self.headerBinaryFormat = '<IIIIHH' - self.headerBinaryFormatSize = struct.calcsize(self.headerBinaryFormat) - - def sortAndLink(self, lstPages, bSort): - """ - Given a list of pages in ascending page number order, - sort them chronologically and link the pages together. - This must be called before iterating over items in a page. - - :param lstPages: The list of pages to be sorted - :param bSort: A boolean indicating whether the logs are full or \ - partially full. If they are full, bSort is :const:`True`. - - :return: The sorted and linked list of pages. - - **Side Effects**: If the starting page cannot be found, this \ - method will fail an assertion. - """ - # Convert lstPages deque to a regular Python list - lstPages = list(lstPages) - - # Sort pages chronologically by finding the first active page. - # Since the pages are filled in order by page-id, the first - # non-full page is the last page written to. - if bSort: - for n in range(len(lstPages)): - if lstPages[0].write_offset != FULL_PAGE_WRITE_OFFSET: - break - lstPages.append(lstPages.pop(0)) - assert lstPages[0].write_offset != FULL_PAGE_WRITE_OFFSET, \ - "Unable to find starting page" - - # link pages - numPages = len(lstPages) - nMaxPage = 0 - for n in range(numPages): - page = lstPages[n] - nMaxPage = max(nMaxPage, page.page_num) - page.previousPage = lstPages[n - 1] - page.nextPage = lstPages[(n + 1) % numPages] - page.iterated = False - assert (nMaxPage + 1) == numPages, \ - "Missing log pages: max page %d, num pages %d" % \ - (nMaxPage, numPages) - - return lstPages - - def log_info(self): - """ - If DEBUG logging is turned on (command line parameter -v), logs the - instance variables of this LogPage_v0 to the output file. See the Python - :mod:`logging` module documentation and the IPC Logging main function - for more info on logging. - - This method only logs the ``read_offset`` and - ``write_offset`` variables as the others are logged elsewhere. - """ - logging.debug("\tread index: %x" % (self.read_offset)) - logging.debug("\twrite index: %x" % (self.write_offset)) - - def debug_save_log_pages(self, fIn, filename, data, options): - """ - Writes a binary file containing the data for the current page. This - is a helper to :func:`cmdParse`, and is particularly useful in - debugging. - - :param fIn: The input file currently being processed by the caller - :param filename: The name of the input file currently being processed - :param data: The binary data for the current page - :param options: Configuration options containing options.output_dir for - the output directory - - **Side Effects**: Writes an output file containing the binary data \ - for the current page. - """ - # Get just the filename, instead of the whole path. - # Otherwise the file open call will fail. - filename = os.path.split(filename)[1] - - strDebugFileName = 'log-%d-%d-%s-%u.bin' % (self.log_id, self.page_num, - filename, fIn.tell() - PAGE_SIZE) - - if options.output_dir: - strDebugFileName = \ - os.path.join(options.output_dir, strDebugFileName) - - logging.info("Writing debug file '%s'\n", strDebugFileName) - fOut = open(strDebugFileName, 'wb') - fOut.write(data) - fOut.close() - - def unpack(self, data): - """ - Unpacks an ipc_log_page version 0 structure, and assigns the contents - into instance variables of this class. This is a wrapper around - the :func:`~struct.unpack()` function from the :mod:`struct` module. - - :param data: The binary ipc_log_context structure to be unpacked. - - :return: :const:`True` if the unpack completes successfully, \ - :const:`False` if not. - - **Side Effects**: If the unpack is successful, the contexts are \ - assigned to instance variables. In this way, :meth:`unpack()` \ - acts as a sort of constructor. This is common to all the \ - unpack methods. - """ - self.data = data - self.magic, self.nmagic, self.log_id, self.page_num, self.read_offset, \ - self.write_offset = struct.unpack(self.headerBinaryFormat, - self.data[0:self.headerBinaryFormatSize]) - - # Mask off the top bit of page_num, which is used to indicate - # a version 1 or greater page - mask = 0x7FFFFFFF - self.page_num = self.page_num & mask - - if self.magic == MAGIC and self.nmagic == NMAGIC: - return True - - return False - - def pack(self, nLogId, nPageNum, nReadOffset, nWriteOffset, strPayload): - """ - Packs a new version 0 page structure. *strPayload* can be empty and - additional payload can be added using :meth:`~LogPage.pack_continue()`. - - :param nLogId: The log ID to be packed into the new structure - :param nPageNum: The page number - :param nReadOffset: The read offset - :param nWriteOffset: The write offset - :param strPayload: The payload of the new structure - - :return: Overflow data (*strPayload* that didn't fit into the page) or \ - :const:`None` if all fit. - """ - data = struct.pack(self.headerBinaryFormat, MAGIC, NMAGIC, - nLogId, nPageNum, nReadOffset, nWriteOffset) - - # add padding for list structures - data += '\0' * (self.page_header_size - len(data)) - - # append message payload - self.data = data + strPayload or '' - - if len(self.data) > PAGE_SIZE: - data = self.data[PAGE_SIZE:] - self.data = self.data[0:PAGE_SIZE] - return data - else: - return None - -class LogPage_v1(LogPage): - """ - A single page in a version 0 IPC Logging log. This class is a descendant of - :class:`LogPage`. - - .. code-block:: c - - struct ipc_log_page_header { - uint32_t magic; - uint32_t nmagic; - uint32_t page_num; - uint16_t read_offset; - uint16_t write_offset; - uint64_t log_id; - uint64_t start_time; - uint64_t end_time; - int64_t context_offset; - - /* add local data structures after this point */ - struct list_head list; - uint16_t nd_read_offset; - }; - """ - def __init__(self): - # Call the parent constructor - super(self.__class__, self).__init__() - self.headerBinaryFormat = '<IIIHHQQQq' - self.headerBinaryFormatSize = struct.calcsize(self.headerBinaryFormat) - self.context = LogContext() - self.context.header_size = V1_PAGE_HDR_SIZE - self.page_header_size = self.context.header_size - - def sortAndLink(self, lstPages, bSort): - """ - Given a list of pages in ascending page number order, - sort them chronologically and link the pages together. - This must be called before iterating over items in a page. - - :param lstPages: The list of pages to be sorted - :param bSort: A boolean indicating whether the logs are full or \ - partially full. If they are full, bSort is :const:`True`. - - :return: The sorted and linked list of pages. - - **Side Effects**: If the starting page cannot be found, this \ - method will fail an assertion. - """ - # Sort pages chronologically by finding the first active page. - # Since the pages are filled in order by page-id, the first - # non-full page is the last page written to. - if bSort: - # Rotate to lowest non-zero end time - min_end_time = None - min_start_time = None - min_end_position = 0 - min_start_position = 0 - for n in range(len(lstPages)): - cur_end_time = lstPages[n].get_end_time() - if cur_end_time == 0: - continue - - if not min_end_time: - min_end_time = cur_end_time - min_end_position = n - continue - - if cur_end_time > 0 and cur_end_time < min_end_time: - min_end_time = cur_end_time - min_end_position = n - - min_position = min_end_position - if lstPages[min_end_position].read_offset == 0: - for n in range(len(lstPages)): - cur_start_time = lstPages[n].get_start_time() - if cur_start_time == 0: - continue - - if not min_start_time: - min_start_time = cur_start_time - min_start_position = n - continue - - if cur_start_time > 0 and cur_start_time < min_start_time: - min_start_time = cur_start_time - min_start_position = n - - if lstPages[min_start_position].read_offset != 0: - min_position = min_start_position - - lstPages.rotate(-min_position) - lstPages = list(lstPages) - - # link pages - numPages = len(lstPages) - nMaxPage = 0 - for n in range(numPages): - page = lstPages[n] - nMaxPage = max(nMaxPage, page.page_num) - page.previousPage = lstPages[n - 1] - page.nextPage = lstPages[(n + 1) % numPages] - assert (nMaxPage + 1) == numPages, \ - "Missing log pages: max page %d, num pages %d" % \ - (nMaxPage, numPages) - - return lstPages - - def log_info(self): - """ - If DEBUG logging is turned on (command line parameter -v), logs the - instance variables of this LogPage_v1 to the output file. See the Python - :mod:`logging` module documentation and the IPC Logging main function - for more info on logging. - - This method logs the ``read_offset``, ``write_offset``, ``start_time``, - ``end_time``, and ``context_offset`` variables. - """ - logging.debug("\tread index: %x" % (self.read_offset)) - logging.debug("\twrite index: %x" % (self.write_offset)) - logging.debug("\tstart_time (seconds): %f" % (self.start_time * math.pow(10, -9))) - logging.debug("\tend_time (seconds): %f" % (self.end_time * math.pow(10, -9))) - logging.debug("\tcontext_offset: %x" % (self.context_offset)) - - def debug_save_log_pages(self, fIn, filename, data, name, options): - """ - If DEBUG logging is turned on, writes a binary file containing the data - for the current page. This is a helper to :func:`cmdParse`. - - :param fIn: The input file currently being processed by the caller - :param filename: The name of the input file currently being processed - :param data: The binary data for the current page - :param options: Configuration options containing options.output_dir for - the output directory - - **Side Effects**: Writes an output file containing the binary data \ - for the current page. - """ - # Get just the filename, instead of the whole path. - # Otherwise the file open call will fail. - filename = os.path.split(filename)[1] - - strDebugFileName = 'log-%s-%d-%s-%u.bin' % (name, self.page_num, - filename, fIn.tell() - PAGE_SIZE) - - if options.output_dir: - strDebugFileName = \ - os.path.join(options.output_dir, strDebugFileName) - - logging.info("Writing debug file '%s'\n", strDebugFileName) - fOut = open(strDebugFileName, 'wb') - fOut.write(data) - fOut.close() - - def unpack(self, data): - """ - Unpacks an ipc_log_page version 1 structure, and assigns the contents - into instance variables of this class. This is a wrapper around - the :func:`~struct.unpack()` function from the :mod:`struct` module. - - :param data: The binary ipc_log_context structure to be unpacked. - - :return: :const:`True` if the unpack completes successfully, \ - :const:`False` if not. - - **Side Effects**: If the unpack is successful, the contexts are \ - assigned to instance variables. In this way, :meth:`unpack()` \ - acts as a sort of constructor. This is common to all the \ - unpack methods. - """ - self.data=data - self.magic, self.nmagic, self.page_num, self.read_offset, \ - self.write_offset, self.log_id, self.start_time, \ - self.end_time, self.context_offset = \ - struct.unpack(self.headerBinaryFormat, - self.data[0:self.headerBinaryFormatSize]) - - # Mask off the top bit of page_num, which is used to indicate - # a version 1 or greater page - mask = 0x7FFFFFFF - self.page_num = self.page_num & mask - - if self.magic == MAGIC and self.nmagic == NMAGIC: - return True - - return False - - def pack(self, nPageNum, nReadOffset, nWriteOffset, nLogId, - nStartTime, nEndTime, nContextOffset, strPayload): - """ - Packs a new version 1 page structure. *strPayload* can be empty and - additional payload can be added using :meth:`~LogPage.pack_continue()`. - - :param nPageNum: The page number to be packed into the new structure - :param nReadOffset: The read offset - :param nWriteOffset: The write offset - :param nLogId: The log ID - :param nStartTime: The start time - :param nEndTime: The end time - :param nContextOffset: The context offset - :param strPayload: The payload of the new structure - - :return: Overflow data (*strPayload* that didn't fit into the page) or \ - :const:`None` if all fit. - """ - - data = struct.pack(self.headerBinaryFormat, MAGIC, NMAGIC, - nPageNum, nReadOffset, nWriteOffset, nLogId, nStartTime, - nEndTime, nContextOffset) - - # add padding for list structures - data += '\0' * (self.page_header_size - len(data)) - - # append message payload - self.data = data + strPayload or '' - - if len(self.data) > PAGE_SIZE: - data = self.data[PAGE_SIZE:] - self.data = self.data[0:PAGE_SIZE] - return data - else: - return None - - def setContext(self, context): - """ - Sets the ``context`` field of this log page to *context*, and sets the - ``page_header_size`` field to the context header size - (``context.header_size``). This method should be used whenever a page's - context is changed in order to make sure the page's information about - its context is consistent. - - :param context: The :class:`LogContext` object to be assigned to this \ - page - """ - self.context = context - self.page_header_size = self.context.header_size - - #@staticmethod - def get_end_time(self): - return self.end_time - - #@staticmethod - def get_start_time(self): - return self.start_time - -class PageIterator(object): - """ - An iterator object for use by the :class:`LogPage` iterator method. - """ - - def __init__(self, page): - self.page = page - - def __iter__(self): - # TODO - clear all iteration flags - logging.debug("Starting iteration through page %d" % \ - (self.page.page_num)) - - def next(self): - while True: - try: - return self.page.next() - except StopIteration: - logging.debug("Trying next page") - self.page = self.page.nextPage - if self.page.iterated: - logging.debug("Page %d already iterated" % \ - (self.page.page_num)) - raise - logging.debug("Iterating through page %d" % \ - (self.page.page_num)) - - # This isn't right. The page iterator is just discarded - # and then we are calling the .next. - self.page.__iter__() - -def dumpLogHelper(fout, pages, numPages, bSort): - """ - Handles iterating through the TSV messages in each :class:`LogPage` object - and dumping them to the output file for the current log ID. This function - is called by :func:`dumpLogWithRetry()`. - - :param fout: The output file for the log currently being processed by \ - :func:`dumpLogWithRetry()`. - :param pages: A list containing the :class:`LogPage` objects for the log \ - currently being processed. - :param numPages: The number of pages in *pages*. - :param bSort: A boolean indicating whether all pages in the log are \ - completely filled, or if some are partially full. If they are \ - full, bSort is :const:`True`. - - :return: :const:`True` if the messages are processed with no errors, \ - :const:`False` if errors are encountered during iteration or \ - during parsing of the TSV messages within the :class:`LogPage` \ - objects. - """ - # sort pages chronologically - try: - lstPages = collections.deque() - presentKey = None - - for key in pages.keys(): - if not presentKey: - presentKey = key - lstPages.append(pages[key]) - - lstPages = pages[presentKey].sortAndLink(lstPages, bSort) - except AssertionError as e: - strMsg = "Exception: %s" % (str(e)) - logging.error(strMsg) - fout.write(strMsg + '\n') - return False - - # dump data in pages - try: - page = lstPages[0] - logging.debug("Extracting messages from %d" % (page.page_num)) - last_unknown_data = '' - for x in page: - if page.unknown_messages_data and \ - page.unknown_messages_data != last_unknown_data: - fout.write(str(page.unknown_messages_data) + "\n") - last_unknown_data = page.unknown_messages_data - - fout.write("\n" + str(x).strip()) - - except AssertionError as e: - strMsg = "Exception: %s" % (str(e)) - logging.debug(strMsg) - fout.write(strMsg + '\n') - return False - - return True - -def dumpLogWithRetry(logId, name, version, pages, numPages, test, options): - """ - This function is called by :func:`dumpLogs()` and :func:`cmdTest()`. It - handles creating output files for the parsed logs and writing data to them. - A significant part of this work is delegated to :func:`dumpLogHelper()`. - - :param logId: The log ID currently being processed - :param name: The name of the log context - :param version: The version of the log - :param pages: List of pages to process and dump to the output file - :param numPages: The number of pages in the current log - :param test: True if this function was called by cmdTest - :param options: Configuration options containing options.output_dir for the - output directory - """ - if version >= 1: - strFileName = "ipc-log-%s.txt" % (str(name)) - titleStr = "Log name: %s (%d pages)\n" % (str(name), numPages) - else: - strFileName = "ipc-log-%s.txt" % (str(logId)) - titleStr = "Log id %s (%d pages)\n" % (str(logId), numPages) - - if test: - strFileName = "ipc-log-%s.txt" % (str(name)) - - if options.output_dir: - strFileName = \ - os.path.join(options.output_dir, strFileName) - - fout = open(strFileName, 'w') - logging.info("Writing log file '%s'" % (strFileName)) - - fout.write('-'*70 + "\n") - fout.write(titleStr) - fout.write('-'*70 + "\n") - - # Parse logs assuming they have wrapped (are full) - fout.write("Parsing as a full log\n") - bSuccess = dumpLogHelper(fout, pages, numPages, True) - if not bSuccess: - # If that fails, try assuming that they are partially-full - strMsg = "Unable to parse as full log, retrying as partial log" - logging.info(strMsg) - fout.write(strMsg + '\n') - bSuccess = dumpLogHelper(fout, pages, numPages, False) - - - if bSuccess: - strMsg = "Log extracted successfully" - logging.info(strMsg) - fout.write('\n' + strMsg + '\n') - else: - strMsg = "Could not extract log successfully. See %s for details." % \ - (strFileName) - if version == 0: - v0_expl_str = \ -""" -You are parsing a v0 log. If you collected logs using debugfs before the system -crashed, it may have adversely affected the RAM dumps. Collect the logs again -without using debugfs, or use the newest version of IPC Logging if possible. -This issue has been resolved in v1 and above. -""" - fout.write(v0_expl_str) - logging.info(strMsg) - - # Add a newline to stdout output - logging.info("") - fout.close() - -def get_context(start_of_page, fIn, page, dictContexts, lstFiles, fileCount): - ''' - Called by :func:`cmdParse()` in order the find and read the context for - the log page currently being processed. The context is found by reading - *page.context_offset* and using it to find the position of the context in - the "logical file" created by :func:`get_files_info()`. Once the input file - and position where the context resides have been calculated, the context can - be read, assigned to the *page.context* field, and added to the log ID-to- - context dictionary *dictContexts* using the :func:`read_context()` function. - - :param start_of_page: The starting position in the current file of the \ - page currently being processed - :param fIn: The input file - :param page: The page currently being processed - :param dictContexts: The dictionary that maintains the \ - log ID-to-:class:`LogContext` mapping. - :param lstFiles: The "logical file" data structure, a list of dictionaries \ - containing the name, starting position, and ending position of \ - each input file. - :param fileCount: An index into *lstFiles*, which indicates the file \ - currently being processed. For example, if *lstFiles* contains \ - three input files and we are processing the second one, fileCount \ - is 1. - - :return: A :class:`LogContext` object containing the context that was \ - found. If no context is found, returns :const:`None`. - - **Side Effects**: There are some performance side effects in this \ - function, as :meth:`file.seek()`, :meth:`file.read()`, and \ - :meth:`file.tell()` are performed several times in order to \ - extract the context based on *page.context_offset*. Since there \ - may be multiple files and the context may not be in the current \ - file, the other files may need to be opened and closed in order \ - to find the context. \ - ''' - context = LogContext() - start_of_file = lstFiles[fileCount]['start'] - end_of_file = lstFiles[fileCount]['end'] - file_position = start_of_file + start_of_page + page.context_offset - data = None - - logging.debug("start of file: 0x%x, end of file: 0x%x, " + \ - "start of page: 0x%x, context offset: 0x%x, file position: 0x%x,", - start_of_file, end_of_file, start_of_page, page.context_offset, - file_position) - - for inFile in lstFiles: - if inFile['start'] <= file_position <= inFile['end']: - # If the position is in the current file, don't open and close a - # temporary file - use fIn - if inFile['start'] == start_of_file: - saved_position = fIn.tell() - fIn.seek(file_position - start_of_file) - data = fIn.read(CTX_READ_SIZE) - fIn.seek(saved_position) - else: - tempIn = open(inFile['name'], 'rb') - tempIn.seek(file_position - inFile['start']) - data = tempIn.read(CTX_READ_SIZE) - tempIn.close() - break - - assert data is not None, "Context not found" - - context = read_context(page, data, dictContexts) - return context - -def read_context(page, data, dictContexts): - ''' - Read a context from *data* and make it the context of *page* using - :meth:`LogPage_v1.setContext()`. Then update the log ID-to-context - dictionary (*dictContexts*), and return the context. In short, find - a log context in *data*, perform some bookkeeping, and return the context. - This is a helper function to :func:`get_context()`, which is a helper to - :func:`cmdParse()`. - - :param page: The page to which the found context will be assigned - :param data: The data to be read - :param dictContexts: The dictionary where the context will be placed, if \ - a new context is read - - :return: The new context if successful, None if not. - ''' - context = LogContext() - - if data and len(data) >= CTX_READ_SIZE and context.unpack(data): - page.setContext(context) - - if not context.log_id in dictContexts: - logging.info("Found log context: %s" % (context.name)) - context.log_info() - - # Log a single newline to the output - logging.info("") - dictContexts[page.log_id] = context - - return context - else: - return None - -def check_log_consistency(dictLogs, dictContexts): - ''' - Creates two sets: - - * The set of log IDs gained from reading each page - - * The set of log IDs gained from reading each context - - Takes the intersection and makes sure that the intersection - equals both sets, i.e. that all log IDs found from reading pages - were found reading contexts, and vice-versa. - - :param dictLogs: The dictionary of log IDs to :class:`LogPage` objects - :param dictContext: The dictionary of log IDs to :class:`LogContext` objects - - :return: The difference of the two sets, contained in a :class:`set`. - ''' - diff = None - context_log_ids = set(dictContexts.keys()) - page_log_ids = set(dictLogs.keys()) - intersection = context_log_ids.intersection(page_log_ids) - if intersection == page_log_ids == context_log_ids: - logging.debug("Logs are consistent - the same log IDs are " + \ - "found in pages and contexts.") - else: - logging.error("Logs inconsistent.") - logging.error("Logs from contexts: %s", - str([hex(n) for n in context_log_ids])) - logging.error("Logs from pages: %s", - str([hex(n) for n in page_log_ids])) - - if len(page_log_ids) > len(context_log_ids): - diff = page_log_ids - context_log_ids - logging.error("IDs from pages, but not contexts: %s", - str([hex(n) for n in diff])) - else: - diff = context_log_ids - page_log_ids - logging.error("IDs from contexts, but not pages: %s", - str([hex(n) for n in diff])) - return diff - -def calc_file_size(fIn): - """ - Calculates the size of an input file. - - :param fIn: The input file - - :return: The file size, which is also the ending position relative to the \ - start of the input file. - - **Side Effects**: This function uses :meth:`file.seek()` and \ - :meth:`file.tell()` to calculate the file size, which can have \ - performance considerations. The original seek position of the file \ - is saved at the beginning of the function and is restored before \ - returning. - """ - saved_file_position = fIn.tell() - fIn.seek(0, 2) - file_size = fIn.tell() - fIn.seek(saved_file_position) - return file_size - -def get_files_info(files): - """ - Calculates the size of each input file and uses this information to build - a "logical file" data structure, which is a list of dictionaries, with one - dictionary per input file. Each dictionary contains the name of the file, - the starting position of the file, the ending position of the file, and - the size of the file. The starting position of the second file in the list - is the ending position of the first file in the list, and so on. Once the - structure is completely built, it can be used to index any position in any - of the input files as if they were one big file, without reading this big - file into memory. This allows the retrieval of contexts located in a - different file than their pages. - - The file size calculation is done using the :func:`calc_file_size()` - function. - - :param files: The list of input files - - :return: The "logical file" data structure, a list of dictionaries \ - containing information about each of the input files - """ - lstFiles = [] - fileCount = 0 - for strFile in files: - fileStart = 0 - - if fileCount > 0: - fileStart = (lstFiles[fileCount-1]['end']) - - strFileSplit = strFile.split(':', 1) - fileName = strFileSplit[0] - if len(strFileSplit) > 1: - fileStart = int(strFileSplit[1], 16) - - lstFiles.append(\ - {'name': fileName, 'start': fileStart, 'end': 0, 'size': 0}) - fIn = open(fileName, 'rb') - file_size = calc_file_size(fIn) - - lstFiles[fileCount]['end'] = fileStart + file_size - lstFiles[fileCount]['size'] = file_size - - fileCount += 1 - - return lstFiles - -def cmdParse(options): - """ - The top-level function, which is called from the main entry point to the - program if the ``parse`` command-line option is given. This function creates - two dictionaries, ``dictLogs`` and ``dictContexts``, which have log IDs as - keys and :class:`LogPage` and :class:`LogContext` objects as values, - respectively. It then populates these dictionaries by going through - each input file and reading data page-by-page. After all data has been read, - this function calls :func:`dumpLogs()` to dump the parsed logs to output - files. - - :param options: Configuration options containing options.output_dir for the - output directory, options.debug_enabled, a flag for - discerning whether the extraction script is running in debug - mode, and options.args, the list of input files. All - configuration options are passed through to helper - functions. - """ - dictLogs = {} - dictContexts = {} - fileCount = 0 - version = 0 - versionIsOneOrGreater = False - lstFiles = get_files_info(options.args) - - # scan files for log pages - for strFile in options.args: - fileName = strFile.split(':', 1)[0] - fIn = open(fileName,'rb') - - logging.info("Parsing '%s'" % (fileName)) - logging.info("-" * 70) - sys.stdout.flush() - page = LogPageVersionUnknown() - context = LogContext() - file_size = lstFiles[fileCount]['size'] - - while True: - start_of_page = fIn.tell() - data = fIn.read(16) - - # PAGE_SIZE - 16 = 4080 - if not data or len(data) + 4080 < PAGE_SIZE: - break - - if page.unpack(data): - data += fIn.read(PAGE_SIZE-16) - - if page.isVersionOneOrGreater(data): - versionIsOneOrGreater = True - page = LogPage_v1() - else: - versionIsOneOrGreater = False - page = LogPage_v0() - - if page.unpack(data): - if versionIsOneOrGreater: - if not page.log_id in dictContexts: - try: - context = get_context(start_of_page, fIn, page, - dictContexts, lstFiles, fileCount) - except: - msg = "Context not found - skipping page " + \ - "and trying to " + \ - "continue with unknown log page version" - logging.debug(msg) - page = LogPageVersionUnknown() - continue - - else: - context = dictContexts[page.log_id] - page.setContext(context) - - if context is None: - logging.debug("Context at 0x%x had no data, " + \ - "skipping this page", page.context_offset) - if options.debug_enabled: - page.log_info() - page = LogPageVersionUnknown() - continue - - version = context.version - - if not page.log_id in dictLogs: - dictLogs[page.log_id] = {} - - if dictLogs[page.log_id].has_key(page.page_num): - logging.error("Duplicate page found log id 0x%x," + \ - "page %d", page.log_id, page.page_num) - - dictLogs[page.log_id][page.page_num] = page - - if versionIsOneOrGreater: - logging.info(\ - "Found log page - context: %s, id: 0x%x, " + \ - "page: %d", page.context.name, \ - page.log_id, page.page_num) - page.log_info() - sys.stdout.flush() - else: - logging.info("Found log page: id 0x%x, page %d" % \ - (page.log_id, page.page_num)) - page.log_info() - sys.stdout.flush() - - # for debug mode, save the extracted log pages - if options.debug_enabled: - if version >= 1: - page.debug_save_log_pages(fIn, fileName, data, - context.name, options) - else: - page.debug_save_log_pages(fIn, fileName, data, - options) - - page = LogPageVersionUnknown() - - fIn.close() - fileCount += 1 - - # Check that log_ids received from contexts and from pages match - if versionIsOneOrGreater: - check_log_consistency(dictLogs, dictContexts) - dumpLogs(dictLogs, version, options) - else: - dumpLogs(dictLogs, 0, options) - - logging.debug("lstFiles: " + str(lstFiles)) - -def dumpLogs(dictLogs, version, options): - """ - Dump logs from the dictionary of log IDs to logs built by - :func:`cmdParse()`. This is called at the end of :func:`cmdParse()`, - after all files have been processed and the dictionary has been built. - - :param dictLogs: The dictionary built by :func:`cmdParse()`. - :param version: The IPC Logging version. - :param options: Configuration options passed through to helper functions - - **Side Effects**: The :func:`dumpLogWithRetry()` function is called, \ - which dumps the parsed logs to output files. - """ - # dump the logs - logs = dictLogs.keys() - logs.sort() - logging.debug("Logs: %s", logs) - for key in logs: - pages = dictLogs[key] - numPages = len(pages.keys()) - - if version >= 1: - first_page_in_log = dictLogs[key].keys()[0] - name = dictLogs[key][first_page_in_log].context.name - else: - name = None - - dumpLogWithRetry(key, name, version, pages, numPages, False, options) - -def cmdTest(options): - """ - Parses the provided log page files in order and extracts the logs. This is - useful for testing and for dealing with failure cases (such as duplicate - logs due to left-over log pages from previous boot cycles). - - :param options: Configuration options containing options.output_dir for - the output directory, and options.args, the list of input - files, and options.arch_64, the flag indicating if the log - pages should be interpreted as 64-bit dumps or not. All - configuration options are passed through to helper - functions. - """ - dictPages = {} - version = 0 - numPages = 0 - - # load existing files - lstFiles = options.args - - try: - version = int(lstFiles.pop()) - except ValueError as e: - strMsg = "Version must be provided! Exiting..." - logging.error("Exception: %s\n%s\n" % (str(e), strMsg)) - raise - - for f in lstFiles: - data = open(f, 'rb').read() - - page = LogPageVersionUnknown() - assert page.unpack(data) == True, "Unable to open file '%s'" % (f) - - if page.isVersionOneOrGreater(data): - page = LogPage_v1() - page.unpack(data) - else: - page = LogPage_v0() - page.unpack(data) - - numPages += 1 - dictPages[page.page_num] = page - logging.info("Loaded '%s' log id %d, page %d" % \ - (f, page.log_id, page.page_num)) - page.log_info() - - try: - if options.arch_64: - page.page_header_size = PAGE_HDR_SIZES_64[version] - else: - page.page_header_size = PAGE_HDR_SIZES[version] - except IndexError as e: - strMsg = "Invalid version! Exiting..." - logging.error("Exception: %s\n%s\n" % (str(e), strMsg)) - raise - - # Use only the last pathname component, as the rest of the path is - # added in dumpLogWithRetry() - if options.output_dir: - filename = os.path.split(lstFiles[0])[1] - else: - filename = lstFiles[0] - - # dump the logs (in the same order provided) - dumpLogWithRetry(page.log_id, 'test-log-%s' % (filename), - version, dictPages, numPages, True, options) - -class LoggingFormatter(logging.Formatter): - """ - Custom logging formatter that prints all INFO messages without formatting - and all other messages with the appropriate level name. - - See :mod:`logging` documentation for more info on Python logging. - """ - infoFormat = '%(message)s' - defaultFormat = '%(levelname)s %(module)s:%(lineno)d: %(message)s' - - def __init__(self, fmt=defaultFormat): - logging.Formatter.__init__(self, fmt) - - def format(self, record): - if record.levelno == logging.INFO: - self._fmt = self.infoFormat - else: - self._fmt = self.defaultFormat - return logging.Formatter.format(self, record) - -def configure_logging(options, stdout): - """ - Configure the logging options. - - :param options: The options object, which contains options.verbosity for - the logging verbosity and options.debug_enabled to indicate - whether the extraction script is running in debug mode. - :param stdout: Whether or not to create a logging handler for stdout - """ - - if stdout: - loggingHandler = logging.StreamHandler(sys.stdout) - loggingHandler.setFormatter(LoggingFormatter()) - logging.root.addHandler(loggingHandler) - - # Set Log level - LOG_LEVELS = { 3: logging.DEBUG, - 2: logging.INFO, - 1: logging.WARNING, - 0: logging.ERROR, - } - assert LOG_LEVELS.has_key(options.verbosity), "Unknown log level %d" % \ - (options.verbosity) - logging.root.setLevel(LOG_LEVELS[options.verbosity]) - - if logging.root.getEffectiveLevel >= 2 and options.quiet: - logging.root.setLevel(LOG_LEVELS[1]) - - options.debug_enabled = False - if options.verbosity >= 3: - options.debug_enabled = True - -def set_output_directory(options): - """ - Set up the output directory. - - :param options: Configuration options containing options.output_dir for the - output directory - - :return: The output logging handler object, or None if no output directory - was provided by the user. - """ - if not options.output_dir: - return None - - options.output_dir = \ - os.path.abspath(os.path.expanduser(options.output_dir)) - - # Create output directory if it does not exist - if not os.path.isdir(options.output_dir): - os.makedirs(options.output_dir) - - # Create logging handler to take output in this directory - output_logging_handler = \ - logging.FileHandler(\ - os.path.join(options.output_dir, "ipc_extraction_report.txt"), - "w") - output_logging_handler.setFormatter(LoggingFormatter()) - logging.root.addHandler(output_logging_handler) - - logging.info("Output path: " + options.output_dir) - return output_logging_handler - -#------------------------------------------------------------------------ -# Main Program Entry Point -#------------------------------------------------------------------------ -if __name__ == '__main__': - # Parse command line - strUsage = """\ -%prog CMD [OPTIONS] - -parse ------ -%prog parse MEMORY_FILE(s) -%prog parse MEMORY_FILE MEMORY_FILE_2:<starting address> - -Parses the provided input files (memory dump files) and extracts the logs. - -Examples: -\t%prog parse DDRCS0.BIN DDRCS1.BIN -\t%prog parse DDRCS0_0.BIN DDRCS0_1.BIN DDRCS1_0.BIN DDRCS1_1.BIN -\t%prog parse DDRCS0_0.BIN DDRCS0_1.BIN DDRCS1_0.BIN:0x80000000 DDRCS1_1.BIN -\t%prog parse DDRCS0_0.BIN:0x00000000 DDRCS0_1.BIN:0x30000000 DDRCS1_0.BIN:0x80000000 DDRCS1_1.BIN:0xb0000000 - -It is only necessary to provide the starting address of a memory dump file if -the dumps are not contiguous in memory. Whether or not the dumps are contiguous, -along with their starting addresses, can be found in the file load.cmm which -is usually included in crash dumps. Starting addresses must be provided in -hexadecimal. - -test ----- -%prog test [--64-bit] LOG_PAGE_FILE(s) VERSION - -Parses the provided log pages files in order and extracts the logs. This is -useful for testing and for dealing with failure cases (such as duplicate logs -due to left-over log pages from previous boot cycles). - -The test command must be provided with the IPC Logging version in order to -determine the right log page header size to use when parsing. During normal -parsing, this information is gained from the log context, which is not provided -to the test command. - -The --64-bit option denotes whether or not to interpret the provided log pages -as 64-bit dumps. This is also necessary to determine the log page header size. - -Examples: -\t%prog test log-1-0.bin log-1-1.bin log-1-2.bin 0 -\t%prog test --64-bit log-1-0.bin log-1-1.bin log-1-2.bin 1 -\t%prog test log-1-0.bin log-1-1.bin log-1-2.bin 1 - -=================================================== -General Options -=================================================== - -Verbosity ---------- -Debug logging can be enabled using the count -v or --verbose option. The -log level based upon the number of occurrences of the -v option will be one of -the following: - ERROR/WARNING/INFO Default - DEBUG (-v) - -Quiet ------ -Logging can be turned off entirely using this option. - -Example: -\t%prog parse -q DDRCS0.BIN DDRCS1.BIN - -Output Directory ----------------- -All output can be redirected to another directory, using -o or --output. -This is helpful if the memory dump files you're parsing are in a location that -is read-only. Use the -o, or --output, option, and provide the path to an -output directory. The ipc_logging script will create the directory if it -doesn't exist. All output will go to that directory. The script's own logging -will go to stdout as well as a file in the specified directory. - -64-bit ------- -This option is only to be used with the test command. It is a no-op otherwise. -If used with the test command, the test command will interpret the log pages -given to it as 64-bit dumps. - -Examples: -\t%prog parse -o ~/test_output DDRCS0.BIN DDRCS1.BIN -\t%prog test -v -o ~/test_output LOG_PAGE_FILE(s) -""" - parser = OptionParser(usage=strUsage) - if len(sys.argv) < 2: - parser.error('Command required') - - strCmd = (sys.argv[1]).lower() - - parser.add_option("-v", "--verbose", - action="count", dest="verbosity", default=2, - help="Log program status to stdout (-v = debug)" - ) - - parser.add_option("-q", "--quiet", - action="count", dest="quiet", default=False, - help="Log no program status to stdout (-q = quiet)" - ) - - parser.add_option("-o", "--output", - action="store", type="string", dest="output_dir", - help="If the current directory can't be written to, specify \ - a path for an output directory. The path is \ - OS-independent, and can be absolute or relative.") - - parser.add_option("", "--64-bit", - action="store_true", dest="arch_64", - help="For use with the test command. Interpret the log pages as \ - 64-bit dumps.") - - (options, args) = parser.parse_args(sys.argv[2:]) - options.cmd = strCmd - - if args: - # extract positions args (args not associated with flags) - options.args = args - - # Configure logging format - configure_logging(options, True) - - if not options.cmd or options.cmd == "help": - parser.error('No command/help specified') - exit(1) - - output_logging_handler = set_output_directory(options) - - dictCmds = {'parse': cmdParse, - 'test': cmdTest, - } - - # run the requested command - if not dictCmds.has_key(options.cmd): - parser.error("Unknown command '%s'" % (options.cmd)) - - dictCmds[options.cmd](options) - - if options.output_dir: - output_logging_handler.close() diff --git a/ipc_logging/ipc_logging.py b/ipc_logging/ipc_logging.py new file mode 120000 index 0000000000000000000000000000000000000000..463e4cac534a0378f71939c0bc732d43505b687a --- /dev/null +++ b/ipc_logging/ipc_logging.py @@ -0,0 +1 @@ +../linux-ramdump-parser-v2/parsers/ipc_logging.py \ No newline at end of file diff --git a/linux-ramdump-parser-v2/parsers/ipc_logging.py b/linux-ramdump-parser-v2/parsers/ipc_logging.py new file mode 100644 index 0000000000000000000000000000000000000000..b360c163a41830cf8ae2f915c8ab89a2e39b6d4e --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/ipc_logging.py @@ -0,0 +1,1850 @@ +#!/usr/bin/env python +""" +Copyright (c) 2015, The Linux Foundation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +IPC Logging Extraction Tool +--------------------------- + +Can be used on RAM dumps (files containing binary data that are produced after +a crash) to extract logs generated by IPC Logging. The dumps must be given in +the order they were written, so that all data can be found. +""" +import struct +import sys +import ctypes +import logging +import os +import collections +import math +from optparse import OptionParser + +MAGIC = 0x52784425L +NMAGIC = ctypes.c_uint32(~MAGIC).value + +CTX_MAGIC = 0x25874452 +CTX_NMAGIC = ctypes.c_uint32(~CTX_MAGIC).value + +def hex_to_str(chars, width, nLineLimit=None): + """ + Converts a binary string to a hex dump. + + :param chars: The string to convert. + :param width: The width of the hex output. + :param nLineLimit: The maximum length of the hex output, in lines. + + :return: The hex dump as a string. + """ + output = [] + offset = 0 + nLines = 0 + + while chars: + line = chars[:width] + chars = chars[width:] + strBlankHex = ' ' * (width - len(line)) + strHex = ' '.join("%02x" % ord(c) for c in line) + strBlankHex + strOut = "%08x %s %s" % (offset, strHex, quotechars(line)) + offset += width + output.append(strOut) + nLines += 1 + if nLineLimit and nLines > nLineLimit: + output.append("...") + break + + return '\n'.join(output) + '\n' + +def quotechars(chars): + return ''.join(['.', c][c.isalnum()] for c in chars) + +def str_to_hex(strHex): + """ + Converts a space-separated string of hex bytes into a binary string. + + :param strHex: The space-separated string of hex bytes. + + Example: + str_to_hex("01 02 0A 0B") ==> "\x01\x02\x0a\x0b" + + :return: The binary string. + """ + return ''.join([chr(int(x.strip(), 16)) for x in strHex.split()]) + +# Element types +#enum { +# TSV_TYPE_INVALID, +# TSV_TYPE_TIMESTAMP, +# TSV_TYPE_POINTER, +# TSV_TYPE_INT32, +# TSV_TYPE_BYTE_ARRAY, +#}; +TSV_TYPE_INVALID = 0 +TSV_TYPE_TIMESTAMP = 1 +TSV_TYPE_POINTER = 2 +TSV_TYPE_INT32 = 3 +TSV_TYPE_BYTE_ARRAY = 4 + +# Message types +#enum { +# TSV_TYPE_MSG_START = 1, +# TSV_TYPE_SKB = TSV_TYPE_MSG_START, +# TSV_TYPE_STRING, +# TSV_TYPE_MSG_END = TSV_TYPE_STRING, +#}; +TSV_TYPE_MSG_START = 1 +TSV_TYPE_SKB = TSV_TYPE_MSG_START +TSV_TYPE_STRING = 2 +TSV_TYPE_MSG_END = 2 + + +# TSV values +#struct tsv_header { +# unsigned char type; +# unsigned char size; /* size of data field */ +#}; +V0_PAGE_HDR_SIZE = 4*4 + 2*2 + 2*4 +V1_PAGE_HDR_SIZE = 0x40 +V1_PAGE_HDR_SIZE_64 = 0x48 +PAGE_SIZE = 4*1024 +FULL_PAGE_WRITE_OFFSET = 0xFFFF +CTX_READ_SIZE = 64 + +# Default header sizes by IPC Logging Version +# V0 was 32-bit only, so it is used in the 64-bit list as a placeholder +PAGE_HDR_SIZES = [V0_PAGE_HDR_SIZE, V1_PAGE_HDR_SIZE] +PAGE_HDR_SIZES_64 = [V0_PAGE_HDR_SIZE, V1_PAGE_HDR_SIZE_64] + +class LogTSV(object): + """Handles processing message/value headers""" + + def __init__(self): + pass + + def unpack(self, data): + """ + Unpacks a TSV message. This is a wrapper around the + :func:`~struct.unpack()` function from the :mod:`struct` module. + + :param data: The binary data to be unpacked. + + :return: The TSV message with the header stripped off. + """ + assert len(data) >= 2, "Invalid message size %d" % (len(data)) + self.msg_type, self.msg_size = struct.unpack('BB', data[0:2]) + return data[2:] + + def pack(self, msg_type, msg_size): + """ + Packs a new TSV message structure. This is a wrapper around the + :func:`~struct.pack()` function from the :mod:`struct` module. + + :param msg_type: The TSV msg_type to be packed into the new \ + TSV message structure. + :param msg_size: The TSV msg_size to be packed into the new \ + TSV message structure. + + :return: The TSV message structure packed into a binary string. + """ + return struct.pack('BB', msg_type, msg_size) + +class TsvTimestamp(object): + """Handles processing TSV Timestamps""" + + def unpack(self, data): + """ + Unpacks a TSV timestamp. This is a wrapper around the + :func:`~struct.unpack()` function from the :mod:`struct` module. + + :return: The unpacked timestamp. + + **Side Effects**: The unpacked timestamp is saved as an instance \ + variable, ``self.fTimestamp``, which can be accessed as a \ + string through the :meth:`__str__()` method of this class. \ + This method is used by :class:`LogMessageString` to get \ + the timestamp in order to dump it to the logs. + """ + tsv = LogTSV() + data = tsv.unpack(data) + assert tsv.msg_type == TSV_TYPE_TIMESTAMP, "timestamp" + assert tsv.msg_size == 8, "msg size" + + # read 64-bit timestamp + timestamp_ns, = struct.unpack('<Q', data[0:8]) + data = data[8:] + self.fTimestamp = timestamp_ns * 1.0E-9 + return data + + def pack(self, fTimestamp): + """ + Packs a timestamp into a new TSV structure. This is a wrapper around the + :func:`~struct.pack()` function from the :mod:`struct` module. + + :param fTimestamp: The TSV timestamp to be packed into a new TSV \ + message structure. + + :return: A TSV message structure with *fTimestamp* packed \ + into the binary string. + """ + self.fTimestamp = fTimestamp + tsv = LogTSV() + data = tsv.pack(TSV_TYPE_TIMESTAMP, 8) + data += struct.pack('<Q', fTimestamp * 1.0E9) + return data + + def __str__(self): + return '%16.9f' % (self.fTimestamp) + +class TsvByteArray(object): + """Handles processing TSV byte arrays.""" + + def unpack(self, data): + """ + Unpacks a TSV byte array. This is a wrapper around the + :func:`~struct.unpack()` function from the :mod:`struct` module. + + :param data: The binary data to be unpacked. + + :return: The header data of the TSV message. + + **Side Effects**: The byte array data is saved as an instance \ + variable, ``self.strByteArray``, which can be accessed as a \ + string through the :meth:`__str__()` method of this class. \ + This method is used by :class:`LogMessageString` to get \ + the byte array data as a string in order to dump it to the logs. + """ + tsv = LogTSV() + data = tsv.unpack(data) + assert tsv.msg_type == TSV_TYPE_BYTE_ARRAY, \ + "%d != %d" % (tsv.msg_type, TSV_TYPE_BYTE_ARRAY) + + # read byte array + self.strByteArray = data[:tsv.msg_size] + data = data[tsv.msg_size:] + return data + + def pack(self, data): + """ + Packs a new TSV message structure, using the TSV byte array in *data*. + This is a wrapper around the :func:`~struct.pack()` function from the + :mod:`struct` module. + + :param data: The data to be packed into the new TSV message structure. + + :return: The new structure as binary data, with *data* appended to the \ + end. + """ + tsv = LogTSV() + lst = tsv.pack(TSV_TYPE_BYTE_ARRAY, len(data)) + lst += data + return lst + + def __str__(self): + return self.strByteArray + +class LogMessageString(object): + """Handles processing TSV message strings.""" + + def unpack(self, data, tsv): + """ + Unpacks a new TSV message string. To do this, it is first + necessary to unpack the timestamp using :meth:`TsvTimestamp.unpack()`, + and proceed to use unpack the byte array from the result using + :meth:`TsvByteArray.unpack()`. The result of this call is what is + returned. + + :param data: The data containing the TSV string message to be unpacked + :param tsv: The :class:`LogTSV` object that was used to unpack *data* + + :return: The TSV message string data + + **Side Effects**: The timestamp and the message extracted from *data* \ + are stored as instance variables, and can be printed using the \ + :meth:`__str__` method of this class. This :meth:`__str__()` \ + method is used to dump the logs in their \ + '[timestamp] <message>' format. + """ + assert tsv.msg_type == TSV_TYPE_STRING, "invalid message type" + + # TSV_TYPE_STRING - message type + # TSV_TYPE_TIMESTAMP + # TSV_TYPE_BYTE_ARRAY + self.timestamp = TsvTimestamp() + data = self.timestamp.unpack(data) + + self.strMessage = TsvByteArray() + data = self.strMessage.unpack(data) + return data + + def pack(self, fTimestamp, strMessage): + """ + Packs a new TSV LogMessageString structure. + + :param fTimestamp: The timestamp to be packed into the structure + :param strMessage: The string message to be packed into the structure + + :return: The packed TSV LogMessageString structure + """ + self.timestamp = TsvTimestamp() + data =self.timestamp.pack(fTimestamp) + data += TsvByteArray().pack(strMessage) + + tsv = LogTSV() + data = tsv.pack(TSV_TYPE_STRING, len(data)) + data + self.strMessage = strMessage + return data + + def __str__(self): + return '[%s] %s' % (self.timestamp, self.strMessage) + +class LogContext(object): + """ + A single context in a version 1 or greater IPC Logging log. + + .. code-block:: c + + struct ipc_log_context { + uint32_t magic; + uint32_t nmagic; + uint32_t version; + uint16_t user_version; + uint16_t header_size; + uint64_t log_id; + + /* Local data structures below ... */ + }; + + The purpose of the log context is to contain the above information about + the log. One log consists of one to several pages. Thus, pages and contexts + are associated by log ID, the one piece of information that is common + between them in their C structs. + + """ + + headerBinaryFormat = '<IIIHHQ20s' + headerBinaryFormatSize = struct.calcsize(headerBinaryFormat) + + def __init__(self): + self.data = None + + def unpack(self, data): + """ + Unpacks an ipc_log_context structure, and assigns the contents + into instance variables of this class. This is a wrapper around + the :func:`~struct.unpack()` function from the :mod:`struct` module. + + :param data: The binary ipc_log_context structure to be unpacked. + + :return: :const:`True` if the unpack completes successfully, \ + :const:`False` if not. + + **Side Effects**: If the unpack is successful, the contexts are \ + assigned to instance variables. In this way, :meth:`unpack()` \ + acts as a sort of constructor. This is common to all the \ + unpack methods. + """ + self.data = data + self.magic, self.nmagic, self.version, self.user_version, \ + self.header_size, self.log_id, self.name = \ + struct.unpack(self.headerBinaryFormat, + self.data[0:self.headerBinaryFormatSize]) + self.name = self.name.rstrip('\0') + + if self.magic == CTX_MAGIC and self.nmagic == CTX_NMAGIC: + return True + + return False + + def pack(self, nVersion, nUserVersion, nHeaderSize, nLogId, nName): + """ + Packs a new ipc_log_context structure using the given elements. This + function is especially useful in unit testing, as it provides the + ability to create structures and then unpack them using the + :meth:`unpack()` method. + + :param nVersion: The IPC Logging version + :param nUserVersion: The user version + :param nHeaderSize: The header size + :param nLogId: The log ID + :param nName: The context name + + :return: The packed binary data. + """ + self.data = struct.pack(self.headerBinaryFormat, CTX_MAGIC, CTX_NMAGIC, + nVersion, nUserVersion, nHeaderSize, nLogId, nName) + + def log_info(self): + """ + If DEBUG logging is turned on (command line parameter -v), logs the + instance variables of this LogContext to the output file. See the Python + :mod:`logging` module documentation and the IPC Logging main function + for more info on logging. + + This method logs the ``version``, ``user_version``, ``log_id``, + ``header_size``, and ``name`` variables to the logging output file. + """ + logging.debug("\tversion: %d" % (self.version)) + logging.debug("\tuser version: %d" % (self.user_version)) + logging.debug("\tlog id: %x" % (self.log_id)) + logging.debug("\theader_size: %x" % (self.header_size)) + logging.debug("\tname: %s" % (self.name)) + +class LogPage(object): + """ + LogPage base class. New versions of IPC Logging will require new descendants + of this class, as their :meth:`~LogPage_v0.unpack()` methods will change, + among other things. In IPC Logging version 1, the children of + :class:`LogPage` include :class:`LogPage_v0` and :class:`LogPage_v1`. There + is another class, :class:`LogPageVersionUnknown`, which is not in this + inheritance hierarchy; this is because it does not use the methods or data + needed by the rest of the :class:`LogPage` classes. + + Note that this is not an abstract class. The reason for this is to allow + new methods to be added here without forcing child classes to define them. + """ + def __init__(self): + self.data = None + self.previousPage = None + self.previousData = None + self.nextPage = None + self.nextData = None + self.iterated = False + self.page_header_size = V0_PAGE_HDR_SIZE + self.unknown_messages_data = None + + def pack_continue(self, strPayload): + """ + Packs a new page structure. The *strPayload* can be empty and + additional payload can be added using :meth:`pack_continue()`. + + Returns: Overflow data (*strPayload* that didn't fit into the page) or + :const:`None` if all fit. + """ + self.data += strPayload + + if len(self.data) > PAGE_SIZE: + overflowData = self.data[PAGE_SIZE:] + self.data = self.data[0:PAGE_SIZE] + else: + return None + + def __iter__(self): + """Message iterator""" + self.iterated = True + self.previousData = None + + logging.debug("Iterating log id %d, page %d" % \ + (self.log_id, self.page_num)) + self.log_info() + + if self.read_offset == FULL_PAGE_WRITE_OFFSET \ + and self.write_offset == FULL_PAGE_WRITE_OFFSET: + # page is either empty or full, but only the page with + # valid read/write pointers knows if we have data + self.iter_data = self.data[self.page_header_size:] + logging.debug("\tfull/empty case") + elif self.write_offset == FULL_PAGE_WRITE_OFFSET: + # page is full + read_iter = self.read_offset + self.page_header_size + self.iter_data = self.data[read_iter:] + logging.debug("\tfull case") + elif self.read_offset == FULL_PAGE_WRITE_OFFSET: + # page hasn't been read, but has only been partially written + write_iter = self.write_offset + self.page_header_size + self.iter_data = self.data[self.page_header_size:write_iter] + logging.debug("\tpartial write, no read") + elif self.read_offset >= self.write_offset: + # almost full buffer + read_iter = self.read_offset + self.page_header_size + write_iter = self.write_offset + self.page_header_size + self.iter_data = self.data[read_iter:] + self.previousData = self.data[self.page_header_size:write_iter] + + if self.page_num == 0 and self.read_offset == self.write_offset: + self.iter_data = self.data[self.page_header_size:] + self.previousData = None + logging.debug("\talmost full - first page offsets equal") + + logging.debug("\talmost full") + else: + # almost empty buffer + read_iter = self.read_offset + self.page_header_size + write_iter = self.write_offset + self.page_header_size + self.iter_data = self.data[read_iter:write_iter] + logging.debug("\talmost empty") + + # prepend data from previous page + if self.previousPage and self.previousPage.nextData: + logging.debug("Pulling previous data len") + logging.debug(hex_to_str(self.previousPage.nextData, 16, 10)) + self.iter_data = self.previousPage.nextData + self.iter_data + + return PageIterator(self) + + def next(self): + # handles full-buffer condition where write pointer + # is behind the read pointer + if self.nextPage and self.nextPage.previousData: + self.iter_data += self.nextPage.previousData + self.nextPage.previousData = None + + if len(self.iter_data) < 2: + # not enough data to retrieve message header + logging.debug("Pushing data to continue\n%s" % \ + (hex_to_str(self.iter_data, 16, 10))) + self.nextData = self.iter_data + self.iter_data = None + raise StopIteration + + while True: + try: + tsv = LogTSV() + data = tsv.unpack(self.iter_data) + if tsv.msg_size > len(data): + # not enough data left to extract entire message + logging.debug("Pushing data to continue\n%s" % \ + (hex_to_str(self.iter_data, 16, 10))) + self.nextData = self.iter_data + self.iter_data = None + raise StopIteration + + # TODO - this needs to be replaced with a dictionary for the + # message deserialization types for custom deserialization + # functions + if tsv.msg_type == 0: + # no more messages + raise StopIteration + + if tsv.msg_type == TSV_TYPE_STRING: + self.iter_data = data + msg = LogMessageString() + self.iter_data = msg.unpack(self.iter_data, tsv) + return msg + else: + debug_str = "Unknown message type 0x%x\n%s" % \ + (tsv.msg_type, hex_to_str(self.iter_data, 16, 10)) + + logging.debug(debug_str) + assert False, debug_str + + except StopIteration: + raise + +class LogPageVersionUnknown(object): + """ + This class is used before the version of the log being parsed is known. It + only unpacks the magic numbers and the page number from the data that is + found by :func:`cmdParse()`, in order to determine the version. + + This class can only be used to discover whether the log is a version 0 log + or a version 1 (or greater) log. See :meth:`isVersionOneOrGreater()` for + more details. + """ + def __init__(self): + self.data = None + self.version = None + self.headerMagicFormat = '<II' + self.headerMagicFormatSize = struct.calcsize(self.headerMagicFormat) + self.versionDataFormat = '<I' + self.versionDataFormatSize = struct.calcsize(self.versionDataFormat) + + def unpack(self, data): + """ + Partially unpacks a page structure in order to inspect the magic number. + This is a wrapper around the :func:`~struct.unpack()` function from the + :mod:`struct` module. + + :param data: The binary data to be unpacked. + + :return: :const:`True` if the unpack is successful, :const:`False` if \ + not. + + **Side Effects**: The unpacked values are assigned to instance \ + variables of this LogPageVersionUnknown instance. + """ + self.data=data + + self.magic, self.nmagic = \ + struct.unpack(self.headerMagicFormat, + self.data[0:self.headerMagicFormatSize]) + + if self.magic == MAGIC and self.nmagic == NMAGIC: + return True + + return False + + def isVersionOneOrGreater(self, data): + """ + This method determines whether the log is version 0, or version 1 or + greater. The top bit of ``self.version_data`` will be 1 if the log + version is at least 1, and 0 if not. Note that this function should + only be called after calling :meth:`unpack()`. + + :return: :const:`True` if the version is 1 or greater, \ + :const:`False` otherwise. + + **Side Effects**: This method unpacks the version data and stores \ + the result in an instance variable. + """ + self.data=data + endSize = self.headerMagicFormatSize + self.versionDataFormatSize + version_data = \ + struct.unpack(self.versionDataFormat, + self.data[self.headerMagicFormatSize:endSize]) + self.version_data = version_data[0] + mask = 0x80000000 + result = (self.version_data & mask) >= 0x80000000 + return result + +class LogPage_v0(LogPage): + """ + A single page in a version 0 IPC Logging log. This class is a descendant of + :class:`LogPage`. + + .. code-block:: c + + struct ipc_log_page_header { + uint32_t magic; + uint32_t nmagic; /* inverse of magic number */ + uint32_t log_id; /* owner of log */ + uint32_t page_num; + uint16_t read_offset; + uint16_t write_offset; + struct list_head list; + }; + """ + def __init__(self): + # Call the parent constructor + super(self.__class__, self).__init__() + self.headerBinaryFormat = '<IIIIHH' + self.headerBinaryFormatSize = struct.calcsize(self.headerBinaryFormat) + + def sortAndLink(self, lstPages, bSort): + """ + Given a list of pages in ascending page number order, + sort them chronologically and link the pages together. + This must be called before iterating over items in a page. + + :param lstPages: The list of pages to be sorted + :param bSort: A boolean indicating whether the logs are full or \ + partially full. If they are full, bSort is :const:`True`. + + :return: The sorted and linked list of pages. + + **Side Effects**: If the starting page cannot be found, this \ + method will fail an assertion. + """ + # Convert lstPages deque to a regular Python list + lstPages = list(lstPages) + + # Sort pages chronologically by finding the first active page. + # Since the pages are filled in order by page-id, the first + # non-full page is the last page written to. + if bSort: + for n in range(len(lstPages)): + if lstPages[0].write_offset != FULL_PAGE_WRITE_OFFSET: + break + lstPages.append(lstPages.pop(0)) + assert lstPages[0].write_offset != FULL_PAGE_WRITE_OFFSET, \ + "Unable to find starting page" + + # link pages + numPages = len(lstPages) + nMaxPage = 0 + for n in range(numPages): + page = lstPages[n] + nMaxPage = max(nMaxPage, page.page_num) + page.previousPage = lstPages[n - 1] + page.nextPage = lstPages[(n + 1) % numPages] + page.iterated = False + assert (nMaxPage + 1) == numPages, \ + "Missing log pages: max page %d, num pages %d" % \ + (nMaxPage, numPages) + + return lstPages + + def log_info(self): + """ + If DEBUG logging is turned on (command line parameter -v), logs the + instance variables of this LogPage_v0 to the output file. See the Python + :mod:`logging` module documentation and the IPC Logging main function + for more info on logging. + + This method only logs the ``read_offset`` and + ``write_offset`` variables as the others are logged elsewhere. + """ + logging.debug("\tread index: %x" % (self.read_offset)) + logging.debug("\twrite index: %x" % (self.write_offset)) + + def debug_save_log_pages(self, fIn, filename, data, options): + """ + Writes a binary file containing the data for the current page. This + is a helper to :func:`cmdParse`, and is particularly useful in + debugging. + + :param fIn: The input file currently being processed by the caller + :param filename: The name of the input file currently being processed + :param data: The binary data for the current page + :param options: Configuration options containing options.output_dir for + the output directory + + **Side Effects**: Writes an output file containing the binary data \ + for the current page. + """ + # Get just the filename, instead of the whole path. + # Otherwise the file open call will fail. + filename = os.path.split(filename)[1] + + strDebugFileName = 'log-%d-%d-%s-%u.bin' % (self.log_id, self.page_num, + filename, fIn.tell() - PAGE_SIZE) + + if options.output_dir: + strDebugFileName = \ + os.path.join(options.output_dir, strDebugFileName) + + logging.info("Writing debug file '%s'\n", strDebugFileName) + fOut = open(strDebugFileName, 'wb') + fOut.write(data) + fOut.close() + + def unpack(self, data): + """ + Unpacks an ipc_log_page version 0 structure, and assigns the contents + into instance variables of this class. This is a wrapper around + the :func:`~struct.unpack()` function from the :mod:`struct` module. + + :param data: The binary ipc_log_context structure to be unpacked. + + :return: :const:`True` if the unpack completes successfully, \ + :const:`False` if not. + + **Side Effects**: If the unpack is successful, the contexts are \ + assigned to instance variables. In this way, :meth:`unpack()` \ + acts as a sort of constructor. This is common to all the \ + unpack methods. + """ + self.data = data + self.magic, self.nmagic, self.log_id, self.page_num, self.read_offset, \ + self.write_offset = struct.unpack(self.headerBinaryFormat, + self.data[0:self.headerBinaryFormatSize]) + + # Mask off the top bit of page_num, which is used to indicate + # a version 1 or greater page + mask = 0x7FFFFFFF + self.page_num = self.page_num & mask + + if self.magic == MAGIC and self.nmagic == NMAGIC: + return True + + return False + + def pack(self, nLogId, nPageNum, nReadOffset, nWriteOffset, strPayload): + """ + Packs a new version 0 page structure. *strPayload* can be empty and + additional payload can be added using :meth:`~LogPage.pack_continue()`. + + :param nLogId: The log ID to be packed into the new structure + :param nPageNum: The page number + :param nReadOffset: The read offset + :param nWriteOffset: The write offset + :param strPayload: The payload of the new structure + + :return: Overflow data (*strPayload* that didn't fit into the page) or \ + :const:`None` if all fit. + """ + data = struct.pack(self.headerBinaryFormat, MAGIC, NMAGIC, + nLogId, nPageNum, nReadOffset, nWriteOffset) + + # add padding for list structures + data += '\0' * (self.page_header_size - len(data)) + + # append message payload + self.data = data + strPayload or '' + + if len(self.data) > PAGE_SIZE: + data = self.data[PAGE_SIZE:] + self.data = self.data[0:PAGE_SIZE] + return data + else: + return None + +class LogPage_v1(LogPage): + """ + A single page in a version 0 IPC Logging log. This class is a descendant of + :class:`LogPage`. + + .. code-block:: c + + struct ipc_log_page_header { + uint32_t magic; + uint32_t nmagic; + uint32_t page_num; + uint16_t read_offset; + uint16_t write_offset; + uint64_t log_id; + uint64_t start_time; + uint64_t end_time; + int64_t context_offset; + + /* add local data structures after this point */ + struct list_head list; + uint16_t nd_read_offset; + }; + """ + def __init__(self): + # Call the parent constructor + super(self.__class__, self).__init__() + self.headerBinaryFormat = '<IIIHHQQQq' + self.headerBinaryFormatSize = struct.calcsize(self.headerBinaryFormat) + self.context = LogContext() + self.context.header_size = V1_PAGE_HDR_SIZE + self.page_header_size = self.context.header_size + + def sortAndLink(self, lstPages, bSort): + """ + Given a list of pages in ascending page number order, + sort them chronologically and link the pages together. + This must be called before iterating over items in a page. + + :param lstPages: The list of pages to be sorted + :param bSort: A boolean indicating whether the logs are full or \ + partially full. If they are full, bSort is :const:`True`. + + :return: The sorted and linked list of pages. + + **Side Effects**: If the starting page cannot be found, this \ + method will fail an assertion. + """ + # Sort pages chronologically by finding the first active page. + # Since the pages are filled in order by page-id, the first + # non-full page is the last page written to. + if bSort: + # Rotate to lowest non-zero end time + min_end_time = None + min_start_time = None + min_end_position = 0 + min_start_position = 0 + for n in range(len(lstPages)): + cur_end_time = lstPages[n].get_end_time() + if cur_end_time == 0: + continue + + if not min_end_time: + min_end_time = cur_end_time + min_end_position = n + continue + + if cur_end_time > 0 and cur_end_time < min_end_time: + min_end_time = cur_end_time + min_end_position = n + + min_position = min_end_position + if lstPages[min_end_position].read_offset == 0: + for n in range(len(lstPages)): + cur_start_time = lstPages[n].get_start_time() + if cur_start_time == 0: + continue + + if not min_start_time: + min_start_time = cur_start_time + min_start_position = n + continue + + if cur_start_time > 0 and cur_start_time < min_start_time: + min_start_time = cur_start_time + min_start_position = n + + if lstPages[min_start_position].read_offset != 0: + min_position = min_start_position + + lstPages.rotate(-min_position) + lstPages = list(lstPages) + + # link pages + numPages = len(lstPages) + nMaxPage = 0 + for n in range(numPages): + page = lstPages[n] + nMaxPage = max(nMaxPage, page.page_num) + page.previousPage = lstPages[n - 1] + page.nextPage = lstPages[(n + 1) % numPages] + assert (nMaxPage + 1) == numPages, \ + "Missing log pages: max page %d, num pages %d" % \ + (nMaxPage, numPages) + + return lstPages + + def log_info(self): + """ + If DEBUG logging is turned on (command line parameter -v), logs the + instance variables of this LogPage_v1 to the output file. See the Python + :mod:`logging` module documentation and the IPC Logging main function + for more info on logging. + + This method logs the ``read_offset``, ``write_offset``, ``start_time``, + ``end_time``, and ``context_offset`` variables. + """ + logging.debug("\tread index: %x" % (self.read_offset)) + logging.debug("\twrite index: %x" % (self.write_offset)) + logging.debug("\tstart_time (seconds): %f" % (self.start_time * math.pow(10, -9))) + logging.debug("\tend_time (seconds): %f" % (self.end_time * math.pow(10, -9))) + logging.debug("\tcontext_offset: %x" % (self.context_offset)) + + def debug_save_log_pages(self, fIn, filename, data, name, options): + """ + If DEBUG logging is turned on, writes a binary file containing the data + for the current page. This is a helper to :func:`cmdParse`. + + :param fIn: The input file currently being processed by the caller + :param filename: The name of the input file currently being processed + :param data: The binary data for the current page + :param options: Configuration options containing options.output_dir for + the output directory + + **Side Effects**: Writes an output file containing the binary data \ + for the current page. + """ + # Get just the filename, instead of the whole path. + # Otherwise the file open call will fail. + filename = os.path.split(filename)[1] + + strDebugFileName = 'log-%s-%d-%s-%u.bin' % (name, self.page_num, + filename, fIn.tell() - PAGE_SIZE) + + if options.output_dir: + strDebugFileName = \ + os.path.join(options.output_dir, strDebugFileName) + + logging.info("Writing debug file '%s'\n", strDebugFileName) + fOut = open(strDebugFileName, 'wb') + fOut.write(data) + fOut.close() + + def unpack(self, data): + """ + Unpacks an ipc_log_page version 1 structure, and assigns the contents + into instance variables of this class. This is a wrapper around + the :func:`~struct.unpack()` function from the :mod:`struct` module. + + :param data: The binary ipc_log_context structure to be unpacked. + + :return: :const:`True` if the unpack completes successfully, \ + :const:`False` if not. + + **Side Effects**: If the unpack is successful, the contexts are \ + assigned to instance variables. In this way, :meth:`unpack()` \ + acts as a sort of constructor. This is common to all the \ + unpack methods. + """ + self.data=data + self.magic, self.nmagic, self.page_num, self.read_offset, \ + self.write_offset, self.log_id, self.start_time, \ + self.end_time, self.context_offset = \ + struct.unpack(self.headerBinaryFormat, + self.data[0:self.headerBinaryFormatSize]) + + # Mask off the top bit of page_num, which is used to indicate + # a version 1 or greater page + mask = 0x7FFFFFFF + self.page_num = self.page_num & mask + + if self.magic == MAGIC and self.nmagic == NMAGIC: + return True + + return False + + def pack(self, nPageNum, nReadOffset, nWriteOffset, nLogId, + nStartTime, nEndTime, nContextOffset, strPayload): + """ + Packs a new version 1 page structure. *strPayload* can be empty and + additional payload can be added using :meth:`~LogPage.pack_continue()`. + + :param nPageNum: The page number to be packed into the new structure + :param nReadOffset: The read offset + :param nWriteOffset: The write offset + :param nLogId: The log ID + :param nStartTime: The start time + :param nEndTime: The end time + :param nContextOffset: The context offset + :param strPayload: The payload of the new structure + + :return: Overflow data (*strPayload* that didn't fit into the page) or \ + :const:`None` if all fit. + """ + + data = struct.pack(self.headerBinaryFormat, MAGIC, NMAGIC, + nPageNum, nReadOffset, nWriteOffset, nLogId, nStartTime, + nEndTime, nContextOffset) + + # add padding for list structures + data += '\0' * (self.page_header_size - len(data)) + + # append message payload + self.data = data + strPayload or '' + + if len(self.data) > PAGE_SIZE: + data = self.data[PAGE_SIZE:] + self.data = self.data[0:PAGE_SIZE] + return data + else: + return None + + def setContext(self, context): + """ + Sets the ``context`` field of this log page to *context*, and sets the + ``page_header_size`` field to the context header size + (``context.header_size``). This method should be used whenever a page's + context is changed in order to make sure the page's information about + its context is consistent. + + :param context: The :class:`LogContext` object to be assigned to this \ + page + """ + self.context = context + self.page_header_size = self.context.header_size + + #@staticmethod + def get_end_time(self): + return self.end_time + + #@staticmethod + def get_start_time(self): + return self.start_time + +class PageIterator(object): + """ + An iterator object for use by the :class:`LogPage` iterator method. + """ + + def __init__(self, page): + self.page = page + + def __iter__(self): + # TODO - clear all iteration flags + logging.debug("Starting iteration through page %d" % \ + (self.page.page_num)) + + def next(self): + while True: + try: + return self.page.next() + except StopIteration: + logging.debug("Trying next page") + self.page = self.page.nextPage + if self.page.iterated: + logging.debug("Page %d already iterated" % \ + (self.page.page_num)) + raise + logging.debug("Iterating through page %d" % \ + (self.page.page_num)) + + # This isn't right. The page iterator is just discarded + # and then we are calling the .next. + self.page.__iter__() + +def dumpLogHelper(fout, pages, numPages, bSort): + """ + Handles iterating through the TSV messages in each :class:`LogPage` object + and dumping them to the output file for the current log ID. This function + is called by :func:`dumpLogWithRetry()`. + + :param fout: The output file for the log currently being processed by \ + :func:`dumpLogWithRetry()`. + :param pages: A list containing the :class:`LogPage` objects for the log \ + currently being processed. + :param numPages: The number of pages in *pages*. + :param bSort: A boolean indicating whether all pages in the log are \ + completely filled, or if some are partially full. If they are \ + full, bSort is :const:`True`. + + :return: :const:`True` if the messages are processed with no errors, \ + :const:`False` if errors are encountered during iteration or \ + during parsing of the TSV messages within the :class:`LogPage` \ + objects. + """ + # sort pages chronologically + try: + lstPages = collections.deque() + presentKey = None + + for key in pages.keys(): + if not presentKey: + presentKey = key + lstPages.append(pages[key]) + + lstPages = pages[presentKey].sortAndLink(lstPages, bSort) + except AssertionError as e: + strMsg = "Exception: %s" % (str(e)) + logging.error(strMsg) + fout.write(strMsg + '\n') + return False + + # dump data in pages + try: + page = lstPages[0] + logging.debug("Extracting messages from %d" % (page.page_num)) + last_unknown_data = '' + for x in page: + if page.unknown_messages_data and \ + page.unknown_messages_data != last_unknown_data: + fout.write(str(page.unknown_messages_data) + "\n") + last_unknown_data = page.unknown_messages_data + + fout.write("\n" + str(x).strip()) + + except AssertionError as e: + strMsg = "Exception: %s" % (str(e)) + logging.debug(strMsg) + fout.write(strMsg + '\n') + return False + + return True + +def dumpLogWithRetry(logId, name, version, pages, numPages, test, options): + """ + This function is called by :func:`dumpLogs()` and :func:`cmdTest()`. It + handles creating output files for the parsed logs and writing data to them. + A significant part of this work is delegated to :func:`dumpLogHelper()`. + + :param logId: The log ID currently being processed + :param name: The name of the log context + :param version: The version of the log + :param pages: List of pages to process and dump to the output file + :param numPages: The number of pages in the current log + :param test: True if this function was called by cmdTest + :param options: Configuration options containing options.output_dir for the + output directory + """ + if version >= 1: + strFileName = "ipc-log-%s.txt" % (str(name)) + titleStr = "Log name: %s (%d pages)\n" % (str(name), numPages) + else: + strFileName = "ipc-log-%s.txt" % (str(logId)) + titleStr = "Log id %s (%d pages)\n" % (str(logId), numPages) + + if test: + strFileName = "ipc-log-%s.txt" % (str(name)) + + if options.output_dir: + strFileName = \ + os.path.join(options.output_dir, strFileName) + + fout = open(strFileName, 'w') + logging.info("Writing log file '%s'" % (strFileName)) + + fout.write('-'*70 + "\n") + fout.write(titleStr) + fout.write('-'*70 + "\n") + + # Parse logs assuming they have wrapped (are full) + fout.write("Parsing as a full log\n") + bSuccess = dumpLogHelper(fout, pages, numPages, True) + if not bSuccess: + # If that fails, try assuming that they are partially-full + strMsg = "Unable to parse as full log, retrying as partial log" + logging.info(strMsg) + fout.write(strMsg + '\n') + bSuccess = dumpLogHelper(fout, pages, numPages, False) + + + if bSuccess: + strMsg = "Log extracted successfully" + logging.info(strMsg) + fout.write('\n' + strMsg + '\n') + else: + strMsg = "Could not extract log successfully. See %s for details." % \ + (strFileName) + if version == 0: + v0_expl_str = \ +""" +You are parsing a v0 log. If you collected logs using debugfs before the system +crashed, it may have adversely affected the RAM dumps. Collect the logs again +without using debugfs, or use the newest version of IPC Logging if possible. +This issue has been resolved in v1 and above. +""" + fout.write(v0_expl_str) + logging.info(strMsg) + + # Add a newline to stdout output + logging.info("") + fout.close() + +def get_context(start_of_page, fIn, page, dictContexts, lstFiles, fileCount): + ''' + Called by :func:`cmdParse()` in order the find and read the context for + the log page currently being processed. The context is found by reading + *page.context_offset* and using it to find the position of the context in + the "logical file" created by :func:`get_files_info()`. Once the input file + and position where the context resides have been calculated, the context can + be read, assigned to the *page.context* field, and added to the log ID-to- + context dictionary *dictContexts* using the :func:`read_context()` function. + + :param start_of_page: The starting position in the current file of the \ + page currently being processed + :param fIn: The input file + :param page: The page currently being processed + :param dictContexts: The dictionary that maintains the \ + log ID-to-:class:`LogContext` mapping. + :param lstFiles: The "logical file" data structure, a list of dictionaries \ + containing the name, starting position, and ending position of \ + each input file. + :param fileCount: An index into *lstFiles*, which indicates the file \ + currently being processed. For example, if *lstFiles* contains \ + three input files and we are processing the second one, fileCount \ + is 1. + + :return: A :class:`LogContext` object containing the context that was \ + found. If no context is found, returns :const:`None`. + + **Side Effects**: There are some performance side effects in this \ + function, as :meth:`file.seek()`, :meth:`file.read()`, and \ + :meth:`file.tell()` are performed several times in order to \ + extract the context based on *page.context_offset*. Since there \ + may be multiple files and the context may not be in the current \ + file, the other files may need to be opened and closed in order \ + to find the context. \ + ''' + context = LogContext() + start_of_file = lstFiles[fileCount]['start'] + end_of_file = lstFiles[fileCount]['end'] + file_position = start_of_file + start_of_page + page.context_offset + data = None + + logging.debug("start of file: 0x%x, end of file: 0x%x, " + \ + "start of page: 0x%x, context offset: 0x%x, file position: 0x%x,", + start_of_file, end_of_file, start_of_page, page.context_offset, + file_position) + + for inFile in lstFiles: + if inFile['start'] <= file_position <= inFile['end']: + # If the position is in the current file, don't open and close a + # temporary file - use fIn + if inFile['start'] == start_of_file: + saved_position = fIn.tell() + fIn.seek(file_position - start_of_file) + data = fIn.read(CTX_READ_SIZE) + fIn.seek(saved_position) + else: + tempIn = open(inFile['name'], 'rb') + tempIn.seek(file_position - inFile['start']) + data = tempIn.read(CTX_READ_SIZE) + tempIn.close() + break + + assert data is not None, "Context not found" + + context = read_context(page, data, dictContexts) + return context + +def read_context(page, data, dictContexts): + ''' + Read a context from *data* and make it the context of *page* using + :meth:`LogPage_v1.setContext()`. Then update the log ID-to-context + dictionary (*dictContexts*), and return the context. In short, find + a log context in *data*, perform some bookkeeping, and return the context. + This is a helper function to :func:`get_context()`, which is a helper to + :func:`cmdParse()`. + + :param page: The page to which the found context will be assigned + :param data: The data to be read + :param dictContexts: The dictionary where the context will be placed, if \ + a new context is read + + :return: The new context if successful, None if not. + ''' + context = LogContext() + + if data and len(data) >= CTX_READ_SIZE and context.unpack(data): + page.setContext(context) + + if not context.log_id in dictContexts: + logging.info("Found log context: %s" % (context.name)) + context.log_info() + + # Log a single newline to the output + logging.info("") + dictContexts[page.log_id] = context + + return context + else: + return None + +def check_log_consistency(dictLogs, dictContexts): + ''' + Creates two sets: + + * The set of log IDs gained from reading each page + + * The set of log IDs gained from reading each context + + Takes the intersection and makes sure that the intersection + equals both sets, i.e. that all log IDs found from reading pages + were found reading contexts, and vice-versa. + + :param dictLogs: The dictionary of log IDs to :class:`LogPage` objects + :param dictContext: The dictionary of log IDs to :class:`LogContext` objects + + :return: The difference of the two sets, contained in a :class:`set`. + ''' + diff = None + context_log_ids = set(dictContexts.keys()) + page_log_ids = set(dictLogs.keys()) + intersection = context_log_ids.intersection(page_log_ids) + if intersection == page_log_ids == context_log_ids: + logging.debug("Logs are consistent - the same log IDs are " + \ + "found in pages and contexts.") + else: + logging.error("Logs inconsistent.") + logging.error("Logs from contexts: %s", + str([hex(n) for n in context_log_ids])) + logging.error("Logs from pages: %s", + str([hex(n) for n in page_log_ids])) + + if len(page_log_ids) > len(context_log_ids): + diff = page_log_ids - context_log_ids + logging.error("IDs from pages, but not contexts: %s", + str([hex(n) for n in diff])) + else: + diff = context_log_ids - page_log_ids + logging.error("IDs from contexts, but not pages: %s", + str([hex(n) for n in diff])) + return diff + +def calc_file_size(fIn): + """ + Calculates the size of an input file. + + :param fIn: The input file + + :return: The file size, which is also the ending position relative to the \ + start of the input file. + + **Side Effects**: This function uses :meth:`file.seek()` and \ + :meth:`file.tell()` to calculate the file size, which can have \ + performance considerations. The original seek position of the file \ + is saved at the beginning of the function and is restored before \ + returning. + """ + saved_file_position = fIn.tell() + fIn.seek(0, 2) + file_size = fIn.tell() + fIn.seek(saved_file_position) + return file_size + +def get_files_info(files): + """ + Calculates the size of each input file and uses this information to build + a "logical file" data structure, which is a list of dictionaries, with one + dictionary per input file. Each dictionary contains the name of the file, + the starting position of the file, the ending position of the file, and + the size of the file. The starting position of the second file in the list + is the ending position of the first file in the list, and so on. Once the + structure is completely built, it can be used to index any position in any + of the input files as if they were one big file, without reading this big + file into memory. This allows the retrieval of contexts located in a + different file than their pages. + + The file size calculation is done using the :func:`calc_file_size()` + function. + + :param files: The list of input files + + :return: The "logical file" data structure, a list of dictionaries \ + containing information about each of the input files + """ + lstFiles = [] + fileCount = 0 + for strFile in files: + fileStart = 0 + + if fileCount > 0: + fileStart = (lstFiles[fileCount-1]['end']) + + strFileSplit = strFile.split(':', 1) + fileName = strFileSplit[0] + if len(strFileSplit) > 1: + fileStart = int(strFileSplit[1], 16) + + lstFiles.append(\ + {'name': fileName, 'start': fileStart, 'end': 0, 'size': 0}) + fIn = open(fileName, 'rb') + file_size = calc_file_size(fIn) + + lstFiles[fileCount]['end'] = fileStart + file_size + lstFiles[fileCount]['size'] = file_size + + fileCount += 1 + + return lstFiles + +def cmdParse(options): + """ + The top-level function, which is called from the main entry point to the + program if the ``parse`` command-line option is given. This function creates + two dictionaries, ``dictLogs`` and ``dictContexts``, which have log IDs as + keys and :class:`LogPage` and :class:`LogContext` objects as values, + respectively. It then populates these dictionaries by going through + each input file and reading data page-by-page. After all data has been read, + this function calls :func:`dumpLogs()` to dump the parsed logs to output + files. + + :param options: Configuration options containing options.output_dir for the + output directory, options.debug_enabled, a flag for + discerning whether the extraction script is running in debug + mode, and options.args, the list of input files. All + configuration options are passed through to helper + functions. + """ + dictLogs = {} + dictContexts = {} + fileCount = 0 + version = 0 + versionIsOneOrGreater = False + lstFiles = get_files_info(options.args) + + # scan files for log pages + for strFile in options.args: + fileName = strFile.split(':', 1)[0] + fIn = open(fileName,'rb') + + logging.info("Parsing '%s'" % (fileName)) + logging.info("-" * 70) + sys.stdout.flush() + page = LogPageVersionUnknown() + context = LogContext() + file_size = lstFiles[fileCount]['size'] + + while True: + start_of_page = fIn.tell() + data = fIn.read(16) + + # PAGE_SIZE - 16 = 4080 + if not data or len(data) + 4080 < PAGE_SIZE: + break + + if page.unpack(data): + data += fIn.read(PAGE_SIZE-16) + + if page.isVersionOneOrGreater(data): + versionIsOneOrGreater = True + page = LogPage_v1() + else: + versionIsOneOrGreater = False + page = LogPage_v0() + + if page.unpack(data): + if versionIsOneOrGreater: + if not page.log_id in dictContexts: + try: + context = get_context(start_of_page, fIn, page, + dictContexts, lstFiles, fileCount) + except: + msg = "Context not found - skipping page " + \ + "and trying to " + \ + "continue with unknown log page version" + logging.debug(msg) + page = LogPageVersionUnknown() + continue + + else: + context = dictContexts[page.log_id] + page.setContext(context) + + if context is None: + logging.debug("Context at 0x%x had no data, " + \ + "skipping this page", page.context_offset) + if options.debug_enabled: + page.log_info() + page = LogPageVersionUnknown() + continue + + version = context.version + + if not page.log_id in dictLogs: + dictLogs[page.log_id] = {} + + if dictLogs[page.log_id].has_key(page.page_num): + logging.error("Duplicate page found log id 0x%x," + \ + "page %d", page.log_id, page.page_num) + + dictLogs[page.log_id][page.page_num] = page + + if versionIsOneOrGreater: + logging.info(\ + "Found log page - context: %s, id: 0x%x, " + \ + "page: %d", page.context.name, \ + page.log_id, page.page_num) + page.log_info() + sys.stdout.flush() + else: + logging.info("Found log page: id 0x%x, page %d" % \ + (page.log_id, page.page_num)) + page.log_info() + sys.stdout.flush() + + # for debug mode, save the extracted log pages + if options.debug_enabled: + if version >= 1: + page.debug_save_log_pages(fIn, fileName, data, + context.name, options) + else: + page.debug_save_log_pages(fIn, fileName, data, + options) + + page = LogPageVersionUnknown() + + fIn.close() + fileCount += 1 + + # Check that log_ids received from contexts and from pages match + if versionIsOneOrGreater: + check_log_consistency(dictLogs, dictContexts) + dumpLogs(dictLogs, version, options) + else: + dumpLogs(dictLogs, 0, options) + + logging.debug("lstFiles: " + str(lstFiles)) + +def dumpLogs(dictLogs, version, options): + """ + Dump logs from the dictionary of log IDs to logs built by + :func:`cmdParse()`. This is called at the end of :func:`cmdParse()`, + after all files have been processed and the dictionary has been built. + + :param dictLogs: The dictionary built by :func:`cmdParse()`. + :param version: The IPC Logging version. + :param options: Configuration options passed through to helper functions + + **Side Effects**: The :func:`dumpLogWithRetry()` function is called, \ + which dumps the parsed logs to output files. + """ + # dump the logs + logs = dictLogs.keys() + logs.sort() + logging.debug("Logs: %s", logs) + for key in logs: + pages = dictLogs[key] + numPages = len(pages.keys()) + + if version >= 1: + first_page_in_log = dictLogs[key].keys()[0] + name = dictLogs[key][first_page_in_log].context.name + else: + name = None + + dumpLogWithRetry(key, name, version, pages, numPages, False, options) + +def cmdTest(options): + """ + Parses the provided log page files in order and extracts the logs. This is + useful for testing and for dealing with failure cases (such as duplicate + logs due to left-over log pages from previous boot cycles). + + :param options: Configuration options containing options.output_dir for + the output directory, and options.args, the list of input + files, and options.arch_64, the flag indicating if the log + pages should be interpreted as 64-bit dumps or not. All + configuration options are passed through to helper + functions. + """ + dictPages = {} + version = 0 + numPages = 0 + + # load existing files + lstFiles = options.args + + try: + version = int(lstFiles.pop()) + except ValueError as e: + strMsg = "Version must be provided! Exiting..." + logging.error("Exception: %s\n%s\n" % (str(e), strMsg)) + raise + + for f in lstFiles: + data = open(f, 'rb').read() + + page = LogPageVersionUnknown() + assert page.unpack(data) == True, "Unable to open file '%s'" % (f) + + if page.isVersionOneOrGreater(data): + page = LogPage_v1() + page.unpack(data) + else: + page = LogPage_v0() + page.unpack(data) + + numPages += 1 + dictPages[page.page_num] = page + logging.info("Loaded '%s' log id %d, page %d" % \ + (f, page.log_id, page.page_num)) + page.log_info() + + try: + if options.arch_64: + page.page_header_size = PAGE_HDR_SIZES_64[version] + else: + page.page_header_size = PAGE_HDR_SIZES[version] + except IndexError as e: + strMsg = "Invalid version! Exiting..." + logging.error("Exception: %s\n%s\n" % (str(e), strMsg)) + raise + + # Use only the last pathname component, as the rest of the path is + # added in dumpLogWithRetry() + if options.output_dir: + filename = os.path.split(lstFiles[0])[1] + else: + filename = lstFiles[0] + + # dump the logs (in the same order provided) + dumpLogWithRetry(page.log_id, 'test-log-%s' % (filename), + version, dictPages, numPages, True, options) + +class LoggingFormatter(logging.Formatter): + """ + Custom logging formatter that prints all INFO messages without formatting + and all other messages with the appropriate level name. + + See :mod:`logging` documentation for more info on Python logging. + """ + infoFormat = '%(message)s' + defaultFormat = '%(levelname)s %(module)s:%(lineno)d: %(message)s' + + def __init__(self, fmt=defaultFormat): + logging.Formatter.__init__(self, fmt) + + def format(self, record): + if record.levelno == logging.INFO: + self._fmt = self.infoFormat + else: + self._fmt = self.defaultFormat + return logging.Formatter.format(self, record) + +def configure_logging(options, stdout): + """ + Configure the logging options. + + :param options: The options object, which contains options.verbosity for + the logging verbosity and options.debug_enabled to indicate + whether the extraction script is running in debug mode. + :param stdout: Whether or not to create a logging handler for stdout + """ + + if stdout: + loggingHandler = logging.StreamHandler(sys.stdout) + loggingHandler.setFormatter(LoggingFormatter()) + logging.root.addHandler(loggingHandler) + + # Set Log level + LOG_LEVELS = { 3: logging.DEBUG, + 2: logging.INFO, + 1: logging.WARNING, + 0: logging.ERROR, + } + assert LOG_LEVELS.has_key(options.verbosity), "Unknown log level %d" % \ + (options.verbosity) + logging.root.setLevel(LOG_LEVELS[options.verbosity]) + + if logging.root.getEffectiveLevel >= 2 and options.quiet: + logging.root.setLevel(LOG_LEVELS[1]) + + options.debug_enabled = False + if options.verbosity >= 3: + options.debug_enabled = True + +def set_output_directory(options): + """ + Set up the output directory. + + :param options: Configuration options containing options.output_dir for the + output directory + + :return: The output logging handler object, or None if no output directory + was provided by the user. + """ + if not options.output_dir: + return None + + options.output_dir = \ + os.path.abspath(os.path.expanduser(options.output_dir)) + + # Create output directory if it does not exist + if not os.path.isdir(options.output_dir): + os.makedirs(options.output_dir) + + # Create logging handler to take output in this directory + output_logging_handler = \ + logging.FileHandler(\ + os.path.join(options.output_dir, "ipc_extraction_report.txt"), + "w") + output_logging_handler.setFormatter(LoggingFormatter()) + logging.root.addHandler(output_logging_handler) + + logging.info("Output path: " + options.output_dir) + return output_logging_handler + +#------------------------------------------------------------------------ +# Main Program Entry Point +#------------------------------------------------------------------------ +if __name__ == '__main__': + # Parse command line + strUsage = """\ +%prog CMD [OPTIONS] + +parse +----- +%prog parse MEMORY_FILE(s) +%prog parse MEMORY_FILE MEMORY_FILE_2:<starting address> + +Parses the provided input files (memory dump files) and extracts the logs. + +Examples: +\t%prog parse DDRCS0.BIN DDRCS1.BIN +\t%prog parse DDRCS0_0.BIN DDRCS0_1.BIN DDRCS1_0.BIN DDRCS1_1.BIN +\t%prog parse DDRCS0_0.BIN DDRCS0_1.BIN DDRCS1_0.BIN:0x80000000 DDRCS1_1.BIN +\t%prog parse DDRCS0_0.BIN:0x00000000 DDRCS0_1.BIN:0x30000000 DDRCS1_0.BIN:0x80000000 DDRCS1_1.BIN:0xb0000000 + +It is only necessary to provide the starting address of a memory dump file if +the dumps are not contiguous in memory. Whether or not the dumps are contiguous, +along with their starting addresses, can be found in the file load.cmm which +is usually included in crash dumps. Starting addresses must be provided in +hexadecimal. + +test +---- +%prog test [--64-bit] LOG_PAGE_FILE(s) VERSION + +Parses the provided log pages files in order and extracts the logs. This is +useful for testing and for dealing with failure cases (such as duplicate logs +due to left-over log pages from previous boot cycles). + +The test command must be provided with the IPC Logging version in order to +determine the right log page header size to use when parsing. During normal +parsing, this information is gained from the log context, which is not provided +to the test command. + +The --64-bit option denotes whether or not to interpret the provided log pages +as 64-bit dumps. This is also necessary to determine the log page header size. + +Examples: +\t%prog test log-1-0.bin log-1-1.bin log-1-2.bin 0 +\t%prog test --64-bit log-1-0.bin log-1-1.bin log-1-2.bin 1 +\t%prog test log-1-0.bin log-1-1.bin log-1-2.bin 1 + +=================================================== +General Options +=================================================== + +Verbosity +--------- +Debug logging can be enabled using the count -v or --verbose option. The +log level based upon the number of occurrences of the -v option will be one of +the following: + ERROR/WARNING/INFO Default + DEBUG (-v) + +Quiet +----- +Logging can be turned off entirely using this option. + +Example: +\t%prog parse -q DDRCS0.BIN DDRCS1.BIN + +Output Directory +---------------- +All output can be redirected to another directory, using -o or --output. +This is helpful if the memory dump files you're parsing are in a location that +is read-only. Use the -o, or --output, option, and provide the path to an +output directory. The ipc_logging script will create the directory if it +doesn't exist. All output will go to that directory. The script's own logging +will go to stdout as well as a file in the specified directory. + +64-bit +------ +This option is only to be used with the test command. It is a no-op otherwise. +If used with the test command, the test command will interpret the log pages +given to it as 64-bit dumps. + +Examples: +\t%prog parse -o ~/test_output DDRCS0.BIN DDRCS1.BIN +\t%prog test -v -o ~/test_output LOG_PAGE_FILE(s) +""" + parser = OptionParser(usage=strUsage) + if len(sys.argv) < 2: + parser.error('Command required') + + strCmd = (sys.argv[1]).lower() + + parser.add_option("-v", "--verbose", + action="count", dest="verbosity", default=2, + help="Log program status to stdout (-v = debug)" + ) + + parser.add_option("-q", "--quiet", + action="count", dest="quiet", default=False, + help="Log no program status to stdout (-q = quiet)" + ) + + parser.add_option("-o", "--output", + action="store", type="string", dest="output_dir", + help="If the current directory can't be written to, specify \ + a path for an output directory. The path is \ + OS-independent, and can be absolute or relative.") + + parser.add_option("", "--64-bit", + action="store_true", dest="arch_64", + help="For use with the test command. Interpret the log pages as \ + 64-bit dumps.") + + (options, args) = parser.parse_args(sys.argv[2:]) + options.cmd = strCmd + + if args: + # extract positions args (args not associated with flags) + options.args = args + + # Configure logging format + configure_logging(options, True) + + if not options.cmd or options.cmd == "help": + parser.error('No command/help specified') + exit(1) + + output_logging_handler = set_output_directory(options) + + dictCmds = {'parse': cmdParse, + 'test': cmdTest, + } + + # run the requested command + if not dictCmds.has_key(options.cmd): + parser.error("Unknown command '%s'" % (options.cmd)) + + dictCmds[options.cmd](options) + + if options.output_dir: + output_logging_handler.close() diff --git a/linux-ramdump-parser-v2/parsers/ipc_logging_ramparse.py b/linux-ramdump-parser-v2/parsers/ipc_logging_ramparse.py new file mode 100644 index 0000000000000000000000000000000000000000..f3d37aabbd9b5ca78112acd133e85d2ac585dbc3 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/ipc_logging_ramparse.py @@ -0,0 +1,163 @@ +""" +Copyright (c) 2015, The Linux Foundation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +from print_out import print_out_str +from parser_util import register_parser, RamParser +import ipc_logging +import os +import sys + +LOG_DEBUG = 3 +LOG_INFO = 2 +LOG_WARNING = 1 +LOG_ERROR = 0 + +strUsage = """\ +python ramparse.py --print-ipc-logging <options> + +Normal Usage +------------ +IPC Logging Extraction can be performed by providing ramdump files to the Linux +Ramdump Parser in the normal fashion. The output is written into the ipc_logging +directory, which is created automatically if it doesn't exist. The IPC Logging +Extraction module's internal logging can be found in +ipc_logging/logging_output.txt. + +skip +----- +If you're parsing everything using --everything or -x and you wish to skip IPC +log extraction, use the --ipc-skip option. + +Example: +\tpython ramparse.py <args> --everything --ipc-skip + +debug +----- +To extract the IPC logs in debug mode, use the --ipc-debug flag. This will +produce the binary of each individual log page, which can be given to the test +command (see below). It will also turn on debug logging. + +Example: +\tpython ramparse.py <args> --ipc-debug + +test +---- +To use the "test" functionality of the IPC Logging Extraction module, provide +the --ipc-test option for each file to test and the IPC Logging version. These +files must be log pages generated by extracting IPC logs in debug mode +(--ipc-debug). + +Be sure to use the --64-bit option correctly. The --ipc-test command will fail +if this option is used incorrectly. This is because the start of the actual page +data depends upon the size of internal data structures and pointers that can +vary with machine size. This information is stored in the logging context for +automated extraction (i.e. the "parse" command), but needs to be provided +manually for the "test" command. + +Examples: +\tpython ramparse.py <args> --print-ipc-logging --ipc-test log_page_1.bin --ipc-test log_page_2.bin --ipc-test 0 +\tpython ramparse.py <args> --64-bit --print-ipc-logging --ipc-test log_page_1.bin --ipc-test log_page_2.bin --ipc-test 1 + +help +---- +To write this help message, use the --ipc-help option. + +Example: +\tpython ramparse.py <args> --ipc-help +""" + +class IPCLoggingOptions: + """ + This class emulates the options object built by optparse in ipc_logging. + """ + def __init__(self): + self.args = [] + self.debug_enabled = False + self.output_dir = "./ipc_logging" + self.verbosity = LOG_INFO + self.quiet = False + self.cmd = "parse" + self.arch64 = False + +@register_parser('--print-ipc-logging', 'Print all the IPC information') +class IPCParse(RamParser): + def parse(self): + options = IPCLoggingOptions() + + if self.ramdump.ipc_log_skip: + msg = "Skipping IPC log extraction." + if self.ramdump.use_stdout: + sys.stderr.write(msg + "\n") + else: + sys.stderr.write(msg + " ") + return + + if self.ramdump.ipc_log_test: + # Do not iterate over the last element in the list as it contains + # the version and will fail the os.path.exists check + for a in self.ramdump.ipc_log_test[:-1]: + if not os.path.exists(a): + msg = \ + "IPC Test file {0} does not exist".format(a) + raise Exception(msg) + + options.cmd = "test" + options.args = self.ramdump.ipc_log_test + options.arch_64 = self.ramdump.arm64 + + if options.cmd == 'parse': + start_sorted_files = self.ramdump.ebi_files + start_sorted_files.sort(key=lambda ram_file_tup: ram_file_tup[1]) + for ram_file_tuple in start_sorted_files: + fd, start, end, path = ram_file_tuple + options.args.append(str(path)+':'+str(start)) + + if self.ramdump.ipc_log_debug: + options.verbosity = LOG_DEBUG + options.debug_enabled = self.ramdump.ipc_log_debug + + if self.ramdump.ipc_log_help: + options.cmd = "help" + + ipc_logging.configure_logging(options, self.ramdump.use_stdout) + + # Return value of set_output_directory() is not checked here because + # set_output_directory() only returns NULL if options.output_dir is not + # set, and options.output_dir is always set per the IPCLoggingOptions + # constructor + ipc_logging.set_output_directory(options) + + if options.cmd == 'parse': + ipc_logging.cmdParse(options) + print_out_str("Wrote parse output to ipc_logging directory") + elif options.cmd == 'test': + ipc_logging.cmdTest(options) + print_out_str("Wrote test output to ipc_logging directory") + else: + print strUsage diff --git a/linux-ramdump-parser-v2/ramdump.py b/linux-ramdump-parser-v2/ramdump.py index 3c767cd775efce07076c58502240f0f5581ea1ae..ca62cb56f28263f20eaea5ec255491a8bf44c06b 100644 --- a/linux-ramdump-parser-v2/ramdump.py +++ b/linux-ramdump-parser-v2/ramdump.py @@ -461,6 +461,11 @@ class RamDump(): self.qtf_path = options.qtf_path self.qtf = options.qtf self.t32_host_system = options.t32_host_system or None + self.ipc_log_test = options.ipc_test + self.ipc_log_skip = options.ipc_skip + self.ipc_log_debug = options.ipc_debug + self.ipc_log_help = options.ipc_help + self.use_stdout = options.stdout if options.ram_addr is not None: # TODO sanity check to make sure the memory regions don't overlap for file_path, start, end in options.ram_addr: diff --git a/linux-ramdump-parser-v2/ramparse.py b/linux-ramdump-parser-v2/ramparse.py index 8e2ede3134a4ca8168e499b618502ee420678003..0295f69bd53956aee1fb886626d6065531e0e4c2 100755 --- a/linux-ramdump-parser-v2/ramparse.py +++ b/linux-ramdump-parser-v2/ramparse.py @@ -129,6 +129,17 @@ if __name__ == '__main__': help='Use QTF tool to parse and save QDSS trace data') parser.add_option('', '--qtf-path', dest='qtf_path', help='QTF tool executable') + parser.add_option('', '--ipc-help', dest='ipc_help', + help='Help for IPC Logging', action='store_true', + default=False) + parser.add_option('', '--ipc-test', dest='ipc_test', + help='List of test files for the IPC Logging test command (name1, name2, ..., nameN, <version>)', + action='append', default=[]) + parser.add_option('', '--ipc-skip', dest='ipc_skip', action='store_true', + help='Skip IPC Logging when parsing everything', + default=False) + parser.add_option('', '--ipc-debug', dest='ipc_debug', action='store_true', + help='Debug Mode for IPC Logging', default=False) for p in parser_util.get_parsers(): parser.add_option(p.shortopt or '',