terminate-on-error patch

Sean Callanan
2005-08-11T19:49:39Z

At Apple, we have a set of scripts that peek around kernel data
structures. One script does a stack dump for each thread activation
in the kernel. Sometimes, the stack dumps run into invalid memory;
right now, that terminates the script, which is bad, because we could
safely continue on to the next activation.

In general, it is desirable to have at least optional handling of
errors by scripts rather than having the scripts simply terminate in
all cases. I have a simple patch that adds this functionality to the
GDB command line.

When the test-and-set variable "terminate-on-error" is on (default),
the behavior is the same as unmodified GDB. When it is off, a failed
command does not terminate a script; rather, the $_error convenience
variable is set to 1. Upon a successful command execution, the
variable is set to 0.

The way one would use this functionality in a script is as follows:
--
set terminate-on-error off
[command]
set $err = $_error
set terminate-on-error on

if($err)
[handle errors]
end
--
I recommend that this patch be added to mainline, because it enables
new script functionality, making scripts that use it more robust,
without changing the behavior of any existing scripts.

Sincerely,
Sean Callanan

Index: gdb/wrapper.h
===================================================================
RCS file: /cvs/src/src/gdb/wrapper.h,v
retrieving revision 1.14
diff -c -r1.14 wrapper.h
*** gdb/wrapper.h 16 Feb 2005 13:21:48 -0000 1.14
--- gdb/wrapper.h 11 Aug 2005 19:42:17 -0000
***************
*** 50,53 ****
--- 50,56 ----

extern int gdb_parse_and_eval_type (char *, int, struct type **);

+ /* Longjmp-safe wrapper for "execute_command". */
+ extern struct gdb_exception safe_execute_command (struct ui_out *,
char *, int);
+
#endif /* wrapper.h */
Index: gdb/cli/cli-interp.c
===================================================================
RCS file: /cvs/src/src/gdb/cli/cli-interp.c,v
retrieving revision 1.10
diff -c -r1.10 cli-interp.c
*** gdb/cli/cli-interp.c 26 Apr 2005 05:03:39 -0000 1.10
--- gdb/cli/cli-interp.c 11 Aug 2005 19:42:17 -0000
***************
*** 33,41 ****

/* These are the ui_out and the interpreter for the console
interpreter. */

- /* Longjmp-safe wrapper for "execute_command". */
- static struct gdb_exception safe_execute_command (struct ui_out
*uiout,
- char *command, int from_tty);
struct captured_execute_command_args
{
char *command;
--- 33,38 ----
***************
*** 122,128 ****
execute_command (args->command, args->from_tty);
}

! static struct gdb_exception
safe_execute_command (struct ui_out *uiout, char *command, int
from_tty)
{
struct gdb_exception e;
--- 119,125 ----
execute_command (args->command, args->from_tty);
}

! struct gdb_exception
safe_execute_command (struct ui_out *uiout, char *command, int
from_tty)
{
struct gdb_exception e;
Index: gdb/cli/cli-script.c
===================================================================
RCS file: /cvs/src/src/gdb/cli/cli-script.c,v
retrieving revision 1.30
diff -c -r1.30 cli-script.c
*** gdb/cli/cli-script.c 26 Apr 2005 14:57:22 -0000 1.30
--- gdb/cli/cli-script.c 11 Aug 2005 19:42:17 -0000
***************
*** 33,38 ****
--- 33,39 ----
#include "cli/cli-cmds.h"
#include "cli/cli-decode.h"
#include "cli/cli-script.h"
+ #include "wrapper.h"

/* Prototypes for local functions */

***************
*** 48,53 ****
--- 49,57 ----
/* Level of control structure. */
static int control_level;

+ /* Terminate scripts on errors, rather than setting _error = 1 */
+ static int terminate_on_error = 1;
+
/* Structure for arguments to user defined functions. */
#define MAXUSERARGS 10
struct user_args
***************
*** 310,316 ****
if (!new_line)
break;
make_cleanup (free_current_contents, &new_line);
! execute_command (new_line, 0);
ret = cmd->control_type;
break;

--- 314,334 ----
if (!new_line)
break;
make_cleanup (free_current_contents, &new_line);
!
! if(terminate_on_error)
! {
! execute_command (new_line, 0);
! }
! else
! {
! struct gdb_exception rv;
! struct value* rv_val;
!
! rv = safe_execute_command (uiout, new_line, 0);
! rv_val = value_from_longest (builtin_type_int, (rv.error !
= NO_ERROR));
! set_internalvar (lookup_internalvar ("_error"), rv_val);
! }
!
ret = cmd->control_type;
break;

***************
*** 1313,1315 ****
--- 1331,1342 ----
fputs_filtered ("\n", stream);
}

+ void
+ _initialize_cli_script (void)
+ {
+ add_setshow_boolean_cmd("terminate-on-error", class_support,
&terminate_on_error,
+ "Set termination of scripts on command
errors.",
+ "Show script termination on command
errors.",
+ "Controls how scripts respond to command
errors: by terminating or setting $_error to 1.",
+ NULL, NULL, &setlist, &showlist);
+ }
Index: gdb/doc/gdb.texinfo
===================================================================
RCS file: /cvs/src/src/gdb/doc/gdb.texinfo,v
retrieving revision 1.276
diff -c -r1.276 gdb.texinfo
*** gdb/doc/gdb.texinfo 1 Aug 2005 18:31:28 -0000 1.276
--- gdb/doc/gdb.texinfo 11 Aug 2005 19:42:18 -0000
***************
*** 1330,1335 ****
--- 1330,1336 ----

@menu
* Command Syntax:: How to give commands to @value{GDBN}
+ * Error Handling:: How to detect errors in commands
* Completion:: Command completion
* Help:: How to ask @value{GDBN} for help
@end menu
***************
*** 1385,1390 ****
--- 1386,1423 ----
then fetches the next line relative to the current line from the
history
for editing.

+ @node Error Handling
+ @section Error handling
+
+ @cindex errors
+ @cindex error handling
+ @kindex @r{(terminate-on-error)}
+ Some @value{GDBN} commands cannot complete successfully, and must
terminate.
+ A command prints an error string when it encounters an error, and the
+ @value{GDBN} script (if any) that invoked the command is terminated
as well.
+
+ In some scripts, this behavior is undesirable. You can tell GDB to
set a
+ condition variable rather than terminate the script.
+
+ @table @code
+ @item set terminate-on-error on
+ Tell @value{GDBN} to terminate a script when a command it invokes
+ encounters an error. This is the default.
+
+ @item set terminate-on-error off
+ Do not terminate a script when a command it invokes encounters an
error;
+ rather, set @code{$_error} to 1 and continue script execution.
Commands
+ that terminate successfully set @code{$_error} to 0.
+
+ @item show terminate-on-error
+ Show whether or not @value{GDBN} will terminate a script if it invokes
+ a command that encounters an error.
+ @end table
+
+ Any command invocation will overwrite @code{$_error}; hence, you
should
+ copy @code{$_error} into a temporary variable after invoking any
command
+ whose output you want to check.
+
@node Completion
@section Command completion

Index: gdb/testsuite/gdb.base/Makefile.in
===================================================================
RCS file: /cvs/src/src/gdb/testsuite/gdb.base/Makefile.in,v
retrieving revision 1.4
diff -c -r1.4 Makefile.in
*** gdb/testsuite/gdb.base/Makefile.in 29 Jan 2004 11:15:44
-0000 1.4
--- gdb/testsuite/gdb.base/Makefile.in 11 Aug 2005 19:42:22 -0000
***************
*** 12,18 ****
scope section_command setshow setvar shmain sigall signals \
solib solib_sl so-impl-ld so-indr-cl \
step-line step-test structs structs2 \
! twice-tmp varargs vforked-prog watchpoint whatis

