[v2,gdb/breakpoints] Fix gdb.base/scope-hw-watch-disable.exp on arm-linux

Message ID 20240821110905.23759-1-tdevries@suse.de
State Committed
Headers
Series [v2,gdb/breakpoints] Fix gdb.base/scope-hw-watch-disable.exp on arm-linux |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Tom de Vries Aug. 21, 2024, 11:09 a.m. UTC
  On arm-linux, with test-case gdb.base/scope-hw-watch-disable.exp I run into:
...
(gdb) awatch a^M
Can't set read/access watchpoint when hardware watchpoints are disabled.^M
(gdb) PASS: $exp: unsuccessful attempt to create an access watchpoint
rwatch b^M
Can't set read/access watchpoint when hardware watchpoints are disabled.^M
(gdb) PASS: $exp: unsuccessful attempt to create a read watchpoint
continue^M
Continuing.^M
^M
Program received signal SIGSEGV, Segmentation fault.^M
0xf7ec82c8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6^M
(gdb) FAIL: $exp: continue until exit
...

Using "maint info break", we can see that the two failed attempts to set a
watchpoint each left behind a stale "watchpoint scope" breakpoint:
...
-5      watchpoint scope del  y   0xf7ec569a  inf 1
-5.1                          y   0xf7ec569a  inf 1
	stop only in stack frame at 0xfffef4f8
-6      watchpoint scope del  y   0xf7ec569a  inf 1
-6.1                          y   0xf7ec569a  inf 1
	stop only in stack frame at 0xfffef4f8
...

The SIGSEGV is a consequence of the stale "watchpoint scope" breakpoint: the
same happens if we:
- have can-use-hw-watchpoints == 1,
- set one of the watchpoints, and
- continue to exit.
The problem is missing symbol info on libc which is supposed to tell which
code is thumb.  After doing "set arm fallback-mode thumb" the SIGSEGV
disappears.

Extend the test-case to check the "maint info break" command before and after
the two failed attempts, to make sure that we catch the stale
"watchpoint scope" breakpoints also on x86_64-linux.

Fix this in watch_command_1 by moving creation of the "watchpoint scope"
breakpoint after the call to update_watchpoint.

Tested on x86_64-linux.

PR breakpoints/31860
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31860
---
 gdb/breakpoint.c                              | 100 ++++++++++--------
 .../gdb.base/scope-hw-watch-disable.exp       |  18 ++++
 2 files changed, 71 insertions(+), 47 deletions(-)


base-commit: 28c3bf55f0f9aca8619c6d01be34a02a887c5577
  

Comments

Tom de Vries Sept. 4, 2024, 7:38 a.m. UTC | #1
On 8/21/24 13:09, Tom de Vries wrote:
> On arm-linux, with test-case gdb.base/scope-hw-watch-disable.exp I run into:
> ...
> (gdb) awatch a^M
> Can't set read/access watchpoint when hardware watchpoints are disabled.^M
> (gdb) PASS: $exp: unsuccessful attempt to create an access watchpoint
> rwatch b^M
> Can't set read/access watchpoint when hardware watchpoints are disabled.^M
> (gdb) PASS: $exp: unsuccessful attempt to create a read watchpoint
> continue^M
> Continuing.^M
> ^M
> Program received signal SIGSEGV, Segmentation fault.^M
> 0xf7ec82c8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6^M
> (gdb) FAIL: $exp: continue until exit
> ...
> 
> Using "maint info break", we can see that the two failed attempts to set a
> watchpoint each left behind a stale "watchpoint scope" breakpoint:
> ...
> -5      watchpoint scope del  y   0xf7ec569a  inf 1
> -5.1                          y   0xf7ec569a  inf 1
> 	stop only in stack frame at 0xfffef4f8
> -6      watchpoint scope del  y   0xf7ec569a  inf 1
> -6.1                          y   0xf7ec569a  inf 1
> 	stop only in stack frame at 0xfffef4f8
> ...
> 
> The SIGSEGV is a consequence of the stale "watchpoint scope" breakpoint: the
> same happens if we:
> - have can-use-hw-watchpoints == 1,
> - set one of the watchpoints, and
> - continue to exit.
> The problem is missing symbol info on libc which is supposed to tell which
> code is thumb.  After doing "set arm fallback-mode thumb" the SIGSEGV
> disappears.
> 
> Extend the test-case to check the "maint info break" command before and after
> the two failed attempts, to make sure that we catch the stale
> "watchpoint scope" breakpoints also on x86_64-linux.
> 
> Fix this in watch_command_1 by moving creation of the "watchpoint scope"
> breakpoint after the call to update_watchpoint.
> 

Ping.

Thanks,
- Tom

> Tested on x86_64-linux.
> 
> PR breakpoints/31860
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31860
> ---
>   gdb/breakpoint.c                              | 100 ++++++++++--------
>   .../gdb.base/scope-hw-watch-disable.exp       |  18 ++++
>   2 files changed, 71 insertions(+), 47 deletions(-)
> 
> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
> index 17bd627f867..6d8610d14e7 100644
> --- a/gdb/breakpoint.c
> +++ b/gdb/breakpoint.c
> @@ -10504,45 +10504,6 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>        'wp_frame'.  */
>     frame_id watchpoint_frame = get_frame_id (wp_frame);
>   
> -  /* If the expression is "local", then set up a "watchpoint scope"
> -     breakpoint at the point where we've left the scope of the watchpoint
> -     expression.  Create the scope breakpoint before the watchpoint, so
> -     that we will encounter it first in bpstat_stop_status.  */
> -  if (exp_valid_block != NULL && wp_frame != NULL)
> -    {
> -      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
> -
> -      if (frame_id_p (caller_frame_id))
> -	{
> -	  gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
> -	  CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
> -
> -	  scope_breakpoint
> -	    = create_internal_breakpoint (caller_arch, caller_pc,
> -					  bp_watchpoint_scope);
> -
> -	  /* create_internal_breakpoint could invalidate WP_FRAME.  */
> -	  wp_frame = NULL;
> -
> -	  scope_breakpoint->enable_state = bp_enabled;
> -
> -	  /* Automatically delete the breakpoint when it hits.  */
> -	  scope_breakpoint->disposition = disp_del;
> -
> -	  /* Only break in the proper frame (help with recursion).  */
> -	  scope_breakpoint->frame_id = caller_frame_id;
> -
> -	  /* Set the address at which we will stop.  */
> -	  bp_location &loc = scope_breakpoint->first_loc ();
> -	  loc.gdbarch = caller_arch;
> -	  loc.requested_address = caller_pc;
> -	  loc.address
> -	    = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
> -					 scope_breakpoint->type,
> -					 current_program_space);
> -	}
> -    }
> -
>     /* Now set up the breakpoint.  We create all watchpoints as hardware
>        watchpoints here even if hardware watchpoints are turned off, a call
>        to update_watchpoint later in this function will cause the type to
> @@ -10613,14 +10574,6 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>         w->watchpoint_thread = null_ptid;
>       }
>   
> -  if (scope_breakpoint != NULL)
> -    {
> -      /* The scope breakpoint is related to the watchpoint.  We will
> -	 need to act on them together.  */
> -      w->related_breakpoint = scope_breakpoint;
> -      scope_breakpoint->related_breakpoint = w.get ();
> -    }
> -
>     if (!just_location)
>       value_free_to_mark (mark);
>   
> @@ -10628,7 +10581,60 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>        that should be inserted.  */
>     update_watchpoint (w.get (), true /* reparse */);
>   
> +  /* If the expression is "local", then set up a "watchpoint scope"
> +     breakpoint at the point where we've left the scope of the watchpoint
> +     expression.  Create the scope breakpoint before the watchpoint, so
> +     that we will encounter it first in bpstat_stop_status.  */
> +  if (exp_valid_block != nullptr && wp_frame != nullptr)
> +    {
> +      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
> +
> +      if (frame_id_p (caller_frame_id))
> +	{
> +	  gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
> +	  CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
> +
> +	  scope_breakpoint
> +	    = create_internal_breakpoint (caller_arch, caller_pc,
> +					  bp_watchpoint_scope);
> +
> +	  /* create_internal_breakpoint could invalidate WP_FRAME.  */
> +	  wp_frame = nullptr;
> +
> +	  scope_breakpoint->enable_state = bp_enabled;
> +
> +	  /* Automatically delete the breakpoint when it hits.  */
> +	  scope_breakpoint->disposition = disp_del;
> +
> +	  /* Only break in the proper frame (help with recursion).  */
> +	  scope_breakpoint->frame_id = caller_frame_id;
> +
> +	  /* Set the address at which we will stop.  */
> +	  bp_location &loc = scope_breakpoint->first_loc ();
> +	  loc.gdbarch = caller_arch;
> +	  loc.requested_address = caller_pc;
> +	  loc.address
> +	    = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
> +					 scope_breakpoint->type,
> +					 current_program_space);
> +	}
> +  }
> +
> +  if (scope_breakpoint != nullptr)
> +    {
> +      /* The scope breakpoint is related to the watchpoint.  We will
> +	 need to act on them together.  */
> +      w->related_breakpoint = scope_breakpoint;
> +      scope_breakpoint->related_breakpoint = w.get ();
> +    }
> +
> +  /* Verify that the scope breakpoint comes before the watchpoint in the
> +     breakpoint chain.  */
> +  gdb_assert (scope_breakpoint == nullptr
> +	      || &breakpoint_chain.back () == scope_breakpoint);
> +  watchpoint *watchpoint_ptr = w.get ();
>     install_breakpoint (internal, std::move (w), 1);
> +  gdb_assert (&breakpoint_chain.back () == watchpoint_ptr);
>   }
>   
>   /* Return count of debug registers needed to watch the given expression.
> diff --git a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
> index 61137703e5c..29eb682df47 100644
> --- a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
> +++ b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
> @@ -29,6 +29,15 @@ if {![runto_main]} {
>       return -1
>   }
>   
> +gdb_test_multiple "maint info break" "maint info break before" {
> +    -re -wrap "watchpoint.*" {
> +	fail $gdb_test_name
> +    }
> +    -re -wrap "" {
> +	pass $gdb_test_name
> +    }
> +}
> +
>   gdb_test "awatch a" \
>       "Can't set read/access watchpoint when hardware watchpoints are disabled." \
>       "unsuccessful attempt to create an access watchpoint"
> @@ -36,5 +45,14 @@ gdb_test "rwatch b" \
>       "Can't set read/access watchpoint when hardware watchpoints are disabled." \
>       "unsuccessful attempt to create a read watchpoint"
>   
> +gdb_test_multiple "maint info break" "maint info break after" {
> +    -re -wrap "watchpoint.*" {
> +	fail $gdb_test_name
> +    }
> +    -re -wrap "" {
> +	pass $gdb_test_name
> +    }
> +}
> +
>   # The program continues until termination.
>   gdb_continue_to_end
> 
> base-commit: 28c3bf55f0f9aca8619c6d01be34a02a887c5577
  
Tom de Vries Sept. 23, 2024, 8:15 a.m. UTC | #2
On 9/4/24 09:38, Tom de Vries wrote:
> On 8/21/24 13:09, Tom de Vries wrote:
>> On arm-linux, with test-case gdb.base/scope-hw-watch-disable.exp I run 
>> into:
>> ...
>> (gdb) awatch a^M
>> Can't set read/access watchpoint when hardware watchpoints are 
>> disabled.^M
>> (gdb) PASS: $exp: unsuccessful attempt to create an access watchpoint
>> rwatch b^M
>> Can't set read/access watchpoint when hardware watchpoints are 
>> disabled.^M
>> (gdb) PASS: $exp: unsuccessful attempt to create a read watchpoint
>> continue^M
>> Continuing.^M
>> ^M
>> Program received signal SIGSEGV, Segmentation fault.^M
>> 0xf7ec82c8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6^M
>> (gdb) FAIL: $exp: continue until exit
>> ...
>>
>> Using "maint info break", we can see that the two failed attempts to 
>> set a
>> watchpoint each left behind a stale "watchpoint scope" breakpoint:
>> ...
>> -5      watchpoint scope del  y   0xf7ec569a  inf 1
>> -5.1                          y   0xf7ec569a  inf 1
>>     stop only in stack frame at 0xfffef4f8
>> -6      watchpoint scope del  y   0xf7ec569a  inf 1
>> -6.1                          y   0xf7ec569a  inf 1
>>     stop only in stack frame at 0xfffef4f8
>> ...
>>
>> The SIGSEGV is a consequence of the stale "watchpoint scope" 
>> breakpoint: the
>> same happens if we:
>> - have can-use-hw-watchpoints == 1,
>> - set one of the watchpoints, and
>> - continue to exit.
>> The problem is missing symbol info on libc which is supposed to tell 
>> which
>> code is thumb.  After doing "set arm fallback-mode thumb" the SIGSEGV
>> disappears.
>>
>> Extend the test-case to check the "maint info break" command before 
>> and after
>> the two failed attempts, to make sure that we catch the stale
>> "watchpoint scope" breakpoints also on x86_64-linux.
>>
>> Fix this in watch_command_1 by moving creation of the "watchpoint scope"
>> breakpoint after the call to update_watchpoint.
>>
> 

Ping^2.

Thanks,
- Tom

>> Tested on x86_64-linux.
>>
>> PR breakpoints/31860
>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31860
>> ---
>>   gdb/breakpoint.c                              | 100 ++++++++++--------
>>   .../gdb.base/scope-hw-watch-disable.exp       |  18 ++++
>>   2 files changed, 71 insertions(+), 47 deletions(-)
>>
>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>> index 17bd627f867..6d8610d14e7 100644
>> --- a/gdb/breakpoint.c
>> +++ b/gdb/breakpoint.c
>> @@ -10504,45 +10504,6 @@ watch_command_1 (const char *arg, int 
>> accessflag, int from_tty,
>>        'wp_frame'.  */
>>     frame_id watchpoint_frame = get_frame_id (wp_frame);
>> -  /* If the expression is "local", then set up a "watchpoint scope"
>> -     breakpoint at the point where we've left the scope of the 
>> watchpoint
>> -     expression.  Create the scope breakpoint before the watchpoint, so
>> -     that we will encounter it first in bpstat_stop_status.  */
>> -  if (exp_valid_block != NULL && wp_frame != NULL)
>> -    {
>> -      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
>> -
>> -      if (frame_id_p (caller_frame_id))
>> -    {
>> -      gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
>> -      CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
>> -
>> -      scope_breakpoint
>> -        = create_internal_breakpoint (caller_arch, caller_pc,
>> -                      bp_watchpoint_scope);
>> -
>> -      /* create_internal_breakpoint could invalidate WP_FRAME.  */
>> -      wp_frame = NULL;
>> -
>> -      scope_breakpoint->enable_state = bp_enabled;
>> -
>> -      /* Automatically delete the breakpoint when it hits.  */
>> -      scope_breakpoint->disposition = disp_del;
>> -
>> -      /* Only break in the proper frame (help with recursion).  */
>> -      scope_breakpoint->frame_id = caller_frame_id;
>> -
>> -      /* Set the address at which we will stop.  */
>> -      bp_location &loc = scope_breakpoint->first_loc ();
>> -      loc.gdbarch = caller_arch;
>> -      loc.requested_address = caller_pc;
>> -      loc.address
>> -        = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
>> -                     scope_breakpoint->type,
>> -                     current_program_space);
>> -    }
>> -    }
>> -
>>     /* Now set up the breakpoint.  We create all watchpoints as hardware
>>        watchpoints here even if hardware watchpoints are turned off, a 
>> call
>>        to update_watchpoint later in this function will cause the type to
>> @@ -10613,14 +10574,6 @@ watch_command_1 (const char *arg, int 
>> accessflag, int from_tty,
>>         w->watchpoint_thread = null_ptid;
>>       }
>> -  if (scope_breakpoint != NULL)
>> -    {
>> -      /* The scope breakpoint is related to the watchpoint.  We will
>> -     need to act on them together.  */
>> -      w->related_breakpoint = scope_breakpoint;
>> -      scope_breakpoint->related_breakpoint = w.get ();
>> -    }
>> -
>>     if (!just_location)
>>       value_free_to_mark (mark);
>> @@ -10628,7 +10581,60 @@ watch_command_1 (const char *arg, int 
>> accessflag, int from_tty,
>>        that should be inserted.  */
>>     update_watchpoint (w.get (), true /* reparse */);
>> +  /* If the expression is "local", then set up a "watchpoint scope"
>> +     breakpoint at the point where we've left the scope of the 
>> watchpoint
>> +     expression.  Create the scope breakpoint before the watchpoint, so
>> +     that we will encounter it first in bpstat_stop_status.  */
>> +  if (exp_valid_block != nullptr && wp_frame != nullptr)
>> +    {
>> +      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
>> +
>> +      if (frame_id_p (caller_frame_id))
>> +    {
>> +      gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
>> +      CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
>> +
>> +      scope_breakpoint
>> +        = create_internal_breakpoint (caller_arch, caller_pc,
>> +                      bp_watchpoint_scope);
>> +
>> +      /* create_internal_breakpoint could invalidate WP_FRAME.  */
>> +      wp_frame = nullptr;
>> +
>> +      scope_breakpoint->enable_state = bp_enabled;
>> +
>> +      /* Automatically delete the breakpoint when it hits.  */
>> +      scope_breakpoint->disposition = disp_del;
>> +
>> +      /* Only break in the proper frame (help with recursion).  */
>> +      scope_breakpoint->frame_id = caller_frame_id;
>> +
>> +      /* Set the address at which we will stop.  */
>> +      bp_location &loc = scope_breakpoint->first_loc ();
>> +      loc.gdbarch = caller_arch;
>> +      loc.requested_address = caller_pc;
>> +      loc.address
>> +        = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
>> +                     scope_breakpoint->type,
>> +                     current_program_space);
>> +    }
>> +  }
>> +
>> +  if (scope_breakpoint != nullptr)
>> +    {
>> +      /* The scope breakpoint is related to the watchpoint.  We will
>> +     need to act on them together.  */
>> +      w->related_breakpoint = scope_breakpoint;
>> +      scope_breakpoint->related_breakpoint = w.get ();
>> +    }
>> +
>> +  /* Verify that the scope breakpoint comes before the watchpoint in the
>> +     breakpoint chain.  */
>> +  gdb_assert (scope_breakpoint == nullptr
>> +          || &breakpoint_chain.back () == scope_breakpoint);
>> +  watchpoint *watchpoint_ptr = w.get ();
>>     install_breakpoint (internal, std::move (w), 1);
>> +  gdb_assert (&breakpoint_chain.back () == watchpoint_ptr);
>>   }
>>   /* Return count of debug registers needed to watch the given 
>> expression.
>> diff --git a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp 
>> b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>> index 61137703e5c..29eb682df47 100644
>> --- a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>> +++ b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>> @@ -29,6 +29,15 @@ if {![runto_main]} {
>>       return -1
>>   }
>> +gdb_test_multiple "maint info break" "maint info break before" {
>> +    -re -wrap "watchpoint.*" {
>> +    fail $gdb_test_name
>> +    }
>> +    -re -wrap "" {
>> +    pass $gdb_test_name
>> +    }
>> +}
>> +
>>   gdb_test "awatch a" \
>>       "Can't set read/access watchpoint when hardware watchpoints are 
>> disabled." \
>>       "unsuccessful attempt to create an access watchpoint"
>> @@ -36,5 +45,14 @@ gdb_test "rwatch b" \
>>       "Can't set read/access watchpoint when hardware watchpoints are 
>> disabled." \
>>       "unsuccessful attempt to create a read watchpoint"
>> +gdb_test_multiple "maint info break" "maint info break after" {
>> +    -re -wrap "watchpoint.*" {
>> +    fail $gdb_test_name
>> +    }
>> +    -re -wrap "" {
>> +    pass $gdb_test_name
>> +    }
>> +}
>> +
>>   # The program continues until termination.
>>   gdb_continue_to_end
>>
>> base-commit: 28c3bf55f0f9aca8619c6d01be34a02a887c5577
>
  
Tom de Vries Oct. 10, 2024, 10:42 a.m. UTC | #3
On 9/23/24 10:15, Tom de Vries wrote:
> On 9/4/24 09:38, Tom de Vries wrote:
>> On 8/21/24 13:09, Tom de Vries wrote:
>>> On arm-linux, with test-case gdb.base/scope-hw-watch-disable.exp I 
>>> run into:
>>> ...
>>> (gdb) awatch a^M
>>> Can't set read/access watchpoint when hardware watchpoints are 
>>> disabled.^M
>>> (gdb) PASS: $exp: unsuccessful attempt to create an access watchpoint
>>> rwatch b^M
>>> Can't set read/access watchpoint when hardware watchpoints are 
>>> disabled.^M
>>> (gdb) PASS: $exp: unsuccessful attempt to create a read watchpoint
>>> continue^M
>>> Continuing.^M
>>> ^M
>>> Program received signal SIGSEGV, Segmentation fault.^M
>>> 0xf7ec82c8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6^M
>>> (gdb) FAIL: $exp: continue until exit
>>> ...
>>>
>>> Using "maint info break", we can see that the two failed attempts to 
>>> set a
>>> watchpoint each left behind a stale "watchpoint scope" breakpoint:
>>> ...
>>> -5      watchpoint scope del  y   0xf7ec569a  inf 1
>>> -5.1                          y   0xf7ec569a  inf 1
>>>     stop only in stack frame at 0xfffef4f8
>>> -6      watchpoint scope del  y   0xf7ec569a  inf 1
>>> -6.1                          y   0xf7ec569a  inf 1
>>>     stop only in stack frame at 0xfffef4f8
>>> ...
>>>
>>> The SIGSEGV is a consequence of the stale "watchpoint scope" 
>>> breakpoint: the
>>> same happens if we:
>>> - have can-use-hw-watchpoints == 1,
>>> - set one of the watchpoints, and
>>> - continue to exit.
>>> The problem is missing symbol info on libc which is supposed to tell 
>>> which
>>> code is thumb.  After doing "set arm fallback-mode thumb" the SIGSEGV
>>> disappears.
>>>
>>> Extend the test-case to check the "maint info break" command before 
>>> and after
>>> the two failed attempts, to make sure that we catch the stale
>>> "watchpoint scope" breakpoints also on x86_64-linux.
>>>
>>> Fix this in watch_command_1 by moving creation of the "watchpoint scope"
>>> breakpoint after the call to update_watchpoint.
>>>
>>
> 
> Ping^2.
> 

I've pushed this.

Thanks,
- Tom

>>> Tested on x86_64-linux.
>>>
>>> PR breakpoints/31860
>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31860
>>> ---
>>>   gdb/breakpoint.c                              | 100 ++++++++++--------
>>>   .../gdb.base/scope-hw-watch-disable.exp       |  18 ++++
>>>   2 files changed, 71 insertions(+), 47 deletions(-)
>>>
>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>> index 17bd627f867..6d8610d14e7 100644
>>> --- a/gdb/breakpoint.c
>>> +++ b/gdb/breakpoint.c
>>> @@ -10504,45 +10504,6 @@ watch_command_1 (const char *arg, int 
>>> accessflag, int from_tty,
>>>        'wp_frame'.  */
>>>     frame_id watchpoint_frame = get_frame_id (wp_frame);
>>> -  /* If the expression is "local", then set up a "watchpoint scope"
>>> -     breakpoint at the point where we've left the scope of the 
>>> watchpoint
>>> -     expression.  Create the scope breakpoint before the watchpoint, so
>>> -     that we will encounter it first in bpstat_stop_status.  */
>>> -  if (exp_valid_block != NULL && wp_frame != NULL)
>>> -    {
>>> -      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
>>> -
>>> -      if (frame_id_p (caller_frame_id))
>>> -    {
>>> -      gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
>>> -      CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
>>> -
>>> -      scope_breakpoint
>>> -        = create_internal_breakpoint (caller_arch, caller_pc,
>>> -                      bp_watchpoint_scope);
>>> -
>>> -      /* create_internal_breakpoint could invalidate WP_FRAME.  */
>>> -      wp_frame = NULL;
>>> -
>>> -      scope_breakpoint->enable_state = bp_enabled;
>>> -
>>> -      /* Automatically delete the breakpoint when it hits.  */
>>> -      scope_breakpoint->disposition = disp_del;
>>> -
>>> -      /* Only break in the proper frame (help with recursion).  */
>>> -      scope_breakpoint->frame_id = caller_frame_id;
>>> -
>>> -      /* Set the address at which we will stop.  */
>>> -      bp_location &loc = scope_breakpoint->first_loc ();
>>> -      loc.gdbarch = caller_arch;
>>> -      loc.requested_address = caller_pc;
>>> -      loc.address
>>> -        = adjust_breakpoint_address (loc.gdbarch, 
>>> loc.requested_address,
>>> -                     scope_breakpoint->type,
>>> -                     current_program_space);
>>> -    }
>>> -    }
>>> -
>>>     /* Now set up the breakpoint.  We create all watchpoints as hardware
>>>        watchpoints here even if hardware watchpoints are turned off, 
>>> a call
>>>        to update_watchpoint later in this function will cause the 
>>> type to
>>> @@ -10613,14 +10574,6 @@ watch_command_1 (const char *arg, int 
>>> accessflag, int from_tty,
>>>         w->watchpoint_thread = null_ptid;
>>>       }
>>> -  if (scope_breakpoint != NULL)
>>> -    {
>>> -      /* The scope breakpoint is related to the watchpoint.  We will
>>> -     need to act on them together.  */
>>> -      w->related_breakpoint = scope_breakpoint;
>>> -      scope_breakpoint->related_breakpoint = w.get ();
>>> -    }
>>> -
>>>     if (!just_location)
>>>       value_free_to_mark (mark);
>>> @@ -10628,7 +10581,60 @@ watch_command_1 (const char *arg, int 
>>> accessflag, int from_tty,
>>>        that should be inserted.  */
>>>     update_watchpoint (w.get (), true /* reparse */);
>>> +  /* If the expression is "local", then set up a "watchpoint scope"
>>> +     breakpoint at the point where we've left the scope of the 
>>> watchpoint
>>> +     expression.  Create the scope breakpoint before the watchpoint, so
>>> +     that we will encounter it first in bpstat_stop_status.  */
>>> +  if (exp_valid_block != nullptr && wp_frame != nullptr)
>>> +    {
>>> +      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
>>> +
>>> +      if (frame_id_p (caller_frame_id))
>>> +    {
>>> +      gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
>>> +      CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
>>> +
>>> +      scope_breakpoint
>>> +        = create_internal_breakpoint (caller_arch, caller_pc,
>>> +                      bp_watchpoint_scope);
>>> +
>>> +      /* create_internal_breakpoint could invalidate WP_FRAME.  */
>>> +      wp_frame = nullptr;
>>> +
>>> +      scope_breakpoint->enable_state = bp_enabled;
>>> +
>>> +      /* Automatically delete the breakpoint when it hits.  */
>>> +      scope_breakpoint->disposition = disp_del;
>>> +
>>> +      /* Only break in the proper frame (help with recursion).  */
>>> +      scope_breakpoint->frame_id = caller_frame_id;
>>> +
>>> +      /* Set the address at which we will stop.  */
>>> +      bp_location &loc = scope_breakpoint->first_loc ();
>>> +      loc.gdbarch = caller_arch;
>>> +      loc.requested_address = caller_pc;
>>> +      loc.address
>>> +        = adjust_breakpoint_address (loc.gdbarch, 
>>> loc.requested_address,
>>> +                     scope_breakpoint->type,
>>> +                     current_program_space);
>>> +    }
>>> +  }
>>> +
>>> +  if (scope_breakpoint != nullptr)
>>> +    {
>>> +      /* The scope breakpoint is related to the watchpoint.  We will
>>> +     need to act on them together.  */
>>> +      w->related_breakpoint = scope_breakpoint;
>>> +      scope_breakpoint->related_breakpoint = w.get ();
>>> +    }
>>> +
>>> +  /* Verify that the scope breakpoint comes before the watchpoint in 
>>> the
>>> +     breakpoint chain.  */
>>> +  gdb_assert (scope_breakpoint == nullptr
>>> +          || &breakpoint_chain.back () == scope_breakpoint);
>>> +  watchpoint *watchpoint_ptr = w.get ();
>>>     install_breakpoint (internal, std::move (w), 1);
>>> +  gdb_assert (&breakpoint_chain.back () == watchpoint_ptr);
>>>   }
>>>   /* Return count of debug registers needed to watch the given 
>>> expression.
>>> diff --git a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp 
>>> b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>>> index 61137703e5c..29eb682df47 100644
>>> --- a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>>> +++ b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>>> @@ -29,6 +29,15 @@ if {![runto_main]} {
>>>       return -1
>>>   }
>>> +gdb_test_multiple "maint info break" "maint info break before" {
>>> +    -re -wrap "watchpoint.*" {
>>> +    fail $gdb_test_name
>>> +    }
>>> +    -re -wrap "" {
>>> +    pass $gdb_test_name
>>> +    }
>>> +}
>>> +
>>>   gdb_test "awatch a" \
>>>       "Can't set read/access watchpoint when hardware watchpoints are 
>>> disabled." \
>>>       "unsuccessful attempt to create an access watchpoint"
>>> @@ -36,5 +45,14 @@ gdb_test "rwatch b" \
>>>       "Can't set read/access watchpoint when hardware watchpoints are 
>>> disabled." \
>>>       "unsuccessful attempt to create a read watchpoint"
>>> +gdb_test_multiple "maint info break" "maint info break after" {
>>> +    -re -wrap "watchpoint.*" {
>>> +    fail $gdb_test_name
>>> +    }
>>> +    -re -wrap "" {
>>> +    pass $gdb_test_name
>>> +    }
>>> +}
>>> +
>>>   # The program continues until termination.
>>>   gdb_continue_to_end
>>>
>>> base-commit: 28c3bf55f0f9aca8619c6d01be34a02a887c5577
>>
>
  
Luis Machado Oct. 10, 2024, 11:03 a.m. UTC | #4
On 10/10/24 11:42, Tom de Vries wrote:
> On 9/23/24 10:15, Tom de Vries wrote:
>> On 9/4/24 09:38, Tom de Vries wrote:
>>> On 8/21/24 13:09, Tom de Vries wrote:
>>>> On arm-linux, with test-case gdb.base/scope-hw-watch-disable.exp I run into:
>>>> ...
>>>> (gdb) awatch a^M
>>>> Can't set read/access watchpoint when hardware watchpoints are disabled.^M
>>>> (gdb) PASS: $exp: unsuccessful attempt to create an access watchpoint
>>>> rwatch b^M
>>>> Can't set read/access watchpoint when hardware watchpoints are disabled.^M
>>>> (gdb) PASS: $exp: unsuccessful attempt to create a read watchpoint
>>>> continue^M
>>>> Continuing.^M
>>>> ^M
>>>> Program received signal SIGSEGV, Segmentation fault.^M
>>>> 0xf7ec82c8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6^M
>>>> (gdb) FAIL: $exp: continue until exit
>>>> ...
>>>>
>>>> Using "maint info break", we can see that the two failed attempts to set a
>>>> watchpoint each left behind a stale "watchpoint scope" breakpoint:
>>>> ...
>>>> -5      watchpoint scope del  y   0xf7ec569a  inf 1
>>>> -5.1                          y   0xf7ec569a  inf 1
>>>>     stop only in stack frame at 0xfffef4f8
>>>> -6      watchpoint scope del  y   0xf7ec569a  inf 1
>>>> -6.1                          y   0xf7ec569a  inf 1
>>>>     stop only in stack frame at 0xfffef4f8
>>>> ...
>>>>
>>>> The SIGSEGV is a consequence of the stale "watchpoint scope" breakpoint: the
>>>> same happens if we:
>>>> - have can-use-hw-watchpoints == 1,
>>>> - set one of the watchpoints, and
>>>> - continue to exit.
>>>> The problem is missing symbol info on libc which is supposed to tell which
>>>> code is thumb.  After doing "set arm fallback-mode thumb" the SIGSEGV
>>>> disappears.
>>>>
>>>> Extend the test-case to check the "maint info break" command before and after
>>>> the two failed attempts, to make sure that we catch the stale
>>>> "watchpoint scope" breakpoints also on x86_64-linux.
>>>>
>>>> Fix this in watch_command_1 by moving creation of the "watchpoint scope"
>>>> breakpoint after the call to update_watchpoint.
>>>>
>>>
>>
>> Ping^2.
>>
> 
> I've pushed this.
> 
> Thanks,
> - Tom
> 
>>>> Tested on x86_64-linux.
>>>>
>>>> PR breakpoints/31860
>>>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31860
>>>> ---
>>>>   gdb/breakpoint.c                              | 100 ++++++++++--------
>>>>   .../gdb.base/scope-hw-watch-disable.exp       |  18 ++++
>>>>   2 files changed, 71 insertions(+), 47 deletions(-)
>>>>
>>>> diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
>>>> index 17bd627f867..6d8610d14e7 100644
>>>> --- a/gdb/breakpoint.c
>>>> +++ b/gdb/breakpoint.c
>>>> @@ -10504,45 +10504,6 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>>>>        'wp_frame'.  */
>>>>     frame_id watchpoint_frame = get_frame_id (wp_frame);
>>>> -  /* If the expression is "local", then set up a "watchpoint scope"
>>>> -     breakpoint at the point where we've left the scope of the watchpoint
>>>> -     expression.  Create the scope breakpoint before the watchpoint, so
>>>> -     that we will encounter it first in bpstat_stop_status.  */
>>>> -  if (exp_valid_block != NULL && wp_frame != NULL)
>>>> -    {
>>>> -      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
>>>> -
>>>> -      if (frame_id_p (caller_frame_id))
>>>> -    {
>>>> -      gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
>>>> -      CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
>>>> -
>>>> -      scope_breakpoint
>>>> -        = create_internal_breakpoint (caller_arch, caller_pc,
>>>> -                      bp_watchpoint_scope);
>>>> -
>>>> -      /* create_internal_breakpoint could invalidate WP_FRAME.  */
>>>> -      wp_frame = NULL;
>>>> -
>>>> -      scope_breakpoint->enable_state = bp_enabled;
>>>> -
>>>> -      /* Automatically delete the breakpoint when it hits.  */
>>>> -      scope_breakpoint->disposition = disp_del;
>>>> -
>>>> -      /* Only break in the proper frame (help with recursion).  */
>>>> -      scope_breakpoint->frame_id = caller_frame_id;
>>>> -
>>>> -      /* Set the address at which we will stop.  */
>>>> -      bp_location &loc = scope_breakpoint->first_loc ();
>>>> -      loc.gdbarch = caller_arch;
>>>> -      loc.requested_address = caller_pc;
>>>> -      loc.address
>>>> -        = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
>>>> -                     scope_breakpoint->type,
>>>> -                     current_program_space);
>>>> -    }
>>>> -    }
>>>> -
>>>>     /* Now set up the breakpoint.  We create all watchpoints as hardware
>>>>        watchpoints here even if hardware watchpoints are turned off, a call
>>>>        to update_watchpoint later in this function will cause the type to
>>>> @@ -10613,14 +10574,6 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>>>>         w->watchpoint_thread = null_ptid;
>>>>       }
>>>> -  if (scope_breakpoint != NULL)
>>>> -    {
>>>> -      /* The scope breakpoint is related to the watchpoint.  We will
>>>> -     need to act on them together.  */
>>>> -      w->related_breakpoint = scope_breakpoint;
>>>> -      scope_breakpoint->related_breakpoint = w.get ();
>>>> -    }
>>>> -
>>>>     if (!just_location)
>>>>       value_free_to_mark (mark);
>>>> @@ -10628,7 +10581,60 @@ watch_command_1 (const char *arg, int accessflag, int from_tty,
>>>>        that should be inserted.  */
>>>>     update_watchpoint (w.get (), true /* reparse */);
>>>> +  /* If the expression is "local", then set up a "watchpoint scope"
>>>> +     breakpoint at the point where we've left the scope of the watchpoint
>>>> +     expression.  Create the scope breakpoint before the watchpoint, so
>>>> +     that we will encounter it first in bpstat_stop_status.  */
>>>> +  if (exp_valid_block != nullptr && wp_frame != nullptr)
>>>> +    {
>>>> +      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
>>>> +
>>>> +      if (frame_id_p (caller_frame_id))
>>>> +    {
>>>> +      gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
>>>> +      CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
>>>> +
>>>> +      scope_breakpoint
>>>> +        = create_internal_breakpoint (caller_arch, caller_pc,
>>>> +                      bp_watchpoint_scope);
>>>> +
>>>> +      /* create_internal_breakpoint could invalidate WP_FRAME.  */
>>>> +      wp_frame = nullptr;
>>>> +
>>>> +      scope_breakpoint->enable_state = bp_enabled;
>>>> +
>>>> +      /* Automatically delete the breakpoint when it hits.  */
>>>> +      scope_breakpoint->disposition = disp_del;
>>>> +
>>>> +      /* Only break in the proper frame (help with recursion).  */
>>>> +      scope_breakpoint->frame_id = caller_frame_id;
>>>> +
>>>> +      /* Set the address at which we will stop.  */
>>>> +      bp_location &loc = scope_breakpoint->first_loc ();
>>>> +      loc.gdbarch = caller_arch;
>>>> +      loc.requested_address = caller_pc;
>>>> +      loc.address
>>>> +        = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
>>>> +                     scope_breakpoint->type,
>>>> +                     current_program_space);
>>>> +    }
>>>> +  }
>>>> +
>>>> +  if (scope_breakpoint != nullptr)
>>>> +    {
>>>> +      /* The scope breakpoint is related to the watchpoint.  We will
>>>> +     need to act on them together.  */
>>>> +      w->related_breakpoint = scope_breakpoint;
>>>> +      scope_breakpoint->related_breakpoint = w.get ();
>>>> +    }
>>>> +
>>>> +  /* Verify that the scope breakpoint comes before the watchpoint in the
>>>> +     breakpoint chain.  */
>>>> +  gdb_assert (scope_breakpoint == nullptr
>>>> +          || &breakpoint_chain.back () == scope_breakpoint);
>>>> +  watchpoint *watchpoint_ptr = w.get ();
>>>>     install_breakpoint (internal, std::move (w), 1);
>>>> +  gdb_assert (&breakpoint_chain.back () == watchpoint_ptr);
>>>>   }
>>>>   /* Return count of debug registers needed to watch the given expression.
>>>> diff --git a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>>>> index 61137703e5c..29eb682df47 100644
>>>> --- a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>>>> +++ b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
>>>> @@ -29,6 +29,15 @@ if {![runto_main]} {
>>>>       return -1
>>>>   }
>>>> +gdb_test_multiple "maint info break" "maint info break before" {
>>>> +    -re -wrap "watchpoint.*" {
>>>> +    fail $gdb_test_name
>>>> +    }
>>>> +    -re -wrap "" {
>>>> +    pass $gdb_test_name
>>>> +    }
>>>> +}
>>>> +
>>>>   gdb_test "awatch a" \
>>>>       "Can't set read/access watchpoint when hardware watchpoints are disabled." \
>>>>       "unsuccessful attempt to create an access watchpoint"
>>>> @@ -36,5 +45,14 @@ gdb_test "rwatch b" \
>>>>       "Can't set read/access watchpoint when hardware watchpoints are disabled." \
>>>>       "unsuccessful attempt to create a read watchpoint"
>>>> +gdb_test_multiple "maint info break" "maint info break after" {
>>>> +    -re -wrap "watchpoint.*" {
>>>> +    fail $gdb_test_name
>>>> +    }
>>>> +    -re -wrap "" {
>>>> +    pass $gdb_test_name
>>>> +    }
>>>> +}
>>>> +
>>>>   # The program continues until termination.
>>>>   gdb_continue_to_end
>>>>
>>>> base-commit: 28c3bf55f0f9aca8619c6d01be34a02a887c5577
>>>
>>
> 

For the record, it looked OK to me. I remember this one, and I'm glad
that was addressed. Thanks!
  

Patch

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 17bd627f867..6d8610d14e7 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -10504,45 +10504,6 @@  watch_command_1 (const char *arg, int accessflag, int from_tty,
      'wp_frame'.  */
   frame_id watchpoint_frame = get_frame_id (wp_frame);
 
-  /* If the expression is "local", then set up a "watchpoint scope"
-     breakpoint at the point where we've left the scope of the watchpoint
-     expression.  Create the scope breakpoint before the watchpoint, so
-     that we will encounter it first in bpstat_stop_status.  */
-  if (exp_valid_block != NULL && wp_frame != NULL)
-    {
-      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
-
-      if (frame_id_p (caller_frame_id))
-	{
-	  gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
-	  CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
-
-	  scope_breakpoint
-	    = create_internal_breakpoint (caller_arch, caller_pc,
-					  bp_watchpoint_scope);
-
-	  /* create_internal_breakpoint could invalidate WP_FRAME.  */
-	  wp_frame = NULL;
-
-	  scope_breakpoint->enable_state = bp_enabled;
-
-	  /* Automatically delete the breakpoint when it hits.  */
-	  scope_breakpoint->disposition = disp_del;
-
-	  /* Only break in the proper frame (help with recursion).  */
-	  scope_breakpoint->frame_id = caller_frame_id;
-
-	  /* Set the address at which we will stop.  */
-	  bp_location &loc = scope_breakpoint->first_loc ();
-	  loc.gdbarch = caller_arch;
-	  loc.requested_address = caller_pc;
-	  loc.address
-	    = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
-					 scope_breakpoint->type,
-					 current_program_space);
-	}
-    }
-
   /* Now set up the breakpoint.  We create all watchpoints as hardware
      watchpoints here even if hardware watchpoints are turned off, a call
      to update_watchpoint later in this function will cause the type to
@@ -10613,14 +10574,6 @@  watch_command_1 (const char *arg, int accessflag, int from_tty,
       w->watchpoint_thread = null_ptid;
     }
 
-  if (scope_breakpoint != NULL)
-    {
-      /* The scope breakpoint is related to the watchpoint.  We will
-	 need to act on them together.  */
-      w->related_breakpoint = scope_breakpoint;
-      scope_breakpoint->related_breakpoint = w.get ();
-    }
-
   if (!just_location)
     value_free_to_mark (mark);
 
