diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py --- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -44,7 +44,7 @@ PYTHONHASHSEED = '123' -def run_gdb(*args, **env_vars): +def run_gdb(*args, cmds_after_breakpoint=None, **env_vars): """Runs gdb in --batch mode with the additional arguments given by *args. Returns its (stdout, stderr) decoded from utf-8 using the replace handler. @@ -54,38 +54,62 @@ env.update(env_vars) else: env = None + + if cmds_after_breakpoint: + mi_cmds = ['-interpreter-exec console "%s"' % cmd + for cmd in cmds_after_breakpoint] + mi_cmds = '\n'.join(mi_cmds).encode('utf-8', 'replace') + else: + mi_cmds = b'' + # -nx: Do not execute commands from any .gdbinit initialization files # (issue #22188) - base_cmd = ('gdb', '--batch', '-nx') + base_cmd = ('gdb', '-nx') if (gdb_major_version, gdb_minor_version) >= (7, 4): base_cmd += ('-iex', 'add-auto-load-safe-path ' + checkout_hook_path) - out, err = subprocess.Popen(base_cmd + args, + out, err = subprocess.Popen(base_cmd + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, - ).communicate() + ).communicate(input=mi_cmds) return out.decode('utf-8', 'replace'), err.decode('utf-8', 'replace') # Verify that "gdb" was built with the embedded python support enabled: -gdbpy_version, _ = run_gdb("--eval-command=python import sys; print(sys.version_info)") +gdbpy_version, _ = run_gdb("--batch", + "--eval-command=python import sys; print(sys.version_info)") if not gdbpy_version: raise unittest.SkipTest("gdb not built with embedded python support") # Verify that "gdb" can load our custom hooks, as OS security settings may # disallow this without a customised .gdbinit. cmd = ['--args', sys.executable] -_, gdbpy_errors = run_gdb('--args', sys.executable) +_, gdbpy_errors = run_gdb('--batch', '--args', sys.executable) if "auto-loading has been declined" in gdbpy_errors: msg = "gdb security settings prevent use of custom hooks: " raise unittest.SkipTest(msg + gdbpy_errors.rstrip()) def gdb_has_frame_select(): # Does this build of gdb have gdb.Frame.select ? - stdout, _ = run_gdb("--eval-command=python print(dir(gdb.Frame))") + stdout, _ = run_gdb("--batch", + "--eval-command=python print(dir(gdb.Frame))") m = re.match(r'.*\[(.*)\].*', stdout) if not m: raise unittest.SkipTest("Unable to parse output from gdb.Frame.select test") gdb_frame_dir = m.group(1).split(', ') return "'select'" in gdb_frame_dir +def gdbmi_console(out): + """Select the gdb/mi console output stream records. + """ + gdmi_cmds = [] + for line in out.splitlines(): + if line.startswith('~"'): + line = line[2:-1] + if line.endswith(r'\n'): + line = line[:-2] + line = line.replace(r'\\', '\\') + line = line.replace(r'\"', '"') + gdmi_cmds.append(line) + return '\n'.join(gdmi_cmds) + HAS_PYUP_PYDOWN = gdb_has_frame_select() BREAKPOINT_FN='builtin_id' @@ -122,17 +146,15 @@ # Generate a list of commands in gdb's language: commands = ['set breakpoint pending yes', - 'break %s' % breakpoint, - 'run'] - if cmds_after_breakpoint: - commands += cmds_after_breakpoint - else: - commands += ['backtrace'] + 'break %s' % breakpoint] + if not cmds_after_breakpoint: + cmds_after_breakpoint = ['backtrace'] + cmds_after_breakpoint.insert(0, 'run') # print commands # Use "commands" to generate the arguments with which to invoke "gdb": - args = ["gdb", "--batch", "-nx"] + args = ["--interpreter=mi", "-quiet"] args += ['--eval-command=%s' % cmd for cmd in commands] args += ["--args", sys.executable] @@ -150,38 +172,9 @@ # print (' '.join(args)) # Use "args" to invoke gdb, capturing stdout, stderr: - out, err = run_gdb(*args, PYTHONHASHSEED=PYTHONHASHSEED) - - errlines = err.splitlines() - unexpected_errlines = [] - - # Ignore some benign messages on stderr. - ignore_patterns = ( - 'Function "%s" not defined.' % breakpoint, - "warning: no loadable sections found in added symbol-file" - " system-supplied DSO", - "warning: Unable to find libthread_db matching" - " inferior's thread library, thread debugging will" - " not be available.", - "warning: Cannot initialize thread debugging" - " library: Debugger service failed", - 'warning: Could not load shared library symbols for ' - 'linux-vdso.so', - 'warning: Could not load shared library symbols for ' - 'linux-gate.so', - 'Do you need "set solib-search-path" or ' - '"set sysroot"?', - 'warning: Source file is more recent than executable.', - # Issue #19753: missing symbols on System Z - 'Missing separate debuginfo for ', - 'Try: zypper install -C ', - ) - for line in errlines: - if not line.startswith(ignore_patterns): - unexpected_errlines.append(line) - - # Ensure no unexpected error messages: - self.assertEqual(unexpected_errlines, []) + out, err = run_gdb(*args, cmds_after_breakpoint=cmds_after_breakpoint, + PYTHONHASHSEED=PYTHONHASHSEED) + out = gdbmi_console(out) return out def get_gdb_repr(self, source, @@ -231,10 +224,22 @@ gdb_output = self.get_stack_trace('id(42)') self.assertTrue(BREAKPOINT_FN in gdb_output) - def assertGdbRepr(self, val, exp_repr=None): + def assertGdbRepr(self, val, exp_repr=None, decode=False): # 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('id(' + ascii(val) + ')') + + # Decode a binary representation filtered by gdb/mi and represented by + # gdb/mi in octal. + if decode: + repr_gdb_repr = repr(gdb_repr).strip('"\'') + self.assertEqual( + repr(''.join(re.findall(r'(\\\d\d\d)', repr_gdb_repr))), + "'" + repr_gdb_repr + "'") + gdb_repr = repr(bytes((int(x, base=8) for x + in repr_gdb_repr.split('\\') + if x)).decode('utf-8', 'replace')) + if not exp_repr: exp_repr = repr(val) self.assertEqual(gdb_repr, exp_repr, @@ -288,9 +293,9 @@ text.encode(encoding) printable = True except UnicodeEncodeError: - self.assertGdbRepr(text, ascii(text)) + self.assertGdbRepr(text, ascii(text), decode=True) else: - self.assertGdbRepr(text) + self.assertGdbRepr(text, decode=True) self.assertGdbRepr('') self.assertGdbRepr('And now for something hopefully the same') @@ -580,7 +585,7 @@ breakpoint='builtin_id', cmds_after_breakpoint=['print (PyFrameObject*)(((PyCodeObject*)v)->co_zombieframe)'] ) - self.assertTrue(re.match('.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file , line 3, in foo \(\)\s+.*', + self.assertTrue(re.match('.*\s+\$1 =\s+Frame 0x-?[0-9a-f]+, for file , line 3, in foo \(\).*', gdb_output, re.DOTALL), 'Unexpected gdb representation: %r\n%s' % (gdb_output, gdb_output)) @@ -603,7 +608,7 @@ ' 9 def baz(*args):\n' ' >10 id(42)\n' ' 11 \n' - ' 12 foo(1, 2, 3)\n', + ' 12 foo(1, 2, 3)', bt) def test_one_abs_arg(self): @@ -614,7 +619,7 @@ self.assertListing(' 9 def baz(*args):\n' ' >10 id(42)\n' ' 11 \n' - ' 12 foo(1, 2, 3)\n', + ' 12 foo(1, 2, 3)', bt) def test_two_abs_args(self): @@ -624,7 +629,7 @@ self.assertListing(' 1 # Sample script for use by test_gdb.py\n' ' 2 \n' - ' 3 def foo(a, b, c):\n', + ' 3 def foo(a, b, c):', bt) class StackNavigationTests(DebuggerTests): @@ -638,8 +643,7 @@ self.assertMultilineMatches(bt, r'''^.* #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\) - baz\(a, b, c\) -$''') + baz\(a, b, c\)$''') @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands") def test_down_at_bottom(self): @@ -647,7 +651,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-down']) self.assertEndsWith(bt, - 'Unable to find a newer python frame\n') + 'Unable to find a newer python frame') @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands") def test_up_at_top(self): @@ -655,7 +659,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-up'] * 4) self.assertEndsWith(bt, - 'Unable to find an older python frame\n') + 'Unable to find an older python frame') @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands") @unittest.skipIf(python_is_optimized(), @@ -669,8 +673,7 @@ #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 7, in bar \(a=1, b=2, c=3\) baz\(a, b, c\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 10, in baz \(args=\(1, 2, 3\)\) - id\(42\) -$''') + id\(42\)$''') class PyBtTests(DebuggerTests): @unittest.skipIf(python_is_optimized(), @@ -689,8 +692,7 @@ File ".*gdb_sample.py", line 4, in foo bar\(a, b, c\) File ".*gdb_sample.py", line 12, in - foo\(1, 2, 3\) -''') + foo\(1, 2, 3\)''') @unittest.skipIf(python_is_optimized(), "Python was compiled with optimizations") @@ -705,8 +707,7 @@ #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 4, in foo \(a=1, b=2, c=3\) bar\(a, b, c\) #[0-9]+ Frame 0x-?[0-9a-f]+, for file .*gdb_sample.py, line 12, in \(\) - foo\(1, 2, 3\) -''') + foo\(1, 2, 3\)''') @unittest.skipUnless(_thread, "Python was compiled without thread support") @@ -808,7 +809,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-print args']) self.assertMultilineMatches(bt, - r".*\nlocal 'args' = \(1, 2, 3\)\n.*") + r".*\nlocal 'args' = \(1, 2, 3\).*") @unittest.skipIf(python_is_optimized(), "Python was compiled with optimizations") @@ -817,7 +818,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-up', 'py-print c', 'py-print b', 'py-print a']) self.assertMultilineMatches(bt, - r".*\nlocal 'c' = 3\nlocal 'b' = 2\nlocal 'a' = 1\n.*") + r".*\nlocal 'c' = 3\nlocal 'b' = 2\nlocal 'a' = 1.*") @unittest.skipIf(python_is_optimized(), "Python was compiled with optimizations") @@ -825,7 +826,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-print __name__']) self.assertMultilineMatches(bt, - r".*\nglobal '__name__' = '__main__'\n.*") + r".*\nglobal '__name__' = '__main__'.*") @unittest.skipIf(python_is_optimized(), "Python was compiled with optimizations") @@ -833,7 +834,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-print len']) self.assertMultilineMatches(bt, - r".*\nbuiltin 'len' = \n.*") + r".*\nbuiltin 'len' = .*") class PyLocalsTests(DebuggerTests): @unittest.skipIf(python_is_optimized(), @@ -842,7 +843,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-locals']) self.assertMultilineMatches(bt, - r".*\nargs = \(1, 2, 3\)\n.*") + r".*\nargs = \(1, 2, 3\).*") @unittest.skipUnless(HAS_PYUP_PYDOWN, "test requires py-up/py-down commands") @unittest.skipIf(python_is_optimized(), @@ -851,7 +852,7 @@ bt = self.get_stack_trace(script=self.get_sample_script(), cmds_after_breakpoint=['py-up', 'py-locals']) self.assertMultilineMatches(bt, - r".*\na = 1\nb = 2\nc = 3\n.*") + r".*\na = 1\nb = 2\nc = 3.*") def test_main(): if support.verbose: