# Copyright 1999, 2000 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 2 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, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Please email any bugs, comments, and/or additions to this file to: # bug-gdb@prep.ai.mit.edu # This file was based on a file written by Fred Fish. (fnf@cygnus.com) # Test setup routines that work with the MI interpreter. # The variable mi_gdb_prompt is a regexp which matches the gdb mi prompt. # Set it if it is not already set. global mi_gdb_prompt if ![info exists mi_gdb_prompt] then { set mi_gdb_prompt "\[(\]gdb\[)\] \r\n" } set MIFLAGS "-i=mi" # # mi_gdb_exit -- exit the GDB, killing the target program if necessary # proc mi_gdb_exit {} { catch mi_uncatched_gdb_exit } proc mi_uncatched_gdb_exit {} { global GDB global GDBFLAGS global verbose global gdb_spawn_id; global gdb_prompt global mi_gdb_prompt global MIFLAGS gdb_stop_suppressing_tests; if { [info procs sid_exit] != "" } { sid_exit } if ![info exists gdb_spawn_id] { return; } verbose "Quitting $GDB $GDBFLAGS $MIFLAGS" if { [is_remote host] && [board_info host exists fileid] } { send_gdb "999-gdb-exit\n"; gdb_expect 10 { -re "y or n" { send_gdb "y\n"; exp_continue; } -re "Undefined command.*$gdb_prompt $" { send_gdb "quit\n" exp_continue; } -re "DOSEXIT code" { } default { } } } if ![is_remote host] { remote_close host; } unset gdb_spawn_id } # # start gdb -- start gdb running, default procedure # # When running over NFS, particularly if running many simultaneous # tests on different hosts all using the same server, things can # get really slow. Give gdb at least 3 minutes to start up. # proc mi_gdb_start { } { global verbose global GDB global GDBFLAGS global gdb_prompt global mi_gdb_prompt global timeout global gdb_spawn_id; global MIFLAGS gdb_stop_suppressing_tests; # Start SID. if { [info procs sid_start] != "" } { verbose "Spawning SID" sid_start } verbose "Spawning $GDB -nw $GDBFLAGS $MIFLAGS" if [info exists gdb_spawn_id] { return 0; } if ![is_remote host] { if { [which $GDB] == 0 } then { perror "$GDB does not exist." exit 1 } } set res [remote_spawn host "$GDB -nw $GDBFLAGS $MIFLAGS [host_info gdb_opts]"]; if { $res < 0 || $res == "" } { perror "Spawning $GDB failed." return 1; } gdb_expect { -re ".*$mi_gdb_prompt$" { verbose "GDB initialized." } -re ".*$gdb_prompt $" { untested "Skip mi tests (got non-mi prompt)." remote_close host; return -1; } -re ".*unrecognized option.*for a complete list of options." { untested "Skip mi tests (not compiled with mi support)." remote_close host; return -1; } -re ".*Interpreter `mi' unrecognized." { untested "Skip mi tests (not compiled with mi support)." remote_close host; return -1; } timeout { perror "(timeout) GDB never initialized after 10 seconds." remote_close host; return -1 } } set gdb_spawn_id -1; # FIXME: mi output does not go through pagers, so these can be removed. # force the height to "unlimited", so no pagers get used send_gdb "100-gdb-set height 0\n" gdb_expect 10 { -re ".*100-gdb-set height 0\r\n100\\\^done\r\n$mi_gdb_prompt$" { verbose "Setting height to 0." 2 } timeout { warning "Couldn't set the height to 0" } } # force the width to "unlimited", so no wraparound occurs send_gdb "101-gdb-set width 0\n" gdb_expect 10 { -re ".*101-gdb-set width 0\r\n101\\\^done\r\n$mi_gdb_prompt$" { verbose "Setting width to 0." 2 } timeout { warning "Couldn't set the width to 0." } } return 0; } # Many of the tests depend on setting breakpoints at various places and # running until that breakpoint is reached. At times, we want to start # with a clean-slate with respect to breakpoints, so this utility proc # lets us do this without duplicating this code everywhere. # proc mi_delete_breakpoints {} { global mi_gdb_prompt # FIXME: The mi operation won't accept a prompt back and will use the 'all' arg send_gdb "102-break-delete\n" gdb_expect 30 { -re "Delete all breakpoints.*y or n.*$" { send_gdb "y\n"; exp_continue } -re ".*102-break-delete\r\n102\\\^done\r\n$mi_gdb_prompt$" { # This happens if there were no breakpoints } timeout { perror "Delete all breakpoints in delete_breakpoints (timeout)" ; return } } # The correct output is not "No breakpoints or watchpoints." but an # empty BreakpointTable. Also, a query is not acceptable with mi. send_gdb "103-break-list\n" gdb_expect 30 { -re "103-break-list\r\n103\\\^done,BreakpointTable=\{\}\r\n$mi_gdb_prompt$" {} -re "103-break-list\r\n103\\\^done,BreakpointTable=\{nr_rows=\".\",nr_cols=\".\",hdr=\\\[\{width=\".*\",alignment=\".*\",col_name=\"number\",colhdr=\"Num\"\}.*colhdr=\"Type\".*colhdr=\"Disp\".*colhdr=\"Enb\".*colhdr=\"Address\".*colhdr=\"What\".*\\\],body=\\\[\\\]\}" {} -re "103-break-list\r\n103\\\^doneNo breakpoints or watchpoints.\r\n\r\n$mi_gdb_prompt$" {warning "Unexpected console text received"} -re "$mi_gdb_prompt$" { perror "Breakpoints not deleted" ; return } -re "Delete all breakpoints.*or n.*$" { warning "Unexpected prompt for breakpoints deletion"; send_gdb "y\n"; exp_continue } timeout { perror "-break-list (timeout)" ; return } } } proc mi_gdb_reinitialize_dir { subdir } { global mi_gdb_prompt global suppress_flag if { $suppress_flag } { return } if [is_remote host] { return ""; } send_gdb "104-environment-directory\n" gdb_expect 60 { -re ".*Reinitialize source path to empty.*y or n. " { warning "Got confirmation prompt for dir reinitialization." send_gdb "y\n" gdb_expect 60 { -re "$mi_gdb_prompt$" {} timeout {error "Dir reinitialization failed (timeout)"} } } -re "$mi_gdb_prompt$" {} timeout {error "Dir reinitialization failed (timeout)"} } send_gdb "105-environment-directory $subdir\n" gdb_expect 60 { -re "Source directories searched.*$mi_gdb_prompt$" { verbose "Dir set to $subdir" } -re "105\\\^done\r\n$mi_gdb_prompt$" { # FIXME: We return just the prompt for now. verbose "Dir set to $subdir" # perror "Dir \"$subdir\" failed." } } } # # load a file into the debugger. # return a -1 if anything goes wrong. # proc mi_gdb_load { arg } { global verbose global loadpath global loadfile global GDB global mi_gdb_prompt upvar timeout timeout # ``gdb_unload'' # ``gdb_file_cmd'' # FIXME: Several of these patterns are only acceptable for console # output. Queries are an error for mi. send_gdb "105-file-exec-and-symbols $arg\n" gdb_expect 120 { -re "Reading symbols from.*done.*$mi_gdb_prompt$" { verbose "\t\tLoaded $arg into the $GDB" # All OK } -re "has no symbol-table.*$mi_gdb_prompt$" { perror "$arg wasn't compiled with \"-g\"" return -1 } -re "A program is being debugged already.*Kill it.*y or n. $" { send_gdb "y\n" verbose "\t\tKilling previous program being debugged" exp_continue } -re "Load new symbol table from \".*\".*y or n. $" { send_gdb "y\n" gdb_expect 120 { -re "Reading symbols from.*done.*$mi_gdb_prompt$" { verbose "\t\tLoaded $arg with new symbol table into $GDB" # All OK } timeout { perror "(timeout) Couldn't load $arg, other program already loaded." return -1 } } } -re "No such file or directory.*$mi_gdb_prompt$" { perror "($arg) No such file or directory\n" return -1 } -re "105-file-exec-and-symbols .*\r\n105\\\^done\r\n$mi_gdb_prompt$" { # We are just giving the prompt back for now # All OK } timeout { perror "couldn't load $arg into $GDB (timed out)." return -1 } eof { # This is an attempt to detect a core dump, but seems not to # work. Perhaps we need to match .* followed by eof, in which # gdb_expect does not seem to have a way to do that. perror "couldn't load $arg into $GDB (end of file)." return -1 } } # ``load'' if { [info procs send_target_sid] != "" } { # For SID, things get complex send_target_sid gdb_expect 60 { -re "\\^done,.*$mi_gdb_prompt$" { } timeout { perror "Unable to connect to SID target" return -1 } } send_gdb "48-target-download\n" gdb_expect 10 { -re "48\\^done.*$mi_gdb_prompt$" { } timeout { perror "Unable to download to SID target" return -1 } } } elseif { [target_info protocol] == "sim" } { # For the simulator, just connect to it directly. send_gdb "47-target-select sim\n" gdb_expect 10 { -re "47\\^connected.*$mi_gdb_prompt$" { } timeout { perror "Unable to select sim target" return -1 } } send_gdb "48-target-download\n" gdb_expect 10 { -re "48\\^done.*$mi_gdb_prompt$" { } timeout { perror "Unable to download to sim target" return -1 } } } return 0 } # mi_gdb_test COMMAND PATTERN MESSAGE -- send a command to gdb; test the result. # # COMMAND is the command to execute, send to GDB with send_gdb. If # this is the null string no command is sent. # PATTERN is the pattern to match for a PASS, and must NOT include # the \r\n sequence immediately before the gdb prompt. # MESSAGE is an optional message to be printed. If this is # omitted, then the pass/fail messages use the command string as the # message. (If this is the empty string, then sometimes we don't # call pass or fail at all; I don't understand this at all.) # # Returns: # 1 if the test failed, # 0 if the test passes, # -1 if there was an internal error. # proc mi_gdb_test { args } { global verbose global mi_gdb_prompt global GDB expect_out upvar timeout timeout if [llength $args]>2 then { set message [lindex $args 2] } else { set message [lindex $args 0] } set command [lindex $args 0] set pattern [lindex $args 1] if [llength $args]==5 { set question_string [lindex $args 3]; set response_string [lindex $args 4]; } else { set question_string "^FOOBAR$" } if $verbose>2 then { send_user "Sending \"$command\" to gdb\n" send_user "Looking to match \"$pattern\"\n" send_user "Message is \"$message\"\n" } set result -1 set string "${command}\n"; if { $command != "" } { while { "$string" != "" } { set foo [string first "\n" "$string"]; set len [string length "$string"]; if { $foo < [expr $len - 1] } { set str [string range "$string" 0 $foo]; if { [send_gdb "$str"] != "" } { global suppress_flag; if { ! $suppress_flag } { perror "Couldn't send $command to GDB."; } fail "$message"; return $result; } gdb_expect 2 { -re "\[\r\n\]" { } timeout { } } set string [string range "$string" [expr $foo + 1] end]; } else { break; } } if { "$string" != "" } { if { [send_gdb "$string"] != "" } { global suppress_flag; if { ! $suppress_flag } { perror "Couldn't send $command to GDB."; } fail "$message"; return $result; } } } if [info exists timeout] { set tmt $timeout; } else { global timeout; if [info exists timeout] { set tmt $timeout; } else { set tmt 60; } } gdb_expect $tmt { -re "\\*\\*\\* DOSEXIT code.*" { if { $message != "" } { fail "$message"; } gdb_suppress_entire_file "GDB died"; return -1; } -re "Ending remote debugging.*$mi_gdb_prompt\[ \]*$" { if ![isnative] then { warning "Can`t communicate to remote target." } gdb_exit gdb_start set result -1 } -re "(${question_string})$" { send_gdb "$response_string\n"; exp_continue; } -re "Undefined.* command:.*$mi_gdb_prompt\[ \]*$" { perror "Undefined command \"$command\"." fail "$message" set result 1 } -re "Ambiguous command.*$mi_gdb_prompt\[ \]*$" { perror "\"$command\" is not a unique command name." fail "$message" set result 1 } -re "\[\r\n\]*($pattern)\[\r\n\]+$mi_gdb_prompt\[ \]*$" { if ![string match "" $message] then { pass "$message" } set result 0 } -re "Program exited with code \[0-9\]+.*$mi_gdb_prompt\[ \]*$" { if ![string match "" $message] then { set errmsg "$message: the program exited" } else { set errmsg "$command: the program exited" } fail "$errmsg" return -1 } -re "The program is not being run.*$mi_gdb_prompt\[ \]*$" { if ![string match "" $message] then { set errmsg "$message: the program is no longer running" } else { set errmsg "$command: the program is no longer running" } fail "$errmsg" return -1 } -re ".*$mi_gdb_prompt\[ \]*$" { if ![string match "" $message] then { fail "$message" } set result 1 } "" { send_gdb "\n" perror "Window too small." fail "$message" } -re "\\(y or n\\) " { send_gdb "n\n" perror "Got interactive prompt." fail "$message" } eof { perror "Process no longer exists" if { $message != "" } { fail "$message" } return -1 } full_buffer { perror "internal buffer is full." fail "$message" } timeout { if ![string match "" $message] then { fail "$message (timeout)" } set result 1 } } return $result } # # MI run command. (A modified version of gdb_run_cmd) # # In patterns, the newline sequence ``\r\n'' is matched explicitly as # ``.*$'' could swallow up output that we attempt to match elsewhere. proc mi_run_cmd {args} { global suppress_flag if { $suppress_flag } { return -1 } global mi_gdb_prompt if [target_info exists gdb_init_command] { send_gdb "[target_info gdb_init_command]\n"; gdb_expect 30 { -re "$mi_gdb_prompt$" { } default { perror "gdb_init_command for target failed"; return; } } } if [target_info exists use_gdb_stub] { if [target_info exists gdb,do_reload_on_run] { # Specifying no file, defaults to the executable # currently being debugged. if { [mi_gdb_load ""] < 0 } { return; } send_gdb "000-exec-continue\n"; gdb_expect 60 { -re "Continu\[^\r\n\]*\[\r\n\]" {} default {} } return; } if [target_info exists gdb,start_symbol] { set start [target_info gdb,start_symbol]; } else { set start "start"; } # HACK: Should either use 000-jump or fix the target code # to better handle RUN. send_gdb "jump *$start\n" warning "Using CLI jump command, expect run-to-main FAIL" return } send_gdb "000-exec-run $args\n" gdb_expect { -re "000\\^running\r\n${mi_gdb_prompt}" { } timeout { perror "Unable to start target" return } } # NOTE: Shortly after this there will be a ``000*stopping,...(gdb)'' } # # Just like run-to-main but works with the MI interface # proc mi_run_to_main { } { global suppress_flag if { $suppress_flag } { return -1 } global mi_gdb_prompt global hex global decimal global srcdir global subdir global binfile global srcfile set test "mi run-to-main" mi_delete_breakpoints mi_gdb_reinitialize_dir $srcdir/$subdir mi_gdb_load ${binfile} mi_gdb_test "200-break-insert main" \ "200\\^done,bkpt=\{number=\"1\",type=\"breakpoint\",disp=\"keep\",enabled=\"y\",addr=\"$hex\",func=\"main\",file=\".*\",line=\"\[0-9\]*\",times=\"0\"\}" \ "breakpoint at main" mi_run_cmd gdb_expect { -re "000\\*stopped,reason=\"breakpoint-hit\",bkptno=\"1\",thread-id=\"$decimal\",frame=\{addr=\"$hex\",func=\"main\",args=\(\\\[\\\]\|\{\}\),file=\".*\",line=\"\[0-9\]*\"\}\r\n$mi_gdb_prompt$" { pass "$test" return 0 } -re ".*$mi_gdb_prompt$" { fail "$test (2)" } timeout { fail "$test (timeout)" return -1 } } } # Next to the next statement proc mi_next { test } { global suppress_flag if { $suppress_flag } { return -1 } global mi_gdb_prompt send_gdb "220-exec-next\n" gdb_expect { -re "220\\^running\r\n${mi_gdb_prompt}220\\*stopped,reason=\"end-stepping-range\",thread-id=\"$decimal\",frame=\{addr=\"$hex\",func=\".*\",args=\[\\\[\{].*[\\\]\}\],file=\".*\",line=\"\[0-9\]*\"\}\r\n$mi_gdb_prompt$" { pass "$test" return 0 } timeout { fail "$test" return -1 } } } # Step to the next statement proc mi_step { test } { global suppress_flag if { $suppress_flag } { return -1 } global mi_gdb_prompt send_gdb "220-exec-step\n" gdb_expect { -re "220\\^running\r\n${mi_gdb_prompt}220\\*stopped,reason=\"end-stepping-range\",thread-id=\"$decimal\",frame=\{addr=\"$hex\",func=\".*\",args=\[\\\[\{\].*\[\\\]\}\],file=\".*\",line=\"\[0-9\]*\"\}\r\n$mi_gdb_prompt$" { pass "$test" return 0 } timeout { fail "$test" return -1 } } } # cmd should not include the number or newline (i.e. "exec-step 3", not # "220-exec-step 3\n" # Can not match -re ".*\r\n${mi_gdb_prompt}", because of false positives # after the first prompt is printed. proc mi_run_to_helper { cmd reason func args file line extra test } { global suppress_flag if { $suppress_flag } { return -1 } global mi_gdb_prompt global hex global decimal send_gdb "220-$cmd\n" gdb_expect { -re "220\\^running\r\n${mi_gdb_prompt}220\\*stopped,reason=\"$reason\",thread-id=\"$decimal\",frame=\{addr=\"$hex\",func=\"$func\",args=$args,file=\".*$file\",line=\"$line\"\}$extra\r\n$mi_gdb_prompt$" { pass "$test" return 0 } -re "220\\^running\r\n${mi_gdb_prompt}220\\*stopped,reason=\"$reason\",thread-id=\"$decimal\",frame=\{addr=\"$hex\",func=\".*\",args=\[\\\[\{\].*\[\\\]\}\],file=\".*\",line=\"\[0-9\]*\"\}.*\r\n$mi_gdb_prompt$" { fail "$test (stopped at wrong place)" return -1 } -re "220\\^running\r\n${mi_gdb_prompt}.*\r\n${mi_gdb_prompt}$" { fail "$test (unknown output after running)" return -1 } timeout { fail "$test (timeout)" return -1 } } } proc mi_run_to { cmd reason func args file line extra test } { mi_run_to_helper "$cmd" "$reason" "$func" "\\\[$args\\\]" \ "$file" "$line" "$extra" "$test" } proc mi_next_to { func args file line test } { mi_run_to "exec-next" "end-stepping-range" "$func" "$args" \ "$file" "$line" "" "$test" } proc mi_step_to { func args file line test } { mi_run_to "exec-step" "end-stepping-range" "$func" "$args" \ "$file" "$line" "" "$test" } proc mi_finish_to { func args file line result ret test } { mi_run_to "exec-finish" "function-finished" "$func" "$args" \ "$file" "$line" \ ",gdb-result-var=\"$result\",return-value=\"$ret\"" \ "$test" } proc mi_continue_to { bkptno func args file line test } { mi_run_to "exec-continue" "breakpoint-hit\",bkptno=\"$bkptno" \ "$func" "$args" "$file" "$line" "" "$test" } proc mi0_run_to { cmd reason func args file line extra test } { mi_run_to_helper "$cmd" "$reason" "$func" "\{$args\}" \ "$file" "$line" "$extra" "$test" } proc mi0_next_to { func args file line test } { mi0_run_to "exec-next" "end-stepping-range" "$func" "$args" \ "$file" "$line" "" "$test" } proc mi0_step_to { func args file line test } { mi0_run_to "exec-step" "end-stepping-range" "$func" "$args" \ "$file" "$line" "" "$test" } proc mi0_finish_to { func args file line result ret test } { mi0_run_to "exec-finish" "function-finished" "$func" "$args" \ "$file" "$line" \ ",gdb-result-var=\"$result\",return-value=\"$ret\"" \ "$test" } proc mi0_continue_to { bkptno func args file line test } { mi0_run_to "exec-continue" "breakpoint-hit\",bkptno=\"$bkptno" \ "$func" "$args" "$file" "$line" "" "$test" }