400
475
+ _typename = 'PyDictObject'
402
+ def proxyval(self):
477
+ def proxyval(self, visited):
478
+ # Guard against infinite loops:
479
+ if self.as_address() in visited:
480
+ return ProxyAlreadyVisited('{...}')
481
+ visited.add(self.as_address())
404
484
+ for i in safe_range(self.field('ma_mask') + 1):
405
485
+ ep = self.field('ma_table') + i
406
486
+ pvalue = PyObjectPtr.from_pyobject_ptr(ep['me_value'])
407
487
+ if not pvalue.is_null():
408
488
+ pkey = PyObjectPtr.from_pyobject_ptr(ep['me_key'])
409
+ result[pkey.proxyval()] = pvalue.proxyval()
489
+ result[pkey.proxyval(visited)] = pvalue.proxyval(visited)
413
493
+class PyInstanceObjectPtr(PyObjectPtr):
414
494
+ _typename = 'PyInstanceObject'
416
+ def proxyval(self):
496
+ def proxyval(self, visited):
497
+ # Guard against infinite loops:
498
+ if self.as_address() in visited:
499
+ return ProxyAlreadyVisited('<...>')
500
+ visited.add(self.as_address())
417
502
+ # Get name of class:
418
503
+ in_class = PyObjectPtr.from_pyobject_ptr(self.field('in_class'))
419
+ cl_name = PyObjectPtr.from_pyobject_ptr(in_class.field('cl_name')).proxyval()
504
+ cl_name = PyObjectPtr.from_pyobject_ptr(in_class.field('cl_name')).proxyval(visited)
421
506
+ # Get dictionary of instance attributes:
422
+ in_dict = PyObjectPtr.from_pyobject_ptr(self.field('in_dict')).proxyval()
507
+ in_dict = PyObjectPtr.from_pyobject_ptr(self.field('in_dict')).proxyval(visited)
424
509
+ # Old-style class:
425
510
+ return InstanceProxy(cl_name, in_dict, long(self._gdbval))
592
711
+ if not value.is_null():
593
712
+ name = PyObjectPtr.from_pyobject_ptr(self.co_varnames[i])
594
713
+ #print 'name=%s' % name
595
+ value = value.proxyval()
714
+ value = value.proxyval(set())
596
715
+ #print 'value=%s' % value
597
716
+ self.locals.append((str(name), value))
718
+ def filename(self):
719
+ '''Get the path of the current Python source file, as a string'''
720
+ return self.co_filename.proxyval(set())
722
+ def current_line_num(self):
723
+ '''Get current line number as an integer (1-based)
725
+ Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
727
+ See Objects/lnotab_notes.txt
729
+ f_trace = self.fval.field('f_trace')
730
+ if long(f_trace) != 0:
731
+ # we have a non-NULL f_trace:
732
+ return self.f_lineno
735
+ return self.co.addr2line(self.f_lasti)
736
+ #except ValueError:
737
+ # return self.f_lineno
739
+ def current_line(self):
740
+ '''Get the text of the current source line as a string, with a trailing
741
+ newline character'''
742
+ with open(self.filename(), 'r') as f:
743
+ all_lines = f.readlines()
744
+ # Convert from 1-based current_line_num to 0-based list offset:
745
+ return all_lines[self.current_line_num()-1]
599
747
+ def __str__(self):
600
+ return ('File %s, line %i, in %s (%s)'
601
+ % (self.co_filename,
748
+ return ('Frame 0x%x, for file %s, line %i, in %s (%s)'
749
+ % (long(self.fval._gdbval),
751
+ self.current_line_num(),
604
753
+ ', '.join(['%s=%s' % (k, stringify(v)) for k, v in self.locals]))
663
812
+ obj.pretty_printers.append(pretty_printer_lookup)
665
814
+register (gdb.current_objfile ())
816
+def get_python_frame(gdb_frame):
818
+ f = gdb_frame.read_var('f')
819
+ return PyFrameObjectPtr.from_pyobject_ptr(f)
823
+def get_selected_python_frame():
824
+ '''Try to obtain a (gdbframe, PyFrameObjectPtr) pair for the
825
+ currently-running python code, or (None, None)'''
826
+ gdb_frame = gdb.selected_frame()
828
+ if (gdb_frame.function() is None or
829
+ gdb_frame.function().name != 'PyEval_EvalFrameEx'):
830
+ gdb_frame = gdb_frame.older()
834
+ f = gdb_frame.read_var('f')
835
+ return gdb_frame, PyFrameObjectPtr.from_pyobject_ptr(f)
837
+ gdb_frame = gdb_frame.older()
840
+class PyList(gdb.Command):
841
+ '''List the current Python source code, if any
845
+ to list at a different line number within the python source.
849
+ to list a specific range of lines within the python source.
852
+ def __init__(self):
853
+ gdb.Command.__init__ (self,
859
+ def invoke(self, args, from_tty):
865
+ m = re.match(r'\s*(\d+)\s*', args)
867
+ start = int(m.group(0))
870
+ m = re.match(r'\s*(\d+)\s*,\s*(\d+)\s*', args)
872
+ start, end = map(int, m.groups())
874
+ gdb_frame, py_frame = get_selected_python_frame()
876
+ print 'Unable to locate python frame'
879
+ fi = FrameInfo(py_frame)
880
+ filename = fi.filename()
881
+ lineno = fi.current_line_num()
890
+ with open(filename, 'r') as f:
891
+ all_lines = f.readlines()
892
+ # start and end are 1-based, all_lines is 0-based;
893
+ # so [start-1:end] as a python slice gives us [start, end] as a
895
+ for i, line in enumerate(all_lines[start-1:end]):
896
+ sys.stdout.write('%4s %s' % (i+start, line))
899
+# ...and register the command:
902
+def move_in_stack(move_up):
903
+ '''Move up or down the stack (for the py-up/py-down command)'''
904
+ gdb_frame, py_frame = get_selected_python_frame()
907
+ iter_frame = gdb_frame.older()
909
+ iter_frame = gdb_frame.newer()
914
+ if (iter_frame.function() and
915
+ iter_frame.function().name == 'PyEval_EvalFrameEx'):
917
+ iter_frame.select()
918
+ py_frame = get_python_frame(iter_frame)
919
+ fi = FrameInfo(py_frame)
921
+ sys.stdout.write(fi.current_line())
924
+ gdb_frame = iter_frame
927
+ print 'Unable to find an older python frame'
929
+ print 'Unable to find a newer python frame'
931
+class PyUp(gdb.Command):
932
+ 'Select and print the python stack frame that called this one (if any)'
933
+ def __init__(self):
934
+ gdb.Command.__init__ (self,
940
+ def invoke(self, args, from_tty):
941
+ move_in_stack(move_up=True)
945
+class PyDown(gdb.Command):
946
+ 'Select and print the python stack frame called by this one (if any)'
947
+ def __init__(self):
948
+ gdb.Command.__init__ (self,
954
+ def invoke(self, args, from_tty):
955
+ move_in_stack(move_up=False)
959
+class PyBacktrace(gdb.Command):
960
+ 'Display the current python frame and all the frames within its call stack (if any)'
961
+ def __init__(self):
962
+ gdb.Command.__init__ (self,
968
+ def invoke(self, args, from_tty):
969
+ gdb_frame, py_frame = get_selected_python_frame()
971
+ gdb_frame = gdb_frame.older()
976
+ if (gdb_frame.function() and
977
+ gdb_frame.function().name == 'PyEval_EvalFrameEx'):
978
+ py_frame = get_python_frame(gdb_frame)
979
+ fi = FrameInfo(py_frame)
981
+ sys.stdout.write(fi.current_line())
666
984
Index: Tools/README
667
985
===================================================================
668
--- Tools/README (revision 78520)
986
--- Tools/README (revision 79422)
669
987
+++ Tools/README (working copy)
670
988
@@ -20,6 +20,9 @@
794
1142
+ # For a nested structure, the first time we hit the breakpoint will
795
1143
+ # give us the top-level structure
796
+ gdb_output = self.get_stack_trace(source, 'PyObject_Print',
797
+ commands_after_breakpoint)
1144
+ gdb_output = self.get_stack_trace(source, breakpoint='PyObject_Print',
1145
+ cmds_after_breakpoint=cmds_after_breakpoint,
1146
+ import_site=import_site)
798
1147
+ m = re.match('.*#0 PyObject_Print \(op\=(.*?), fp=.*\).*',
799
1148
+ gdb_output, re.DOTALL)
800
1149
+ #print m.groups()
801
1150
+ return m.group(1), gdb_output
1152
+ def assertEndsWith(self, actual, exp_end):
1153
+ '''Ensure that the given "actual" string ends with "exp_end"'''
1154
+ self.assert_(actual.endswith(exp_end),
1155
+ msg='%r did not end with %r' % (actual, exp_end))
1157
+ def assertMultilineMatches(self, actual, pattern):
1158
+ m = re.match(pattern, actual, re.DOTALL)
1160
+ msg='%r did not match %r' % (actual, pattern))
1162
+class PrettyPrintTests(DebuggerTests):
803
1163
+ def test_getting_backtrace(self):
804
1164
+ gdb_output = self.get_stack_trace('print 42')
805
1165
+ self.assertTrue('PyObject_Print' in gdb_output)
807
+ def assertGdbRepr(self, val, commands_after_breakpoint=None):
1167
+ def assertGdbRepr(self, val, cmds_after_breakpoint=None):
808
1168
+ # Ensure that gdb's rendering of the value in a debugged process
809
1169
+ # matches repr(value) in this process:
810
1170
+ gdb_repr, gdb_output = self.get_gdb_repr('print ' + repr(val),
811
+ commands_after_breakpoint)
1171
+ cmds_after_breakpoint)
812
1172
+ self.assertEquals(gdb_repr, repr(val), gdb_output)
814
1174
+ def test_int(self):
1175
+ 'Verify the pretty-printing of various "int" values'
815
1176
+ self.assertGdbRepr(42)
816
1177
+ self.assertGdbRepr(0)
817
1178
+ self.assertGdbRepr(-7)
819
1180
+ self.assertGdbRepr(-sys.maxint)
821
1182
+ def test_long(self):
1183
+ 'Verify the pretty-printing of various "long" values'
822
1184
+ self.assertGdbRepr(0L)
823
1185
+ self.assertGdbRepr(1000000000000L)
824
1186
+ self.assertGdbRepr(-1L)
825
1187
+ self.assertGdbRepr(-1000000000000000L)
827
1189
+ def test_singletons(self):
1190
+ 'Verify the pretty-printing of True, False and None'
828
1191
+ self.assertGdbRepr(True)
829
1192
+ self.assertGdbRepr(False)
830
1193
+ self.assertGdbRepr(None)
832
1195
+ def test_dicts(self):
1196
+ 'Verify the pretty-printing of dictionaries'
833
1197
+ self.assertGdbRepr({})
834
1198
+ self.assertGdbRepr({'foo': 'bar'})
836
1200
+ def test_lists(self):
1201
+ 'Verify the pretty-printing of lists'
837
1202
+ self.assertGdbRepr([])
838
1203
+ self.assertGdbRepr(range(5))
840
1205
+ def test_strings(self):
1206
+ 'Verify the pretty-printing of strings'
841
1207
+ self.assertGdbRepr('')
842
1208
+ self.assertGdbRepr('And now for something hopefully the same')
1209
+ self.assertGdbRepr('string with embedded NUL here \0 and then some more text')
1210
+ self.assertGdbRepr('this is byte 255:\xff and byte 128:\x80')
844
1212
+ def test_tuples(self):
1213
+ 'Verify the pretty-printing of tuples'
845
1214
+ self.assertGdbRepr(tuple())
846
1215
+ self.assertGdbRepr((1,))
848
1217
+ def test_unicode(self):
1218
+ 'Verify the pretty-printing of unicode values'
849
1219
+ self.assertGdbRepr(u'hello world')
850
1220
+ self.assertGdbRepr(u'\u2620')
1222
+ def test_sets(self):
1223
+ 'Verify the pretty-printing of sets'
1224
+ self.assertGdbRepr(set())
1225
+ self.assertGdbRepr(set(['a','b','c']))
1226
+ self.assertGdbRepr(set([4, 5, 6]))
1228
+ # Ensure that we handled sets containing the "dummy" key value,
1229
+ # which happens on deletion:
1230
+ gdb_repr, gdb_output = self.get_gdb_repr('''s = set(['a','b'])
1233
+ self.assertEquals(gdb_repr, "set(['b'])")
1235
+ def test_frozensets(self):
1236
+ 'Verify the pretty-printing of frozensets'
1237
+ self.assertGdbRepr(frozenset())
1238
+ self.assertGdbRepr(frozenset(['a','b','c']))
1239
+ self.assertGdbRepr(frozenset([4, 5, 6]))
1241
+ def test_exceptions(self):
1242
+ # Test a RuntimeError
1243
+ gdb_repr, gdb_output = self.get_gdb_repr('''
1245
+ raise RuntimeError("I am an error")
1246
+except RuntimeError, e:
1249
+ self.assertEquals(gdb_repr,
1250
+ "exceptions.RuntimeError('I am an error',)")
1253
+ # Test division by zero:
1254
+ gdb_repr, gdb_output = self.get_gdb_repr('''
1257
+except ZeroDivisionError, e:
1260
+ self.assertEquals(gdb_repr,
1261
+ "exceptions.ZeroDivisionError('integer division or modulo by zero',)")
852
1263
+ def test_classic_class(self):
1264
+ 'Verify the pretty-printing of classic class instances'
853
1265
+ gdb_repr, gdb_output = self.get_gdb_repr('''
913
1333
+ def test_NULL_ptr(self):
914
1334
+ 'Ensure that a NULL PyObject* is handled gracefully'
915
+ self.assertSane('print 42',
916
+ 'set variable op=0')
1335
+ gdb_repr, gdb_output = (
1336
+ self.get_gdb_repr('print 42',
1337
+ cmds_after_breakpoint=['set variable op=0',
1341
+ self.assertEquals(gdb_repr, '0x0')
918
1343
+ def test_NULL_ob_type(self):
1344
+ 'Ensure that a PyObject* with NULL ob_type is handled gracefully'
919
1345
+ self.assertSane('print 42',
920
1346
+ 'set op->ob_type=0')
922
1348
+ def test_corrupt_ob_type(self):
1349
+ 'Ensure that a PyObject* with a corrupt ob_type is handled gracefully'
923
1350
+ self.assertSane('print 42',
924
1351
+ 'set op->ob_type=0xDEADBEEF')
926
1353
+ def test_corrupt_tp_flags(self):
1354
+ 'Ensure that a PyObject* with a type with corrupt tp_flags is handled'
927
1355
+ self.assertSane('print 42',
928
1356
+ 'set op->ob_type->tp_flags=0x0',
929
1357
+ exp_type='int')
931
1359
+ def test_corrupt_tp_name(self):
1360
+ 'Ensure that a PyObject* with a type with corrupt tp_name is handled'
932
1361
+ self.assertSane('print 42',
933
1362
+ 'set op->ob_type->tp_name=0xDEADBEEF')
1364
+ def test_NULL_instance_dict(self):
1365
+ 'Ensure that a PyInstanceObject with with a NULL in_dict is handled'
1366
+ self.assertSane('''
1372
+ 'set ((PyInstanceObject*)op)->in_dict = 0',
1375
+ def test_builtins_help(self):
1376
+ 'Ensure that the new-style class _Helper in site.py can be handled'
1377
+ # (this was the issue causing tracebacks in
1378
+ # http://bugs.python.org/issue8032#msg100537 )
1380
+ gdb_repr, gdb_output = self.get_gdb_repr('print __builtins__.help', import_site=True)
1381
+ m = re.match(r'<_Helper at remote 0x[0-9a-f]+>', gdb_repr)
1382
+ self.assertTrue(m,
1383
+ msg='Unexpected rendering %r' % gdb_repr)
1385
+ def test_selfreferential_list(self):
1386
+ '''Ensure that a reference loop involving a list doesn't lead proxyval
1387
+ into an infinite loop:'''
1388
+ gdb_repr, gdb_output = \
1389
+ self.get_gdb_repr("a = [3, 4, 5] ; a.append(a) ; print a")
1391
+ self.assertEquals(gdb_repr, '[3, 4, 5, [...]]')
1393
+ gdb_repr, gdb_output = \
1394
+ self.get_gdb_repr("a = [3, 4, 5] ; b = [a] ; a.append(b) ; print a")
1396
+ self.assertEquals(gdb_repr, '[3, 4, 5, [[...]]]')
1398
+ def test_selfreferential_dict(self):
1399
+ '''Ensure that a reference loop involving a dict doesn't lead proxyval
1400
+ into an infinite loop:'''
1401
+ gdb_repr, gdb_output = \
1402
+ self.get_gdb_repr("a = {} ; b = {'bar':a} ; a['foo'] = b ; print a")
1404
+ self.assertEquals(gdb_repr, "{'foo': {'bar': {...}}}")
1406
+ def test_selfreferential_old_style_instance(self):
1407
+ gdb_repr, gdb_output = \
1408
+ self.get_gdb_repr('''
1414
+ self.assertTrue(re.match('<Foo\(an_attr=<\.\.\.>\) at remote 0x[0-9a-f]+>',
1416
+ 'Unexpected gdb representation: %r\n%s' % \
1417
+ (gdb_repr, gdb_output))
1419
+ def test_selfreferential_new_style_instance(self):
1420
+ gdb_repr, gdb_output = \
1421
+ self.get_gdb_repr('''
1427
+ self.assertTrue(re.match('<Foo\(an_attr=<\.\.\.>\) at remote 0x[0-9a-f]+>',
1429
+ 'Unexpected gdb representation: %r\n%s' % \
1430
+ (gdb_repr, gdb_output))
1432
+ gdb_repr, gdb_output = \
1433
+ self.get_gdb_repr('''
1441
+ self.assertTrue(re.match('<Foo\(an_attr=<Foo\(an_attr=<\.\.\.>\) at remote 0x[0-9a-f]+>\) at remote 0x[0-9a-f]+>',
1443
+ 'Unexpected gdb representation: %r\n%s' % \
1444
+ (gdb_repr, gdb_output))
1450
+class PyListTests(DebuggerTests):
1451
+ def assertListing(self, expected, actual):
1452
+ self.assertEndsWith(actual, expected)
1454
+ def test_basic_command(self):
1455
+ 'Verify that the "py-list" command works'
1456
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1457
+ cmds_after_breakpoint=['py-list'])
1459
+ self.assertListing('''
1461
+ 6 def bar(a, b, c):
1471
+ def test_one_abs_arg(self):
1472
+ 'Verify the "py-list" command with one absolute argument'
1473
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1474
+ cmds_after_breakpoint=['py-list 9'])
1476
+ self.assertListing('''
1484
+ def test_two_abs_args(self):
1485
+ 'Verify the "py-list" command with two absolute arguments'
1486
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1487
+ cmds_after_breakpoint=['py-list 1,3'])
1489
+ self.assertListing('''
1490
+ 1 # Sample script for use by test_gdb.py
1492
+ 3 def foo(a, b, c):
1496
+class StackNavigationTests(DebuggerTests):
1497
+ def test_pyup_command(self):
1498
+ 'Verify that the "py-up" command works'
1499
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1500
+ cmds_after_breakpoint=['py-up'])
1501
+ self.assertMultilineMatches(bt,
1503
+Frame 0x[0-9a-f]+, for file Lib/test/test_gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
1507
+ def test_down_at_bottom(self):
1508
+ 'Verify handling of "py-down" at the bottom of the stack'
1509
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1510
+ cmds_after_breakpoint=['py-down'])
1511
+ self.assertEndsWith(bt,
1512
+ 'Unable to find a newer python frame\n')
1514
+ def test_up_at_top(self):
1515
+ 'Verify handling of "py-up" at the top of the stack'
1516
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1517
+ cmds_after_breakpoint=['py-up'] * 4)
1518
+ self.assertEndsWith(bt,
1519
+ 'Unable to find an older python frame\n')
1521
+ def test_up_then_down(self):
1522
+ 'Verify "py-up" followed by "py-down"'
1523
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1524
+ cmds_after_breakpoint=['py-up', 'py-down'])
1525
+ self.assertMultilineMatches(bt,
1527
+Frame 0x[0-9a-f]+, for file Lib/test/test_gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
1529
+Frame 0x[0-9a-f]+, for file Lib/test/test_gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\)
1533
+class PyBtTests(DebuggerTests):
1534
+ def test_basic_command(self):
1535
+ 'Verify that the "py-bt" command works'
1536
+ bt = self.get_stack_trace(script='Lib/test/test_gdb_sample.py',
1537
+ cmds_after_breakpoint=['py-bt'])
1538
+ self.assertMultilineMatches(bt,
1540
+ Frame 0x[0-9a-f]+, for file Lib/test/test_gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\)
1542
+ Frame 0x[0-9a-f]+, for file Lib/test/test_gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\)
1544
+ Frame 0x[0-9a-f]+, for file Lib/test/test_gdb_sample.py, line 12, in <module> \(\)
939
1548
+def test_main():
940
+ run_unittest(DebuggerTests)
1549
+ run_unittest(PrettyPrintTests,
1551
+ StackNavigationTests,
942
1554
+if __name__ == "__main__":
944
1556
Index: Makefile.pre.in
945
1557
===================================================================
946
--- Makefile.pre.in (revision 78520)
1558
--- Makefile.pre.in (revision 79422)
947
1559
+++ Makefile.pre.in (working copy)
950
1562
# Default target