diff mbox

"$ gdb PROGRAM" vs "(gdb) file PROGRAM" difference; warn on failure to remove breakpoint.

Message ID 1402323778-27849-1-git-send-email-palves@redhat.com
State Committed
Headers show

Commit Message

Pedro Alves June 9, 2014, 2:22 p.m. UTC
Turns out there's a difference between loading the program with "gdb
PROGRAM", vs loading it with "(gdb) file PROGRAM".  The latter results
in the objfile ending up with OBJF_USERLOADED set, while not with the
former.  (That difference seems bogus, but still that's not the point
of this patch.  We can revisit that afterwards.)

The new code that suppresses breakpoint removal errors for
add-symbol-file objects ends up being too greedy:

      /* In some cases, we might not be able to remove a breakpoint in
         a shared library that has already been removed, but we have
         not yet processed the shlib unload event.  Similarly for an
         unloaded add-symbol-file object - the user might not yet have
         had the chance to remove-symbol-file it.  shlib_disabled will
         be set if the library/object has already been removed, but
         the breakpoint hasn't been uninserted yet, e.g., after
         "nosharedlibrary" or "remove-symbol-file" with breakpoints
         always-inserted mode.  */
      if (val
          && (bl->loc_type == bp_loc_software_breakpoint
              && (bl->shlib_disabled
                  || solib_name_from_address (bl->pspace, bl->address)
                  || userloaded_objfile_contains_address_p (bl->pspace,
                                                            bl->address))))
        val = 0;

as it turns out that OBJF_USERLOADED can be set for objfiles loaded by
some other means not add-symbol-file.  In this case, symbol-file (or
"file", which is really just "exec-file"+"symbol-file").

Recall that add-symbol-file is documented as:

 (gdb) help add-symbol-file
 Load symbols from FILE, assuming FILE has been dynamically loaded.
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

And it's the "dynamically loaded" aspect that the breakpoint.c code
cares about.  So make add-symbol-file set OBJF_SHARED on its objfiles
too, and tweak the breakpoint.c code to look for OBJF_SHARED instead
of OBJF_USERLOADED.

This restores back the missing breakpoint removal warning when we let
sss-bp-on-user-bp-2.exp run on native GNU/Linux
(https://sourceware.org/ml/gdb-patches/2014-06/msg00335.html):

 (gdb) PASS: gdb.base/sss-bp-on-user-bp-2.exp: define stepi_del_break
 stepi_del_break
 warning: Error removing breakpoint 3
 (gdb) FAIL: gdb.base/sss-bp-on-user-bp-2.exp: stepi_del_break

I say "restores" because this wasGDB's behavior in 7.7 and earlier.

And, likewise, "file" with no arguments only started turning
breakpoints set in the main executable to "<pending>" with the
remote-symbol-file patch (63644780).  The old behavior is now
restored, and we break-unload-file.exp test now exercizes both "gdb;
file PROGRAM" and "gdb PROGRAM".

gdb/
2014-06-09  Pedro Alves  <palves@redhat.com>

	* breakpoint.c (insert_bp_location, remove_breakpoint_1): Adjust.
	(disable_breakpoints_in_freed_objfile): Skip objfiles that don't
	have OBJF_SHARED set.
	* objfiles.c (userloaded_objfile_contains_address_p): Rename to...
	(shared_objfile_contains_address_p): ... this.  Check OBJF_SHARED
	instead of OBJF_USERLOADED.
	* objfiles.h (OBJF_SHARED): Update comment.
	(userloaded_objfile_contains_address_p): Rename to ...
	(shared_objfile_contains_address_p): ... this, and update
	comments.
	* symfile.c (add_symbol_file_command): Also set OBJF_SHARED in the
	new objfile.
	(remove_symbol_file_command): Skip objfiles that don't have
	OBJF_SHARED set.

gdb/testsuite/
2014-06-09  Pedro Alves  <palves@redhat.com>

	* gdb.base/break-main-file-remove-fail.c: New file.
	* gdb.base/break-main-file-remove-fail.exp: New file.
	* gdb.base/break-unload-file.exp: Use build_executable instead of
	prepare_for_testing.
	(test_break): New parameter "initial_load".  Handle it.
	(top level): Add initial_load cmdline/file axis.
---
 gdb/breakpoint.c                                   |  25 ++---
 gdb/objfiles.c                                     |   6 +-
 gdb/objfiles.h                                     |  18 ++--
 gdb/symfile.c                                      |  10 +-
 .../gdb.base/break-main-file-remove-fail.c         |  46 +++++++++
 .../gdb.base/break-main-file-remove-fail.exp       | 106 +++++++++++++++++++++
 gdb/testsuite/gdb.base/break-unload-file.exp       |  71 ++++++++++----
 7 files changed, 230 insertions(+), 52 deletions(-)
 create mode 100644 gdb/testsuite/gdb.base/break-main-file-remove-fail.c
 create mode 100644 gdb/testsuite/gdb.base/break-main-file-remove-fail.exp

Comments

Tom Tromey June 11, 2014, 7:01 p.m. UTC | #1
>>>>> "Pedro" == Pedro Alves <palves@redhat.com> writes:

Pedro> Turns out there's a difference between loading the program with "gdb
Pedro> PROGRAM", vs loading it with "(gdb) file PROGRAM".  The latter results
Pedro> in the objfile ending up with OBJF_USERLOADED set, while not with the
Pedro> former.  (That difference seems bogus, but still that's not the point
Pedro> of this patch.  We can revisit that afterwards.)

[...]

Pedro> 2014-06-09  Pedro Alves  <palves@redhat.com>

Pedro> 	* breakpoint.c (insert_bp_location, remove_breakpoint_1): Adjust.
Pedro> 	(disable_breakpoints_in_freed_objfile): Skip objfiles that don't
Pedro> 	have OBJF_SHARED set.
Pedro> 	* objfiles.c (userloaded_objfile_contains_address_p): Rename to...
Pedro> 	(shared_objfile_contains_address_p): ... this.  Check OBJF_SHARED
Pedro> 	instead of OBJF_USERLOADED.
Pedro> 	* objfiles.h (OBJF_SHARED): Update comment.
Pedro> 	(userloaded_objfile_contains_address_p): Rename to ...
Pedro> 	(shared_objfile_contains_address_p): ... this, and update
Pedro> 	comments.
Pedro> 	* symfile.c (add_symbol_file_command): Also set OBJF_SHARED in the
Pedro> 	new objfile.
Pedro> 	(remove_symbol_file_command): Skip objfiles that don't have
Pedro> 	OBJF_SHARED set.

FWIW I read through this and it looks reasonable to me.

Tom
Yao Qi June 12, 2014, 3:47 a.m. UTC | #2
On 06/09/2014 10:22 PM, Pedro Alves wrote:
> And, likewise, "file" with no arguments only started turning
> breakpoints set in the main executable to "<pending>" with the
> remote-symbol-file patch (63644780).  The old behavior is now
  ^^^^^^ remove

I don't see breakpoint set in the main executable becomes "<pending>"
(with and without this patch applied), if the following steps are what
you meant.

(gdb) b main
Breakpoint 1 at 0x80484c1: file
../../../../git/gdb/testsuite/gdb.base/wchar.c, line 29.

(gdb) file
No executable file now.
Discard symbol table from
`/home/yao/Source/gnu/gdb/build-git/x86/gdb/testsuite/gdb.base/wchar'?
(y or n) y
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the
"file" command.
No symbol file now.
(gdb) info breakpoints


Num     Type           Disp Enb Address    What


1       breakpoint     keep n   0x080484c1

> restored, and we break-unload-file.exp test now exercizes both "gdb;
> file PROGRAM" and "gdb PROGRAM".

Hope this table helps the understanding to the flags setting in
different scenarios:

shared library loaded by program   OBJF_SHARED
add-symbol-file foo                OBJF_SHARED | OBJF_USERLOADED
symbol-file or file foo            OBJF_USERLOADED
./gdb foo                          Neither

> @@ -7729,18 +7729,19 @@ disable_breakpoints_in_freed_objfile (struct objfile *objfile)
>    if (objfile == NULL)
>      return;
>  
> -  /* OBJF_USERLOADED are dynamic modules manually managed by the user
> -     with add-symbol-file/remove-symbol-file.  Similarly to how
> -     breakpoints in shared libraries are handled in response to
> -     "nosharedlibrary", mark breakpoints in OBJF_USERLOADED modules
> +  /* OBJF_SHARED|OBJF_USERLOADED objfiles are dynamic modules manually
> +     managed by the user with add-symbol-file/remove-symbol-file.
> +     Similarly to how breakpoints in shared libraries are handled in
> +     response to "nosharedlibrary", mark breakpoints in such modules

The comments are clear to me...

>       shlib_disabled so they end up uninserted on the next global
>       location list update.  Shared libraries not loaded by the user
>       aren't handled here -- they're already handled in
>       disable_breakpoints_in_unloaded_shlib, called by solib.c's
>       solib_unloaded observer.  We skip objfiles that are not
> -     OBJF_USERLOADED (nor OBJF_SHARED) as those aren't considered
> -     dynamic objects (e.g. the main objfile).  */
> -  if ((objfile->flags & OBJF_USERLOADED) == 0)
> +     OBJF_SHARED as those aren't considered dynamic objects (e.g. the
> +     main objfile).  */
> +  if ((objfile->flags & OBJF_SHARED) == 0
> +      || (objfile->flags & OBJF_USERLOADED) == 0)
>      return;

... these are clear too, but shouldn't we only check
"(objfile->flags & OBJF_SHARED) == 0" here? which means objfile is a
shared library loaded by program or is added by add-symbol-file can
be processed afterwards.
Pedro Alves June 12, 2014, 12:07 p.m. UTC | #3
On 06/12/2014 04:47 AM, Yao Qi wrote:
> On 06/09/2014 10:22 PM, Pedro Alves wrote:
>> And, likewise, "file" with no arguments only started turning
>> breakpoints set in the main executable to "<pending>" with the
>> remote-symbol-file patch (63644780).  The old behavior is now
>   ^^^^^^ remove
> 
> I don't see breakpoint set in the main executable becomes "<pending>"
> (with and without this patch applied), if the following steps are what
> you meant.
> 
> (gdb) b main
> Breakpoint 1 at 0x80484c1: file
> ../../../../git/gdb/testsuite/gdb.base/wchar.c, line 29.
> 
> (gdb) file
> No executable file now.
> Discard symbol table from
> `/home/yao/Source/gnu/gdb/build-git/x86/gdb/testsuite/gdb.base/wchar'?
> (y or n) y
> Error in re-setting breakpoint 1: No symbol table is loaded.  Use the
> "file" command.
> No symbol file now.
> (gdb) info breakpoints

That's with "gdb PROGRAM".  If you do "gdb -ex "file PROGRAM" instead,
you get:

$ ./gdb -q -ex "file ~/gdb/tests/main"

Reading symbols from ~/gdb/tests/main...done.
(gdb) b main
Breakpoint 1 at 0x4004cf: file main.c, line 5.
(gdb) file
No executable file now.
Discard symbol table from `/home/pedro/gdb/tests/main'? (y or n) y
No symbol file now.
(gdb) info breakpoints
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   <PENDING>          main
(gdb)

Note how currently break-unload-file.exp is expecting
the <pending>, and is passing today.  That's only because the
testsuite always uses "file PROGRAM".

The patch does:

-	gdb_test "info break" "y.*PENDING.*foo" \
-	    "breakpoint is pending"
+	# This test used to behave differently depending on whether
+	# the program was first loaded through "file PROGRAM" or "gdb
+	# PROGRAM".
+	set ws "\[ \t\]"
+	gdb_test "info break" "breakpoint${ws}+keep${ws}+n${ws}+$hex${ws}*" \
+	    "breakpoint is disabled"

... and also makes break-unload-file.exp load the PROGRAM into
GDB using both forms (runs the whole test file once for each form).
Without the code patch, that "info break" would show PENDING in one
case, but not on the other.

> shared library loaded by program   OBJF_SHARED
> add-symbol-file foo                OBJF_SHARED | OBJF_USERLOADED
> symbol-file or file foo            OBJF_USERLOADED
> ./gdb foo                          Neither

Correct.

>>       shlib_disabled so they end up uninserted on the next global
>>       location list update.  Shared libraries not loaded by the user
>>       aren't handled here -- they're already handled in
>>       disable_breakpoints_in_unloaded_shlib, called by solib.c's
>>       solib_unloaded observer.  We skip objfiles that are not
>> -     OBJF_USERLOADED (nor OBJF_SHARED) as those aren't considered
>> -     dynamic objects (e.g. the main objfile).  */
>> -  if ((objfile->flags & OBJF_USERLOADED) == 0)
>> +     OBJF_SHARED as those aren't considered dynamic objects (e.g. the
>> +     main objfile).  */
>> +  if ((objfile->flags & OBJF_SHARED) == 0
>> +      || (objfile->flags & OBJF_USERLOADED) == 0)
>>      return;
> 
> ... these are clear too, but shouldn't we only check
> "(objfile->flags & OBJF_SHARED) == 0" here? which means objfile is a
> shared library loaded by program or is added by add-symbol-file can
> be processed afterwards.

Shared libraries loaded by program (or more correctly, automatically
managed by the solib framework) are handled by
disable_breakpoints_in_unloaded_shlib, as the comment above explains:

"Shared libraries not loaded by the user aren't handled here -- they're already
handled in disable_breakpoints_in_unloaded_shlib, called by solib.c's
solib_unloaded observer."
Yao Qi June 12, 2014, 1:21 p.m. UTC | #4
OK, I have no comments then.
Pedro Alves June 12, 2014, 1:25 p.m. UTC | #5
On 06/12/2014 02:21 PM, Yao Qi wrote:
> OK, I have no comments then.

Thanks Yao.  Your reviews are very much appreciated.
Pedro Alves June 16, 2014, 2:44 p.m. UTC | #6
On 06/09/2014 03:22 PM, Pedro Alves wrote:

> I say "restores" because this was GDB's behavior in 7.7 and earlier.
> 
> And, likewise, "file" with no arguments only started turning
> breakpoints set in the main executable to "<pending>" with the
> remote-symbol-file patch (63644780).  The old behavior is now
> restored, and we break-unload-file.exp test now exercizes both "gdb;
> file PROGRAM" and "gdb PROGRAM".

I've now pushed this to both mainline and the 7.8 branch.
diff mbox

Patch

diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 23c8895..e6cda4e 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -2665,8 +2665,8 @@  insert_bp_location (struct bp_location *bl,
 	  if ((bp_err == GENERIC_ERROR || bp_err == MEMORY_ERROR)
 	      && bl->loc_type == bp_loc_software_breakpoint
 	      && (solib_name_from_address (bl->pspace, bl->address)
-		  || userloaded_objfile_contains_address_p (bl->pspace,
-							    bl->address)))
+		  || shared_objfile_contains_address_p (bl->pspace,
+							bl->address)))
 	    {
 	      /* See also: disable_breakpoints_in_shlibs.  */
 	      bl->shlib_disabled = 1;
@@ -3805,7 +3805,7 @@  remove_breakpoint_1 (struct bp_location *bl, insertion_state_t is)
 	     whether another dynamic object might have loaded over the
 	     breakpoint's address -- the user might well let us know
 	     about it next with add-symbol-file (the whole point of
-	     OBJF_USERLOADED is letting the user manually maintain a
+	     add-symbol-file is letting the user manually maintain a
 	     list of dynamically loaded objects).  If we have the
 	     breakpoint's shadow memory, that is, this is a software
 	     breakpoint managed by GDB, check whether the breakpoint
@@ -3878,8 +3878,8 @@  remove_breakpoint_1 (struct bp_location *bl, insertion_state_t is)
 	  && (bl->loc_type == bp_loc_software_breakpoint
 	      && (bl->shlib_disabled
 		  || solib_name_from_address (bl->pspace, bl->address)
-		  || userloaded_objfile_contains_address_p (bl->pspace,
-							    bl->address))))
+		  || shared_objfile_contains_address_p (bl->pspace,
+							bl->address))))
 	val = 0;
 
       if (val)
@@ -7729,18 +7729,19 @@  disable_breakpoints_in_freed_objfile (struct objfile *objfile)
   if (objfile == NULL)
     return;
 
-  /* OBJF_USERLOADED are dynamic modules manually managed by the user
-     with add-symbol-file/remove-symbol-file.  Similarly to how
-     breakpoints in shared libraries are handled in response to
-     "nosharedlibrary", mark breakpoints in OBJF_USERLOADED modules
+  /* OBJF_SHARED|OBJF_USERLOADED objfiles are dynamic modules manually
+     managed by the user with add-symbol-file/remove-symbol-file.
+     Similarly to how breakpoints in shared libraries are handled in
+     response to "nosharedlibrary", mark breakpoints in such modules
      shlib_disabled so they end up uninserted on the next global
      location list update.  Shared libraries not loaded by the user
      aren't handled here -- they're already handled in
      disable_breakpoints_in_unloaded_shlib, called by solib.c's
      solib_unloaded observer.  We skip objfiles that are not
-     OBJF_USERLOADED (nor OBJF_SHARED) as those aren't considered
-     dynamic objects (e.g. the main objfile).  */
-  if ((objfile->flags & OBJF_USERLOADED) == 0)
+     OBJF_SHARED as those aren't considered dynamic objects (e.g. the
+     main objfile).  */
+  if ((objfile->flags & OBJF_SHARED) == 0
+      || (objfile->flags & OBJF_USERLOADED) == 0)
     return;
 
   ALL_BREAKPOINTS (b)
diff --git a/gdb/objfiles.c b/gdb/objfiles.c
index 81bbf24..a86e8bc 100644
--- a/gdb/objfiles.c
+++ b/gdb/objfiles.c
@@ -1454,14 +1454,14 @@  is_addr_in_objfile (CORE_ADDR addr, const struct objfile *objfile)
 }
 
 int
-userloaded_objfile_contains_address_p (struct program_space *pspace,
-				       CORE_ADDR address)
+shared_objfile_contains_address_p (struct program_space *pspace,
+				   CORE_ADDR address)
 {
   struct objfile *objfile;
 
   ALL_PSPACE_OBJFILES (pspace, objfile)
     {
-      if ((objfile->flags & OBJF_USERLOADED) != 0
+      if ((objfile->flags & OBJF_SHARED) != 0
 	  && is_addr_in_objfile (address, objfile))
 	return 1;
     }
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 6684278..57a94e1 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -427,12 +427,9 @@  struct objfile
 #define OBJF_REORDERED	(1 << 0)	/* Functions are reordered */
 
 /* Distinguish between an objfile for a shared library and a "vanilla"
-   objfile.  (If not set, the objfile may still actually be a solib.
-   This can happen if the user created the objfile by using the
-   add-symbol-file command.  GDB doesn't in that situation actually
-   check whether the file is a solib.  Rather, the target's
-   implementation of the solib interface is responsible for setting
-   this flag when noticing solibs used by an inferior.)  */
+   objfile.  This may come from a target's implementation of the solib
+   interface, from add-symbol-file, or any other mechanism that loads
+   dynamic objects.  */
 
 #define OBJF_SHARED     (1 << 1)	/* From a shared library */
 
@@ -515,12 +512,11 @@  extern void objfiles_changed (void);
 
 extern int is_addr_in_objfile (CORE_ADDR addr, const struct objfile *objfile);
 
-/* Return true if ADDRESS maps into one of the sections of the
-   userloaded ("add-symbol-file") objfiles of PSPACE and false
-   otherwise.  */
+/* Return true if ADDRESS maps into one of the sections of a
+   OBJF_SHARED objfile of PSPACE and false otherwise.  */
 
-extern int userloaded_objfile_contains_address_p (struct program_space *pspace,
-						  CORE_ADDR address);
+extern int shared_objfile_contains_address_p (struct program_space *pspace,
+					      CORE_ADDR address);
 
 /* This operation deletes all objfile entries that represent solibs that
    weren't explicitly loaded by the user, via e.g., the add-symbol-file
diff --git a/gdb/symfile.c b/gdb/symfile.c
index 7ad4a44..5b94797 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -2262,7 +2262,7 @@  add_symbol_file_command (char *args, int from_tty)
 {
   struct gdbarch *gdbarch = get_current_arch ();
   char *filename = NULL;
-  int flags = OBJF_USERLOADED;
+  int flags = OBJF_USERLOADED | OBJF_SHARED;
   char *arg;
   int section_index = 0;
   int argcnt = 0;
@@ -2445,8 +2445,8 @@  remove_symbol_file_command (char *args, int from_tty)
 
       ALL_OBJFILES (objf)
 	{
-	  if (objf != 0
-	      && objf->flags & OBJF_USERLOADED
+	  if ((objf->flags & OBJF_USERLOADED) != 0
+	      && (objf->flags & OBJF_SHARED) != 0
 	      && objf->pspace == pspace && is_addr_in_objfile (addr, objf))
 	    break;
 	}
@@ -2464,8 +2464,8 @@  remove_symbol_file_command (char *args, int from_tty)
 
       ALL_OBJFILES (objf)
 	{
-	  if (objf != 0
-	      && objf->flags & OBJF_USERLOADED
+	  if ((objf->flags & OBJF_USERLOADED) != 0
+	      && (objf->flags & OBJF_SHARED) != 0
 	      && objf->pspace == pspace
 	      && filename_cmp (filename, objfile_name (objf)) == 0)
 	    break;
diff --git a/gdb/testsuite/gdb.base/break-main-file-remove-fail.c b/gdb/testsuite/gdb.base/break-main-file-remove-fail.c
new file mode 100644
index 0000000..8d89b6b
--- /dev/null
+++ b/gdb/testsuite/gdb.base/break-main-file-remove-fail.c
@@ -0,0 +1,46 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2014 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 <unistd.h>
+#include <sys/mman.h>
+
+size_t pg_size;
+
+void
+start (void)
+{
+}
+
+void
+foo (void)
+{
+}
+
+int
+main (void)
+{
+  pg_size = getpagesize ();
+
+  /* This just makes sure the test fails to compile (and is therefore
+     skipped) on targets that don't have munmap.  */
+  munmap (0, 0);
+
+  start ();
+  foo ();
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/break-main-file-remove-fail.exp b/gdb/testsuite/gdb.base/break-main-file-remove-fail.exp
new file mode 100644
index 0000000..395cf97
--- /dev/null
+++ b/gdb/testsuite/gdb.base/break-main-file-remove-fail.exp
@@ -0,0 +1,106 @@ 
+# Copyright 2014 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/>.  */
+
+# Test that GDB isn't silent if it fails to remove a breakpoint from
+# the main program, independently of whether the program was loaded
+# with "file PROGRAM" or directly from the command line with "gdb
+# PROGRAM".
+
+standard_testfile
+
+if {[build_executable "failed to prepare" $testfile $srcfile debug]} {
+    return -1
+}
+
+# Run the test proper.  INITIAL_LOAD determines whether the program is
+# initially loaded by the "file" command or by passing it to GDB on
+# the command line.
+proc test_remove_bp { initial_load } {
+    with_test_prefix "$initial_load" {
+	global srcdir subdir binfile
+	global gdb_prompt hex
+	global GDBFLAGS
+
+	gdb_exit
+
+	set saved_gdbflags $GDBFLAGS
+
+	# See "used to behave differently" further below.
+	if { $initial_load == "file" } {
+	    gdb_start
+	    gdb_file_cmd $binfile
+	} else {
+	    global last_loaded_file
+
+	    # gdb_file_cmd sets this.  This is what gdb_reload
+	    # implementations use as binary.
+	    set last_loaded_file $binfile
+
+	    set GDBFLAGS "$GDBFLAGS $binfile"
+	    gdb_start
+	}
+	gdb_reinitialize_dir $srcdir/$subdir
+	gdb_reload
+	set GDBFLAGS $saved_gdbflags
+
+	if ![runto start] {
+	    fail "Can't run to start"
+	    return
+	}
+
+	delete_breakpoints
+
+	# So we can easily control when are breakpoints removed.
+	gdb_test_no_output "set breakpoint always-inserted on"
+
+	set bp_addr ""
+
+	set test "break foo"
+	gdb_test_multiple $test $test {
+	    -re "Breakpoint .* at ($hex).*$gdb_prompt $" {
+		set bp_addr $expect_out(1,string)
+		pass $test
+	    }
+	}
+
+	if {$bp_addr == ""} {
+	    unsupported "can't extract foo's address"
+	    return
+	}
+
+	gdb_test "info break" "y.*$hex.*in foo at.*" \
+	    "breakpoint is set"
+
+	# Now unmap the page where the breakpoint is set.  Trying to
+	# remove the memory breakpoint afterwards should fail, and GDB
+	# should warn the user about it.
+	set pagesize [get_integer_valueof "pg_size" 0]
+	set align_addr [expr $bp_addr - $bp_addr % $pagesize]
+	set munmap [get_integer_valueof "munmap ($align_addr, $pagesize)" -1]
+
+	if {$munmap != 0} {
+	    unsupported "can't munmap foo's page"
+	    return
+	}
+
+	gdb_test "delete \$bpnum" \
+	    "warning: Error removing breakpoint .*" \
+	    "failure to remove breakpoint warns"
+    }
+}
+
+foreach initial_load { "cmdline" "file" } {
+    test_remove_bp $initial_load
+}
diff --git a/gdb/testsuite/gdb.base/break-unload-file.exp b/gdb/testsuite/gdb.base/break-unload-file.exp
index 342bcf5..20762bb 100644
--- a/gdb/testsuite/gdb.base/break-unload-file.exp
+++ b/gdb/testsuite/gdb.base/break-unload-file.exp
@@ -18,24 +18,46 @@ 
 
 standard_testfile
 
-if {[prepare_for_testing "failed to prepare" $testfile $srcfile debug]} {
+if {[build_executable "failed to prepare" $testfile $srcfile debug]} {
     return -1
 }
 
-if ![runto_main] then {
-    fail "Can't run to main"
-    return 0
-}
-
-# Run the test proper.  ALWAYS_INSERT determines whether
-# always-inserted mode is on/off, and BREAK_COMMAND is the break
-# command being tested.
+# Run the test proper.  INITIAL_LOAD determines whether the program is
+# initially loaded by the "file" command or by passing it to GDB on
+# the command line.  ALWAYS_INSERT determines whether always-inserted
+# mode is on/off.  BREAK_COMMAND is the break command being tested.
 #
-proc test_break { always_inserted break_command } {
-    global gdb_prompt binfile hex
+proc test_break { initial_load always_inserted break_command } {
+    global srcdir subdir binfile
+    global gdb_prompt hex
+    global GDBFLAGS
+
+    append prefix "$initial_load: "
+    append prefix "always-inserted $always_inserted: "
+    append prefix "$break_command"
+    with_test_prefix "$prefix" {
+	gdb_exit
+
+	set saved_gdbflags $GDBFLAGS
+
+	# See "used to behave differently" further below.
+	if { $initial_load == "file" } {
+	    gdb_start
+	    gdb_file_cmd $binfile
+	} else {
+	    global last_loaded_file
+
+	    # gdb_file_cmd sets this.  This is what gdb_reload
+	    # implementations use as binary.
+	    set last_loaded_file $binfile
+
+	    set GDBFLAGS "$GDBFLAGS $binfile"
+	    gdb_start
+	}
 
-    with_test_prefix "always-inserted $always_inserted: $break_command" {
-	clean_restart $binfile
+	gdb_reinitialize_dir $srcdir/$subdir
+	gdb_reload
+	set GDBFLAGS $saved_gdbflags
 
 	if ![runto_main] then {
 	    fail "Can't run to main"
@@ -88,8 +110,12 @@  proc test_break { always_inserted break_command } {
 	    }
 	}
 
-	gdb_test "info break" "y.*PENDING.*foo" \
-	    "breakpoint is pending"
+	# This test used to behave differently depending on whether
+	# the program was first loaded through "file PROGRAM" or "gdb
+	# PROGRAM".
+	set ws "\[ \t\]"
+	gdb_test "info break" "breakpoint${ws}+keep${ws}+n${ws}+$hex${ws}*" \
+	    "breakpoint is disabled"
 
 	# Now delete the breakpoint from GDB's tables, to make sure
 	# GDB doesn't reinsert it, masking the bug (with the bug, on
@@ -118,11 +144,14 @@  proc test_break { always_inserted break_command } {
     }
 }
 
-# While it doesn't trigger the original bug this is a regression test
-# for, test with breakpoint always-inserted off for extra coverage.
-foreach always_inserted { "off" "on" } {
-    test_break $always_inserted "break"
-    if {![skip_hw_breakpoint_tests]} {
-	test_break $always_inserted "hbreak"
+foreach initial_load { "cmdline" "file" } {
+    # While it doesn't trigger the original bug this is a regression
+    # test for, test with breakpoint always-inserted off for extra
+    # coverage.
+    foreach always_inserted { "off" "on" } {
+	test_break $initial_load $always_inserted "break"
+	if {![skip_hw_breakpoint_tests]} {
+	    test_break $initial_load $always_inserted "hbreak"
+	}
     }
 }