[5/9] testsuite: Record all gdb input to gdb.in

Message ID 20190514151238.8765-6-alan.hayward@arm.com
State New
Headers show
Series
  • Create gdb.debug, gdb.cmd and gdb.in files when running the testsuite
Related show

Commit Message

Alan Hayward May 14, 2019, 3:12 p.m.
When debugging testsuite failures, it can be awkward parsing gdb.log to
obtain all the commands run in order to manually re-run the test.

This patch adds the functionality to save all gdb commands to the file gdb.in
when the testsuite is run. The file is saved in the directory for the test and
if gdb is restarted then .1, .2, .3 etc is added to the filename.

Once a test has been run, the .in file can be used to re-run the test in the
following way:

  gdb -x outputs/gdb.store/gdb.in outputs/gdb.store/store

The code works by intercepting send_gdb.  I've added a TYPE to ensure that any
commands that would destroy the playback are kept from the log (for example the
Y from an answer to a y/n question).

Adds library function standard_output_file_with_gdb_instance to open a file
postfixed with count of the gdb instance.  Ensure this count is reset when a new
.exp script is run.

I've re-run a random selection of .in files to check they do not error. Logs with
commands such as "attach <pid>" will not directly work when re-run.

gdb/testsuite/ChangeLog:

2019-05-14  Alan Hayward  <alan.hayward@arm.com>

	* lib/gdb.exp (gdb_unload): Mark Y as an answer.
	(delete_breakpoints): Likewise.
	(gdb_run_cmd): Likewise.
	(gdb_start_cmd): Likewise.
	(gdb_starti_cmd): Likewise.
	(gdb_internal_error_resync): Likewise.
	(gdb_test_multiple): Likewise.
	(gdb_reinitialize_dir): Likewise.
	(default_gdb_exit): Likewise.
	(gdb_file_cmd): Mark kill as optional.
	(default_gdb_start): Call gdb_stdin_log_init.
	(send_gdb): Call gdb_stdin_log_write.
	(rerun_to_main): Mark Y as an answer.
	(gdb_stdin_log_init): New function.
	(gdb_stdin_log_write): Likewise.
---
 gdb/testsuite/lib/gdb.exp | 106 +++++++++++++++++++++++++++++++-------
 1 file changed, 88 insertions(+), 18 deletions(-)

-- 
2.20.1 (Apple Git-117)

Comments

Tom Tromey May 16, 2019, 7:24 p.m. | #1
>>>>> "Alan" == Alan Hayward <Alan.Hayward@arm.com> writes:


Alan> When debugging testsuite failures, it can be awkward parsing gdb.log to
Alan> obtain all the commands run in order to manually re-run the test.

Alan> This patch adds the functionality to save all gdb commands to the file gdb.in
Alan> when the testsuite is run. The file is saved in the directory for the test and
Alan> if gdb is restarted then .1, .2, .3 etc is added to the filename.

Thank you.  This seems very handy.

