Skip to content

Instantly share code, notes, and snippets.

@osa1
Last active August 29, 2015 14:07
Show Gist options
  • Save osa1/046975c2e2471388cdbf to your computer and use it in GitHub Desktop.
Save osa1/046975c2e2471388cdbf to your computer and use it in GitHub Desktop.
gdb
# Some utilities to record jump table/virtual method calls.
# Implemented commands:
# `dynamics break-from-file`: reads an objdump output from given file and adds breakpoints to
# to dynamic calls. When one of the breakpoints are hit, it records jump address without
# dropping to the GDB prompt.
# `dynamics save`: saves collected dynamic call info to the hard-coded file `breakpoint_info`.
# `dynamics print`: prints collected dynamic call info.
# `dynamics disable-hits`: disables breakpoints that are hit at least one time.
# WIP, NOT WORKING YET:
# `dynamics note`: This should be run when $eip is on a dynamic call instruction.
# It records the jump address. Use for manually inspecting stuff.
import gdb
def read_proc_info(proc_str):
""" Parse pid and exe path from `info proc`. Example output:
(gdb) info proc
process 7709
cmdline = '/home/omer/test/out'
cwd = '/home/omer/test'
exe = '/home/omer/test/out'
"""
lines = proc_str.split('\n')
# remove empty string added because of last \n
lines.pop()
pid = int(lines[0].split()[-1])
exe = lines[-1].split(" = ")[-1][1:-1]
return {"pid": pid, "exe": exe}
class Dynamics(gdb.Command):
"""Automated operations on jump tables/virtual method calls."""
def __init__(self):
super(Dynamics, self).__init__("dynamics", gdb.COMMAND_USER, prefix=True)
Dynamics()
class BreakDynamics(gdb.Command):
"""Reads objdump output from given file and adds breakpoints to
dynamic calls.
"""
unknown_method_call_regex = re.compile(r"\s*([0-9a-f]+):.*call[^<]*$")
def __init__(self):
super(BreakDynamics, self).__init__(
"dynamics break-from-file", gdb.COMMAND_BREAKPOINTS)
self.method_breakpoints = []
self.breakpoint_jump_addrs = {}
self.just_run_stepi = False
self.last_bp_addr = None
def save(self):
f = open("breakpoint_info", "w")
f.write(self.to_str())
f.close()
def invoke(self, args, from_tty):
f = open(args, "r")
added = False
for line in f.readlines():
addr = self.unknown_method_call_regex.match(line)
if addr:
addr = addr.groups()[0]
bp = gdb.Breakpoint("* 0x" + addr)
self.method_breakpoints.append(bp)
self.breakpoint_jump_addrs[bp.location] = set()
added = True
if added:
gdb.events.stop.connect(dynamics.handle_breakpoint)
def handle_breakpoint(self, stop_ev):
if self.just_run_stepi:
# we had run `stepi` just before this, so we note the address and continue
addr = gdb.execute("p $eip", to_string=True)
addr = addr.split("<")[-1][:-2]
self.breakpoint_jump_addrs[self.last_bp_addr].add(addr)
self.just_run_stepi = False
# Annoying limitation of GDB: `gdb.execute("continue")` never returns,
# because `gdb.execute` doesn't return until the command is done, and
# `continue` is never "done" because it just continues executing the program.
# (so it's done when program is done)
# gdb.execute("continue")
# So we use this instead:
gdb.post_event(lambda: gdb.execute("continue"))
elif type(stop_ev) == gdb.BreakpointEvent:
# for now, assume that we hit only one breakpoint
bp = stop_ev.breakpoints[0]
# we only handle breakpoints that we've added
if self.__search_bp(bp):
# .. do the jump
self.just_run_stepi = True
self.last_bp_addr = bp.location
# similar to `continue` case
# gdb.execute("stepi")
gdb.post_event(lambda: gdb.execute("stepi"))
def __search_bp(self, bp):
for mbp in self.method_breakpoints:
if id(bp) == id(mbp):
return True
return False
def to_str(self):
lines = []
for (addr, jumps) in self.breakpoint_jump_addrs.iteritems():
jumps_str = ", ".join(list(jumps))
lines.append(" ".join([addr[2:], "->", jumps_str]))
return "\n".join(lines)
dynamics = BreakDynamics()
class SaveDynamics(gdb.Command):
def __init__(self):
super(SaveDynamics, self).__init__(
"dynamics save", gdb.COMMAND_BREAKPOINTS)
def invoke(self, args, from_tty):
dynamics.save()
SaveDynamics()
class PrintDynamics(gdb.Command):
def __init__(self):
super(PrintDynamics, self).__init__(
"dynamics print", gdb.COMMAND_BREAKPOINTS)
def invoke(self, args, from_tty):
print dynamics.to_str()
PrintDynamics()
class NoteDynamics(gdb.Command):
"""Only use when stopped at a dynamic call. Steps inside the call and takes
note the jump address.
"""
def __init__(self):
super(NoteDynamics, self).__init__(
"dynamics note", gdb.COMMAND_BREAKPOINTS)
def invoke(self, args, from_tty):
# TODO: this is tricky. we need to disable breakpoint callbacks
# before running `stepi`, otherwise it'll be called.
pass
NoteDynamics()
class DisableHitDynamics(gdb.Command):
"""Disable the breakpoints that we already hit at least one time.
"""
def __init__(self):
super(DisableHitDynamics, self).__init__(
"dynamics disable-hits", gdb.COMMAND_BREAKPOINTS)
def invoke(self, args, from_tty):
disabled = []
for bp in dynamics.method_breakpoints:
if len(dynamics.breakpoint_jump_addrs[bp.location]) != 0 and bp.enabled:
bp.enabled = False
disabled.append(str(bp.number))
print "disable breakpoints", " ".join(disabled)
DisableHitDynamics()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment