From daaf7acf47a12d10459060dca5500b63273cd683 Mon Sep 17 00:00:00 2001 From: Bruno Larsen Date: Tue, 22 Feb 2022 11:44:44 -0300 Subject: [PATCH] [gdb/testsuite] test a function call by hand from pretty printer The test case added here is testing the bug gdb/28856, where calling a function by hand from a pretty printer makes GDB crash. There are 6 mechanisms to trigger this crash in the current test, using the commands backtrace, up, down, finish, step and continue. Since the failure happens because of use-after-free (more details below) the tests will always have a chance of passing through sheer luck, but anecdotally they seem to fail all of the time. The reason GDB is crashing is a use-after-free problem. The above mentioned functions save a pointer to the current frame's information, then calls the pretty printer, and uses the saved pointer for different reasons, depending on the function. The issue happens because call_function_by_hand needs to reset the obstack to get the current frame, invalidating the saved pointer. --- .../gdb.python/pretty-print-call-by-hand.c | 53 ++++++++ .../gdb.python/pretty-print-call-by-hand.exp | 127 ++++++++++++++++++ .../gdb.python/pretty-print-call-by-hand.py | 41 ++++++ 3 files changed, 221 insertions(+) create mode 100644 gdb/testsuite/gdb.python/pretty-print-call-by-hand.c create mode 100644 gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp create mode 100644 gdb/testsuite/gdb.python/pretty-print-call-by-hand.py diff --git a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.c b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.c new file mode 100644 index 00000000000..3be5675b096 --- /dev/null +++ b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.c @@ -0,0 +1,53 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2022 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +struct mytype +{ + char *x; +}; + +void +rec (int i) +{ + if (i <= 0) + return; + rec (i-1); +} + +int +f () +{ + rec (100); + return 2; +} + +void +g (struct mytype mt, int depth) +{ + if (depth <= 0) + return; /* TAG: final frame */ + g (mt, depth - 1); /* TAG: first frame */ +} + +int +main () +{ + struct mytype mt; + mt.x = "hello world"; + g (mt, 10); /* TAG: outside the frame */ + return 0; +} diff --git a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp new file mode 100644 index 00000000000..0a6d014cf12 --- /dev/null +++ b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.exp @@ -0,0 +1,127 @@ +# Copyright (C) 2022 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This file is part of the GDB testsuite. It tests a pretty printer that +# calls an inferior function by hand, triggering a Use-after-Free bug +# (PR gdb/28856). + +load_lib gdb-python.exp + +standard_testfile + +# gdb needs to be started here for skip_python_tests to work. +# prepare_for_testing could be used instead, but it could compile the program +# unnecessarily, so starting GDB like this is preferable. +gdb_start + +# Skip all tests if Python scripting is not enabled. +if { [skip_python_tests] } { continue } + +if { [prepare_for_testing "failed to prepare" $testfile $srcfile debug] } { + untested "failed to compile" + return -1 +} + +# This proc restarts GDB, makes the inferior reach the desired spot - marked +# by a comment in the .c file - and turns on the pretty printer for testing. +# Starting with a new GDB is important because the test may crash GDB. The +# return values are here to avoid us trying to test the pretty printer if +# there was a problem getting to main. +proc start_test { breakpoint_comment } { + global srcdir subdir testfile binfile + + # Start with a fresh gdb. + # This is important because the test can crash GDB. + + clean_restart ${binfile} + + if { ![runto_main] } then { + untested "couldn't run to breakpoint" + return -1 + } + + # Let GDB get to the return line. + gdb_breakpoint [gdb_get_line_number ${breakpoint_comment} ${testfile}.c ] + gdb_continue_to_breakpoint ${breakpoint_comment} ".*" + + gdb_test_no_output "set print pretty on" "starting to pretty print" + + set remote_python_file [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py] + gdb_test_no_output "source ${remote_python_file}" "load python file" + + return 0 +} + +# Testing the backtrace command. +with_test_prefix "frame print" { + if { [start_test "TAG: final frame"] == 0 } { + setup_kfail gdb/28856 "*-*-*" + gdb_test "backtrace -frame-arguments all" [multi_line \ + "#0 .*g \\(mt=mytype is .*\\).*"\ + "#1 .*g \\(mt=mytype is .*\\).*"\ + "#2 .*g \\(mt=mytype is .*\\).*"\ + "#3 .*g \\(mt=mytype is .*\\).*"\ + "#4 .*g \\(mt=mytype is .*\\).*"\ + "#5 .*g \\(mt=mytype is .*\\).*"\ + "#6 .*g \\(mt=mytype is .*\\).*"\ + "#7 .*g \\(mt=mytype is .*\\).*"\ + "#8 .*g \\(mt=mytype is .*\\).*"\ + "#9 .*g \\(mt=mytype is .*\\).*"\ + "#10 .*g \\(mt=mytype is .*\\).*"\ + "#11 .*main \\(\\).*"] \ + "backtrace test" + } +} +# Testing the down command. +with_test_prefix "frame movement down" { + if { [start_test "TAG: first frame"] == 0 } { + gdb_test "up" [multi_line "#1 .*in main \\(\\) at .*" ".*outside the frame.*"] + setup_kfail gdb/28856 "*-*-*" + gdb_test "down" [multi_line "#0\\s+g \\(mt=mytype is .*\\).*" ".*first frame.*"] + } +} + +# Testing the up command. +with_test_prefix "frame movement up" { + if { [start_test "TAG: final frame"] == 0 } { + setup_kfail gdb/28856 "*-*-*" + gdb_test "up" [multi_line "#1 .*in g \\(mt=mytype is .*\\).*" ".*first frame.*"] + } +} + +# Testing the finish command. +with_test_prefix "frame exit through finish" { + if { [start_test "TAG: final frame"] == 0 } { + setup_kfail gdb/28856 "*-*-*" + gdb_test "finish" [multi_line "Run till exit from #0 .*" ".* g \\(mt=mytype is .*\\, depth=1\\).*" ".*first frame.*"] + } +} + +# Testing the step command. +with_test_prefix "frame enter through step" { + if { [start_test "TAG: outside the frame"] == 0 } { + setup_kfail gdb/28856 "*-*-*" + gdb_test "step" [multi_line "g \\(mt=mytype is .*\\, depth=10\\).*" "41.*if \\(depth \\<= 0\\)"] + } +} + +# Testing the continue command. +with_test_prefix "frame enter through continue" { + if { [start_test "TAG: outside the frame"] == 0 } { + setup_kfail gdb/28856 "*-*-*" + gdb_breakpoint [gdb_get_line_number "TAG: first frame" ${testfile}.c ] + gdb_continue_to_breakpoint "TAG: first frame" ".*TAG: first frame.*" + } +} diff --git a/gdb/testsuite/gdb.python/pretty-print-call-by-hand.py b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.py new file mode 100644 index 00000000000..f8f5df678f8 --- /dev/null +++ b/gdb/testsuite/gdb.python/pretty-print-call-by-hand.py @@ -0,0 +1,41 @@ +# Copyright (C) 2022 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +class MytypePrinter: + """pretty print my type""" + + def __init__(self, val): + self.val = val + + def to_string(self): + calls = gdb.parse_and_eval('f()') + return "mytype is %s" % self.val['x'] + +def ec_lookup_function(val): + typ = val.type + if typ.code == gdb.TYPE_CODE_REF: + typ = typ.target() + if str(typ) == 'struct mytype': + return MytypePrinter(val) + return None + +def disable_lookup_function(): + ec_lookup_function.enabled = False + +def enable_lookup_function(): + ec_lookup_function.enabled = True + +gdb.pretty_printers.append(ec_lookup_function) -- 2.30.2