@@ -10628,7 +10581,60 @@  watch_command_1 (const char *arg, int accessflag, int from_tty,
      that should be inserted.  */
   update_watchpoint (w.get (), true /* reparse */);
 
+  /* If the expression is "local", then set up a "watchpoint scope"
+     breakpoint at the point where we've left the scope of the watchpoint
+     expression.  Create the scope breakpoint before the watchpoint, so
+     that we will encounter it first in bpstat_stop_status.  */
+  if (exp_valid_block != nullptr && wp_frame != nullptr)
+    {
+      frame_id caller_frame_id = frame_unwind_caller_id (wp_frame);
+
+      if (frame_id_p (caller_frame_id))
+	{
+	  gdbarch *caller_arch = frame_unwind_caller_arch (wp_frame);
+	  CORE_ADDR caller_pc = frame_unwind_caller_pc (wp_frame);
+
+	  scope_breakpoint
+	    = create_internal_breakpoint (caller_arch, caller_pc,
+					  bp_watchpoint_scope);
+
+	  /* create_internal_breakpoint could invalidate WP_FRAME.  */
+	  wp_frame = nullptr;
+
+	  scope_breakpoint->enable_state = bp_enabled;
+
+	  /* Automatically delete the breakpoint when it hits.  */
+	  scope_breakpoint->disposition = disp_del;
+
+	  /* Only break in the proper frame (help with recursion).  */
+	  scope_breakpoint->frame_id = caller_frame_id;
+
+	  /* Set the address at which we will stop.  */
+	  bp_location &loc = scope_breakpoint->first_loc ();
+	  loc.gdbarch = caller_arch;
+	  loc.requested_address = caller_pc;
+	  loc.address
+	    = adjust_breakpoint_address (loc.gdbarch, loc.requested_address,
+					 scope_breakpoint->type,
+					 current_program_space);
+	}
+  }
+
+  if (scope_breakpoint != nullptr)
+    {
+      /* The scope breakpoint is related to the watchpoint.  We will
+	 need to act on them together.  */
+      w->related_breakpoint = scope_breakpoint;
+      scope_breakpoint->related_breakpoint = w.get ();
+    }
+
+  /* Verify that the scope breakpoint comes before the watchpoint in the
+     breakpoint chain.  */
+  gdb_assert (scope_breakpoint == nullptr
+	      || &breakpoint_chain.back () == scope_breakpoint);
+  watchpoint *watchpoint_ptr = w.get ();
   install_breakpoint (internal, std::move (w), 1);
+  gdb_assert (&breakpoint_chain.back () == watchpoint_ptr);
 }
 
 /* Return count of debug registers needed to watch the given expression.
diff --git a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
index 61137703e5c..29eb682df47 100644
--- a/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
+++ b/gdb/testsuite/gdb.base/scope-hw-watch-disable.exp
@@ -29,6 +29,15 @@  if {![runto_main]} {
     return -1
 }
 
+gdb_test_multiple "maint info break" "maint info break before" {
+    -re -wrap "watchpoint.*" {
+	fail $gdb_test_name
+    }
+    -re -wrap "" {
+	pass $gdb_test_name
+    }
+}
+
 gdb_test "awatch a" \
     "Can't set read/access watchpoint when hardware watchpoints are disabled." \
     "unsuccessful attempt to create an access watchpoint"
@@ -36,5 +45,14 @@  gdb_test "rwatch b" \
     "Can't set read/access watchpoint when hardware watchpoints are disabled." \
     "unsuccessful attempt to create a read watchpoint"
 
+gdb_test_multiple "maint info break" "maint info break after" {
+    -re -wrap "watchpoint.*" {
+	fail $gdb_test_name
+    }
+    -re -wrap "" {
+	pass $gdb_test_name
+    }
+}
+
 # The program continues until termination.
 gdb_continue_to_end