#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright 2014 The Plaso Project Authors. # Please see the AUTHORS file for details on individual authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """A simple tool that provides an overview of running log2timeline processes. The tool iterates over all process running on the system looking for one running log2timeline. If it finds it, it will print out information detected from each process. There is also an option to drop into an IPython shell to further interact with the process, giving the user the option to for instance terminate processes that are in a zombie state. """ import argparse import IPython import sys import textwrap import psutil from plaso.frontend import frontend from plaso.multi_processing import process_info def IsWorkerProcess(process): """Checks whether a process is a worker process. Args: process: A process object (instance of ProcessInfo). Returns: A boolean value indicating whether or not the process is a worker. """ # The parent needs to be log2timeline. if not 'log2timeline' in process.parent.name: return False # If it has an active RPC server then we know for sure. rpc_status = process.GetProcessStatus() if rpc_status: return True # We still want to continue checking, in case the RPC # server was not working. # TODO: Add additional tests to verify this is a worker, # perhaps look at libraries loaded, etc. return False class ProcessInformationFrontend(frontend.Frontend): """A frontend implementation for the process information tool.""" def __init__(self): """Initialize the process information frontend.""" self._input_reader = frontend.StdinFrontendInputReader() self._output_writer = frontend.StdoutFrontendOutputWriter() self._parent_list = [] self._process_list = [] super(ProcessInformationFrontend, self).__init__( self._input_reader, self._output_writer) def PrintRPCDetails(self, process): """Print detailed information about a running process. Args: process: A process object (instance of ProcessInfo). """ self._output_writer.Write(u'RPC Status:\n') rpc_status = process.GetProcessStatus() if rpc_status: for key, value in rpc_status.iteritems(): self._output_writer.Write(u'\t{0:s} = {1!s}\n'.format(key, value)) else: self._output_writer.Write(u'\tNo RPC client listening.\n') def PrintProcessDetails(self, process): """Print detailed information about a running process. Args: process: A process object (instance of ProcessInfo). """ mem_info = process.GetMemoryInformation() self.PrintSeparatorLine() self._output_writer.Write(u'\n{0:20s}{1:s} [{2:d}]\n'.format( u'', process.name, process.pid)) self.PrintSeparatorLine() self.PrintHeader(u'Basic Information') self._output_writer.Write(u'Name:\n\t{0:s}\n'.format(process.name)) self._output_writer.Write(u'PID:\n\t{0:d}\n'.format(process.pid)) self._output_writer.Write(u'Command Line:\n\t{0:s}\n'.format( process.command_line)) self._output_writer.Write(u'Process Alive:\n\t{0!s}\n'.format( process.IsAlive())) self._output_writer.Write(u'Process Status:\n\t{0:s}\n'.format( process.status)) is_a_worker = IsWorkerProcess(process) if is_a_worker: self._output_writer.Write(u'This is a worker thread.\n') else: self._output_writer.Write(u'This is NOT a worker.\n') self._output_writer.Write(u'\n') self.PrintHeader(u' * Additional Information') self._output_writer.Write(u'Parent PID:\n\t{0:d} ({1:s})\n'.format( process.parent.pid, process.parent.name)) self._output_writer.Write(u'Children:\n') for child in process.children: self._output_writer.Write(u'\t{0:d} [{1:s}]\n'.format( child.pid, child.name)) if is_a_worker: self.PrintRPCDetails(process) self._output_writer.Write('Nr. of Threads:\n\t{0:d}\n'.format( process.number_of_threads)) self._output_writer.Write('Open files:\n') for open_file in process.open_files: self._output_writer.Write(u'\t{0:s}\n'.format(open_file)) self._output_writer.Write(u'Memory:\n') # We need to access a protected attribute to get the # name of all the fields in the memory object. # pylint: disable=protected-access for field in mem_info._fields: self._output_writer.Write(u'\t{0:s} = {1!s}\n'.format( field, getattr(mem_info, field, u''))) self._output_writer.Write('Memory map: \n') for memory_map in process.memory_map: self._output_writer.Write(u'\t{0:s}\n'.format(memory_map.path)) def BuildProcessList(self): """Build a list of processes.""" for process_object in psutil.get_process_list(): # TODO: This may catch other processes, such as "vim # foo/log2timeline/foo.py" since that's in the command line. However the # python log2timeline.py will cause the older approach of name to fail. try: command_line = u' '.join(process_object.cmdline) # pylint: disable=protected-access except psutil._error.AccessDenied: continue if 'log2timeline' in command_line: process_details = process_info.ProcessInfo(pid=process_object.pid) self._process_list.append(process_details) parent_process = process_details.parent children = list(process_details.children) if 'log2timeline' not in parent_process.name and len(children): self._parent_list.append(process_details) def TerminateWorkers(self): for process_object in self._process_list: # Find out which process is a worker and which one isn't. if IsWorkerProcess(process_object): self._output_writer.Write( u'Killing process: {0:s} [{1:d}] - {2:s}\n'.format( process_object.name, process_object.pid, process_object.status)) process_object.TerminateProcess() def ListProcesses(self): if self._parent_list: self._output_writer.Write(u'Main process (careful before killing):\n') for parent_process in self._parent_list: if parent_process.IsAlive(): status = u'Alive' else: status = u'Dead' self._output_writer.Write(( u'{4}\n\tPid: {1:d}\n\tCommand Line: {0:s}\n\tStatus:{2} ' u'<{3:s}>\n{4:s}\n').format( parent_process.command_line, parent_process.pid, status, parent_process.status, u'-'*40)) self._output_writer.Write(u'\n') if not self._process_list: self._output_writer.Write( u'No processes discovered. Are you sure log2timeline is running?\n') return self._output_writer.Write(u'='*80) self._output_writer.Write(u'\n\t\tDiscovered Processes\n') self._output_writer.Write(u'='*80) self._output_writer.Write(u'\n') for process_object in self._process_list: self.PrintProcessDetails(process_object) def Main(): """Read parameters and run the tool.""" front_end = ProcessInformationFrontend() description = ( u'A simple tool that tries to list up all processes that belong to ' u'log2timeline. Once a process is detected it will print out ' u'statistical information about it, as well as providing an option ' u'to attempt to "kill" worker threads.') arg_parser = argparse.ArgumentParser( description=textwrap.dedent(description)) arg_parser.add_argument( '-c', '--console', dest='console', action='store_true', default=False, help=u'Open up an IPython console.') arg_parser.add_argument( '-k', '--kill-workers', '--kill_workers', dest='kill_workers', action='store_true', default=False, help=( u'The tool does a rudimentary check to discover worker threads ' u'and terminates those it finds. This can be used in the case ' u'where the tool is stuck due to a non-functioning worker that ' u'prevents the tool from completing it\'s processing.')) # TODO: Add an option to specify certain parent if we are killing workers. options = arg_parser.parse_args() front_end.BuildProcessList() if options.console: IPython.embed() return True if options.kill_workers: front_end.TerminateWorkers() else: front_end.ListProcesses() return True if __name__ == '__main__': if not Main(): sys.exit(1) else: sys.exit(0)