summaryrefslogtreecommitdiffstats
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
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.
-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