Fix before_prompt event on old GDB versions (#464)

* Fix before_prompt event on old GDB versions

This adds an `EventWrapper` class which behaves similar to gdb events but lets us:
* check whether event is a real gdb event or not
* call event callbacks if it is not a real gdb event

* Better comment
pull/465/head
Disconnect3d 8 years ago committed by GitHub
parent 9fd5d35712
commit 7d77bf7004
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -39,12 +39,15 @@ class StartEvent(object):
def __init__(self): def __init__(self):
self.registered = list() self.registered = list()
self.running = False self.running = False
def connect(self, function): def connect(self, function):
if function not in self.registered: if function not in self.registered:
self.registered.append(function) self.registered.append(function)
def disconnect(self, function): def disconnect(self, function):
if function in self.registered: if function in self.registered:
self.registered.remove(function) self.registered.remove(function)
def on_new_objfile(self): def on_new_objfile(self):
if self.running or not gdb.selected_thread(): if self.running or not gdb.selected_thread():
return return
@ -62,8 +65,46 @@ class StartEvent(object):
def on_stop(self): def on_stop(self):
self.on_new_objfile() self.on_new_objfile()
gdb.events.start = StartEvent() 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 # In order to support reloading, we must be able to re-fire
# all 'objfile' and 'stop' events. # all 'objfile' and 'stop' events.
registered = { registered = {
@ -72,7 +113,7 @@ registered = {
gdb.events.new_objfile: [], gdb.events.new_objfile: [],
gdb.events.stop: [], gdb.events.stop: [],
gdb.events.start: [], 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 # GDB 7.9 and above only
@ -82,20 +123,24 @@ try:
except (NameError, AttributeError): except (NameError, AttributeError):
pass pass
class Pause(object): class Pause(object):
def __enter__(self, *a, **kw): def __enter__(self, *a, **kw):
global pause global pause
pause += 1 pause += 1
def __exit__(self, *a, **kw): def __exit__(self, *a, **kw):
global pause global pause
pause -= 1 pause -= 1
# When performing remote debugging, gdbserver is very noisy about which # When performing remote debugging, gdbserver is very noisy about which
# objects are loaded. This greatly slows down the debugging session. # objects are loaded. This greatly slows down the debugging session.
# In order to combat this, we keep track of which objfiles have been loaded # 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. # this session, and only emit objfile events for each *new* file.
objfile_cache = set() objfile_cache = set()
def connect(func, event_handler, name=''): def connect(func, event_handler, name=''):
if debug: if debug:
print("Connecting", func.__name__, event_handler) print("Connecting", func.__name__, event_handler)
@ -130,25 +175,31 @@ def connect(func, event_handler, name=''):
event_handler.connect(caller) event_handler.connect(caller)
return func return func
def exit(func): return connect(func, gdb.events.exited, 'exit') def exit(func): return connect(func, gdb.events.exited, 'exit')
def cont(func): return connect(func, gdb.events.cont, 'cont') def cont(func): return connect(func, gdb.events.cont, 'cont')
def new_objfile(func): return connect(func, gdb.events.new_objfile, 'obj') def new_objfile(func): return connect(func, gdb.events.new_objfile, 'obj')
def stop(func): return connect(func, gdb.events.stop, 'stop') def stop(func): return connect(func, gdb.events.stop, 'stop')
def start(func): return connect(func, gdb.events.start, 'start') def start(func): return connect(func, gdb.events.start, 'start')
before_prompt = partial(connect, event_handler=gdb.events.before_prompt, name='before_prompt') before_prompt = partial(connect, event_handler=gdb.events.before_prompt, name='before_prompt')
def reg_changed(func): def reg_changed(func):
try: try:
return connect(func, gdb.events.register_changed, 'reg_changed') return connect(func, gdb.events.register_changed, 'reg_changed')
except Exception: except AttributeError:
return func return func
def mem_changed(func): def mem_changed(func):
try: try:
return connect(func, gdb.events.memory_changed, 'mem_changed') return connect(func, gdb.events.memory_changed, 'mem_changed')
except Exception: except AttributeError:
return func return func
def log_objfiles(ofile=None): def log_objfiles(ofile=None):
if not (debug and ofile): if not (debug and ofile):
return return
@ -158,8 +209,10 @@ def log_objfiles(ofile=None):
print("objfile: %r" % name) print("objfile: %r" % name)
gdb.execute('info sharedlibrary') gdb.execute('info sharedlibrary')
gdb.events.new_objfile.connect(log_objfiles) gdb.events.new_objfile.connect(log_objfiles)
def after_reload(start=True): def after_reload(start=True):
if gdb.selected_inferior().pid: if gdb.selected_inferior().pid:
for f in registered[gdb.events.stop]: for f in registered[gdb.events.stop]:
@ -168,6 +221,9 @@ def after_reload(start=True):
if start: f() if start: f()
for f in registered[gdb.events.new_objfile]: for f in registered[gdb.events.new_objfile]:
f() f()
for f in registered[gdb.events.before_prompt]:
f()
def on_reload(): def on_reload():
for event, functions in registered.items(): for event, functions in registered.items():
@ -175,18 +231,22 @@ def on_reload():
event.disconnect(function) event.disconnect(function)
registered[event] = [] registered[event] = []
@new_objfile @new_objfile
def _start_newobjfile(): def _start_newobjfile():
gdb.events.start.on_new_objfile() gdb.events.start.on_new_objfile()
@exit @exit
def _start_exit(): def _start_exit():
gdb.events.start.on_exited() gdb.events.start.on_exited()
@stop @stop
def _start_stop(): def _start_stop():
gdb.events.start.on_stop() gdb.events.start.on_stop()
@exit @exit
def _reset_objfiles(): def _reset_objfiles():
global objfile_cache global objfile_cache

@ -58,4 +58,13 @@ def set_prompt():
gdb.execute('set prompt %s' % 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

Loading…
Cancel
Save