MISCELLANEOUS = coremmap.data ../foobar.baz \
shr1.sl shr2.sl solib_sl.sl solib1.sl solib2.sl
--- 12,18 ----
scope section_command setshow setvar shmain sigall signals \
solib solib_sl so-impl-ld so-indr-cl \
step-line step-test structs structs2 \
! terminate-on-error twice-tmp varargs vforked-prog watchpoint
whatis

MISCELLANEOUS = coremmap.data ../foobar.baz \
shr1.sl shr2.sl solib_sl.sl solib1.sl solib2.sl
Index: gdb/testsuite/gdb.base/terminate-on-error.c
===================================================================
RCS file: gdb/testsuite/gdb.base/terminate-on-error.c
diff -N gdb/testsuite/gdb.base/terminate-on-error.c
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- gdb/testsuite/gdb.base/terminate-on-error.c 11 Aug 2005
19:42:24 -0000
***************
*** 0 ****
--- 1,6 ----
+ #include <stdio.h>
+
+ int main(int argc, char** argv)
+ {
+ printf("Hello world!\n");
+ }
Index: gdb/testsuite/gdb.base/terminate-on-error.exp
===================================================================
RCS file: gdb/testsuite/gdb.base/terminate-on-error.exp
diff -N gdb/testsuite/gdb.base/terminate-on-error.exp
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- gdb/testsuite/gdb.base/terminate-on-error.exp 11 Aug 2005
19:42:24 -0000
***************
*** 0 ****
--- 1,72 ----
+ # Copyright 2001, 2003 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
+ # use this to debug:
+ #
+ # log_user 1
+
+ # terminate-on-error.exp -- Expect script to test proper error
handling when
+ # terminate-on-error is disabled.
+
+ if $tracelevel then {
+ strace $tracelevel
+ }
+
+ set testfile terminate-on-error
+ set srcfile ${testfile}.c
+ set binfile ${objdir}/${subdir}/${testfile}
+
+ remote_exec build "rm -f ${binfile}"
+ if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}"
executable {debug}] != "" } {
+ gdb_suppress_entire_file "Testcase compile failed, so all
tests in this file will automatically fail."
+ }
+
+ gdb_exit
+ gdb_start
+ gdb_reinitialize_dir $srcdir/$subdir
+ gdb_load ${binfile}
+
+ gdb_test "source ${srcdir}/${subdir}/terminate-on-error.gdb" \
+ "" \
+ "load test macro"
+
+ gdb_test "show terminate-on-error" \
+ "Script termination on command errors is on\..*" \
+ "check default value of terminate-on-error"
+
+ gdb_test "try" \
+ "Before.*evaluation of this expression requires the target
program to be active" \
+ "verify script terminates when terminate-on-error is on"
+
+ gdb_test "set terminate-on-error off" \
+ "" \
+ "turn terminate-on-error off"
+
+ gdb_test "try" \
+ "Before.*evaluation of this expression requires the target
program to be active.*After" \
+ "verify script does not terminate when terminate-on-error
is off"
+
+ gdb_test "print \$_error" \
+ ".*= 0" \
+ "verify that the last printf succeeded"
+
+ gdb_test "print \$stored_error" \
+ ".*= 1" \
+ "verify that the print failed"
+
+ return 0
Index: gdb/testsuite/gdb.base/terminate-on-error.gdb
===================================================================
RCS file: gdb/testsuite/gdb.base/terminate-on-error.gdb
diff -N gdb/testsuite/gdb.base/terminate-on-error.gdb
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- gdb/testsuite/gdb.base/terminate-on-error.gdb 11 Aug 2005
19:42:24 -0000
***************
*** 0 ****
--- 1,8 ----
+ set $stored_error = 0
+
+ define try
+ printf "Before\n"
+ print "test\n"
+ set $stored_error = $_error
+ printf "After\n"
+ end