def format_shortcut(k): k = str(k) sh = k try: if k in key_names: sh = key_names[k] elif k.startswith('KEY_F('): fnkey = int(sh[6:-1]) if fnkey > 48: sh = 'M-F{}'.format(fnkey - 48) elif fnkey > 24: sh = 'C-F{}'.format(fnkey - 24) elif fnkey > 12: sh = 'Sh-F{}'.format(fnkey - 12) else: sh = 'F{}'.format(fnkey) elif k.startswith('CTRL_'): sh = 'C-{}'.format(k[5:].lower()) else: if len(k) == 1: if k.isalpha() and k.lower() != k: sh = 'Sh-{}'.format(k.lower()) elif k == ' ': sh = 'Space' else: sh = k else: sh = k.capitalize() except: log_traceback() return sh
def run_script(self, script, quiet=False): log('executing script {}'.format(script)) self.inject() try: fname = '{}/{}'.format(self.script_dir, script) with open(fname) as fd: src = fd.read() except: log_traceback() if not quiet: self.print_message('Unable to load {}'.format(fname), color=palette.ERROR) return try: result = self.injection_command(src=src) except Exception as e: result = e if not isinstance(result, Exception): if result is None: msg = '{} executed'.format(script) else: msg = '{}: {}'.format(script, result) if not quiet: self.print_message(msg, color=palette.GREEN_BOLD) else: if not quiet: self.print_message('{}: {}'.format(script, result), color=palette.ERROR)
def update_status(**kwargs): try: _d.status = command('.status') except: log_traceback() status = -2 finally: time.sleep(1)
def grab_stdout(**kwargs): try: result = command('.gs') with stdout_buf_lock: _d.stdout_buf += result except: log_traceback() finally: time.sleep(0.5)
def set_cursor(mode): if (term.startswith('screen') or not scr.active) and tput: try: code = os.system(tput + ' ' + ('civis' if not mode else 'cnorm')) if code: raise RuntimeError('tput error code: {}'.format(code)) return except: log_traceback() try: curses.curs_set(mode) except: pass
def load_data(self): with self.data_lock: self.data.clear() try: for g in glob.glob(self.script_dir + '/**/*.py', recursive=True): d = OrderedDict() script = g[len(self.script_dir) + 1:] d['script'] = script d['shortcut'] = self.script_hotkey_help.get(script, '') self.data.append(d) except: log_traceback()
def main(): import argparse ap = argparse.ArgumentParser() ap.add_argument('file', metavar='FILE', help='File to launch') ap.add_argument('cpid', metavar='PID', type=int, help='Client PID') ap.add_argument('-w', '--wait', metavar='SEC', type=float, help='Wait seconds till start') ap.add_argument('-p', '--protocol', metavar='VER', type=int, help='Pickle protocol') ap.add_argument('-a', '--args', metavar='ARGS', help='Child args (quoted)') ap.add_argument('--log', metavar='FILE', help='Send debug log to file') a = ap.parse_args() if a.protocol and a.protocol > PICKLE_PROTOCOL: raise ValueError('Protocol {} is not supported'.format(a.protocol)) if a.log: init_logging(a.log) with open(a.file) as fh: src = fh.read() sys.argv = [a.file] if a.args: import shlex sys.argv += shlex.split(a.args) log('pptop injection runner started') launch(a.cpid, wait=True if a.wait is None else a.wait) g._runner_status = 1 log('starting main code') try: code = compile(src, a.file, 'exec') launcher_g = {'__file__': a.file} exec(code, launcher_g) g._runner_status = 0 log('main code finished') except: g._runner_status = -2 log_traceback('exception in main code') e = sys.exc_info() with _g_lock: g._last_exception = (e[0].__name__, str( e[1]), ['']) # TODO: correct tb traceback.format_tb(e[2])) while not g._server_finished: time.sleep(0.2) # usually not executed as server kills process log('pptop injection runner stopped')
def select_process(): with scr.lock: cls() hide_cursor() selector = ProcesSelector(interval=1) selector.events = 0 selector.name = 'process_selector' selector.sorting_rev = False selector.selectable = True selector.finish_event = threading.Event() selector.lock = threading.Lock() selector.title = 'Select process' selector.show() selector.start() _d.current_plugin = {'p': selector} while True: try: try: k = format_key(scr.stdscr.getkey()) event = get_key_event(k) except KeyboardInterrupt: return except curses.error: resize_handler.trigger_threadsafe(force=True) continue if event == 'back': selector.stop(wait=False) return elif event == 'filter': apply_filter(selector) elif event == 'select': if not selector.dtd: continue selector.stop(wait=False) return psutil.Process(selector.dtd[selector.cursor]['pid']) elif event == 'pause': with scr.lock: selector.toggle_pause() else: with scr.lock: selector.key_code = k selector.key_event = event selector.trigger_threadsafe() except: log_traceback() raise return
def load_data(self): self.data.clear() px = ['python', 'python2', 'python3'] user = getpass.getuser() if os.getuid() else 'root' for p in psutil.process_iter(): try: with p.oneshot(): name = p.name().split('.', 1)[0] fname = p.exe().rsplit('/', 1)[-1].split('.', 1)[0] if (name in px or fname in px) and p.pid != os.getpid() and ( user == 'root' or p.username() == user): d = OrderedDict() d['pid'] = p.pid d['command line'] = ' '.join(p.cmdline()) self.data.append(d) except psutil.AccessDenied: pass except: log_traceback()
def load_data(self): ''' Load plugin data Default method sends command cmd=<plugin_name> Returns: if False is returned, the plugin is stopped (doesn't works if self.background_loader=True) ''' try: result = self.load_remote_data() processed = self.process_data(result) if isinstance(processed, list): result = processed if result is False or processed is False: return False if isinstance(result, list): d = result else: d = [] with self.data_lock: if self.append_data: self.data += d else: self.data = d if self.data_records_max and len( self.data) > self.data_records_max: self.data = self.data[len(self.data) - self.data_records_max:] self._error = False self._display_ui() return True except Exception as e: log_traceback() self.data = [] with scr.lock: self._error = True self.msg = e if self._visible: self.print_title()
def command(cmd, params=None): with client_lock: _d.client_frame_id += 1 if _d.client_frame_id >= frame_counter_reset: _d.client_frame_id = 1 _d.last_frame_id = 0 try: frame = cmd.encode() if params is not None: frame += b'\xff' + pickle.dumps(params, protocol=_d.protocol) client.sendall( struct.pack('I', len(frame)) + struct.pack('I', _d.client_frame_id) + frame) time_start = time.time() data = client.recv(4) frame_id = struct.unpack('I', client.recv(4))[0] except: log_traceback() raise CriticalException('Injector is gone') if not data: log('critical: no data from injector') raise CriticalException('Injector error') l = struct.unpack('I', data)[0] data = b'' while len(data) != l: data += client.recv(socket_buf) if time.time() > time_start + socket_timeout: raise CriticalException('Socket timeout') if frame_id != _d.client_frame_id: log('critical: got wrong frame, channel is broken') raise CriticalException('Wrong frame') _d.last_frame_id += 1 with ifoctets_lock: _d.ifoctets += len(data) + 8 if _d.ifoctets > 1000000000: _d.ifoctets = _d.ifoctets - 1000000000 if data[0] != 0: log('injector command error, code: {}'.format(data[0])) raise RuntimeError('Injector command error') return pickle.loads(data[1:]) if len(data) > 1 else True
def run(self, **kwargs): ''' Primary plugin executor method ''' try: if (not self.key_event or self.key_event == 'reload' ) and not self._paused and not self._loader_active: self._loader_active = True if self.background_loader: spawn(self._load_data) return else: if self._load_data() is False: return False return self._display_ui() except Exception as e: log_traceback() if self._visible: with scr.lock: self._error = True self.msg = str(e) self.print_title() return False
def loop(cpid, protocol, runner_mode=False): class STD: pass class ppStdout(object): write_through = False mode = 'w' def __init__(self, name, real, std): self.name = '<{}>'.format(name) self.real = real self.std = std try: self.encoding = real.encoding except: self.encoding = 'UTF-8' self.flush = real.flush self.isatty = real.isatty def writable(self): return True def write(self, text): with self.std.lock: self.std.buf += text return self.real.write(text) def writelines(self, lines): with self.std.lock: for l in lines: self.std.buf += l return self.real.writelines(lines) def send_frame(conn, frame_id, data): conn.sendall( struct.pack('I', len(data)) + struct.pack('I', frame_id) + data) # log('{}: frame {}, {} bytes sent'.format(cpid, frame_id, len(data))) def send_serialized(conn, frame_id, data): send_frame(conn, frame_id, b'\x00' + pickle.dumps(data, protocol=protocol)) def send_ok(conn, frame_id): send_frame(conn, frame_id, b'\x00') def format_injection_unload_code(injection_id, src): return compile(u + '\ninjection_unload()', '__pptop_injection_unload_' + injection_id, 'exec') server_address = '/tmp/.pptop.{}'.format(cpid) try: os.unlink(server_address) except: pass server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) server.bind(server_address) os.chmod(server_address, 0o600) server.listen(0) server.settimeout(socket_timeout) server.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, socket_buf) server.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, socket_buf) injections = {} real_stdout = None real_stderr = None log('Pickle protocol: {}'.format(protocol)) log('listening') try: connection, client_address = server.accept() log('connected') connection.sendall(struct.pack('b', protocol)) with _g_lock: g.clients += 1 connection.settimeout(socket_timeout) exec_globals = {} while True: try: time_start = time.time() data = connection.recv(4) frame_id = struct.unpack('I', connection.recv(4))[0] if data: l = struct.unpack('I', data)[0] frame = b'' while len(frame) != l: if time.time() > time_start + socket_timeout: raise TimeoutError frame += connection.recv(socket_buf) else: break except: log_traceback('invalid data received or client is gone') break if frame: try: cmd, params = frame.split(b'\xff', 1) cmd = cmd.decode() params = pickle.loads(params) except: cmd = frame.decode() params = {} try: if cmd == '.test': send_ok(connection, frame_id) elif cmd == '.bye': break elif cmd == '.gs': if real_stdout is None: std = STD() std.lock = threading.Lock() std.buf = '' with std.lock: real_stdout = sys.stdout real_stderr = sys.stderr sys.stdout = ppStdout('stdout', real_stdout, std) sys.stderr = ppStdout('stderr', real_stderr, std) buf = '' else: with std.lock: buf = std.buf std.buf = '' send_serialized(connection, frame_id, buf) elif cmd == '.status': send_serialized(connection, frame_id, g._runner_status if runner_mode else 1) elif cmd == '.path': send_serialized(connection, frame_id, sys.path) elif cmd == '.x': x = {} try: exec(params, x) result = (0, x.get('out')) except: log_traceback() e = sys.exc_info() result = (1, e[0].__name__, str(e[1])) send_serialized(connection, frame_id, result) elif cmd == '.exec': try: if params.startswith('help'): raise RuntimeError( 'Help on remote is not supported') if params.startswith('try: __result '): src = params else: p1 = params.split(' ', 1)[0] prfunc = '_print' if sys.version_info < ( 3, 0) else 'print' if p1 in [ 'import', 'def', 'class', 'for', 'while', 'raise', 'if', 'with', 'from', 'try:' ]: src = ( 'def {}(*args):\n' + ' __resultl.append(\' \'.join(str(a) ' + 'for a in args))\n__resultl=[]\n{}' + '\n__result = \'\\n\'.join(__resultl) ' + 'if __resultl else None').format( prfunc, params) else: src = ('def {}(*args): ' + 'return \' \'.join(str(a) ' + 'for a in args)\n' + '__result = {}').format( prfunc, params) exec(src, exec_globals) result = exec_globals.get('__result') try: data = pickle.dumps( (0, safe_serialize(result)), protocol=protocol) except: log_traceback() data = pickle.dumps((0, str(result)), protocol=protocol) send_frame(connection, frame_id, b'\x00' + data) except: log_traceback() e = sys.exc_info() with _g_lock: g._last_exception = (e[0].__name__, str(e[1]), ['']) send_serialized(connection, frame_id, (-1, e[0].__name__, str(e[1]))) elif cmd == '.le': with _g_lock: send_serialized(connection, frame_id, g._last_exception) elif cmd == '.ready': g._runner_ready = True send_ok(connection, frame_id) elif cmd == '.inject': log(params) injection_id = params['id'] if injection_id in injections: u = injections[injection_id].get('u') if u: try: code = format_injection_unload_code( injection_id, u) exec(code, injections[injection_id]['g']) log('injection removed: {}'.format( injection_id)) except: log_traceback() injections[injection_id] = { 'g': { 'g': SimpleNamespace(), 'mg': g }, 'u': params.get('u') } if 'l' in params: code = compile( params['l'] + '\ninjection_load(**load_kw)', '__pptop_injection_load_' + injection_id, 'exec') injections[injection_id]['g'][ 'load_kw'] = params.get('lkw', {}) exec(code, injections[injection_id]['g']) if 'i' in params: src = params['i'] + '\n_r = injection(**kw)' else: src = '_r = None' injections[injection_id]['i'] = compile( src, '__pptop_injection_' + injection_id, 'exec') log('injection completed: {}'.format(injection_id)) send_ok(connection, frame_id) elif cmd in injections: log('command {}, data: {}'.format(cmd, params)) gl = injections[cmd]['g'] gl['kw'] = params exec(injections[cmd]['i'], gl) send_serialized(connection, frame_id, gl['_r']) else: send_frame(connection, frame_id, b'\x01') except: log_traceback() send_frame(connection, frame_id, b'\x02') else: break except Exception as e: log_traceback() for i, v in injections.items(): u = v.get('u') if u: try: code = format_injection_unload_code(i, u) exec(code, v['g']) log('injection removed: {}'.format(i)) except: log_traceback() try: server.close() except: pass try: with _g_lock: g.clients -= 1 except: pass if real_stdout is not None: sys.stdout = real_stdout if real_stderr is not None: sys.stderr = real_stderr try: os.unlink(server_address) except: pass log('finished') if runner_mode: g._server_finished = True os._exit(0)
def run(): def autostart_plugins(): for plugin in plugins_autostart: if plugin['p'] is not _d.current_plugin.get('p'): log('autostarting {}'.format(plugin['m'])) inject_plugin(plugin) p = plugin['p'] if p.background: p.start() try: if not _d.work_pid: init_curses(initial=True, after_resize=after_resize, colors=config['display'].get('colors'), glyphs=config['display'].get('glyphs')) p = select_process() else: p = psutil.Process(_d.work_pid) if not p: return _d.process = p client.settimeout(socket_timeout) client.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, socket_buf) client.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, socket_buf) if _d.need_inject_server: inject_server(_d.gdb, p) log('server injected') sock_path = '/tmp/.pptop.{}'.format(os.getpid()) for i in range(injection_timeout * 10): if os.path.exists(sock_path): break time.sleep(0.1) try: client.connect(sock_path) except: log_traceback() raise RuntimeError('Unable to connect to process') log('connected') frame = b'' with client_lock: time_start = time.time() while len(frame) < 1: data = client.recv(1) if data: frame += data if time.time() > time_start + socket_timeout: raise CriticalException('Socket timeout') server_protocol = struct.unpack('b', frame)[0] if server_protocol < _d.protocol: if _d.force_protocol: raise RuntimeError( 'Process doesn\'t support protocol {}'.format(_d.protocol)) else: _d.protocol = server_protocol log('Falling back to protocol {}'.format(_d.protocol)) if _d.exec_code: end_curses() result = command('.x', _d.exec_code) if result[0] == 0: if _d.output_as_json: print_json(result[1]) else: print(result[1] if result[1] else '') else: print(err('{}: {}'.format(result[1], result[2]))) return init_curses(initial=True, after_resize=after_resize, colors=config['display'].get('colors'), glyphs=config['display'].get('glyphs')) signal.signal(signal.SIGWINCH, sigwinch_handler) calc_bw.start() update_status.start() if _d.grab_stdout: grab_stdout.start() _d.process_path.clear() plugin_process_path.clear() if _d.grab_stdout: try: command('.gs') except: raise RuntimeError('Unable to set stdout grabber') ppath = [] for i in command('.path'): ppath.append(os.path.abspath(i)) _d.process_path.extend(sorted(ppath, reverse=True)) plugin_process_path.extend(_d.process_path) log('process path: {}'.format(_d.process_path)) switch_plugin(_d.default_plugin) recalc_info_col_pos() show_process_info.start(p=p) show_bottom_bar.start() neotasker.spawn(autostart_plugins) log('main loop started') while True: try: try: k = format_key(scr.stdscr.getkey()) event = get_key_event(k) except KeyboardInterrupt: return except curses.error: resize_handler.trigger_threadsafe(force=True) continue if show_process_info.is_stopped(): return elif k in plugin_shortcuts: switch_plugin(plugin_shortcuts[k]) elif event == 'ready': try: result = command('.ready') except: result = None with scr.lock: if result: print_message('Ready event sent', color=palette.OK) else: print_message('Command failed', color=palette.ERROR) elif event == 'reinject' and \ _d.current_plugin['p'].injected is not None: try: result = command('.inject', _d.current_plugin['i']) except: result = None with scr.lock: if result: print_message('Plugin re-injected', color=palette.OK) else: print_message('Plugin re-injection failed', color=palette.ERROR) elif event == 'quit': _d.current_plugin['p'].stop(wait=False) show_process_info.stop(wait=False) show_bottom_bar.stop(wait=False) return elif event == 'console': with scr.lock: end_curses() if _d.grab_stdout: print_stdout.start() cli_mode() if _d.grab_stdout: print_stdout.stop() init_curses(after_resize=after_resize) resize_term() elif event == 'show-console': with scr.lock: end_curses() hide_cursor() if _d.grab_stdout: print_stdout.start() try: wait_key() except KeyboardInterrupt: pass if _d.grab_stdout: print_stdout.stop() init_curses(after_resize=after_resize) resize_term() elif event == 'filter': apply_filter(_d.current_plugin['p']) elif event == 'interval': apply_interval(_d.current_plugin['p']) elif event == 'pause': with scr.lock: _d.current_plugin['p'].toggle_pause() elif event in _d.current_plugin['p'].inputs: with scr.lock: try: prev_value = _d.current_plugin['p'].get_input( event) except ValueError: continue value = prompt( ps=_d.current_plugin['p'].get_input_prompt(event), value=prev_value if prev_value is not None else '') _d.current_plugin['p'].inputs[event] = value try: _d.current_plugin['p'].handle_input( event, value, prev_value) except: pass else: for i, plugin in plugins.items(): try: plugin['p'].handle_key_global_event(event, k) except: log_traceback() with scr.lock: _d.current_plugin['p'].key_code = k _d.current_plugin['p'].key_event = event _d.current_plugin['p'].trigger_threadsafe() except: log_traceback() return except: log_traceback() raise finally: end_curses()
def start(): def format_plugin_option(dct, o, v): if o.find('.') != -1: x, y = o.split('.', 1) dct[x] = {} format_plugin_option(dct[x], y, v) else: dct[o] = v _me = 'ppTOP version %s' % __version__ ap = argparse.ArgumentParser(description=_me) ap.add_argument('-V', '--version', help='Print version and exit', action='store_true') ap.add_argument('-R', '--raw', help='Raw mode (disable colors and unicode glyphs)', action='store_true') ap.add_argument('--disable-glyphs', help='disable unicode glyphs', action='store_true') ap.add_argument('file', nargs='?', help='File, PID file or PID', metavar='FILE/PID') ap.add_argument('-a', '--args', metavar='ARGS', help='Child args (quoted)') ap.add_argument('--python', metavar='FILE', help='Python interpreter to launch file') ap.add_argument('--gdb', metavar='FILE', help='Path to gdb') ap.add_argument( '-p', '--protocol', metavar='VER', type=int, help=textwrap.dedent('''Pickle protocol, default is highest. 4: Python 3.4+, 3: Python 3.0+, 2: Python 2.3+, 1: vintage''')) ap.add_argument('--inject-method', choices=['auto', 'native', 'loadcffi', 'unsafe'], help='Inject method') ap.add_argument('-g', '--grab-stdout', help='Grab stdout/stderr of injected process', action='store_true') ap.add_argument( '-w', '--wait', metavar='SEC', type=float, help='If file is specified, wait seconds to start main code') ap.add_argument( '-f', '--config-file', help='Alternative config file (default: ~/.pptop/pptop.yml)', metavar='CONFIG', dest='config') ap.add_argument('-d', '--default', help='Default plugin to launch', metavar='PLUGIN', dest='plugin') ap.add_argument( '-o', '--plugin-option', help='Override plugin config option, e.g. threads.filter=mythread', metavar='NAME=VALUE', action='append', dest='plugin_options') ap.add_argument('--log', metavar='FILE', help='Send debug log to file') ap.add_argument('-x', '--exec', help='Exec code from a file ("-" for stdin) and exit ' ' (the code can put result to "out" var)', metavar='FILE', dest='_exec') ap.add_argument('-J', '--json', help='Output exec result as JSON', action='store_true') try: import argcomplete argcomplete.autocomplete(ap) except: pass a = ap.parse_args() if a.log: log_config.fname = a.log log_config.name = 'client:{}'.format(os.getpid()) logging.getLogger('asyncio').setLevel(logging.DEBUG) logging.getLogger('neotasker').setLevel(logging.DEBUG) logging.basicConfig(level=logging.DEBUG) le = logging.getLogger() le.addHandler(ppLoghandler()) list(map(le.removeHandler, le.handlers)) neotasker.set_debug(True) init_logging() if a.version: print(_me) exit() log('initializing') if a.file: try: # pid? _d.work_pid = int(a.file) except: # probably pid file try: with open(a.file) as fh: _d.work_pid = int(fh.read(128)) except: # okay, program to launch _d.child_cmd = os.path.abspath(a.file) _d.pptop_dir = os.path.expanduser('~/.pptop') if a.config: config_file = a.config use_default_config = False else: config_file = _d.pptop_dir + '/pptop.yml' use_default_config = True sys.path.append(_d.pptop_dir + '/lib') config.clear() if use_default_config and not os.path.isfile(config_file): log('no user config, setting default') try: os.mkdir(_d.pptop_dir) except: pass if not os.path.isdir(_d.pptop_dir + '/scripts'): shutil.copytree(dir_me + '/config/scripts', _d.pptop_dir + '/scripts') shutil.copy(dir_me + '/config/pptop.yml', _d.pptop_dir + '/pptop.yml') if not os.path.isdir(_d.pptop_dir + '/lib'): os.mkdir(_d.pptop_dir + '/lib') with open(config_file) as fh: config.update(yaml.load(fh.read())) console = config.get('console') if console is None: console = {} _d.console_json_mode = console.get('json-mode') _d.inject_method = a.inject_method if a.inject_method else config.get( 'inject-method') if config.get('display') is None: config['display'] = {} if a.raw: config['display']['colors'] = False if a.grab_stdout: _d.grab_stdout = True if a.raw or a.disable_glyphs: config['display']['glyphs'] = False if a._exec: if a._exec == '-': _d.exec_code = sys.stdin.read() else: with open(a._exec) as fd: _d.exec_code = fd.read() _d.output_as_json = a.json else: ebk = {} global_keys = config.get('keys') if global_keys: for event, keys in global_keys.items(): for k, v in events_by_key.copy().items(): if event == v: del events_by_key[k] if keys is not None: for k in keys if isinstance(keys, list) else [keys]: ebk[str(k)] = str(event) events_by_key.update(ebk) plugin_options = {} for x in a.plugin_options or []: try: o, v = x.split('=', 1) except: o = x v = None format_plugin_option(plugin_options, o, v) if plugin_options: config.update(merge_dict(config, {'plugins': plugin_options})) log('loading plugins') try: plugins.clear() for i, v in config.get('plugins', {}).items(): try: log('+ plugin ' + i) if v is None: v = {} try: mod = importlib.import_module('pptop.plugins.' + i) mod.__version__ = 'built-in' except ModuleNotFoundError: mod = importlib.import_module('pptopcontrib.' + i) try: mod.__version__ except: raise RuntimeError( 'Please specify __version__ in plugin file') plugin = {'m': mod} plugins[i] = plugin p = mod.Plugin(interval=float( v.get('interval', mod.Plugin.default_interval))) p.command = command p.get_plugins = get_plugins p.get_plugin = get_plugin p.get_config_dir = get_config_dir p.switch_plugin = switch_plugin p.get_process = get_process p.get_process_path = get_process_path p.global_config = config plugin['p'] = p plugin['id'] = i p._inject = partial(inject_plugin, plugin=plugin) injection = {'id': i} need_inject = False try: injection['l'] = inspect.getsource(mod.injection_load) need_inject = True except: pass try: injection['i'] = inspect.getsource(mod.injection) need_inject = True except: pass try: injection['u'] = inspect.getsource( mod.injection_unload) need_inject = True except: pass if need_inject: p.injected = False plugin['i'] = injection else: p.injected = None if not _d.default_plugin or val_to_boolean( v.get('default')) or i == a.plugin: _d.default_plugin = plugin p_cfg = v.get('config') p.config = {} if p_cfg is None else p_cfg p.on_load() p._on_load() if 'l' in injection: injection['lkw'] = p.get_injection_load_params() if 'shortcut' in v: sh = v['shortcut'] plugin['shortcut'] = sh plugin_shortcuts[sh] = plugin if sh.startswith('KEY_F('): try: f = int(sh[6:-1]) if f <= 10: bottom_bar_help[f] = p.short_name except: pass else: plugin['shortcut'] = '' if 'filter' in v: p.filter = str(v['filter']) if 'cursor' in v: p._cursor_enabled_by_user = val_to_boolean(v['cursor']) if val_to_boolean(v.get('autostart')): plugins_autostart.append(plugin) except Exception as e: raise RuntimeError('plugin {}: {}'.format(i, e)) except: log_traceback() raise neotasker.task_supervisor.start() neotasker.task_supervisor.create_aloop('pptop', default=True, daemon=True) neotasker.task_supervisor.create_aloop('service', daemon=True) try: if a.file and not _d.work_pid: # launch file _d.need_inject_server = False if a.python: python_path = a.python else: python_path = shutil.which('python3') if not python_path: raise RuntimeError( 'python3 not found in path, please specify manually') args = (python_path, '-m', 'pptop.injection', a.file, str(os.getpid())) if a.wait is not None: args += ('-w', str(a.wait)) if a.protocol is not None: args += ('-p', str(a.protocol)) if a.args: args += ('-a', a.args) if log_config.fname: args += ('--log', log_config.fname) log('starting child process') _d.child = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) _d.work_pid = _d.child.pid _d.protocol = pickle.HIGHEST_PROTOCOL else: if a.gdb: _d.gdb = a.gdb else: _d.gdb = shutil.which('gdb') if not _d.gdb or not os.path.isfile(_d.gdb): raise RuntimeError('gdb not found') # check yama ptrace scope try: with open('/proc/sys/kernel/yama/ptrace_scope') as fd: yps = int(fd.read().strip()) except: yps = None if yps: raise RuntimeError( 'yama ptrace scope is on. ' + 'disable with "sudo sysctl -w kernel.yama.ptrace_scope=0"') init_inject() log('inject method: {}'.format(_d.inject_method)) log('inject library: {}'.format(_d.inject_lib)) if a.protocol is not None: if a.protocol > pickle.HIGHEST_PROTOCOL or a.protocol < 1: raise ValueError('Protocol {} is not supported'.format( a.protocol)) _d.protocol = a.protocol _d.force_protocol = a.protocol else: _d.protocol = pickle.HIGHEST_PROTOCOL log('Pickle protocol: {}'.format(_d.protocol)) run() log('terminating') for p, v in plugins.items(): v['p'].on_unload() except Exception as e: log_traceback() raise finally: try: client.close() except: pass neotasker.task_supervisor.stop(wait=False, cancel_tasks=True) return 0
async def show_process_info(p, **kwargs): def error(txt): cls() scr.stdscr.addstr(0, 0, str(txt), palette.ERROR) scr.stdscr.refresh() return False def draw_val(row, col, label='', value=None, color=palette.DEFAULT, spacer=True): width = _info_col_width[col] pos = _info_col_pos[col] val = str(value) if value is not None else '' scr.infowin.move(row + 1, pos) if label: scr.infowin.addstr(label) if spacer: scr.infowin.addstr( ('.' if config['display']['colors'] else ' ') * (width - len(label) - len(val)), palette.DARKGREY) scr.infowin.move(row + 1, pos + width - len(val)) else: scr.infowin.addstr(' ') scr.infowin.addstr(val, color) try: width = scr.infowin.getmaxyx()[1] status = _d.status scr.infowin.clear() with p.oneshot(): ct = p.cpu_times() memf = p.memory_full_info() mem = p.memory_info() ioc = p.io_counters() scr.infowin.move(0, 0) scr.infowin.addstr('Process: ') cmdline = format_cmdline(p, _d.need_inject_server) scr.infowin.addstr(cmdline[:width - 25], palette.YELLOW) scr.infowin.addstr(' [') scr.infowin.addstr( str(p.pid), palette.GREEN if status == 1 else palette.GREY_BOLD) scr.infowin.addstr(']') if status == -1: xst = 'WAIT' xstc = palette.GREY_BOLD elif status == 0: xst = 'DONE' xstc = palette.GREY_BOLD elif status == -2: xst = 'ERROR' xstc = palette.ERROR else: xst = None if xst: scr.infowin.addstr(' ' + xst, xstc) cpup = p.cpu_percent() draw_val(0, 0, 'CPU', '{}%'.format(cpup), palette.BLUE_BOLD) draw_val(1, 0, 'user', ct.user, palette.BOLD) draw_val(2, 0, 'system', ct.system, palette.BOLD) # always hide pptop thread draw_val(3, 0, 'threads', p.num_threads() - 1, palette.MAGENTA) # if config['display'].get('glyphs'): # gauge = _vblks[-1] * int(cpup // 25) # i = int(cpup % 25 / 25 * len(_vblks)) # if i: # gauge += _vblks[i - 1] # x = _info_col_width[0] + 1 # for i, g in enumerate(gauge): # scr.stdscr.addstr(4 - i, x, g * 2, # (palette.GREEN, palette.YELLOW, # palette.RED, palette.RED)[i]) draw_val(0, 1, 'Memory uss', bytes_to_iso(memf.uss), palette.BOLD) draw_val(1, 1, 'pss', bytes_to_iso(memf.pss), palette.BOLD) draw_val(2, 1, 'swap', bytes_to_iso(memf.swap), palette.GREY if memf.swap < 1000000 else palette.YELLOW) draw_val(0, 2, 'shd', bytes_to_iso(mem.shared), palette.BOLD) draw_val(1, 2, 'txt', bytes_to_iso(mem.text), palette.BOLD) draw_val(2, 2, 'dat', bytes_to_iso(mem.data), palette.BOLD) draw_val(0, 3, 'Files:', len(p.open_files()), palette.CYAN, spacer=False) draw_val(1, 3, value='{} {} ({})'.format(glyph.UPLOAD, ioc.read_count, bytes_to_iso(ioc.read_chars)), color=palette.GREEN) draw_val(2, 3, value='{} {} ({})'.format(glyph.DOWNLOAD, ioc.write_count, bytes_to_iso(ioc.write_chars)), color=palette.BLUE) with scr.lock: scr.infowin.refresh() scr.stdscr.refresh() except psutil.AccessDenied: log_traceback() return error('Access denied') except psutil.NoSuchProcess: log_traceback() return error('Process is gone') except CriticalException: log_traceback() return error('Process server is gone') except curses.error: log_traceback() try: for i in range(2): scr.stdscr.move(i, 0) scr.stdscr.clrtoeol() scr.stdscr.refresh() except: pass except Exception as e: return error(e)
def cli_mode(): def compl(text, state): if not text or text.find('.') == -1: return None o = text.rsplit('.', 1)[0] src = 'try: __result = dir({})\nexcept: pass'.format(o) result = command('.exec', src) if not result or result[0] or not result[1]: return None matches = [ s for s in result[1] if ('{}.{}'.format(o, s)).startswith(text) ] try: return '{}.{}'.format(o, matches[state]) except IndexError: return None log('cli mode started') if _d.cli_first_time: # os.system('clear') print( colored('Console mode, process {} connected'.format( _d.process.pid), color='green', attrs=['bold'])) print( colored(format_cmdline(_d.process, _d.need_inject_server), color='yellow')) print( colored( 'Enter any Python command, press Ctrl-D or type "exit" to quit' )) print(colored('To toggle between JSON and normal mode, type ".j"')) if _d.grab_stdout: print(colored('To toggle stdout/stderr output, type ".p"')) print( colored( 'To execute multiple commands from file, type "< filename"')) print( colored( 'To explore object, type "obj?" (transformed to "dir(obj)")')) if _d.protocol < 3: print( colored('For Python 2 use \'_print\' instead of \'print\'', color='yellow', attrs=['bold'])) print() _d.cli_first_time = False readline.set_history_length(100) readline.set_completer_delims('') readline.set_completer(compl) readline.parse_and_bind('tab: complete') try: readline.read_history_file('{}/console.history'.format(_d.pptop_dir)) except: pass try: while True: try: cmd = input('>>> ').strip() if cmd == '': continue elif cmd == 'exit': raise EOFError elif _d.grab_stdout and cmd == '.p': if print_stdout.is_active(): print_stdout.stop() else: print_stdout.start() elif cmd == '.j': _d.console_json_mode = not _d.console_json_mode print('JSON mode ' + ('on' if _d.console_json_mode else 'off')) else: if cmd.startswith('<'): with open(os.path.expanduser(cmd[1:].strip())) as fh: cmds = filter(None, [x.strip() for x in fh.readlines()]) elif cmd.endswith('?'): cmds = ['dir({})'.format(cmd[:-1]).strip()] else: cmds = [cmd] for cmd in cmds: r = command('.exec', cmd) if r[0] == -1: print(err('{}: {}'.format(r[1], r[2]))) else: if r[1] is not None: if _d.console_json_mode and \ (isinstance(r[1], dict) or \ isinstance(r[1], list)): print_json(r[1]) else: print(r[1]) except EOFError: return except KeyboardInterrupt: print() continue except Exception as e: log_traceback() print(err(e)) finally: log('cli mode completed') try: readline.write_history_file('{}/console.history'.format( _d.pptop_dir)) except: log_traceback()