Last active
August 29, 2015 14:07
-
-
Save osa1/046975c2e2471388cdbf to your computer and use it in GitHub Desktop.
gdb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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