[v3,PR,python/29603] Disable out-of-scope watchpoints

Message ID 20221020181101.193226-1-j3.soon777@gmail.com
State New
Headers
Series [v3,PR,python/29603] Disable out-of-scope watchpoints |

Commit Message

Johnson Sun Oct. 20, 2022, 6:11 p.m. UTC
  Currently, when a local software watchpoint goes out of scope, GDB sets the
watchpoint's disposition to `delete at next stop' and then normal stops
(i.e., stop and wait for the next GDB command). When GDB normal stops, it
automatically deletes the breakpoints with their disposition set to
`delete at next stop'.

Suppose a Python script decides not to normal stop when a local software
watchpoint goes out of scope, the watchpoint will not be automatically
deleted even when its disposition is set to `delete at next stop'.

Since GDB single-steps the program and tests the watched expression after each
instruction, not deleting the watchpoint causes the watchpoint to be hit many
more times than it should, as reported in PR python/29603.

This was happening because the watchpoint is not deleted or disabled when
going out of scope.

This commit fixes this issue by disabling the watchpoint when going out of
scope. It also adds a test to ensure this feature isn't regressed in the
future.

Two other solutions seem to solve this issue, but are in fact inappropriate:
1. Automatically delete breakpoints on all kinds of stops
   (in `fetch_inferior_event'). This solution is very slow since the deletion
   requires O(N) time for N breakpoints.
2. Disable the watchpoint after the watchpoint's disposition is set to
   `delete at next stop' (in `watchpoint_del_at_next_stop'). This solution
   modifies a non-extension-related code path, and isn't preferred since this
   issue cannot occur without extension languages. (gdb scripts always normal
   stop before continuing execution)

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
---
 gdb/breakpoint.c                           |  2 +
 gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
 gdb/testsuite/gdb.python/py-watchpoint.exp | 48 ++++++++++++++++++++++
 gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
 4 files changed, 107 insertions(+)
 create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
 create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
 create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
  

Comments

Johnson Sun Nov. 18, 2022, 12:17 p.m. UTC | #1
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 10/21/2022 2:11 AM, Johnson Sun wrote:
> Currently, when a local software watchpoint goes out of scope, GDB sets the
> watchpoint's disposition to `delete at next stop' and then normal stops
> (i.e., stop and wait for the next GDB command). When GDB normal stops, it
> automatically deletes the breakpoints with their disposition set to
> `delete at next stop'.
>
> Suppose a Python script decides not to normal stop when a local software
> watchpoint goes out of scope, the watchpoint will not be automatically
> deleted even when its disposition is set to `delete at next stop'.
>
> Since GDB single-steps the program and tests the watched expression after each
> instruction, not deleting the watchpoint causes the watchpoint to be hit many
> more times than it should, as reported in PR python/29603.
>
> This was happening because the watchpoint is not deleted or disabled when
> going out of scope.
>
> This commit fixes this issue by disabling the watchpoint when going out of
> scope. It also adds a test to ensure this feature isn't regressed in the
> future.
>
> Two other solutions seem to solve this issue, but are in fact inappropriate:
> 1. Automatically delete breakpoints on all kinds of stops
>     (in `fetch_inferior_event'). This solution is very slow since the deletion
>     requires O(N) time for N breakpoints.
> 2. Disable the watchpoint after the watchpoint's disposition is set to
>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This solution
>     modifies a non-extension-related code path, and isn't preferred since this
>     issue cannot occur without extension languages. (gdb scripts always normal
>     stop before continuing execution)
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
> ---
>   gdb/breakpoint.c                           |  2 +
>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 ++++++++++++++++++++++
>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>   4 files changed, 107 insertions(+)
>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index bff3bac7d1a..15f4ae2131c 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
>     /* Evaluate extension language breakpoints that have a "stop" method
>        implemented.  */
>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
> +  if (b->disposition == disp_del_at_next_stop)
> +    disable_breakpoint(b);
>   
>     if (is_watchpoint (b))
>       {
> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c b/gdb/testsuite/gdb.python/py-watchpoint.c
> new file mode 100644
> index 00000000000..4e1760bb05b
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
> @@ -0,0 +1,27 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2022 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 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  <http://www.gnu.org/licenses/>.  */
> +
> +#include <stdio.h>
> +
> +int
> +main (void)
> +{
> +  int i;
> +  for (i = 0; i < 3; i++)
> +    printf ("%d", i);
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp b/gdb/testsuite/gdb.python/py-watchpoint.exp
> new file mode 100644
> index 00000000000..ac58d75c523
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
> @@ -0,0 +1,48 @@
> +# Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
> +
> +# Check that Watchpoints are deleted after use.
> +
> +load_lib gdb-python.exp
> +
> +standard_testfile
> +
> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
> +    return -1
> +}
> +
> +# Skip all tests if Python scripting is not enabled.
> +if { [skip_python_tests] } { continue }
> +
> +if ![runto_main] then {
> +    return 0
> +}
> +
> +# For remote host testing
> +set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
> +
> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use hardware watchpoints"
> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check default BP count"
> +gdb_test "source $pyfile" ".*Python script imported.*" \
> +    "import python scripts"
> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check modified BP count"
> +gdb_test_sequence "continue" "run until program stops" {
> +    "Watchpoint Hit: ."
> +    "\[\r\n\]+Watchpoint . deleted because the program has left the block in"
> +    "\[\r\n\]+which its expression is valid\."
> +    "Watchpoint Hit: ."
> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
> +}
> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP count"
> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py b/gdb/testsuite/gdb.python/py-watchpoint.py
> new file mode 100644
> index 00000000000..ce5dee118ad
> --- /dev/null
> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
> @@ -0,0 +1,30 @@
> +# Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
> +
> +
> +class MyBreakpoint(gdb.Breakpoint):
> +    def __init__(self, *args, **kwargs):
> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
> +        self.i = 0
> +
> +    def stop(self):
> +        self.i += 1
> +        print("Watchpoint Hit:", self.i)
> +        return False
> +
> +
> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
> +
> +print("Python script imported")
  
Johnson Sun Nov. 25, 2022, 3:11 p.m. UTC | #2
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 11/18/2022 8:17 PM, JohnsonSun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>
> Johnson
>
> On 10/21/2022 2:11 AM, Johnson Sun wrote:
>> Currently, when a local software watchpoint goes out of scope, GDB 
>> sets the
>> watchpoint's disposition to `delete at next stop' and then normal stops
>> (i.e., stop and wait for the next GDB command). When GDB normal 
>> stops, it
>> automatically deletes the breakpoints with their disposition set to
>> `delete at next stop'.
>>
>> Suppose a Python script decides not to normal stop when a local software
>> watchpoint goes out of scope, the watchpoint will not be automatically
>> deleted even when its disposition is set to `delete at next stop'.
>>
>> Since GDB single-steps the program and tests the watched expression 
>> after each
>> instruction, not deleting the watchpoint causes the watchpoint to be 
>> hit many
>> more times than it should, as reported in PR python/29603.
>>
>> This was happening because the watchpoint is not deleted or disabled 
>> when
>> going out of scope.
>>
>> This commit fixes this issue by disabling the watchpoint when going 
>> out of
>> scope. It also adds a test to ensure this feature isn't regressed in the
>> future.
>>
>> Two other solutions seem to solve this issue, but are in fact 
>> inappropriate:
>> 1. Automatically delete breakpoints on all kinds of stops
>>     (in `fetch_inferior_event'). This solution is very slow since the 
>> deletion
>>     requires O(N) time for N breakpoints.
>> 2. Disable the watchpoint after the watchpoint's disposition is set to
>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>> solution
>>     modifies a non-extension-related code path, and isn't preferred 
>> since this
>>     issue cannot occur without extension languages. (gdb scripts 
>> always normal
>>     stop before continuing execution)
>>
>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>> ---
>>   gdb/breakpoint.c                           |  2 +
>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 ++++++++++++++++++++++
>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>   4 files changed, 107 insertions(+)
>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index bff3bac7d1a..15f4ae2131c 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat *bs, 
>> thread_info *thread)
>>     /* Evaluate extension language breakpoints that have a "stop" method
>>        implemented.  */
>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>> +  if (b->disposition == disp_del_at_next_stop)
>> +    disable_breakpoint(b);
>>       if (is_watchpoint (b))
>>       {
>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c 
>> b/gdb/testsuite/gdb.python/py-watchpoint.c
>> new file mode 100644
>> index 00000000000..4e1760bb05b
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
>> @@ -0,0 +1,27 @@
>> +/* This testcase is part of GDB, the GNU debugger.
>> +
>> +   Copyright 2022 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 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 
>> <http://www.gnu.org/licenses/>.  */
>> +
>> +#include <stdio.h>
>> +
>> +int
>> +main (void)
>> +{
>> +  int i;
>> +  for (i = 0; i < 3; i++)
>> +    printf ("%d", i);
>> +  return 0;
>> +}
>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp 
>> b/gdb/testsuite/gdb.python/py-watchpoint.exp
>> new file mode 100644
>> index 00000000000..ac58d75c523
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
>> @@ -0,0 +1,48 @@
>> +# Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
>> +
>> +# Check that Watchpoints are deleted after use.
>> +
>> +load_lib gdb-python.exp
>> +
>> +standard_testfile
>> +
>> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
>> +    return -1
>> +}
>> +
>> +# Skip all tests if Python scripting is not enabled.
>> +if { [skip_python_tests] } { continue }
>> +
>> +if ![runto_main] then {
>> +    return 0
>> +}
>> +
>> +# For remote host testing
>> +set pyfile [gdb_remote_download host 
>> ${srcdir}/${subdir}/${testfile}.py]
>> +
>> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use 
>> hardware watchpoints"
>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check default 
>> BP count"
>> +gdb_test "source $pyfile" ".*Python script imported.*" \
>> +    "import python scripts"
>> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check modified 
>> BP count"
>> +gdb_test_sequence "continue" "run until program stops" {
>> +    "Watchpoint Hit: ."
>> +    "\[\r\n\]+Watchpoint . deleted because the program has left the 
>> block in"
>> +    "\[\r\n\]+which its expression is valid\."
>> +    "Watchpoint Hit: ."
>> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
>> +}
>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP count"
>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py 
>> b/gdb/testsuite/gdb.python/py-watchpoint.py
>> new file mode 100644
>> index 00000000000..ce5dee118ad
>> --- /dev/null
>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
>> @@ -0,0 +1,30 @@
>> +# Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
>> +
>> +
>> +class MyBreakpoint(gdb.Breakpoint):
>> +    def __init__(self, *args, **kwargs):
>> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
>> +        self.i = 0
>> +
>> +    def stop(self):
>> +        self.i += 1
>> +        print("Watchpoint Hit:", self.i)
>> +        return False
>> +
>> +
>> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
>> +
>> +print("Python script imported")
>
  
Johnson Sun Dec. 4, 2022, 4:45 p.m. UTC | #3
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 11/25/2022 11:11 PM, Johnson Sun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>
> Johnson
>
> On 11/18/2022 8:17 PM, JohnsonSun wrote:
>> Ping for: 
>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>
>> Johnson
>>
>> On 10/21/2022 2:11 AM, Johnson Sun wrote:
>>> Currently, when a local software watchpoint goes out of scope, GDB 
>>> sets the
>>> watchpoint's disposition to `delete at next stop' and then normal stops
>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>> stops, it
>>> automatically deletes the breakpoints with their disposition set to
>>> `delete at next stop'.
>>>
>>> Suppose a Python script decides not to normal stop when a local 
>>> software
>>> watchpoint goes out of scope, the watchpoint will not be automatically
>>> deleted even when its disposition is set to `delete at next stop'.
>>>
>>> Since GDB single-steps the program and tests the watched expression 
>>> after each
>>> instruction, not deleting the watchpoint causes the watchpoint to be 
>>> hit many
>>> more times than it should, as reported in PR python/29603.
>>>
>>> This was happening because the watchpoint is not deleted or disabled 
>>> when
>>> going out of scope.
>>>
>>> This commit fixes this issue by disabling the watchpoint when going 
>>> out of
>>> scope. It also adds a test to ensure this feature isn't regressed in 
>>> the
>>> future.
>>>
>>> Two other solutions seem to solve this issue, but are in fact 
>>> inappropriate:
>>> 1. Automatically delete breakpoints on all kinds of stops
>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>> the deletion
>>>     requires O(N) time for N breakpoints.
>>> 2. Disable the watchpoint after the watchpoint's disposition is set to
>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>>> solution
>>>     modifies a non-extension-related code path, and isn't preferred 
>>> since this
>>>     issue cannot occur without extension languages. (gdb scripts 
>>> always normal
>>>     stop before continuing execution)
>>>
>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>> ---
>>>   gdb/breakpoint.c                           |  2 +
>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>> ++++++++++++++++++++++
>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>   4 files changed, 107 insertions(+)
>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>
>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>> index bff3bac7d1a..15f4ae2131c 100644
>>> --- a/gdb/breakpoint.c
>>> +++ b/gdb/breakpoint.c
>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>> *bs, thread_info *thread)
>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>> method
>>>        implemented.  */
>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>> +  if (b->disposition == disp_del_at_next_stop)
>>> +    disable_breakpoint(b);
>>>       if (is_watchpoint (b))
>>>       {
>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c 
>>> b/gdb/testsuite/gdb.python/py-watchpoint.c
>>> new file mode 100644
>>> index 00000000000..4e1760bb05b
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
>>> @@ -0,0 +1,27 @@
>>> +/* This testcase is part of GDB, the GNU debugger.
>>> +
>>> +   Copyright 2022 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 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 
>>> <http://www.gnu.org/licenses/>.  */
>>> +
>>> +#include <stdio.h>
>>> +
>>> +int
>>> +main (void)
>>> +{
>>> +  int i;
>>> +  for (i = 0; i < 3; i++)
>>> +    printf ("%d", i);
>>> +  return 0;
>>> +}
>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp 
>>> b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>> new file mode 100644
>>> index 00000000000..ac58d75c523
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>> @@ -0,0 +1,48 @@
>>> +# Copyright (C) 2022 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 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 
>>> <http://www.gnu.org/licenses/>.
>>> +
>>> +# Check that Watchpoints are deleted after use.
>>> +
>>> +load_lib gdb-python.exp
>>> +
>>> +standard_testfile
>>> +
>>> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
>>> +    return -1
>>> +}
>>> +
>>> +# Skip all tests if Python scripting is not enabled.
>>> +if { [skip_python_tests] } { continue }
>>> +
>>> +if ![runto_main] then {
>>> +    return 0
>>> +}
>>> +
>>> +# For remote host testing
>>> +set pyfile [gdb_remote_download host 
>>> ${srcdir}/${subdir}/${testfile}.py]
>>> +
>>> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use 
>>> hardware watchpoints"
>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check default 
>>> BP count"
>>> +gdb_test "source $pyfile" ".*Python script imported.*" \
>>> +    "import python scripts"
>>> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check modified 
>>> BP count"
>>> +gdb_test_sequence "continue" "run until program stops" {
>>> +    "Watchpoint Hit: ."
>>> +    "\[\r\n\]+Watchpoint . deleted because the program has left the 
>>> block in"
>>> +    "\[\r\n\]+which its expression is valid\."
>>> +    "Watchpoint Hit: ."
>>> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
>>> +}
>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP count"
>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py 
>>> b/gdb/testsuite/gdb.python/py-watchpoint.py
>>> new file mode 100644
>>> index 00000000000..ce5dee118ad
>>> --- /dev/null
>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
>>> @@ -0,0 +1,30 @@
>>> +# Copyright (C) 2022 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 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 
>>> <http://www.gnu.org/licenses/>.
>>> +
>>> +
>>> +class MyBreakpoint(gdb.Breakpoint):
>>> +    def __init__(self, *args, **kwargs):
>>> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
>>> +        self.i = 0
>>> +
>>> +    def stop(self):
>>> +        self.i += 1
>>> +        print("Watchpoint Hit:", self.i)
>>> +        return False
>>> +
>>> +
>>> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
>>> +
>>> +print("Python script imported")
>>
  
Johnson Sun Dec. 12, 2022, 9:44 p.m. UTC | #4
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 12/5/2022 12:45 AM, Johnson Sun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>
> Johnson
>
> On 11/25/2022 11:11 PM, Johnson Sun wrote:
>> Ping for: 
>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>
>> Johnson
>>
>> On 11/18/2022 8:17 PM, JohnsonSun wrote:
>>> Ping for: 
>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>
>>> Johnson
>>>
>>> On 10/21/2022 2:11 AM, Johnson Sun wrote:
>>>> Currently, when a local software watchpoint goes out of scope, GDB 
>>>> sets the
>>>> watchpoint's disposition to `delete at next stop' and then normal 
>>>> stops
>>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>>> stops, it
>>>> automatically deletes the breakpoints with their disposition set to
>>>> `delete at next stop'.
>>>>
>>>> Suppose a Python script decides not to normal stop when a local 
>>>> software
>>>> watchpoint goes out of scope, the watchpoint will not be automatically
>>>> deleted even when its disposition is set to `delete at next stop'.
>>>>
>>>> Since GDB single-steps the program and tests the watched expression 
>>>> after each
>>>> instruction, not deleting the watchpoint causes the watchpoint to 
>>>> be hit many
>>>> more times than it should, as reported in PR python/29603.
>>>>
>>>> This was happening because the watchpoint is not deleted or 
>>>> disabled when
>>>> going out of scope.
>>>>
>>>> This commit fixes this issue by disabling the watchpoint when going 
>>>> out of
>>>> scope. It also adds a test to ensure this feature isn't regressed 
>>>> in the
>>>> future.
>>>>
>>>> Two other solutions seem to solve this issue, but are in fact 
>>>> inappropriate:
>>>> 1. Automatically delete breakpoints on all kinds of stops
>>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>>> the deletion
>>>>     requires O(N) time for N breakpoints.
>>>> 2. Disable the watchpoint after the watchpoint's disposition is set to
>>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>>>> solution
>>>>     modifies a non-extension-related code path, and isn't preferred 
>>>> since this
>>>>     issue cannot occur without extension languages. (gdb scripts 
>>>> always normal
>>>>     stop before continuing execution)
>>>>
>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>>> ---
>>>>   gdb/breakpoint.c                           |  2 +
>>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>>> ++++++++++++++++++++++
>>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>>   4 files changed, 107 insertions(+)
>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>>
>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>> index bff3bac7d1a..15f4ae2131c 100644
>>>> --- a/gdb/breakpoint.c
>>>> +++ b/gdb/breakpoint.c
>>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>>> *bs, thread_info *thread)
>>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>>> method
>>>>        implemented.  */
>>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>>> +  if (b->disposition == disp_del_at_next_stop)
>>>> +    disable_breakpoint(b);
>>>>       if (is_watchpoint (b))
>>>>       {
>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c 
>>>> b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>> new file mode 100644
>>>> index 00000000000..4e1760bb05b
>>>> --- /dev/null
>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>> @@ -0,0 +1,27 @@
>>>> +/* This testcase is part of GDB, the GNU debugger.
>>>> +
>>>> +   Copyright 2022 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 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 
>>>> <http://www.gnu.org/licenses/>.  */
>>>> +
>>>> +#include <stdio.h>
>>>> +
>>>> +int
>>>> +main (void)
>>>> +{
>>>> +  int i;
>>>> +  for (i = 0; i < 3; i++)
>>>> +    printf ("%d", i);
>>>> +  return 0;
>>>> +}
>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp 
>>>> b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>> new file mode 100644
>>>> index 00000000000..ac58d75c523
>>>> --- /dev/null
>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>> @@ -0,0 +1,48 @@
>>>> +# Copyright (C) 2022 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 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 
>>>> <http://www.gnu.org/licenses/>.
>>>> +
>>>> +# Check that Watchpoints are deleted after use.
>>>> +
>>>> +load_lib gdb-python.exp
>>>> +
>>>> +standard_testfile
>>>> +
>>>> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
>>>> +    return -1
>>>> +}
>>>> +
>>>> +# Skip all tests if Python scripting is not enabled.
>>>> +if { [skip_python_tests] } { continue }
>>>> +
>>>> +if ![runto_main] then {
>>>> +    return 0
>>>> +}
>>>> +
>>>> +# For remote host testing
>>>> +set pyfile [gdb_remote_download host 
>>>> ${srcdir}/${subdir}/${testfile}.py]
>>>> +
>>>> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use 
>>>> hardware watchpoints"
>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check default 
>>>> BP count"
>>>> +gdb_test "source $pyfile" ".*Python script imported.*" \
>>>> +    "import python scripts"
>>>> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check 
>>>> modified BP count"
>>>> +gdb_test_sequence "continue" "run until program stops" {
>>>> +    "Watchpoint Hit: ."
>>>> +    "\[\r\n\]+Watchpoint . deleted because the program has left 
>>>> the block in"
>>>> +    "\[\r\n\]+which its expression is valid\."
>>>> +    "Watchpoint Hit: ."
>>>> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
>>>> +}
>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP count"
>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py 
>>>> b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>> new file mode 100644
>>>> index 00000000000..ce5dee118ad
>>>> --- /dev/null
>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>> @@ -0,0 +1,30 @@
>>>> +# Copyright (C) 2022 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 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 
>>>> <http://www.gnu.org/licenses/>.
>>>> +
>>>> +
>>>> +class MyBreakpoint(gdb.Breakpoint):
>>>> +    def __init__(self, *args, **kwargs):
>>>> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
>>>> +        self.i = 0
>>>> +
>>>> +    def stop(self):
>>>> +        self.i += 1
>>>> +        print("Watchpoint Hit:", self.i)
>>>> +        return False
>>>> +
>>>> +
>>>> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
>>>> +
>>>> +print("Python script imported")
>>>
  
