Skip to content

Instantly share code, notes, and snippets.

@scottt
Created April 5, 2013 00:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scottt/5315727 to your computer and use it in GitHub Desktop.
Save scottt/5315727 to your computer and use it in GitHub Desktop.
#!/usr/bin/gdb --python
# vim: set filetype=python:
import sys
import optparse
import struct
import logging
import signal
import elftools.elf.elffile
import gdb
# gdbcall type: must match gdbcall.h
(GDBCALL_GDB,
GDBCALL_PYTHON,
GDBCALL_PYTHON_VA,
) = (
0xdeadbeef,
0xdead5a5a,
0xdead8877,
)
GDBCALL_TYPES = set( v for (k, v) in globals().iteritems() if k.startswith('GDBCALL_') )
def _parse_gdbcall_tlv(elffile, section):
(f, s) = (elffile, section)
if f.little_endian:
endian_fmt = '<'
else:
endian_fmt = '>'
int32_fmt = '%sI' % (endian_fmt,)
(offset, d) = (0, s.data())
while offset < len(d):
t = struct.unpack_from(int32_fmt, d, offset)[0]; offset += 4
l = struct.unpack_from(int32_fmt, d, offset)[0]; offset += 4
yield (t, (d, offset, l)); offset += l
def _parse_gdbcalls(filename):
'-> (type, value) ...'
f = elftools.elf.elffile.ELFFile(open(filename))
s = f.get_section_by_name('.note.gdbcall')
if s is None: # no section with the name
return []
if f.little_endian:
endian_fmt = '<'
else:
endian_fmt = '>'
if f.elfclass == 64:
(addr_fmt, addr_size) = ('Q', 8)
elif f.elfclass == 32:
(addr_fmt, addr_size) = ('I', 4)
addr_fmt = '%s%s' % (endian_fmt, addr_fmt)
type_with_addr = GDBCALL_TYPES
out = []
for (t, (d, offset, l)) in _parse_gdbcall_tlv(f, s):
if t not in type_with_addr:
logging.error(str((t, d, offset, l)))
assert(t in type_with_addr)
addr = struct.unpack_from(addr_fmt, d, offset)[0]
cmd = struct.unpack_from('%ds' % (l - addr_size,), d, offset + addr_size)[0]
cmd = ','.join(x for x in cmd.split('\x00') if x)
logging.debug('0x%x, 0x%08x, 0x%x, %d, %r' % (t, addr, offset, l, cmd))
out.append((t, addr, cmd))
return out
breakpoint_handler = {}
def _on_breakpoint_hit(e):
if not isinstance(e, gdb.BreakpointEvent):
return True
for b in e.breakpoints:
f = breakpoint_handler[b.number]
f()
gdb.execute('continue')
return True
def _gdbcall_setup_breakpoint(t, addr, cmd, options):
global breakpoint_handler
# parse addr, cmd from type
def run_command():
gdb.execute(cmd)
def run_python():
eval(cmd_code, globals())
bp = gdb.Breakpoint('*0x%x' % (addr,), internal=True)
if options.debug > 1:
bp.silent = False
else:
bp.silent = True
if t == GDBCALL_GDB:
breakpoint_handler[bp.number] = run_command
elif t == GDBCALL_PYTHON:
# unescape string, e.g. "f()" -> f()
cmd0 = eval(cmd)
cmd_code = compile(cmd0, '<gdbcall>', 'single')
breakpoint_handler[bp.number] = run_python
elif t == GDBCALL_PYTHON_VA:
cmd_code = compile(cmd, '<gdbcall>', 'single')
breakpoint_handler[bp.number] = run_python
else:
logging.info('gdbcall-load: unknown type: 0x%x, addr: 0x%x, cmd: %r' % (t, addr, cmd))
def _gdbcall_setup(filename, options):
gdb.events.stop.connect(_on_breakpoint_hit)
for (t, addr, cmd) in _parse_gdbcalls(filename):
_gdbcall_setup_breakpoint(t, addr, cmd, options)
def gdb_fix_argv():
'''
gdb breaks sys.argv[0] in the way it embeds Python
./gdb-python-script -> sys.argv: ['']
when argv should be: [ './gdb-python-script' ]
./gdb-python-script 0 1 2 -> sys.argv: ['0', '1', '2']
when argv should be should be: [ './gdb-python-script', '0', '1', '2' ]
'''
try:
f = open('/proc/%d/cmdline' % (os.getpid(),))
except IOError:
return
l = f.readline()
f.close()
cmdline = l.split('\x00')
if cmdline[-1] == '':
cmdline = cmdline[:-1]
if cmdline[0].endswith('gdb') and cmdline[1] == '--python':
sys.argv = cmdline[2:]
def on_inferior_exit(filename):
exitcode = gdb.parse_and_eval('$_exitcode')
if exitcode.type.code == gdb.TYPE_CODE_INT:
sys.exit(int(exitcode))
else:
siginfo = gdb.parse_and_eval('$_siginfo')
if siginfo.type.code != gdb.TYPE_CODE_VOID:
signal_names = dict( (getattr(signal, x), x)
for x in dir(signal) if x.startswith('SIG') )
sig = siginfo['si_signo']
msg = [ '%s terminated by signal %d' % (filename, sig,) ]
signame = signal_names.get(int(sig))
if signame is not None:
msg.append(' (%s)' % (signame,))
if sig == signal.SIGSEGV:
# $_siginfo._sifields._sigfault.si_addr
try:
addr = siginfo['_sifields']['_sigfault']['si_addr']
except gdb.error:
pass
if addr is not None:
msg.append(' while accessing address: %s' % (addr,))
logging.error(''.join(msg))
else:
logging.error('%s terminated abnormally\n' % (filename,))
sys.exit(3)
def main(args):
op = optparse.OptionParser(option_list = [
optparse.Option('-d', '--debug', type=int, default=0, help='debug level'),
])
(options, args) = op.parse_args(args)
if options.debug:
level = logging.DEBUG
else:
level = logging.WARNING
logging.basicConfig(format='%(message)s', level=level)
filename = args[0]
gdb.execute('set python print-stack full')
gdb.execute('set print inferior-events off')
_gdbcall_setup(filename, options)
gdb.execute('file %s' % (filename,))
sys.argv = args
gdb.execute('run')
on_inferior_exit(filename)
if __name__ == '__main__':
gdb_fix_argv()
main(sys.argv[1:])
#ifndef GDB_CALL_H
#define GDB_CALL_H
/* .note.gdbcall section format: TLV,
* Type (4 byte)
* Length (4 byte, number of bytes for the variable length value that follows.
* Does not include the 4 bytes for the length field itself.)
* Value (variable length)
*
*
* See:
* http://gcc.gnu.org/onlinedocs/gcc/Variadic-Macros.html
* http://sourceware.org/binutils/docs/as/Symbol-Names.html#index-local-labels-217
*/
#define GDB(cmd, args...) \
0: nop; \
.pushsection .note.gdbcall,"?","note"; \
.balign 4; \
.4byte 0xdeadbeef; /* type */ \
.4byte 2f-1f; /* length */ \
1: .8byte 0b; \
.asciz #cmd #args; \
.balign 4; \
2: .popsection;
#define GDBPY(stmt, args...) \
0: nop; \
.pushsection .note.gdbcall,"?","note"; \
.balign 4; \
.4byte 0xdead8877; /* type */ \
.4byte 2f-1f; /* length */ \
1: .8byte 0b; \
.asciz #stmt #args; \
.balign 4; \
2: .popsection;
#endif
#include <sys/syscall.h>
#include "gdbcall.h"
.section .text
.global _start
_start:
movq $3, %rdi
movq $0, %rsi
movq $2, %rdx
GDBPY(import sys, gdb)
GDBPY(gdb.execute('set variable $rdi = %d' % (int(sys.argv[1]),)))
call hanoi
/* exit_group(0) */
movq $SYS_exit_group, %rax
movq $0, %rdi
syscall
.type hanoi,STT_FUNC
hanoi:
cmp $1, %rdi
jg recurse
GDB(printf "move %d, %d, %d\n", $rdi - 1, $rsi, $rdx)
ret
recurse:
/* tmp = 0 + 1 + 2 - src - dst */
push %rbx
movq $3, %rbx
sub %rsi, %rbx
sub %rdx, %rbx
/* hanoi(n-1, src, tmp) */
push %rdi
dec %rdi
push %rsi
push %rdx
movq %rbx, %rdx
call hanoi
pop %rdx
pop %rsi
pop %rdi
GDB(printf "move %d, %d, %d\n", $rdi - 1, $rsi, $rdx)
/* hanoi(n-1, tmp, dst) */
dec %rdi
movq %rbx, %rsi
call hanoi
pop %rbx
ret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment