Really fail inserting software breakpoints on read-only regions
authorPedro Alves <palves@redhat.com>
Wed, 1 Oct 2014 22:31:55 +0000 (23:31 +0100)
committerPedro Alves <palves@redhat.com>
Wed, 1 Oct 2014 22:31:55 +0000 (23:31 +0100)
Currently, with "set breakpoint auto-hw off", we'll still try to
insert a software breakpoint at addresses covered by supposedly
read-only or inacessible regions:

 (top-gdb) mem 0x443000 0x450000 ro
 (top-gdb) set mem inaccessible-by-default off
 (top-gdb) disassemble
 Dump of assembler code for function main:
    0x0000000000443956 <+34>:    movq   $0x0,0x10(%rax)
 => 0x000000000044395e <+42>:    movq   $0x0,0x18(%rax)
    0x0000000000443966 <+50>:    mov    -0x24(%rbp),%eax
    0x0000000000443969 <+53>:    mov    %eax,-0x20(%rbp)
 End of assembler dump.
 (top-gdb) b *0x0000000000443969
 Breakpoint 5 at 0x443969: file ../../src/gdb/gdb.c, line 29.
 (top-gdb) c
 Continuing.
 warning: cannot set software breakpoint at readonly address 0x443969

 Breakpoint 5, 0x0000000000443969 in main (argc=1, argv=0x7fffffffd918) at ../../src/gdb/gdb.c:29
 29        args.argc = argc;
 (top-gdb)

We warn, saying that the insertion can't be done, but then proceed
attempting the insertion anyway, and in case of manually added
regions, the insert actually succeeds.

This is a regression; GDB used to fail inserting the breakpoint.  More
below.

I stumbled on this as I wrote a test that manually sets up a read-only
memory region with the "mem" command, in order to test GDB's behavior
with breakpoints set on read-only regions, even when the real memory
the breakpoints are set at isn't really read-only.  I wanted that in
order to add a test that exercises software single-stepping through
read-only regions.

Note that the memory regions that target_memory_map returns aren't
like e.g., what would expect to see in /proc/PID/maps on Linux.
Instead, they're the physical memory map from the _debuggers_
perspective.  E.g., a read-only region would be real ROM or flash
memory, while a read-only+execute mapping in /proc/PID/maps is still
read-write to the debugger (otherwise the debugger wouldn't be able to
set software breakpoints in the code segment).

If one tries to manually write to memory that falls within a memory
region that is known to be read-only, with e.g., "p foo = 1", then we
hit a check in memory_xfer_partial_1 before the write mananges to make
it to the target side.

But writing a software/memory breakpoint nowadays goes through
target_write_raw_memory, and unlike when writing memory with
TARGET_OBJECT_MEMORY, nothing on the TARGET_OBJECT_RAW_MEMORY path
checks whether we're trying to write to a read-only region.

At the time "breakpoint auto-hw" was added, we didn't have the
TARGET_OBJECT_MEMORY vs TARGET_OBJECT_RAW_MEMORY target object
distinction yet, and the code path in memory_xfer_partial that blocks
writes to read-only memory was hit for memory breakpoints too.  With
GDB 6.8 we had:

 warning: cannot set software breakpoint at readonly address 0000000000443943
 Warning:
 Cannot insert breakpoint 1.
 Error accessing memory address 0x443943: Input/output error.

So I started out by fixing this by adding the memory region validation
to TARGET_OBJECT_RAW_MEMORY too.

But later, when testing against GDBserver, I realized that that would
only block software/memory breakpoints GDB itself inserts with
gdb/mem-break.c.  If a target has a to_insert_breakpoint method, the
insertion request will still pass through to the target.  So I ended
up converting the "cannot set breakpoint" warning in breakpoint.c to a
real error return, thus blocking the insertion sooner.

With that, we'll end up no longer needing the TARGET_OBJECT_RAW_MEMORY
changes once software single-step breakpoints are converted to real
breakpoints.  We need them today as software single-step breakpoints
bypass insert_bp_location.  But, it'll be best to leave that in as
safeguard anyway, for other direct uses of TARGET_OBJECT_RAW_MEMORY.

Tested on x86_64 Fedora 20, native and gdbserver.

gdb/
2014-10-01  Pedro Alves  <palves@redhat.com>

* breakpoint.c (insert_bp_location): Error out if inserting a
software breakpoint at a read-only address.
* target.c (memory_xfer_check_region): New function, factored out
from ...
(memory_xfer_partial_1): ... this.  Make the 'reg_len' local a
ULONGEST.
(target_xfer_partial) <TARGET_OBJECT_RAW_MEMORY>: Check the access
against the memory region attributes.

gdb/testsuite/
2014-10-01  Pedro Alves  <palves@redhat.com>

* gdb.base/breakpoint-in-ro-region.c: New file.
* gdb.base/breakpoint-in-ro-region.exp: New file.

gdb/ChangeLog
gdb/breakpoint.c
gdb/target.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.base/breakpoint-in-ro-region.c [new file with mode: 0644]
gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp [new file with mode: 0644]

index 8a268c5..e595386 100644 (file)
@@ -1,3 +1,14 @@
+2014-10-01  Pedro Alves  <palves@redhat.com>
+
+       * breakpoint.c (insert_bp_location): Error out if inserting a
+       software breakpoint at a read-only address.
+       * target.c (memory_xfer_check_region): New function, factored out
+       from ...
+       (memory_xfer_partial_1): ... this.  Make the 'reg_len' local a
+       ULONGEST.
+       (target_xfer_partial) <TARGET_OBJECT_RAW_MEMORY>: Check the access
+       against the memory region attributes.
+
 2014-10-01  Simon Marchi  <simon.marchi@ericsson.com>
 
        * NEWS: Announce new exit-code field in -list-thread-groups
index bec7f68..44f6f14 100644 (file)
@@ -2674,10 +2674,16 @@ insert_bp_location (struct bp_location *bl,
                    }
                }
              else if (bl->loc_type == bp_loc_software_breakpoint
-                      && mr->attrib.mode != MEM_RW)        
-               warning (_("cannot set software breakpoint "
-                          "at readonly address %s"),
-                        paddress (bl->gdbarch, bl->address));
+                      && mr->attrib.mode != MEM_RW)
+               {
+                 fprintf_unfiltered (tmp_error_stream,
+                                     _("Cannot insert breakpoint %d.\n"
+                                       "Cannot set software breakpoint "
+                                       "at read-only address %s\n"),
+                                     bl->owner->number,
+                                     paddress (bl->gdbarch, bl->address));
+                 return 1;
+               }
            }
        }
         
index c1b5182..4606d17 100644 (file)
@@ -909,6 +909,59 @@ target_section_by_addr (struct target_ops *target, CORE_ADDR addr)
   return NULL;
 }
 
+
+/* Helper for the memory xfer routines.  Checks the attributes of the
+   memory region of MEMADDR against the read or write being attempted.
+   If the access is permitted returns true, otherwise returns false.
+   REGION_P is an optional output parameter.  If not-NULL, it is
+   filled with a pointer to the memory region of MEMADDR.  REG_LEN
+   returns LEN trimmed to the end of the region.  This is how much the
+   caller can continue requesting, if the access is permitted.  A
+   single xfer request must not straddle memory region boundaries.  */
+
+static int
+memory_xfer_check_region (gdb_byte *readbuf, const gdb_byte *writebuf,
+                         ULONGEST memaddr, ULONGEST len, ULONGEST *reg_len,
+                         struct mem_region **region_p)
+{
+  struct mem_region *region;
+
+  region = lookup_mem_region (memaddr);
+
+  if (region_p != NULL)
+    *region_p = region;
+
+  switch (region->attrib.mode)
+    {
+    case MEM_RO:
+      if (writebuf != NULL)
+       return 0;
+      break;
+
+    case MEM_WO:
+      if (readbuf != NULL)
+       return 0;
+      break;
+
+    case MEM_FLASH:
+      /* We only support writing to flash during "load" for now.  */
+      if (writebuf != NULL)
+       error (_("Writing to flash memory forbidden in this context"));
+      break;
+
+    case MEM_NONE:
+      return 0;
+    }
+
+  /* region->hi == 0 means there's no upper bound.  */
+  if (memaddr + len < region->hi || region->hi == 0)
+    *reg_len = len;
+  else
+    *reg_len = region->hi - memaddr;
+
+  return 1;
+}
+
 /* Read memory from more than one valid target.  A core file, for
    instance, could have some of memory but delegate other bits to
    the target below it.  So, we must manually try all targets.  */
@@ -970,7 +1023,7 @@ memory_xfer_partial_1 (struct target_ops *ops, enum target_object object,
                       ULONGEST len, ULONGEST *xfered_len)
 {
   enum target_xfer_status res;
-  int reg_len;
+  ULONGEST reg_len;
   struct mem_region *region;
   struct inferior *inf;
 
@@ -1017,34 +1070,10 @@ memory_xfer_partial_1 (struct target_ops *ops, enum target_object object,
     }
 
   /* Try GDB's internal data cache.  */
-  region = lookup_mem_region (memaddr);
-  /* region->hi == 0 means there's no upper bound.  */
-  if (memaddr + len < region->hi || region->hi == 0)
-    reg_len = len;
-  else
-    reg_len = region->hi - memaddr;
-
-  switch (region->attrib.mode)
-    {
-    case MEM_RO:
-      if (writebuf != NULL)
-       return TARGET_XFER_E_IO;
-      break;
 
-    case MEM_WO:
-      if (readbuf != NULL)
-       return TARGET_XFER_E_IO;
-      break;
-
-    case MEM_FLASH:
-      /* We only support writing to flash during "load" for now.  */
-      if (writebuf != NULL)
-       error (_("Writing to flash memory forbidden in this context"));
-      break;
-
-    case MEM_NONE:
-      return TARGET_XFER_E_IO;
-    }
+  if (!memory_xfer_check_region (readbuf, writebuf, memaddr, len, &reg_len,
+                                &region))
+    return TARGET_XFER_E_IO;
 
   if (!ptid_equal (inferior_ptid, null_ptid))
     inf = find_inferior_pid (ptid_get_pid (inferior_ptid));
@@ -1184,6 +1213,16 @@ target_xfer_partial (struct target_ops *ops,
                                  writebuf, offset, len, xfered_len);
   else if (object == TARGET_OBJECT_RAW_MEMORY)
     {
+      /* Skip/avoid accessing the target if the memory region
+        attributes block the access.  Check this here instead of in
+        raw_memory_xfer_partial as otherwise we'd end up checking
+        this twice in the case of the memory_xfer_partial path is
+        taken; once before checking the dcache, and another in the
+        tail call to raw_memory_xfer_partial.  */
+      if (!memory_xfer_check_region (readbuf, writebuf, offset, len, &len,
+                                    NULL))
+       return TARGET_XFER_E_IO;
+
       /* Request the normal memory object from other layers.  */
       retval = raw_memory_xfer_partial (ops, readbuf, writebuf, offset, len,
                                        xfered_len);
index cd17e5d..4580eb2 100644 (file)
@@ -1,3 +1,8 @@
+2014-10-01  Pedro Alves  <palves@redhat.com>
+
+       * gdb.base/breakpoint-in-ro-region.c: New file.
+       * gdb.base/breakpoint-in-ro-region.exp: New file.
+
 2014-10-01  Simon Marchi  <simon.marchi@ericsson.com>
 
        * gdb.mi/mi-exit-code.exp: New file.
diff --git a/gdb/testsuite/gdb.base/breakpoint-in-ro-region.c b/gdb/testsuite/gdb.base/breakpoint-in-ro-region.c
new file mode 100644 (file)
index 0000000..696a67d
--- /dev/null
@@ -0,0 +1,28 @@
+/* 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/>.  */
+
+volatile int i;
+
+int
+main (void)
+{
+  i = 0;
+  i = 0;
+  i = 0;
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp b/gdb/testsuite/gdb.base/breakpoint-in-ro-region.exp
new file mode 100644 (file)
index 0000000..8eacefa
--- /dev/null
@@ -0,0 +1,142 @@
+# 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/>.
+
+# This file is part of the gdb testsuite
+
+standard_testfile
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } {
+    return -1
+}
+
+if ![runto main] {
+    return -1
+}
+
+delete_breakpoints
+
+# Get the bounds of a function, and write them to FUNC_LO (inclusive),
+# FUNC_HI (exclusive).  Return true on success and false on failure.
+proc get_function_bounds {function func_lo func_hi} {
+    global gdb_prompt
+    global hex decimal
+
+    upvar $func_lo lo
+    upvar $func_hi hi
+
+    set lo ""
+    set size ""
+
+    set test "get lo address of $function"
+    gdb_test_multiple "disassemble $function" $test {
+       -re "($hex) .*$hex <\\+($decimal)>:\[^\r\n\]+\r\nEnd of assembler dump\.\r\n$gdb_prompt $" {
+           set lo $expect_out(1,string)
+           set size $expect_out(2,string)
+           pass $test
+       }
+    }
+
+    if { $lo == "" || $size == "" } {
+       return false
+    }
+
+    # Account for the size of the last instruction.
+    set test "get hi address of $function"
+    gdb_test_multiple "x/2i $function+$size" $test {
+       -re ".*$hex <$function\\+$size>:\[^\r\n\]+\r\n\[ \]+($hex).*\.\r\n$gdb_prompt $" {
+           set hi $expect_out(1,string)
+           pass $test
+       }
+    }
+
+    if { $hi == "" } {
+       return false
+    }
+
+    # Remove unnecessary leading 0's (0x00000ADDR => 0xADDR) so we can
+    # easily do matches.  Disassemble includes leading zeros, while
+    # x/i doesn't.
+    regsub -all "0x0\+" $lo "0x" lo
+    regsub -all "0x0\+" $hi "0x" hi
+
+    return true
+}
+
+# Get the address where the thread is currently stopped.
+proc get_curr_insn {} {
+    global gdb_prompt
+    global hex
+
+    set pc ""
+    set test "get current insn"
+    gdb_test_multiple "p /x \$pc" $test {
+       -re " = ($hex)\r\n$gdb_prompt $" {
+           set pc $expect_out(1,string)
+           pass $test
+       }
+    }
+
+    return $pc
+}
+
+# Get the address of where a single-step should land.
+proc get_next_insn {} {
+    global gdb_prompt
+    global hex
+
+    set next ""
+    set test "get next insn"
+    gdb_test_multiple "x/2i \$pc" $test {
+       -re "$hex .*:\[^\r\n\]+\r\n\[ \]+($hex).*\.\r\n$gdb_prompt $" {
+           set next $expect_out(1,string)
+           pass $test
+       }
+    }
+
+    return $next
+}
+
+
+if ![get_function_bounds "main" main_lo main_hi] {
+    # Can't do the following tests if main's bounds are unknown.
+    return -1
+}
+
+# Manually create a read-only memory region that covers 'main'.
+gdb_test_no_output "mem $main_lo $main_hi ro" \
+    "create read-only mem region covering main"
+
+# So that we don't fail inserting breakpoints on addresses outside
+# main, like the internal event breakpoints.
+gdb_test_no_output "set mem inaccessible-by-default off"
+
+# So we get an immediate warning/error without needing to resume the
+# target.
+gdb_test_no_output "set breakpoint always-inserted on"
+
+# Disable the automatic fallback to HW breakpoints.  We want a
+# software breakpoint to be attempted, and to fail.
+gdb_test_no_output "set breakpoint auto-hw off"
+
+# Confirm manual writes to the read-only memory region fail.
+gdb_test "p /x *(char *) $main_lo = 1" \
+    "Cannot access memory at address $main_lo" \
+    "writing to read-only memory fails"
+
+# Ensure that inserting a software breakpoint in a known-read-only
+# region fails.
+gdb_test "break *$main_lo" \
+    "Cannot insert breakpoint .*Cannot set software breakpoint at read-only address $main_lo.*" \
+    "inserting software breakpoint in read-only memory fails"