[RFA,v2,7/8] Allow breakpoint commands to be set from Python

Message ID 20180425154133.3989-8-tom@tromey.com
State New, archived
Headers

Commit Message

Tom Tromey April 25, 2018, 3:41 p.m. UTC
  This changes the Python API so that breakpoint commands can be set by
writing to the "commands" attribute.

gdb/ChangeLog
2018-04-25  Tom Tromey  <tom@tromey.com>

	PR python/22731:
	* NEWS: Mention that breakpoint commands are writable.
	* python/py-breakpoint.c (bppy_set_commands): New function.
	(breakpoint_object_getset) <"commands">: Use it.

gdb/doc/ChangeLog
2018-04-25  Tom Tromey  <tom@tromey.com>

	PR python/22731:
	* python.texi (Breakpoints In Python): Mention that "commands" is
	writable.

gdb/testsuite/ChangeLog
2018-04-25  Tom Tromey  <tom@tromey.com>

	PR python/22731:
	* gdb.python/py-breakpoint.exp: Test setting breakpoint commands.
---
 gdb/ChangeLog                              |  7 +++++
 gdb/NEWS                                   |  5 ++++
 gdb/doc/ChangeLog                          |  6 ++++
 gdb/doc/python.texi                        |  2 +-
 gdb/python/py-breakpoint.c                 | 45 +++++++++++++++++++++++++++++-
 gdb/testsuite/ChangeLog                    |  5 ++++
 gdb/testsuite/gdb.python/py-breakpoint.exp | 10 ++++++-
 7 files changed, 77 insertions(+), 3 deletions(-)
  

Comments

Eli Zaretskii April 25, 2018, 4:13 p.m. UTC | #1
> From: Tom Tromey <tom@tromey.com>
> Cc: Tom Tromey <tom@tromey.com>
> Date: Wed, 25 Apr 2018 09:41:32 -0600
> 
> This changes the Python API so that breakpoint commands can be set by
> writing to the "commands" attribute.
> 
> gdb/ChangeLog
> 2018-04-25  Tom Tromey  <tom@tromey.com>
> 
> 	PR python/22731:
> 	* NEWS: Mention that breakpoint commands are writable.
> 	* python/py-breakpoint.c (bppy_set_commands): New function.
> 	(breakpoint_object_getset) <"commands">: Use it.
> 
> gdb/doc/ChangeLog
> 2018-04-25  Tom Tromey  <tom@tromey.com>
> 
> 	PR python/22731:
> 	* python.texi (Breakpoints In Python): Mention that "commands" is
> 	writable.
> 
> gdb/testsuite/ChangeLog
> 2018-04-25  Tom Tromey  <tom@tromey.com>
> 
> 	PR python/22731:
> 	* gdb.python/py-breakpoint.exp: Test setting breakpoint commands.

OK for the documentation parts.

Thanks.
  
Phil Muldoon April 30, 2018, 1:07 p.m. UTC | #2
>  
>  @node Finish Breakpoints in Python
> diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
> index d654b92a8c..a66e553cf8 100644
> --- a/gdb/python/py-breakpoint.c
> +++ b/gdb/python/py-breakpoint.c
> @@ -510,6 +510,49 @@ bppy_get_commands (PyObject *self, void *closure)
>    return host_string_to_python_string (stb.c_str ());
>  }
>  
> +/* Set the commands attached to a breakpoint.  Returns 0 on success.
> +   Returns -1 on error, with a python exception set.  */
> +static int
> +bppy_set_commands (PyObject *self, PyObject *newvalue, void *closure)
> +{
> +  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
> +  struct breakpoint *bp = self_bp->bp;
> +  struct gdb_exception except = exception_none;
> +
> +  BPPY_SET_REQUIRE_VALID (self_bp);
> +
> +  gdb::unique_xmalloc_ptr<char> commands
> +    (python_string_to_host_string (newvalue));
> +  if (commands == nullptr)
> +    return -1;
> +
> +  TRY
> +    {
> +      bool first = true;
> +      char *save_ptr = nullptr;
> +      auto reader
> +	= [&] ()
> +	  {
> +	    const char *result = strtok_r (first ? commands.get () : nullptr,
> +					   "\n", &save_ptr);
> +	    first = false;
> +	    return result;
> +	  };
> +
> +      counted_command_line lines = read_command_lines_1 (reader, 1, nullptr);
> +      breakpoint_set_commands (self_bp->bp, std::move (lines));
> +    }
> +  CATCH (ex, RETURN_MASK_ALL)
> +    {
> +      except = ex;
> +    }
> +  END_CATCH
> +
> +  GDB_PY_SET_HANDLE_EXCEPTION (except);
> +
> +  return 0;
> +}

The code bits LGTM but on a somewhat side note I'm wondering if there
are any side effects with the Python breakpoint stop callback? This
shouldn't interfere with the viability of this patch, though, because
a breakpoint previously could have had a command list attached to it
after being created in Python and via the commands command. I'm mildly
curious what would get called first.

Cheers

Phil
  
