diff --git a/pwndbg/events.py b/pwndbg/events.py index b3cbf083c..f717a47b2 100644 --- a/pwndbg/events.py +++ b/pwndbg/events.py @@ -39,12 +39,15 @@ class StartEvent(object): def __init__(self): self.registered = list() self.running = False + def connect(self, function): if function not in self.registered: self.registered.append(function) + def disconnect(self, function): if function in self.registered: self.registered.remove(function) + def on_new_objfile(self): if self.running or not gdb.selected_thread(): return @@ -62,8 +65,46 @@ class StartEvent(object): def on_stop(self): self.on_new_objfile() + gdb.events.start = StartEvent() + +class EventWrapper(object): + """ + Wraper for GDB events which may not exist on older GDB versions but we still can + fire them manually (to invoke them you have to call `invoke_callbacks`). + """ + def __init__(self, name): + self.name = name + + self._event = getattr(gdb.events, self.name, None) + self._is_real_event = self._event is not None + + def connect(self, func): + if self._event is not None: + self._event.connect(func) + + def disconnect(self, func): + if self._event is not None: + self._event.disconnect(func) + + @property + def is_real_event(self): + return self._is_real_event + + def invoke_callbacks(self): + """ + As an optimization please don't call this if your GDB has this event (check `.is_real_event`). + """ + for f in registered[self]: + f() + + +# Old GDBs doesn't have gdb.events.before_prompt, so we will emulate it using gdb.prompt_hook +before_prompt_event = EventWrapper('before_prompt') +gdb.events.before_prompt = before_prompt_event + + # In order to support reloading, we must be able to re-fire # all 'objfile' and 'stop' events. registered = { @@ -72,7 +113,7 @@ registered = { gdb.events.new_objfile: [], gdb.events.stop: [], gdb.events.start: [], - gdb.events.before_prompt: [] + gdb.events.before_prompt: [] # The real event might not exist, but we wrap it } # GDB 7.9 and above only @@ -82,20 +123,24 @@ try: except (NameError, AttributeError): pass + class Pause(object): def __enter__(self, *a, **kw): global pause pause += 1 + def __exit__(self, *a, **kw): global pause pause -= 1 + # When performing remote debugging, gdbserver is very noisy about which # objects are loaded. This greatly slows down the debugging session. # In order to combat this, we keep track of which objfiles have been loaded # this session, and only emit objfile events for each *new* file. objfile_cache = set() + def connect(func, event_handler, name=''): if debug: print("Connecting", func.__name__, event_handler) @@ -130,25 +175,31 @@ def connect(func, event_handler, name=''): event_handler.connect(caller) return func + def exit(func): return connect(func, gdb.events.exited, 'exit') def cont(func): return connect(func, gdb.events.cont, 'cont') def new_objfile(func): return connect(func, gdb.events.new_objfile, 'obj') def stop(func): return connect(func, gdb.events.stop, 'stop') def start(func): return connect(func, gdb.events.start, 'start') + before_prompt = partial(connect, event_handler=gdb.events.before_prompt, name='before_prompt') + def reg_changed(func): try: return connect(func, gdb.events.register_changed, 'reg_changed') - except Exception: + except AttributeError: return func + + def mem_changed(func): try: return connect(func, gdb.events.memory_changed, 'mem_changed') - except Exception: + except AttributeError: return func + def log_objfiles(ofile=None): if not (debug and ofile): return @@ -158,8 +209,10 @@ def log_objfiles(ofile=None): print("objfile: %r" % name) gdb.execute('info sharedlibrary') + gdb.events.new_objfile.connect(log_objfiles) + def after_reload(start=True): if gdb.selected_inferior().pid: for f in registered[gdb.events.stop]: @@ -168,6 +221,9 @@ def after_reload(start=True): if start: f() for f in registered[gdb.events.new_objfile]: f() + for f in registered[gdb.events.before_prompt]: + f() + def on_reload(): for event, functions in registered.items(): @@ -175,18 +231,22 @@ def on_reload(): event.disconnect(function) registered[event] = [] + @new_objfile def _start_newobjfile(): gdb.events.start.on_new_objfile() + @exit def _start_exit(): gdb.events.start.on_exited() + @stop def _start_stop(): gdb.events.start.on_stop() + @exit def _reset_objfiles(): global objfile_cache diff --git a/pwndbg/prompt.py b/pwndbg/prompt.py index 946b68126..7645310ab 100644 --- a/pwndbg/prompt.py +++ b/pwndbg/prompt.py @@ -58,4 +58,13 @@ def set_prompt(): gdb.execute('set prompt %s' % prompt) -gdb.prompt_hook = prompt_hook +if pwndbg.events.before_prompt_event.is_real_event: + gdb.prompt_hook = prompt_hook + +else: + # Old GDBs doesn't have gdb.events.before_prompt, so we will emulate it using gdb.prompt_hook + def extended_prompt_hook(*a): + pwndbg.events.before_prompt_event.invoke_callbacks() + return prompt_hook(*a) + + gdb.prompt_hook = extended_prompt_hook