Johnson Sun Dec. 20, 2022, 10:08 p.m. UTC | #5
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 12/13/2022 5:44 AM, Johnson Sun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>
> Johnson
>
> On 12/5/2022 12:45 AM, Johnson Sun wrote:
>> Ping for: 
>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>
>> Johnson
>>
>> On 11/25/2022 11:11 PM, Johnson Sun wrote:
>>> Ping for: 
>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>
>>> Johnson
>>>
>>> On 11/18/2022 8:17 PM, JohnsonSun wrote:
>>>> Ping for: 
>>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>>
>>>> Johnson
>>>>
>>>> On 10/21/2022 2:11 AM, Johnson Sun wrote:
>>>>> Currently, when a local software watchpoint goes out of scope, GDB 
>>>>> sets the
>>>>> watchpoint's disposition to `delete at next stop' and then normal 
>>>>> stops
>>>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>>>> stops, it
>>>>> automatically deletes the breakpoints with their disposition set to
>>>>> `delete at next stop'.
>>>>>
>>>>> Suppose a Python script decides not to normal stop when a local 
>>>>> software
>>>>> watchpoint goes out of scope, the watchpoint will not be 
>>>>> automatically
>>>>> deleted even when its disposition is set to `delete at next stop'.
>>>>>
>>>>> Since GDB single-steps the program and tests the watched 
>>>>> expression after each
>>>>> instruction, not deleting the watchpoint causes the watchpoint to 
>>>>> be hit many
>>>>> more times than it should, as reported in PR python/29603.
>>>>>
>>>>> This was happening because the watchpoint is not deleted or 
>>>>> disabled when
>>>>> going out of scope.
>>>>>
>>>>> This commit fixes this issue by disabling the watchpoint when 
>>>>> going out of
>>>>> scope. It also adds a test to ensure this feature isn't regressed 
>>>>> in the
>>>>> future.
>>>>>
>>>>> Two other solutions seem to solve this issue, but are in fact 
>>>>> inappropriate:
>>>>> 1. Automatically delete breakpoints on all kinds of stops
>>>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>>>> the deletion
>>>>>     requires O(N) time for N breakpoints.
>>>>> 2. Disable the watchpoint after the watchpoint's disposition is 
>>>>> set to
>>>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>>>>> solution
>>>>>     modifies a non-extension-related code path, and isn't 
>>>>> preferred since this
>>>>>     issue cannot occur without extension languages. (gdb scripts 
>>>>> always normal
>>>>>     stop before continuing execution)
>>>>>
>>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>>>> ---
>>>>>   gdb/breakpoint.c                           |  2 +
>>>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>>>> ++++++++++++++++++++++
>>>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>>>   4 files changed, 107 insertions(+)
>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>
>>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>>> index bff3bac7d1a..15f4ae2131c 100644
>>>>> --- a/gdb/breakpoint.c
>>>>> +++ b/gdb/breakpoint.c
>>>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>>>> *bs, thread_info *thread)
>>>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>>>> method
>>>>>        implemented.  */
>>>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>>>> +  if (b->disposition == disp_del_at_next_stop)
>>>>> +    disable_breakpoint(b);
>>>>>       if (is_watchpoint (b))
>>>>>       {
>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c 
>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>>> new file mode 100644
>>>>> index 00000000000..4e1760bb05b
>>>>> --- /dev/null
>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>>> @@ -0,0 +1,27 @@
>>>>> +/* This testcase is part of GDB, the GNU debugger.
>>>>> +
>>>>> +   Copyright 2022 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 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 
>>>>> <http://www.gnu.org/licenses/>.  */
>>>>> +
>>>>> +#include <stdio.h>
>>>>> +
>>>>> +int
>>>>> +main (void)
>>>>> +{
>>>>> +  int i;
>>>>> +  for (i = 0; i < 3; i++)
>>>>> +    printf ("%d", i);
>>>>> +  return 0;
>>>>> +}
>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp 
>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>> new file mode 100644
>>>>> index 00000000000..ac58d75c523
>>>>> --- /dev/null
>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>> @@ -0,0 +1,48 @@
>>>>> +# Copyright (C) 2022 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 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 
>>>>> <http://www.gnu.org/licenses/>.
>>>>> +
>>>>> +# Check that Watchpoints are deleted after use.
>>>>> +
>>>>> +load_lib gdb-python.exp
>>>>> +
>>>>> +standard_testfile
>>>>> +
>>>>> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
>>>>> +    return -1
>>>>> +}
>>>>> +
>>>>> +# Skip all tests if Python scripting is not enabled.
>>>>> +if { [skip_python_tests] } { continue }
>>>>> +
>>>>> +if ![runto_main] then {
>>>>> +    return 0
>>>>> +}
>>>>> +
>>>>> +# For remote host testing
>>>>> +set pyfile [gdb_remote_download host 
>>>>> ${srcdir}/${subdir}/${testfile}.py]
>>>>> +
>>>>> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use 
>>>>> hardware watchpoints"
>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check 
>>>>> default BP count"
>>>>> +gdb_test "source $pyfile" ".*Python script imported.*" \
>>>>> +    "import python scripts"
>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check 
>>>>> modified BP count"
>>>>> +gdb_test_sequence "continue" "run until program stops" {
>>>>> +    "Watchpoint Hit: ."
>>>>> +    "\[\r\n\]+Watchpoint . deleted because the program has left 
>>>>> the block in"
>>>>> +    "\[\r\n\]+which its expression is valid\."
>>>>> +    "Watchpoint Hit: ."
>>>>> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
>>>>> +}
>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP count"
>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py 
>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>>> new file mode 100644
>>>>> index 00000000000..ce5dee118ad
>>>>> --- /dev/null
>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Copyright (C) 2022 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 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 
>>>>> <http://www.gnu.org/licenses/>.
>>>>> +
>>>>> +
>>>>> +class MyBreakpoint(gdb.Breakpoint):
>>>>> +    def __init__(self, *args, **kwargs):
>>>>> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
>>>>> +        self.i = 0
>>>>> +
>>>>> +    def stop(self):
>>>>> +        self.i += 1
>>>>> +        print("Watchpoint Hit:", self.i)
>>>>> +        return False
>>>>> +
>>>>> +
>>>>> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
>>>>> +
>>>>> +print("Python script imported")
>>>>
  
Johnson Sun Dec. 27, 2022, 4:40 p.m. UTC | #6
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 12/21/2022 6:08 AM, Johnson Sun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>
> Johnson
>
> On 12/13/2022 5:44 AM, Johnson Sun wrote:
>> Ping for: 
>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>
>> Johnson
>>
>> On 12/5/2022 12:45 AM, Johnson Sun wrote:
>>> Ping for: 
>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>
>>> Johnson
>>>
>>> On 11/25/2022 11:11 PM, Johnson Sun wrote:
>>>> Ping for: 
>>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>>
>>>> Johnson
>>>>
>>>> On 11/18/2022 8:17 PM, JohnsonSun wrote:
>>>>> Ping for: 
>>>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>>>
>>>>> Johnson
>>>>>
>>>>> On 10/21/2022 2:11 AM, Johnson Sun wrote:
>>>>>> Currently, when a local software watchpoint goes out of scope, 
>>>>>> GDB sets the
>>>>>> watchpoint's disposition to `delete at next stop' and then normal 
>>>>>> stops
>>>>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>>>>> stops, it
>>>>>> automatically deletes the breakpoints with their disposition set to
>>>>>> `delete at next stop'.
>>>>>>
>>>>>> Suppose a Python script decides not to normal stop when a local 
>>>>>> software
>>>>>> watchpoint goes out of scope, the watchpoint will not be 
>>>>>> automatically
>>>>>> deleted even when its disposition is set to `delete at next stop'.
>>>>>>
>>>>>> Since GDB single-steps the program and tests the watched 
>>>>>> expression after each
>>>>>> instruction, not deleting the watchpoint causes the watchpoint to 
>>>>>> be hit many
>>>>>> more times than it should, as reported in PR python/29603.
>>>>>>
>>>>>> This was happening because the watchpoint is not deleted or 
>>>>>> disabled when
>>>>>> going out of scope.
>>>>>>
>>>>>> This commit fixes this issue by disabling the watchpoint when 
>>>>>> going out of
>>>>>> scope. It also adds a test to ensure this feature isn't regressed 
>>>>>> in the
>>>>>> future.
>>>>>>
>>>>>> Two other solutions seem to solve this issue, but are in fact 
>>>>>> inappropriate:
>>>>>> 1. Automatically delete breakpoints on all kinds of stops
>>>>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>>>>> the deletion
>>>>>>     requires O(N) time for N breakpoints.
>>>>>> 2. Disable the watchpoint after the watchpoint's disposition is 
>>>>>> set to
>>>>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). 
>>>>>> This solution
>>>>>>     modifies a non-extension-related code path, and isn't 
>>>>>> preferred since this
>>>>>>     issue cannot occur without extension languages. (gdb scripts 
>>>>>> always normal
>>>>>>     stop before continuing execution)
>>>>>>
>>>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>>>>> ---
>>>>>>   gdb/breakpoint.c                           |  2 +
>>>>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>>>>> ++++++++++++++++++++++
>>>>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>>>>   4 files changed, 107 insertions(+)
>>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>>
>>>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>>>> index bff3bac7d1a..15f4ae2131c 100644
>>>>>> --- a/gdb/breakpoint.c
>>>>>> +++ b/gdb/breakpoint.c
>>>>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>>>>> *bs, thread_info *thread)
>>>>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>>>>> method
>>>>>>        implemented.  */
>>>>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>>>>> +  if (b->disposition == disp_del_at_next_stop)
>>>>>> +    disable_breakpoint(b);
>>>>>>       if (is_watchpoint (b))
>>>>>>       {
>>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c 
>>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>> new file mode 100644
>>>>>> index 00000000000..4e1760bb05b
>>>>>> --- /dev/null
>>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>> @@ -0,0 +1,27 @@
>>>>>> +/* This testcase is part of GDB, the GNU debugger.
>>>>>> +
>>>>>> +   Copyright 2022 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 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 
>>>>>> <http://www.gnu.org/licenses/>.  */
>>>>>> +
>>>>>> +#include <stdio.h>
>>>>>> +
>>>>>> +int
>>>>>> +main (void)
>>>>>> +{
>>>>>> +  int i;
>>>>>> +  for (i = 0; i < 3; i++)
>>>>>> +    printf ("%d", i);
>>>>>> +  return 0;
>>>>>> +}
>>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp 
>>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>> new file mode 100644
>>>>>> index 00000000000..ac58d75c523
>>>>>> --- /dev/null
>>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>> @@ -0,0 +1,48 @@
>>>>>> +# Copyright (C) 2022 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 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 
>>>>>> <http://www.gnu.org/licenses/>.
>>>>>> +
>>>>>> +# Check that Watchpoints are deleted after use.
>>>>>> +
>>>>>> +load_lib gdb-python.exp
>>>>>> +
>>>>>> +standard_testfile
>>>>>> +
>>>>>> +if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
>>>>>> +    return -1
>>>>>> +}
>>>>>> +
>>>>>> +# Skip all tests if Python scripting is not enabled.
>>>>>> +if { [skip_python_tests] } { continue }
>>>>>> +
>>>>>> +if ![runto_main] then {
>>>>>> +    return 0
>>>>>> +}
>>>>>> +
>>>>>> +# For remote host testing
>>>>>> +set pyfile [gdb_remote_download host 
>>>>>> ${srcdir}/${subdir}/${testfile}.py]
>>>>>> +
>>>>>> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use 
>>>>>> hardware watchpoints"
>>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check 
>>>>>> default BP count"
>>>>>> +gdb_test "source $pyfile" ".*Python script imported.*" \
>>>>>> +    "import python scripts"
>>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check 
>>>>>> modified BP count"
>>>>>> +gdb_test_sequence "continue" "run until program stops" {
>>>>>> +    "Watchpoint Hit: ."
>>>>>> +    "\[\r\n\]+Watchpoint . deleted because the program has left 
>>>>>> the block in"
>>>>>> +    "\[\r\n\]+which its expression is valid\."
>>>>>> +    "Watchpoint Hit: ."
>>>>>> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
>>>>>> +}
>>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP 
>>>>>> count"
>>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py 
>>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>> new file mode 100644
>>>>>> index 00000000000..ce5dee118ad
>>>>>> --- /dev/null
>>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Copyright (C) 2022 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 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 
>>>>>> <http://www.gnu.org/licenses/>.
>>>>>> +
>>>>>> +
>>>>>> +class MyBreakpoint(gdb.Breakpoint):
>>>>>> +    def __init__(self, *args, **kwargs):
>>>>>> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
>>>>>> +        self.i = 0
>>>>>> +
>>>>>> +    def stop(self):
>>>>>> +        self.i += 1
>>>>>> +        print("Watchpoint Hit:", self.i)
>>>>>> +        return False
>>>>>> +
>>>>>> +
>>>>>> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
>>>>>> +
>>>>>> +print("Python script imported")
>>>>>
  
Johnson Sun Jan. 12, 2023, 6:34 p.m. UTC | #7
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.

Johnson

On 12/28/2022 12:40 AM, JohnsonSun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>
> Johnson
>
> On 12/21/2022 6:08 AM, Johnson Sun wrote:
>> Ping for: 
>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>
>> Johnson
>>
>> On 12/13/2022 5:44 AM, Johnson Sun wrote:
>>> Ping for: 
>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>
>>> Johnson
>>>
>>> On 12/5/2022 12:45 AM, Johnson Sun wrote:
>>>> Ping for: 
>>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>>
>>>> Johnson
>>>>
>>>> On 11/25/2022 11:11 PM, Johnson Sun wrote:
>>>>> Ping for: 
>>>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>>>
>>>>> Johnson
>>>>>
>>>>> On 11/18/2022 8:17 PM, JohnsonSun wrote:
>>>>>> Ping for: 
>>>>>> <https://sourceware.org/pipermail/gdb-patches/2022-October/192909.html>.
>>>>>>
>>>>>> Johnson
>>>>>>
>>>>>> On 10/21/2022 2:11 AM, Johnson Sun wrote:
>>>>>>> Currently, when a local software watchpoint goes out of scope, 
>>>>>>> GDB sets the
>>>>>>> watchpoint's disposition to `delete at next stop' and then 
>>>>>>> normal stops
>>>>>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>>>>>> stops, it
>>>>>>> automatically deletes the breakpoints with their disposition set to
>>>>>>> `delete at next stop'.
>>>>>>>
>>>>>>> Suppose a Python script decides not to normal stop when a local 
>>>>>>> software
>>>>>>> watchpoint goes out of scope, the watchpoint will not be 
>>>>>>> automatically
>>>>>>> deleted even when its disposition is set to `delete at next stop'.
>>>>>>>
>>>>>>> Since GDB single-steps the program and tests the watched 
>>>>>>> expression after each
>>>>>>> instruction, not deleting the watchpoint causes the watchpoint 
>>>>>>> to be hit many
>>>>>>> more times than it should, as reported in PR python/29603.
>>>>>>>
>>>>>>> This was happening because the watchpoint is not deleted or 
>>>>>>> disabled when
>>>>>>> going out of scope.
>>>>>>>
>>>>>>> This commit fixes this issue by disabling the watchpoint when 
>>>>>>> going out of
>>>>>>> scope. It also adds a test to ensure this feature isn't 
>>>>>>> regressed in the
>>>>>>> future.
>>>>>>>
>>>>>>> Two other solutions seem to solve this issue, but are in fact 
>>>>>>> inappropriate:
>>>>>>> 1. Automatically delete breakpoints on all kinds of stops
>>>>>>>     (in `fetch_inferior_event'). This solution is very slow 
>>>>>>> since the deletion
>>>>>>>     requires O(N) time for N breakpoints.
>>>>>>> 2. Disable the watchpoint after the watchpoint's disposition is 
>>>>>>> set to
>>>>>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). 
>>>>>>> This solution
>>>>>>>     modifies a non-extension-related code path, and isn't 
>>>>>>> preferred since this
>>>>>>>     issue cannot occur without extension languages. (gdb scripts 
>>>>>>> always normal
>>>>>>>     stop before continuing execution)
>>>>>>>
>>>>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>>>>>> ---
>>>>>>>   gdb/breakpoint.c                           |  2 +
>>>>>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>>>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>>>>>> ++++++++++++++++++++++
>>>>>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>>>>>   4 files changed, 107 insertions(+)
>>>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>>>
>>>>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>>>>> index bff3bac7d1a..15f4ae2131c 100644
>>>>>>> --- a/gdb/breakpoint.c
>>>>>>> +++ b/gdb/breakpoint.c
>>>>>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>>>>>> *bs, thread_info *thread)
>>>>>>>     /* Evaluate extension language breakpoints that have a 
>>>>>>> "stop" method
>>>>>>>        implemented.  */
>>>>>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>>>>>> +  if (b->disposition == disp_del_at_next_stop)
>>>>>>> +    disable_breakpoint(b);
>>>>>>>       if (is_watchpoint (b))
>>>>>>>       {
>>>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c 
>>>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>>> new file mode 100644
>>>>>>> index 00000000000..4e1760bb05b
>>>>>>> --- /dev/null
>>>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>>> @@ -0,0 +1,27 @@
>>>>>>> +/* This testcase is part of GDB, the GNU debugger.
>>>>>>> +
>>>>>>> +   Copyright 2022 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 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 
>>>>>>> <http://www.gnu.org/licenses/>.  */
>>>>>>> +
>>>>>>> +#include <stdio.h>
>>>>>>> +
>>>>>>> +int
>>>>>>> +main (void)
>>>>>>> +{
>>>>>>> +  int i;
>>>>>>> +  for (i = 0; i < 3; i++)
>>>>>>> +    printf ("%d", i);
>>>>>>> +  return 0;
>>>>>>> +}
>>>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp 
>>>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>>> new file mode 100644
>>>>>>> index 00000000000..ac58d75c523
>>>>>>> --- /dev/null
>>>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>>> @@ -0,0 +1,48 @@
>>>>>>> +# Copyright (C) 2022 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 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 
>>>>>>> <http://www.gnu.org/licenses/>.
>>>>>>> +
>>>>>>> +# Check that Watchpoints are deleted after use.
>>>>>>> +
>>>>>>> +load_lib gdb-python.exp
>>>>>>> +
>>>>>>> +standard_testfile
>>>>>>> +
>>>>>>> +if {[prepare_for_testing "failed to prepare" $testfile 
>>>>>>> $srcfile]} {
>>>>>>> +    return -1
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Skip all tests if Python scripting is not enabled.
>>>>>>> +if { [skip_python_tests] } { continue }
>>>>>>> +
>>>>>>> +if ![runto_main] then {
>>>>>>> +    return 0
>>>>>>> +}
>>>>>>> +
>>>>>>> +# For remote host testing
>>>>>>> +set pyfile [gdb_remote_download host 
>>>>>>> ${srcdir}/${subdir}/${testfile}.py]
>>>>>>> +
>>>>>>> +gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use 
>>>>>>> hardware watchpoints"
>>>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check 
>>>>>>> default BP count"
>>>>>>> +gdb_test "source $pyfile" ".*Python script imported.*" \
>>>>>>> +    "import python scripts"
>>>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "2" "check 
>>>>>>> modified BP count"
>>>>>>> +gdb_test_sequence "continue" "run until program stops" {
>>>>>>> +    "Watchpoint Hit: ."
>>>>>>> +    "\[\r\n\]+Watchpoint . deleted because the program has left 
>>>>>>> the block in"
>>>>>>> +    "\[\r\n\]+which its expression is valid\."
>>>>>>> +    "Watchpoint Hit: ."
>>>>>>> +    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited 
>>>>>>> normally\\]"
>>>>>>> +}
>>>>>>> +gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP 
>>>>>>> count"
>>>>>>> diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py 
>>>>>>> b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>>> new file mode 100644
>>>>>>> index 00000000000..ce5dee118ad
>>>>>>> --- /dev/null
>>>>>>> +++ b/gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Copyright (C) 2022 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 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 
>>>>>>> <http://www.gnu.org/licenses/>.
>>>>>>> +
>>>>>>> +
>>>>>>> +class MyBreakpoint(gdb.Breakpoint):
>>>>>>> +    def __init__(self, *args, **kwargs):
>>>>>>> +        gdb.Breakpoint.__init__(self, *args, **kwargs)
>>>>>>> +        self.i = 0
>>>>>>> +
>>>>>>> +    def stop(self):
>>>>>>> +        self.i += 1
>>>>>>> +        print("Watchpoint Hit:", self.i)
>>>>>>> +        return False
>>>>>>> +
>>>>>>> +
>>>>>>> +MyBreakpoint("i", gdb.BP_WATCHPOINT)
>>>>>>> +
>>>>>>> +print("Python script imported")
>>>>>>
>
  
Simon Marchi Jan. 13, 2023, 3:40 p.m. UTC | #8
On 10/20/22 14:11, Johnson Sun via Gdb-patches wrote:
> Currently, when a local software watchpoint goes out of scope, GDB sets the
> watchpoint's disposition to `delete at next stop' and then normal stops
> (i.e., stop and wait for the next GDB command). When GDB normal stops, it
> automatically deletes the breakpoints with their disposition set to
> `delete at next stop'.
> 
> Suppose a Python script decides not to normal stop when a local software
> watchpoint goes out of scope, the watchpoint will not be automatically
> deleted even when its disposition is set to `delete at next stop'.
> 
> Since GDB single-steps the program and tests the watched expression after each
> instruction, not deleting the watchpoint causes the watchpoint to be hit many
> more times than it should, as reported in PR python/29603.
> 
> This was happening because the watchpoint is not deleted or disabled when
> going out of scope.
> 
> This commit fixes this issue by disabling the watchpoint when going out of
> scope. It also adds a test to ensure this feature isn't regressed in the
> future.
> 
> Two other solutions seem to solve this issue, but are in fact inappropriate:
> 1. Automatically delete breakpoints on all kinds of stops
>    (in `fetch_inferior_event'). This solution is very slow since the deletion
>    requires O(N) time for N breakpoints.
> 2. Disable the watchpoint after the watchpoint's disposition is set to
>    `delete at next stop' (in `watchpoint_del_at_next_stop'). This solution
>    modifies a non-extension-related code path, and isn't preferred since this
>    issue cannot occur without extension languages. (gdb scripts always normal
>    stop before continuing execution)

I have a bit of trouble reviewing this, because I'm not too familiar
with the lifecycle of watchpoints (I know the principle, but not the
specifically where things happen in GDB).  So it's difficult to tell
whether this is right or if there's a better way to handle it.

However, the supplied test does not pass for me:

    source /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.python/py-watchpoint/py-watchpoint.py
    Watchpoint 2: i
    Python script imported
    (gdb) PASS: gdb.python/py-watchpoint.exp: import python scripts
    python print(len(gdb.breakpoints()))
    2
    (gdb) PASS: gdb.python/py-watchpoint.exp: check modified BP count
    gdb_expect_list pattern: /Watchpoint Hit: ./
    continue
    Continuing.
    Watchpoint Hit: 1
    gdb_expect_list pattern: /[
    ]+Watchpoint . deleted because the program has left the block in/
    FAIL: gdb.python/py-watchpoint.exp: run until program stops (pattern 2) (timeout)
    gdb_expect_list pattern: /[
    ]+which its expression is valid./
    gdb_expect_list pattern: /Watchpoint Hit: ./
    gdb_expect_list pattern: /[
    ]+012\[Inferior 1 \(process .*\) exited normally\]/
    gdb_expect_list pattern: //
    python print(len(gdb.breakpoints()))
    Watchpoint Hit: 2
    Watchpoint Hit: 3
    Watchpoint Hit: 4

    Watchpoint 2 deleted because the program has left the block in
    which its expression is valid.
    Watchpoint Hit: 5
    012[Inferior 1 (process 2381681) exited normally]
    (gdb) FAIL: gdb.python/py-watchpoint.exp: check BP count (the program exited)

> 
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
> ---
>  gdb/breakpoint.c                           |  2 +
>  gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>  gdb/testsuite/gdb.python/py-watchpoint.exp | 48 ++++++++++++++++++++++
>  gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>  4 files changed, 107 insertions(+)
>  create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>  create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
> 
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index bff3bac7d1a..15f4ae2131c 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
>    /* Evaluate extension language breakpoints that have a "stop" method
>       implemented.  */
>    bs->stop = breakpoint_ext_lang_cond_says_stop (b);
> +  if (b->disposition == disp_del_at_next_stop)
> +    disable_breakpoint(b);

Is there a reason to do it here in particular, and not, let's say as
soon as we change the disposition to disp_del_at_next_stop?

Other question: shouldn't marking the watchpoint as
disp_del_at_next_stop make it so it won't be inserted next time we
resume?  After all should_be_inserted returns false for breakpoint
locations that are disp_del_at_next_stop.  Perhaps it's because since we
don't do a "normal" stop, breakpoint locations stay inserted, and
there's nothing that pulls this location out of the target?  Therefore,
maybe a solution, to keep things coherent, would be to pull the
breakpoint locations out when marking the breakpoint as
disp_del_at_next_stop?  This way, we would respect the invariant that a
breakpoint disp_del_at_next_stop can't be inserted (I don't know if this
is really what is happening though, it's just speculation).

And, in a broader scope, why wait until the next normal stop to delete
the watchpoint?  A "normal" stop being a stop that we present to the
user (the normal_stop function).  We currently call
breakpoint_auto_delete in normal_stop, which is why we don't reach it
when your Breakpoint.stop method returns False.  At the end of, let's
say, handle_signal_stop, could we go over the bpstat chain and delete
any breakpoint we've just hit that is marked for deletion?

Simon
  
Johnson Sun Jan. 23, 2023, 10:15 a.m. UTC | #9
Hi,

Thanks for reviewing this!

 > the supplied test does not pass for me

The current test doesn't seem to produce consistent results across different
machines, I'll try to fix it in the next version.

 > Is there a reason to do it here in particular, and not, let's say as
 > soon as we change the disposition to disp_del_at_next_stop?

I have implemented this in the v1 patch, I called `disable_breakpoint' as
soon as we change the disposition to `disp_del_at_next_stop'
(in `watchpoint_del_at_next_stop'). However,
LancelotSIX <lsix@lancelotsix.com> mentioned that the fix is in a
non-extension-related code path, and suggested disabling it in
`bpstat_check_breakpoint_conditions' instead (the v3 patch).
See: https://sourceware.org/pipermail/gdb-patches/2022-September/192120.html

Both the v1 and v3 patch fixes the issue. I personally prefer the v1 patch.
(i.e., modifying `watchpoint_del_at_next_stop')

 > shouldn't marking the watchpoint as
 > disp_del_at_next_stop make it so it won't be inserted next time we
 > resume?  After all should_be_inserted returns false for breakpoint
 > locations that are disp_del_at_next_stop.  Perhaps it's because since we
 > don't do a "normal" stop, breakpoint locations stay inserted, and
 > there's nothing that pulls this location out of the target? Therefore,
 > maybe a solution, to keep things coherent, would be to pull the
 > breakpoint locations out when marking the breakpoint as
 > disp_del_at_next_stop?  This way, we would respect the invariant that a
 > breakpoint disp_del_at_next_stop can't be inserted (I don't know if this
 > is really what is happening though, it's just speculation).

For software watchpoints, they cannot be pulled out of the target, since
they may not be inserted into the target in the first place:

      /* Locations of type bp_loc_other and
     bp_loc_software_watchpoint are only maintained at GDB side,
     so there is no need to remove them.  */

     -- gdb/breakpoint.c:3840

Their expressions are re-evaluated by single-stepping through the program:

     Software watchpoints are very slow, since GDB needs to single-step
     the program being debugged and test the value of the watched
     expression(s) after each instruction.

     -- https://sourceware.org/gdb/wiki/Internals%20Watchpoints

Therefore, we must either disable or delete the software watchpoint.
We cannot pull it out of the target since it's only maintained on the
GDB side.

 > And, in a broader scope, why wait until the next normal stop to delete
 > the watchpoint?  A "normal" stop being a stop that we present to the
 > user (the normal_stop function).  We currently call
 > breakpoint_auto_delete in normal_stop, which is why we don't reach it
 > when your Breakpoint.stop method returns False.  At the end of, let's
 > say, handle_signal_stop, could we go over the bpstat chain and delete
 > any breakpoint we've just hit that is marked for deletion?

I believe this choice is due to performance issues.

In an early attempt, I tried to call `breakpoint_auto_delete' on all kinds
of stops. However, this implementation is very slow when we have a large
number of breakpoints, since we need to go over the entire bpstat chain on
all stops. I recall that this implementation fails on certain testcases with
a large number of breakpoints with many breakpoint stops.

Furthermore, the reason for using the `disp_del_at_next_stop' state remains
unclear, as mentioned in the comments:

     /* NOTE drow/2003-09-08: This state only exists for removing
        watchpoints.  It's not clear that it's necessary...  */

     -- gdb/breakpoint.c:2914

By tracing the git history, the `disp_del_at_next_stop' state is introduced
in commit c906108c21474dfb4ed285bcc0ac6fe02cd400cc, which doesn't provide
any proper explanation of this state.

I think the best way to deal with this is to avoid going over the entire
bpstat chain when deleting breakpoints. Potentially by keeping track of
a chain of breakpoints that should be deleted, and changing the bpstat chain
to a doubly linked list for the ease of deletion. Such changes deserve a
dedicated patch, though.

To sum up, I prefer modifying `watchpoint_del_at_next_stop' (i.e., the 
v1 patch).
If you also think it's appropriate, I'll fix the failing test and 
prepare the
v4 patch accordingly.

Johnson

On 1/13/2023 11:40 PM, SimonMarchi wrote:
>
> On 10/20/22 14:11, Johnson Sun via Gdb-patches wrote:
>> Currently, when a local software watchpoint goes out of scope, GDB sets the
>> watchpoint's disposition to `delete at next stop' and then normal stops
>> (i.e., stop and wait for the next GDB command). When GDB normal stops, it
>> automatically deletes the breakpoints with their disposition set to
>> `delete at next stop'.
>>
>> Suppose a Python script decides not to normal stop when a local software
>> watchpoint goes out of scope, the watchpoint will not be automatically
>> deleted even when its disposition is set to `delete at next stop'.
>>
>> Since GDB single-steps the program and tests the watched expression after each
>> instruction, not deleting the watchpoint causes the watchpoint to be hit many
>> more times than it should, as reported in PR python/29603.
>>
>> This was happening because the watchpoint is not deleted or disabled when
>> going out of scope.
>>
>> This commit fixes this issue by disabling the watchpoint when going out of
>> scope. It also adds a test to ensure this feature isn't regressed in the
>> future.
>>
>> Two other solutions seem to solve this issue, but are in fact inappropriate:
>> 1. Automatically delete breakpoints on all kinds of stops
>>     (in `fetch_inferior_event'). This solution is very slow since the deletion
>>     requires O(N) time for N breakpoints.
>> 2. Disable the watchpoint after the watchpoint's disposition is set to
>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This solution
>>     modifies a non-extension-related code path, and isn't preferred since this
>>     issue cannot occur without extension languages. (gdb scripts always normal
>>     stop before continuing execution)
> I have a bit of trouble reviewing this, because I'm not too familiar
> with the lifecycle of watchpoints (I know the principle, but not the
> specifically where things happen in GDB).  So it's difficult to tell
> whether this is right or if there's a better way to handle it.
>
> However, the supplied test does not pass for me:
>
>      source /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.python/py-watchpoint/py-watchpoint.py
>      Watchpoint 2: i
>      Python script imported
>      (gdb) PASS: gdb.python/py-watchpoint.exp: import python scripts
>      python print(len(gdb.breakpoints()))
>      2
>      (gdb) PASS: gdb.python/py-watchpoint.exp: check modified BP count
>      gdb_expect_list pattern: /Watchpoint Hit: ./
>      continue
>      Continuing.
>      Watchpoint Hit: 1
>      gdb_expect_list pattern: /[
>      ]+Watchpoint . deleted because the program has left the block in/
>      FAIL: gdb.python/py-watchpoint.exp: run until program stops (pattern 2) (timeout)
>      gdb_expect_list pattern: /[
>      ]+which its expression is valid./
>      gdb_expect_list pattern: /Watchpoint Hit: ./
>      gdb_expect_list pattern: /[
>      ]+012\[Inferior 1 \(process .*\) exited normally\]/
>      gdb_expect_list pattern: //
>      python print(len(gdb.breakpoints()))
>      Watchpoint Hit: 2
>      Watchpoint Hit: 3
>      Watchpoint Hit: 4
>
>      Watchpoint 2 deleted because the program has left the block in
>      which its expression is valid.
>      Watchpoint Hit: 5
>      012[Inferior 1 (process 2381681) exited normally]
>      (gdb) FAIL: gdb.python/py-watchpoint.exp: check BP count (the program exited)
>
>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>> ---
>>   gdb/breakpoint.c                           |  2 +
>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 ++++++++++++++++++++++
>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>   4 files changed, 107 insertions(+)
>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index bff3bac7d1a..15f4ae2131c 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
>>     /* Evaluate extension language breakpoints that have a "stop" method
>>        implemented.  */
>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>> +  if (b->disposition == disp_del_at_next_stop)
>> +    disable_breakpoint(b);
> Is there a reason to do it here in particular, and not, let's say as
> soon as we change the disposition to disp_del_at_next_stop?
>
> Other question: shouldn't marking the watchpoint as
> disp_del_at_next_stop make it so it won't be inserted next time we
> resume?  After all should_be_inserted returns false for breakpoint
> locations that are disp_del_at_next_stop.  Perhaps it's because since we
> don't do a "normal" stop, breakpoint locations stay inserted, and
> there's nothing that pulls this location out of the target?  Therefore,
> maybe a solution, to keep things coherent, would be to pull the
> breakpoint locations out when marking the breakpoint as
> disp_del_at_next_stop?  This way, we would respect the invariant that a
> breakpoint disp_del_at_next_stop can't be inserted (I don't know if this
> is really what is happening though, it's just speculation).
>
> And, in a broader scope, why wait until the next normal stop to delete
> the watchpoint?  A "normal" stop being a stop that we present to the
> user (the normal_stop function).  We currently call
> breakpoint_auto_delete in normal_stop, which is why we don't reach it
> when your Breakpoint.stop method returns False.  At the end of, let's
> say, handle_signal_stop, could we go over the bpstat chain and delete
> any breakpoint we've just hit that is marked for deletion?
>
> Simon
>
  
Johnson Sun Feb. 18, 2023, 4:26 p.m. UTC | #10
Ping for: 
<https://sourceware.org/pipermail/gdb-patches/2023-January/196068.html>.

I would like to ask for some feedback before submitting the v4 patch.

Johnson

On 1/23/2023 6:15 PM, Johnson Sun wrote:
> Hi,
>
> Thanks for reviewing this!
>
> > the supplied test does not pass for me
>
> The current test doesn't seem to produce consistent results across 
> different
> machines, I'll try to fix it in the next version.
>
> > Is there a reason to do it here in particular, and not, let's say as
> > soon as we change the disposition to disp_del_at_next_stop?
>
> I have implemented this in the v1 patch, I called `disable_breakpoint' as
> soon as we change the disposition to `disp_del_at_next_stop'
> (in `watchpoint_del_at_next_stop'). However,
> LancelotSIX <lsix@lancelotsix.com> mentioned that the fix is in a
> non-extension-related code path, and suggested disabling it in
> `bpstat_check_breakpoint_conditions' instead (the v3 patch).
> See: 
> https://sourceware.org/pipermail/gdb-patches/2022-September/192120.html
>
> Both the v1 and v3 patch fixes the issue. I personally prefer the v1 
> patch.
> (i.e., modifying `watchpoint_del_at_next_stop')
>
> > shouldn't marking the watchpoint as
> > disp_del_at_next_stop make it so it won't be inserted next time we
> > resume?  After all should_be_inserted returns false for breakpoint
> > locations that are disp_del_at_next_stop.  Perhaps it's because 
> since we
> > don't do a "normal" stop, breakpoint locations stay inserted, and
> > there's nothing that pulls this location out of the target? Therefore,
> > maybe a solution, to keep things coherent, would be to pull the
> > breakpoint locations out when marking the breakpoint as
> > disp_del_at_next_stop?  This way, we would respect the invariant that a
> > breakpoint disp_del_at_next_stop can't be inserted (I don't know if 
> this
> > is really what is happening though, it's just speculation).
>
> For software watchpoints, they cannot be pulled out of the target, since
> they may not be inserted into the target in the first place:
>
>      /* Locations of type bp_loc_other and
>     bp_loc_software_watchpoint are only maintained at GDB side,
>     so there is no need to remove them.  */
>
>     -- gdb/breakpoint.c:3840
>
> Their expressions are re-evaluated by single-stepping through the 
> program:
>
>     Software watchpoints are very slow, since GDB needs to single-step
>     the program being debugged and test the value of the watched
>     expression(s) after each instruction.
>
>     -- https://sourceware.org/gdb/wiki/Internals%20Watchpoints
>
> Therefore, we must either disable or delete the software watchpoint.
> We cannot pull it out of the target since it's only maintained on the
> GDB side.
>
> > And, in a broader scope, why wait until the next normal stop to delete
> > the watchpoint?  A "normal" stop being a stop that we present to the
> > user (the normal_stop function).  We currently call
> > breakpoint_auto_delete in normal_stop, which is why we don't reach it
> > when your Breakpoint.stop method returns False.  At the end of, let's
> > say, handle_signal_stop, could we go over the bpstat chain and delete
> > any breakpoint we've just hit that is marked for deletion?
>
> I believe this choice is due to performance issues.
>
> In an early attempt, I tried to call `breakpoint_auto_delete' on all 
> kinds
> of stops. However, this implementation is very slow when we have a large
> number of breakpoints, since we need to go over the entire bpstat 
> chain on
> all stops. I recall that this implementation fails on certain 
> testcases with
> a large number of breakpoints with many breakpoint stops.
>
> Furthermore, the reason for using the `disp_del_at_next_stop' state 
> remains
> unclear, as mentioned in the comments:
>
>     /* NOTE drow/2003-09-08: This state only exists for removing
>        watchpoints.  It's not clear that it's necessary...  */
>
>     -- gdb/breakpoint.c:2914
>
> By tracing the git history, the `disp_del_at_next_stop' state is 
> introduced
> in commit c906108c21474dfb4ed285bcc0ac6fe02cd400cc, which doesn't provide
> any proper explanation of this state.
>
> I think the best way to deal with this is to avoid going over the entire
> bpstat chain when deleting breakpoints. Potentially by keeping track of
> a chain of breakpoints that should be deleted, and changing the bpstat 
> chain
> to a doubly linked list for the ease of deletion. Such changes deserve a
> dedicated patch, though.
>
> To sum up, I prefer modifying `watchpoint_del_at_next_stop' (i.e., the 
> v1 patch).
> If you also think it's appropriate, I'll fix the failing test and 
> prepare the
> v4 patch accordingly.
>
> Johnson
>
> On 1/13/2023 11:40 PM, SimonMarchi wrote:
>>
>> On 10/20/22 14:11, Johnson Sun via Gdb-patches wrote:
>>> Currently, when a local software watchpoint goes out of scope, GDB 
>>> sets the
>>> watchpoint's disposition to `delete at next stop' and then normal stops
>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>> stops, it
>>> automatically deletes the breakpoints with their disposition set to
>>> `delete at next stop'.
>>>
>>> Suppose a Python script decides not to normal stop when a local 
>>> software
>>> watchpoint goes out of scope, the watchpoint will not be automatically
>>> deleted even when its disposition is set to `delete at next stop'.
>>>
>>> Since GDB single-steps the program and tests the watched expression 
>>> after each
>>> instruction, not deleting the watchpoint causes the watchpoint to be 
>>> hit many
>>> more times than it should, as reported in PR python/29603.
>>>
>>> This was happening because the watchpoint is not deleted or disabled 
>>> when
>>> going out of scope.
>>>
>>> This commit fixes this issue by disabling the watchpoint when going 
>>> out of
>>> scope. It also adds a test to ensure this feature isn't regressed in 
>>> the
>>> future.
>>>
>>> Two other solutions seem to solve this issue, but are in fact 
>>> inappropriate:
>>> 1. Automatically delete breakpoints on all kinds of stops
>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>> the deletion
>>>     requires O(N) time for N breakpoints.
>>> 2. Disable the watchpoint after the watchpoint's disposition is set to
>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>>> solution
>>>     modifies a non-extension-related code path, and isn't preferred 
>>> since this
>>>     issue cannot occur without extension languages. (gdb scripts 
>>> always normal
>>>     stop before continuing execution)
>> I have a bit of trouble reviewing this, because I'm not too familiar
>> with the lifecycle of watchpoints (I know the principle, but not the
>> specifically where things happen in GDB).  So it's difficult to tell
>> whether this is right or if there's a better way to handle it.
>>
>> However, the supplied test does not pass for me:
>>
>>      source 
>> /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.python/py-watchpoint/py-watchpoint.py
>>      Watchpoint 2: i
>>      Python script imported
>>      (gdb) PASS: gdb.python/py-watchpoint.exp: import python scripts
>>      python print(len(gdb.breakpoints()))
>>      2
>>      (gdb) PASS: gdb.python/py-watchpoint.exp: check modified BP count
>>      gdb_expect_list pattern: /Watchpoint Hit: ./
>>      continue
>>      Continuing.
>>      Watchpoint Hit: 1
>>      gdb_expect_list pattern: /[
>>      ]+Watchpoint . deleted because the program has left the block in/
>>      FAIL: gdb.python/py-watchpoint.exp: run until program stops 
>> (pattern 2) (timeout)
>>      gdb_expect_list pattern: /[
>>      ]+which its expression is valid./
>>      gdb_expect_list pattern: /Watchpoint Hit: ./
>>      gdb_expect_list pattern: /[
>>      ]+012\[Inferior 1 \(process .*\) exited normally\]/
>>      gdb_expect_list pattern: //
>>      python print(len(gdb.breakpoints()))
>>      Watchpoint Hit: 2
>>      Watchpoint Hit: 3
>>      Watchpoint Hit: 4
>>
>>      Watchpoint 2 deleted because the program has left the block in
>>      which its expression is valid.
>>      Watchpoint Hit: 5
>>      012[Inferior 1 (process 2381681) exited normally]
>>      (gdb) FAIL: gdb.python/py-watchpoint.exp: check BP count (the 
>> program exited)
>>
>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>> ---
>>>   gdb/breakpoint.c                           |  2 +
>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>> ++++++++++++++++++++++
>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>   4 files changed, 107 insertions(+)
>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>
>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>> index bff3bac7d1a..15f4ae2131c 100644
>>> --- a/gdb/breakpoint.c
>>> +++ b/gdb/breakpoint.c
>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>> *bs, thread_info *thread)
>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>> method
>>>        implemented.  */
>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>> +  if (b->disposition == disp_del_at_next_stop)
>>> +    disable_breakpoint(b);
>> Is there a reason to do it here in particular, and not, let's say as
>> soon as we change the disposition to disp_del_at_next_stop?
>>
>> Other question: shouldn't marking the watchpoint as
>> disp_del_at_next_stop make it so it won't be inserted next time we
>> resume?  After all should_be_inserted returns false for breakpoint
>> locations that are disp_del_at_next_stop.  Perhaps it's because since we
>> don't do a "normal" stop, breakpoint locations stay inserted, and
>> there's nothing that pulls this location out of the target? Therefore,
>> maybe a solution, to keep things coherent, would be to pull the
>> breakpoint locations out when marking the breakpoint as
>> disp_del_at_next_stop?  This way, we would respect the invariant that a
>> breakpoint disp_del_at_next_stop can't be inserted (I don't know if this
>> is really what is happening though, it's just speculation).
>>
>> And, in a broader scope, why wait until the next normal stop to delete
>> the watchpoint?  A "normal" stop being a stop that we present to the
>> user (the normal_stop function).  We currently call
>> breakpoint_auto_delete in normal_stop, which is why we don't reach it
>> when your Breakpoint.stop method returns False.  At the end of, let's
>> say, handle_signal_stop, could we go over the bpstat chain and delete
>> any breakpoint we've just hit that is marked for deletion?
>>
>> Simon
>>
  
Johnson Sun Feb. 26, 2023, 6:16 a.m. UTC | #11
Request for comments: 
<https://sourceware.org/pipermail/gdb-patches/2023-January/196068.html>.

Johnson

On 2/19/2023 12:26 AM, JohnsonSun wrote:
> Ping for: 
> <https://sourceware.org/pipermail/gdb-patches/2023-January/196068.html>.
>
> I would like to ask for some feedback before submitting the v4 patch.
>
> Johnson
>
> On 1/23/2023 6:15 PM, Johnson Sun wrote:
>> Hi,
>>
>> Thanks for reviewing this!
>>
>> > the supplied test does not pass for me
>>
>> The current test doesn't seem to produce consistent results across 
>> different
>> machines, I'll try to fix it in the next version.
>>
>> > Is there a reason to do it here in particular, and not, let's say as
>> > soon as we change the disposition to disp_del_at_next_stop?
>>
>> I have implemented this in the v1 patch, I called 
>> `disable_breakpoint' as
>> soon as we change the disposition to `disp_del_at_next_stop'
>> (in `watchpoint_del_at_next_stop'). However,
>> LancelotSIX <lsix@lancelotsix.com> mentioned that the fix is in a
>> non-extension-related code path, and suggested disabling it in
>> `bpstat_check_breakpoint_conditions' instead (the v3 patch).
>> See: 
>> https://sourceware.org/pipermail/gdb-patches/2022-September/192120.html
>>
>> Both the v1 and v3 patch fixes the issue. I personally prefer the v1 
>> patch.
>> (i.e., modifying `watchpoint_del_at_next_stop')
>>
>> > shouldn't marking the watchpoint as
>> > disp_del_at_next_stop make it so it won't be inserted next time we
>> > resume?  After all should_be_inserted returns false for breakpoint
>> > locations that are disp_del_at_next_stop.  Perhaps it's because 
>> since we
>> > don't do a "normal" stop, breakpoint locations stay inserted, and
>> > there's nothing that pulls this location out of the target? Therefore,
>> > maybe a solution, to keep things coherent, would be to pull the
>> > breakpoint locations out when marking the breakpoint as
>> > disp_del_at_next_stop?  This way, we would respect the invariant 
>> that a
>> > breakpoint disp_del_at_next_stop can't be inserted (I don't know if 
>> this
>> > is really what is happening though, it's just speculation).
>>
>> For software watchpoints, they cannot be pulled out of the target, since
>> they may not be inserted into the target in the first place:
>>
>>      /* Locations of type bp_loc_other and
>>     bp_loc_software_watchpoint are only maintained at GDB side,
>>     so there is no need to remove them.  */
>>
>>     -- gdb/breakpoint.c:3840
>>
>> Their expressions are re-evaluated by single-stepping through the 
>> program:
>>
>>     Software watchpoints are very slow, since GDB needs to single-step
>>     the program being debugged and test the value of the watched
>>     expression(s) after each instruction.
>>
>>     -- https://sourceware.org/gdb/wiki/Internals%20Watchpoints
>>
>> Therefore, we must either disable or delete the software watchpoint.
>> We cannot pull it out of the target since it's only maintained on the
>> GDB side.
>>
>> > And, in a broader scope, why wait until the next normal stop to delete
>> > the watchpoint?  A "normal" stop being a stop that we present to the
>> > user (the normal_stop function).  We currently call
>> > breakpoint_auto_delete in normal_stop, which is why we don't reach it
>> > when your Breakpoint.stop method returns False.  At the end of, let's
>> > say, handle_signal_stop, could we go over the bpstat chain and delete
>> > any breakpoint we've just hit that is marked for deletion?
>>
>> I believe this choice is due to performance issues.
>>
>> In an early attempt, I tried to call `breakpoint_auto_delete' on all 
>> kinds
>> of stops. However, this implementation is very slow when we have a large
>> number of breakpoints, since we need to go over the entire bpstat 
>> chain on
>> all stops. I recall that this implementation fails on certain 
>> testcases with
>> a large number of breakpoints with many breakpoint stops.
>>
>> Furthermore, the reason for using the `disp_del_at_next_stop' state 
>> remains
>> unclear, as mentioned in the comments:
>>
>>     /* NOTE drow/2003-09-08: This state only exists for removing
>>        watchpoints.  It's not clear that it's necessary...  */
>>
>>     -- gdb/breakpoint.c:2914
>>
>> By tracing the git history, the `disp_del_at_next_stop' state is 
>> introduced
>> in commit c906108c21474dfb4ed285bcc0ac6fe02cd400cc, which doesn't 
>> provide
>> any proper explanation of this state.
>>
>> I think the best way to deal with this is to avoid going over the entire
>> bpstat chain when deleting breakpoints. Potentially by keeping track of
>> a chain of breakpoints that should be deleted, and changing the 
>> bpstat chain
>> to a doubly linked list for the ease of deletion. Such changes deserve a
>> dedicated patch, though.
>>
>> To sum up, I prefer modifying `watchpoint_del_at_next_stop' (i.e., 
>> the v1 patch).
>> If you also think it's appropriate, I'll fix the failing test and 
>> prepare the
>> v4 patch accordingly.
>>
>> Johnson
>>
>> On 1/13/2023 11:40 PM, SimonMarchi wrote:
>>>
>>> On 10/20/22 14:11, Johnson Sun via Gdb-patches wrote:
>>>> Currently, when a local software watchpoint goes out of scope, GDB 
>>>> sets the
>>>> watchpoint's disposition to `delete at next stop' and then normal 
>>>> stops
>>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>>> stops, it
>>>> automatically deletes the breakpoints with their disposition set to
>>>> `delete at next stop'.
>>>>
>>>> Suppose a Python script decides not to normal stop when a local 
>>>> software
>>>> watchpoint goes out of scope, the watchpoint will not be automatically
>>>> deleted even when its disposition is set to `delete at next stop'.
>>>>
>>>> Since GDB single-steps the program and tests the watched expression 
>>>> after each
>>>> instruction, not deleting the watchpoint causes the watchpoint to 
>>>> be hit many
>>>> more times than it should, as reported in PR python/29603.
>>>>
>>>> This was happening because the watchpoint is not deleted or 
>>>> disabled when
>>>> going out of scope.
>>>>
>>>> This commit fixes this issue by disabling the watchpoint when going 
>>>> out of
>>>> scope. It also adds a test to ensure this feature isn't regressed 
>>>> in the
>>>> future.
>>>>
>>>> Two other solutions seem to solve this issue, but are in fact 
>>>> inappropriate:
>>>> 1. Automatically delete breakpoints on all kinds of stops
>>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>>> the deletion
>>>>     requires O(N) time for N breakpoints.
>>>> 2. Disable the watchpoint after the watchpoint's disposition is set to
>>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>>>> solution
>>>>     modifies a non-extension-related code path, and isn't preferred 
>>>> since this
>>>>     issue cannot occur without extension languages. (gdb scripts 
>>>> always normal
>>>>     stop before continuing execution)
>>> I have a bit of trouble reviewing this, because I'm not too familiar
>>> with the lifecycle of watchpoints (I know the principle, but not the
>>> specifically where things happen in GDB).  So it's difficult to tell
>>> whether this is right or if there's a better way to handle it.
>>>
>>> However, the supplied test does not pass for me:
>>>
>>>      source 
>>> /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.python/py-watchpoint/py-watchpoint.py
>>>      Watchpoint 2: i
>>>      Python script imported
>>>      (gdb) PASS: gdb.python/py-watchpoint.exp: import python scripts
>>>      python print(len(gdb.breakpoints()))
>>>      2
>>>      (gdb) PASS: gdb.python/py-watchpoint.exp: check modified BP count
>>>      gdb_expect_list pattern: /Watchpoint Hit: ./
>>>      continue
>>>      Continuing.
>>>      Watchpoint Hit: 1
>>>      gdb_expect_list pattern: /[
>>>      ]+Watchpoint . deleted because the program has left the block in/
>>>      FAIL: gdb.python/py-watchpoint.exp: run until program stops 
>>> (pattern 2) (timeout)
>>>      gdb_expect_list pattern: /[
>>>      ]+which its expression is valid./
>>>      gdb_expect_list pattern: /Watchpoint Hit: ./
>>>      gdb_expect_list pattern: /[
>>>      ]+012\[Inferior 1 \(process .*\) exited normally\]/
>>>      gdb_expect_list pattern: //
>>>      python print(len(gdb.breakpoints()))
>>>      Watchpoint Hit: 2
>>>      Watchpoint Hit: 3
>>>      Watchpoint Hit: 4
>>>
>>>      Watchpoint 2 deleted because the program has left the block in
>>>      which its expression is valid.
>>>      Watchpoint Hit: 5
>>>      012[Inferior 1 (process 2381681) exited normally]
>>>      (gdb) FAIL: gdb.python/py-watchpoint.exp: check BP count (the 
>>> program exited)
>>>
>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>>> ---
>>>>   gdb/breakpoint.c                           |  2 +
>>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>>> ++++++++++++++++++++++
>>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>>   4 files changed, 107 insertions(+)
>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>>
>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>> index bff3bac7d1a..15f4ae2131c 100644
>>>> --- a/gdb/breakpoint.c
>>>> +++ b/gdb/breakpoint.c
>>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>>> *bs, thread_info *thread)
>>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>>> method
>>>>        implemented.  */
>>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>>> +  if (b->disposition == disp_del_at_next_stop)
>>>> +    disable_breakpoint(b);
>>> Is there a reason to do it here in particular, and not, let's say as
>>> soon as we change the disposition to disp_del_at_next_stop?
>>>
>>> Other question: shouldn't marking the watchpoint as
>>> disp_del_at_next_stop make it so it won't be inserted next time we
>>> resume?  After all should_be_inserted returns false for breakpoint
>>> locations that are disp_del_at_next_stop.  Perhaps it's because 
>>> since we
>>> don't do a "normal" stop, breakpoint locations stay inserted, and
>>> there's nothing that pulls this location out of the target? Therefore,
>>> maybe a solution, to keep things coherent, would be to pull the
>>> breakpoint locations out when marking the breakpoint as
>>> disp_del_at_next_stop?  This way, we would respect the invariant that a
>>> breakpoint disp_del_at_next_stop can't be inserted (I don't know if 
>>> this
>>> is really what is happening though, it's just speculation).
>>>
>>> And, in a broader scope, why wait until the next normal stop to delete
>>> the watchpoint?  A "normal" stop being a stop that we present to the
>>> user (the normal_stop function).  We currently call
>>> breakpoint_auto_delete in normal_stop, which is why we don't reach it
>>> when your Breakpoint.stop method returns False.  At the end of, let's
>>> say, handle_signal_stop, could we go over the bpstat chain and delete
>>> any breakpoint we've just hit that is marked for deletion?
>>>
>>> Simon
>>>
>
  
Johnson Sun March 12, 2023, 5:24 p.m. UTC | #12
Ping for comments: 
<https://sourceware.org/pipermail/gdb-patches/2023-January/196068.html>.

Johnson

On 2/26/2023 2:16 PM, Johnson Sun wrote:
> Request for comments: 
> <https://sourceware.org/pipermail/gdb-patches/2023-January/196068.html>.
>
> Johnson
>
> On 2/19/2023 12:26 AM, JohnsonSun wrote:
>> Ping for: 
>> <https://sourceware.org/pipermail/gdb-patches/2023-January/196068.html>.
>>
>> I would like to ask for some feedback before submitting the v4 patch.
>>
>> Johnson
>>
>> On 1/23/2023 6:15 PM, Johnson Sun wrote:
>>> Hi,
>>>
>>> Thanks for reviewing this!
>>>
>>> > the supplied test does not pass for me
>>>
>>> The current test doesn't seem to produce consistent results across 
>>> different
>>> machines, I'll try to fix it in the next version.
>>>
>>> > Is there a reason to do it here in particular, and not, let's say as
>>> > soon as we change the disposition to disp_del_at_next_stop?
>>>
>>> I have implemented this in the v1 patch, I called 
>>> `disable_breakpoint' as
>>> soon as we change the disposition to `disp_del_at_next_stop'
>>> (in `watchpoint_del_at_next_stop'). However,
>>> LancelotSIX <lsix@lancelotsix.com> mentioned that the fix is in a
>>> non-extension-related code path, and suggested disabling it in
>>> `bpstat_check_breakpoint_conditions' instead (the v3 patch).
>>> See: 
>>> https://sourceware.org/pipermail/gdb-patches/2022-September/192120.html
>>>
>>> Both the v1 and v3 patch fixes the issue. I personally prefer the v1 
>>> patch.
>>> (i.e., modifying `watchpoint_del_at_next_stop')
>>>
>>> > shouldn't marking the watchpoint as
>>> > disp_del_at_next_stop make it so it won't be inserted next time we
>>> > resume?  After all should_be_inserted returns false for breakpoint
>>> > locations that are disp_del_at_next_stop.  Perhaps it's because 
>>> since we
>>> > don't do a "normal" stop, breakpoint locations stay inserted, and
>>> > there's nothing that pulls this location out of the target? 
>>> Therefore,
>>> > maybe a solution, to keep things coherent, would be to pull the
>>> > breakpoint locations out when marking the breakpoint as
>>> > disp_del_at_next_stop?  This way, we would respect the invariant 
>>> that a
>>> > breakpoint disp_del_at_next_stop can't be inserted (I don't know 
>>> if this
>>> > is really what is happening though, it's just speculation).
>>>
>>> For software watchpoints, they cannot be pulled out of the target, 
>>> since
>>> they may not be inserted into the target in the first place:
>>>
>>>      /* Locations of type bp_loc_other and
>>>     bp_loc_software_watchpoint are only maintained at GDB side,
>>>     so there is no need to remove them.  */
>>>
>>>     -- gdb/breakpoint.c:3840
>>>
>>> Their expressions are re-evaluated by single-stepping through the 
>>> program:
>>>
>>>     Software watchpoints are very slow, since GDB needs to single-step
>>>     the program being debugged and test the value of the watched
>>>     expression(s) after each instruction.
>>>
>>>     -- https://sourceware.org/gdb/wiki/Internals%20Watchpoints
>>>
>>> Therefore, we must either disable or delete the software watchpoint.
>>> We cannot pull it out of the target since it's only maintained on the
>>> GDB side.
>>>
>>> > And, in a broader scope, why wait until the next normal stop to 
>>> delete
>>> > the watchpoint?  A "normal" stop being a stop that we present to the
>>> > user (the normal_stop function).  We currently call
>>> > breakpoint_auto_delete in normal_stop, which is why we don't reach it
>>> > when your Breakpoint.stop method returns False.  At the end of, let's
>>> > say, handle_signal_stop, could we go over the bpstat chain and delete
>>> > any breakpoint we've just hit that is marked for deletion?
>>>
>>> I believe this choice is due to performance issues.
>>>
>>> In an early attempt, I tried to call `breakpoint_auto_delete' on all 
>>> kinds
>>> of stops. However, this implementation is very slow when we have a 
>>> large
>>> number of breakpoints, since we need to go over the entire bpstat 
>>> chain on
>>> all stops. I recall that this implementation fails on certain 
>>> testcases with
>>> a large number of breakpoints with many breakpoint stops.
>>>
>>> Furthermore, the reason for using the `disp_del_at_next_stop' state 
>>> remains
>>> unclear, as mentioned in the comments:
>>>
>>>     /* NOTE drow/2003-09-08: This state only exists for removing
>>>        watchpoints.  It's not clear that it's necessary...  */
>>>
>>>     -- gdb/breakpoint.c:2914
>>>
>>> By tracing the git history, the `disp_del_at_next_stop' state is 
>>> introduced
>>> in commit c906108c21474dfb4ed285bcc0ac6fe02cd400cc, which doesn't 
>>> provide
>>> any proper explanation of this state.
>>>
>>> I think the best way to deal with this is to avoid going over the 
>>> entire
>>> bpstat chain when deleting breakpoints. Potentially by keeping track of
>>> a chain of breakpoints that should be deleted, and changing the 
>>> bpstat chain
>>> to a doubly linked list for the ease of deletion. Such changes 
>>> deserve a
>>> dedicated patch, though.
>>>
>>> To sum up, I prefer modifying `watchpoint_del_at_next_stop' (i.e., 
>>> the v1 patch).
>>> If you also think it's appropriate, I'll fix the failing test and 
>>> prepare the
>>> v4 patch accordingly.
>>>
>>> Johnson
>>>
>>> On 1/13/2023 11:40 PM, SimonMarchi wrote:
>>>>
>>>> On 10/20/22 14:11, Johnson Sun via Gdb-patches wrote:
>>>>> Currently, when a local software watchpoint goes out of scope, GDB 
>>>>> sets the
>>>>> watchpoint's disposition to `delete at next stop' and then normal 
>>>>> stops
>>>>> (i.e., stop and wait for the next GDB command). When GDB normal 
>>>>> stops, it
>>>>> automatically deletes the breakpoints with their disposition set to
>>>>> `delete at next stop'.
>>>>>
>>>>> Suppose a Python script decides not to normal stop when a local 
>>>>> software
>>>>> watchpoint goes out of scope, the watchpoint will not be 
>>>>> automatically
>>>>> deleted even when its disposition is set to `delete at next stop'.
>>>>>
>>>>> Since GDB single-steps the program and tests the watched 
>>>>> expression after each
>>>>> instruction, not deleting the watchpoint causes the watchpoint to 
>>>>> be hit many
>>>>> more times than it should, as reported in PR python/29603.
>>>>>
>>>>> This was happening because the watchpoint is not deleted or 
>>>>> disabled when
>>>>> going out of scope.
>>>>>
>>>>> This commit fixes this issue by disabling the watchpoint when 
>>>>> going out of
>>>>> scope. It also adds a test to ensure this feature isn't regressed 
>>>>> in the
>>>>> future.
>>>>>
>>>>> Two other solutions seem to solve this issue, but are in fact 
>>>>> inappropriate:
>>>>> 1. Automatically delete breakpoints on all kinds of stops
>>>>>     (in `fetch_inferior_event'). This solution is very slow since 
>>>>> the deletion
>>>>>     requires O(N) time for N breakpoints.
>>>>> 2. Disable the watchpoint after the watchpoint's disposition is 
>>>>> set to
>>>>>     `delete at next stop' (in `watchpoint_del_at_next_stop'). This 
>>>>> solution
>>>>>     modifies a non-extension-related code path, and isn't 
>>>>> preferred since this
>>>>>     issue cannot occur without extension languages. (gdb scripts 
>>>>> always normal
>>>>>     stop before continuing execution)
>>>> I have a bit of trouble reviewing this, because I'm not too familiar
>>>> with the lifecycle of watchpoints (I know the principle, but not the
>>>> specifically where things happen in GDB).  So it's difficult to tell
>>>> whether this is right or if there's a better way to handle it.
>>>>
>>>> However, the supplied test does not pass for me:
>>>>
>>>>      source 
>>>> /home/simark/build/binutils-gdb/gdb/testsuite/outputs/gdb.python/py-watchpoint/py-watchpoint.py
>>>>      Watchpoint 2: i
>>>>      Python script imported
>>>>      (gdb) PASS: gdb.python/py-watchpoint.exp: import python scripts
>>>>      python print(len(gdb.breakpoints()))
>>>>      2
>>>>      (gdb) PASS: gdb.python/py-watchpoint.exp: check modified BP count
>>>>      gdb_expect_list pattern: /Watchpoint Hit: ./
>>>>      continue
>>>>      Continuing.
>>>>      Watchpoint Hit: 1
>>>>      gdb_expect_list pattern: /[
>>>>      ]+Watchpoint . deleted because the program has left the block in/
>>>>      FAIL: gdb.python/py-watchpoint.exp: run until program stops 
>>>> (pattern 2) (timeout)
>>>>      gdb_expect_list pattern: /[
>>>>      ]+which its expression is valid./
>>>>      gdb_expect_list pattern: /Watchpoint Hit: ./
>>>>      gdb_expect_list pattern: /[
>>>>      ]+012\[Inferior 1 \(process .*\) exited normally\]/
>>>>      gdb_expect_list pattern: //
>>>>      python print(len(gdb.breakpoints()))
>>>>      Watchpoint Hit: 2
>>>>      Watchpoint Hit: 3
>>>>      Watchpoint Hit: 4
>>>>
>>>>      Watchpoint 2 deleted because the program has left the block in
>>>>      which its expression is valid.
>>>>      Watchpoint Hit: 5
>>>>      012[Inferior 1 (process 2381681) exited normally]
>>>>      (gdb) FAIL: gdb.python/py-watchpoint.exp: check BP count (the 
>>>> program exited)
>>>>
>>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=29603
>>>>> ---
>>>>>   gdb/breakpoint.c                           |  2 +
>>>>>   gdb/testsuite/gdb.python/py-watchpoint.c   | 27 ++++++++++++
>>>>>   gdb/testsuite/gdb.python/py-watchpoint.exp | 48 
>>>>> ++++++++++++++++++++++
>>>>>   gdb/testsuite/gdb.python/py-watchpoint.py  | 30 ++++++++++++++
>>>>>   4 files changed, 107 insertions(+)
>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.c
>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.exp
>>>>>   create mode 100644 gdb/testsuite/gdb.python/py-watchpoint.py
>>>>>
>>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>>> index bff3bac7d1a..15f4ae2131c 100644
>>>>> --- a/gdb/breakpoint.c
>>>>> +++ b/gdb/breakpoint.c
>>>>> @@ -5340,6 +5340,8 @@ bpstat_check_breakpoint_conditions (bpstat 
>>>>> *bs, thread_info *thread)
>>>>>     /* Evaluate extension language breakpoints that have a "stop" 
>>>>> method
>>>>>        implemented.  */
>>>>>     bs->stop = breakpoint_ext_lang_cond_says_stop (b);
>>>>> +  if (b->disposition == disp_del_at_next_stop)
>>>>> +    disable_breakpoint(b);
>>>> Is there a reason to do it here in particular, and not, let's say as
>>>> soon as we change the disposition to disp_del_at_next_stop?
>>>>
>>>> Other question: shouldn't marking the watchpoint as
>>>> disp_del_at_next_stop make it so it won't be inserted next time we
>>>> resume?  After all should_be_inserted returns false for breakpoint
>>>> locations that are disp_del_at_next_stop.  Perhaps it's because 
>>>> since we
>>>> don't do a "normal" stop, breakpoint locations stay inserted, and
>>>> there's nothing that pulls this location out of the target? Therefore,
>>>> maybe a solution, to keep things coherent, would be to pull the
>>>> breakpoint locations out when marking the breakpoint as
>>>> disp_del_at_next_stop?  This way, we would respect the invariant 
>>>> that a
>>>> breakpoint disp_del_at_next_stop can't be inserted (I don't know if 
>>>> this
>>>> is really what is happening though, it's just speculation).
>>>>
>>>> And, in a broader scope, why wait until the next normal stop to delete
>>>> the watchpoint?  A "normal" stop being a stop that we present to the
>>>> user (the normal_stop function).  We currently call
>>>> breakpoint_auto_delete in normal_stop, which is why we don't reach it
>>>> when your Breakpoint.stop method returns False.  At the end of, let's
>>>> say, handle_signal_stop, could we go over the bpstat chain and delete
>>>> any breakpoint we've just hit that is marked for deletion?
>>>>
>>>> Simon
>>>>
>>
  
Simon Marchi March 13, 2023, 4 p.m. UTC | #13
On 1/23/23 05:15, Johnson Sun via Gdb-patches wrote:
> Hi,
> 
> Thanks for reviewing this!

Sorry for the delay.

> 
>> the supplied test does not pass for me
> 
> The current test doesn't seem to produce consistent results across different
> machines, I'll try to fix it in the next version.
> 
>> Is there a reason to do it here in particular, and not, let's say as
>> soon as we change the disposition to disp_del_at_next_stop?
> 
> I have implemented this in the v1 patch, I called `disable_breakpoint' as
> soon as we change the disposition to `disp_del_at_next_stop'
> (in `watchpoint_del_at_next_stop'). However,
> LancelotSIX <lsix@lancelotsix.com> mentioned that the fix is in a
> non-extension-related code path, and suggested disabling it in
> `bpstat_check_breakpoint_conditions' instead (the v3 patch).
> See: https://sourceware.org/pipermail/gdb-patches/2022-September/192120.html

Ah, sorry for missing that.  I now see that you also mentionned it in
your commit message.

> Both the v1 and v3 patch fixes the issue. I personally prefer the v1 patch.
> (i.e., modifying `watchpoint_del_at_next_stop')
> 
>> shouldn't marking the watchpoint as
>> disp_del_at_next_stop make it so it won't be inserted next time we
>> resume?  After all should_be_inserted returns false for breakpoint
>> locations that are disp_del_at_next_stop.  Perhaps it's because since we
>> don't do a "normal" stop, breakpoint locations stay inserted, and
>> there's nothing that pulls this location out of the target? Therefore,
>> maybe a solution, to keep things coherent, would be to pull the
>> breakpoint locations out when marking the breakpoint as
>> disp_del_at_next_stop?  This way, we would respect the invariant that a
>> breakpoint disp_del_at_next_stop can't be inserted (I don't know if this
>> is really what is happening though, it's just speculation).
> 
> For software watchpoints, they cannot be pulled out of the target, since
> they may not be inserted into the target in the first place:
> 
>      /* Locations of type bp_loc_other and
>     bp_loc_software_watchpoint are only maintained at GDB side,
>     so there is no need to remove them.  */
> 
>     -- gdb/breakpoint.c:3840
> 
> Their expressions are re-evaluated by single-stepping through the program:
> 
>     Software watchpoints are very slow, since GDB needs to single-step
>     the program being debugged and test the value of the watched
>     expression(s) after each instruction.
> 
>     -- https://sourceware.org/gdb/wiki/Internals%20Watchpoints
> 
> Therefore, we must either disable or delete the software watchpoint.
> We cannot pull it out of the target since it's only maintained on the
> GDB side.

Ah ok, I had not understood that we are talking about software
watchpoints.

> 
>> And, in a broader scope, why wait until the next normal stop to delete
>> the watchpoint?  A "normal" stop being a stop that we present to the
>> user (the normal_stop function).  We currently call
>> breakpoint_auto_delete in normal_stop, which is why we don't reach it
>> when your Breakpoint.stop method returns False.  At the end of, let's
>> say, handle_signal_stop, could we go over the bpstat chain and delete
>> any breakpoint we've just hit that is marked for deletion?
> 
> I believe this choice is due to performance issues.
> 
> In an early attempt, I tried to call `breakpoint_auto_delete' on all kinds
> of stops. However, this implementation is very slow when we have a large
> number of breakpoints, since we need to go over the entire bpstat chain on
> all stops. I recall that this implementation fails on certain testcases with
> a large number of breakpoints with many breakpoint stops.

There's a difference between going over all breakpoints, and going over
the bpstat chain.  Unecessarily going over all breakpoints is not a good
idea, indeed.  But the bpstat chain only contains elements for
breakpoints that were hit right now.  In a pathological case, you could
have a lot of elements in that chain, but I don't think it's the norm.
The typical case is to have just one element, when hitting a single
breakpoint.

breakpoint_auto_delete does both.  I was thinking that just looking at
the bpstat chain, let's say at the end of handle_inferior_event, could
be done for cheap.

But in any case, that's a larger change, I think we should focus on your
fix, and if somebody feels like going it, this can be another change
later.

> Furthermore, the reason for using the `disp_del_at_next_stop' state remains
> unclear, as mentioned in the comments:
> 
>     /* NOTE drow/2003-09-08: This state only exists for removing
>        watchpoints.  It's not clear that it's necessary...  */
> 
>     -- gdb/breakpoint.c:2914
> 
> By tracing the git history, the `disp_del_at_next_stop' state is introduced
> in commit c906108c21474dfb4ed285bcc0ac6fe02cd400cc, which doesn't provide
> any proper explanation of this state.
> 
> I think the best way to deal with this is to avoid going over the entire
> bpstat chain when deleting breakpoints. Potentially by keeping track of
> a chain of breakpoints that should be deleted, and changing the bpstat chain
> to a doubly linked list for the ease of deletion. Such changes deserve a
> dedicated patch, though.

Maybe the bpstat chain could use intrusive_list:

https://gitlab.com/gnutools/binutils-gdb/-/blob/master/gdbsupport/intrusive_list.h

> To sum up, I prefer modifying `watchpoint_del_at_next_stop' (i.e., the v1 patch).
> If you also think it's appropriate, I'll fix the failing test and prepare the
> v4 patch accordingly.

I think I prefer the v1 too, it puts the action of disabling the
watchpoint closer to where we decide to disable it, so it's easier to
follow.  I don't think it's a problem that the fix touches a
non-extension code path.  The change in v1 sounds logical to me: the
intent of watchpoint_del_at_next_stop is to get rid of the watchpoint,
make it so the user can't hit it anymore.  It's just that for some
reason (unknown to me at this point), we can't delete it right away.  If
disabling it is needed to ensure the watchpoint is not seen by the user
in some circumstances, then so be it.

I guess another solution would be to make watchpoint_check check
b->disposition, and skip it if it's disp_del_at_next_stop.  I don't
think it's any better though.

Simon
  
Johnson Sun March 23, 2023, 6:25 p.m. UTC | #14
Hi Simon,

Thanks for reviewing this!  I revised the patch and

will send version 4 shortly.

Cheers,
Johnson
  

Patch

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index bff3bac7d1a..15f4ae2131c 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -5340,6 +5340,8 @@  bpstat_check_breakpoint_conditions (bpstat *bs, thread_info *thread)
   /* Evaluate extension language breakpoints that have a "stop" method
      implemented.  */
   bs->stop = breakpoint_ext_lang_cond_says_stop (b);
+  if (b->disposition == disp_del_at_next_stop)
+    disable_breakpoint(b);
 
   if (is_watchpoint (b))
     {
diff --git a/gdb/testsuite/gdb.python/py-watchpoint.c b/gdb/testsuite/gdb.python/py-watchpoint.c
new file mode 100644
index 00000000000..4e1760bb05b
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-watchpoint.c
@@ -0,0 +1,27 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022 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 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  <http://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+
+int
+main (void)
+{
+  int i;
+  for (i = 0; i < 3; i++)
+    printf ("%d", i);
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-watchpoint.exp b/gdb/testsuite/gdb.python/py-watchpoint.exp
new file mode 100644
index 00000000000..ac58d75c523
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-watchpoint.exp
@@ -0,0 +1,48 @@ 
+# Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
+
+# Check that Watchpoints are deleted after use.
+
+load_lib gdb-python.exp
+
+standard_testfile
+
+if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
+    return -1
+}
+
+# Skip all tests if Python scripting is not enabled.
+if { [skip_python_tests] } { continue }
+
+if ![runto_main] then {
+    return 0
+}
+
+# For remote host testing
+set pyfile [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+gdb_test_no_output "set can-use-hw-watchpoints 0" "Don't use hardware watchpoints"
+gdb_test "python print(len(gdb.breakpoints()))" "1" "check default BP count"
+gdb_test "source $pyfile" ".*Python script imported.*" \
+    "import python scripts"
+gdb_test "python print(len(gdb.breakpoints()))" "2" "check modified BP count"
+gdb_test_sequence "continue" "run until program stops" {
+    "Watchpoint Hit: ."
+    "\[\r\n\]+Watchpoint . deleted because the program has left the block in"
+    "\[\r\n\]+which its expression is valid\."
+    "Watchpoint Hit: ."
+    "\[\r\n\]+012\\[Inferior 1 \\(process .*\\) exited normally\\]"
+}
+gdb_test "python print(len(gdb.breakpoints()))" "1" "check BP count"
diff --git a/gdb/testsuite/gdb.python/py-watchpoint.py b/gdb/testsuite/gdb.python/py-watchpoint.py
new file mode 100644
index 00000000000..ce5dee118ad
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-watchpoint.py
@@ -0,0 +1,30 @@ 
+# Copyright (C) 2022 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 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 <http://www.gnu.org/licenses/>.
+
+
+class MyBreakpoint(gdb.Breakpoint):
+    def __init__(self, *args, **kwargs):
+        gdb.Breakpoint.__init__(self, *args, **kwargs)
+        self.i = 0
+
+    def stop(self):
+        self.i += 1
+        print("Watchpoint Hit:", self.i)
+        return False
+
+
+MyBreakpoint("i", gdb.BP_WATCHPOINT)
+
+print("Python script imported")