summaryrefslogtreecommitdiffstats
path: root/test_gdb.py
diff options
context:
space:
mode:
authorDavid Malcolm <dmalcolm@redhat.com>2010-02-26 23:51:19 -0500
committerDavid Malcolm <dmalcolm@redhat.com>2010-02-26 23:51:19 -0500
commit64296d87c8db921ec58528d5a460a1f1ded1219c (patch)
treee33649e3a09e5dd91ed969d896279706174d6321 /test_gdb.py
parent21dc2fe23c9186f67f7170c70e69127e2a317269 (diff)
downloadlibpython-64296d87c8db921ec58528d5a460a1f1ded1219c.tar.gz
libpython-64296d87c8db921ec58528d5a460a1f1ded1219c.tar.xz
libpython-64296d87c8db921ec58528d5a460a1f1ded1219c.zip
Add tests of the handling of corrupt data
Rework get_stack_trace to support the injection of additional gdb commands after hitting the breakpoint, before running "bt" Rework get_gdb_repr so that it captures the value in frame #0 of the backtrace, rather than the value when the breakpoint hits. Add four more unit tests, using the above to corrupt the data before getting the backtrace representation.
Diffstat (limited to 'test_gdb.py')
-rw-r--r--test_gdb.py78
1 files changed, 63 insertions, 15 deletions
diff --git a/test_gdb.py b/test_gdb.py
index 36aa8de..e9ce82a 100644
--- a/test_gdb.py
+++ b/test_gdb.py
@@ -43,11 +43,16 @@ class DebuggerTests(unittest.TestCase):
).communicate()
return out, err
- def get_stack_trace(self, source, breakpoint='PyObject_Print'):
+ def get_stack_trace(self, source, breakpoint='PyObject_Print',
+ commands_after_breakpoint=None):
'''
Run 'python -c SOURCE' under gdb with a breakpoint.
+ Support injecting commands after the breakpoint is reached
+
Returns the stdout from gdb
+
+ commands_after_breakpoint: if provided, a list of strings: gdb commands
'''
# We use "set breakpoint pending yes" to avoid blocking with a:
# Function "foo" not defined.
@@ -61,14 +66,28 @@ class DebuggerTests(unittest.TestCase):
# Initially I had "--eval-command=continue" here, but removed it to
# avoid repeated print breakpoints when traversing hierarchical data
# structures
- out, err = self.run_gdb("gdb", "--batch",
- "--eval-command=set breakpoint pending yes",
- "--eval-command=break %s" % breakpoint,
-
- "--eval-command=run",
- "--eval-command=backtrace",
- "--args",
- sys.executable, "-S", "-c", source)
+
+ # Generate a list of commands in gdb's language:
+ commands = ['set breakpoint pending yes',
+ 'break %s' % breakpoint,
+ 'run']
+ if commands_after_breakpoint:
+ commands += commands_after_breakpoint
+ commands += ['backtrace']
+
+ # print commands
+
+ # Use "commands" to generate the arguments with which to invoke "gdb":
+ args = ["gdb", "--batch"]
+ args += ['--eval-command=%s' % cmd for cmd in commands]
+ args += ["--args",
+ sys.executable, "-S", "-c", source]
+ # -S suppresses the default 'import site'
+
+ # print args
+
+ # Use "args" to invoke gdb, capturing stdout, stderr:
+ out, err = self.run_gdb(*args)
# Ignore some noise on stderr due to the pending breakpoint:
err = err.replace('Function "%s" not defined.\n' % breakpoint, '')
@@ -78,7 +97,8 @@ class DebuggerTests(unittest.TestCase):
return out
- def get_gdb_repr(self, source):
+ def get_gdb_repr(self, source,
+ commands_after_breakpoint=None):
# Given an input python source representation of data,
# run "python -c'print DATA'" under gdb with a breakpoint on
# PyObject_Print and scrape out gdb's representation of the "op"
@@ -86,19 +106,19 @@ class DebuggerTests(unittest.TestCase):
#
# For a nested structure, the first time we hit the breakpoint will
# give us the top-level structure
- gdb_output = self.get_stack_trace(source)
- m = re.match('.*Breakpoint 1, PyObject_Print \(op\=(.*?), fp=.*\).*', gdb_output, re.DOTALL)
- # print m.groups()
+ gdb_output = self.get_stack_trace(source, 'PyObject_Print', commands_after_breakpoint)
+ m = re.match('.*#0 PyObject_Print \(op\=(.*?), fp=.*\).*', gdb_output, re.DOTALL)
+ #print m.groups()
return m.group(1), gdb_output
def test_getting_backtrace(self):
gdb_output = self.get_stack_trace('print 42')
self.assertTrue('PyObject_Print' in gdb_output)
- def assertGdbRepr(self, val):
+ def assertGdbRepr(self, val, commands_after_breakpoint=None):
# Ensure that gdb's rendering of the value in a debugged process
# matches repr(value) in this process:
- gdb_repr, gdb_output = self.get_gdb_repr('print ' + repr(val))
+ gdb_repr, gdb_output = self.get_gdb_repr('print ' + repr(val), commands_after_breakpoint)
self.assertEquals(gdb_repr, repr(val), gdb_output)
def test_int(self):
@@ -139,6 +159,14 @@ class DebuggerTests(unittest.TestCase):
self.assertGdbRepr( u'hello world', )
self.assertGdbRepr( u'\u2620')
+ def assert_is_failsafe_repr(self, gdb_repr, exp_type='unknown'):
+ '''Verify that the given gdb_repr string is the expected failsafe
+ representation for when there's corrupt data within the inferior
+ process'''
+ self.assertTrue(re.match('<%s at remote 0x[0-9a-f]+>' % exp_type,
+ gdb_repr),
+ 'Unexpected gdb representation: %r' % gdb_repr)
+
def test_classic_class(self):
gdb_repr, gdb_output = self.get_gdb_repr('''
class Foo:
@@ -149,6 +177,26 @@ print foo''')
# FIXME: is there an "assertMatches"; should there be?
m = re.match(r'<Foo\(an_int=42\) at remote 0x[0-9a-f]+>', gdb_repr)
self.assertTrue(m, msg='Unexpected classic-class rendering %r' % gdb_repr)
+
+ def test_NULL_ob_type(self):
+ gdb_repr, gdb_output = self.get_gdb_repr('print 42',
+ commands_after_breakpoint=['set op->ob_type=0'])
+ self.assert_is_failsafe_repr(gdb_repr)
+
+ def test_corrupt_ob_type(self):
+ gdb_repr, gdb_output = self.get_gdb_repr('print "this string will have its ob_type corrupted"',
+ commands_after_breakpoint=['set op->ob_type=0xDEADBEEF'])
+ self.assert_is_failsafe_repr(gdb_repr)
+
+ def test_corrupt_tp_flags(self):
+ gdb_repr, gdb_output = self.get_gdb_repr('print 42',
+ commands_after_breakpoint=['set op->ob_type->tp_flags=0x0'])
+ self.assert_is_failsafe_repr(gdb_repr, exp_type='int')
+
+ def test_corrupt_tp_name(self):
+ gdb_repr, gdb_output = self.get_gdb_repr('print 42',
+ commands_after_breakpoint=['set op->ob_type->tp_name=0xDEADBEEF'])
+ self.assert_is_failsafe_repr(gdb_repr)
# TODO:
# new-style classes