mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
Compare commits
7 Commits
1ae9fa5c60
...
users/abur
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e93fc16e0 | ||
|
|
7a3bbd74c8 | ||
|
|
add5cd03c6 | ||
|
|
d96d2aec84 | ||
|
|
f6957916d5 | ||
|
|
bb71410f3a | ||
|
|
a73fbd7eba |
131
gdb/linux-tdep.c
131
gdb/linux-tdep.c
@@ -630,9 +630,9 @@ mapping_is_anonymous_p (const char *filename)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return 0 if the memory mapping (which is related to FILTERFLAGS, V,
|
||||
MAYBE_PRIVATE_P, MAPPING_ANONYMOUS_P, ADDR and OFFSET) should not
|
||||
be dumped, or greater than 0 if it should.
|
||||
/* Return 0 if the memory mapping represented by MAP should not be dumped,
|
||||
or greater than 0 if it should. FILTERFLAGS guides which mappings
|
||||
should be dumped.
|
||||
|
||||
In a nutshell, this is the logic that we follow in order to decide
|
||||
if a mapping should be dumped or not.
|
||||
@@ -677,11 +677,14 @@ mapping_is_anonymous_p (const char *filename)
|
||||
header (of a DSO or an executable, for example). If it is, and
|
||||
if the user is interested in dump it, then we should dump it. */
|
||||
|
||||
static int
|
||||
dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
int maybe_private_p, int mapping_anon_p, int mapping_file_p,
|
||||
const char *filename, ULONGEST addr, ULONGEST offset)
|
||||
static bool
|
||||
dump_mapping_p (filter_flags filterflags, const smaps_data &map)
|
||||
{
|
||||
/* Older Linux kernels did not support the "Anonymous:" counter.
|
||||
If it is missing, we can't be sure what to dump, so dump everything. */
|
||||
if (!map.has_anonymous)
|
||||
return true;
|
||||
|
||||
/* Initially, we trust in what we received from our caller. This
|
||||
value may not be very precise (i.e., it was probably gathered
|
||||
from the permission line in the /proc/PID/smaps list, which
|
||||
@@ -689,41 +692,42 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
what we have until we take a look at the "VmFlags:" field
|
||||
(assuming that the version of the Linux kernel being used
|
||||
supports it, of course). */
|
||||
int private_p = maybe_private_p;
|
||||
int dump_p;
|
||||
int private_p = map.priv;
|
||||
|
||||
/* We always dump vDSO and vsyscall mappings, because it's likely that
|
||||
there'll be no file to read the contents from at core load time.
|
||||
The kernel does the same. */
|
||||
if (strcmp ("[vdso]", filename) == 0
|
||||
|| strcmp ("[vsyscall]", filename) == 0)
|
||||
return 1;
|
||||
if (map.filename == "[vdso]" || map.filename == "[vsyscall]")
|
||||
return true;
|
||||
|
||||
if (v->initialized_p)
|
||||
if (map.vmflags.initialized_p)
|
||||
{
|
||||
/* We never dump I/O mappings. */
|
||||
if (v->io_page)
|
||||
return 0;
|
||||
if (map.vmflags.io_page)
|
||||
return false;
|
||||
|
||||
/* Check if we should exclude this mapping. */
|
||||
if (!dump_excluded_mappings && v->exclude_coredump)
|
||||
return 0;
|
||||
if (!dump_excluded_mappings && map.vmflags.exclude_coredump)
|
||||
return false;
|
||||
|
||||
/* Update our notion of whether this mapping is shared or
|
||||
private based on a trustworthy value. */
|
||||
private_p = !v->shared_mapping;
|
||||
private_p = !map.vmflags.shared_mapping;
|
||||
|
||||
/* HugeTLB checking. */
|
||||
if (v->uses_huge_tlb)
|
||||
if (map.vmflags.uses_huge_tlb)
|
||||
{
|
||||
if ((private_p && (filterflags & COREFILTER_HUGETLB_PRIVATE))
|
||||
|| (!private_p && (filterflags & COREFILTER_HUGETLB_SHARED)))
|
||||
return 1;
|
||||
return true;
|
||||
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int mapping_anon_p = map.mapping_anon_p;
|
||||
int mapping_file_p = map.mapping_file_p;
|
||||
bool dump_p;
|
||||
if (private_p)
|
||||
{
|
||||
if (mapping_anon_p && mapping_file_p)
|
||||
@@ -763,7 +767,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
|
||||
A mapping contains an ELF header if it is a private mapping, its
|
||||
offset is zero, and its first word is ELFMAG. */
|
||||
if (!dump_p && private_p && offset == 0
|
||||
if (!dump_p && private_p && map.offset == 0
|
||||
&& (filterflags & COREFILTER_ELF_HEADERS) != 0)
|
||||
{
|
||||
/* Useful define specifying the size of the ELF magical
|
||||
@@ -774,7 +778,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
|
||||
/* Let's check if we have an ELF header. */
|
||||
gdb_byte h[SELFMAG];
|
||||
if (target_read_memory (addr, h, SELFMAG) == 0)
|
||||
if (target_read_memory (map.start_address, h, SELFMAG) == 0)
|
||||
{
|
||||
/* The EI_MAG* and ELFMAG* constants come from
|
||||
<elf/common.h>. */
|
||||
@@ -783,7 +787,7 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
{
|
||||
/* This mapping contains an ELF header, so we
|
||||
should dump it. */
|
||||
dump_p = 1;
|
||||
dump_p = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,20 +798,24 @@ dump_mapping_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
/* As above, but return true only when we should dump the NT_FILE
|
||||
entry. */
|
||||
|
||||
static int
|
||||
dump_note_entry_p (filter_flags filterflags, const struct smaps_vmflags *v,
|
||||
int maybe_private_p, int mapping_anon_p, int mapping_file_p,
|
||||
const char *filename, ULONGEST addr, ULONGEST offset)
|
||||
static bool
|
||||
dump_note_entry_p (filter_flags filterflags, const smaps_data &map)
|
||||
{
|
||||
/* vDSO and vsyscall mappings will end up in the core file. Don't
|
||||
put them in the NT_FILE note. */
|
||||
if (strcmp ("[vdso]", filename) == 0
|
||||
|| strcmp ("[vsyscall]", filename) == 0)
|
||||
return 0;
|
||||
/* No NT_FILE entry for mappings with no filename. */
|
||||
if (map.filename.length () == 0)
|
||||
return false;
|
||||
|
||||
/* Special kernel mappings, those with names like '[vdso]' and
|
||||
'[vsyscall]' will be placed in the core file, but shouldn't get an
|
||||
NT_FILE entry. These special mappings all have a zero inode. */
|
||||
if (map.inode == 0
|
||||
&& map.filename.front () == '['
|
||||
&& map.filename.back () == ']')
|
||||
return false;
|
||||
|
||||
/* Otherwise, any other file-based mapping should be placed in the
|
||||
note. */
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Implement the "info proc" command. */
|
||||
@@ -1314,21 +1322,15 @@ linux_core_xfer_siginfo (struct gdbarch *gdbarch, gdb_byte *readbuf,
|
||||
}
|
||||
|
||||
typedef int linux_find_memory_region_ftype (ULONGEST vaddr, ULONGEST size,
|
||||
ULONGEST offset, ULONGEST inode,
|
||||
ULONGEST offset,
|
||||
int read, int write,
|
||||
int exec, int modified,
|
||||
bool memory_tagged,
|
||||
const char *filename,
|
||||
const std::string &filename,
|
||||
void *data);
|
||||
|
||||
typedef int linux_dump_mapping_p_ftype (filter_flags filterflags,
|
||||
const struct smaps_vmflags *v,
|
||||
int maybe_private_p,
|
||||
int mapping_anon_p,
|
||||
int mapping_file_p,
|
||||
const char *filename,
|
||||
ULONGEST addr,
|
||||
ULONGEST offset);
|
||||
typedef bool linux_dump_mapping_p_ftype (filter_flags filterflags,
|
||||
const struct smaps_data &map);
|
||||
|
||||
/* Helper function to parse the contents of /proc/<pid>/smaps into a data
|
||||
structure, for easy access.
|
||||
@@ -1590,35 +1592,15 @@ linux_find_memory_regions_full (struct gdbarch *gdbarch,
|
||||
|
||||
for (const struct smaps_data &map : smaps)
|
||||
{
|
||||
int should_dump_p = 0;
|
||||
|
||||
if (map.has_anonymous)
|
||||
{
|
||||
should_dump_p
|
||||
= should_dump_mapping_p (filterflags, &map.vmflags,
|
||||
map.priv,
|
||||
map.mapping_anon_p,
|
||||
map.mapping_file_p,
|
||||
map.filename.c_str (),
|
||||
map.start_address,
|
||||
map.offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Older Linux kernels did not support the "Anonymous:" counter.
|
||||
If it is missing, we can't be sure - dump all the pages. */
|
||||
should_dump_p = 1;
|
||||
}
|
||||
|
||||
/* Invoke the callback function to create the corefile segment. */
|
||||
if (should_dump_p)
|
||||
if (should_dump_mapping_p (filterflags, map))
|
||||
{
|
||||
func (map.start_address, map.end_address - map.start_address,
|
||||
map.offset, map.inode, map.read, map.write, map.exec,
|
||||
map.offset, map.read, map.write, map.exec,
|
||||
1, /* MODIFIED is true because we want to dump
|
||||
the mapping. */
|
||||
map.vmflags.memory_tagging != 0,
|
||||
map.filename.c_str (), obfd);
|
||||
map.filename, obfd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1644,10 +1626,10 @@ struct linux_find_memory_regions_data
|
||||
|
||||
static int
|
||||
linux_find_memory_regions_thunk (ULONGEST vaddr, ULONGEST size,
|
||||
ULONGEST offset, ULONGEST inode,
|
||||
ULONGEST offset,
|
||||
int read, int write, int exec, int modified,
|
||||
bool memory_tagged,
|
||||
const char *filename, void *arg)
|
||||
const std::string &filename, void *arg)
|
||||
{
|
||||
struct linux_find_memory_regions_data *data
|
||||
= (struct linux_find_memory_regions_data *) arg;
|
||||
@@ -1693,8 +1675,6 @@ struct linux_make_mappings_data
|
||||
struct type *long_type;
|
||||
};
|
||||
|
||||
static linux_find_memory_region_ftype linux_make_mappings_callback;
|
||||
|
||||
/* A callback for linux_find_memory_regions_full that updates the
|
||||
mappings data for linux_make_mappings_corefile_notes.
|
||||
|
||||
@@ -1703,17 +1683,16 @@ static linux_find_memory_region_ftype linux_make_mappings_callback;
|
||||
|
||||
static int
|
||||
linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
|
||||
ULONGEST offset, ULONGEST inode,
|
||||
ULONGEST offset,
|
||||
int read, int write, int exec, int modified,
|
||||
bool memory_tagged,
|
||||
const char *filename, void *data)
|
||||
const std::string &filename, void *data)
|
||||
{
|
||||
struct linux_make_mappings_data *map_data
|
||||
= (struct linux_make_mappings_data *) data;
|
||||
gdb_byte buf[sizeof (ULONGEST)];
|
||||
|
||||
if (*filename == '\0' || inode == 0)
|
||||
return 0;
|
||||
gdb_assert (filename.length () > 0);
|
||||
|
||||
++map_data->file_count;
|
||||
|
||||
@@ -1724,7 +1703,7 @@ linux_make_mappings_callback (ULONGEST vaddr, ULONGEST size,
|
||||
pack_long (buf, map_data->long_type, offset);
|
||||
obstack_grow (map_data->data_obstack, buf, map_data->long_type->length ());
|
||||
|
||||
obstack_grow_str0 (map_data->filename_obstack, filename);
|
||||
obstack_grow_str0 (map_data->filename_obstack, filename.c_str ());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
441
gdb/testsuite/gdb.base/corefile-shmem-zero-id-lib.c
Normal file
441
gdb/testsuite/gdb.base/corefile-shmem-zero-id-lib.c
Normal file
@@ -0,0 +1,441 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2025 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 contains a library that can be preloaded into GDB on Linux
|
||||
using the LD_PRELOAD technique.
|
||||
|
||||
The library intercepts calls to OPEN, CLOSE, READ, and PREAD in order to
|
||||
fake the inode number of a shared memory mapping.
|
||||
|
||||
When GDB creates a core file (e.g. with the 'gcore' command), then
|
||||
shared memory mappings should be included in the generated core file.
|
||||
|
||||
The 'id' for the shared memory mapping shares the inode slot in the
|
||||
/proc/PID/smaps file, which is what GDB consults to decide which
|
||||
mappings should be included in the core file.
|
||||
|
||||
It is possible for a shared memory mapping to have an 'id' of zero.
|
||||
|
||||
At one point there was a bug in GDB where mappings with an inode of zero
|
||||
would not be included in the generated core file. This meant that most
|
||||
shared memory mappings would be included in the generated core file,
|
||||
but, if a shared memory mapping happened to get an 'id' of zero, then,
|
||||
because this would appear as a zero inode in the smaps file, this shared
|
||||
memory mapping would be excluded from the generated core file.
|
||||
|
||||
This preload library spots when GDB opens a /proc/PID/smaps file and
|
||||
immediately copies the contents of this file into an internal buffer.
|
||||
The buffer is then scanned looking for a shared memory mapping, and, if
|
||||
a shared memory mapping is found, its 'id' (in the inode position) is
|
||||
changed to zero.
|
||||
|
||||
Calls to read/pread are intercepted, and attempts to read from the smaps
|
||||
file are then served from the modified buffer contents.
|
||||
|
||||
The close calls are monitored and, when the smaps file is closed, the
|
||||
internal buffer is released.
|
||||
|
||||
This works with GDB (currently) because the requirements for access to
|
||||
the smaps file are pretty simple. GDB opens the file and grabs the
|
||||
entire contents with a single pread call and a large buffer. There's no
|
||||
seeking within the file or anything like that.
|
||||
|
||||
The intention is that this library is preloaded into a GDB session which
|
||||
is then used to start an inferior and generate a core file. GDB will
|
||||
then see the zero inode for the shared memory mapping and should, if the
|
||||
bug is correctly fixed, still add the shared memory mapping to the
|
||||
generated core file. */
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* Logging. */
|
||||
|
||||
static void
|
||||
log_msg (const char *fmt, ...)
|
||||
{
|
||||
#ifdef LOGGING
|
||||
va_list ap;
|
||||
|
||||
va_start (ap, fmt);
|
||||
vfprintf (stderr, fmt, ap);
|
||||
va_end (ap);
|
||||
#endif /* LOGGING */
|
||||
}
|
||||
|
||||
/* Error handling, message and exit. */
|
||||
|
||||
static void
|
||||
error (const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start (ap, fmt);
|
||||
vfprintf (stderr, fmt, ap);
|
||||
va_end (ap);
|
||||
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/* The type of the open() function. */
|
||||
typedef int (*open_func_type)(const char *pathname, int flags, ...);
|
||||
|
||||
/* The type of the close() function. */
|
||||
typedef int (*close_func_type)(int fd);
|
||||
|
||||
/* The type of the read() function. */
|
||||
typedef ssize_t (*read_func_type)(int fd, void *buf, size_t count);
|
||||
|
||||
/* The type of the pread() function. */
|
||||
typedef ssize_t (*pread_func_type) (int fd, void *buf, size_t count, off_t offset);
|
||||
|
||||
/* Structure that holds information about a /proc/PID/smaps file that has
|
||||
been opened. */
|
||||
struct interesting_file
|
||||
{
|
||||
/* The file descriptor for the opened file. */
|
||||
int fd;
|
||||
|
||||
/* The read offset within the file. Set to zero when the file is
|
||||
opened. Any 'read' calls will update this offset. */
|
||||
size_t offset;
|
||||
|
||||
/* The size of the contents within the buffer. This is not the total
|
||||
buffer size (which might be larger). Attempts to read beyond SIZE
|
||||
indicate an attempt to read beyond the end of the file. */
|
||||
size_t size;
|
||||
|
||||
/* The (possibly modified) contents of the file. */
|
||||
char *content;
|
||||
};
|
||||
|
||||
/* We only track a single interesting file. Currently, for the use case
|
||||
we imagine, GDB will only ever open one /proc/PID/smaps file at once. */
|
||||
struct interesting_file the_file = { -1, 0, 0, NULL };
|
||||
|
||||
/* Update the contents of the global THE_FILE buffer. It is assumed that
|
||||
the file contents have already been loaded into THE_FILE's content
|
||||
buffer.
|
||||
|
||||
Look for any lines that represent a shared memory mapping and modify
|
||||
the inode field (which holds the shared memory id) to be zero. */
|
||||
static void
|
||||
update_file_content_buffer (void)
|
||||
{
|
||||
assert (the_file.content != NULL);
|
||||
|
||||
char *start = the_file.content;
|
||||
do
|
||||
{
|
||||
/* Every line, even the last one, ends with a newline. */
|
||||
char *end = strchrnul (start, '\n');
|
||||
assert (end != NULL);
|
||||
assert (*end != '\0');
|
||||
|
||||
/* Attribute lines start with an uppercase letter. The lines we want
|
||||
to modify should start with a lower case hex character,
|
||||
i.e. [0-9a-f]. Also, every line that we want to consider should
|
||||
be long enough, but just in case, check the longest possible
|
||||
filename that we care about. */
|
||||
if (isxdigit (*start) && (isdigit (*start) || islower (*start))
|
||||
&& (end - start) > 23)
|
||||
{
|
||||
/* There are two possible filenames that we look for:
|
||||
/SYSV%08x
|
||||
/SYSV%08x (deleted)
|
||||
The END pointer is pointing to the first character after the
|
||||
filename.
|
||||
|
||||
Setup OFFSET to be the offset from END to the start of the
|
||||
filename. As we check the filename we set OFFSET to 0 if the
|
||||
filename doesn't match one of the expected patterns. */
|
||||
size_t offset;
|
||||
if (strncmp ((end - 13), "/SYSV", 5) == 0)
|
||||
offset = 13;
|
||||
else if (strncmp ((end - 23), "/SYSV", 5) == 0)
|
||||
{
|
||||
if (strncmp ((end - 10), " (deleted)", 10) == 0)
|
||||
offset = 23;
|
||||
else
|
||||
offset = 0;
|
||||
}
|
||||
else
|
||||
offset = 0;
|
||||
|
||||
for (int i = 0; i < 8 && offset != 0; ++i)
|
||||
{
|
||||
if (!isdigit (*(end - offset + 5 + i)))
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
/* If OFFSET is non-zero then the filename on this line looks
|
||||
like a shared memory mapping, and OFFSET is the offset from
|
||||
END to the first character of the filename. */
|
||||
if (offset != 0)
|
||||
{
|
||||
log_msg ("[LD_PRELOAD] shared memory entry: %.*s\n",
|
||||
offset, (end - offset));
|
||||
|
||||
/* Set PTR to the first character before the filename. This
|
||||
should be a white space character. */
|
||||
char *ptr = end - offset - 1;
|
||||
assert (isspace (*ptr));
|
||||
|
||||
/* Walk backwards until we find the inode field. */
|
||||
while (isspace (*ptr))
|
||||
--ptr;
|
||||
|
||||
/* Now replace every character in the inode field, except the
|
||||
first one, with a space character. */
|
||||
while (!isspace (*(ptr - 1)))
|
||||
{
|
||||
assert (isdigit (*ptr));
|
||||
*ptr = ' ';
|
||||
--ptr;
|
||||
}
|
||||
|
||||
/* Replace the first character with '0'. */
|
||||
assert (isdigit (*ptr));
|
||||
*ptr = '0';
|
||||
|
||||
/* This print is checked for from GDB. */
|
||||
printf ("[LD_PRELOAD] updated a shared memory mapping\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* Update START to point to the next line. The last line of the
|
||||
file will be empty. */
|
||||
assert (*end == '\n');
|
||||
start = end;
|
||||
while (*start == '\n')
|
||||
++start;
|
||||
}
|
||||
while (*start != '\0');
|
||||
}
|
||||
|
||||
/* Intercept calls to 'open'. If this is an attempt to open a
|
||||
/proc/PID/smaps file then intercept it, load the file contents into a
|
||||
buffer and update the file contents. For all other open requests, just
|
||||
forward to the real open function. */
|
||||
int
|
||||
open (const char *pathname, int flags, ...)
|
||||
{
|
||||
/* Pointer to the real open function. */
|
||||
static open_func_type real_open = NULL;
|
||||
|
||||
/* Mode is only used if the O_CREAT flag is set in FLAGS. */
|
||||
mode_t mode = 0;
|
||||
|
||||
/* Set true if this is a /proc/PID/smaps file. */
|
||||
bool is_interesting = false;
|
||||
|
||||
/* Check if O_CREAT is in flags. If it is, get the mode. */
|
||||
if (flags & O_CREAT) {
|
||||
va_list args;
|
||||
va_start (args, flags);
|
||||
mode = va_arg (args, mode_t);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
/* Is this a '/proc/PID/smaps' filename? */
|
||||
if (the_file.fd == -1 && strncmp (pathname, "/proc/", 6) == 0)
|
||||
{
|
||||
int idx = 6;
|
||||
while (isdigit (pathname[idx]))
|
||||
idx++;
|
||||
if (idx > 6 && strcmp (&pathname[idx], "/smaps") == 0)
|
||||
is_interesting = true;
|
||||
}
|
||||
|
||||
/* Debug. */
|
||||
if (is_interesting)
|
||||
log_msg ("[LD_PRELOAD] Opening file: %s\n", pathname);
|
||||
|
||||
/* Make sure we have a pointer to the real open() function. */
|
||||
if (real_open == NULL)
|
||||
{
|
||||
/* Get the address of the real open() function. */
|
||||
real_open = (open_func_type) dlsym (RTLD_NEXT, "open");
|
||||
if (real_open == NULL)
|
||||
error ("[LD_PRELOAD] dlsym() error for 'open': %s\n", dlerror ());
|
||||
}
|
||||
|
||||
/* Call the original open() function with the provided arguments. */
|
||||
int res = -1;
|
||||
if (flags & O_CREAT)
|
||||
res = real_open (pathname, flags, mode);
|
||||
else
|
||||
res = real_open (pathname, flags);
|
||||
|
||||
if (is_interesting)
|
||||
{
|
||||
#define BLOCK_SIZE 1024
|
||||
/* Slurp contents into a local buffer. */
|
||||
size_t buffer_size = 1024;
|
||||
size_t offset = 0;
|
||||
|
||||
assert (the_file.size == 0);
|
||||
assert (the_file.content == NULL);
|
||||
assert (the_file.fd == -1);
|
||||
assert (the_file.offset == 0);
|
||||
|
||||
do
|
||||
{
|
||||
the_file.content = (char *) realloc (the_file.content, buffer_size);
|
||||
if (the_file.content == NULL)
|
||||
error ("[LD_PRELOAD] Failed allocating memory: %s\n", strerror (errno));
|
||||
|
||||
ssize_t bytes_read = read (res, the_file.content + offset, BLOCK_SIZE);
|
||||
if (bytes_read == -1)
|
||||
error ("[LD_PRELOAD] Failed reading file: %s\n", strerror (errno));
|
||||
|
||||
the_file.size += bytes_read;
|
||||
|
||||
if (bytes_read < BLOCK_SIZE)
|
||||
break;
|
||||
|
||||
offset += BLOCK_SIZE;
|
||||
buffer_size += BLOCK_SIZE;
|
||||
}
|
||||
while (true);
|
||||
|
||||
/* Add a null terminator. This makes the update easier. We know
|
||||
there will be space because we only break out of the loop above
|
||||
when the last read returns less than BLOCK_SIZE bytes. This means
|
||||
we allocated an extra BLOCK_SIZE bytes, but didn't fill them all.
|
||||
This means there must be at least 1 byte available for the null. */
|
||||
the_file.content[the_file.size] = '\0';
|
||||
|
||||
/* Reset the seek pointer. */
|
||||
if (lseek (res, 0, SEEK_SET) == (off_t) -1)
|
||||
error ("[LD_PRELOAD] Failed to lseek in file: %s\n", strerror (errno));
|
||||
|
||||
/* Record the file descriptor, this is used in read, pread, and close
|
||||
in order to spot when we need to intercept the call. */
|
||||
the_file.fd = res;
|
||||
|
||||
update_file_content_buffer ();
|
||||
#undef BLOCK_SIZE
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Intercept the 'close' function. If this is a previously opened
|
||||
interesting file then clean up. Otherwise, forward to the normal close
|
||||
function. */
|
||||
int
|
||||
close (int fd)
|
||||
{
|
||||
static close_func_type real_close = NULL;
|
||||
|
||||
if (fd == the_file.fd)
|
||||
{
|
||||
the_file.fd = -1;
|
||||
free (the_file.content);
|
||||
the_file.content = NULL;
|
||||
the_file.offset = 0;
|
||||
the_file.size = 0;
|
||||
log_msg ("[LD_PRELOAD] Closing file.\n");
|
||||
}
|
||||
|
||||
/* Make sure we have a pointer to the real open() function. */
|
||||
if (real_close == NULL)
|
||||
{
|
||||
/* Get the address of the real open() function. */
|
||||
real_close = (close_func_type) dlsym (RTLD_NEXT, "close");
|
||||
if (real_close == NULL)
|
||||
error ("[LD_PRELOAD] dlsym() error for 'close': %s\n", dlerror ());
|
||||
}
|
||||
|
||||
return real_close (fd);
|
||||
}
|
||||
|
||||
/* Intercept 'pread' calls. If this is a pread from a previously opened
|
||||
interesting file, then read from the in memory buffer. Otherwise,
|
||||
forward to the real pread function. */
|
||||
ssize_t
|
||||
pread (int fd, void *buf, size_t count, off_t offset)
|
||||
{
|
||||
static pread_func_type real_pread = NULL;
|
||||
|
||||
if (fd == the_file.fd)
|
||||
{
|
||||
size_t max;
|
||||
|
||||
if (offset > the_file.size)
|
||||
max = 0;
|
||||
else
|
||||
max = the_file.size - offset;
|
||||
if (count > max)
|
||||
count = max;
|
||||
|
||||
memcpy (buf, the_file.content + offset, count);
|
||||
log_msg ("[LD_PRELOAD] Read from file.\n");
|
||||
return count;
|
||||
}
|
||||
|
||||
if (real_pread == NULL)
|
||||
{
|
||||
/* Get the address of the real read() function. */
|
||||
real_pread = (pread_func_type) dlsym (RTLD_NEXT, "pread");
|
||||
if (real_pread == NULL)
|
||||
error ("[LD_PRELOAD] dlsym() error for 'pread': %s\n", dlerror ());
|
||||
}
|
||||
|
||||
return real_pread (fd, buf, count, offset);
|
||||
}
|
||||
|
||||
/* Intercept 'read' calls. If this is a read from a previously opened
|
||||
interesting file, then read from the in memory buffer. Otherwise,
|
||||
forward to the real read function. */
|
||||
ssize_t
|
||||
read (int fd, void *buf, size_t count)
|
||||
{
|
||||
static read_func_type real_read = NULL;
|
||||
|
||||
if (fd == the_file.fd)
|
||||
{
|
||||
ssize_t bytes_read = pread (fd, buf, count, the_file.offset);
|
||||
if (bytes_read > 0)
|
||||
the_file.offset += bytes_read;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
if (real_read == NULL)
|
||||
{
|
||||
/* Get the address of the real read() function. */
|
||||
real_read = (read_func_type) dlsym (RTLD_NEXT, "read");
|
||||
if (real_read == NULL)
|
||||
error ("[LD_PRELOAD] dlsym() error for 'read': %s\n", dlerror ());
|
||||
}
|
||||
|
||||
return real_read (fd, buf, count);
|
||||
}
|
||||
63
gdb/testsuite/gdb.base/corefile-shmem-zero-id.c
Normal file
63
gdb/testsuite/gdb.base/corefile-shmem-zero-id.c
Normal file
@@ -0,0 +1,63 @@
|
||||
/* This testcase is part of GDB, the GNU debugger.
|
||||
|
||||
Copyright 2025 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 <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
|
||||
void
|
||||
breakpt (void)
|
||||
{
|
||||
/* Nothing. */
|
||||
}
|
||||
|
||||
int
|
||||
main (void)
|
||||
{
|
||||
/* Create a shared memory mapping. */
|
||||
int sid = shmget (IPC_PRIVATE, 0x1000, IPC_CREAT | IPC_EXCL | 0777);
|
||||
if (sid == -1)
|
||||
{
|
||||
perror ("shmget");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
/* Attach the shared memory mapping. */
|
||||
void *addr = shmat (sid, NULL, SHM_RND);
|
||||
if (addr == (void *) -1L)
|
||||
{
|
||||
perror ("shmat");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
breakpt ();
|
||||
|
||||
/* Mark the shared memory mapping as deleted -- once the last user
|
||||
has finished with it. */
|
||||
if (shmctl (sid, IPC_RMID, NULL) != 0)
|
||||
{
|
||||
perror ("shmctl");
|
||||
exit (1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
228
gdb/testsuite/gdb.base/corefile-shmem-zero-id.exp
Normal file
228
gdb/testsuite/gdb.base/corefile-shmem-zero-id.exp
Normal file
@@ -0,0 +1,228 @@
|
||||
# Copyright 2025 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 test script tries to check GDB's ability to create a core file
|
||||
# (e.g. with 'gcore' command) when there's a shared memory mapping
|
||||
# with the id zero.
|
||||
#
|
||||
# Testing this case is hard. Older kernels don't even seem to give
|
||||
# out the shared memory id zero. And on new kernels you still cannot
|
||||
# guarantee to grab the zero id for testing; the id might be in use by
|
||||
# some other process, or the kernel might just not give out that id
|
||||
# for some other reason.
|
||||
#
|
||||
# To figure out which mappings to include in the core file, GDB reads
|
||||
# the /proc/PID/smaps file. There is a field in this file which for
|
||||
# file backed mappings, holds the inode of the file. But for shared
|
||||
# memory mappings this field holds the shared memory id. The problem
|
||||
# was that GDB would ignore any entry in /proc/PID/smaps with an inode
|
||||
# entry of zero, which would catch the shared memory mapping with a
|
||||
# zero id.
|
||||
#
|
||||
# There was an attempt to write a test which spammed out requests for
|
||||
# shared memory mappings and tried to find the one with id zero, but
|
||||
# this was still really unreliable.
|
||||
#
|
||||
# This test takes a different approach. We compile a library which we
|
||||
# preload into the GDB process. This library intercepts calls to
|
||||
# open, close, read, and pread, and watches for an attempt to open the
|
||||
# /proc/PID/smaps file.
|
||||
#
|
||||
# When we see that file being opened, we copy the file contents into a
|
||||
# memory buffer and modify the buffer so that the inode field for any
|
||||
# shared memory mappings is set to zero. We then intercept calls to
|
||||
# read and pread and return results from that in memory buffer.
|
||||
#
|
||||
# The test executable itself create a shared memory mapping (which
|
||||
# might have any id).
|
||||
#
|
||||
# GDB, with the pre-load library in place, start the inferior and then
|
||||
# uses the 'gcore' command to dump a core file. When GDB opens the
|
||||
# smaps file and reads from it, the preload library ensures that GDB
|
||||
# sees an inode of zero.
|
||||
#
|
||||
|
||||
# This test only works on Linux
|
||||
require isnative
|
||||
require {!is_remote host}
|
||||
require {!is_remote target}
|
||||
require {istarget *-linux*}
|
||||
require gcore_cmd_available
|
||||
|
||||
standard_testfile .c -lib.c
|
||||
|
||||
set libfile ${testfile}-lib
|
||||
set libobj [standard_output_file ${libfile}.so]
|
||||
|
||||
# Compile the preload library. We only get away with this as we
|
||||
# limit this test to running when ISNATIVE is true.
|
||||
if { [build_executable "build preload lib" $libobj $srcfile2 \
|
||||
{debug shlib libs=-ldl}] == -1 } {
|
||||
return
|
||||
}
|
||||
|
||||
# Now compile the inferior executable.
|
||||
if {[build_executable "build executable" $testfile $srcfile] == -1} {
|
||||
return
|
||||
}
|
||||
|
||||
# Spawn GDB with LIBOBJ preloaded using LD_PRELOAD.
|
||||
save_vars { env(LD_PRELOAD) env(ASAN_OPTIONS) } {
|
||||
if { ![info exists env(LD_PRELOAD) ]
|
||||
|| $env(LD_PRELOAD) == "" } {
|
||||
set env(LD_PRELOAD) "$libobj"
|
||||
} else {
|
||||
append env(LD_PRELOAD) ":$libobj"
|
||||
}
|
||||
|
||||
# Prevent address sanitizer error:
|
||||
# ASan runtime does not come first in initial library list; you should
|
||||
# either link runtime to your application or manually preload it with
|
||||
# LD_PRELOAD.
|
||||
append_environment_default ASAN_OPTIONS verify_asan_link_order 0
|
||||
|
||||
clean_restart $binfile
|
||||
|
||||
# Start GDB with the modified environment, this means that, when
|
||||
# using remote targets, gdbserver will also use the preload
|
||||
# library.
|
||||
if {![runto_main]} {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
gdb_breakpoint breakpt
|
||||
gdb_continue_to_breakpoint "run to breakpt"
|
||||
|
||||
# Check the /proc/PID/smaps file itself. The call to 'cat' should
|
||||
# inherit the preload library, so should see the modified file
|
||||
# contents. Check that the shared memory mapping line has an id of
|
||||
# zero. This confirms that the preload library is working. If the
|
||||
# preload library breaks then we'll start seeing non-zero shared
|
||||
# memory ids, which always worked, so we'd never know that this test
|
||||
# is broken!
|
||||
#
|
||||
# This check ensures the test is working as expected.
|
||||
set shmem_line_count 0
|
||||
set fixup_line_count 0
|
||||
set inf_pid [get_inferior_pid]
|
||||
gdb_test_multiple "shell cat /proc/${inf_pid}/smaps" "check smaps" {
|
||||
-re "^\\\[LD_PRELOAD\\\] updated a shared memory mapping\r\n" {
|
||||
incr fixup_line_count
|
||||
exp_continue
|
||||
}
|
||||
-re "^\[^\r\n\]+($decimal)\\s+/SYSV\[0-9\]{8}(?: \\(deleted\\))?\r\n" {
|
||||
set id $expect_out(1,string)
|
||||
if { $id == 0 } {
|
||||
incr shmem_line_count
|
||||
}
|
||||
exp_continue
|
||||
}
|
||||
-re "^$gdb_prompt $" {
|
||||
with_test_prefix $gdb_test_name {
|
||||
gdb_assert { $shmem_line_count == 1 } \
|
||||
"single shared memory mapping found"
|
||||
gdb_assert { $fixup_line_count == 1 } \
|
||||
"single fixup line found"
|
||||
}
|
||||
}
|
||||
-re "^\[^\r\n\]+\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
|
||||
# Now generate a core file. This will use the preload library to read
|
||||
# the smaps file. The code below is copied from 'proc gdb_gcore_cmd',
|
||||
# but we don't use that as we also look for a message that is printed
|
||||
# by the LD_PRELOAD library. This is an extra level of check that the
|
||||
# preload library is triggering when needed.
|
||||
set corefile [standard_output_file ${testfile}.core]
|
||||
set saw_ld_preload_msg false
|
||||
set saw_saved_msg false
|
||||
with_timeout_factor 3 {
|
||||
gdb_test_multiple "gcore $corefile" "save core file" {
|
||||
-re "^\\\[LD_PRELOAD\\\] updated a shared memory mapping\r\n" {
|
||||
# GDB actually reads the smaps file multiple times when
|
||||
# creating a core file, so we'll see multiple of these
|
||||
# fixup lines.
|
||||
set saw_ld_preload_msg true
|
||||
exp_continue
|
||||
}
|
||||
-re "^Saved corefile \[^\r\n\]+\r\n" {
|
||||
set saw_saved_msg true
|
||||
exp_continue
|
||||
}
|
||||
-re "^$gdb_prompt $" {
|
||||
with_test_prefix $gdb_test_name {
|
||||
gdb_assert { $saw_saved_msg } \
|
||||
"saw 'Saved corefile' message"
|
||||
|
||||
# If we're using a remote target then the message from
|
||||
# the preload library will go to gdbservers stdout,
|
||||
# not GDB's, so don't check for it.
|
||||
if { [gdb_protocol_is_native] } {
|
||||
gdb_assert { $saw_ld_preload_msg } \
|
||||
"saw LD_PRELOAD message from library"
|
||||
}
|
||||
}
|
||||
}
|
||||
-re "^\[^\r\n\]*\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Restart GDB. This time we are _not_ using the preload library. We
|
||||
# no longer need it as we are only analysing the core file now.
|
||||
clean_restart $binfile
|
||||
|
||||
# Load the core file.
|
||||
gdb_test "core-file $corefile" \
|
||||
"Program terminated with signal SIGTRAP, Trace/breakpoint trap\\..*" \
|
||||
"load core file"
|
||||
|
||||
# Look through the mappings. We _should_ see the shared memory
|
||||
# mapping. We _should_not_ see any of the special '[blah]' style
|
||||
# mappings, e.g. [vdso], [vstack], [vsyscalls], etc.
|
||||
set saw_special_mapping false
|
||||
set saw_shmem_mapping false
|
||||
gdb_test_multiple "info proc mappings" "" {
|
||||
-re "\r\nStart Addr\[^\r\n\]+File\\s*\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
|
||||
-re "^$hex\\s+$hex\\s+$hex\\s+$hex\\s+\\\[\\S+\\\]\\s*\r\n" {
|
||||
set saw_special_mapping true
|
||||
exp_continue
|
||||
}
|
||||
|
||||
-re "^$hex\\s+$hex\\s+$hex\\s+$hex\\s+/SYSV\[0-9\]+ \\(deleted\\)\\s*\r\n" {
|
||||
set saw_shmem_mapping true
|
||||
exp_continue
|
||||
}
|
||||
|
||||
-re "^$hex\\s+$hex\\s+$hex\\s+$hex\[^\r\n\]*\r\n" {
|
||||
exp_continue
|
||||
}
|
||||
|
||||
-re "^$gdb_prompt $" {
|
||||
with_test_prefix $gdb_test_name {
|
||||
gdb_assert { $saw_shmem_mapping } \
|
||||
"check shared memory mapping exists"
|
||||
gdb_assert { !$saw_special_mapping } \
|
||||
"check no special mappings added"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user