diff --git a/gdb/fbsd-nat.c b/gdb/fbsd-nat.c index 706d6efd342..ecd0df95fc7 100644 --- a/gdb/fbsd-nat.c +++ b/gdb/fbsd-nat.c @@ -186,7 +186,7 @@ fbsd_nat_target::find_memory_regions (find_memory_region_ftype func) Pass MODIFIED as true, we do not know the real modification state. */ func (kve->kve_start, size, kve->kve_protection & KVME_PROT_READ, kve->kve_protection & KVME_PROT_WRITE, - kve->kve_protection & KVME_PROT_EXEC, true, false); + kve->kve_protection & KVME_PROT_EXEC, true, false, false); } return true; } diff --git a/gdb/find-memory-region.h b/gdb/find-memory-region.h index 23776caebb2..85f4a4acd0f 100644 --- a/gdb/find-memory-region.h +++ b/gdb/find-memory-region.h @@ -27,11 +27,14 @@ MEMORY_TAGGED is true if the memory region contains memory tags, false otherwise. + HOLE is true if the memory region is known to be all zeroes, false + otherwise. + Return true on success, false otherwise. */ using find_memory_region_ftype = gdb::function_view; + bool memory_tagged, bool hole)>; #endif /* GDB_FIND_MEMORY_REGION_H */ diff --git a/gdb/gcore.c b/gdb/gcore.c index 149ade2a08f..e50115370c7 100644 --- a/gdb/gcore.c +++ b/gdb/gcore.c @@ -393,20 +393,23 @@ make_output_phdrs (bfd *obfd, asection *osec) bfd_record_phdr (obfd, p_type, 1, p_flags, 0, 0, 0, 0, 1, &osec); } -/* find_memory_region_ftype implementation. +/* Helper for gcore_memory_sections's gdbarch_find_memory_regions + find_memory_region_ftype callback. - MEMORY_TAGGED is true if the memory region contains memory tags, false - otherwise. + Extra arguments compared to find_memory_region_ftype: OBFD is the core file GDB is creating. */ static bool gcore_create_callback (CORE_ADDR vaddr, unsigned long size, bool read, bool write, bool exec, bool modified, - bool memory_tagged, bfd *obfd) + bool memory_tagged, bool hole, bfd *obfd) { asection *osec; - flagword flags = SEC_ALLOC | SEC_HAS_CONTENTS | SEC_LOAD; + flagword flags = SEC_ALLOC; + + if (!hole) + flags |= SEC_HAS_CONTENTS | SEC_LOAD; /* If the memory segment has no permissions set, ignore it, otherwise when we later try to access it for read/write, we'll get an error @@ -421,7 +424,7 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, bool read, return true; } - if (!write && !modified && !solib_keep_data_in_core (vaddr, size)) + if (!hole && !write && !modified && !solib_keep_data_in_core (vaddr, size)) { /* See if this region of memory lies inside a known file on disk. If so, we can avoid copying its contents by clearing SEC_LOAD. */ @@ -482,10 +485,11 @@ gcore_create_callback (CORE_ADDR vaddr, unsigned long size, bool read, return true; } -/* gdbarch_find_memory_regions callback for creating a memory tag section. +/* Helper for gcore_memory_sections's gdbarch_find_memory_regions + find_memory_region_ftype callback, for creating a memory tag + section. - MEMORY_TAGGED is true if the memory region contains memory tags, false - otherwise. + Extra arguments compared to find_memory_region_ftype: OBFD is the core file GDB is creating. */ @@ -493,7 +497,7 @@ static bool gcore_create_memtag_section_callback (CORE_ADDR vaddr, unsigned long size, bool read, bool write, bool exec, bool modified, bool memory_tagged, - bfd *obfd) + bool hole, bfd *obfd) { /* Are there memory tags in this particular memory map entry? */ if (!memory_tagged) @@ -550,7 +554,8 @@ objfile_find_memory_regions (struct target_ops *self, (flags & SEC_READONLY) == 0, /* Writable. */ (flags & SEC_CODE) != 0, /* Executable. */ true, /* MODIFIED is unknown, pass it as true. */ - false /* No memory tags in the object file. */); + false, /* No memory tags in the object file. */ + false /* Not known to be an all-zeroes hole. */); if (!ret) return ret; } @@ -563,7 +568,8 @@ objfile_find_memory_regions (struct target_ops *self, true, /* Stack section will be writable. */ false, /* Stack section will not be executable. */ true, /* Stack section will be modified. */ - false /* No memory tags in the object file. */)) + false, /* No memory tags in the object file. */ + false /* Not known to be an all-zeroes hole. */)) return false; /* Make a heap segment. */ @@ -574,7 +580,8 @@ objfile_find_memory_regions (struct target_ops *self, true, /* Heap section will be writable. */ false, /* Heap section will not be executable. */ true, /* Heap section will be modified. */ - false /* No memory tags in the object file. */)) + false, /* No memory tags in the object file. */ + false /* Not known to be an all-zeroes hole. */)) return false; return true; @@ -845,10 +852,10 @@ static bool gcore_memory_sections (bfd *obfd) { auto cb = [obfd] (CORE_ADDR vaddr, unsigned long size, bool read, bool write, - bool exec, bool modified, bool memory_tagged) + bool exec, bool modified, bool memory_tagged, bool hole) { return gcore_create_callback (vaddr, size, read, write, exec, modified, - memory_tagged, obfd); + memory_tagged, hole, obfd); }; /* Try gdbarch method first, then fall back to target method. */ @@ -862,11 +869,11 @@ gcore_memory_sections (bfd *obfd) auto cb_memtag = [obfd] (CORE_ADDR vaddr, unsigned long size, bool read, bool write, bool exec, bool modified, - bool memory_tagged) + bool memory_tagged, bool hole) { return gcore_create_memtag_section_callback (vaddr, size, read, write, exec, modified, - memory_tagged, obfd); + memory_tagged, hole, obfd); }; /* Take care of dumping memory tags, if there are any. */ diff --git a/gdb/gnu-nat.c b/gdb/gnu-nat.c index 9f481ba7e8b..985aa14debc 100644 --- a/gdb/gnu-nat.c +++ b/gdb/gnu-nat.c @@ -2623,7 +2623,8 @@ gnu_nat_target::find_memory_regions (find_memory_region_ftype func) last_protection & VM_PROT_WRITE, last_protection & VM_PROT_EXECUTE, true, /* MODIFIED is unknown, pass it as true. */ - false /* No memory tags in the object file. */); + false, /* No memory tags in the object file. */ + false /* Not known to be all zeroes. */); last_region_address = region_address; last_region_end = region_address += region_length; last_protection = protection; @@ -2637,7 +2638,8 @@ gnu_nat_target::find_memory_regions (find_memory_region_ftype func) last_protection & VM_PROT_WRITE, last_protection & VM_PROT_EXECUTE, true, /* MODIFIED is unknown, pass it as true. */ - false /* No memory tags in the object file. */); + false, /* No memory tags in the object file. */ + false /* Not known to be all zeroes. */); return true; } diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index 4bc5a2b6948..a7381677498 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -121,6 +121,9 @@ struct smaps_data ULONGEST inode; ULONGEST offset; + + ULONGEST rss; + ULONGEST swap; }; /* Whether to take the /proc/PID/coredump_filter into account when @@ -1425,12 +1428,46 @@ linux_core_xfer_siginfo (struct gdbarch *gdbarch, struct bfd &cbfd, } using linux_find_memory_region_ftype - = gdb::function_view; + = gdb::function_view; typedef bool linux_dump_mapping_p_ftype (filter_flags filterflags, const smaps_data &map); +/* Parse a KEY value out of a /proc/pid/smaps line. KEYWORD is the + keyword that was extracted out of the LINE we're considering. + MAPS_FILENAME is the /proc/pid/smaps filename. The result is + written to *VALUE. Returns true if LINE is a line for KEY, false + otherwise. */ + +static bool +parse_smaps_key_value (const char *keyword, const char *line, + const char *key, + const std::string &maps_filename, + ULONGEST *value) +{ + if (!streq (keyword, key)) + return false; + + const char *startptr = skip_spaces (line + strlen (key)); + const char *endptr; + *value = strtoulst (startptr, &endptr, 0); + if (endptr == startptr) + { + warning (_("Error parsing {s,}maps file '%s' number"), + maps_filename.c_str ()); + } + return true; +} + /* Helper function to parse the contents of /proc//smaps into a data structure, for easy access. @@ -1456,6 +1493,8 @@ parse_smaps_data (const char *data, int has_anonymous = 0; int mapping_anon_p; int mapping_file_p; + ULONGEST rss = -1; + ULONGEST swap = -1; memset (&v, 0, sizeof (v)); struct mapping m = read_mapping (line); @@ -1513,6 +1552,16 @@ parse_smaps_data (const char *data, else if (streq (keyword, "VmFlags:")) decode_vmflags (line, &v); + if (parse_smaps_key_value (keyword, line, "Rss:", + maps_filename, + &rss)) + continue; + + if (parse_smaps_key_value (keyword, line, "Swap:", + maps_filename, + &swap)) + continue; + if (streq (keyword, "AnonHugePages:") || streq (keyword, "Anonymous:")) { @@ -1562,6 +1611,8 @@ parse_smaps_data (const char *data, map.mapping_file_p = mapping_file_p? true : false; map.offset = m.offset; map.inode = m.inode; + map.rss = rss; + map.swap = swap; smaps.emplace_back (map); } @@ -1692,12 +1743,23 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch, for (const struct smaps_data &map : smaps) { /* Invoke the callback function to create the corefile segment. */ - if (should_dump_mapping_p (filterflags, map) - && !func (map.start_address, map.end_address - map.start_address, - map.offset, map.read, map.write, map.exec, - /* MODIFIED is true because we want to dump the mapping. */ - true, map.vmflags.memory_tagging != 0, map.filename)) - return false; + if (should_dump_mapping_p (filterflags, map)) + { + /* If this is an anonymous PROT_NONE private mapping, check + if the mapping has any physical memory backing. If not, + then we know that reading it would just yield zeroes, so + we can later skip reading it. */ + bool hole = (map.mapping_anon_p && map.priv + && !map.read && !map.write && !map.exec + && map.rss == 0 && map.swap == 0); + if (!func (map.start_address, map.end_address - map.start_address, + map.offset, map.read, map.write, map.exec, + /* MODIFIED is true because we want to dump the + mapping. */ + true, map.vmflags.memory_tagging != 0, hole, + map.filename)) + return false; + } } return true; @@ -1712,8 +1774,9 @@ linux_find_memory_regions (struct gdbarch *gdbarch, { auto cb = [&] (ULONGEST vaddr, ULONGEST size, ULONGEST offset, bool read, bool write, bool exec, bool modified, bool memory_tagged, - const std::string &filename) - { return func (vaddr, size, read, write, exec, modified, memory_tagged); }; + bool hole, const std::string &filename) + { return func (vaddr, size, read, write, exec, modified, memory_tagged, + hole); }; return linux_find_memory_regions_full (gdbarch, dump_mapping_p, cb); } @@ -1741,7 +1804,7 @@ linux_make_mappings_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, auto cb = [&] (ULONGEST vaddr, ULONGEST size, ULONGEST offset, bool read, bool write, bool exec, bool modified, bool memory_tagged, - const std::string &filename) + bool hole, const std::string &filename) { gdb_assert (filename.length () > 0); diff --git a/gdb/netbsd-nat.c b/gdb/netbsd-nat.c index 36bd91f968c..9eb1e3081f8 100644 --- a/gdb/netbsd-nat.c +++ b/gdb/netbsd-nat.c @@ -258,7 +258,7 @@ nbsd_nat_target::find_memory_regions (find_memory_region_ftype func) Pass MODIFIED as true, we do not know the real modification state. */ func (kve->kve_start, size, kve->kve_protection & KVME_PROT_READ, kve->kve_protection & KVME_PROT_WRITE, - kve->kve_protection & KVME_PROT_EXEC, true, false); + kve->kve_protection & KVME_PROT_EXEC, true, false, false); } return true; } diff --git a/gdb/testsuite/gdb.base/gcore-anon-priv-protnone.c b/gdb/testsuite/gdb.base/gcore-anon-priv-protnone.c new file mode 100644 index 00000000000..38cd5da4fb0 --- /dev/null +++ b/gdb/testsuite/gdb.base/gcore-anon-priv-protnone.c @@ -0,0 +1,60 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2026 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 . */ + +#include +#include +#include +#include +#include + +void +done () +{ +} + +void *mmapped_data; + +int +main () +{ + /* 10 pages on most systems. */ + size_t sz = 4096 * 10; + + /* Allocate anonymous memory. */ + mmapped_data = mmap (NULL, sz, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mmapped_data == MAP_FAILED) + { + perror ("mmap"); + return 1; + } + +#ifdef FILL_WITH_DATA + /* Fill with a recognizable pattern. */ + memset (mmapped_data, 0xab, sz); +#endif + + /* Remove all permissions. */ + if (mprotect (mmapped_data, sz, PROT_NONE) == -1) + { + perror ("mprotect"); + return 1; + } + + done (); + return 0; +} diff --git a/gdb/testsuite/gdb.base/gcore-anon-priv-protnone.exp b/gdb/testsuite/gdb.base/gcore-anon-priv-protnone.exp new file mode 100644 index 00000000000..7f0c0f8366b --- /dev/null +++ b/gdb/testsuite/gdb.base/gcore-anon-priv-protnone.exp @@ -0,0 +1,126 @@ +# Copyright 2026 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 . + +# Make sure that: +# +# data mode - gcore dumps 'PROT_NONE anonymous private mappings that +# have resident data' as segments with data. +# +# nodata mode - gcore dumps 'PROT_NONE anonymous private mappings that +# have no resident data' as segments with no data (not even zeroes). +# +# Also make sure that GDB is able to read back the produced cores and +# read the contents of the mappings. In nodata mode, GDB should read +# back zeroes. + +standard_testfile + +# MODE is either data or nodata. +proc test {mode} { + global srcfile testfile binfile + + set options {debug} + if { $mode == "data" } { + lappend options "additional_flags=-DFILL_WITH_DATA" + } + + if { [prepare_for_testing "failed to prepare" ${testfile}-$mode ${srcfile} $options] } { + return + } + + # Get the core into the output directory. + set_inferior_cwd_to_output_dir + + if {![runto done]} { + return + } + + if { $mode == "data" } { + set val "0xab" + } else { + set val "0x0" + } + + gdb_test "p/x *(unsigned char *) mmapped_data@40960" \ + "\\{$val \\}" \ + "access mmapped data, live" + + set corefile [standard_output_file ${binfile}-$mode.corefile] + gdb_gcore_cmd "$corefile" "gcore corefile" + + gdb_test "core $corefile" \ + "Core was generated by.*" \ + "load corefile" \ + "A program is being debugged already. Kill it. .y or n. " \ + "y" + + gdb_test "p/x *(unsigned char *) mmapped_data@40960" \ + "\\{$val \\}" \ + "access mmapped data, core" + + # Use readelf to make sure the load segment for our mapping in the + # core file looks like what we want it to. + + # Get the address and strip the '0x'. + set mapped_addr_no_0x "UNKNOWN" + gdb_test_multiple "print/x mmapped_data" "get address" { + -re -wrap " = 0x(\[0-9a-f\]+).*" { + set mapped_addr_no_0x $expect_out(1,string) + pass "$gdb_test_name" + } + } + + # Construct a regex that matches "0x" followed by optional leading + # zeroes, then our specific hex address. For example, if + # mapped_addr_no_0x is 7ffff7fb3000, this matches + # 0x00007ffff7fb3000 and 0x7ffff7fb3000. + set mapped_addr_re "0x0*${mapped_addr_no_0x}" + + set readelf_program [gdb_find_readelf] + set cmd [list $readelf_program -lW $corefile] + set res [catch {exec {*}$cmd} output] + verbose -log "running: $cmd" + verbose -log "result: $res" + verbose -log "output: $output" + if { $res == 0 } { + # Look for LOAD up to MemSiz, and capture FileSiz and MemSiz, in: + # + # Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align + # + # Where VirtAddr is our mapping's address. + set ws "\[\t \]+" + set p_header_re \ + "LOAD${ws}$::hex${ws}${mapped_addr_re}${ws}$::hex${ws}($::hex)${ws}($::hex)${ws}" + set res [regexp $p_header_re $output full filesiz memsiz] + set test "segment has expected sizes" + if { $res } { + verbose -log "res = $res" + verbose -log "filesiz: $filesiz" + verbose -log "memsiz: $memsiz" + + if { $mode == "data" } { + gdb_assert { $filesiz == $memsiz && $filesiz != 0 } $test + } else { + gdb_assert { $filesiz == 0 } $test + } + } else { + fail "$test (regexp failed)" + } + } +} + +foreach_with_prefix mode { "data" "nodata" } { + test $mode +}