Alan> @@ -4829,6 +4854,10 @@ proc gdb_init { test_file_name } {
Alan>      set gdbserver_reconnect_p 1
Alan>      unset gdbserver_reconnect_p
 
Alan> +    # Reset GDB number of instances
Alan> +    global gdb_instances
Alan> +    set gdb_instances 0

Does this placement work even if a single runtest runs multiple .exp
files?  Like "runtest --directory gdb.cp"?

Alan> +# Write to the file for logging gdb input.
Alan> +# TYPE can be one of the following:
Alan> +# STANDARD : Default. Standard message written to the log
Alan> +# ANSWER : Answer to a question (eg "Y"). Not written the log.
Alan> +# OPTIONAL : Optional message. Not written to the log.

Normally the GNU style reserves upper-case in situations like this to
refer to the value of a variable.  For a string I would just use quotes,
like:

# "standard": Default.  ...

Tom
Alan Hayward May 17, 2019, 3:35 p.m. | #2
> On 16 May 2019, at 20:24, Tom Tromey <tom@tromey.com> wrote:

> 

>>>>>> "Alan" == Alan Hayward <Alan.Hayward@arm.com> writes:

> 

> Alan> When debugging testsuite failures, it can be awkward parsing gdb.log to

> Alan> obtain all the commands run in order to manually re-run the test.

> 

> Alan> This patch adds the functionality to save all gdb commands to the file gdb.in

> Alan> when the testsuite is run. The file is saved in the directory for the test and

> Alan> if gdb is restarted then .1, .2, .3 etc is added to the filename.

> 

> Thank you.  This seems very handy.

> 

> Alan> @@ -4829,6 +4854,10 @@ proc gdb_init { test_file_name } {

> Alan>      set gdbserver_reconnect_p 1

> Alan>      unset gdbserver_reconnect_p

> 

> Alan> +    # Reset GDB number of instances

> Alan> +    global gdb_instances

> Alan> +    set gdb_instances 0

> 

> Does this placement work even if a single runtest runs multiple .exp

> files?  Like "runtest --directory gdb.cp”?


I’ve just tried this and it works fine.

> 

> Alan> +# Write to the file for logging gdb input.

> Alan> +# TYPE can be one of the following:

> Alan> +# STANDARD : Default. Standard message written to the log

> Alan> +# ANSWER : Answer to a question (eg "Y"). Not written the log.

> Alan> +# OPTIONAL : Optional message. Not written to the log.

> 

> Normally the GNU style reserves upper-case in situations like this to

> refer to the value of a variable.  For a string I would just use quotes,

> like:

> 

> # "standard": Default.  …


Done.

> 

> Tom



And pushed with above change:


diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index ba276ac929..d00f1cefd3 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -167,11 +167,11 @@ proc gdb_unload {} {
        -re "No executable file now\[^\r\n\]*\[\r\n\]" { exp_continue }
        -re "No symbol file now\[^\r\n\]*\[\r\n\]" { exp_continue }
        -re "A program is being debugged already.*Are you sure you want to change the file.*y or n. $" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            exp_continue
        }
        -re "Discard symbol table from .*y or n.*$" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            exp_continue
        }
        -re "$gdb_prompt $" {}
@@ -201,7 +201,7 @@ proc delete_breakpoints {} {
     set deleted 0
     gdb_test_multiple "delete breakpoints" "$msg" {
        -re "Delete all breakpoints.*y or n.*$" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            exp_continue
        }
        -re "$gdb_prompt $" {
@@ -307,7 +307,7 @@ proc gdb_run_cmd {args} {
                    set start_attempt 0
                }
                -re "Line.* Jump anyway.*y or n. $" {
-                   send_gdb "y\n"
+                   send_gdb "y\n" answer
                }
                -re "The program is not being run.*$gdb_prompt $" {
                    if { [gdb_reload] != 0 } {
@@ -335,7 +335,7 @@ proc gdb_run_cmd {args} {
 # may test for additional start-up messages.
    gdb_expect 60 {
        -re "The program .* has been started already.*y or n. $" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            exp_continue
        }
        -notransfer -re "Starting program: \[^\r\n\]*" {}
@@ -374,7 +374,7 @@ proc gdb_start_cmd {args} {
     # may test for additional start-up messages.
     gdb_expect 60 {
        -re "The program .* has been started already.*y or n. $" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            exp_continue
        }
        -notransfer -re "Starting program: \[^\r\n\]*" {
@@ -411,7 +411,7 @@ proc gdb_starti_cmd {args} {
     send_gdb "starti $args\n"
     gdb_expect 60 {
        -re "The program .* has been started already.*y or n. $" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            exp_continue
        }
        -re "Starting program: \[^\r\n\]*" {
@@ -673,11 +673,11 @@ proc gdb_internal_error_resync {} {
     while {$count < 10} {
        gdb_expect {
            -re "Quit this debugging session\\? \\(y or n\\) $" {
-               send_gdb "n\n"
+               send_gdb "n\n" answer
                incr count
            }
            -re "Create a core file of GDB\\? \\(y or n\\) $" {
-               send_gdb "n\n"
+               send_gdb "n\n" answer
                incr count
            }
            -re "$gdb_prompt $" {
@@ -971,7 +971,7 @@ proc gdb_test_multiple { command message user_code } {
            set result -1
        }
        -re "\\((y or n|y or \\\[n\\\]|\\\[y\\\] or n)\\) " {
-           send_gdb "n\n"
+           send_gdb "n\n" answer
            gdb_expect -re "$gdb_prompt $"
            fail "$message (got interactive prompt)"
            set result -1
@@ -1451,7 +1451,7 @@ proc gdb_reinitialize_dir { subdir } {
     send_gdb "dir\n"
     gdb_expect 60 {
        -re "Reinitialize source path to empty.*y or n. " {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            gdb_expect 60 {
                -re "Source directories searched.*$gdb_prompt $" {
                    send_gdb "dir $subdir\n"
@@ -1511,7 +1511,7 @@ proc default_gdb_exit {} {
        send_gdb "quit\n"
        gdb_expect 10 {
            -re "y or n" {
-               send_gdb "y\n"
+               send_gdb "y\n" answer
                exp_continue
            }
            -re "DOSEXIT code" { }
@@ -1568,11 +1568,12 @@ proc gdb_file_cmd { arg } {
     }

     # The file command used to kill the remote target.  For the benefit
-    # of the testsuite, preserve this behavior.
-    send_gdb "kill\n"
+    # of the testsuite, preserve this behavior.  Mark as optional so it doesn't
+    # get written to the stdin log.
+    send_gdb "kill\n" optional
     gdb_expect 120 {
        -re "Kill the program being debugged. .y or n. $" {
-           send_gdb "y\n"
+           send_gdb "y\n" answer
            verbose "\t\tKilling previous program being debugged"
            exp_continue
        }
@@ -1599,7 +1600,7 @@ proc gdb_file_cmd { arg } {
            return 0
         }
         -re "Load new symbol table from \".*\".*y or n. $" {
-            send_gdb "y\n"
+            send_gdb "y\n" answer
             gdb_expect 120 {
                 -re "Reading symbols from.*$gdb_prompt $" {
                     verbose "\t\tLoaded $arg with new symbol table into $GDB"
@@ -1695,6 +1696,12 @@ proc default_gdb_start { } {
        return 0
     }

+    # Keep track of the number of times GDB has been launched.
+    global gdb_instances
+    incr gdb_instances
+
+    gdb_stdin_log_init
+
     set res [gdb_spawn]
     if { $res != 0} {
        return $res
@@ -3965,11 +3972,15 @@ proc gdb_compile_objc {source dest type options} {
     }
 }

-proc send_gdb { string } {
+# Send a command to GDB.
+# For options for TYPE see gdb_stdin_log_write
+
+proc send_gdb { string {type standard}} {
     global suppress_flag
     if { $suppress_flag } {
        return "suppressed"
     }
+    gdb_stdin_log_write $string $type
     return [remote_send host "$string"]
 }

@@ -4635,6 +4646,20 @@ proc standard_output_file {basename} {
     return [file join $dir $basename]
 }

+# Turn BASENAME into a full file name in the standard output directory.  If
+# GDB has been launched more than once then append the count, starting with
+# a ".1" postfix.
+
+proc standard_output_file_with_gdb_instance {basename} {
+    global gdb_instances
+    set count [expr $gdb_instances - 1 ]
+
+    if {$count == 0} {
+      return [standard_output_file $basename]
+    }
+    return [standard_output_file ${basename}.${count}]
+}
+
 # Return the name of a file in our standard temporary directory.

 proc standard_temp_file {basename} {
@@ -4829,6 +4854,10 @@ proc gdb_init { test_file_name } {
     set gdbserver_reconnect_p 1
     unset gdbserver_reconnect_p

+    # Reset GDB number of instances
+    global gdb_instances
+    set gdb_instances 0
+
     return [default_gdb_init $test_file_name]
 }

@@ -5097,7 +5126,7 @@ proc rerun_to_main {} {
     send_gdb "run\n"
     gdb_expect {
       -re "The program .* has been started already.*y or n. $" {
-         send_gdb "y\n"
+         send_gdb "y\n" answer
          exp_continue
       }
       -re "Starting program.*$gdb_prompt $"\
@@ -6470,5 +6499,46 @@ proc gdbserver_debug_enabled { } {
     return 0
 }

+# Open the file for logging gdb input
+
+proc gdb_stdin_log_init { } {
+    global in_file
+
+    if {[info exists in_file]} {
+      # Close existing file.
+      catch "close $in_file"
+    }
+
+    set logfile [standard_output_file_with_gdb_instance gdb.in]
+    set in_file [open $logfile w]
+}
+
+# Write to the file for logging gdb input.
+# TYPE can be one of the following:
+# "standard" : Default. Standard message written to the log
+# "answer" : Answer to a question (eg "Y"). Not written the log.
+# "optional" : Optional message. Not written to the log.
+
+proc gdb_stdin_log_write { message {type standard} } {
+
+    global in_file
+    if {![info exists in_file]} {
+      return
+    }
+
+    # Check message types.
+    switch -regexp -- $type {
+        "answer" {
+            return
+        }
+        "optional" {
+            return
+        }
+    }
+
+    #Write to the log
+    puts -nonewline $in_file "$message"
+}
+
 # Always load compatibility stuff.
 load_lib future.exp

Patch

diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index ba276ac9297..3815f86ffc8 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -167,11 +167,11 @@  proc gdb_unload {} {
 	-re "No executable file now\[^\r\n\]*\[\r\n\]" { exp_continue }
 	-re "No symbol file now\[^\r\n\]*\[\r\n\]" { exp_continue }
 	-re "A program is being debugged already.*Are you sure you want to change the file.*y or n. $" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    exp_continue
 	}
 	-re "Discard symbol table from .*y or n.*$" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    exp_continue
 	}
 	-re "$gdb_prompt $" {}
@@ -201,7 +201,7 @@  proc delete_breakpoints {} {
     set deleted 0
     gdb_test_multiple "delete breakpoints" "$msg" {
 	-re "Delete all breakpoints.*y or n.*$" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    exp_continue
 	}
 	-re "$gdb_prompt $" {
@@ -307,7 +307,7 @@  proc gdb_run_cmd {args} {
 		    set start_attempt 0
 		}
 		-re "Line.* Jump anyway.*y or n. $" {
-		    send_gdb "y\n"
+		    send_gdb "y\n" answer
 		}
 		-re "The program is not being run.*$gdb_prompt $" {
 		    if { [gdb_reload] != 0 } {
@@ -335,7 +335,7 @@  proc gdb_run_cmd {args} {
 # may test for additional start-up messages.
    gdb_expect 60 {
 	-re "The program .* has been started already.*y or n. $" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    exp_continue
 	}
 	-notransfer -re "Starting program: \[^\r\n\]*" {}
@@ -374,7 +374,7 @@  proc gdb_start_cmd {args} {
     # may test for additional start-up messages.
     gdb_expect 60 {
 	-re "The program .* has been started already.*y or n. $" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    exp_continue
 	}
 	-notransfer -re "Starting program: \[^\r\n\]*" {
@@ -411,7 +411,7 @@  proc gdb_starti_cmd {args} {
     send_gdb "starti $args\n"
     gdb_expect 60 {
 	-re "The program .* has been started already.*y or n. $" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    exp_continue
 	}
 	-re "Starting program: \[^\r\n\]*" {
@@ -673,11 +673,11 @@  proc gdb_internal_error_resync {} {
     while {$count < 10} {
 	gdb_expect {
 	    -re "Quit this debugging session\\? \\(y or n\\) $" {
-		send_gdb "n\n"
+		send_gdb "n\n" answer
 		incr count
 	    }
 	    -re "Create a core file of GDB\\? \\(y or n\\) $" {
-		send_gdb "n\n"
+		send_gdb "n\n" answer
 		incr count
 	    }
 	    -re "$gdb_prompt $" {
@@ -971,7 +971,7 @@  proc gdb_test_multiple { command message user_code } {
 	    set result -1
 	}
 	-re "\\((y or n|y or \\\[n\\\]|\\\[y\\\] or n)\\) " {
-	    send_gdb "n\n"
+	    send_gdb "n\n" answer
 	    gdb_expect -re "$gdb_prompt $"
 	    fail "$message (got interactive prompt)"
 	    set result -1
@@ -1451,7 +1451,7 @@  proc gdb_reinitialize_dir { subdir } {
     send_gdb "dir\n"
     gdb_expect 60 {
 	-re "Reinitialize source path to empty.*y or n. " {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    gdb_expect 60 {
 		-re "Source directories searched.*$gdb_prompt $" {
 		    send_gdb "dir $subdir\n"
@@ -1511,7 +1511,7 @@  proc default_gdb_exit {} {
 	send_gdb "quit\n"
 	gdb_expect 10 {
 	    -re "y or n" {
-		send_gdb "y\n"
+		send_gdb "y\n" answer
 		exp_continue
 	    }
 	    -re "DOSEXIT code" { }
@@ -1568,11 +1568,12 @@  proc gdb_file_cmd { arg } {
     }
 
     # The file command used to kill the remote target.  For the benefit
-    # of the testsuite, preserve this behavior.
-    send_gdb "kill\n"
+    # of the testsuite, preserve this behavior.  Mark as optional so it doesn't
+    # get written to the stdin log.
+    send_gdb "kill\n" optional
     gdb_expect 120 {
 	-re "Kill the program being debugged. .y or n. $" {
-	    send_gdb "y\n"
+	    send_gdb "y\n" answer
 	    verbose "\t\tKilling previous program being debugged"
 	    exp_continue
 	}
@@ -1599,7 +1600,7 @@  proc gdb_file_cmd { arg } {
 	    return 0
         }
         -re "Load new symbol table from \".*\".*y or n. $" {
-            send_gdb "y\n"
+            send_gdb "y\n" answer
             gdb_expect 120 {
                 -re "Reading symbols from.*$gdb_prompt $" {
                     verbose "\t\tLoaded $arg with new symbol table into $GDB"
@@ -1695,6 +1696,12 @@  proc default_gdb_start { } {
 	return 0
     }
 
+    # Keep track of the number of times GDB has been launched.
+    global gdb_instances
+    incr gdb_instances
+
+    gdb_stdin_log_init
+
     set res [gdb_spawn]
     if { $res != 0} {
 	return $res
@@ -3965,11 +3972,15 @@  proc gdb_compile_objc {source dest type options} {
     }
 }
 
-proc send_gdb { string } {
+# Send a command to GDB.
+# For options for TYPE see gdb_stdin_log_write
+
+proc send_gdb { string {type standard}} {
     global suppress_flag
     if { $suppress_flag } {
 	return "suppressed"
     }
+    gdb_stdin_log_write $string $type
     return [remote_send host "$string"]
 }
 
@@ -4635,6 +4646,20 @@  proc standard_output_file {basename} {
     return [file join $dir $basename]
 }
 
+# Turn BASENAME into a full file name in the standard output directory.  If
+# GDB has been launched more than once then append the count, starting with
+# a ".1" postfix.
+
+proc standard_output_file_with_gdb_instance {basename} {
+    global gdb_instances
+    set count [expr $gdb_instances - 1 ]
+
+    if {$count == 0} {
+      return [standard_output_file $basename]
+    }
+    return [standard_output_file ${basename}.${count}]
+}
+
 # Return the name of a file in our standard temporary directory.
 
 proc standard_temp_file {basename} {
@@ -4829,6 +4854,10 @@  proc gdb_init { test_file_name } {
     set gdbserver_reconnect_p 1
     unset gdbserver_reconnect_p
 
+    # Reset GDB number of instances
+    global gdb_instances
+    set gdb_instances 0
+
     return [default_gdb_init $test_file_name]
 }
 
@@ -5097,7 +5126,7 @@  proc rerun_to_main {} {
     send_gdb "run\n"
     gdb_expect {
       -re "The program .* has been started already.*y or n. $" {
-	  send_gdb "y\n"
+	  send_gdb "y\n" answer
 	  exp_continue
       }
       -re "Starting program.*$gdb_prompt $"\
@@ -6470,5 +6499,46 @@  proc gdbserver_debug_enabled { } {
     return 0
 }
 
+# Open the file for logging gdb input
+
+proc gdb_stdin_log_init { } {
+    global in_file
+
+    if {[info exists in_file]} {
+      # Close existing file.
+      catch "close $in_file"
+    }
+
+    set logfile [standard_output_file_with_gdb_instance gdb.in]
+    set in_file [open $logfile w]
+}
+
+# Write to the file for logging gdb input.
+# TYPE can be one of the following:
+# STANDARD : Default. Standard message written to the log
+# ANSWER : Answer to a question (eg "Y"). Not written the log.
+# OPTIONAL : Optional message. Not written to the log.
+
+proc gdb_stdin_log_write { message {type standard} } {
+
+    global in_file
+    if {![info exists in_file]} {
+      return
+    }
+
+    # Check message types.
+    switch -regexp -- $type {
+        "answer" {
+            return
+        }
+        "optional" {
+            return
+        }
+    }
+
+    #Write to the log
+    puts -nonewline $in_file "$message"
+}
+
 # Always load compatibility stuff.
 load_lib future.exp