/* Scheme interface to breakpoints. Copyright (C) 2008-2021 Free Software Foundation, Inc. This file is part of GDB. 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 . */ /* See README file in this directory for implementation notes, coding conventions, et.al. */ #include "defs.h" #include "value.h" #include "breakpoint.h" #include "gdbcmd.h" #include "gdbthread.h" #include "observable.h" #include "cli/cli-script.h" #include "ada-lang.h" #include "arch-utils.h" #include "language.h" #include "guile-internal.h" #include "location.h" /* The smob. N.B.: The name of this struct is known to breakpoint.h. Note: Breakpoints are added to gdb using a two step process: 1) Call make-breakpoint to create a object. 2) Call register-breakpoint! to add the breakpoint to gdb. It is done this way so that the constructor, make-breakpoint, doesn't have any side-effects. This means that the smob needs to store everything that was passed to make-breakpoint. */ typedef struct gdbscm_breakpoint_object { /* This always appears first. */ gdb_smob base; /* Non-zero if this breakpoint was created with make-breakpoint. */ int is_scheme_bkpt; /* For breakpoints created with make-breakpoint, these are the parameters that were passed to make-breakpoint. These values are not used except to register the breakpoint with GDB. */ struct { /* The string representation of the breakpoint. Space for this lives in GC space. */ char *location; /* The kind of breakpoint. At the moment this can only be one of bp_breakpoint, bp_watchpoint. */ enum bptype type; /* If a watchpoint, the kind of watchpoint. */ enum target_hw_bp_type access_type; /* Non-zero if the breakpoint is an "internal" breakpoint. */ int is_internal; /* Non-zero if the breakpoint is temporary. */ int is_temporary; } spec; /* The breakpoint number according to gdb. For breakpoints created from Scheme, this has the value -1 until the breakpoint is registered with gdb. This is recorded here because BP will be NULL when deleted. */ int number; /* The gdb breakpoint object, or NULL if the breakpoint has not been registered yet, or has been deleted. */ struct breakpoint *bp; /* Backlink to our containing smob. This is needed when we are deleted, we need to unprotect the object from GC. */ SCM containing_scm; /* A stop condition or #f. */ SCM stop; } breakpoint_smob; static const char breakpoint_smob_name[] = "gdb:breakpoint"; /* The tag Guile knows the breakpoint smob by. */ static scm_t_bits breakpoint_smob_tag; /* Variables used to pass information between the breakpoint_smob constructor and the breakpoint-created hook function. */ static SCM pending_breakpoint_scm = SCM_BOOL_F; /* Keywords used by create-breakpoint!. */ static SCM type_keyword; static SCM wp_class_keyword; static SCM internal_keyword; static SCM temporary_keyword; /* Administrivia for breakpoint smobs. */ /* The smob "free" function for . */ static size_t bpscm_free_breakpoint_smob (SCM self) { breakpoint_smob *bp_smob = (breakpoint_smob *) SCM_SMOB_DATA (self); if (bp_smob->bp) bp_smob->bp->scm_bp_object = NULL; /* Not necessary, done to catch bugs. */ bp_smob->bp = NULL; bp_smob->containing_scm = SCM_UNDEFINED; bp_smob->stop = SCM_UNDEFINED; return 0; } /* Return the name of TYPE. This doesn't handle all types, just the ones we export. */ static const char * bpscm_type_to_string (enum bptype type) { switch (type) { case bp_none: return "BP_NONE"; case bp_breakpoint: return "BP_BREAKPOINT"; case bp_watchpoint: return "BP_WATCHPOINT"; case bp_hardware_watchpoint: return "BP_HARDWARE_WATCHPOINT"; case bp_read_watchpoint: return "BP_READ_WATCHPOINT"; case bp_access_watchpoint: return "BP_ACCESS_WATCHPOINT"; case bp_catchpoint: return "BP_CATCHPOINT"; default: return "internal/other"; } } /* Return the name of ENABLE_STATE. */ static const char * bpscm_enable_state_to_string (enum enable_state enable_state) { switch (enable_state) { case bp_disabled: return "disabled"; case bp_enabled: return "enabled"; case bp_call_disabled: return "call_disabled"; default: return "unknown"; } } /* The smob "print" function for . */ static int bpscm_print_breakpoint_smob (SCM self, SCM port, scm_print_state *pstate) { breakpoint_smob *bp_smob = (breakpoint_smob *) SCM_SMOB_DATA (self); struct breakpoint *b = bp_smob->bp; gdbscm_printf (port, "#<%s", breakpoint_smob_name); /* Only print what we export to the user. The rest are possibly internal implementation details. */ gdbscm_printf (port, " #%d", bp_smob->number); /* Careful, the breakpoint may be invalid. */ if (b != NULL) { gdbscm_printf (port, " %s %s %s", bpscm_type_to_string (b->type), bpscm_enable_state_to_string (b->enable_state), b->silent ? "silent" : "noisy"); gdbscm_printf (port, " hit:%d", b->hit_count); gdbscm_printf (port, " ignore:%d", b->ignore_count); if (b->location != nullptr) { const char *str = event_location_to_string (b->location.get ()); if (str != nullptr) gdbscm_printf (port, " @%s", str); } } scm_puts (">", port); scm_remember_upto_here_1 (self); /* Non-zero means success. */ return 1; } /* Low level routine to create a object. */ static SCM bpscm_make_breakpoint_smob (void) { breakpoint_smob *bp_smob = (breakpoint_smob *) scm_gc_malloc (sizeof (breakpoint_smob), breakpoint_smob_name); SCM bp_scm; memset (bp_smob, 0, sizeof (*bp_smob)); bp_smob->number = -1; bp_smob->stop = SCM_BOOL_F; bp_scm = scm_new_smob (breakpoint_smob_tag, (scm_t_bits) bp_smob); bp_smob->containing_scm = bp_scm; gdbscm_init_gsmob (&bp_smob->base); return bp_scm; } /* Return non-zero if we want a Scheme wrapper for breakpoint B. If FROM_SCHEME is non-zero,this is called for a breakpoint created by the user from Scheme. Otherwise it is zero. */ static int bpscm_want_scm_wrapper_p (struct breakpoint *bp, int from_scheme) { /* Don't create objects for internal GDB breakpoints. */ if (bp->number < 0 && !from_scheme) return 0; /* The others are not supported. */ if (bp->type != bp_breakpoint && bp->type != bp_watchpoint && bp->type != bp_hardware_watchpoint && bp->type != bp_read_watchpoint && bp->type != bp_access_watchpoint && bp->type != bp_catchpoint) return 0; return 1; } /* Install the Scheme side of a breakpoint, CONTAINING_SCM, in the gdb side BP. */ static void bpscm_attach_scm_to_breakpoint (struct breakpoint *bp, SCM containing_scm) { breakpoint_smob *bp_smob; bp_smob = (breakpoint_smob *) SCM_SMOB_DATA (containing_scm); bp_smob->number = bp->number; bp_smob->bp = bp; bp_smob->containing_scm = containing_scm; bp_smob->bp->scm_bp_object = bp_smob; /* The owner of this breakpoint is not in GC-controlled memory, so we need to protect it from GC until the breakpoint is deleted. */ scm_gc_protect_object (containing_scm); } /* Return non-zero if SCM is a breakpoint smob. */ static int bpscm_is_breakpoint (SCM scm) { return SCM_SMOB_PREDICATE (breakpoint_smob_tag, scm); } /* (breakpoint? scm) -> boolean */ static SCM gdbscm_breakpoint_p (SCM scm) { return scm_from_bool (bpscm_is_breakpoint (scm)); } /* Returns the object in SELF. Throws an exception if SELF is not a object. */ static SCM bpscm_get_breakpoint_arg_unsafe (SCM self, int arg_pos, const char *func_name) { SCM_ASSERT_TYPE (bpscm_is_breakpoint (self), self, arg_pos, func_name, breakpoint_smob_name); return self; } /* Returns a pointer to the breakpoint smob of SELF. Throws an exception if SELF is not a object. */ static breakpoint_smob * bpscm_get_breakpoint_smob_arg_unsafe (SCM self, int arg_pos, const char *func_name) { SCM bp_scm = bpscm_get_breakpoint_arg_unsafe (self, arg_pos, func_name); breakpoint_smob *bp_smob = (breakpoint_smob *) SCM_SMOB_DATA (bp_scm); return bp_smob; } /* Return non-zero if breakpoint BP_SMOB is valid. */ static int bpscm_is_valid (breakpoint_smob *bp_smob) { return bp_smob->bp != NULL; } /* Returns the breakpoint smob in SELF, verifying it's valid. Throws an exception if SELF is not a object, or is invalid. */ static breakpoint_smob * bpscm_get_valid_breakpoint_smob_arg_unsafe (SCM self, int arg_pos, const char *func_name) { breakpoint_smob *bp_smob = bpscm_get_breakpoint_smob_arg_unsafe (self, arg_pos, func_name); if (!bpscm_is_valid (bp_smob)) { gdbscm_invalid_object_error (func_name, arg_pos, self, _("")); } return bp_smob; } /* Breakpoint methods. */ /* (make-breakpoint string [#:type integer] [#:wp-class integer] [#:internal boolean] [#:temporary boolean]) -> The result is the Scheme object. The breakpoint is not available to be used yet, however. It must still be added to gdb with register-breakpoint!. */ static SCM gdbscm_make_breakpoint (SCM location_scm, SCM rest) { const SCM keywords[] = { type_keyword, wp_class_keyword, internal_keyword, temporary_keyword, SCM_BOOL_F }; char *s; char *location; int type_arg_pos = -1, access_type_arg_pos = -1, internal_arg_pos = -1, temporary_arg_pos = -1; enum bptype type = bp_breakpoint; enum target_hw_bp_type access_type = hw_write; int internal = 0; int temporary = 0; SCM result; breakpoint_smob *bp_smob; gdbscm_parse_function_args (FUNC_NAME, SCM_ARG1, keywords, "s#iitt", location_scm, &location, rest, &type_arg_pos, &type, &access_type_arg_pos, &access_type, &internal_arg_pos, &internal, &temporary_arg_pos, &temporary); result = bpscm_make_breakpoint_smob (); bp_smob = (breakpoint_smob *) SCM_SMOB_DATA (result); s = location; location = gdbscm_gc_xstrdup (s); xfree (s); switch (type) { case bp_breakpoint: if (access_type_arg_pos > 0) { gdbscm_misc_error (FUNC_NAME, access_type_arg_pos, scm_from_int (access_type), _("access type with breakpoint is not allowed")); } break; case bp_watchpoint: switch (access_type) { case hw_write: case hw_access: case hw_read: break; default: gdbscm_out_of_range_error (FUNC_NAME, access_type_arg_pos, scm_from_int (access_type), _("invalid watchpoint class")); } break; case bp_none: case bp_hardware_watchpoint: case bp_read_watchpoint: case bp_access_watchpoint: case bp_catchpoint: { const char *type_name = bpscm_type_to_string (type); gdbscm_misc_error (FUNC_NAME, type_arg_pos, gdbscm_scm_from_c_string (type_name), _("unsupported breakpoint type")); } break; default: gdbscm_out_of_range_error (FUNC_NAME, type_arg_pos, scm_from_int (type), _("invalid breakpoint type")); } bp_smob->is_scheme_bkpt = 1; bp_smob->spec.location = location; bp_smob->spec.type = type; bp_smob->spec.access_type = access_type; bp_smob->spec.is_internal = internal; bp_smob->spec.is_temporary = temporary; return result; } /* (register-breakpoint! ) -> unspecified It is an error to register a breakpoint created outside of Guile, or an already-registered breakpoint. */ static SCM gdbscm_register_breakpoint_x (SCM self) { breakpoint_smob *bp_smob = bpscm_get_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); gdbscm_gdb_exception except {}; const char *location, *copy; /* We only support registering breakpoints created with make-breakpoint. */ if (!bp_smob->is_scheme_bkpt) scm_misc_error (FUNC_NAME, _("not a Scheme breakpoint"), SCM_EOL); if (bpscm_is_valid (bp_smob)) scm_misc_error (FUNC_NAME, _("breakpoint is already registered"), SCM_EOL); pending_breakpoint_scm = self; location = bp_smob->spec.location; copy = skip_spaces (location); event_location_up eloc = string_to_event_location_basic (©, current_language, symbol_name_match_type::WILD); try { int internal = bp_smob->spec.is_internal; int temporary = bp_smob->spec.is_temporary; switch (bp_smob->spec.type) { case bp_breakpoint: { const breakpoint_ops *ops = breakpoint_ops_for_event_location (eloc.get (), false); create_breakpoint (get_current_arch (), eloc.get (), NULL, -1, NULL, false, 0, temporary, bp_breakpoint, 0, AUTO_BOOLEAN_TRUE, ops, 0, 1, internal, 0); break; } case bp_watchpoint: { enum target_hw_bp_type access_type = bp_smob->spec.access_type; if (access_type == hw_write) watch_command_wrapper (location, 0, internal); else if (access_type == hw_access) awatch_command_wrapper (location, 0, internal); else if (access_type == hw_read) rwatch_command_wrapper (location, 0, internal); else gdb_assert_not_reached ("invalid access type"); break; } default: gdb_assert_not_reached ("invalid breakpoint type"); } } catch (const gdb_exception &ex) { except = unpack (ex); } /* Ensure this gets reset, even if there's an error. */ pending_breakpoint_scm = SCM_BOOL_F; GDBSCM_HANDLE_GDB_EXCEPTION (except); return SCM_UNSPECIFIED; } /* (delete-breakpoint! ) -> unspecified Scheme function which deletes (removes) the underlying GDB breakpoint from GDB's list of breakpoints. This triggers the breakpoint_deleted observer which will call gdbscm_breakpoint_deleted; that function cleans up the Scheme bits. */ static SCM gdbscm_delete_breakpoint_x (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); gdbscm_gdb_exception exc {}; try { delete_breakpoint (bp_smob->bp); } catch (const gdb_exception &except) { exc = unpack (except); } GDBSCM_HANDLE_GDB_EXCEPTION (exc); return SCM_UNSPECIFIED; } /* iterate_over_breakpoints function for gdbscm_breakpoints. */ static void bpscm_build_bp_list (struct breakpoint *bp, SCM *list) { breakpoint_smob *bp_smob = bp->scm_bp_object; /* Lazily create wrappers for breakpoints created outside Scheme. */ if (bp_smob == NULL) { if (bpscm_want_scm_wrapper_p (bp, 0)) { SCM bp_scm; bp_scm = bpscm_make_breakpoint_smob (); bpscm_attach_scm_to_breakpoint (bp, bp_scm); /* Refetch it. */ bp_smob = bp->scm_bp_object; } } /* Not all breakpoints will have a companion Scheme object. Only breakpoints that trigger the created_breakpoint observer call, and satisfy certain conditions (see bpscm_want_scm_wrapper_p), get a companion object (this includes Scheme-created breakpoints). */ if (bp_smob != NULL) *list = scm_cons (bp_smob->containing_scm, *list); } /* (breakpoints) -> list Return a list of all breakpoints. */ static SCM gdbscm_breakpoints (void) { SCM list = SCM_EOL; for (breakpoint *bp : all_breakpoints ()) bpscm_build_bp_list (bp, &list); return scm_reverse_x (list, SCM_EOL); } /* (breakpoint-valid? ) -> boolean Returns #t if SELF is still valid. */ static SCM gdbscm_breakpoint_valid_p (SCM self) { breakpoint_smob *bp_smob = bpscm_get_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_bool (bpscm_is_valid (bp_smob)); } /* (breakpoint-enabled? ) -> boolean */ static SCM gdbscm_breakpoint_enabled_p (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_bool (bp_smob->bp->enable_state == bp_enabled); } /* (set-breakpoint-enabled? boolean) -> unspecified */ static SCM gdbscm_set_breakpoint_enabled_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); SCM_ASSERT_TYPE (gdbscm_is_bool (newvalue), newvalue, SCM_ARG2, FUNC_NAME, _("boolean")); gdbscm_gdb_exception exc {}; try { if (gdbscm_is_true (newvalue)) enable_breakpoint (bp_smob->bp); else disable_breakpoint (bp_smob->bp); } catch (const gdb_exception &except) { exc = unpack (except); } GDBSCM_HANDLE_GDB_EXCEPTION (exc); return SCM_UNSPECIFIED; } /* (breakpoint-silent? ) -> boolean */ static SCM gdbscm_breakpoint_silent_p (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_bool (bp_smob->bp->silent); } /* (set-breakpoint-silent?! boolean) -> unspecified */ static SCM gdbscm_set_breakpoint_silent_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); SCM_ASSERT_TYPE (gdbscm_is_bool (newvalue), newvalue, SCM_ARG2, FUNC_NAME, _("boolean")); gdbscm_gdb_exception exc {}; try { breakpoint_set_silent (bp_smob->bp, gdbscm_is_true (newvalue)); } catch (const gdb_exception &except) { exc = unpack (except); } GDBSCM_HANDLE_GDB_EXCEPTION (exc); return SCM_UNSPECIFIED; } /* (breakpoint-ignore-count ) -> integer */ static SCM gdbscm_breakpoint_ignore_count (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_long (bp_smob->bp->ignore_count); } /* (set-breakpoint-ignore-count! integer) -> unspecified */ static SCM gdbscm_set_breakpoint_ignore_count_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); long value; SCM_ASSERT_TYPE (scm_is_signed_integer (newvalue, LONG_MIN, LONG_MAX), newvalue, SCM_ARG2, FUNC_NAME, _("integer")); value = scm_to_long (newvalue); if (value < 0) value = 0; gdbscm_gdb_exception exc {}; try { set_ignore_count (bp_smob->number, (int) value, 0); } catch (const gdb_exception &except) { exc = unpack (except); } GDBSCM_HANDLE_GDB_EXCEPTION (exc); return SCM_UNSPECIFIED; } /* (breakpoint-hit-count ) -> integer */ static SCM gdbscm_breakpoint_hit_count (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_long (bp_smob->bp->hit_count); } /* (set-breakpoint-hit-count! integer) -> unspecified */ static SCM gdbscm_set_breakpoint_hit_count_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); long value; SCM_ASSERT_TYPE (scm_is_signed_integer (newvalue, LONG_MIN, LONG_MAX), newvalue, SCM_ARG2, FUNC_NAME, _("integer")); value = scm_to_long (newvalue); if (value < 0) value = 0; if (value != 0) { gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, newvalue, _("hit-count must be zero")); } bp_smob->bp->hit_count = 0; return SCM_UNSPECIFIED; } /* (breakpoint-thread ) -> integer */ static SCM gdbscm_breakpoint_thread (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); if (bp_smob->bp->thread == -1) return SCM_BOOL_F; return scm_from_long (bp_smob->bp->thread); } /* (set-breakpoint-thread! integer) -> unspecified */ static SCM gdbscm_set_breakpoint_thread_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); long id; if (scm_is_signed_integer (newvalue, LONG_MIN, LONG_MAX)) { id = scm_to_long (newvalue); if (!valid_global_thread_id (id)) { gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, newvalue, _("invalid thread id")); } } else if (gdbscm_is_false (newvalue)) id = -1; else SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f")); breakpoint_set_thread (bp_smob->bp, id); return SCM_UNSPECIFIED; } /* (breakpoint-task ) -> integer */ static SCM gdbscm_breakpoint_task (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); if (bp_smob->bp->task == 0) return SCM_BOOL_F; return scm_from_long (bp_smob->bp->task); } /* (set-breakpoint-task! integer) -> unspecified */ static SCM gdbscm_set_breakpoint_task_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); long id; int valid_id = 0; if (scm_is_signed_integer (newvalue, LONG_MIN, LONG_MAX)) { id = scm_to_long (newvalue); gdbscm_gdb_exception exc {}; try { valid_id = valid_task_id (id); } catch (const gdb_exception &except) { exc = unpack (except); } GDBSCM_HANDLE_GDB_EXCEPTION (exc); if (! valid_id) { gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG2, newvalue, _("invalid task id")); } } else if (gdbscm_is_false (newvalue)) id = 0; else SCM_ASSERT_TYPE (0, newvalue, SCM_ARG2, FUNC_NAME, _("integer or #f")); gdbscm_gdb_exception exc {}; try { breakpoint_set_task (bp_smob->bp, id); } catch (const gdb_exception &except) { exc = unpack (except); } GDBSCM_HANDLE_GDB_EXCEPTION (exc); return SCM_UNSPECIFIED; } /* (breakpoint-location ) -> string */ static SCM gdbscm_breakpoint_location (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); const char *str; if (bp_smob->bp->type != bp_breakpoint) return SCM_BOOL_F; str = event_location_to_string (bp_smob->bp->location.get ()); if (! str) str = ""; return gdbscm_scm_from_c_string (str); } /* (breakpoint-expression ) -> string This is only valid for watchpoints. Returns #f for non-watchpoints. */ static SCM gdbscm_breakpoint_expression (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); struct watchpoint *wp; if (!is_watchpoint (bp_smob->bp)) return SCM_BOOL_F; wp = (struct watchpoint *) bp_smob->bp; const char *str = wp->exp_string; if (! str) str = ""; return gdbscm_scm_from_c_string (str); } /* (breakpoint-condition ) -> string */ static SCM gdbscm_breakpoint_condition (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); char *str; str = bp_smob->bp->cond_string; if (! str) return SCM_BOOL_F; return gdbscm_scm_from_c_string (str); } /* (set-breakpoint-condition! string|#f) -> unspecified */ static SCM gdbscm_set_breakpoint_condition_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); SCM_ASSERT_TYPE (scm_is_string (newvalue) || gdbscm_is_false (newvalue), newvalue, SCM_ARG2, FUNC_NAME, _("string or #f")); return gdbscm_wrap ([=] { gdb::unique_xmalloc_ptr exp = (gdbscm_is_false (newvalue) ? nullptr : gdbscm_scm_to_c_string (newvalue)); set_breakpoint_condition (bp_smob->bp, exp ? exp.get () : "", 0, false); return SCM_UNSPECIFIED; }); } /* (breakpoint-stop ) -> procedure or #f */ static SCM gdbscm_breakpoint_stop (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return bp_smob->stop; } /* (set-breakpoint-stop! procedure|#f) -> unspecified */ static SCM gdbscm_set_breakpoint_stop_x (SCM self, SCM newvalue) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); const struct extension_language_defn *extlang = NULL; SCM_ASSERT_TYPE (gdbscm_is_procedure (newvalue) || gdbscm_is_false (newvalue), newvalue, SCM_ARG2, FUNC_NAME, _("procedure or #f")); if (bp_smob->bp->cond_string != NULL) extlang = get_ext_lang_defn (EXT_LANG_GDB); if (extlang == NULL) extlang = get_breakpoint_cond_ext_lang (bp_smob->bp, EXT_LANG_GUILE); if (extlang != NULL) { char *error_text = xstrprintf (_("Only one stop condition allowed. There is" " currently a %s stop condition defined for" " this breakpoint."), ext_lang_capitalized_name (extlang)); scm_dynwind_begin ((scm_t_dynwind_flags) 0); gdbscm_dynwind_xfree (error_text); gdbscm_out_of_range_error (FUNC_NAME, SCM_ARG1, self, error_text); /* The following line, while unnecessary, is present for completeness sake. */ scm_dynwind_end (); } bp_smob->stop = newvalue; return SCM_UNSPECIFIED; } /* (breakpoint-commands ) -> string */ static SCM gdbscm_breakpoint_commands (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); struct breakpoint *bp; SCM result; bp = bp_smob->bp; if (bp->commands == NULL) return SCM_BOOL_F; string_file buf; current_uiout->redirect (&buf); gdbscm_gdb_exception exc {}; try { print_command_lines (current_uiout, breakpoint_commands (bp), 0); } catch (const gdb_exception &except) { exc = unpack (except); } current_uiout->redirect (NULL); GDBSCM_HANDLE_GDB_EXCEPTION (exc); result = gdbscm_scm_from_c_string (buf.c_str ()); return result; } /* (breakpoint-type ) -> integer */ static SCM gdbscm_breakpoint_type (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_long (bp_smob->bp->type); } /* (breakpoint-visible? ) -> boolean */ static SCM gdbscm_breakpoint_visible (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_bool (bp_smob->bp->number >= 0); } /* (breakpoint-number ) -> integer */ static SCM gdbscm_breakpoint_number (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_long (bp_smob->number); } /* (breakpoint-temporary? ) -> boolean */ static SCM gdbscm_breakpoint_temporary (SCM self) { breakpoint_smob *bp_smob = bpscm_get_valid_breakpoint_smob_arg_unsafe (self, SCM_ARG1, FUNC_NAME); return scm_from_bool (bp_smob->bp->disposition == disp_del || bp_smob->bp->disposition == disp_del_at_next_stop); } /* Return TRUE if "stop" has been set for this breakpoint. This is the extension_language_ops.breakpoint_has_cond "method". */ int gdbscm_breakpoint_has_cond (const struct extension_language_defn *extlang, struct breakpoint *b) { breakpoint_smob *bp_smob = b->scm_bp_object; if (bp_smob == NULL) return 0; return gdbscm_is_procedure (bp_smob->stop); } /* Call the "stop" method in the breakpoint class. This must only be called if gdbscm_breakpoint_has_cond returns true. If the stop method returns #t, the inferior will be stopped at the breakpoint. Otherwise the inferior will be allowed to continue (assuming other conditions don't indicate "stop"). This is the extension_language_ops.breakpoint_cond_says_stop "method". */ enum ext_lang_bp_stop gdbscm_breakpoint_cond_says_stop (const struct extension_language_defn *extlang, struct breakpoint *b) { breakpoint_smob *bp_smob = b->scm_bp_object; SCM predicate_result; int stop; if (bp_smob == NULL) return EXT_LANG_BP_STOP_UNSET; if (!gdbscm_is_procedure (bp_smob->stop)) return EXT_LANG_BP_STOP_UNSET; stop = 1; predicate_result = gdbscm_safe_call_1 (bp_smob->stop, bp_smob->containing_scm, NULL); if (gdbscm_is_exception (predicate_result)) ; /* Exception already printed. */ /* If the "stop" function returns #f that means the Scheme breakpoint wants GDB to continue. */ else if (gdbscm_is_false (predicate_result)) stop = 0; return stop ? EXT_LANG_BP_STOP_YES : EXT_LANG_BP_STOP_NO; } /* Event callback functions. */ /* Callback that is used when a breakpoint is created. For breakpoints created by Scheme, i.e., gdbscm_create_breakpoint_x, finish object creation by connecting the Scheme wrapper to the gdb object. We ignore breakpoints created from gdb or python here, we create the Scheme wrapper for those when there's a need to, e.g., gdbscm_breakpoints. */ static void bpscm_breakpoint_created (struct breakpoint *bp) { SCM bp_scm; if (gdbscm_is_false (pending_breakpoint_scm)) return; /* Verify our caller error checked the user's request. */ gdb_assert (bpscm_want_scm_wrapper_p (bp, 1)); bp_scm = pending_breakpoint_scm; pending_breakpoint_scm = SCM_BOOL_F; bpscm_attach_scm_to_breakpoint (bp, bp_scm); } /* Callback that is used when a breakpoint is deleted. This will invalidate the corresponding Scheme object. */ static void bpscm_breakpoint_deleted (struct breakpoint *b) { int num = b->number; struct breakpoint *bp; /* TODO: Why the lookup? We have B. */ bp = get_breakpoint (num); if (bp) { breakpoint_smob *bp_smob = bp->scm_bp_object; if (bp_smob) { bp_smob->bp = NULL; bp_smob->number = -1; bp_smob->stop = SCM_BOOL_F; scm_gc_unprotect_object (bp_smob->containing_scm); } } } /* Initialize the Scheme breakpoint code. */ static const scheme_integer_constant breakpoint_integer_constants[] = { { "BP_NONE", bp_none }, { "BP_BREAKPOINT", bp_breakpoint }, { "BP_WATCHPOINT", bp_watchpoint }, { "BP_HARDWARE_WATCHPOINT", bp_hardware_watchpoint }, { "BP_READ_WATCHPOINT", bp_read_watchpoint }, { "BP_ACCESS_WATCHPOINT", bp_access_watchpoint }, { "BP_CATCHPOINT", bp_catchpoint }, { "WP_READ", hw_read }, { "WP_WRITE", hw_write }, { "WP_ACCESS", hw_access }, END_INTEGER_CONSTANTS }; static const scheme_function breakpoint_functions[] = { { "make-breakpoint", 1, 0, 1, as_a_scm_t_subr (gdbscm_make_breakpoint), "\ Create a GDB breakpoint object.\n\ \n\ Arguments:\n\ location [#:type ] [#:wp-class ] [#:internal ] [#:temporary ]\n\ Returns:\n\ object" }, { "register-breakpoint!", 1, 0, 0, as_a_scm_t_subr (gdbscm_register_breakpoint_x), "\ Register a object with GDB." }, { "delete-breakpoint!", 1, 0, 0, as_a_scm_t_subr (gdbscm_delete_breakpoint_x), "\ Delete the breakpoint from GDB." }, { "breakpoints", 0, 0, 0, as_a_scm_t_subr (gdbscm_breakpoints), "\ Return a list of all GDB breakpoints.\n\ \n\ Arguments: none" }, { "breakpoint?", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_p), "\ Return #t if the object is a object." }, { "breakpoint-valid?", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_valid_p), "\ Return #t if the breakpoint has not been deleted from GDB." }, { "breakpoint-number", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_number), "\ Return the breakpoint's number." }, { "breakpoint-temporary?", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_temporary), "\ Return #t if the breakpoint is a temporary breakpoint." }, { "breakpoint-type", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_type), "\ Return the type of the breakpoint." }, { "breakpoint-visible?", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_visible), "\ Return #t if the breakpoint is visible to the user." }, { "breakpoint-location", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_location), "\ Return the location of the breakpoint as specified by the user." }, { "breakpoint-expression", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_expression), "\ Return the expression of the breakpoint as specified by the user.\n\ Valid for watchpoints only, returns #f for non-watchpoints." }, { "breakpoint-enabled?", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_enabled_p), "\ Return #t if the breakpoint is enabled." }, { "set-breakpoint-enabled!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_enabled_x), "\ Set the breakpoint's enabled state.\n\ \n\ Arguments: boolean" }, { "breakpoint-silent?", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_silent_p), "\ Return #t if the breakpoint is silent." }, { "set-breakpoint-silent!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_silent_x), "\ Set the breakpoint's silent state.\n\ \n\ Arguments: boolean" }, { "breakpoint-ignore-count", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_ignore_count), "\ Return the breakpoint's \"ignore\" count." }, { "set-breakpoint-ignore-count!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_ignore_count_x), "\ Set the breakpoint's \"ignore\" count.\n\ \n\ Arguments: count" }, { "breakpoint-hit-count", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_hit_count), "\ Return the breakpoint's \"hit\" count." }, { "set-breakpoint-hit-count!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_hit_count_x), "\ Set the breakpoint's \"hit\" count. The value must be zero.\n\ \n\ Arguments: 0" }, { "breakpoint-thread", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_thread), "\ Return the breakpoint's global thread id or #f if there isn't one." }, { "set-breakpoint-thread!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_thread_x), "\ Set the global thread id for this breakpoint.\n\ \n\ Arguments: global-thread-id" }, { "breakpoint-task", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_task), "\ Return the breakpoint's Ada task-id or #f if there isn't one." }, { "set-breakpoint-task!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_task_x), "\ Set the breakpoint's Ada task-id.\n\ \n\ Arguments: task-id" }, { "breakpoint-condition", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_condition), "\ Return the breakpoint's condition as specified by the user.\n\ Return #f if there isn't one." }, { "set-breakpoint-condition!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_condition_x), "\ Set the breakpoint's condition.\n\ \n\ Arguments: condition\n\ condition: a string" }, { "breakpoint-stop", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_stop), "\ Return the breakpoint's stop predicate.\n\ Return #f if there isn't one." }, { "set-breakpoint-stop!", 2, 0, 0, as_a_scm_t_subr (gdbscm_set_breakpoint_stop_x), "\ Set the breakpoint's stop predicate.\n\ \n\ Arguments: procedure\n\ procedure: A procedure of one argument, the breakpoint.\n\ Its result is true if program execution should stop." }, { "breakpoint-commands", 1, 0, 0, as_a_scm_t_subr (gdbscm_breakpoint_commands), "\ Return the breakpoint's commands." }, END_FUNCTIONS }; void gdbscm_initialize_breakpoints (void) { breakpoint_smob_tag = gdbscm_make_smob_type (breakpoint_smob_name, sizeof (breakpoint_smob)); scm_set_smob_free (breakpoint_smob_tag, bpscm_free_breakpoint_smob); scm_set_smob_print (breakpoint_smob_tag, bpscm_print_breakpoint_smob); gdb::observers::breakpoint_created.attach (bpscm_breakpoint_created, "scm-breakpoint"); gdb::observers::breakpoint_deleted.attach (bpscm_breakpoint_deleted, "scm-breakpoint"); gdbscm_define_integer_constants (breakpoint_integer_constants, 1); gdbscm_define_functions (breakpoint_functions, 1); type_keyword = scm_from_latin1_keyword ("type"); wp_class_keyword = scm_from_latin1_keyword ("wp-class"); internal_keyword = scm_from_latin1_keyword ("internal"); temporary_keyword = scm_from_latin1_keyword ("temporary"); }