Tom Tromey April 30, 2018, 2:40 p.m. UTC | #3
Phil> The code bits LGTM but on a somewhat side note I'm wondering if there
Phil> are any side effects with the Python breakpoint stop callback? This
Phil> shouldn't interfere with the viability of this patch, though, because
Phil> a breakpoint previously could have had a command list attached to it
Phil> after being created in Python and via the commands command. I'm mildly
Phil> curious what would get called first.

The stop method is called first and the commands are only run if stop
returns True.  There's no conflict between the two.

Tom
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index 63fe30d175..14d5fbb7c0 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -24,6 +24,11 @@  set|show record btrace cpu
   Controls the processor to be used for enabling errata workarounds for
   branch trace decode.
 
+* Python Scripting
+
+  ** The commands attached to a breakpoint can be set by assigning to
+     the breakpoint's "commands" field.
+
 * New targets
 
 RiscV ELF			riscv*-*-elf
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index ebd48fffe7..a2b948a4ca 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -5116,7 +5116,7 @@  value is @code{None}.  This attribute is writable.
 This attribute holds the commands attached to the breakpoint.  If
 there are commands, this attribute's value is a string holding all the
 commands, separated by newlines.  If there are no commands, this
-attribute is @code{None}.  This attribute is not writable.
+attribute is @code{None}.  This attribute is writable.
 @end defvar
 
 @node Finish Breakpoints in Python
diff --git a/gdb/python/py-breakpoint.c b/gdb/python/py-breakpoint.c
index d654b92a8c..a66e553cf8 100644
--- a/gdb/python/py-breakpoint.c
+++ b/gdb/python/py-breakpoint.c
@@ -510,6 +510,49 @@  bppy_get_commands (PyObject *self, void *closure)
   return host_string_to_python_string (stb.c_str ());
 }
 
+/* Set the commands attached to a breakpoint.  Returns 0 on success.
+   Returns -1 on error, with a python exception set.  */
+static int
+bppy_set_commands (PyObject *self, PyObject *newvalue, void *closure)
+{
+  gdbpy_breakpoint_object *self_bp = (gdbpy_breakpoint_object *) self;
+  struct breakpoint *bp = self_bp->bp;
+  struct gdb_exception except = exception_none;
+
+  BPPY_SET_REQUIRE_VALID (self_bp);
+
+  gdb::unique_xmalloc_ptr<char> commands
+    (python_string_to_host_string (newvalue));
+  if (commands == nullptr)
+    return -1;
+
+  TRY
+    {
+      bool first = true;
+      char *save_ptr = nullptr;
+      auto reader
+	= [&] ()
+	  {
+	    const char *result = strtok_r (first ? commands.get () : nullptr,
+					   "\n", &save_ptr);
+	    first = false;
+	    return result;
+	  };
+
+      counted_command_line lines = read_command_lines_1 (reader, 1, nullptr);
+      breakpoint_set_commands (self_bp->bp, std::move (lines));
+    }
+  CATCH (ex, RETURN_MASK_ALL)
+    {
+      except = ex;
+    }
+  END_CATCH
+
+  GDB_PY_SET_HANDLE_EXCEPTION (except);
+
+  return 0;
+}
+
 /* Python function to get the breakpoint type.  */
 static PyObject *
 bppy_get_type (PyObject *self, void *closure)
@@ -1185,7 +1228,7 @@  when setting this property.", NULL },
   { "condition", bppy_get_condition, bppy_set_condition,
     "Condition of the breakpoint, as specified by the user,\
 or None if no condition set."},
-  { "commands", bppy_get_commands, NULL,
+  { "commands", bppy_get_commands, bppy_set_commands,
     "Commands of the breakpoint, as specified by the user."},
   { "type", bppy_get_type, NULL,
     "Type of breakpoint."},
diff --git a/gdb/testsuite/gdb.python/py-breakpoint.exp b/gdb/testsuite/gdb.python/py-breakpoint.exp
index 6e0ff88f87..3ce0ea11de 100644
--- a/gdb/testsuite/gdb.python/py-breakpoint.exp
+++ b/gdb/testsuite/gdb.python/py-breakpoint.exp
@@ -197,8 +197,16 @@  proc_with_prefix test_bkpt_cond_and_cmds { } {
 
     gdb_py_test_silent_cmd "python blist = gdb.breakpoints()" \
 	"Get Breakpoint List" 0
-    gdb_test "python print (blist\[len(blist)-1\].commands)" \
+    gdb_py_test_silent_cmd "python last_bp = blist\[len(blist)-1\]" \
+	"Find last breakpoint" 0
+    gdb_test "python print (last_bp.commands)" \
 	"print \"Command for breakpoint has been executed.\".*print result"
+
+    gdb_test_no_output "python last_bp.commands = 'echo hi\\necho there'" \
+	"set commands"
+    # Note the length is 3 because the string ends in a \n.
+    gdb_test "python print (len(last_bp.commands.split('\\n')))" "3" \
+	"check number of lines in commands"
 }
 
 proc_with_prefix test_bkpt_invisible { } {